Works by

Ren's blog

@rennnosuke_rk 技術ブログです

【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__() に委譲して結果を反転させます。

参考文献

http://docs.python.jp/2/reference/datamodel.html

【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なんちゃらのよう。

加えて重要なことには、そのタスクを消化する(した)ことに対する報酬を自分にちらつかせるのがよいのかも。
これができたら、新しいものが作れるようになる。認めてもらえる。これをやったら、楽しいかも。面白いかも。とか。
「めんどくさい」以上の動機を与えてやる必要がある。

ここまで書くのに一時間かかりました。 あれ・・・?