Pythonを使ってこの方さまざまな点につまずいたが、ここではそんなトラップを回避して快適なPython Lifeを送っていただくべく、書き始める前に知っておけばよかったというTipsをまとめておく。
Python2系と3系について
Pythonには2系と3系があり、3系では後方互換性に影響のある変更が入れられている。つまり、Python3のコードはPython2では動かないことがある(逆もしかり)。
Python3ではPython2における様々な点が改善されており、今から使うなら最新版のPython3で行うのが基本だ(下記でも、Python3で改善されるものは明記するようにした)。何より、Python2は2020年1月1日をもってサポートが終了した。よって今からPython2を使う理由はない。未だにPython2を使う者は、小学生にもディスられる。
しかし、世の中にはまだPython3に対応していないパッケージもあり、自分のプロジェクトでそうしたパッケージに当たるかどうかが運命の分かれ道になる。対応状況については、こちらから一覧で確認することができる。ただ、ほとんどのパッケージは対応済みかPython3で動かすすべがあると思ってよいと思うし2から3への自動変換もある。逆にPython3に対応していないならメンテナンス状況を疑うべき状況に来ているともいえる。Google App Engineについても第二世代でとうとうPython3に対応した。人工知能の先端的研究機関であるOpenAIでもPython3へ移行しているので、「機械学習周りはまだ~」という者は少なくとも先端でない人間なので、言うことを当てにする必要はない。
Pythonのセットアップ
Pythonの開発をする場合Python本体以外にpip、virtualenvのインストールがほぼ必須になるので、各環境でのPython・pip・virtualenvのセットアップ方法をまとめておく。
機械学習を行いたい場合
Pythonを選択する理由として機械学習を行ってみたい、というのが多くの理由としてあると思うので、そのための方法をまず先に紹介しておく。機械学習のための環境を構築するには、Mac/Linux/Windowsの何れかに関わらずMinicondaの利用を推奨する。
インストール方法は上記のページの通りとなる。Minicondaはアプリケーションごとの環境(仮想環境)を作成する機能の他、機械学習関係のパッケージのインストールが行いやすくなっている。また、GPUに必要なcudaや、Windowsのビルド環境であるmingwなどもインストールが可能であり、単純なパッケージ以外のツールの導入も楽になる。TensorFlowについては、condaでインストールすることでGPU周りのツールもインストールしてくれる他、CPU版の場合高速に動作する(詳細)。
scikit-learnをJupyter Notebook上で行うというような一般的な機械学習の環境の構築は、Minicondaインストール後以下のようなコマンドで作成できる。
conda create -n ml_env numpy scipy scikit-learn matplotlib jupyter
activate ml_env
conda create
でml_env
という環境を作成し、activate
でそれを有効化している。conda
コマンドの詳しい利用方法については、こちらを参考されたし(Windows/Powershellの場合は起動しない。詳細はWindowsのセクション参考)。
Minicondaの環境はDockerfile
でも提供されており、これをFROM continuumio/miniconda3
などで継承して利用することで簡単にコンテナを作成することができる。
- Python2用:docker-images/miniconda
- Python3用:docker-images/miniconda3
Dockerコンテナで機械学習環境を共有したい場合、またHerokuなどのPaaS環境へのデプロイを考えている場合はDockerの使用を検討することをお勧めする(HerokuはContainer Registryの機能でdocker pushに対応している)。
なお、以下2点の理由によりAnacondaは機械学習の環境として推奨しない。
- サイズが重すぎる
- Anacondaはインストールしただけで2~3Gは持っていかれるが、Minicondaはその1/4以下で同等の環境を作成できる
- 依存関係がわからなくなる
- Anacondaはいわゆる全部入りの環境なので、アプリケーションの開発を行った際どのライブラリに依存しているのかわからなくなる。これは、デプロイなどを行う際問題となる
なお、Minicondaでも定期的にconda clean --all
で使っていないパッケージを削除しないと軽く数G持っていかれるので注意が必要。これは、condaが一度ダウンロードしたことがあるパッケージをキャッシュとして保管するためである。今は使っていない古いパッケージであっても消していない限り残るので、これを削除するのが吉。
Pythonの導入
Mac/Linuxの場合
システムデフォルトでPythonが入っているが、開発したいバージョンとは一致しないこともある。そのため、pyenvを利用してプロジェクトごとに利用するPythonのバージョンを切り替えられるようにするのが良い。
ただ、前述の通りMinicondaを利用する場合はこちらでPythonのバージョン管理が可能なため、あえてpyenvを導入する必要はない。さらに言えば、仮想環境の有効化(activate
)を行う際にコマンドのバッティングが発生しシェルがハングするためMinicondaを利用する場合はpyenvを導入しないほうが良い。
- pyenvのインストール
- Mac: 【Mac】Pythonのインストール+pyenvの使い方について
- Linux: PyenvによるPython3.x環境構築(CentOS, Ubuntu)
- Pythonのインストール:
pyenv install x.x.x
で使用するPythonをインストール。その後、pyenv global x.x.x
で使うPythonを設定
各プロジェクトで使用するPythonは、pyenv local x.x.x
で設定する。
なお、pyenvで新しいPython環境をインストールした後はpyenv rehash
が必要。それでもインストールした環境が見つからない場合は、ターミナルを再起動してみる。
Windowsの場合
普通にインストールしてもよいが、Minicondaをお勧める。
なお、2018時点ではPowerShellではまだactivate
が起動しないので注意が必要。いちいちcmd
に切り替えるのが面倒な場合は、pscondaenvs
を導入すれば使うことができる(以下は、グローバルの環境(root
)に導入する場合)。
conda install -n root -c pscondaenvs pscondaenvs
パッケージ管理(pip)/仮想環境構築ライブラリ(virtualenv)の導入
パッケージ管理はライブラリのインストールに、仮想環境はプロジェクトごとにPythonのバージョンや使用するライブラリを切り分けるために導入する。
pip
はPython3.4から標準搭載されているため、現状では別途インストールを行う必要はない。古いPythonを使用する場合は、 get_pip.pyでインストールを行う。
virtualenv
は、pip install virtualenv
で導入可能である。
ただ、公式ではpip/virtualenvの機能を内包したPipenvの利用を推奨している。
Pipenvでは、Node.jsにおけるnpmのように、インストールと同時に依存ライブラリを書き出す、開発用/本番用のインストールライブラリを切り分ける、といったことが容易に可能となっている。
Pythonでの開発の流れ
Pythonの開発はだいたい以下の段取りで行う。
- プロジェクト用の仮想環境を作成する
Pythonではプロジェクトの稼働に必要なライブラリはグローバルにインストールせず、個別の仮想環境にインストールするのが通例で、そのためのツールがvirtualenvやcondaなどになる - 仮想環境に必要なモジュールをインストールする
仮想環境を有効化した後、pipなどを使用しプロジェクトに必要なライブラリをインストールする。インストールしたライブラリはファイルに書き出し、他の人がインストールできるようにする。逆に他の人が作成したツールなどを使う際は、ファイルに書き出された依存ライブラリをインストールする - Pythonプログラムの作成、実行を行う
コマンドに書き直すと以下のような形となる。新式のpipenvを使用する場合、従来のpip/virtualenvを使用する場合、condaを使う場合の3つを紹介する。
pipenvを使用する場合
pipenv
では、明示的に仮想環境の作成を指示する必要がない。pipenv install
でインストールを行えば勝手に仮想環境を作成しそこにインストールしてくれる。ただし、デフォルトではよくわからないパスに作成され、そのままでは統合開発環境などでコード保管の恩恵が受けにくい。
そのため、使用前にはPIPENV_VENV_IN_PROJECT=1
(もしくはtrue
)を設定する方が良い。こうすることで、カレントフォルダの.venv
に仮想環境が構築される。
# 1. プロジェクトのフォルダを作成
mkdir myproject
cd myproject
# 2. pipenvで必要なライブラリをインストール
pipenv install xxxx
## 開発でしか使わないライブラリ(testなどに使うライブラリなど)には`-dev`をつける
pipenv install --dev xxxx
## pipenvでインストールしたライブラリは自動的にPipfile.lockに書き込まれる。これを読み込みインストールする場合は、単にpipenv installを実行する
pipenv install
# 3. 実行する
pipenv run python xxx.py
## shellを起動したい場合は、pipenv shellとする
pipenv shell
# 作成した環境を削除する際は以下
pipenv --rm
pip/virtualenvを使用する場合
# 1. プロジェクトのフォルダを作成
mkdir myproject
cd myproject
# 2. 仮想環境の作成 venvはフォルダ名でここにプロジェクト用の環境が用意される
virtualenv venv
# 3. 仮想環境を有効化し、pipで必要なモジュールをインストール
# Windowsの場合、venv/Scripts/activate.bat なお、Gitのシェルを利用すると同じようにsourceでできる
# 仮想環境のactivateを行わないと、グローバルにインストールされるので注意 なお、無効化はdeactivate
source venv/bin/activate
pip install xxxx
# pip freezeで作成した一覧(requirements.txt)からインストールする場合は以下
pip install -r requirements.txt
# 4.実行する
python xxx.py
condaを使用する場合
# 1. 仮想環境を作成(virtualenv的なもの)
conda create -n my_env numpy scipy
## 仮想環境の一覧を表示
conda info -e
# 2. 仮想環境を有効化
activate my_env # Windows
source activate my_env # Max/Linux
## 仮想環境に追加でインストール(バージョンを指定する場合conda install scipy=0.12.0など)
conda install scikit-learn
## condaで取得できないものについて、pipでインストール(仮想環境にpipを入れて対応)
conda install pip
pip install Flask
## インストールしたパッケージのアップデート(conda自身はconda update conda)
conda update numpy
# 3. condaで構築した環境を書き出し/読み込み
conda env export > environment.yml
conda env create -f environment.yml
# 4. 仮想環境無効化
deactivate # Windows
source deactivate # Max/Linux
詳細はこちら参照。
Gitによる管理について
バージョン管理の対象としてはずしておくべきなのが、上記の仮想環境のフォルダ(venvなど)と、.pyc
ファイルになる。
.pyc
ファイルは実行を高速化するためのファイルで、一旦実行すると一ファイルにつき一つできていく。これをバージョン管理に入れてしまうとファイルが二倍になってしまうので、これははずしておく必要がある(-Bオプションをつけるか、環境変数 PYTHONDONTWRITEBYTECODEを設定することで生成しないようにすることもできる(参照))。
その他無視すべきファイルについては、Pythonの.gitignore等を参考にしてください。
Tips
なお、ライブラリによってはpip install
で素直に入らないものも存在する。これはWindowsにおいて顕著である。conda install
で回避可能な場合が多いが、それでも解決しない場合以下の対応を行う。
-
Unofficial Windows Binaries for Python Extension Packagesで該当するパッケージを見つけ、
pip install <file_path>
でインストールを行う(余談だが、wheelを使うことで依存ライブラリをコンパイルした状態でリポジトリに含めることができる。これにより配布・デプロイする際相手先の環境でpip installできないという事態を防ぐことができる。詳細はドキュメント参考)。 - exe形式のファイルが提供されている場合は、easy_installを頑張って入れてインストールする。
- コンパイルするしかない!という場合は・・・まずはVisual Studioをインストールしよう。その後、以下を参考に環境変数を設定をする。
- Python で “error: Unable to find vcvarsall.bat” が出た場合。Add Star
- 「Unable to find vcvarsall.bat」の対処法
- MinGWやCygwinが必要になるケースもあるが、MinGWについては実はcondaでconda install mingwでインストールできるので、これで対応可能な場合もある。ただ、そんな事態の場合は
gfortran
とかがどのみち必要なので、他の環境の整備も考えれば素直にインストールしたほうがいいかもしれない。
- 最終手段として、Windows Subsystem for Linuxを使いLinux環境で開発を行う。セットアップ方法については、こちらを参考されたし。この最終手段が取れるようになったことにより、Windowsで「できない」という状況は事実上なくなった。
これでPythonの開発環境は整ったので、以下からは本題であるPythonで開発をする際に気を付けるべき点を挙げていく。
ファイルのエンコードを宣言する必要がある(2のみ)
各ファイルの先頭に以下を付けておかないと、日本語などは文字化けるので注意。こちらで言及されているように、ファイルの先頭に常につけるようにした方がよいだろう。
# -*- coding: utf-8 -*-
なお、Python3ではデフォルトのエンコードがUTF-8となっているのでこの対応は不要となっている(PEP 3120 -- Using UTF-8 as the default source encoding)。
幾つか下記でも述べるがPython2/3両対応が想定される場合、そうでなくてもPython3の恩恵(特にunicode統一)を受けたい場合は以下のimportもしておくと良い(こちらご参考)。
from __future__ import division, print_function, absolute_import, unicode_literals
コーディングガイドがあり統合開発環境でチェックできる
Pythonには公式に定められたコーディングガイド(PEP8)があり(原文/日本語訳)、多くの統合開発環境ではこれを利用したチェックが可能になっている。
改行が多すぎる、空白文字の有無、といった点など結構細かくチェックでき、自分でも見落としていた点を修正できるので便利。逆に、最初から入れていないと後で修正が大変なので、環境セットアップ時に入れておこう。
その名の通りPEP8に適合しているかどうかをチェックするpep8というパッケージは、現在メンテナンスされておらずpycodestyleとなっているので注意が必要(この背景としては、"pep8"というパッケージ名が規約のことなのかツールのことなのか紛らわしいということで、名称が変更されが提案されたため)。
pep8と合わせ、論理的なチェック(不要なインポートや未使用変数など)のみにとどめたい場合は、pep8のチェックを行うpycodestyleと論理チェックを行うpyflakesがパックになったflake8を推奨する。
Pylintは多様なチェックが可能だが、煩わしく感じられるシーンも多く適切なレベルに設定するにはそれなりコストを払う必要がある。
そのため、まずはflake8、その後コーディング規約をより高度にする必要に迫られた場合にPylintへ移行するというパスを推奨したい。
ユニットテストフレームワークが標準搭載
Pythonにはunittest
が標準搭載されているため、単体テストをするためにパッケージを追加する必要はない。
unittest2
はあたかも新式フレームワークのように見えるが、これは古いバージョンで新しいバージョン用の単体テストフレームワークを利用するためのものなので、基本的に導入する必要はない(余談だがこの数字を振ったパッケージ名はやめてほしいと思う。urllib2とか)。
文字列には通常の文字列とユニコード文字列がある(2のみ)
Pythonでは通常の文字列のstr
とユニコード文字列を扱うunicode
が分かれている。
str = "abc" # An ordinary string
uni_str = u"abc" # A Unicode string
内部的にはstrのままであまり問題ないが、Webアプリケーションなど外部からの入力/外部への出力が絡む場合、unicodeで統一しておいた方がよい(フレームワークを利用する場合は、どちらで入ってくるか、どちらで値を渡すべきかを押さえておく必要がある)。
なお、Python3ではunicodeに統一される。また、Python2でもfrom __future__ import unicode_literals
を使うことでunicode
に統一できる。
割り算の結果がfloat型にならない(2のみ)
Python2系では1/3といった整数同士の割り算の結果がfloat型にならない。
>>> 1/3
0
>>> 3/2
1
これが、Python3では解消する(上記の例では、0.333・・・、1.5という風に普通の算術演算結果となる)。
Python2系でもPython3と同じようにしたい、という場合は以下のimportを先頭で行うことで解消できる。
from __future__ import division
なお、除算では//
という符号も存在する。これは一見商を出すための演算子と思いきや、そうではない。//
による演算は、厳密には「計算結果を超えない最大の整数値」になる。
例えば3を2で割った場合、1.5となり、これを超えない最大の整数は1になる。これは商と等しく問題ないのだが、問題は値が負の場合。
>>> -3 / 2
-2
符号がマイナスになっただけで結果が変わっている。何これ?と思うかもしれないが、マイナスの場合は-1.5を超えない最大の整数は-2
になる、とそういうことになっており、バグではなく仕様である。そして、この仕様はPython3でも同様である。
よって、安全に商を計算したい場合は、結果がfloatになるよう計算し小数点以下を切り捨てる方が良い。Python3であれば、これは単純に/
で計算した結果を切り捨てればよい。
なお、divmod
という商と余りを同時に計算してくれる組み込み関数があるが、この関数における「商」は//
の演算結果と同様なので注意が必要(ドキュメントで「商」としている以上、これはさすがにバグと言っていい気もするが・・・)。
四捨五入の仕様が銀行丸め(3より)
Pythonでの四捨五入にあたるround
処理は、銀行丸めとなっている(最近接偶数への丸め、JIS丸め、ISO丸めとも呼ばれる)。具体的には、小数点以下が「0.5」となる場合偶数に近い方に寄せられる。これは、常に0.5をプラスに切り上げていると丸め前後の集計では(切り上げた)丸め後の集計の方が値が大きくなってしまう=偏りがでてしまうことから行われる処理になっている(詳細はWikipediaの端数処理参照)。
>>> round(3 / 2)
2
>>> round(1 / 2)
0
1 / 2 = 0.5
なので1じゃないのか、と思うがこれは算術丸めの話で、銀行丸めの場合は前述の通り「最も0.5に近い偶数」、すなわち0にまるめられる。なお、Python2では算術丸めなので注意が必要。
Python以外に銀行丸めを採用している言語としては、C#などがある(詳細はこちら参照)。
新スタイルクラスと旧スタイルクラスがある(2のみ)
良く紹介されている下記のクラス宣言では、旧スタイルクラスになる。
class MyClass():
pass
旧スタイルクラスと何が違うか?とはいろいろあるが、少なくとも今旧スタイルクラスで作成することは何のメリットもないということだ。
旧スタイルクラスでは親クラスを呼び出すsuper
が機能しないので何のための継承かわからなくなる。
そのため、クラスは以下のように宣言する。
class MyClass(object):
pass
object
を継承するということだ。
なお、Python3からはデフォルトで新スタイルクラスになり旧スタイルクラスは削除される。
また、Python3では親クラスの呼び出し処理も簡易化される。Python2ではsuper(子クラス名, self).parent_method()
といった煩雑な呼び出し方法だったが、Python3では普通にsuper().parent_method()
で呼び出せるようになる。
抽象クラス/インタフェースがない
継承は可能だが、下位クラスに実装を強制するような仕組みがない。
ただ、Python2.6からはabcが標準で追加され、疑似的に抽象クラスを実装できるようになった(abcとはabstract base classの略)。
from abc import ABCMeta
class MyABC:
__metaclass__ = ABCMeta
def describe(self):
print("I'm abc")
@abstractmethod
def must_implements(self):
pass
Python3ではクラスの作成時にmetaclass
を使用できるようになったので、よりシンプルに書ける。
from abc import ABCMeta
class MyABC(metaclass=ABCMeta):
...
また、register
を使うことであるクラスを自分の配下(サブクラス)に登録できる。ただ、単に「そう振る舞っているように見せる」だけなので、以下例でも実際にtupleがMyABCの継承クラスになっているわけではなく、現にMyABCで実装されているメソッドも使用することができない。
MyABC.register(tuple)
assert issubclass(tuple, MyABC)
assert isinstance((), MyABC)
().describe() # Attribute Error
isinstance
でTrueになっているにもかかわらず、メソッドでAttribute Errorが出るのは正直混乱をきたす気がするので、利用シーンはあまりイメージできない。普通に継承したほうがよいと思う。
もちろん、普通にクラスを宣言して実装が強制されるメソッドは親クラスで例外をraiseするという手もある。
多重継承が可能
Pythonは数少ない多重継承をサポートしている言語になる。
class MultiInheritance(Base1, Base2, Base3):
...
解決は左から順に行われ、上記では自分自身になければBase1、その次にBase2・・・と探索が行われる。
superは、普通に呼んだ場合一番左のクラスになる。
上述の通りPythonにはインタフェースがないため、その代りに多重継承を利用することになる(あるいはModule/Mix-in的な利用)。多重継承はそれ以外の用途には使用しない方が良いと思う。
ただ、見た目は全員クラスになるのでインタフェース的に使用するものはI
から始めるなどクラス名のルールを決めておくと良いだろう(あるいはInterface/Moduleといったクラスを作りそれを継承させるなど)。
コンストラクタ(と思われるもの)が2つある
Pythonには__new__
と__init__
という、どちらともコンストラクタなんじゃないの?と見える関数が2つある。
基本的には__init__
を利用する。
ただ、引数のself
を見ればわかるとおり__init__
は「インスタンスが作成された後の初期化処理」であり、厳密にはコンストラクタではない。__new__
は__init__
で初期化するためのインスタンスを返却する処理になる。やろうと思えば自分のクラス以外のインスタンスを返却したりもできるが、そのメリットはあんまりない。シングルトンの実装に使うぐらいか。
クラスに隠し属性が存在する
Pythonのインスタンスには上述の__init__
のように、アンダースコア2つで囲われている隠し属性が存在する(クラス定義/関数定義自体にも存在する)。
これらはメタ情報を格納したり基本的な操作の実行(属性に値をセットする/取り出す際の挙動など)に使われている。
詳細はデータモデルに詳しいが、よく使う/役立ちそうなものは下記となる。
属性名 | 内容 |
---|---|
__class__ |
クラス定義。__class__.__name__ でクラス名が取得可能 |
__dict__ |
全属性(メソッド含む)をディクショナリ化したもの |
__doc__ |
クラスやメソッドのdocstringを取得可能。使い方が分からないけどソースコードやAPIドキュメントを探すのが面倒、という時にさくっと見られる |
メソッド名 | 内容 |
---|---|
__getattr__ |
属性アクセスを行い、対象の属性がなかった場合呼び出される |
__getattribute__ |
属性アクセスを行う際、常に呼び出される |
__setattr__ |
属性への代入時に呼び出される |
__delattr__ |
属性の削除時に呼び出される(del obj.xxx ) |
__getstate__ |
オブジェクトの直列化(pickle化)を行う |
__setstate__ |
直列化したオブジェクトから復元を行う |
__str__ |
クラスを文字列化するためのメソッド(いわゆるtoString) |
__repr__ |
インスタンスの表記を出力するためのメソッド(型や一部のメンバなどを表記) |
__str__
とは__repr__
は似ているが、__str__
は人間が変数の内容を知るためのもの(Readable)、__repr__
は意図した型の変数かどうかを確認するためのもの(Unambiguous)という使い分けがある(参考)。行列変数などで__repr__
にshapeを含めるのは良い使い方で、意図した変数を受け取り処理が行われているかを確認できる。ログ・コンソールなどに出力する際に人間が読めるようにするために、__str__
でインスタンスの状態をフォーマット出力するのは意図に沿っている。
obj["xxx"]
といった文字列でなく、obj.xxx
と属性でアクセスしたい場合が多々あるが、その場合に上記の__getattr__
などが有効になる。以下、soundcloud-pythonでの実装例(__init__
で渡されているobjはWeb APIから取得したディクショナリ型オブジェクト)。
class Resource(object):
"""Object wrapper for resources.
Provides an object interface to resources returned by the Soundcloud API.
"""
def __init__(self, obj):
self.obj = obj
def __getstate__(self):
return self.obj.items()
def __setstate__(self, items):
if not hasattr(self, 'obj'):
self.obj = {}
for key, val in items:
self.obj[key] = val
def __getattr__(self, name):
if name in self.obj:
return self.obj.get(name)
raise AttributeError
def fields(self):
return self.obj
def keys(self):
return self.obj.keys()
WebAPIのラッパーなど、相手のレスポンスによって動的に属性を変えたい場合などに利用できるテクニックになる。詳細な利用方法はこちらも詳しい。
なお、インスタンス/クラス定義等の属性はinspect
モジュールで抽出することができる。
>>> import inspect
>>> inspect.getmembers(some_instance)
Enumがない(2のみ)
Python3.4.1からは標準搭載されるが、それまではEnumがない。
使いたい場合は、pip install enum34
でenumをインストールして使用する。
import enum
class Color(enum.Enum):
Red = 10
Blue = 20
Yellow = 30
>>> Color.Red == Color.Red
True
>>> Color.Red == 10
False
# EnumはあくまでEnum型オブジェクトのためこれはFalse。これをTrueにするIntEnumもある
>>> Color.Red == Color(10)
True
#コンストラクタに値を渡すことで該当のEnumを作成可能
>>> Color.Red == Color["Red"]
True
#名前から作成する際は[]で名称を指定
>>> {Color.Red:"red", Color.Blue:"blue"}
{<Color.Blue: 20>: 'blue', <Color.Red: 10>: 'red'}
# リストのキーとしても使用可能
なお、各項目の文字列を定義したい場合、__str__
と併用することも可能。ラベル表示などを行う際、その定義を分散させたくないときに役に立つ。
class Color(enum.Enum):
Red = 10
Blue = 20
Yellow = 30
def __str__(self):
if self.value == Color.Red.value:
return "赤"
elif self.value == Color.Blue.value:
return "青"
elif self.value == Color.Yellow.value:
return "青"
>>> print(Color.Red)
赤
PrivateやProtectedがない
ないが、Privateなものは先頭に__
か_
を付けるという慣習がある。継承クラスにだけ見せたいなど、いわゆるProtectedに相当する概念はない。
では、__
と_
は何が違うのか?これは外部から隠蔽される度合いが変わってくる。
class Test(object):
def __init__(self):
self.__a = 'a' # two underscores
self._b = 'b' # one underscore
>>> t = Test()
>>> t._b
'b'
>>> t.__a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Test' object has no attribute '__a'
t.__a isn't found because it no longer exists due to namemangling
>>> t._Test__a
'a'
上記のように、アンダースコア一つの場合は一応通常通りアクセス可能だが(PEPでチェックしていると警告を出してくれる)、アンダースコアを二つ付けた場合は_Test__a
としないとアクセスできない、というようによりPrivate的な形(普通は見えない)にしてくれる。
<参考>
The meaning of a single- and a double-underscore before an object name in Python
Difference between _, __ and xx in Python
const/finalがない
Pythonでは定数を定義することができない。
ただ、後述するタプルは変更不可能なリストとなるので、これを利用すれば疑似的に定数を作成することは可能(変更不可なリストがあるくらいならconstがあっていい気もするが・・・)。
ここはPEP8では定数は全大文字でアンダースコアでつなぐべしとしているが、これに対するチェックはない。
メソッドの第一引数が予約されている
※ここではメソッド=クラス内の関数、としています。通常の関数にこの制約はありません。
メソッドの最初の引数は、普通のメンバメソッドはself
、クラスメソッドの場合cls
で予約されている(引数名はじつは何でもよいが、慣例としてself
とcls
が使用されている)。
class Test(object):
def __init__():
pass
def get_num(self):
return 1
def add_num(self, num):
return self.get_num() + num
@classmethod
def test_print(cls):
print "test"
上記のget_num
は明らかに引数一つとっているように見えるが、これは実質的には引数なしのメソッドである。
そして、同クラス内のメンバ関数はself.get_num()
となっているようにこの暗黙の第一引数であるself
やcls
を利用して呼び出しを行う。
リストとタプル(+可変長の引数)
Pythonには、ざっくり言えば「変更できないリスト」であるタプルというオブジェクトが存在する。これらは宣言方法が異なる。
v_list = [10, 20, 30] # list
v_tuple = (10, 20, 30) # tuple
v_list[1] = 11 # ok
v_tuple[1] = 11 # error! 'tuple' object does not support item assignment
タプルは、その特性上ディクショナリのキーになることも可能である。
リストとタプルは可変長の引数を扱う関数/メソッドにも深く関わっている。
def out_with_tuple(a,b,*args):
# a,b以降の引数はtupleにまとめられる
print(a,b,args)
>>> out_with_tuple(1,2,3,4,5)
(1, 2, (3, 4, 5))
def out_with_dict(a,b,**args):
print(a,b,args)
>>> out_with_dict(1,2,one=3,two=4,three=5)
(1, 2, {'three': 5, 'two': 4, 'one': 3})
*
というとポインタと思ってしまいがちだが、これは任意個数の引数を表し*
の場合タプル、**
の場合ディクショナリにまとめられる(併用も可能)。
そして、呼び出し側で*
,**
使うと逆にリスト/タプルの値を展開して引数として渡すことができる。
>>> def out(a,b):
... print(a,b)
>>> out([1,2]) # 普通にリストを渡した場合、当然NG
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: out() takes exactly 2 arguments (1 given)
>>> out(*[1,2])
(1,2) # *によって、リスト内の要素が展開され、out(1,2)と呼び出したのと同じになる
>>> out_with_dict(**{"a":1,"b":2})
(1, 2, {}) # 上記同様、**によってディクショナリで渡した引数が展開される
デフォルト引数はimmutableである必要がある
メソッドや関数のデフォルト引数として与える値は、immutableである必要がある。大体はそうなので問題はないが、空の配列はmutableに該当するので注意する必要がある。
>>> class ArrayDefault():
>>> def __init__(self, array=[]):
>>> self.array = array #! mutable default value
>>> a = ArrayDefault()
>>> len(a.array)
0
>>> a.array += [1,2,3]
>>> a.array
[1,2,3]
>>> b = ArrayDefault()
>>> b.array
[1, 2, 3] # !!!!!
a
とは全く関係ないb
のインスタンスになぜかあらかじめ値が入っている。完全に複雑怪奇な挙動に見えるが、これが引数がimmutableでなければならない理由である。デフォルト引数のスコープはそのメソッド/関数と同じであるようで、immutableでない場合グローバル変数と同じような働きをし上記のような挙動を起こす。
よって、上記の場合はNoneをデフォルト引数にしてNoneなら[]を設定するといった処理に変える必要がある。配列同様、参照を初期値として設定するようなケースは同様に注意が必要。
Default Parameter Values in Python
メソッドのオーバーロードができない
Pythonでは引数を変えて同名で定義するオーバーロードが使用できない。
そのためオーバーロードを使用したい場合は、デフォルト引数を使用したり内部で引数の型を判定するなどして、1メソッド内の分岐で対応する必要がある。
また、同名のクラスメソッドとメンバメソッドも許容されない。
ただ、singledispatchを利用することで条件分岐を排してオーバーロード的な実装を実現できる(Python3.4からは標準搭載)。
演算子による計算を実装可能(オペレーターオーバーロード)
Pythonでは、+
や-
といった演算子による計算を実装することができる(いわゆるオペレーターオーバーロード)(参考)。
class Point(object):
def __init__(self, x, y):
self.x = 0 if x is None else x
self.y = 0 if y is None else y
def __add__(self, other):
x = self.x + other.x
y = self.y + other.y
return Point(x, y)
def __str__(self):
return "x:{0}, y:{1}".format(self.x, self.y)
>>> print(Point(1, 2))
x:1, y:2
>>> print(Point(1, 2) + Point(1, 2))
x:2, y:4
また、==
や>
といった論理演算子の実装も可能。ただ、演算子をすべてスキなく定義するのはかなり骨が折れる。数値演算系なら自前で実装するより適切なライブラリがないかどうか確認したほうがよい。
型定義を記述することができる(3より)
Python3より(特にPython3.5から)、引数や関数の返り値について型を書くことができるようになった。
def greeting(name: str) -> str:
return 'Hello ' + name
Python3.6からは変数にも型情報を付けることができるようになった(PEP 526)。
my_string: str
my_dict: Dict[str, int] = {}
これはtype annotationといい、実行時にエラーを出すわけではないが事前にこの情報を使いチェックをかけることができる。チェックのためのツールとしては、mypyなどがあり、これを利用することで実行前に型の整合性チェックを行うことができる。詳細は以下を参照。
なお、Javaのアノテーションと記述が同じに見える@
がつくものがあるが、これはアノテーションではなく「デコレーター」であり、その挙動は全く異なりこちらは関数の実行に作用する。
デコレート、の名の通り「関数をラップする関数」として機能するのだ。
import functools
def makebold(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return "<b>" + func(*args, **kwargs) + "</b>"
return wrapper
def makeitalic(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return "<i>" + func(*args, **kwargs) + "</i>"
return wrapper
@makebold
@makeitalic
def hello(your_name):
return "hello {0}.".format(your_name)
>>> print hello("hoge") ## returns <b><i>hello hoge.</i></b>
参考:How can I make a chain of function decorators in Python?
上記は、makebold(makeitalic(hello))(your_name)
を実行したのと同じことになる(関数定義に近いデコレーターから順次実行されていく)。
メソッド実行前にチェックを入れたい場合(権限など。Overridesのチェックなどもできたりする)、また実行前後に処理を入れたい場合(実行時間の計測など)に利用される。なお、functools.wrap
はデコレートしたことで関数の情報が元の関数でなくデコレート関数に置き換わってしまうのを防ぐためのもの(こちら参照)。
メソッドに付与されたデコレーターを取得するのはかなり難しいので、メタプログラミング用途の場合(アノテーションが付与されたメソッドを取得して実行など)は潔くPython3に上げてアノテーションを利用したほうがよい。
名称空間とimportについて
PythonではNamespaceやpackageの宣言をしなくても、名称空間がファイルごとに(自動的に)切られるようになっている。ファイルごとに切られた名称空間の名前はファイル名と同義になるため、package/test_class.py
の中に定義されたTestClass
を呼び出す場合、from package.test_class import TestClass
とする必要がある。
※このため、他のパッケージ名とファイル名がバッティングするとインポートしたいパッケージがインポートできなくなったりするので注意が必要(こちらはファイル名をunittest.pyにしたために標準のunittestとバッティングした例)。'module' object has no attribute 'xxxx'
といったエラーが出たら要注意。
また、ファイル単位で切られるため同じフォルダ内のファイルでも参照する際にimport宣言が必要になる。なお、フォルダは__init__.py
というファイルがあるとパッケージと認識される(Python3からはなくてもよくなる)。
Javaなどと同様、パッケージ/クラス名でインポートを行いたい場合(from package import TestClass
というような感じ)、__init__.py
にファイルのimport文を書いておくことで対応できる。from package
部分で読み込まれるのはフォルダ内の__init__.py
であるため、ここにimport文があればファイルをインポートしたのと同じになるためである。
なお、相互参照しているファイルのimportの場合(AはBを参照していて、BはAを参照しているようなケース)はデッドロックのようになりインポートできなくなるので注意。これを回避するには、ファイル先頭でなく必要な処理内でimportするなどの工夫が必要になる(参考)。
あらゆるファイルをスクリプトとして実行可能
if __name__ == "__main__":
は、スクリプトとして実行された際(python xxx.py
と実行したとき)の処理を記載できる(トップレベルのスクリプト環境参照)。
if __name__ == "__main__":
print "Run Script"
これはクラスを定義しているファイルだろうが関数を定義しているファイルだろうが関係ない。
そのため、ちょっと今定義しているクラスの動作を確認してみたい、という場合ファイル下部に上記のようなif文を設置しておき、その配下に動作確認用の処理を書いておけばpython xxx.py
とすることで確認が可能だ。これは非常に便利。
配列内で処理を記載できる(リスト内包表記)
Pythonでは以下のように、配列内での演算が可能になっている。
>>> [ x for x in [1, 2, 3, 4, 5] if x > 3]
[4, 5]
>>> [ x*2 for x in [1, 2, 3, 4, 5]]
[2, 4, 6, 8, 10]
# 関数を利用することも可能
>>>def calc_double(x):
>>> return x*2
>>> [ calc_double(x) for x in [1, 2, 3, 4, 5]]
[2, 4, 6, 8, 10]
動作はmap
やfilter
といった組み込み関数と同じだが、場合によっては可読性を上げつつ簡単に書くことができる。なお、Python3からはmap
やfilter
はlistそのものでなくiteratorを返すようになったので、Python2同様listで扱いたい場合はリスト内包表記、iteratorから後続処理にチェインさせたい場合はmap
/filter
などを使うといった棲み分けをしておくと両バージョン対応しやすい。
また、実行速度が速いらしい。
Python3.6から、このリスト内包表記でも非同期のイテレーターを扱うことが可能になった(PEP 530)。
result = [i async for i in async_iter() if i % 2]
result = [await fun() for fun in async_funcs if await condition()]
匿名関数の作成
Pythonではlambda
を使用することで匿名関数を作成できる。
>>> list(map(lambda x: x * 2, range(5)))
[0, 2, 4, 6, 8]
上記ではmap
を利用しlambda x: x * 2
という関数をrange(5)
の各値に対して適用している。
lambda
で作成した関数は変数に代入することも可能で、上記は以下と等価である。
>>> f = lambda x: x * 2
>>> list(map(f, range(5)))
[0, 2, 4, 6, 8]
上記のリスト内包表記と合わせると、以下のような処理もかける。
>>> f = lambda a, b: a + b
>>> [f(*z) for z in zip([1, 2, 3], [4, 5, 6])]
[5, 7, 9]
zip
は複数の配列を引数にとりインデックスごとにまとめてくれる便利関数で、zip([1, 2, 3], [4, 5, 6])
は[(1, 4), (2, 5), (3, 6)]
となる。これを*
を使って関数f
の引数に展開し、処理を行っている。
for文について
Pythonのfor文はfor eachのような形で、デフォルトではインデックスがとれない。
ループ文中でindexを利用したい場合は、以下のようにする。
for index, value in enumerate(list):
print "index:" + index
単純にインデックスだけで回したい場合、以下のようにも書ける(list.length
のようなものはPythonにはないので、長さを取得する際はlen(list)
でとる)。
for index in range(len(list)):
print "index:" + index
便利な特性として、Pythonのfor文ではelseを設定することができる(whileの方が利用シーンは多いかもしれないが)。
child = "Bob"
for c in ["Alice", "Tom", "Ann", "Cony"]:
if c == child:
break
else:
print("{} is not here.".format(child))
else
で定義した処理はbreak
が実行されなかった場合に処理される。そのため、ループ中で検出ができなかった場合の処理を記載するのに使用できる。
if文について
Pythonにはswitch文がないため、if-elseで代用する。
えっ、と思うかもしれないが、Pythonはインデントが定められておりifでもかなりきれいになるのでこれで困ることはあまりない。
また、このためPythonでは1行のif文(いわゆる三項演算子)が書けないのではと思う方がいるかもしれないが、Python 2.5以降では以下のように書くことができる(PEP 308 -- Conditional Expressions(条件演算式) 参照)。
>>> test = 1
>>> "test eq 1" if test == 1 else "test not eq 1"
'test eq 1'
非同期処理を実装するためのシンタックスがある(3から)
Python3.5から、async
/await
というシンタックスを利用し非同期処理が簡単に実装できるようになった(その前からasyncio
で実装は可能だったのだが、はれて文法として実装された)。
ある連続した処理(以下ではtasks
)を、複数プロセスで処理する場合は以下のような感じで書くことができる。
import asyncio
import random
tasks = asyncio.Queue()
for t in range(10):
tasks.put_nowait(t)
async def execute(p):
while not tasks.empty():
t = await tasks.get()
await asyncio.sleep(random.randint(0, 3)) # emulate the waiting time until task t finish
print("Process{} done task {}".format(p, t))
return True
if __name__ == "__main__":
loop = asyncio.get_event_loop()
execution = asyncio.wait([execute(process_no) for process_no in range(2)])
loop.run_until_complete(execution)
実行結果は以下のようになる(※当然、実行するたびに結果は異なる)。
Process1 done task 0
Process0 done task 1
Process1 done task 2
Process0 done task 3
Process1 done task 4
Process0 done task 5
Process1 done task 6
Process0 done task 7
Process0 done task 9
Process1 done task 8
詳細については以下の記事にまとめているので、ご参考ください。
Pythonにおける非同期処理: asyncio逆引きリファレンス
なお、Python3.6からyield
を使用しより簡単に非同期イテレーターを作成することが可能になった(PEP 525)。
async def ticker(delay, to):
"""Yield numbers from 0 to *to* every *delay* seconds."""
for i in range(to):
yield i
await asyncio.sleep(delay)
作成したパッケージの公開が可能(PyPI)
PythonではパッケージをホスティングしているPyPIというサイトがあり、こちらに作成したパッケージを公開することでpip installなどでほかの人に広く使ってもらうことが可能になる(JavaにおけるMaven、Rubyにおけるrubygems.org、.NETにおけるnugetのようなもの)。
この方法については、普通に検索すると旧い手法がよく引っかかってしまうので、最新のアップロード方法については以下を参照されたし。
その他
ここに上げた以外のものについては、以下も参照ください。
[python] 細かすぎて伝わりにくい、Pythonの本当の落とし穴10選