投稿者「admin」のアーカイブ

Pythonの楽な環境構築 2023 (pyenv不使用)

pythonでの環境構築の手順をまとめます。
追記:python3をメインに環境構築についてシンプルに書きなおしました。

pythonの開発環境構築には、いろいろな意見や古い記事も混在していて迷うところなのですが、できるだけシンプルにハマりの少ない方法について考えてみました。
python3をメインに考えています。python2でも基本的な考えは同じですがvenvやらを別途準備する点が面倒になってくると思います。

まとめると

  • 安全にpython3をインストールする
  • venvを使用してプロジェクト用のpython環境を作る

です。

安全にpython3をインストールする

python3を安全に楽にインストールするには公式 python.org から自分のプラットフォーム向けバイナリを入手するのが確実です。

https://www.python.org/downloads

WindowsとMacはこの方法が可能です。Linux環境(もしくはWindowsでもWSL)の場合はソースコードしか配布されていませんが、各Linuxディストリビューションで提供されるパッケージ(aptなど)でビルド済みのバイナリが入手できます。それを使用しましょう。
「パッケージインストールでは個別のバージョン毎のランタイムが管理できない」という状況のときにpyenvやソースコードからのビルドを検討したら良いと思います。

pyenvの使用を書いてある記事も多いのですがイキナリそこから入るのはハマることが多いと思います。複数のバージョンのpythonランタイムを管理したい人が楽するためにpyenvを使うのは良いと思うのですが、「まずpython使って勉強したいんだ」という人はそんなこと気にせず公式バイナリをシステムにインストールしましょう。ランタイムをシステムにインストールしても開発プロジェクトごとにライブラリを管理することはできます(後述のvenv)。

venvを使用してプロジェクト用のpython環境を作る

pythonのインストールが完了したら、次は開発プロジェクトごとの環境を構築します。これはなぜ必要になるかというと、プロジェクトごとに依存ライブラリが違うからです。

あるプロジェクトAはライブラリXのバージョン2を使用し、別のプロジェクトBはライブラリXのバージョン3を使用する場合、ひとつのpython環境では用意するライブラリが競合してしまいます。
そのためシステムにインストールしたpython環境にはライブラリをインストールせずクリーンなままにしておきます。そして各プロジェクトごとにクリーンにpython環境を用意します。

開発プロジェクトのディレクトリで以下のコマンドを実行します。 myenvというのは作成する仮想環境の名前で任意の名称です。venvとか.venvなどが使用されます。

仮想環境作成

$ python3 -m venv myenv

これでmyenvというディレクトリが作成され、インストールしたpython3環境と同じものが構築されます。

Ubuntu Linux環境において、

The virtual environment was not created successfully because ensurepip is not
available.  On Debian/Ubuntu systems, you need to install the python3-venv
package using the following command.

というメッセージが表示された場合は、venvが入っていないことが原因です。venvはpython3では標準インストールされているものなのですが、Debian/Ubuntuの場合は個別にインストールする必要があります。

$ sudo apt install python3-venv

という形でパッケージ版のpython3環境にvenvライブラリをインストール後、もういちどpython3 -m venv myenv をつかって仮想環境を作成してください。

アクティブ化

あとはこのmyenv環境をアクティブにすることで、システムにインストールしたpythonとは分離された環境で自由にライブラリをインストールできます。

$ . myenv/bin/activate
(myenv) $  pip install flask
...
(Windowsの場合は、myenv/Scripts/activate.batになる)

myenv環境でpipを使ってライブラリをインストールできます。どこにインストールされるかというとmyenvディレクトリの中に入ります。このディレクトリをゴソっと削除すればなかったことになりますので自由に環境を汚して構いません。pythonコマンドはシステムのものではなく、myenv環境にあるpythonコマンドが使用されます。

アクティブ解除

(myenv) $  deactivate

このコマンドでアクティブにした仮想環境から出ます。pythonコマンドはシステムのものが使用されるようになり、先ほどインストールしたライブラリは使えなくなります。

インストールされた依存ライブラリを記録する

(myenv) $ pip freeze > requirements.txt

pip freezeでこのmyenv環境にインストールされているライブラリのバージョンを出力できます。このファイルをgitに登録し、myenvディレクトリはgitignoreにしておきます。環境を再現するときには、このrequirements.txtから復元します。

requirements.txtから環境を復元

(myenv) $ pip install -r requirements.txt

myenv環境を作りなおしたときなどには、requirements.txtから読み込んでインストールすることで、環境を再現できます。

Python2の場合は?

pipやvenvが標準でインストールされていないです。

pip

pipはpython2.7.9から標準となっているため、ない場合は別途インストールする必要があります。

Linuxであればpython-pipというパッケージが用意されているものもあります。その他の環境でもこちらを参考にget-pip.pyをダウンロードして実行することで最新のpipを導入できます。このツールはシステムにインストールしても構いません。

virtualenv

venvはpython3.4から標準となっているため、別途virtualenvというツールをインストールする必要があります。(python3のvenvはこれをベースに作られているのでほとんど同じです)

Linuxであればpython-virtualenvというパッケージが用意されているものもあります。pip を使ってvirtualenvをインストールすることもできます。システムのpythonにインストールするため管理者権限が必要になります。このツールはシステムにインストールしても構いません。

$ sudo pip install virtualenv
$ virtualenv myenv
$ . myenv/bin/activate

実はpython3のvenvよりもvirtualenvの方ができることが多くて、作成するpython環境の実行ファイルを指定して環境を作ることができるので、python2.7の環境、python3.5の環境、python3.6の環境などいろいろ混在して作りたい場合は、virtualenvを使って仮想環境を構築します。

参考サイト

__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

参考
pickle — Python オブジェクトの直列化

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

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

>>> def func():
...   a = 10
...   b = 20
...   c = "hello"
...   print vars()  #ローカル変数テーブルのディクショナリ
...   #キーワード引数にアンパックしてformat関数で表示
...   print "a={a}, b={b}, c={c}".format(**vars()) 
... 
>>> 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) が導入されました。※後述

埋め込み

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

# キーワード引数で指定
>>> "A={ab}. B={cd}".format(ab="John", cd="Taro")
'A=John. B=Taro'

# ディクショナリで指定する場合はアンパックすればよい
>>> d = {"a": "John", "b": 20}
>>> "A={a}. B={b}.".format(**d)
        #アンパックしてキーワード引数に
"A=John. B=20."

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

書式指定

書式指定の詳細は、こちらを参照。
6.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};".format(42)
'int: 42;  hex: 2A;'
>>> "oct: {0:o};  bin: {0:b}".format(42)
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'

日付フォーマット文字列はここを参照。
https://docs.python.org/ja/3/library/time.html#time.strftime

Directive Meaning Notes
%a ロケールにおける省略形の曜日名。
%A ロケールにおける省略なしの曜日名。
%b ロケールにおける省略形の月名。
%B ロケールにおける省略なしの月名。
%c ロケールにおける適切な日付および時刻表現。
%d 月の始めから何日目かを表す 10 進数 [01,31]。
%H (24 時間計での) 時を表す 10 進数 [00,23]。
%I (12 時間計での) 時を表す 10 進数 [01,12]。
%j 年の初めから何日目かを表す 10 進数 [001,366]。
%m 月を表す 10 進数 [01,12]。
%M 分を表す 10 進数 [00,59]。
%p ロケールにおける AM または PM に対応する文字列。 (1)
%S 秒を表す 10 進数 [00,61]。 (2)
%U 年の初めから何週目か (日曜を週の始まりとします)を表す
10 進数 [00,53]。年が明けてから最初の日曜日までの全ての
曜日は 0 週目に属すると見なされます。
(3)
%w 曜日を表す 10 進数 [0(日曜日),6]。
%W 年の初めから何週目か (日曜を週の始まりとします)を表す
10 進数 [00,53]。年が明けてから最初の月曜日までの全ての
曜日は 0 週目に属すると見なされます。
(3)
%x ロケールにおける適切な日付の表現。
%X ロケールにおける適切な時刻の表現。
%y 上 2 桁なしの西暦年を表す 10 進数 [00,99]。
%Y 上 2 桁付きの西暦年を表す 10 進数。
%Z タイムゾーンの名前 (タイムゾーンがない場合には空文字列)。
%% 文字 “%” 自体の表現。

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 = {1, 2, 3} # 以前はsetのリテラルがなかったため set([1, 2, 3])のように書いていました。
>>> s
{1, 2, 3}
>>> s.add(3)   #重複するので変化ない
>>> s.add(4)
>>> s
{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 = {1, 2, 3}
>>> s.union({3, 4, 5})  #和集合
{1, 2, 3, 4, 5}
# もしくは s | { 3, 4, 5} と書いても同じ
>>> s | {3, 4, 5}
{1, 2, 3, 4, 5}

>>> s.difference({3, 4, 5})  #差集合
{1, 2}
# もしくは s - { 3, 4, 5} と書いても同じ
>>> s - {3, 4, 5}
{1, 2}

>>> s.intersection({3, 4, 5}) #積集合 (共通部分)
{3}
# もしくは s & { 3, 4, 5} と書いても同じ
>>> s & { 3, 4, 5}
{3}

>>> s.symmetric_difference({3, 4, 5})  #どちらか一方に属する要素
{1, 2, 4, 5}
# もしくは s ^ { 3, 4, 5} と書いても同じ
>>> s ^ { 3, 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, 1]が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の方が良い。

参考:class collections.deque([iterable[, maxlen]])