Pythonの入門書「Pythonチュートリアル」。
もともとPython作者のGuido van Rossum自身が書いたドキュメントが出展で、
理解のしやすさを目指して日本語訳が作られている。
Pythonの更新に対応するため幾度か改版され、第4版は3.9対応を果たしている。
タイトルの通りひたすら「Pythonチュートリアル第4版」を読んでみる。
全てを1つの記事に書くスタイル。読み進めた部分を足していく。
[arst_toc tag=\"h3\"]
Pythonインタープリタの使い方
対話モード
コマンドをttyから読み込むモード。 >>> で複数行のコマンドを受け付ける。
2行目から...で受け付ける。
> python 月 4/11 23:35:41 2022
Python 3.9.11 (main, Apr 11 2022, 01:59:37)
[Clang 10.0.1 (clang-1001.0.46.4)] on darwin
Type \"help\", \"copyright\", \"credits\" or \"license\" for more information.
>>> hoge = True
>>> if hoge:
... print(\"hoge is true\")
...
hoge is true
ソースコードエンコーディング
shebangとは別にファイルの先頭に特殊コメントを書くことでファイルのencodingを指定できる。
UTF8の場合は記述しない。非UTF8の場合にのみ書く。shebangがある場合2行目。
ちなみにコメントは「coding[=:]s*([-w.]+)」の正規表現にマッチすればよい。
#!/bin/sh
# 🍣🍣🍣 coding=cp1252 🍣🍣🍣
とはいえ、教科書的には「# -*- coding: cp1252 -*-」。
気楽な入門編
対話モードの最終評価値はアンダースコア(_)に格納される。へぇ。
型と変数と評価
#加算
>>> 1+1
2
#減算
>>> 5-4
1
#乗算
>>> 3*2
6
#除算
>>> 5/3
1.6666666666666667
>>> 1*(3+9)
12
#変数への代入と評価
>>> hoge=100
>>> hoge
100
#最終評価値の記憶(アンダースコア)
>>> price = 100
>>> tax = 0.25
>>> price * tax
25.0
>>> price + _
125.0
文字列
シングルクォートまたはダブルクォート。バックスラッシュでエスケープ。
文字列リテラルにrを前置することでエスケープ文字をエスケープしない.へぇ。
>>> str = \'hogehoge\';
>>> str2 = str + \'100t200\';
>>> str2
\'hogehoge100t200\'
>>> print(str2)
hogehoge100 200
>>> str3 = str + r\'100t200\';
>>> str3
\'hogehoge100\\t200\'
いわゆるヒアドキュメント。複数行の文字列リテラルはトリプルクォート。
>>> print(\"\"\"
... This is a pen.
... This was a pen.
... This will be a pen.
... \"\"\");
This is a pen.
This was a pen.
This will be a pen.
文字列リテラルを列挙すると結合される。 phpのドット演算子とは異なり文字列リテラルのみに作用する。
文字列リテラルと変数は無理。 phpに慣れてるとやりかねない。
>>> text = (\'文字列1\'
... \'文字列2\' \'文字列3\'
... \'文字列4\')
>>> text
\'文字列1文字列2文字列3文字列4\'
>>> text2 = \'hogehoge\'
>>> text text2
File \"\", line 1
text text2
^
SyntaxError: invalid syntax
インデックス演算子で文字列内の文字(1文字の文字列)にアクセス可。
負の値を指定すると後ろから何個目...というアクセスの仕方ができる。0と-0は同じ。
範囲外アクセス(Out of bounds)でエラー。
>>> str3 = \'123456789\'
>>> str3[3]
\'4\'
>>> str3[-2]
\'8\'
>>> str3[0]
\'1\'
>>> str3[-0]
\'1\'
>>> str3[100]
Traceback (most recent call last):
File \"\", line 1, in
IndexError: string index out of range
文字列とスライス
スライス演算子で部分文字列にアクセス可。始点は含み終点は含まない。
>>> str3[2:5]
\'345\'
>>> str3[3:]
\'456789\'
>>> str3[-2:]
\'89\'
>>> str3[:5]
\'12345\'
参考書にスライスについて面白い書き方がされている。
インデックスとは文字と文字の間の位置を表す。最初の文字の左端がゼロ。
インデックスiからインデックスjのスライス[i:j]は境界iと境界jに挟まれた全ての文字。
例えば[2,5]は t h o 。
+---+---+---+---+---+---+
| P | y | t | h | o | n |
+---+---+---+---+---+---+
0 1 2 3 4 5 6
-6 -5 -4 -3 -2 -1
スライスには範囲外アクセス(Out of range)はない。超えた分を含む最大を取ってくれる。
>>> str3[2:100]
\'3456789\'
Pythonの文字列はImmutable。インデックス演算子によりアクセスした部分文字を書き換えられない。
>>> str3[3] = \'A\'
Traceback (most recent call last):
File \"\", line 1, in
TypeError: \'str\' object does not support item assignment
コピーして新しい文字列を作って加工する。
>>> str4 = str3[2:5]
>>> str4 = str4 + \"hoge\"
>>> str4
\'345hoge\'
リスト
シンプルなコレクション。異なる型の値を格納できる。
リストはミュータブルでスライスアクセスによりシャローコピーを返す。
スライスで戻る新たなリストは元のリストのポインタで値を変更できる。
>>> list = [1,2,3,4,5]
>>> list[2:4] = [100,200]
>>> list
[1, 2, 100, 200, 5]
>>> list[:] = []
>>> list
[]
>>> list.append(100)
>>> list
[100]
#入れ子
>>> list = [1,2,3,4,5,[6,7]]
>>> list
[1, 2, 3, 4, 5, [6, 7]]
フィボナッチ数列
簡単なフィボナッチ数列を例にPythonのいくつかのフィーチャーが説明されている。
まず、多重代入が言語仕様としてサポートされている。
真偽のモデルは「0でない値が真、0だけが偽」のパターン。
ブロックをインデントで表現する。同一ブロックはインデントが揃っている必要がある。
>>> a,b = 0, 1
>>> while a < 10:
... print(a)
... a, b = b, a + b
0
1
1
2
3
5
8
制御構造
if
ブロックはインデントで表現。else ifの短縮系として elif を使用できる。
if .. elif .. elif .. else 。 elifを続けて書ける。
>>> x = int(input(\"整数を入力:\"))
整数を入力:100
>>> if x < 0:
... x = 0
... print('負数はゼロ')
... elif x == 0:
... print('ゼロ')
... elif x == 1:
... print('1つ')
... else:
... print('もっと')
for
C形式、つまり初期値、反復間隔、停止条件の指定では書けないのがポイント。
シーケンス(リスト、文字列)のアイテムに対してそのシーケンス内の順序で反復を書くことになる。
>>> words = [ \'hoge\', \'fuga\', \'foo\']
>>> for w in words:
... print(w, len(w))
...
hoge 4
fuga 4
foo 3
シーケンス内のアイテムがシーケンスの場合、アイテムを直接受け取れる。
>>> users = [ [\'kuma\',1], [\'peco\', 2], [\'hoge\', 3]]
>>> for user, status in users:
... print(user, status)
...
kuma 1
peco 2
hoge 3
Cスタイルの反復条件をループ内で変更する際に終了判定が複雑になるように、
Pythonのスタイルであっても反復対象のシーケンスを直接変更すると面倒なことになる。
本書では、シーケンスをコピーし新しいシーケンスを作って操作する例が示されている。
まぁそうだろうが、本書のここまで辞書(dict)の説明は出てきていない。まぁいいか。
>>> users = { \'hoge\':100, \'fuga\':200, \'peco\':300 }
>>> for user, status in users.copy().items():
... if status == 200:
... del users[user]
...
>>> users
{\'hoge\': 100, \'peco\': 300}
>>> active_users = {}
>>> for user, status in users.items():
... if status == 300:
... active_users[user] = status
...
>>> active_users
{\'peco\': 300}
range
任意の反復を実行するために反復条件を表すシーケンスを定義してやる必要がある。
ビルトイン関数のrange()を使うことで等差数列を持つiterableを生成できる。
range()は省メモリのため評価時にメモリを確保しない。
つまり、range()が返すのはiterableでありシーケンスではない。
第3引数はステップで省略すると1が使われる。
先頭から順に評価時に消費され遂には空になる、というイメージ。
>>> for i in range (1,100,10):
... print(i)
...
1
11
21
31
41
51
61
71
81
91
とはいえ他の処理でシーケンスを作成済みで再利用するケースは多い。
iterableではなく既にコレクションが存在する場合、以下のようになる。
>>> a = [\'hoge\', \'fuga\', \'kuma\',\'aaa\',\'bbb\']
>>> for i in range(len(a)):
... print(i, a[i])
...
0 hoge
1 fuga
2 kuma
3 aaa
4 bbb
iterableを引数に取る関数はある。例えばsum()はiterableを引数に取り合計を返す。
>>> sum(range(10))
45
ループのelse
forループでiterableを使い果たすか1件も消費できないケースでforループにつけたelseが評価される。
ただしforループをbreakで抜けた場合はforループのelseは評価されない。
例えば2から9までの数値について素数か素数でなければ約数を求める処理を構文で表現できる。
ループのelseはtryによる例外評価に似ているという記述がある。え..?
要は「forの処理が期待したパスを通らない場合に評価される」ということだろうか...
イマジネーションの世界..
>>> for n in range(2, 10):
... for x in range(2, n):
... if n % x == 0:
... print(n, \'equals\', x, \'*\', n/x)
... break
... else:
... print(n, \'is a prime number\')
...
2 is a prime number
3 is a prime number
4 equals 2 * 2.0
5 is a prime number
6 equals 2 * 3.0
7 is a prime number
8 equals 2 * 4.0
9 equals 3 * 3.0
pass
構文的に文が必要なのにプログラム的には何もする必要がないときにpassを使う。
もうこれ以上説明は不要。やはり原著は良い。
>>> r = range(1,10)
>>> for i in r:
... if i % 2 == 0:
... print(i)
... else:
... pass
...
2
4
6
8
関数の定義
本書においてスコープの実装が書かれている。言語仕様をわかりやすく説明してくれている。
プログラミング言語自体の実装において変数などのシンボルはスコープの範囲で格納され参照される。
本書においてPythonのスコープは内側から順に以下の通りとなると記述がある。
より外側のスコープのシンボル表は内側のスコープのシンボル表に含まれる。
内側のスコープから外側のシンボル表を更新することはできない。
関数内スコープ
関数を定義したスコープ
グローバルスコープ
ビルトインスコープ
>>> hgoe = 100
>>> def bar():
... hoge = 200
... print(hoge)
...
>>> bar()
200
>>> hoge
100
引数はcall by object reference
Pythonの関数の引数は値渡しなのか参照渡しなのか。原著には簡潔に答えが書かれている。
関数のコールの時点でその関数にローカルなシンボル表が作られる。
ローカルなシンボル表に外側のシンボル表の値の参照がコピーされる。まさに事実はこれだけ。
call by valueに対して、call by object referenceという表現がされている。
引数が巨大であっても関数のコールの度に値がコピーされることはないし、
関数スコープで引数を弄っても外側のスコープに影響することはない。
関数の戻り値
Pythonにはprocedureとfunctionの区別がない。全てfunction。
procedureであっても(つまり明示的にreturnで返さなくても)暗黙的にNoneを返す。
>>> def bar():
... hoge = 100
...
>>> print(bar())
None
>>> def foo():
... hoge = 100
... return hoge
...
>>> print(foo())
100
本書で書かれているフィボナッチ級数をリストで返す関数を定義してみる。
>>> def fib(n):
... result = []
... a, b = 0, 1
... while a >> fib(100)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
引数のデフォルト引数
デフォルト値の評価は関数を定義した時点で定義を書いたスコープで行われる。
まさに原著に書かれているこの書かれ方の通り。
>>> N=300
>>> def foo(hoge, fuga=100, bar=N):
... print(hoge, fuga, bar)
...
>>> foo(100)
100 100 300
>>> foo(100,200)
100 200 300
>>> foo(100,200,500)
100 200 500
そして、デフォルト値の評価は一度しか起きない。デフォルト値がリストなどの可変オブジェクトの場合、
定義時に1度だけデフォルト値が評価されるだけで、コール時にはデフォルト値は評価されない。
本書の例がわかりやすかった。
>>> def foo(hoge,L=[]):
... L.append(hoge)
... return L
...
>>> foo(100)
[100]
>>> foo(200)
[100, 200]
>>> foo(300)
[100, 200, 300]
キーワード引数
キーワード引数によりコール時の引数の順序を変更できる。
デフォルト引数の定義がキーワード引数の定義を兼ねている。
デフォルト定義がない引数は位置が制約された位置引数。
位置引数は必須でありキーワード引数よりも前に出現する必要がある。
>>> def foo(hoge, fuga=100, bar=N):
... print(hoge, fuga, bar)
>>> foo(100,fuga=500)
100 500 300
「*名前」を引数に設定すると、仮引数にない位置指定型引数を全て含むタプルが渡る。
「**名前」を引数に設定すると、仮引数に対応するキーワードを除いた全てのキーワード引数がdictで渡る。
dict内の順序は関数のコール時の指定順序が保持される。
>>> def aaa(kind, *arguments, **keywords):
... for arg in arguments:
... print(arg)
... for kw in keywords:
... print(kw,\':\',keywords[kw])
...
>>> aaa(\"111\", \"222\", \"333\", hoge=\"444\", fuga=\"500\", poo=\"600\")
222
333
hoge : 444
fuga : 500
poo : 600
位置のみ,位置またはキーワード,キーワードのみ指定
引数は位置引数,キーワード引数のいずれにでもなることができるが出現位置は決められている。
引数リストの前半は位置引数, 後半はキーワード引数であり, 位置引数はMust、キーワード引数はOptional。
Optionalな部分は位置引数なのかキーワード引数なのか文脈で決まることになる。
言語仕様によって,どの引数が「位置引数限定」,「キーワード引数限定」,「どちらでも良い」かを指定できる。
特殊引数 / と * を使用する。 /の前に定義した引数は位置引数としてのみ使用できる。
また / と * の間に定義した引数は位置引数,キーワード引数のいずれでも使用できる。
* の後に定義した引数はキーワード引数としてのみ使用できる。
/が無ければ位置引数指定がないことを表す。*が無ければキーワード指定がないことを表す。
つまり / も * もない場合は、全ての引数が位置引数にもキーワード引数にもなれるデフォルトの挙動となる。
>>> def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
... print(pos1, pos2)
... print(pos_or_kwd)
... print(kwd1, kwd2)
...
>>> f(10,20,30,kwd1=40,kwd2=50)
10 20
30
40 50
# 前から最大3個しか位置引数になれないため5個渡すとエラーとなる
>>> f(10,20,30,40,50)
Traceback (most recent call last):
File \"\", line 1, in
TypeError: f() takes 3 positional arguments but 5 were given
# h, zを位置引数に限定。キーワード指定して呼ぶとエラーとなる
>>> def j(h,z,/):
... print(h,z)
...
>>> j(200, z=100)
Traceback (most recent call last):
File \"\", line 1, in
TypeError: j() got some positional-only arguments passed as keyword arguments: \'z\'
# h, zをキーワード引数に限定。位置指定して呼ぶとエラーとなる
>>> def n(*,h,z):
... print(h, z)
...
>>> n(100, z=200)
Traceback (most recent call last):
File \"\", line 1, in
TypeError: n() takes 0 positional arguments but 1 positional argument (and 1 keyword-only argument) were given
本書に微妙な部分を説明する記述があった。
位置引数nameと, キーワード引数リストargsを取る関数fooを定義し,
nameというキーを持つdictを第2引数として渡した場合, nameは必ず位置引数に設定され,
argsには含まれない。そのような呼び方をすると呼んだ時点でエラーとなる。
>>> def foo(name, **args):
... return \'name\' in args
...
>>> foo(1, **{\'name\': 2})
Traceback (most recent call last):
File \"\", line 1, in
TypeError: foo() got multiple values for argument \'name\'
引数リストにおいてnameを位置引数に限定した場合, **{\'name\':2}はnameに設定されず,
*argsで受けられるようになる。
>>> def bar(name,/,**args):
... return \'name\' in args
...
>>> bar(1, **{\'name\': 3})
True
どの引数を位置引数限定,キーワード引数限定にすべきか手引きが書いてある。
ただ、ちょっとアバウトすぎるというか決めてに書ける。
位置引数にすべき場合は以下。
引数名に本当に意味がない場合
呼び出し時に引数の順序を強制したい場合
いくつかの位置引数と任意個数のキーワード引数を取りたい場合
キーワード引数に限定すべき場合は以下。
引数名に意味がある場合
明示することで関数宣言が理解しやすくなる場合
引数の位置に頼らせたくない場合
特に、キーワード引数とした場合将来引数名が変更されたときに破壊的変更になるから
API定義時には位置引数とすべき、なんて書いてある。え...
位置引数の扱いが変わり、渡した引数が意図しない使われ方をすることを許容するのだろうか。
任意引数
仮引数リストの末尾に*から始まる仮引数を置くと任意の引数を吸収するタプルで受けられる。
# hogeは仮引数, hoge以降に指定した任意の数の値をタプルargsで受ける。
>>> def k(hoge, *args):
... print(hoge)
... print(\'/\'.join(args))
...
>>> k(100,\'a\',\'b\',\'c\',\'d\')
100
a/b/c/d
任意引数以降は全てキーワード引数となる。任意引数以降に位置引数を定義することはできない。
キーワード引数はOKなので,任意引数の後ろに新たな引数を置くことはできる。
その引数はキーワード引数となる。
>>> def concat(*args, sep=\'/\'):
... return sep.join(args)
...
>>> concat(\'hoge\',\'fuga\',\'foo\')
\'hoge/fuga/foo\'
引数のアンパック
変数のコレクションがあり、コレクションから変数にバラす操作をアンパックという。
引数として渡すべき変数の位置でコレクションからアンパックする、という操作をおこなえる。
*演算子によりシーケンスをアンパックできる。
例えば、シーケンス [1,5] があり、このシーケンスからrange(1,5) を作る場合は以下。
>>> cols = [1, 5]
>>> v = range(*cols)
>>> v
range(1, 5)
また**演算子によりdeictionaryをアンパックできる。
>>> def z(hoge=300, fuga=500):
... print(hoge, fuga)
...
>>> z()
300 500
>>> dict = { \'hoge\': 100, \'fuga\' : 200 }
>>> z(**dict)
100 200
lambda式
無名関数。関数オブジェクトを返す。通常の関数とは異なり単一の式しか持てない制限がある。
2個の引数を取り,それぞれの和を求める関数オブジェクトを返すlambdaを定義し使ってみる。
>>> bar = lambda a,b : a+b
>>> bar(100,200)
300
lambdaが定義された位置の外側のスコープの変数を参照できる。
これはlambdaが関数のシュガーシンタックスで、関数の入れ子を書いているのと同じだから。
例えば以下のように1個の引数xをとるlambdaにおいて外側にある変数nを参照できる。
>>> def make_incrementor(n):
... return lambda x: x + n
...
>>> f = make_incrementor(42)
>>> f(0)
42
>>> f(10)
52
ドキュメンテーション文字列(docstring)
関数定義の中にコメントを書くPython固有のコメント仕様について決まりがまとまっている。
1行目は目的を簡潔に要約する。英文の場合大文字で始まりピリオドで終わること。
よくあるダメコメントパターンの1つである変数名自体の説明は避けるなどが書かれている。
2行目は空行。3行目以降の記述と1行目の要約を視覚的に分離する。
関数オブジェクトの__doc__属性を参照することでdocstringを取得できる。
>>> def my_func():
... \"\"\"Do nothing, but document it.
...
... No, really, it doesn\'t do anything.
... \"\"\"
... pass
>>> print(my_func.__doc__)
Do nothing, but document it.
No, really, it doesn\'t do anything.
関数アノテーション
ユーザ定義関数で使われる型についてのメタデータ情報を任意に付けられる。
アノテーションは関数の__annotations__属性を参照することで取得できる。
仮引数のアノテーションは仮引数名の後にコロンで繋いで指定。
関数の型のアノテーションは def の最後のコロンの手前に->で繋いで指定。
>>> def f(ham: str, eggs: str = \'eggs\') -> str:
... print(\"Annotations:\", f.__annotations__)
... print(\"Arguments:\", ham, eggs)
... return ham + \' and \' + eggs
...
>>> f(\'hoge\')
Annotations: {\'ham\': , \'eggs\': , \'return\': }
Arguments: hoge eggs
\'hoge and eggs\'
コーディング規約(PEP8)
ざっくりPEP8の要点が書かれている。
インデントはスペース4つ。タブは使わない。
1行は79文字以下
関数内で大きめのブロックを分離するために空行を使う
コメント行は独立
docstringを使う
演算子の周囲やカンマの後ろにはスペースを入れるがカッコのすぐ内側にはいれない
クラス、関数名は一貫した命名規則を使う。クラス名はUpperCamelCase、関数名はlower_case_with_underscores
メソッドの第1引数は常にself
エンコーディングはUTF8
データ構造
リストの操作
コレクションに対する操作方法が解説されている。破壊的メソッドはデータ構造を変更した後Noneを返す。
# 末尾に追加
>>> hoge = [1,2,3,4,5]
>>> hoge.append(6)
>>> hoge
[1, 2, 3, 4, 5, 6]
# iterableを追加
>>> hoge.extend(range(7,9))
>>> hoge
[1, 2, 3, 4, 5, 6, 7, 8]
# これは以下と等価
>>> hoge = [1,2,3,4,5]
>>> hoge[len(hoge):] = range(6,9)
>>> hoge
[1, 2, 3, 4, 5, 6, 7, 8]
# insert
>>> hoge.insert(3,100)
>>> hoge
[1, 2, 3, 100, 4, 5, 6, 7, 8]
# remove
>>> hoge.remove(3)
>>> hoge
[1, 2, 100, 4, 5, 6, 7, 8]
# pop
>>> hoge.pop()
8
>>> hoge
[1, 2, 100, 4, 5, 6, 7]
# pop(i)
>>> hoge.pop(4)
5
>>> hoge
[1, 2, 100, 4, 6, 7]
# clear
>>> hoge.clear()
>>> hoge
[]
# []
>>> hoge = [1,2,3,4,5]
>>> hoge[2:4]
[3, 4]
# count(i) リスト内のiの数を返す。リストの個数ではない
>>> hoge.count(3)
1
# reverse
>>> hoge.reverse()
>>> hoge
[5, 4, 3, 2, 1]
>>> fuga = hoge.copy()
>>> fuga
[5, 4, 3, 2, 1]
リストは比較不可能な要素を持つことができるが、sort()等のように順序を使うメソッドは比較を行わない。
>>> bar = [3,1,2,4,5]
>>> bar.sort()
>>> bar
[1, 2, 3, 4, 5]
>>> foo = [3,1,2,4,None,5]
>>> foo
[3, 1, 2, 4, None, 5]
>>> foo.sort()
Traceback (most recent call last):
File \"\", line 1, in
TypeError: \'<' not supported between instances of 'NoneType' and 'int'
リストをスタック、キューとして使う
引数無しのpop()により末尾の要素を削除し返すことができる。append()とpop()でLIFOを作れる。
insert()とpop(0)によりFIFOを作ることもできるが,押し出されるデータの再配置により遅いため,
deque()を使うとよい。deque()は再配置がなく高速。
# LIFO
>>> stack = [1,2,3,4,5]
>>> stack.append(6)
>>> stack.pop()
6
>>> stack
[1, 2, 3, 4, 5]
# FIFO (Slow)
>>> stack.insert(0,100)
>>> stack.pop(0)
100
>>> stack
[1, 2, 3, 4, 5]
# FIFO (Fast)
>>> from collections import deque
>>> queue = deque([1,2,3,4,5])
>>> queue
deque([1, 2, 3, 4, 5])
>>> queue.popleft()
1
>>> queue
deque([2, 3, 4, 5])
リスト内包(list comprehension)
list comprehensionの日本語訳がリスト内包。本書には等価な変形が書かれていて、説明にはこれで十分なのではないかと思う。
# forを使って2乗数からなるシーケンスを取得
>>> for x in range(10):
... squares.append(x**2)
...
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# Lambdaを使った等価表現
>>> squares2 = list(map(lambda x: x**2, range(10)))
>>> squares2
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# list comprehension
>>> squares3 = [x**2 for x in range(10)]
>>> squares3
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
構文としては以下。
式
for節
0個以上のfor節やif節
2重のforを1つのリスト内包表記できる。外側のfor,内側のfor,ifの出現順序が保持されていることに注意、という記述がある。
# forによる表現
>>> for x in [1,2,3]:
... for y in [3,1,4]:
... if x != y:
... combs.append((x,y))
...
>>> combs
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
# list comprehension
>>> [(x,y) for x in [1,2,3] for y in [3,1,4] if x != y]
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
タプルのリストなんかも作れる。
>>> [(x, x**2) for x in [1,2,3]]
[(1, 1), (2, 4), (3, 9)]
式を修飾できる。
>>> from math import pi
>>> [str(round(pi,i)) for i in range(1,6)]
[\'3.1\', \'3.14\', \'3.142\', \'3.1416\', \'3.14159\']
入れ子のリスト内包
本書には入れ子のリスト内包の等価表現が書かれている。
行列の転値を得る例で説明されているので追ってみる。
# 元の行列
>>> matrix = [
... [1, 2, 3, 4],
... [5, 6, 7, 8],
... [9, 10, 11, 12],
... ]
>>> matrix
[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
# 2重ループを全てforで書き下した
>>> transposed = []
>>> for row in matrix:
... transposed_row = []
... for i in range(4):
... transposed_row.append(row[i])
... transposed.append(transposed_row)
...
>>> transposed
[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
# 1つのループをfor、もう1つをリスト内包
>>> transposed = []
>>> for i in range(4):
... transposed.append([row[i] for row in matrix])
...
>>> transposed
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
# 全部リスト内包
>>> [[row[i] for row in matrix] for i in range(4)]
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
zip関数
例えばforループにおいて複数のiterableオブジェクトの要素を同時に取得したいときzip()を使う。
何とも書きづらいが, zip(hoge,fuga,foo)とすることでhoge,fuga,fooを1つにまとめることができ,
それをforループ内の変数に展開することができる。
# zip()について
>>> hoge = [1,2,3]
>>> fuga = [4,5,6]
>>> foo = [7,8,9]
>>> zip(hoge,fuga,foo)
# hoge, fuga, fooを固めたものから 変数x,y,zで取り出す
>>> for x,y,z in zip(hoge,fuga,foo):
... print(x,y,z)
...
1 4 7
2 5 8
3 6 9
matrix=[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]をアンパックすることで、
[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]が得られる。
これをzip()に与えると3つの要素を持つ4つのタプルにアクセス可能なオブジェクトが得られる。
forループの変数で受けると(1,5,9),(2,6,10),(3,7,11),(4,8,12)が得られる。
>>> for x in zip(*matrix):
... print(x)
...
(1, 5, 9)
(2, 6, 10)
(3, 7, 11)
(4, 8, 12)
>>> list(zip(*matrix))
[(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]
del
リストの要素をインデックス指定またはスライス指定で削除できる。
変数自体を削除できる。
>>> hoge = [1,2,3,4,5,6]
>>> del(hoge[3])
>>> hoge
[1, 2, 3, 5, 6]
>>> del(hoge[2:5])
>>> hoge
[1, 2]
>>> del hoge
>>> hoge
Traceback (most recent call last):
File \"\", line 1, in
NameError: name \'hoge\' is not defined
タプル
リスト、タプルともにシーケンスだがリストはmutable(可変体)、タプルはimutable(不変体)。
シーケンスであるから、文字列、リストと同様にインデックスアクセスできる。
本書では空のタプル、要素数が1のタプルの作り方が紹介されている。
>>> t = 1,2,3,4,5
>>> t
(1, 2, 3, 4, 5)
>>> u = t, (1,2,3,4,5)
>>> u
((1, 2, 3, 4, 5), (1, 2, 3, 4, 5))
>>> u[1][2]
3
# 要素数がゼロのタプルを作る
>>> empty = ()
>>> empty
()
# 要素数が1のタプルを作る
>>> singleton = \'hoge\' ,
>>> singleton
(\'hoge\',)
# 1個の要素を()で囲ってもタプルにならない!
>>> singleton2 = (\'hoge\')
>>> singleton2
\'hoge\'
タプルパッキングとシーケンスアンパッキングについて紹介されている。
要はカンマで区切った一連の要素はタプルに入る。
また、右辺のシーケンス(タプルでなくても良い)の要素を左辺の変数に代入できる。
多重代入はシーケンスアンパッキングであるという記述がある。
# タプルパッキング
>>> foo = 1,2,3
>>> foo
(1, 2, 3)
# シーケンスアンパッキング
>>> a,b,c = foo
>>> a
1
>>> b
2
>>> c
3
集合
重複しない要素を順序を持たないで保持するコレクション。いわゆる集合演算を備えている。
主に存在判定に用いるという記述がある。重複と順序がなければ任意の値へ高速にアクセス可能なデータ構造で実装できる。
空集合の作り方は少し異なる。間違って空の辞書を作ってしまわないように注意。
>>> hoge = {1,2,3,4,5}
>>> hoge
{1, 2, 3, 4, 5}
# 空の集合
>>> phi = set()
>>> phi
set()
# 空のディクショナリ
>>> phi2 = {}
>>> phi2
{}
集合内包も可。
>>> z = set()
>>> for x in \'abracadabra\':
... if x not in \'abc\':
... z.add(x)
...
>>> z
{\'d\', \'r\'}
>>> z2 = { x for x in \'abracadabra\' if x not in \'abc\'}
>>> z2
{\'d\', \'r\'}
辞書
連想配列。キーをインデックス、スライスで書き換えられないデータ構造。
辞書は、値を何らかのキーと共に格納しキー指定で値を取り出すことを目的とするデータ構造。
存在するキーを再代入することで上書き。存在しないキーによるアクセスはエラー。
キーに対してimmutableである前提を置くことでインデックス、スライスで書き換えられないことを保証する。
数値、文字列、immutableな要素だけからなるタプルはキーになる。
可変な要素を持つタプルやリストについては、キー自体を変更できてしまうことになるからNG。
言い換えると辞書は「キー:バリュー」を要素とする集合。
# 初期化
>>> c = { \'hoge\': 100, \'fuga\': 200, \'foo\': 300 }
>>> c
{\'hoge\': 100, \'fuga\': 200, \'foo\': 300}
# キーバリュー追加
>>> c[\'bar\'] = 400
>>> c
{\'hoge\': 100, \'fuga\': 200, \'foo\': 300, \'bar\': 400}
# キーによるアクセス
>>> c[\'fuga\']
200
# キーバリューの削除
>>> del(c[\'foo\'])
>>> c
{\'hoge\': 100, \'fuga\': 200, \'bar\': 400}
# キーの存在チェック
>>> \'hoge\' in c
True
>>> \'hogehoge\' in c
False
注釈に「連想記憶(associative memories)という名前のデータ型をもったプログラム言語はない」という記述がある。
この辺りの使われ方がカオスな言語としてphpがあると思うが、phpは「配列」の添え字として数値も文字列も使える、
という仕様であって「連想配列」という型があるわけでない。
# 順序なしでキーをリスト化 (キーの登録順??)
>>> list(c)
[\'hoge\', \'fuga\', \'bar\']
# キーでソートしてキーをリスト化
>>> sorted(c)
[\'bar\', \'fuga\', \'hoge\']
辞書内包もできる。
>>> { x: x**2 for x in (2,4,6)}
{2: 4, 4: 16, 6: 36}
辞書の初期化は色々バリエーションがある。
# dictのコンストラクタにタプルのリストを指定する
>>> d = dict([(\'hoge\',100),(\'fuga\',200),(\'foo\',300)])
>>> d
{\'hoge\': 100, \'fuga\': 200, \'foo\': 300}
# dictのコンストラクタに個数可変のキーワード引数を指定する
>>> e = dict(hoge=100,fuga=200,foo=300)
>>> e
{\'hoge\': 100, \'fuga\': 200, \'foo\': 300}
ループの仕方
辞書からキーバリューを取る。
>>> hoge = { \"hoge\" : 100, \"fuga\" : 200, \"foo\" : 300 }
>>> for k, v in hoge.items():
... print(k,v)
...
hoge 100
fuga 200
foo 300
シーケンスからインデックスと値をとる。
>>> fuga = [ 1, 3, 5, 7 ]
>>> for i,j in enumerate(fuga):
... print(i, j)
...
0 1
1 3
2 5
3 7
2つ以上のシーケンスから同時に値をとる。
>>> ary1 = [ \"a\", \"b\", \"c\" ]
>>> ary2 = [ 100, 200, 300 ]
>>> for i, j in zip(ary1, ary2):
... print(i, j)
...
a 100
b 200
c 300
条件
条件についての諸々が書いてある。
論理演算子の優先順位は not > and > or。なので A and not B or C = (A and (not B)) or C。
論理演算子andとorは短絡評価。if A and B and C において BがFalseであればCは評価されない。
最後に評価された A and B が全体の評価結果となる。
比較は連鎖可能。if a < b == c と書くと、a < b と b == c の2つが評価される。
a > 1 and b > 3 を 1 < a < 3 と書ける。
式の中での代入は:=演算子を使わないとできない。
# 式の中での代入は:=
>>> if a := 100 == 100 :
... print(\"hoge\")
...
hoge
# C風の書き方はNG
>>> if a = 100 == 100 :
File \"\", line 1
if a = 100 == 100 :
^
SyntaxError: invalid syntax
シーケンスの比較
同じシーケンス型同士を比較が出来てしまう。
前から順に再帰的に要素を比較する。ある時点で要素が異なっていればその比較結果が最終結果。
最後まで要素が同じであれば、シーケンスは同じ判定になる。
片方が短い場合、短い方が小となる。
文字の比較はUnicodeコードポイント番号の比較が行われる。
異なる型の比較の場合、オブジェクトがその比較をサポートしている限り行われる。
比較をサポートしていない場合エラー。
>>> (1,2,3) >> (1,2,3) >> (1,2) >> \'a\' < 'b' >> \'c\' < 'b' >> 10 >> 1 == \"1\"
False
# 整数と文字列の > はサポートされていないためエラー
>>> 1 > \"1\"
Traceback (most recent call last):
File \"\", line 1, in
TypeError: \'>\' not supported between instances of \'int\' and \'str\'
モジュール
呼び出し元のシンボル表を汚さないimport
hoge.pyというファイルに関数fugaを用意しモジュールhogeをインポートする。
関数fuga()の完全な名称はhoge.fuga。hogeはモジュール名,fugaはモジュール内の関数名。
モジュールはimport元とは異なるローカルなシンボル表を持つ。
importによってモジュール内のシンボルが呼び出し元のシンボル表を汚すことはない。
~/i/pytest cat hoge.py 26.7s 土 4/30 14:40:15 2022
def fuga(v):
print(v)
~/i/pytest python
Python 3.9.11 (main, Apr 11 2022, 01:59:37)
[Clang 10.0.1 (clang-1001.0.46.4)] on darwin
Type \"help\", \"copyright\", \"credits\" or \"license\" for more information.
>>>
>>> import hoge
>>> hoge.fuga(123)
123
>>> foo = hoge.fuga
>>> foo(321)
321
モジュール内のシンボルを呼び出し元のシンボル表に直接取り込む
とはいえ、モジュール名を修飾しなければならないのはあまりに遠すぎる。
モジュールではなくモジュール内のシンボルを直接呼び出し元に取り込むことができる。
以下の通りhogeモジュール内の関数fugaを呼び出し元のシンボル表に直接ロードし呼び出している。
なお、この場合モジュール自体は呼び出し元のシンボル表に取り込めない。
呼び出し元に同名のシンボルがある場合、上書きされる。
>>> def fuga(v):
... print(v**2)
...
>>> fuga(3)
9
>>> from hoge import fuga
>>> fuga(3)
3
より楽をしたいのであればimport * を使うとモジュール内のアンダースコア(_)で始まるシンボル以外の全てを読み込むことができる。
ただ、シンボル名を指定しないで呼び出し元のシンボル表を上書きするのはあまりに乱暴なので、通常推奨されない。
>>> from hoge import *
>>> fuga(300)
300
モジュール内のシンボルをインポートする際に、呼び出し元のシンボルを上書きしないために、
別名をつけてインポートすることができる。
>>> from hoge import fuga as foo
>>> foo(3)
3
モジュールはimportされた最初の1回だけ評価される。
関数であれトップレベルに書いたコードであれ最初の評価時に1回実行される。
ロード済みのモジュールを変更する場合インタープリタの再ロードが必要となる場合がある。
または明示的にimportlib.reload()を使ってモジュールをリロードする。
>>> import importlib
>>> importlib.reload(hoge)
モジュールから他のモジュールをimportすることはできる。
慣例ではimport文はモジュールの先頭で記述すべきだが先頭でなくても許容される。
モジュールをスクリプトとして実行可能にする
pythonコマンドの引数としてモジュールを渡すと、モジュール内において__name__が__main__となる。
これを利用して、pythonコマンドの引数として実行された場合にのみ動くコードを付与できる。
まぁ、モジュール単体でスクリプトからデバッグする時なんかに使うんだろう。
# hoge.py
def fuga(v):
print(v)
if __name__ == \"__main__\":
import sys
fuga(int(sys.argv[1]))
# モジュールのインポート時はifブロック内は評価されない
>>> from hoge import fuga
>>>
# pythonコマンドの引数として実行した場合にifブロック内が評価
~/i/pytest python hoge.py 3 1100ms 水 5/ 4 21:30:09 2022
3
モジュール検索パス
指定したモジュールを探す順序。同名のモジュールが複数ある場合には優先してインポートされる。
例えば hoge という名前をモジュール名として指定した場合、hoge.py を探し出す。
ビルトインモジュール内。無ければ以下
sys.path変数に格納されるディレクトリリスト。初期値は以下。
入力スクリプトがあるディレクトリ、カレントディレクトリ/li>
環境変数 PYTHONPATH
インストールごとのデフォルト?
やたら曖昧で文書を読むのが嫌になるような書かれ方をしている。合っているのか?解釈してみる。
sys.pathはappend()等により変更できる。sys.pathの初期値は直感と合うように構成されている。
基本的にはプロジェクトディレクトリにモジュールを配置する訳で、標準ライブラリよりも先に
ユーザ定義モジュールが読まれるように探してもらいたい。
ユーザ定義モジュールが無い場合に標準ライブラリを探して欲しい訳だから、
標準ライブラリはsys.pathの後の方に配置する。
標準ライブラリと同じ順位の位置にユーザ定義モジュールを置くと「置き換え」の扱いとなる。
この「置き換え」について事故が起こらないような仕組みがあり後述する。
コンパイル済みPythonファイル
モジュールの読み込みを高速化する目的で、
Pythonはモジュールファイルをプラットフォーム非依存の形式でキャッシュする。
あくまでも読み込みが高速化されるだけで、読み込まれたコードの実行が速くなる訳ではない。
キャッシュ場所は__pycache__ディレクトリ。
キャッシュヒット判定はモジュールファイルの最終更新日時で行われる。
つまり新しいモジュールファイルがあればヒットせずソースが読まれる。
モジュールのソースを削除しキャッシュだけを配置すると、
常にキャッシュが読まれる。この仕組みにより「ソース無し配布」が可能になる。
スクリプトから読み込む場合、常にキャッシュは使われない。
パッケージ
直感的には名前空間の定義。異なる名前空間のモジュール同士、シンボル名の衝突を避けられる。
公式リファレンスは以下。インポートシステム
多くの処理系で、名前空間を解決するために結構泥臭い実装になっている部分。
以下のディレクトリ階層と__init__.pyにより、dir1、dir1_1、dir1_2パッケージを定義する。
tree . 水 5/ 4 22:32:48 2022
.
└── dir1
├── __init__.py
├── dir1_1
│ ├── __init__.py
│ ├── p1.py
│ ├── p2.py
│ └── p3.py
└── dir1_2
├── __init__.py
├── q1.py
├── q2.py
└── q3.py
dir1パッケージの下にdir1_1、dir1_2パッケージがある。dir1_1パッケージの下にp1,p2,p3モジュールがある。
p1,p2,p3はモジュールであり、実際には各モジュール内に関数やクラスなどのimportすべきシンボルがある。
例えばp1の中にhoge_p1()という関数があるとして、以下でhoge_p1をimportできる。
なお、dir1直下の__init__.pyには\"__init__.py dir1\"、
dir1_1直下の__init__.pyには\"__init__.py dir1_1\"という文字列をprint()している。
# dir1.dir1_1パッケージのp1モジュールをインポートしhoge_p1()を実行
>>> import dir1.dir1_1.p1
__init__.py dir1
__init__.py dir1_1
>>> p1.hoge_p1()
This is p1.
読み込みシーケンスとしては、まず dir1直下の__init__.py内のコードが実行され dir1名前空間の初期化が終わる。
次にdir1_1直下の__init__.py内のコードが実行され、dir1_1名前空間の初期化が終わる。
__init__.pyを置くことで初めてdir1,dir1_1が名前空間であることが定義される。
ワイルドカードimport
dir1.dir1_1の下にある p1,p2,p3...を呼び出すために dir1.dir1_1.p1 のようにモジュール名(p1)までを
指定しないといけないのであれば、p1,p2,p3それぞれを個別にimportしないといけなくなる。
またもしp4が追加された場合、 呼び出し元にp4のimportを追加しないといけなくなるかもしれない。
dir1.dir1_1をimportするだけでp1,p2,p3を呼び出せることを期待してしまう。
それを実現するために__init__.pyを使うことができる。
ワイルドカード(*)を使ったimportを行う際、__init__.pyに対象のモジュールを__all__に
定義しておかないと、ワイルドカード(*)importでは何もimportされない。
例えば、dir1_1直下の__init__.pyで__all__としてp1とp2を指定しp3を指定しない場合、
p1,p2はimportされるがp3はimportされない。このように明示しないと*によるimportは出来ない。
# dir1/dir1_1/__init__.pyの記述
__all__ = [\"p1\",\"p2\"]
# *を使ったimportと実行
>>> from dir1.dir1_1 import *
__init__.py dir1
__init__.py dir1_1
>>> p1.hoge_p1()
This is p1.
>>> p1.hoge_p3()
Traceback (most recent call last):
File \"\", line 1, in
AttributeError: module \'dir1.dir1_1.p1\' has no attribute \'hoge_p3\'
また、別のやり方として、__init__.pyにモジュールのimportを書いておくやり方をしている人がいた。
ディレクトリと対応するパッケージをimpoortすることで同時に配下のモジュールからシンボルをimportする。
この例だと__all__を設定した方が良さそうだが、__init__.pyの動作を理解の助けになる。
# dir1/dir1_1/__init__.pyを以下の通りとする
from .p1 import hoge_p1
from .p2 import hoge_p2
print(\"__init__.py dir1_1\")
# ワイルドカードimport
>>> from dir1.dir1_1 import *
__init__.py dir1
__init__.py dir1_1
>>> p1.hoge_p1()
This is p1.
>>> p2.hoge_p2()
This is p2.
何やら歴史的な経緯があるようで、かなり分かりづらい仕様となっている。
「名前空間パッケージ」と「普通のパッケージ」のようなカオスな世界が広がっている。
python3.3以降、ディレクトリ内に__init__.pyを置かなくても、ディレクトリ階層を名前空間として
認識してくれるような振る舞いになっている。ただ、この振る舞いは名前空間パッケージの一部でしかなく、
無条件に「python3.3以降は__init__.pyは不要である」ということではない。
PEP 420: Implicit Namespace Packages
Native support for package directories that don’t require __init__.py marker files and can automatically span multiple path segments (inspired by various third party approaches to namespace packages, as described in PEP 420)
入出力
文字列のフォーマット
他言語にある変数内展開と近いのはf-string。接頭辞fをつけた文字列の内部にブラケットで括った
式を記述すると、そのブラケット内の変数が文字列に展開される。
式の後ろにフォーマット指定子を指定することで細かい表現ができる。
>>> year = 2020
>>> event = \'hoge\'
>>> f\'Results of the {year} {event}\'
\'Results of the 2022 hoge\'
>>> import math
>>> f\'πの値はおよそ{math.pi:.3f}である。\'
\'πの値はおよそ3.142である。\'
>>> table = {\'hoge\':100,\'fuga\':200,\'foo\':300}
>>> for key,value in table.items():
... print(f\'{key:10} ==> {value:10d}\')
...
hoge ==> 100
fuga ==> 200
foo ==> 300
stringモジュール内にあるTmeplateクラスにも近い機能がある。
SQLのプレースホルダリプレイスメントのような使い方で文字列をフォーマットできる。
>>> from string import Template
>>> hoge = 100
>>> fuga = 200
>>> s = Template(\'hoge is ${hoge}, fuga is ${fuga}\')
>>> print(s.substitute(hoge=hoge,fuga=fuga))
hoge is 100, fuga is 200
str.format()により、文字列の中にプレースホルダを配置し、渡した変数でリプレースする。
プレースホルダ内に位置情報を含めない場合、format()に渡した値が左から順番にリプレースされる。
位置引数やキーワード引数とすることもできる。その場合format()に渡す値の順序に囚われない。
他言語で良くやるコレクションを渡して文字列に展開する方法が書かれている。
# プレースホルダ空文字. フォーマット指定子.
>>> yes_votes = 42_572_654
>>> no_votes = 43_132_495
>>> percentage = yes_votes / (yes_votes + no_votes)
>>> \'{:-9} YES votes {:2.2%}\'.format(yes_votes, percentage)
\' 42572654 YES votes 49.67%\'
# 位置引数
>>> f\'This is {0}, That is {2}, This was {1}, That was {4}\'.format(1,2,3,4)
\'This is 0, That is 2, This was 1, That was 4\'
# キーワード引数
>>> aaa = 300
>>> bbb = 400
>>> \'This is {aaa}, that is {bbb}.\'.format(aaa=aaa,bbb=bbb)
\'This is 300, that is 400.\'
# dictを渡す
>>> table = {\'hoge\': 1, \'fuga\':2, \'foo\': 3}
>>> \'hoge is {0[hoge]:d}, fuga is {0[fuga]:d}, foo is {0[foo]:d}\'.format(table)
\'hoge is 1, fuga is 2, foo is 3\'
# **表記でdictを渡す(可変長引数)
>>> \'hoge is {hoge:d}, fuga is {fuga:d}, foo is {foo:d}\'.format(**table)
\'hoge is 1, fuga is 2, foo is 3\'
単純に加算演算子+を使って文字列を結合して自力でフォーマットできる。
その際、オブジェクトを文字列に型変換する必要がありstr()を使う。
>>> s2 = \'String 1 is \' + str(hoge) + \',String 2 is \' + str(fuga)
>>> s2
\'String 1 is 100,String 2 is 200\'
右寄せはrjust()、左寄せはljust()、中央寄せはcenter()。指定した幅の中で文字列を寄せる。
指定した幅よりも値が長い場合切り詰めない。切り詰める場合、スライスで部分文字列を取得。
print()に複数の値を与えると、各値の間に空白が1つ挿入される。
print()はデフォルトで末尾が改行となるが、キーワード引数でendとして空文字を
渡すことで末尾を空文字に書き換えられる。
# 右寄せ
>>> for x in range(1,11):
... print(repr(x).rjust(2), repr(x*x).rjust(3), end=\' \')
... print(repr(x*x*x).rjust(4))
...
1 1 1
2 4 8
3 9 27
4 16 64
5 25 125
6 36 216
7 49 343
8 64 512
9 81 729
10 100 1000
ゼロ埋めはzfill()。右寄せして左側にゼロを埋める。
>>> for x in range(1,11):
... print(repr(x).zfill(5))
...
00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
C言語のprintf()風の文字列補完
正直最初からこれを使っておけば良い気がするが、printf()のような文字列補完ができる。
>>> \'This is %d, That is %d, This was %d, That was %d\' % (1,2,3,4)
\'This is 1, That is 2, This was 3, That was 4\'
ファイルの読み書き
C言語のfopen()を単純化したようなインターフェースが備わっている。
モードは\'r\'が読み取り専用、\'w\'が書き込み専用、追記なら\'a\',読み書き両用なら\'r+\'。
省略時には\'r\'。それぞれモード文字の末尾に\'b\'を付与することでバイナリ対応可。
開いたファイルはclose()により必ず閉じる必要があり、try-finallyのパターンで対応する。
withを利用することでclose()を省略しつつclose()のコールを保証できる。
withはGCによりリソースを破棄する。実際の破棄はGCのタイミング次第。
# try-finally
>>> def open_hoge():
... try:
... fh = open(\'hoge.txt\', \'r\')
... read_data = f.read()
... finally:
... fh.close()
...
>>> open_hoge()
# with
>>> def open_hoge2():
... with open(\'hoge.txt\',\'r\') as f:
... read_data = f.read()
...
>>> open_hoge2()
>>>
read(SIZE)によりファイルからデータを読み取る。テキストモードの場合、単位は[文字]。
テキストモードの場合UNICODEでもASCIIでも指定した文字だけ取得してくれる。
バイナリモードの場合、単位は[バイト]。
SIZEのデフォルトは-1が指定されていて、ファイル内の全てを読み取る。
省略するとSIZE=-1が使われる。
>>> with open(\'hoge.txt\',\'r\') as f:
... v = f.read(1)
... print(v)
...
h
テキストファイルから各行にアクセスする、というのが良くある使い方。
readline()はファイルから改行コード単位に1行読み込む。
ファイルオブジェクトが開かれている限り,コールにより次の行を読み進める。
最終行を読み取った後、readlineは空文字を返すようになる。
>>> fh = open(\'hoge.txt\',\'r\')
>>> fh.readline()
\'hogehogen\'
>>> fh.readline()
\'fugafugan\'
>>> fh.readline()
\'foofoon\'
>>> fh.readline()
\'\'
ファイルオブジェクトにループをかけると省メモリで全行を読み取れる。
>>> with open(\'hoge.txt\') as f:
... for line in f:
... print(line,end=\'\')
...
hogehoge
fugafuga
foofoo
そして readlines(),list()により各行をシーケンスで取得できる。
>>> with open(\'hoge.txt\') as f:
... ls = f.readlines()
... print(ls)
...
[\'hogehogen\', \'fugafugan\', \'foofoon\']
>>> with open(\'hoge.txt\') as f:
... l = list(f)
... print(l)
...
[\'hogehogen\', \'fugafugan\', \'foofoon\']
write()によりファイルに書き込める。
非文字列を書き込む場合はstr()などにより先に文字列化する必要がある。
>>> with open(\'fuga.txt\',\'w\') as f:
... f.write(\'This is testn\')
...
13 #書き込んだキャラクタの数。
>>> with open(\'fuga.txt\') as f:
... print(f.readline())
...
This is test
# シーケンスを文字列化して書き込む
>>> with open(\'fuga.txt\',\'w\') as f:
... ary = [1,2,3,4,5]
... f.write(str(ary))
...
15
>>> with open(\'fuga.txt\') as f:
... l = f.readline()
... print(l)
...
[1, 2, 3, 4, 5]
構造があるデータをjsonで保存
dumps()により構造化データをJSONにシリアライズできる。
dumps()とwrite()を組み合わせるかdump()を使うことでJSONをファイルに書き込める。
# dictをJSONにシリアライズ
>>> ary = { \'hoge\':100, \'fuga\':200, \'foo\':300 }
>>> json.dumps(ary)
\'{\"hoge\": 100, \"fuga\": 200, \"foo\": 300}\'
# 一度にdictをシリアライズしてファイルに書き込む
>>> dict = {\'hoge\':100, \'fuga\':200, \'foo\':300}
>>> with open(\'fuga.txt\',\'w\') as f:
... json.dump(dict,f)
...
>>> with open(\'fuga.txt\') as f:
... print(f.readlines())
...
[\'{\"hoge\": 100, \"fuga\": 200, \"foo\": 300}\']
# JSONをでシリアライズ
>>> js = json.dumps(dict)
>>> js
\'{\"hoge\": 100, \"fuga\": 200, \"foo\": 300}\'
>>> jjs = json.loads(js)
>>> jjs
{\'hoge\': 100, \'fuga\': 200, \'foo\': 300}
# ファイル内のJSONをdictにデシリアライズ
>>> with open(\'fuga.txt\') as f:
... v = json.load(f)
... print(v)
...
{\'hoge\': 100, \'fuga\': 200, \'foo\': 300}
続く...