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 するメソッドとプロパティ |
---|---|---|---|
IOBase | fileno, seek, truncate | close, closed, __enter__, __exit__, flush, isatty, __iter__, __next__, readable, readline, readlines, seekable, tell, writable, writelines | |
RawIOBase | IOBase | readinto, write | IOBase から継承したメソッド、 read, readall |
BufferedIOBase | IOBase | detach, read, read1, write | IOBase から継承したメソッド、 readinto, readinto1 |
TextIOBase | IOBase | detach, read, readline, write | IOBase から継承したメソッド、 encoding, errors, newlines |
以下のコードで実際に各クラスを生成できる。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文字読み込み | 1 | 1234567890ABC 1文字読み込んだ後と考えると2文字目位置にABCが望ましい? |
読み込み後に、seekしてから書き込む | f.read(1) # 1文字読み込み | 1 | 1ABC567890 |
書込み後に、何もしないで読み込む | f.write("ABC") # 3文字書き込み | 1 ABC書込み後と考えると、4文字目の4が望ましい? | 1234567890ABC 最初に書き込んだと考えると1文字目位置にABCが望ましい? |
書込み後に、flushしてから読み込む | f.write("ABC") # 3文字書き込み | 4 | ABC4567890 |
他にテキスト用に開くかバイナリ用に開くかのモードも指定できる。バイナリモードて読み込みなら'rb'
となる。デフォルトでテキストモードのため'r'
は'rt'
と等しい。
モード | 意味 |
---|---|
'b' | バイナリモード |
't' | テキストモード (デフォルト) |
エンコーディング
文字をバイト列に変換する方法(文字コード、符号化方法)を指定する。日本で使われる主なエンコードを示す。なお_を-に置き換えたものも有効なエイリアスである。
指定 | 文字コード | 指定 | 文字コード |
---|---|---|---|
utf_8 (utf-8) | UTF-8 | utf_8_sig (utf-8-sig) | BOM有りUTF-8 sign付きUTF-8 |
utf_16 (utf-16) | UTF-16 | euc_jp (euc-jp) | EUC-JP |
shift_jis (shift-jis) | Shift_JIS | cp932 | Shift-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("..") | >>> p = pathlib.Path("..") pathlibのabsoluteはabspathとは異なり相対パスを解決しない。 >>> p = pathlib.Path("..") |
アイテム名(ファイル名・フォルダ名)を取得 | >>> os.path.basename(r'C:\Users\sample.txt') * ディレクトリのパスは末尾の"\"の有無により挙動が変わる。 | >>> pathlib.Path( r'C:\Users\sample.txt' ).name * 末尾の"\"の有無による違いはない |
共通するディレクトリを取得 | >>> os.path.commonpath([r'C:\Users\sample.txt', r'C:\Users\abc123\', r'C:\Users\eee\sample.txt']) | 対応する機能なし? |
共通するプレフィクスを取得 | >>> os.path.commonprefix([r'C:\Users\sample.txt', r'C:\Users\sazae\']) * このメソッドはパスの区切りを考慮しないため不適切なパスを返しうる。 | 対応する機能なし? |
ディレクトリを取得 | * ファイルに対して実行した場合は親ディレクトリ * ディレクトリに対して実行した場合は末尾の"\"の有無により挙動が変わる。 >>> os.path.dirname(r'C:\Users\sample.txt') | * ファイル・フォルダのどちらでも親ディレクトリになる>>> pathlib.Path( r'C:\Users\sample.txt' ).parent |
存在確認 | >>> os.path.exists(r'C:\Users') | >>> pathlib.Path( r'C:\Users\sample.txt' ).exists() |
ホームディレクトリの展開 | >>> os.path.expanduser(r'~') | >>> pathlib.Path( '~' ).expanduser() ホームディレクトリの取得だけなら以下でOK >>> pathlib.Path.home() |
環境変数の展開 | >>> os.path.expandvars(r'%HOMEPATH%') | 対応する機能なし |
パスの結合 | >>> os.path.join('C:\Users', 'xxx\yyy' ) ルートを含むパスと結合しようとするとそれまでの値は無視される >>> os.path.join('test', 'C:\Users', 'xxx' ) Windowsにおいてドライブ文字だけを渡すときは明示的にルートを示す必要がある。 >>> os.path.join('C:', 'Users', 'xxx' ) | (1) joinpathメソッド>>> p1 = pathlib.Path('C:\Users') (2) /演算子 >>> p1 = Path('C:\Users') いずれの方法でも、ルートを含むパスと結合しようとするとそれまでの値は無視される。 >>> p3 = pathlib.Path('C:\\Test') ドライブ文字だけの場合は明示的にルートを示す必要がある >>> p1 = pathlib.Path('C:') またはresolveを使っても良い >>> p1.joinpath(p2).resolve() |
パスの正規化1 | >>> os.path.normcase('C:\\Users/xxx\\yyy' ) * 大文字小文字を区別しないWindowsにおいてはすべて小文字にした上で、/を\する。 * Unix系OSでは何もしない。 | >>> pathlib.Path(r'C:\Users/xxxxx').resolve() *normcaseとは異なり小文字にはしない |
パスの正規化2 | >>> os.path.normpath(r'C:\Users/xxx\.\..\yyy' ) * 相対パスを解決したり、多すぎる/を取り除いたりする。Windowsにおいては/を\に変換もする。 | >>> pathlib.Path(r'C:\\Users///xxx\.\..\yyy').resolve() |
相対パス化 | >>> os.path.relpath(r'C:\Users\xxx\sample.txt', r'C:\Users') | >>> p1 = pathlib.Path(r'C:\Users\xxx\test') |
パスの分割1 (ベースネーム) | >>> os.path.split("C:\\Users\\xxxxx\\Links") * 最後のパス区切り文字で2つに分割するため、末尾が区切り文字の場合に注意 >>> os.path.split("C:\\Users\\xxxxx\\Links\\") | 同じ機能はないがparentとnameでそれぞれ取得できる。>>> p = pathlib.Path(r"C:\Users\xxxxx\Links") |
パスの分割2 (ドライブ文字) | >>> os.path.splitdrive(r"C:\Users\xxxxx\Links") * ドライブ文字がないUNIXでは常にドライブが空文字になる。 | 同じ機能はないが、ドライブは以下で取得できる。>>> pathlib.Path(r'C:\Users\xxx\test').drive |
パスの分割3 (拡張子) | >>> os.path.splitext( "C:\Users\xxxxx\test.jpg" ) * パス区切り文字の直後にあるドットはファイル名として認識される。 >>> os.path.splitext( r"D:\file\.bashrc" ) | 同じ機能はないが、ファイル名、ファイル拡張子はそれぞれ以下で取得できる。>>> p1 = pathlib.Path(r'C:\Users\xxx\test.txt.tar.gz') * パス区切り文字の直後にあるドットはファイル名として認識される |
パスの検査
操作 | osライブラリのサンプルと実行例 | pathlibライブラリのサンプルと実行例 |
---|---|---|
存在するかどうか確認 | >>> os.path.exists(r'C:\Users') | >>> p1 = pathlib.Path(r'C:\Users').exists() |
絶対パスかどうか検査 | >>> os.path.isabs(r'C:\Users\abc123') | >>> pathlib.Path(r'C:\Users\abc123').is_absolute() |
ファイルかどうか検査 | >>> os.path.isfile(r'C:\Users\xxxxx\test.txt') * 存在しないパスもFalseになる
| >>> pathlib.Path(r'C:\Users\xxxxx\test.txt').is_file() * 存在しないパスもFalseになる
|
ディレクトリかどうか検査 | >>> os.path.isdir(r'C:\Users\xxxxx\test.txt') * 存在しないパスもFalseになる
| >>> pathlib.Path(r'C:\Users\xxxxx\test.txt').is_dir() * 存在しないパスもFalseになる
|
シンボリックリンクかどうか検査 | >>> os.path.islink(r'C:\Users\sample.txt') | >>> pathlib.Path(r'C:\Users\xxxxx').is_symlink() |
対象の属性情報を取得
操作 | osライブラリのサンプルと実行例 | pathlibライブラリのサンプルと実行例 |
---|---|---|
スタットを取得 | >>> stat = os.stat(r"D:\desktop") | >>> stat = pathlib.Path(r"D:\desktop").stat() |
最終アクセス日時を取得 | >>> os.path.getatime( r"D:\desktop" ) * os.stat(r"D:\desktop").st_atime も同じ | >>> stat.st_atime |
最終更新日時を取得 | >>> os.path.getmtime( r"D:\desktop" ) * os.stat(r"D:\desktop").st_mtime も同じ | >>> stat.st_mtime |
作成日時を取得 | >>> os.path.getctime( r"D:\desktop" ) * os.stat(r"D:\desktop").st_ctime も同じ | >>> stat.st_ctime |
サイズを取得 | >>> os.path.getsize( r"D:\desktop" ) * os.stat(r"D:\desktop").st_size も同じ | >>> stat.st_size |
所有者、グループを取得 | >>> os.stat(r"/usr/abc").st_uid | >>> pathlib.Path(r"/usr/abc").owner() * 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") ] | 直接的な関数はない。iterdirで取得したものをフィルタする。>>> p = pathlib.Path(r"C:\Users") # ジェネレータ式の例 >>> files = [ x for x in p.iterdir() if x.is_file() ] # フィルタ関数の例
|
glob
なお、ファイル・ディレクトリの一覧を取得する別の方法にglobモジュールがある。globはパターンにマッチするファイル・ディレクトリを取得できるものである。Pathlibにもglobモジュールと同等の機能が実装されている。
操作 | globモジュールのサンプルと使用例 | pathlibライブラリのサンプルと使用例 |
---|---|---|
UNIXシェル形式のワイルドカード指定で取得 * : 任意の文字列 ? : 任意の一文字 [] : 特定の一文字 | >>> import glob ※上記はfiles直下の拡張子がtxtを探す。サブディレクトリを再帰的に検索するには**とrecursiveフラグを使う >>> import glob | >>> p = pathlib.Path(r'D:\files') ※上記はfiles直下の拡張子がtxtを探す。サブディレクトリを再帰的に含めて検索するには**を使う >>> items = p.glob('**\*.txt') ※Windowsでは大文字と小文字を区別しないが、case_sensitive引数で挙動を変更できる。 |
ディレクトリ・ファイルの操作
コピー・移動・削除などの操作の一部はpathlibでは提供されていない。そのような場合はosライブラリやshutilライブラリを使う。
コピー
操作 | サンプルと実行例 |
---|---|
ファイルコピー | shutil.copy( 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.values(): # オプションのバリューのイテレータ |
オプションがあるか検査 | 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)
|
正規表現(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") '123456' match[1] None match[2] '45' match.group('name') '45' |
match.groups( default=None ) | キャプチャグループすべての中身を取り出す。 パターン全体は含まれない。 一致しなかったキャプチャグループの値をdefaultで指定できる。 | match = re.search(r"1(.)3(..)?4", "1234") ('2', None) match.groups( default='' ) ('2', '') |
match.start( [group] ) match.end( [group] ) | マッチした位置が文字列全体の中でどこかを表すインデックスを返す。 groupが指定された場合はそのキャプチャグループがマッチした位置を返す。 マッチしていないキャプチャグループの場合は-1。 | match = re.search(r"1(.)3(.)?4", "012345") 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") (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|B | Aまたは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") 後読みアサーションは前の文字が条件を満たすか確認するが、マッチ文字列には含まれない。戻り読みアサーションと呼ばれることもある。 | (?<!...) | 否定後読みアサーション 先行する文字列の制約。 パターンの長さは固定長でなければならない。 後読みアサーションは前の文字が条件を満たすか確認するが、マッチ文字列には含まれない。戻り読みアサーションと呼ばれることもある。 |
\d | 任意の Unicode 10進数字 (Unicode 文字カテゴリ [Nd])。 全角の0や1にもマッチする。 ASCIIフラグが有効な場合は半角の[0-9]のみ。 | \D | \dの反対 |
\s | Unicodeの空白文字。日本語の全角空白も含まれる。 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'