Pythonまとめ(2.標準ライブラリその1)

基本用語とインポート

モジュール、パッケージ、ライブラリ

モジュールプログラムの1つのまとまり・実体。ファイルのイメージ。
パッケージモジュールをまとめるもの。ディレクトリのイメージ。
パッケージは特殊なモジュールであり、多くの場合は__init__.pyを持つディレクトリである。
ライブラリモジュール・パッケージを配布用にまとめたもの。
Pythonに付属している標準ライブラリと、追加インストールが必要な外部ライブラリがある。

インポート

Python本体が持たない機能は、自分で実装するか、その機能が実装されたライブラリを使用する。ライブラリはimport文を使用してインポートして使う。このimport文はライブラリを探し、発見したライブラリをPythonファイル内で参照可能な名前に紐付ける。インポートされたライブラリはmoduleクラスなどのオブジェクトになり、moduleオブジェクトを介してライブラリの中のクラスや関数を使用できる。

以下にloggingライブラリをインポートする例を示す。

>>> import logging
>>> type( logging )
<class 'module'>
>>> type(logging.Logger)
<class 'type'>
>>> type(logging.getLevelName)
<class 'function'>

一部機能だけをインポート

from module import ...でライブラリの中の特定のクラスや関数だけをインポートできる。一部の機能だけ必要な場合やモジュール名を省略したい場合などに使われる。

>>> import logging            # loggingをそのままインポート
>>> logging.getLevelName(10)  # そのモジュールの関数を使うときはモジュール名.関数名
>>> 
>>> from logging import getLevelName # loggingの中のgetLevelNameだけをimport
>>> getLevelName(10)                 # 関数名だけでOK

名前を指定してインポート

import文で紐付ける名前はasで指定できる。名前の重複回避や長い名前の省略したい場合などに使われる。

>>> import logging as l
>>> type( l )
<class 'module'>
>>> from logging import Logger as L
>>> type( L )
<class 'type'>

__init__.py

import loggingとした場合、Pythonはlogging/__init__.pyやlogging.pyを探す。__init__.pyも通常通り関数やクラスを定義できるほか、インポート時の挙動を指定できる。たとえば__init__.py以外にもモジュールがある場合、__init__.pyがそれらのモジュールをインポートしており意識せず利用できる場合もあれば、利用者が明示的にインポートしなければならない場合もある。

たとえば、loggingライブラリではlogging/__init__.py以外にlogging/handlers.pyもある。__init__.pyはhandlers.pyをインポートしていないため、handlers.pyの内容を使うには明示的にインポートしなければならない。

>>> from logging import *
>>> type( BaseRotatingHandler )
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'BaseRotatingHandler' is not defined
>>> from logging.handlers import *       # モジュール名まで指定してインポートすれば使える
>>> type( BaseRotatingHandler )
<class 'type'>

インポートパス

import文がPythonが探索するパスはsys.pathで確認できる。以下は見やすいように出力を加工している。

>>> import sys
>>> sys.path
""
"C:\Users\xxxxx\AppData\Local\Programs\Python\Python311\python311.zip"
"C:\Users\xxxxx\AppData\Local\Programs\Python\Python311\DLLs"
"C:\Users\xxxxx\AppData\Local\Programs\Python\Python311\Lib"
"C:\Users\xxxxx\AppData\Local\Programs\Python\Python311"
"C:\Users\xxxxx\AppData\Local\Programs\Python\Python311\Lib\site-packages"

一番上の""はカレントディレクトリを意味している。この並び順に探索されるため、他のライブラリと同名のモジュールがカレントディレクトリにあるとカレントディレクトリが優先される。

相対インポート

現在のディテクトリを基準とした相対パスでもインポートできる。これは自作モジュールのインポートや同じプロジェクト内の他のモジュールのインポートなどで役に立つ。逆にそれ以外の目的では使わないほうが良い。

相対インポートは

同じディレクトリからインポートfrom . import modulename
下位のディレクトリからインポートfrom .directory import modulename
上位のディレクトリからインポートfrom .. import modulename # 1つ上のディレクトリ
from ... import modulename # 2つ上のディレクトリ(以降.1つごとに階層を1つ上がる)

インポートするパスの追加

相対インポートには参照できる範囲に制限がある。

PS C:\Users\xxxxx> py
Python 3.11.4
>>> from .. import Test
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: attempted relative import with no known parent package

これはカレントディレクトリの1つ上のディレクトリからTestライブラリをインポートしようとしたときのエラーである。このような場合は相対パスでのインポートではなく、インポートしたいライブラリがあるパスを追加するべきである。

インポートするパスを追加する方法として3つ挙げる。

sys.pathに直接追加する方法

sys.pathに直接パスを追加できる。この方法はファイル単位でインポートするパスを設定できる一方で、将来的にパスが失われたときに、ファイルを修正しなければならない危険性をはらんでいる。しかしテスト用途など一度だけ使えれば良い場合などであればこれでも十分である。

>>> import sys
>>> sys.path.append(r"C:\Users")
環境変数に追加する方法

環境変数PYTHONPATHにパスを追加できる。この方法はすべてのPythonファイルに適用される。以下はC:\をPYTHONPATHに追加した場合のサンプルである。Windows環境ではカレントディレクトリについで高い優先順位となった。

PS C:\Users\xxxxx> py
Python 3.11.4
>>> import sys
>>> sys.path
['', 'C:\\', ...
site-packagesに追加する方法

sys.pathにsite-packagesという名前のディレクトリが含まれている。このディレクトリは外部ライブラリのインストールなどに使われるディレクトリである。

"C:\Users\xxxxx\AppData\Local\Programs\Python\Python311\Lib\site-packages"

ここに追加するパスを書いたテキストファイル(拡張子PTH)を置くことで、パスを追加できる。この方法も環境変数と同じくすべてのPythonファイルに適用されるが、Pythonのバージョンごとに設定を変えられる(逆に言うとバージョンごとに設定が必要)という点で違いがある。

PTHファイルの中身は以下のようにパスを1行ずつ書いたものである。

D:\files\Python

入出力(IO)

ioモジュールは入出力に関する基本的な機能を提供する。I/Oに関するクラスは抽象基底クラス(ABC)があり、それを実装したクラスを使う形となる。IOBaseが一番の大本にあり、用途に応じたRawIOBase(バッファなし)、BufferedIOBase(バイナリI/O)、TextIOBase(テキストI/O)があり、通常はバイナリI/OかテキストI/Oを使う。

ABC継承元スタブメソッドMixin するメソッドとプロパティ
IOBasefileno, seek, truncateclose, closed, __enter__, __exit__, flush, isatty, __iter__, __next__, readable, readline, readlines, seekable, tell, writable, writelines
RawIOBaseIOBasereadinto, writeIOBase から継承したメソッド、 read, readall
BufferedIOBaseIOBasedetach, read, read1, writeIOBase から継承したメソッド、 readinto, readinto1
TextIOBaseIOBasedetach, read, readline, writeIOBase から継承したメソッド、 encoding, errors, newlines
Python3公式ドキュメントより引用

以下のコードで実際に各クラスを生成できる。openはファイルを開く関数であり後述する。

import io

# 実行するコード
codes = [ r'open("log.ini", mode="r")', r'open("log.ini", mode="rb")', r'open("log.ini", mode="rb", buffering=0)' ]

for code in codes:
    print( code )
    f = eval( code )

    print ( f"{isinstance( f, io.IOBase)         = }" )
    print ( f"{isinstance( f, io.RawIOBase)      = }" )
    print ( f"{isinstance( f, io.BufferedIOBase) = }" )
    print ( f"{isinstance( f, io.TextIOBase)     = }" )

    f.close()
open("log.ini", mode="r")
isinstance( f, io.IOBase)         = True
isinstance( f, io.RawIOBase)      = False
isinstance( f, io.BufferedIOBase) = False
isinstance( f, io.TextIOBase)     = True
open("log.ini", mode="rb")
isinstance( f, io.IOBase)         = True
isinstance( f, io.RawIOBase)      = False
isinstance( f, io.BufferedIOBase) = True
isinstance( f, io.TextIOBase)     = False
open("log.ini", mode="rb", buffering=0)
isinstance( f, io.IOBase)         = True
isinstance( f, io.RawIOBase)      = True
isinstance( f, io.BufferedIOBase) = False
isinstance( f, io.TextIOBase)     = False

これは抽象クラスであるため、実体は別のクラスがある。たとえば

f = open("log.ini", mode="r")
print( f )
f.close()
<_io.TextIOWrapper name='log.ini' mode='r' encoding='cp932'>

となる。ただ多くの場合はTextIOWrapperクラスであることを考慮する必要はなく、基底抽象クラスが定義しているreadlineなどを使うことになる。

主要メソッド

メソッド定義クラス用途
read(size=-1)RawIOBase
TextIOBase
BufferedIOBase
データを読み込む。sizeで最大読み込みバイト数または文字数を指定できる。指定がなければすべて読み込む。
readline(size=-1)IOBaseデータを1行読み込む。sizeで読み込む最大バイト数または文字数を指定できる。
readlines(hint=-1)IOBaseデータを複数行読み込む。hintで最大読み込み行数を指定できる。指定がなければすべて読み込む。
ファイルをすべて1行ずつ読み込むだけなら、このメソッドを使用せず、ファイルオブジェクトをfor ... in ...で処理すれば良い。
write( data )TextIOBase
BytesIO
BufferedWriter
データを書き込む。
dataはTextIOBaseなら文字列、BytesIOかBufferedWriterならバイト列。
writelines(lines)IOBaseデータを複数行書き込む。
seek(offset, whence=SEEK_SET)IOBaseストリームの位置を指定の場所に変える。BufferedRandomなどのseekableなストリームに限定して利用できる。
位置はwhenceで指定された場所からのoffsetになり、whenceはSEEK_SET(ストリームの先頭。0)、SEEK_CUR(現在の位置。1)、SEEK_END(ストリームの末尾。2)から選択できる。
flush()IOBase可能なら書き込みバッファをフラッシュする。
close()IOBaseストリームを閉じる。

実装クラス

クラス種類概要
TextIOWrapperテキストI/O高水準のアクセスを提供するバッファ付きテキストストリーム
StringIOテキストI/Oインメモリのテキストバッファを使ったテキストストリーム。このストリームに書き込み、getvalue()をすると文字列としてバッファの内容が得られる。
BytesIOバイナリI/Oインメモリのバイトバッファを使ったバイナリストリーム。このストリー部に書き込み、getvalue()をするとバイト列としてバッファの内容が得られる。
BufferedReaderバイナリI/O読み出し可能、シーク不可であるバッファされたバイナリストリーム
BufferedWriterバイナリI/O書き込み可能、シーク不可であるバッファされたバイナリストリーム
BufferedRandomバイナリI/Oシーク可能なバッファされたバイナリストリーム。BufferedReaderとBufferedWriterを継承する。
BufferedRWPairバイナリI/O読み出し可能、書き込み可能である2つのシーク不可なバッファされたバイナリストリーム

ファイルの読み書き

ファイルオープン

ファイルを開くには組み込み関数openを使う。openメソッドが返すIOBaseはコンテキストマネージャであるため、withで使用できる。withについてはその他の文-withを参照。

# 基本はこれ
open( filename, mode='r', encoding=None )
# たまにerrorsやnewlineを使用
open( filename, mode='r', encoding=None, errors=None, newline=None )
モード

モードはテキスト/バイナリや読み込み専用などを指定する文字列。

モード意味ファイルが存在しない場合
r読み込み (デフォルト)例外FileNotFoundErrorが発生
w上書き新規作成
x新規作成(ファイルが存在する場合はエラー)新規作成
a追記新規作成

'r+'などのように+を付与したものは更新用という意味でWriteもReadもできる。現在のファイル内容の扱いやオープン時のカーソル位置などが微妙に異なる。

モード意味既存ファイルの内容オープン時のカーソルの位置書き込み時の位置
r+更新保持(上書き可)先頭カーソル位置
w+更新破棄先頭 (内容が破棄されているため先頭=末尾)カーソル位置
a+更新保護(上書き不可)末尾常に末尾

更新モードで読み込み・書き込みを交互にするケースでは、バッファが原因で意図しないファイル内容になることがある。読み込み後に書き込むときはseekメソッドを、書込み後に読み込むときはflushメソッドを呼び出すことで防ぐことができる。

パターンコードf.readの結果ファイルの内容例
前提条件fは1234567890という内容のテキストファイルを
r+モードで開いたもの
f = open("test.txt", "r+")
読み込み後に、何もしないで書き込むf.read(1) # 1文字読み込み
f.write("ABC") # 3文字書き込み
11234567890ABC
1文字読み込んだ後と考えると2文字目位置にABCが望ましい?
読み込み後に、seekしてから書き込むf.read(1) # 1文字読み込み
f.seek(f.tell(),0) # 現在位置にカーソルを設定
f.write("ABC") # 3文字書き込み
11ABC567890
書込み後に、何もしないで読み込むf.write("ABC") # 3文字書き込み
f.read(1) # 1文字読み込み
1
ABC書込み後と考えると、4文字目の4が望ましい?
1234567890ABC
最初に書き込んだと考えると1文字目位置にABCが望ましい?
書込み後に、flushしてから読み込むf.write("ABC") # 3文字書き込み
f.flush() # バッファをフラッシュ
f.read(1) # 1文字書き込み
4ABC4567890

他にテキスト用に開くかバイナリ用に開くかのモードも指定できる。バイナリモードて読み込みなら'rb'となる。デフォルトでテキストモードのため'r''rt'と等しい。

モード意味
'b'バイナリモード
't'テキストモード (デフォルト)
エンコーディング

文字をバイト列に変換する方法(文字コード、符号化方法)を指定する。日本で使われる主なエンコードを示す。なお_を-に置き換えたものも有効なエイリアスである。

指定文字コード指定文字コード
utf_8 (utf-8)UTF-8utf_8_sig (utf-8-sig)BOM有りUTF-8 sign付きUTF-8
utf_16 (utf-16)UTF-16euc_jp (euc-jp)EUC-JP
shift_jis (shift-jis)Shift_JIScp932Shift-JISのMS独自拡張

他のエンコーディングは公式ドキュメントに記載がある。

エラー処理

errorsでエンコードエラー時の処理を指定できる。

エラー時処理処理
'strict'例外を発生(デフォルト)
'ignore'エラーを無視
'replace'他の文字(?など)に置換
改行処理

newlineで改行文字をどう変換するか指定できる。CSVなどの一部ファイルではそのモジュールが改行処理を行うためIOモジュール側では何もしないように''を指定したりする。

設定値読み込み時書き込み時
None'\n'、'\r'、'\r\n' のすべてを改行文字とみなし、'\n' に変換する(デフォルト)システムのデフォルト os.linesep に変換する
'''\n'、'\r'、'\r\n' のすべてを改行文字とみなし、変換しない変換しない
'\n'指定された文字を改行文字とみなし、変換しない変換しない
'\r' または '\r\n'指定された文字を改行文字とみなし、変換しない指定された文字に変換する

以下に\r、\n、\r\nを含むファイルを各設定で読み込んだ結果を示す。

line1\nline2\rline3\r\nline4
None'''\n''\r''\r\n'
'line1\n'
'line2\n'
'line3\n'
'line4'
'line1\n'
'line2\r'
'line3\r\n'
'line4'
'line1\n'
'line2\rline3\r\n'
'line4'
'line1\nline2\r'
'line3\r'
'\nline4'
'line1\nline2\rline3\r\n'
'line4'

ファイル・ディレクトリの操作

パス関連の操作

パスはOSに依存するものでosライブラリの一部機能として提供される。主にos.pathが該当する。また、Python3.4から標準ライブラリとなったpathlibはos.pathよりも高レベルのパス関連の機能を提供している。os.pathはパスを表す文字列を使用して操作を行うのに対し、pathlibではパスを表すPathオブジェクトで扱う。

また、Python3.6からosライブラリにos.PassLikeという抽象基底クラス(ABC)が作成され、pathlibのPathがそれを実装するようになった。あわせて標準ライブラリのパスを指定する場所の一部?ではPathLikeを受け取れるようになった。すなわち、pathlibのPathオブジェクトをそのまま標準ライブラリに渡せるようになった。そのため、特に理由がなければpathlibを使ったほうが良いと個人的には感じる。

基本操作

操作osライブラリのサンプルと実行例patlibのサンプルと実行例
絶対パスを取得>>> os.path.abspath("..")
C:\Users
>>> p = pathlib.Path("..")
>>> p.resolve()
WindowsPath('C:/Users')


pathlibのabsoluteはabspathとは異なり相対パスを解決しない。
>>> p = pathlib.Path("..")
>>> p.absolute()
WindowsPath('C:/Users/xxxxx/..')
アイテム名(ファイル名・フォルダ名)を取得>>> os.path.basename(r'C:\Users\sample.txt')
sample.txt
>>> os.path.basename('C:\\Users\\')
'' (空文字列)
>>> os.path.basename(r'C:\Users')
Users

* ディレクトリのパスは末尾の"\"の有無により挙動が変わる。
>>> pathlib.Path( r'C:\Users\sample.txt' ).name
sample.txt
>>> pathlib.Path( 'C:\\Users\\' ).name
Users
>>> pathlib.Path( r'C:\Users' ).name
Users

* 末尾の"\"の有無による違いはない
共通するディレクトリを取得>>> os.path.commonpath([r'C:\Users\sample.txt', r'C:\Users\abc123\', r'C:\Users\eee\sample.txt'])
C:\Users
対応する機能なし?
共通するプレフィクスを取得>>> os.path.commonprefix([r'C:\Users\sample.txt', r'C:\Users\sazae\'])
C:\Users\sa

* このメソッドはパスの区切りを考慮しないため不適切なパスを返しうる。
対応する機能なし?
ディレクトリを取得* ファイルに対して実行した場合は親ディレクトリ
* ディレクトリに対して実行した場合は末尾の"\"の有無により挙動が変わる。
>>> os.path.dirname(r'C:\Users\sample.txt')
C:\Users
>>> os.path.dirname(r'C:\Users\')
C:\Users
>>> os.path.dirname(r'C:\Users')
C:\
* ファイル・フォルダのどちらでも親ディレクトリになる
>>> pathlib.Path( r'C:\Users\sample.txt' ).parent
C:\Users
>>> pathlib.Path( 'C:\\Users\\' ).parent
C:\
>>> pathlib.Path( r'C:\Users' ).parent
C:\
存在確認>>> os.path.exists(r'C:\Users')
True
>>> os.path.exists(r'C:\Users\abc123')
False
>>> pathlib.Path( r'C:\Users\sample.txt' ).exists()
False
>>> pathlib.Path( r'C:\Users' ).exists()
True
ホームディレクトリの展開>>> os.path.expanduser(r'~')
C:\Users\xxxxx
>>> os.path.expanduser(r'~\Document')
C:\Users\Document
>>> pathlib.Path( '~' ).expanduser()
C:\Users\xxxxx
>>> pathlib.Path( '~\\sample.txt' ).expanduser()
C:\Users\xxxxx\sample.txt

ホームディレクトリの取得だけなら以下でOK
>>> pathlib.Path.home()
WindowsPath('C:/Users/xxxxx')
環境変数の展開>>> os.path.expandvars(r'%HOMEPATH%')
\Users\xxxxx
>>> os.path.expandvars(r'%userprofile%')
C:\Users\xxxxx
対応する機能なし
パスの結合>>> os.path.join('C:\Users', 'xxx\yyy' )
C:\Users\xxx\yyy


ルートを含むパスと結合しようとするとそれまでの値は無視される
>>> os.path.join('test', 'C:\Users', 'xxx' )
C:\Users\xxxxx

Windowsにおいてドライブ文字だけを渡すときは明示的にルートを示す必要がある。
>>> os.path.join('C:', 'Users', 'xxx' )
C:Users\xxx
>>> os.path.join('C:', '\\', 'Users', 'xxx' )
C:\Users\xxx
(1) joinpathメソッド
>>> p1 = pathlib.Path('C:\Users')
>>> p2 = pathlib.Path('xxx\yyy')
>>> p1.joinpath(p2)
WindowsPath('C:/Users/xxx/yyy')


(2) /演算子
>>> p1 = Path('C:\Users')
>>> p2 = p1 / 'test.txt'
>>> str(p2)
'C:\Users\test.txt'


いずれの方法でも、ルートを含むパスと結合しようとするとそれまでの値は無視される。
>>> p3 = pathlib.Path('C:\\Test')
>>> p1.joinpath(p3)
WindowsPath('C:/Test')


ドライブ文字だけの場合は明示的にルートを示す必要がある
>>> p1 = pathlib.Path('C:')
>>> p2 = pathlib.Path('xxx\\yyy')
>>> p3 = pathlib.Path('\\xxx\\yyy')
>>> p1.joinpath(p2)
WindowsPath('C:xxx/yyy')
>>> p1.joinpath(p3)
WindowsPath('C:/xxx/yyy')


またはresolveを使っても良い
>>> p1.joinpath(p2).resolve()
WindowsPath('C:/xxx/yyy')
パスの正規化1>>> os.path.normcase('C:\\Users/xxx\\yyy' )
c:\users\xxx\yyy

* 大文字小文字を区別しないWindowsにおいてはすべて小文字にした上で、/を\する。
* Unix系OSでは何もしない。
>>> pathlib.Path(r'C:\Users/xxxxx').resolve()
WindowsPath('C:/Users/xxxxx')

*normcaseとは異なり小文字にはしない
パスの正規化2>>> os.path.normpath(r'C:\Users/xxx\.\..\yyy' )
C:\Users\yyy

* 相対パスを解決したり、多すぎる/を取り除いたりする。Windowsにおいては/を\に変換もする。
>>> pathlib.Path(r'C:\\Users///xxx\.\..\yyy').resolve()
WindowsPath('C:/Users/yyy')
相対パス化>>> os.path.relpath(r'C:\Users\xxx\sample.txt', r'C:\Users')
xxx\sample.txt
>>> p1 = pathlib.Path(r'C:\Users\xxx\test')
>>> p1.relative_to( r'C:\Users\xxx' )
WindowsPath('test')
パスの分割1
(ベースネーム)
>>> os.path.split("C:\\Users\\xxxxx\\Links")
('C:\Users\xxxxx', 'Links')


* 最後のパス区切り文字で2つに分割するため、末尾が区切り文字の場合に注意
>>> os.path.split("C:\\Users\\xxxxx\\Links\\")
('C:\Users\xxxxx\Links', '')
同じ機能はないがparentとnameでそれぞれ取得できる。
>>> p = pathlib.Path(r"C:\Users\xxxxx\Links")
>>> p.parent
C:\Users\xxxxx
>>> p.name
Links
パスの分割2
(ドライブ文字)
>>> os.path.splitdrive(r"C:\Users\xxxxx\Links")
('C:', '\Users\xxxxx\Links')


* ドライブ文字がないUNIXでは常にドライブが空文字になる。
同じ機能はないが、ドライブは以下で取得できる。
>>> pathlib.Path(r'C:\Users\xxx\test').drive
C:
パスの分割3
(拡張子)
>>> os.path.splitext( "C:\Users\xxxxx\test.jpg" )
('C:\Users\xxxxx\test', '.jpg')


* パス区切り文字の直後にあるドットはファイル名として認識される。
>>> os.path.splitext( r"D:\file\.bashrc" )
('D:\file\.bashrc', '')
>>> s.path.splitext( r"D:\file\...txt" )
('D:\file\...txt', '')
同じ機能はないが、ファイル名、ファイル拡張子はそれぞれ以下で取得できる。
>>> p1 = pathlib.Path(r'C:\Users\xxx\test.txt.tar.gz')
>>> p1.stem
test.txt.tar
>>> p1.suffix
gz


* パス区切り文字の直後にあるドットはファイル名として認識される

パスの検査

操作osライブラリのサンプルと実行例pathlibライブラリのサンプルと実行例
存在するかどうか確認>>> os.path.exists(r'C:\Users')
True
>>> os.path.exists(r'C:\Users\abc123')
False
>>> p1 = pathlib.Path(r'C:\Users').exists()
True
>>> p2 = pathlib.Path(r'C:\Users\abc123').exists()
False
絶対パスかどうか検査>>> os.path.isabs(r'C:\Users\abc123')
True
>>> os.path.isabs(r'\abc123')
True
>>> os.path.isabs(r'.\abc123')
False
>>> pathlib.Path(r'C:\Users\abc123').is_absolute()
True
>>> pathlib.Path(r'\abc123').is_absolute()
False
>>> pathlib.Path(r'.\abc123').is_absolute()
False
ファイルかどうか検査>>> os.path.isfile(r'C:\Users\xxxxx\test.txt')
True
>>> os.path.isfile(r'C:\Users\xxxxx\test2.txt')
False

* 存在しないパスもFalseになる
>>> os.path.isfile(r'C:\Users\xxxxx')
False
>>> pathlib.Path(r'C:\Users\xxxxx\test.txt').is_file()
True
>>> pathlib.Path(r'C:\Users\xxxxx\test2.txt').is_file()
False

* 存在しないパスもFalseになる
>>> pathlib.Path(r'C:\Users\xxxxx').is_file()
False
ディレクトリかどうか検査>>> os.path.isdir(r'C:\Users\xxxxx\test.txt')
False
>>> os.path.isdir(r'C:\Users\xxxxx\test2.txt')
False

* 存在しないパスもFalseになる
>>> os.path.isdir(r'C:\Users\xxxxx')
True
>>> pathlib.Path(r'C:\Users\xxxxx\test.txt').is_dir()
False
>>> pathlib.Path(r'C:\Users\xxxxx\test2.txt').is_dir()
False

* 存在しないパスもFalseになる
>>> pathlib.Path(r'C:\Users\xxxxx').is_dir()
True
シンボリックリンクかどうか検査>>> os.path.islink(r'C:\Users\sample.txt')
False
>>> pathlib.Path(r'C:\Users\xxxxx').is_symlink()
False

対象の属性情報を取得

操作osライブラリのサンプルと実行例pathlibライブラリのサンプルと実行例
スタットを取得>>> stat = os.stat(r"D:\desktop")>>> stat = pathlib.Path(r"D:\desktop").stat()
最終アクセス日時を取得>>> os.path.getatime( r"D:\desktop" )
1691250411.9327028

* os.stat(r"D:\desktop").st_atime も同じ
>>> stat.st_atime
1691250411.9327028
最終更新日時を取得>>> os.path.getmtime( r"D:\desktop" )
1686329308.9348652

* os.stat(r"D:\desktop").st_mtime も同じ
>>> stat.st_mtime
1686329308.9348652
作成日時を取得>>> os.path.getctime( r"D:\desktop" )
1656431619.328862

* os.stat(r"D:\desktop").st_ctime も同じ
>>> stat.st_ctime
1656431619.328862
サイズを取得>>> os.path.getsize( r"D:\desktop" )
4096

* os.stat(r"D:\desktop").st_size も同じ
>>> stat.st_size
4096
所有者、グループを取得>>> os.stat(r"/usr/abc").st_uid
>>> os.stat(r"/usr/abc").st_gid
>>> pathlib.Path(r"/usr/abc").owner()
>>> pathlib.Path(r"/usr/abc").group()

* Windowsはサポートされない

時刻はUNIX時間のため、datetimeに変換すると良い。

>>> import datetime
>>> import os
>>> 
>>> datetime.datetime.fromtimestamp( os.path.getmtime( r"D:\desktop" ) )
datetime.datetime(2023, 6, 10, 1, 48, 28, 934865)

ディレクトリアクセス

操作osライブラリのサンプルと実行例pathlibライブラリのサンプルと実行例
カレントディレクトリを取得
* CWD:Current Working Directory
>>> os.getcwd()
'C:\Users\xxxxx'
>>> pathlib.Path.cwd()
WindowsPath('C:/Users')
カレントディレクトリを変更>>> os.chdir( r".." )同等の機能なし
ファイルおよびディレクトリの一覧を取得>>> os.listdir( r"C:\Users" )
* 引数を省略した場合はカレントディレクトリが対象となる。
* 取得できるのはファイルやディレクトリの名前のみになる。

* 名前ではなくパスのリストを取得したい場合、加工が必要
>>> [ os.path.join(r"C:\Users", x) for x in os.listdir(r"C:\Users") ]
['C:\Users\All Users',
'C:\Users\Default',
'C:\Users\Default User',
... ]
>>> pathlib.Path(r"D:\desktop").iterdir()
* 取得できるのはPathオブジェクトのイテレータである
* 元が絶対パスのPathオブジェクトなら、結果も絶対パスのPathオブジェクトになる
ファイルまたはディレクトリの一覧を取得直接的な関数はない。listdirで取得したものをフィルタする。
listdirの結果にはパスが含まれておらず、isfile()やisdir()は絶対パスであることに注意。

# カレントディレクトリの例
>>> [ x for x in os.listdir() if os.path.isfile( os.path.abspath(x) ) ]

# 指定のディレクトリの例
>>> [ x for x in os.listdir(r"C:\Users") if os.path.isfile( os.path.join( r"C:\Users" ,x) ) ]

# フィルタ関数の例
>>> items = [ os.path.join(r"C:\Users", x) for x in os.listdir(r"C:\Users") ]
>>> filter( lambda x: os.path.isfile(x), items )
直接的な関数はない。iterdirで取得したものをフィルタする。

>>> p = pathlib.Path(r"C:\Users")

# ジェネレータ式の例
>>> files = [ x for x in p.iterdir() if x.is_file() ]

# フィルタ関数の例
>>> files = filter( lambda x: x.is_file(), p.iterdir() )

glob

なお、ファイル・ディレクトリの一覧を取得する別の方法にglobモジュールがある。globはパターンにマッチするファイル・ディレクトリを取得できるものである。Pathlibにもglobモジュールと同等の機能が実装されている。

操作globモジュールのサンプルと使用例pathlibライブラリのサンプルと使用例
UNIXシェル形式のワイルドカード指定で取得
* : 任意の文字列
? : 任意の一文字
[] : 特定の一文字
>>> import glob
>>> glob.glob( r'D:\files\*.txt' )
['D:\files\keymap.txt', ... ]


※上記はfiles直下の拡張子がtxtを探す。サブディレクトリを再帰的に検索するには**とrecursiveフラグを使う
>>> import glob
>>> glob.glob( r'D:\files\**\*.txt', recursive=True )
['D:\files\keymap.txt', ... ]
>>> p = pathlib.Path(r'D:\files')
>>> items = p.glob('*.txt')


※上記はfiles直下の拡張子がtxtを探す。サブディレクトリを再帰的に含めて検索するには**を使う
>>> items = p.glob('**\*.txt')

※Windowsでは大文字と小文字を区別しないが、case_sensitive引数で挙動を変更できる。

ディレクトリ・ファイルの操作

コピー・移動・削除などの操作の一部はpathlibでは提供されていない。そのような場合はosライブラリやshutilライブラリを使う。

コピー

操作サンプルと実行例
ファイルコピーshutil.copy( src, dst )
shutil.copy2( src, dst )


* copy()はパーミッション以外のメタデータはコピーされない。copy2()は可能な限りコピーする。
* dstがディレクトリの場合、元の名前でコピーされる。
* dstが存在するファイルの場合、上書きされる。
パーミッションのコピーshutil.copymode(src, dst)
statのコピーshutil.copystat(src, dst)
コンテンツのコピーshutil.copyfile(src, dst)
ファイルライクオブジェクトのコピーshutil.copyfileobj(fsrc, fdst)
パスライクオブジェクトではなくファイルライクオブジェクトのときに使うコピー関数
再帰的なコピーshutil.copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, ignore_dangling_symlinks=False, dirs_exist_ok=False)
srcをルートとしてディレクトリ、ファイルを再帰的にコピーする。
* ignoreでコピーするか判定する関数を指定できる。shutil.ignore_patterns( pattern )を使うとコピーしないパターンをglobスタイルで指定してできる。
* デフォルトではコピー先のディレクトリがあるとエラーになる。無視するにはdirs_exist_ok=Trueとする。同じファイルがあった場合は上書きされる。

移動

操作osライブラリ/shutilライブラリのサンプルと使用例pathlibライブラリのサンプルと使用例
リネームos.rename(src, dst)

宛先が存在する場合例外OSError(のサブクラス)が発生する。
Windowsの場合はFileExistsErrorが発生する。
また、srcとdstが異なるファイルシステムの場合失敗することがある。
srcpath.rename(dstpath)

宛先が存在する場合例外OSError(のサブクラス)が発生する。
Windowsの場合はFileExistsErrorが発生する。
また、srcとdstが異なるファイルシステムの場合失敗することがある。
置き換えos.replace(src, dst)

現在のパスを宛先のパスに置き換える。renameとは異なり、宛先が既存のファイルや空のディレクトリの場合に上書きされる。
srcとdstが異なるファイルシステムの場合失敗することがある。
srcpath.replace(dstpath)

現在のパスを宛先のパスに置き換える。renameとは異なり、宛先が既存のファイルや空のディレクトリの場合に上書きされる。
srcとdstが異なるファイルシステムの場合失敗することがある。
移動shutil.move(src, dst, copy_function=copy2)

再帰的にファイル・ディレクトリを移動させる。
* dstがディレクトリの場合、ディレクトリ内に移動される
* dstがファイルで存在する場合、上書きされる可能性がある。
* srcとdstが同じファイルシステム上にあればos.rename()が使われるが、そうでなければcopy_functionでコピーしたあとsrcが削除される。
同等の機能なし

削除

操作osライブラリ/shutilライブラリのサンプルと使用例pathlibライブラリのサンプルと使用例
ファイルを削除os.remove( path )
pathはファイルでなければならない。
path.unlink(missing_ok=False)
pathはファイルまたはシンボリックリンクでなければならない。
missing_okをTrueにすると削除対象がなくても例外が発生しない。
ディレクトリを削除os.rmdir( path )
pathは空のディレクトリでなければならない。
path.rmdir()
pathは空のディレクトリでなければならない。
空のディレクトリを再帰的に削除os.removedirs( path )
pathだけでなく、その親のディレクトリも空であれば再帰的に削除する。
同等の機能なし
再帰的に削除shutil.rmtree( path, ignore_errors=False, onerror=None, )

再帰的にpath配下のファイル・ディレクトリを削除する。
* ignore_errorsをTrueにすれば、削除できないファイルがあっても無視する。
* onerrorにエラーがあったときに呼ばれる関数を指定できる。
同等の機能なし
実装は表外下部参照

Pythondocによる、Windowsにおけるrmtreeのサンプルは以下の通りになっている。これはReadonlyフラグを削除した後にリトライを試みる実装である。

# 引用元:https://docs.python.org/3/library/shutil.html?highlight=shutil#rmtree-example

import os, stat
import shutil

def remove_readonly(func, path, _):
    "Clear the readonly bit and reattempt the removal"
    os.chmod(path, stat.S_IWRITE)
    func(path)

shutil.rmtree(directory, onerror=remove_readonly)

INIファイル

標準ライブラリのconfigparserでINIファイル形式の設定を読み込める。INIファイルは以下のような[]で囲まれたセクションがあり、キーバリュー型で設定値が書かれているファイルである。

[LOG]
MODE=debug
FILE=log.txt

[HOME]
USER=abc
DIRECTORY=/home/abc

INI形式の設定を読み込む

readメソッドで指定のパスから設定を読み込むことができる。resultには読み込みに成功したファイル名が含まれており、読み込んだ設定はConfigParserオブジェクトcfに反映されている。

import pathlib
import configparser

cf = configparser.ConfigParser()

p = pathlib.Path(r"D:\programs\test.ini")
result = cf.read(p)

他にも文字列から読み込むread_stringや、オープンしたファイルなどIterable[str]から読み込むread_file、dictオブジェクトから読み込むread_dictなどがある。

INI形式で設定を書き込む

writeメソッドでファイルに書き込む事ができる。ファイルは事前にオープンしておく必要がある。

cf.write( open(pathlib.Path(r"D:\programs\test2.ini"), mode="w") )

設定の操作

セクション関連

操作サンプル
セクションの一覧を取得cf.sections()
['LOG', 'HOME']
セクションがあるか検査cf.has_section('LOG')
True
セクションを追加cf.add_section("New Section")
セクションを削除cf.remove_section("LOG")
セクションを取得cf['LOG']
<Section: LOG>
セクションに含まれる項目を取得cf.options('LOG')
['mode', 'file']

オプション関連

操作サンプル
セクション内のオプション一覧を取得cf.options('LOG') # セクション名を指定
['mode', 'file']
オプションのキー、バリューを取得セクションオブジェクトはdictionaryと同じ操作が可能

section = cf['LOG']
for i in section.keys(): # オプションのキーのイテレータ
print(i)

for i in section.values(): # オプションのバリューのイテレータ
print(i)
for i in section.items(): # オプションのキーバリューペアのイテレータ
print(i)
オプションがあるか検査cf.has_option("LOG","mode")
True
オプションを削除cf.remove_option("LOG","mode")
オプションの値を取得cf['LOG']['mode']
debug
cf.get('LOG', 'mode', fallback="")
debug

* getではオプションが存在しないときの値をfallbackで指定できる
オプションを追加または値を設定cf.set("LOG", "newone", "newvalue")

section = cf['LOG']
section['newone2'] = 'newvalue2'

* どちらもセクションが存在しないとエラーになる
オプションの値を指定の型に変換して取得cf.getint ( 'section', 'option', fallback=default_value)
cf.getfloat ( 'section', 'option', fallback=default_value)

cf.getboolean( 'section', 'option', fallback=default_value)

正規表現(RE)

reライブラリで正規表現が利用できる。

import re

基本的な使用法

個人的にはre.search()かre.finditer()を使う機会が多い。

構文使用法戻り値
re.search( pattern, string, flag )文字列がパターンに一致するか調べ、最初に一致したMatchオブジェクトを返す。最初にパターンに一致した部分を表すMatchオブジェクト
一致しなかった場合はNone
re.finditer(pattern, string, flags)文字列の中でパターンに一致した部分すべてのMatchオブジェクトを指すイテレータで取り出す。Matchオブジェクトのイテレータ。
一致しなかった場合も空のイテレータになる。
re.match( pattern, string, flag )文字列の先頭がパターンに一致するか調べる。一致した場合Matchオブジェクト
一致しなかった場合None
re.fullmatch( pattern, string, flag )文字列の全体がパターンに一致するか調べる。一致した場合Matchオブジェクト
一致しなかった場合None
re.findall( pattern, string, flag )文字列の中でパターンに一致した部分すべてを文字列のリストで取り出す。一致した部分の文字列のリスト
一致しなければ空のリスト
re.finditer(pattern, string, flags)文字列の中でパターンに一致した部分すべてのMatchオブジェクトを指すイテレータで取り出す。一致した部分のMatchオブジェクトのイテレータ

Matchオブジェクト

マッチした結果を表すオブジェクト。マッチしなかった場合はNoneが返されるため、Matchオブジェクトが返ってきた=正規表現が一致したことを意味する。Matchオブジェクトのブール値は常にTrueとなるため、一致したか判定するためにはif match:のようにすればよい。

構文使用法サンプル
match.group( ... )指定されたキャプチャグループの中身を取り出す。
0はパターン全体を表し、以降は1,2,3,4...の順になる。
名前付きキャプチャグループがあれば、名前も使用できる。
match[0]のようにインデックスでもアクセスできる。
(.)?のようにキャプチャグループがなくてもマッチする場合はNoneになりうる
match = re.search(r"1(.)?23(?P<name>..)6", "123456789")
match.group(0)

'123456'
match[1]
None
match[2]
'45'
match.group('name')
'45'
match.groups( default=None )キャプチャグループすべての中身を取り出す。
パターン全体は含まれない
一致しなかったキャプチャグループの値をdefaultで指定できる。
match = re.search(r"1(.)3(..)?4", "1234")
match.groups()

('2', None)
match.groups( default='' )
('2', '')
match.start( [group] )
match.end( [group] )
マッチした位置が文字列全体の中でどこかを表すインデックスを返す。
groupが指定された場合はそのキャプチャグループがマッチした位置を返す。
マッチしていないキャプチャグループの場合は-1。
match = re.search(r"1(.)3(.)?4", "012345")
match.start()

1
match.end()
5
match.start(1)
2
match.end(2)
-1
match.span( [group] )マッチした位置が文字列全体の中でどこかを表すインデックスをタプルで返す。
groupが指定された場合はそのキャプチャグループがマッチした位置を返す。
マッチしていないキャプチャグループの場合は(-1,-1)
match = re.search(r"1(.)3(.)?4", "012345")
match.span(1)

(2, 3)
match.span(2)
(-1, -1)

フラグ

flagで正規表現の挙動を制御できる。主なもの4つを示す。

フラグ意味
re.IGNORECASE大文字小文字を区別しない
re.MULTILINE指定されている場合、^が各行の先頭、$が各行の末尾にもマッチする。
re.DOTALL指定されている場合、.が改行にもマッチする。
re.ASCII一部の正規表現がASCIIだけを考慮した形式に変わる。

主な正規表現の記法

記法意味記法意味
.任意の1文字?直前の0回または1回の繰り返し
*0回以上の繰り返し+1回以上の繰り返し
^文字列の先頭$文字列の末尾
あるいは文字列の末尾の改行の直前
*?
+?
??
非貪欲なマッチ*+
++
?+
最大量のマッチ
パターンa*+aは文字列aaaaにマッチしなくなる
{m}
{m,n}
指定回数の繰り返し{m,n}?
{m,n}+
非貪欲または最長の指定回数の繰り返し
[]文字の集合A|BAまたはB
Aに一致した場合Bは評価されない
(...)グループ\番号指定番号のグループの後方参照
(?:...)キャプチャしないグループ
(?aiLmsux)パターンの先頭でのみ使用可能(3.11から)
グループ内でのみ有効な正規表現の動作フラグを指定
(?aiLmsux-imsx:...)グループ内でのみ有効な正規表現の動作フラグを指定
(?P<name>...)名前付きグループ(?P=name)名前付きグループの後方参照
(?=...)先読みアサーション
後に続く文字列の制約。
re.match(r"1(.)34(?=5)", "12345")
re.Match object; span=(0, 4), match='1234'
先読みアサーションは後に続く文字が条件を満たすか確認するが、マッチ文字列には含まれない。
(?!...)否定先読みアサーション
後に続く文字列の制約。
先読みアサーションは後に続く文字が条件を満たすか確認するが、マッチ文字列には含まれない。
(?<=...)後読みアサーション
先行する文字列の制約。
パターンの長さは固定長でなければならない。
match = re.search("(?<=ab).*ef", "abcdefg")
re.Match object; span=(2, 6), match='cdef'

後読みアサーションは前の文字が条件を満たすか確認するが、マッチ文字列には含まれない。戻り読みアサーションと呼ばれることもある。
(?<!...)否定後読みアサーション
先行する文字列の制約。
パターンの長さは固定長でなければならない。
後読みアサーションは前の文字が条件を満たすか確認するが、マッチ文字列には含まれない。戻り読みアサーションと呼ばれることもある。
\d任意の Unicode 10進数字 (Unicode 文字カテゴリ [Nd])。
全角の0や1にもマッチする。
ASCIIフラグが有効な場合は半角の[0-9]のみ。
\D\dの反対
\sUnicodeの空白文字。日本語の全角空白も含まれる。
ASCIIフラグが有効な場合は[ \t\n\r\f\v]
\S\sの反対
\w単語を構成する文字\W\wの反対

パターンのコンパイル

パターンを表す文字列はパターンオブジェクトにコンパイルされる。re.compile(pattern, flag)でコンパイルできる。

re.match()などでパターンを文字列として渡したときも内部でコンパイルが行われている。コンパイルされた結果はreライブラリ側でキャッシュされため、同じ正規表現を数回呼び出す程度ならコンパイルで生じるオーバーヘッドはあまり意識しなくて良い。ただし繰り返し、大量に呼ばれるような場合はコンパイルするメリットがある。

import re
from datetime import datetime

start = datetime.now()
for time in range(1000000):
    re.search(r"a(b*)c*\1a", "abqabcddddddfccccabaaabaabbbbcccacbbbcabbbcccbbbaadadddeabac")
end = datetime.now()
print( end - start )

start = datetime.now()
p = re.compile(r"a(b*)c*\1a") # 事前にコンパイル
for time in range(1000000):
    p.search("abqabcddddddfccccabaaabaabbbbcccacbbbcabbbcccbbbaadadddeabac")
end = datetime.now()
print( end - start )
0:00:01.482701 # コンパイル無しで100万回実行
0:00:00.343597 # コンパイル有りで100万回実行

乱数(random)

randomモジュールで乱数を使用できる。

Python3公式ドキュメントより引用
このモジュールの擬似乱数生成器をセキュリティ目的に使用してはいけません。セキュリティや暗号学的な用途についてはsecretsモジュールを参照してください。

初期化と状態の再現

乱数生成機はseedメソッドで初期化する。デフォルトではシステム時刻が使用され、可能ならOSの乱数生成機を使う。

import random

random.seed(a=None, version=2)

random.getstate()およびrandom.setstate(state)で乱数生成機の内部状態を取得、復元できる。

乱数の取得

整数

random.randrange(stop)
random.randrange(start, stop[, step])
start以上stop未満の乱数値を返す。
引数はRangeオブジェクトの指定の仕方と同じ。
random.randint(a, b)a以上b以下の乱数値を返す。
random.rangestart(a,b+1)と等しい。
random.getrandbits(k)kビットのランダムな正の整数を返す。

実数

これ以外にも様々な分布のメソッドが用意されている。

random.random()0.0 <= x < 1.0 のランダムな浮動小数点を返す。

シーケンスに対する操作

リストの中からランダムに要素を選ぶ

random.choice(seq)シーケンスからランダムに要素を選択する。
random.shuffle(x)シーケンスをインプレースでシャッフルする。
イミュータブルなシーケンスはsample(x, k=len(x))を代わりに使う。

secretモジュールのRandom

SecretモジュールにはSystemRandomクラスがある。これはrandomモジュールが定義するRandomを継承している。そのためrandomモジュールと同じように使用できる。ただしseedは無視される、getstate()、setstate()はサポートされないなどの違いがある。これはセキュリティの関係上、同じ乱数列が作成できてはいけないためである。

import random
import secrets

rand = secrets.SystemRandom()

print( isinstance(rand, random.Random) )
print( isinstance(rand, random.SystemRandom) )

ハッシュ

hashlibライブラリでハッシュを計算できる。Hashオブジェクトを作成(new)、ハッシュするメッセージを設定(update)、ハッシュを計算(digest)の順となる。

>>> import hashlib
>>> md5 = hashlib.md5()
>>> type(md5)
<class '_hashlib.HASH'>
>>> md5.update(b"Test Message")
>>> md5.hexdigest()
'd1d4180b7e411c4be86b00fb2ee103eb'

Hashオブジェクトの生成

Hashオブジェクトを作るには、コンストラクタを呼び出すか、Hash.new()を使う。

md5 = hashlib.md5()      # 結果は同じだがこちらの方が早い
md5 = hashlib.new('md5')

使用できるのはmd5、sha1、sha224、sha256、sha384、sha512、sha3_224、sha3_256、sha3_384、sha3_512

メッセージの設定

コンストラクタで設定するか、updateで設定する。updateは繰り返し呼び出すことで追記できる。メッセージは文字列ではなくバイト列である。(文字列の場合エンコードによりハッシュ値が変わるため)

以下の4つはいずれも同じハッシュ値になる。

md5 = hashlib.md5(b"DATA")
md5 = hashlib.new('md5', b"DATA")
md5 = hashlib.md5()
md5.update( b"DA" )
md5.update( b"TA" )
md5 = hashlib.new('md5', b"DA")
md5.update( b"TA" )

ハッシュ値の計算

digestでハッシュ値を計算する。hexdigestはハッシュ値を16進表記の文字列で表したものが得られる。

>>> md5.digest()
b"\xe4O\x9e4\x8eA\xcb'.\xfa\x878w(W\x1b"
>>> md5.hexdigest()
'e44f9e348e41cb272efa87387728571b'

マークアップ

ウェブアクセス

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です