【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 ...
【python】デコレータ
pythonにはデコレータ構文が標準で実装されている。デコレータを使うことで、既存の関数に対し簡単に別の機能を付加(デコレート)することができる。
例えば、以下の様なコードを書いたとする。
def cake(): print("cake") cake()
とくれば、もちろん出力は
cake
となる。
次に、新たな関数を以下のように書き加えてみる。
def chocolate(func): print("chocolate") func() @chocolate def cake(): print("cake") cake
出力:
chocolate cake
出力結果を見ると、cake()
の出力の前に呼び出してもいないchocolate()
の出力が表示されている。@chocolate
をくっつけたcake
を呼び出しただけで、 chocolate()
の提供する機能を実行している。これがデコレータ。
もっと言うと、変更後コード中の関数呼び出しcake
は以下と等価とのこと。
cake = chocolate(cake)
すなわち、変数cakeに、関数オブジェクトchocolateが同じく関数オブジェクトであるcakeを引数に取りつつ格納されているとみなせる。cake()
呼び出し時に括弧が外れていたのはそのため。
また当然ながら、chocolate()
内部で引数に取る関数の呼びたしタイミングは任意に決めることができる。
def chocolate(func): print("chocolate") @chocolate def cake(): print("cake") cake
出力:
cake chocolate
ネストも可能。
def jam(func): print("jam") return func def chocolate(func): print("chocolate") return func @jam @chocolate def cake(): print("cake") cake()
デコレータ用の関数で関数を返戻しているのは、ネストした関数を正しく呼び出すため。
例えば以下のようにすると、
def jam(func): print("jam") func() def chocolate(func): print("chocolate") func() @jam @chocolate def cake(): print("cake") cake
以下の様なエラーを吐く。
Traceback (most recent call last): chocolate File "/Users/.../Test/calc/__init__.py", line 14, in <module> cake @chocolate jam File "/Users/.../Test/calc/__init__.py", line 7, in jam func() TypeError: 'NoneType' object is not callable
当然といえば当然で、この時のcakeは
cake = jam(chocolate(cake))
と等価で、中の
chocolate(cake)
は関数呼び出しであり、ひと通りchocolate()
が実行されたあともその返戻値はNoneTypeである。したがって「NoneTypeは関数呼び出しできませんよー」と怒られている。
タスク処理が遅い
最近、タスクの処理がめっぽう遅い!と感じるようにもなったし、外からのアクションもそれっぽくなってきたので、 なんで遅いんだろう、と考えた結果を書き留めておこうと思いました。
プログラミングとは殆ど関係ない(?)内容ですが、 自戒の意味を込めて。
1.ゴールを設定していない、意識していない
こういうモノを作ろうとか、こういうコトをしようといった、その作業の末に到達するであろう目的を明確化していない。
手段が目的より先行している時に起こりがちな気がする。
なぜこんな考えに至ったのかといえば、今までの経験で「この作業をした時ははかどってたなあ」と感じたのは明確に発表したい内容のあるプレゼン資料を作っていたときだったから。
あとはそこに対して不足知識と調べつつ自分の考えを書き足すだけだった。
2.具体的な時間設定をしていない
「〇〇今日中にやる」でも、例えば午後6時に終わるのと午後11時に終わるのとでは5時間も違う。 5時間でできること、学べることなどいやほどある。 逆にきっちり時間を決めすぎても、例のアレなのか、最低稼働時間が指定時間いっぱいになってしまう事が多い。
3.取り組んだという事実に概ね満足している
結局そこまでの完成品を見てがっかりしているけれども。
目標よりも完成度の低い成果物の状態でタイムアウトになったときは、これか4.の場合が多い。または両方。
4.見積の精度が低い
具体的な時間設定〜同様。見積もるのはいいが、あてにならない。 前の結果を踏まえていない。
じゃあどうすればいいのよ
上から順に愚直に解決しようとするならば、
1.目標を明確化し、
2.時間設定を明確化し、
3.終了時の作業進捗を目標と比較し、ギャップを把握
4.3より、見積もりをし直す
となるのかな。まるでPDCAなんちゃらのよう。
加えて重要なことには、そのタスクを消化する(した)ことに対する報酬を自分にちらつかせるのがよいのかも。
これができたら、新しいものが作れるようになる。認めてもらえる。これをやったら、楽しいかも。面白いかも。とか。
「めんどくさい」以上の動機を与えてやる必要がある。
ここまで書くのに一時間かかりました。 あれ・・・?