【python】書いてて気づいた2系と3系の違い
pythonの2系と3系間で記述の仕方や処理の挙動が異なる場所について、 自分が気になった場所を列挙しました。
随時更新していきます。(最終更新2017/3/15)
reduceが組み込みの関数ではなくなった
2系では組み込みの関数であったreduce
はfunctools
をimportして使用する形に変更されていました。
from functools import reduce print(reduce(lambda x, y : x + y, xrange(10))) #45
reduce
関数はそこそこ使っていただけに、少し面倒です。
目的は名前空間汚染の防止?
実引数アンパック時の制約が緩和
以前の記事で関数に渡す引数のアンパック機能について説明しましたが、2系ではアンパックした引数のあとに名前無し引数をとれませんでした。
import math def polar_coordinates(x, y, z): r = math.sqrt(x ** 2 + y ** 2) s = math.sqrt(r ** 2 + z ** 2) theta = math.atan(y / x) phi = math.atan(z / r) return r, s, theta, phi x = 4. y = 3. coordinates = (x, y) # coordinatesをアンパックして引数に取る print(polar_coordinates(*coordinates,1)) #(5.0, 5.0990195135927845, 0.6435011087932844, 0.19739555984988078)
3系ではそのような制約がなくなり、よりアンパックが使いやすくなりました。
【python】"import *" でインポートされるモジュールを制限する
パッケージ中の__init__.py
内で__all__
変数を書き換えることで、import *
でimportするモジュールを制限することができます。
__all__ = ["hoge", "fuga", "piyo"]
このような記述を例えばpackage/__init__.py
が含むとき、
from package import *
によってimportされるモジュールはhoge
,fuga
,piyo
のみとなります。
デフォルトではfrom hoge import *
の記述によってhoge中の全モジュールをimportできますが、
使わないモジュールのimportが発生する、名前空間を汚染するという点で推奨されていません。
一方で、冗長なモジュール記述を避けられるというメリットがあります。
importしたいモジュール群がある程度fixしているとき、この手法が使えそうです。
参考文献
https://docs.python.jp/3/tutorial/modules.html#importing-from-a-package
【python】ユーザクラス定義時の==比較定義__eq__()と!=比較定義__ne__()
友人にちらと聞いたpython話のメモです。
pythonのユーザ定義クラスでは、Javaでequals()メソッドをオーバライドするように、同値(または同一)比較の再定義をすることができます。これはpythonの特殊メソッド(今まで組み込み関数と混同してました・・・)と呼ばれる全てのオブジェクト型が持つメソッドのうち、__eq__()メソッドをオーバライドすることで可能になります。
class Hoge(object): def __init__(self, value): self.value = value def __eq__(self, other): return self.value == other.value hoge1 = Hoge(1) hoge2 = Hoge(1) hoge3 = Hoge(2) print hoge1 == hoge2 # True print hoge1 == hoge3 # False
しかし2系のpythonにおいては、__eq__()関数のみの定義だけでは、以下の様な「等しくないかどうか」の判定を正しく行うことができません。
print hoge1 != hoge2 # True (!?) print hoge1 != hoge3 # True
pythonのクラスは、==の定義を記述する__eq__()のほかに__ne__()関数を持ち、正しい比較を行いたい場合はこれら2つの関数をオーバロードする必要があります。これはどちらかといえばC++の==演算子と!=演算子のオーバロードに近いような気がします。
class Hoge(object): def __init__(self, value): self.value = value def __eq__(self, other): return self.value == other.value hoge1 = Hoge(1) hoge2 = Hoge(1) hoge3 = Hoge(2) print hoge1 == hoge2 # True print hoge1 == hoge3 # False print hoge1 != hoge2 # False print hoge1 != hoge3 # True
python2リファレンスにも同様のことが記述されています。
比較演算子間には、暗黙的な論理関係はありません。すなわち、 x==y が真である場合、暗黙のうちに x!=y が偽になるわけではありません。従って、 __eq__() を実装する際、演算子が期待通りに動作するようにするために __ne__() も定義する必要があります。
ちなみに修正前の!=判定でいずれもTrueを返戻していたのは、python2系ではデフォルトの__ne__()がTrueを返すからです。同様に、デフォルトの__eq__()はいかなる場合もFalseを返します。
class Hoge(object): def __init__(self, value): self.value = value hoge1 = Hoge(1) hoge2 = Hoge(1) hoge3 = Hoge(2) print hoge1 == hoge2 # False print hoge1 == hoge3 # False print hoge1 != hoge2 # True print hoge1 != hoge3 # True
なおpython3系ではこの__ne__()の問題が少し解消しているようで、__ne__()関数が__eq__()に処理を委譲し、__eq__()の結果を反転して返す実装になっているとのことで、__eq__()を実装するだけで良さそうです。
以下python3リファレンスより
デフォルトでは __ne__() は NotImplemented でない限り __eq__() に委譲して結果を反転させます。
参考文献
【C++】vectorと生配列の速度比較
STLライブラリ上の動的配列'vector'と生配列のアクセス速度比較メモ。vectorはat()アクセスでなければ十分高速とのことなのですが、そうはいっても生配列と比べたら流石に遅かろうと思い、実験してみました。
#include<iostream> #include<vector> int main(){ int length = 10000000; std::vector<float> v(length, 0); float *array = new float[length]; time_t start = clock(); for (int i = 0; i < length; ++i) { array[i] = 5; } std::cout << "raw : " << clock() - start << "μs"<< std::endl; start = clock(); for (int i = 0; i < length; ++i) { v[i] = 5; } std::cout << "vector : " << clock() - start << "μs"<< std::endl; }
生配列とvectorをそれぞれ同じ長さで用意し、各々の要素に定数を格納していきます。 検証方法が初歩的かつ適当ですが、ご了承ください。。。
そして結果
まずは最適化無し(-O0)で
raw : 60103μs vector : 48983μs
次に最適化オプションあり(-O3)で
raw : 51μs vector : 4779μs
流石に生配列、最適化がかかると高速です。 一方で最適化前はvectorのほうが速いのですが、これが謎。 一応、双方とも要素がシーケンシャルに入っているようなので、ランダムアクセスとかはなさそうですが・・・
vector内部と最適化オプションの調査が必要みたいです。
(以下、要素アドレス確認のためのコード)
int length = 10000000; std::vector<float> v(length, 0); float *array = new float[length]; time_t start = clock(); for (int i = 0; i < length; ++i) { std::cout << &array[i] << std::endl; if(i > 5)break; } std::cout << "raw : " << clock() - start << "μs"<< std::endl; start = clock(); for (int i = 0; i < length; ++i) { std::cout << &v[i] << std::endl; if(i > 5)break; } std::cout << "vector : " << clock() - start << "μs"<< std::endl;
0x108c93000 0x108c93004 0x108c93008 0x108c9300c 0x108c93010 0x108c93014 0x108c93018 raw : 86μs 0x10666c000 0x10666c004 0x10666c008 0x10666c00c 0x10666c010 0x10666c014 0x10666c018 vector : 41μs
【python】関数呼び出しとメソッド呼び出しの速度を比較する
前々から気になっていた、関数呼び出しとメソッド呼び出しの速度比較の結果をやってみた。 あまり厳密ではないけれど・・・
import time import numpy as np # 適当なクラス class Hoge: # 適当なメソッド def hoge(self): print ('hoge') # 適当な関数(Hogeクラスのメソッドと機能は同じとする) def hoge(): print ('hoge') # 100000回の呼び出しを1000回試行 def profile(func): results = [] for j in xrange(1000): start = time.clock() for i in xrange(10000): func() results.append(time.clock() - start) return results # オブジェクトの生成時間は含めない obj = Hoge() # 各処理の実行時間リスト func_pt = profile(hoge) method_pt = profile(obj.hoge) print 'function process time average : {}s'.format(np.mean(func_pt)) print 'method process time average : {}s'.format(np.mean(method_pt))
結果
function process time average : 0.020806763s method process time average : 0.021944795s
結果だけ見ると、関数呼び出しの方が若干速い。 ぱっと見中身が同じ関数オブジェクトを同じように呼び出し処理しているんだけれど、もしかしたらメモリアクセスの仕方等に違いがあるのかも。
【Python】実引数として渡すリストのアンパック
Python書いてて初めて知ったのでメモ。
関数に引数を渡すとき、以下のようにリストが格納された変数の頭に"*"をつけることで、 リストがアンパックされて引数の羅列として扱えるらしい。
import math # 極座標系に変換 def polar_coordinates(x, y): r = math.sqrt(x ** 2 + y ** 2) theta = math.atan(y / x) return r, theta x = 4. y = 3. coordinates = (x, y) print polar_coordinates(*coordinates) # アンパック # >> (5.0, 0.6435011087932844)
上記のコードは雑な例で申し訳ないのだけれども、 特定の括りでまとめた値をそのまま関数に渡したい時に便利かも。
追記
Pythonのバージョンが3.5以前だと、アンパックした引数のあとに名前なし引数をとれない。
import math # 極座標系に変換 def polar_coordinates(x, y, z): r = math.sqrt(x ** 2 + y ** 2) s = math.sqrt(r ** 2 + z ** 2) theta = math.atan(y / x) phi = math.atan(z / r) return r, s, theta, phi x = 4. y = 3. coordinates = (x, y) print polar_coordinates(*coordinates,1) # SyntaxError: only named arguments may follow *expression
リストやタプルなどをアンパックしつつ他の引数も取りたい場合、 引数の名前を明示的に指定するか、アンパック引数の前に他の引数を記述する。
# 引数の名前を明示 print polar_coordinates(*coordinates,z=1.) # >> (5.0, 5.0990195135927845, 0.6435011087932844, 0.19739555984988078) # アンパックする引数の前に、他の引数を記述 x = 4. y = 3. z = 1. coordinates = (y, z) print polar_coordinates(x, *coordinates) # >> (5.0, 5.0990195135927845, 0.6435011087932844, 0.19739555984988078)
【Python】IPythonをつかう
IPython
Pythonをインストールすると、標準のインタラクティブシェルがすでに使えるようになっているが、 別のPythonインタラクティブシェルであるIPythonを使うとより快適にPythonコードを実行できる。
インストール
pip install ipython
んで
$ ipython2 bash-3.2$ ipython2 Python 2.7.6 (default, Sep 9 2014, 15:04:36) Type "copyright", "credits" or "license" for more information. IPython 3.1.0 -- An enhanced Interactive Python. ? -> Introduction and overview of IPython's features. %quickref -> Quick reference. help -> Python's own help system. object? -> Details about 'object', use 'object??' for extra details. In [1]:
IPythonではプロンプト上の入力部分がインタラクティブシェルのような>>>
ではなく、In [1]
のように入力回数をあらわすものになっている。
自動補完
In [1]: im<TAB>
↓
In [1]: import
複数の候補があると、一覧表示される。
In [1]: __<TAB> __ __builtin__ __name__ __IPYTHON__ __debug__ __package__ __IPYTHON__active __doc__ ___ __import__
情報の参照
IPython中で定義した変数の後に?
や??
を付加すると、様々な情報を取ってこれる。
In [12]: obj = 1 In [13]: obj? Type: int String form: 1 Docstring: int(x=0) -> int or long int(x, base=10) -> int or long Convert a number or string to an integer, or return 0 if no arguments are given. If x is floating point, the conversion truncates towards zero. If x is outside the integer range, the function returns a long instead. If x is not a number or if base is given, then x must be a string or Unicode object representing an integer literal in the given base. The literal can be preceded by '+' or '-' and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal. >>> int('0b100', base=0) 4
履歴を残す
標準インタラクティブシェルを終了すると、以前の実行履歴は消えてなくなってしまう。 IPythonは、逐次実行履歴をpythonファイルとして残しておける。
In [1]: %logstart log.py Activating auto-logging. Current session state plus future input saved. Filename : log.py Mode : backup Output logging : False Raw input log : False Timestamping : False State : active In [2]: print ('%logstartでログ開始') %logstartでログ開始 In [3]: %logoff Switching logging OFF In [4]: print ('%logoffでログ一時中断') %logoffでログ一時中断 In [5]: %logon Switching logging ON In [6]: print ('%logonで再開') %logonで再開 In [7]: %logstop In [8]: print ('%logstopでログ終了') %logstopでログ終了
ログはこんな感じ↓
# IPython log file get_ipython().magic(u'logstart log.py') print ('%logstartでログ開始') get_ipython().magic(u'logoff') print ('%logonで再開') get_ipython().magic(u'logstop')
シェルコマンドの実行
インタラクティブシェル上でシェルコマンドもできる。
!+[シェルコマンド]
で使用可能。
In [18]: !ls Applications Sensor-Bin-MacOSX-v5.1.2.1 Desktop TEST Documents apatch Downloads bin Dropbox build Library dotfiles Movies ...