作成者別アーカイブ: admin

pythonでの環境構築

pythonでの環境構築の手順をまとめます。
追記
pythonbrewは現状ではdeprecatedになってしまったので、似たような別プロダクトであるpyenvで記述を修正しました。

pythonbrew, pyenvをインストールする

pyenvはシステムのpythonを使わず、各バージョンのpythonを切り替えて使用できるようにするために使う。例えば2.6, 2.7, 3.0でそれぞれのバージョンに依存したプロジェクトを作成している場合、プロジェクトごとにpython切り替えなければならない。
次のvirtualenvと組み合わせてプロジェクトごとにクリーンな環境を用意する。

virtualenvはパッケージ環境をプロジェクトごとに用意するために使う。virutalenvが使うpythonを「pyenvでインストールしたpython」にすることでシステムpythonへの依存をなくす。

例:
プロジェクトA(python2.6, 依存パッケージA,B)
プロジェクトB(python2.7, 依存パッケージB,C),
プロジェクトC(python3.0, 依存パッケージA,C)

pyenvで利用するPythonのバージョンをインストールする

プロジェクトで使用するバージョンのPythonを用意する。ライブラリのパッケージはvirtualenvの--no-site-packagesでクリーンにインストールされるので、あくまでPythonバージョンとしての用意になる。

システムで用意されているPythonを使う場合には、ここでの作業は不要で次の仮想環境を作成するときに、そのPythonをvirtualenvの--pythonで指定すれば良い。システムにないPythonを使うときなど自前で用意したPythonバージョンを使うときに、pyenvでPythonをインストールする。

$ pyenv install 2.7.5

これでPythonをダウンロード&ビルドしてインストールすることができる。ビルドエラーになった場合は、Common build problems · yyuu/pyenv Wikiなどを参考にしてみる。

インストールされたpythonは~/.pyenv/versions/2.7.5/bin/pythonのようにpyenvのPYENV_ROOT以下にインストールされている。

プロジェクトの仮想環境を作成

virtualenvで仮想環境を作成する。
$ virtualenv --no-site-packages --python [pyenvでinstallした任意のpythonのパス] env
–no-site-packagesでパッケージ環境を空で作成。pythonのパスも指定。envというのは仮想環境の名前なので任意。

作成した仮想環境をアクティブにする。
$ source env/bin/activate
これでpyenvでインストールした特定バージョンのpythonが使われ、パッケージ環境はまっさらになります。

パッケージを利用する

pipで使いたいパッケージを追加。インストールされるのは仮想環境内なので自由にインストールしてよい。
$ pip install my-some-package

インストールされた依存パッケージを記述ファイルに出力する

このプロジェクトで使用されているパッケージをpackages.txtに出力し、環境を再度構築するときや、他の人がすぐに環境構築できるようにする。
$ pip freeze > packages.txt

環境を再構築するとき

まっさらなパッケージ環境から、依存パッケージをインストールし直す時。
$ pip install -r packages.txt
freezeした時に生成されたパッケージ情報から必要なパッケージをインストールする。

__xxx__ 形式の特殊メソッド、特殊フィールド

__xxx__ 形式で定義される特殊メソッド、特殊フィールドについて、よく利用されるもの。

メソッド

__init__

コンストラクタ。インスタンスを生成する際に呼び出される。

>>> class Hoge(object):
...   def __init__(self, name):
...     self.name = name
...
>>> h = Hoge("John")
>>> h.name
'John'

__getitem__, __setitem__

添字付きアクセスに対する[]演算子オーバーロード。添字でのアクセスが可能なオブジェクトになる。

>>> class MyList(object):
...   def __getitem__(self, key):
...     return key+10
...   def __setitem__(self, key, value):
...     print "set {0}={1}".format(key, value)
...
>>> ml = MyList()
>>> ml[0]
10
>>> ml[1]
11
>>> ml[0] = 10
set 0=10

__iter__

イテレータが要求された際に呼びだされるメソッド。イテレータオブジェクトを返す。

__enter__, __exit__

with文に対する入り口、出口の処理。

__cmp__

比較演算子用。__cmp__(self, other)の関数は、self < otherの時に負の値。self == otherの時に0。self > otherの時に正の値を返すように実装する。これでself < otherのように比較演算子が使用できるようになる。__lt__, __le__のような拡張比較メソッドもあり、それを実装した場合は、__cmp__よりも優先される。拡張比較メソッド
※Python3系では、__cmp__は廃止されて、拡張比較メソッドで実装するようになった。__lt__, __eq__を実装することで同等となる。

フィールド

__name__

モジュール内のスクリプトにおいてはモジュール名。関数オブジェクトにおいては関数名になる。メインスクリプトとして実行されたときは__name__は”__main__”になる。

>>> print __name__
__main__
>>> def hoge():
...   pass
...
>>> hoge.__name__
'hoge'

__class__

オブジェクトのクラス。

__file__

スクリプトのファイル名。

__all__

import *した時に、importするモジュールのリスト。パッケージの__init__.pyのモジュールで使用する。

__all__ = ["echo", "surround", "reverse"] 
このパッケージをインポートした時に、3つのモジュールがインポートされる。

__doc__

ドキュメンテーションコメントを参照する変数。

__slots__

オブジェクトがもつ属性のリスト。オブジェクトに属性を追加できないように制限できる。詳細は__slot__を参照。

Pythonでの3項演算子の記述はどうするのか?

単純なケースならばif文よりも簡潔に記述できるので、Pythonでも使いたいです。

JavaScriptの例

var x = (a == 1 ? 10 : 20);  //aの値が1のとき、xに10を代入。そうでないとき20。

同様のことをPythonで記述するには、以下のようになります。

x = 10 if a == 1 else 20

JavaScriptでの記述に慣れているとすごく違和感あります。

JavaScriptタイプの記述はJava, C言語などでも使われているため広く知られている記述方法です。それゆえPython方式の記述は変な感じです。
ただリスト内包表記でのifなども似ているので、Pythonに慣れ親しめばそうでもありません。

ちなみに

この条件的な代入方式の記述を、「3項演算子」って呼ぶのは不適切です。(正しくは条件演算子だそうです。)
3項演算子は「項が3つある演算子」という意味です。+演算子などの2項演算子に対して3つあるから3項演算子です。ですがJavaやC言語において項が3つある演算子はこの条件的な代入しかないため、3項演算子=条件的な代入と定着しています。演算子の種類として3項ではありますが、必ずしも3項演算子=条件的な代入とみなすのはおかしいと思っています。

ただ一般的に、検索したり会話に用いられるこの条件的な代入を「3項演算子」と呼ぶのは紛れも無い事実だと思います。もはや条件演算子の事を3項演算子って呼ぶのは、しかたのないことだと思っています。

pickleモジュールによるシリアライズ(直列化)

データを保存したり復元したりするためにpickleモジュールが使える。

保存

pickle.dump()でオブジェクトをファイルに保存できる。実際にはファイルでなくてもwriteメソッドをもつオブジェクトであればよい。

>>> import pickle
>>> l = [10, 20, 30]
>>> f = open("pickle.dump", "w")
>>> pickle.dump(l, f)            # ファイルに保存
>>> f.close()

復元

pickle.load()でオブジェクトをファイルから復元できる。

>>> f = open("pickle.dump", "r")
>>> l2 = pickle.load(f)          # ファイルから復元
>>> f.close()
>>> l2
[10, 20, 30]

ユーザー定義クラスの保存

>>> class Hoge:                  # Hogeクラスの定義
...   def __init__(self):
...     self.name = "john"
...     self.age = 20
... 
>>> hoge = Hoge()
>>> hoge.name
'john'
>>> hoge.name = "Smith"
>>> hoge.age = 30
>>> hoge.name 
'Smith'
>>> hoge.age
30
>>> f = open("pickle.dump", "w")
>>> pickle.dump([10, 20, hoge], f)  # Hogeインスタンスをリストの要素の一つとして保存
>>> f.close()
>>> f = open("pickle.dump", "r")
>>> l = pickle.load(f)              # 復元
>>> f.close()
>>> l
[10, 20, <__main__.Hoge instance at 0x109e2b128>]
>>> l[2].name
'Smith'
>>> l[2].age
30

参考
11.1. pickle — Python オブジェクトの整列化 — Python 2.7ja1 documentation

vars()関数によるローカル変数の列挙

vars関数でローカルスコープの変数のディクショナリが得られる。

>>> def func():
...   a = 10
...   b = 20
...   c = "hello"
...   print vars()  #ローカル変数テーブルのディクショナリ
...   print "a={a}, b={b}, c={c}".format(**vars()) #キーワード引数にアンパックしてformat関数で表示
... 
>>> func()
{'a': 10, 'c': 'hello', 'b': 20}
a=10, b=20, c=hello

format関数による文字列フォーマット(新しい形式 / 3.6対応)

Python2.6から使用可能なstr.format関数。旧来の%形式の文字列フォーマットよりも好ましい書き方。

波括弧で囲まれたフィールドにパラメータを埋め込んで文字列を指定する。波括弧自体を扱う場合は二重に記述してエスケープする。{{, }}
詳細について 6.1.3. 書式指定文字列の文法

またPython3.6からフォーマット関数と同等の機能をもつ フォーマット済み文字列リテラル(f-string) が導入されました。※後述

埋め込み

>>> "My Name is {0}.".format("John")   #インデックスを指定して埋め込み
'My Name is John.'
>>> "My Name is {0}. Hello {1}".format("John", "Taro")
'My Name is John. Hello Taro'

# キーワード引数で指定
>>> "My Name is {person1}. Hello {person2}".format(person1="John", person2="Taro")
'My Name is John. Hello Taro'

# ディクショナリで指定する場合はアンパックすればよい
>>> d = {"name": "John", "age": 20}
>>> "My name is {name}. I'm {age} years old.".format(**d) #アンパックしてキーワード引数に
"My name is John. I'm 20 years old."

# 属性の取得
>>> class Person:
...   def __init__(self, name, age):
...     self.name = name
...     self.age = age
... 
>>> p = Person('John', 25)
>>> p.name
'John'
>>> "My name is {0.name}. I'm {0.age} years old.".format(p)  #p.name, p.ageを埋め込み
"My name is John. I'm 25 years old."

書式指定

書式指定の詳細は、こちらを参照。
7.1.3.1. 書式指定ミニ言語仕様

# 幅指定
>>> "{0:<10}".format("Hello") #10文字幅。左寄せ
'Hello     '
>>> "{0:^10}".format("Hello") #10文字幅。センタリング
'  Hello   '
>>> "{0:>10}".format("Hello") #10文字幅。右寄せ
'     Hello'
>>> "{0:_>10}".format("Hello") # 詰め文字に_を指定。10文字幅。右寄せ
'_____Hello'
>>> "{0:0>6}".format(123) #前ゼロ埋め
'000123'


# 数値(10進, 16進, 8進, 2進)
>>> "int: {0:d};  hex: {0:X};  oct: {0:o};  bin: {0:b}".format(42)
'int: 42;  hex: 2A;  oct: 52;  bin: 101010'
# 数値 3桁カンマ区切り (Python2.7以降)
>>> "{0:,d}".format(1234567)
'1,234,567'
# 固定小数点3桁
>>> "{0:.3f}".format(12.34)
'12.340'

# 日付フォーマット
>>> import datetime
>>> d = datetime.datetime.now()
>>> "{0:%Y-%m-%d %H:%M:%S}".format(d)
'2012-10-07 08:15:03'

Python3.6以降のフォーマット済み文字列リテラル(f-string)

Python3.6からformat関数を使わなくても、fで始まる文字列リテラルf"" で簡単に文字列埋め込みができるようになりました。

フォーマット済み文字列リテラル(f-string)

>>> foo=123
>>> bar="Hello"
>>> f"{foo:0>4}: {bar}"
'0123: Hello'

fで始まる文字列リテラルを使うだけで書式指定などはformat関数と同様です。参照可能な変数名を埋め込むことができます。

forループで便利な zip, enumerate関数

zip関数 複数のシーケンスをまとめてループ

データのzip圧縮についてはこちらを参照

複数のシーケンスオブジェクトを同時にループするときに使用する。要素数が違う場合は一番少ないものに合わせられる。

>>> list1 = [1, 2, 3]
>>> list2 = [4, 5, 6]
>>> for (a, b) in zip(list1, list2):   #list1,list2を同時にループ
...   print a,b
... 
1 4
2 5
3 6

>>> list3 = [7, 8]
>>> for (a, b) in zip(list1, list3):   #要素数が少ないlist3に合わせられる
...   print a,b
... 
1 7
2 8

#行,列変換
>>> list4 = [
...   [1, 2, 3],
...   [4, 5, 6],
...   [7, 8, 9]
... ]
>>> for (a, b, c) in zip(*list4):
...    print a,b,c
... 
1 4 7
2 5 8
3 6 9

enumerate関数 インデックスとともにループ

ループする際にインデックスつきで要素を得ることができる。

>>> list1 = ['a', 'b', 'c']
>>> for (i, x) in enumerate(list1):
...   print i,x
... 
0 a
1 b
2 c

重複のない集合set

Setは重複のない要素をもつ順序なしのコレクションオブジェクト。
リストから重複する要素を取り除いたり、積集合・和集合・差集合のような集合演算を使うために使用します。

>>> s = set([1, 2, 3])
>>> s
set([1, 2, 3])
>>> s.add(3)   #重複するので変化ない
>>> s.add(4)
>>> s
set([1, 2, 3, 4])

>>> l = ["hello", "world", "apple", "world", "python", "apple"]   #重複する要素をもつリスト
>>> l
['hello', 'world', 'apple', 'world', 'python', 'apple']
>>> list(set(l))                       #setを作成して重複を取り除いて再びリスト化する
['python', 'world', 'hello', 'apple']

集合演算

>>> s = set([1, 2, 3])
>>> s.union([3, 4, 5])  #和集合
set([1, 2, 3, 4, 5])

>>> s.difference([3, 4, 5])  #差集合
set([1, 2])

>>> s.intersection([3, 4, 5]) #積集合 (共通部分)
set([3])

>>> s.symmetric_difference([3, 4, 5])  #どちらか一方に属する要素
set([1, 2, 4, 5])

>>> s.issubset([3, 1, 2, 5])  # sが [3, 1, 2, 5]の部分集合である場合True
True
>>> s.issubset([3, 1,  5])
False

>>> s.issuperset([3, 1])    # [3, 2]がsの部分集合の場合True
True
>>> s.issuperset([3, 1, 2])
True
>>> s.issuperset([3, 1, 2, 5])
False

map, reduce, filterによるシーケンス操作

シーケンスに対して繰り返し操作するためのビルドイン関数。forで繰り返し処理する代わりに記述できる。mapとfilterはリスト内包表記で記述でき、そちらのほうが概ね高速なのでリスト内包表記で記述したほうが良い。

map: すべての要素に処理を行う

シーケンスのすべての要素を関数の引数として実行し、その実行結果から新しいlistを作成する。

>>> items = [1, 2, 3]
>>> def plus(n):         # 10加算して返すだけの関数plus
...   return n+10
...
>>> map(plus, items)     #itemsの要素すべてにplus関数の実行し、新しいlistを返す
[11, 12, 13]
>>> items                #元のitemsは変更されない
[1, 2, 3]

>>> map(lambda n:n+20, items)   #関数部分をlambda関数で指定
[21, 22, 23]

>>> [x+20 for x in items]       #同じ事をリスト内包表記で(最適な方法)
[21, 22, 23]

filter: 条件に一致する要素のみ抽出する

シーケンスのすべての要素を関数の引数として実行し、Trueを返却した要素のみから新しいlistを作成する。

>>> filter(lambda n:n%2==1, items)     #lambda関数がTrueを返す要素(奇数)要素のみ抽出する
[1, 3]
>>> items             #itemsは不変
[1, 2, 3]

>>> [x for x in items if x%2==1]       #同じ事をリスト内包表記で。(最適な方法)
[1, 3]

reduce: 全部まとめて1つに

シーケンスのすべての要素をまとめて1つの値に集約します。集約する方法は、引数を2つ受け取る関数を用意します。
関数は最初1番目、2番目の要素を受け取りその集約結果を返します。次にその集約結果を引数1に、3番目の要素を引数2に受け取ります。この繰り返しですべての要素を処理します。

文章で書くとややこしいですが、コードを書くと簡単です。

>>> def add_func(a, b):
...   return a+b
...
>>> items = [1, 2, 3]
>>> reduce(add_func, items)        # add_func(add_func(1, 2), 3)
6

両端キューdeque

キューとスタックを使うために両端キュー(deque)が使える。

>>> import collections
>>> d = collections.deque()
>>> d.append("a")
>>> d.append("b")
>>> d
deque(['a', 'b'])
>>> d.popleft()
'a'
>>> d
deque(['b'])
>>> d.popleft()
'b'
>>> d
deque([])

listを使っても同様のことができるが、listは末尾に対する操作は高速でも、dequeのpopleft()やappendleft()のような先頭に対する操作は遅くなる(pop(0), insert(0, v))ので、その場合にはdequeの方が良い。