概要
自分向けの覚書。
O'REILLYの 初めてのpython を読んで、「使える」と思ったことを列挙。
リスト変更時の注意(6章 6.2.1 共有リファレンスとオブジェクトの上書き)
例えば、以下のようなリストの使い方は注意が必要。
L = [1, 2, 3] # リスト生成
M = L # リストコピー
M[0] = 5 # Mの0番目要素を変更
print M
>>> [5, 2, 3] # よしよし変わってるな
print L
>>> [5, 2, 3] # んん!?、Lのリストは変えたくなかったんだけど・・・
pythonでは、変数に値を代入する際(x = 1など)、変数という箱と値というオブジェクトの2つが生成されます。
変数には、オブジェクトへの参照情報が格納され、変数の同士の代入(y = xなど)は参照情報がコピーされます。
なので、変数Lにオブジェクト[1, 2, 3]の参照情報があり、これをM = Lとすると、変数Mには先ほどのオブジェクト[1, 2, 3]の参照情報が格納されることになります。
よって、M[0] = 5という変更は、Lも参照しているオブジェクトに変更を加える行為となるため、Mだけ変えたつもりでも、Lも変更してしまいます。
これは可変性を持つオブジェクトだけの注意点です。
可変性を持つのは、リスト・ディクショナリー・クラスなので、これらも同じく注意が必要。
やるなら、以下のように、新しくオブジェクトを生成してあげる。
L = [1, 2, 3] # リスト生成
M = L[:] # リストの全要素をコピー
M[0] = 5 # Mの0番目要素を変更
print M
>>> [5, 2, 3]
print L
>>> [1, 2, 3] # Lには変更の影響なし!
ディクショナリあれこれ(8章 8.4 ディクショナリの利用)
keyのリスト取得 〜D.keys()〜
D1 = {'spam':2, 'ham':1, 'eggs':3}
print D1.keys()
>>> ['eggs', 'ham', 'spam']
# D1.keys()でディクショナリのkeyのリストゲット
# keyの順番にディクショナリを処理するときなどに便利
L = D1.keys()
L.sort()
for x in L:
print x, D1[x]
>>> eggs 3
>>> ham 1
>>> spam 2
ディクショナリのマージ 〜D1.update(D2)〜
D1 = {'spam':2, 'ham':1, 'eggs':3}
D2 = {'milk':4, 'ham': 4}
D1.update(D2)
print D1
>>> {'eggs': 3, 'milk': 4, 'ham': 4, 'spam': 2}
# D2にしかない要素は新規追加、D1にもD2にもある要素は更新
二つのリストでディクショナリ作成 〜dict(zip(L1, L2))〜
keys = ['spam', 'eggs', 'toast']
vals = [1, 3, 5]
D3 = dict(zip(keys, vals))
print D3
>>> {'toast': 5, 'eggs': 3, 'spam': 1}
pickleでオブジェクトをファイル保存/読取(9章 9.2.3 ファイルオブジェクトの使用例)
# pickleで保存
D = {'a':1, 'b':2}
import pickle
F = open('dump.pkl', 'w')
pickle.dump(D, F) # pickleでシリアライズ
F.close()
# pickleからロード
F = open('dump.pkl', 'r')
E = pickle.load(F) # pickleでデシリアライズ
print E
>>> {'a':1, 'b':2}
if文系(12章 ifステートメント)
ディクショナリでのif文実行のような書き方。
例えば以下は、branchディクショナリの中の要素の有無チェックをしている。
branch = {'spam': 2, 'ham': 3, 'eggs': 1}
print branch.get('spam', 'nothing!')
>>> 2
print branch.get('bacon', 'nothing!')
>>> nothing!
whileやforのelse文 (13章 13.2 break,continue,passステートメントとelseブロック)
elseの利用
whileやfor文でelseが使える。elseは、ループ中にbreakで抜けたら実行されないが、最後までループしきったら実行される。
例えば、以下の素数判定などのときの利用例
y = 13
x = y /2
while x > 1:
if y % x == 0:
print y, 'has factor', x
break
x = x -1
else:
print y, 'is prime'
# 1より大きい、割り切れる数があればbreak。そうでなければ、else内で素数と出力。
引数に*や**を使用(16章 16.5 引数のバリエーション)
関数の引数を*付や**で定義すると、引数がタプル、ディクショナリ型で受け取れる。
関数を呼ぶときに、何個の変数を利用できるか不明な場合に有効。
# 引数の値がタプルになる。
def f1(*args):
print args
f1()
>>> ()
# 空のタプルが出力される。
f1(1)
>>> (1, )
# 1だけのタプルが出力される。
f1(1, 2, 3, 4)
>>> (1, 2, 3, 4)
# 1, 2, 3, 4のタプルが出力される
def f2(**args):
print args
f2()
>>> {}
# 空のディクショナリが出力される
f2(a = 1, b = 2)
>>> {'a': 1, 'b': 2}
# {'a': 1, 'b': 2}のディクショナリが出力される
関数系(17章 関数に関連する高度なテクニック)
lambda式
名前についてはLISPで使われていた言葉で、λに由来するみたい。
defと同じく関数を定義するために使う。
L = [(lambda x : x ** 2), (lambda x : x ** 3), (lambda x : x ** 4)]
for f in L:
print f(2)
>>> 4
>>> 8
>>> 16
defを使うと、以下となり冗長。しかも一つひとつの関数まで飛んで、内容を確認しないと動作の把握ができない。
def f1(x): retrun x ** 2
def f2(x): retrun x ** 3
def f3(x): retrun x ** 4
L = [f1(2), f2(2), f3(2)]
for f in L:
print f
>>> 4
>>> 8
>>> 16
lambdaは、簡単な関数を利用するときに使うと効果的。
しかも、式がすぐ近くにあるから、視認性が高くて良い。
tensorflowのソースなどにも多用されていて、覚えておいて損はなさそう。
でも、なんかすんなり理解できない・・・。。 これは何度も復習しよう。
map関数
リストなどのシーケンス[文字列(一文字一文字)やディクショナリなど]に対して、同じ関数を適応させたいときに利用。
def inc(x): return x + 10
L = [11, 12, 13, 14, 15]
print map(inc, L)
>>> [21, 22, 23, 24, 25]
lambdaと合わせて利用も可能。
L = [1, 2, 3, 4]
print map((lambda x: x + 3), L)
>>> [4, 5, 6, 7]
リスト内包表記
map同様、ある値達に関数を適応してリストとして結果を受け取ることができる
# シンプルなループ
print [x ** 2 for x in range(5)]
>>> [0, 1, 4, 9, 16]
mapでも同じことが可能だけど、基本リスト内包表記の方がシンプルに書ける。
# 2重ループも表現できるよ
print [x + y for x in 'spam' for y in 'SPAM']
>>> ['sS', 'sP', 'sA', 'sM', 'pS', 'pP', 'pA', 'pM', .. , 'mA', 'mM']
# ifと組み合わせたりもできるよ
print [(x, y) for x in range(5) if x % 2 == 0 for y in range(5) if y % 2 == 1]
>>> [(0, 1), (0, 3), (2, 1), (2, 3), (4, 1), (4, 3)]
# matrixの扱いも得意
M = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
N = [[2, 2, 2],
[3, 3, 3],
[4, 4, 4]]
# Mの2行目を取り出す
print [row[1] for row in M]
>>> [2, 5, 8]
# 各要素をかける
print [[M[row][col] * N[row][col] for col in range(3)] for row in range(3)]
>>> [[2, 4, 6], [12, 15, 18], [28, 32, 36]]
ジェネレータ
関数ににているが、結果をreturnで一発で返さず、任意のタイミングで処理を実行できる。returnがなく、yieldが記述される。
# ジェネレータの作成
def gensquares(N):
for i in range(N):
yield i ** 2
# イテレータを用いた実行
x = gensquares(5) # xにジェネレータを代入
print x.next()
>>> 0
print x.next()
>>> 1
print x.next()
>>> 4
print x.next()
>>> 9
print x.next()
>>> 16
print x.next()
>>> error ...
インポートの処理フロー(18章 18.3 インポートの処理)
インポートとは、以下の流れを指す。
1. import対象モジュールを探す。
2. 見つかったファイルをコンパイルしてバイトコード(.pyc)化する。
3. バイトコードを実行し、import対象モジュールのオブジェクトを作成する。
1.import対象モジュールを探す。
sys.path(import sys; print sys.path
で確認できる)のディレクトリから探す。
sys.pathは、以下4つからなる。
① 実行プログラムの作業ディレクトリ
② 環境変数PYTHONPATHに設定されたディレクトリ
③ 標準ライブラリモジュール(最初から梱包されているライブラリ群[sysやmathなどなど])のディレクトリ
④ .pthファイルに記載されたディレクトリ(myconfig.pthらしいけど、自分の環境に見当たらなかった・・・)
③は、例えばmathであれば、以下のmath.fileで得られるパスにある。これが標準ライブラリのパスの一つになる。
>>> import math; dir(math)
['__doc__', '__file__', '__name__', '__package__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'hypot', 'isinf', 'isnan', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'modf', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'trunc']
>>> math.__file__
'/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-dynload/math.so'
逆に、このmath.soを標準ライブラリのディレクトリ配下からmvすると、mathが使えなくなる。
MacBook-Pro:hoge$ sudo mv /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-dynload/math.so ./
cd ..
MacBook-Pro:work fuga$ python
Python 2.7.10 (default, Jul 14 2015, 19:46:27)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.39)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import math
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: No module named math
2.見つかったファイルをコンパイルしてバイトコード(.pyc)化する。
すでに.pycがあれば、わざわざバイトコード化しない。
ただし、インポート対象の.pyファイルのタイムスタンプの方が.pycよりも大きいなら、バイトコード化する。(.pycが古いみたいだから)
ただ、.pycがファイルとしてできあがるかというと、そうではない。
あくまで今の実行モジュールで利用するためにバイトコード化するだけ。.pycを出力はしません。
3.バイトコードを実行し、import対象モジュールのオブジェクトを作成する。
上から順にオブジェクト生成。
インポートについて(19章 モジュールのインポート、リロード)
importとfromの違い
インポート対象をmodule_test.py、インポートする側をmodule_use.pyとする。
def printer(x):
print x
# importはモジュール全体を読み込む
import module_test
module_test.pirinter('spam')
>>> spam
# from xxx import yyyは、xxxのyyy変数を読み込む
from module_test import printer
printer('spam')
>>> spam