Works by

Ren's blog

@rennnosuke_rk 技術ブログです

【python】書いてて気づいた2系と3系の違い

pythonの2系と3系間で記述の仕方や処理の挙動が異なる場所について、 自分が気になった場所を列挙しました。

随時更新していきます。(最終更新2017/3/15)

reduceが組み込みの関数ではなくなった

2系では組み込みの関数であったreducefunctoolsを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__() に委譲して結果を反転させます。

参考文献

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              ...