Works by ...

プログラミング関連でメモする

【python】pythonのクロージャ

pythonでは、クロージャと呼ばれる関数オブジェクトを実装することができます。

関数オブジェクトとは、クラスのインスタンスのように関数をオブジェクトとして扱ったものです。

クロージャの定義は、Wikipedia氏曰く

引数以外の変数を実行時の環境ではなく、自身が定義された環境(静的スコープ)において解決することを特徴とする。(Wikipediaより引用)

だそうです。

実装例を下に示します。

def counter():
    c = 0
    def count():
        nonlocal c
        c = c + 1
        return c
    return count

f = counter()
print(f()) # 1
print(f()) # 2
print(f()) # 3

よくあるクロージャの実装では、関数の中にさらに関数を定義し、 外側の関数に変数などを持たせ、内側の関数にその更新を行わせます。 外側の関数は内側関数のオブジェクトを返戻することで、「変数等の状態を保持する関数」としての機能を提供します。

例えば上のコードでは、

counter()は変数cを定義し、count()cの更新+cの返戻を行っています。

②関数counter()count()関数を内側で定義し、かつ関数オブジェクトとして返戻しています。

counter()から返戻されるcount()関数オブジェクトが変数fに代入され、呼び出し毎に返戻値が変化しています。

counter

結果としてfが状態を持つ関数を提供し、同じ呼び出しに対して異なる値が返ってきています。

【python】pythonにおけるスコープ【グローバル変数】

pythonでは変数のスコープに最新の注意を払う必要があり、特に万一グローバル変数(厳密にはスコープはモジュール内に収まるので、モジュール変数?)の変更を扱うような場合、気を緩めていると下のコードに示すような問題を引き起こす可能性があります。

is_odd = False
def check_odd(value):
    if value % 2 == 1:
        # 書き換え(たつもり)
        is_odd = True

check_odd(1)
print(is_odd) # False(Trueではない)

のような問題が起こります。 問題はランタイムがこれを正常なコードとして解釈できてしまうことで(当然ですが)、check_odd内でのグローバル変数is_oddの書き換え(たつもり)処理は単なるcheck_odd中のローカル変数is_odd定義に終わります。なのでグローバル変数is_oddに変化はありません。

global修飾子を使えば、一応所望の結果を得ることができます。

is_odd = False
def check_odd(value):
    global is_odd # グローバルなis_oddを参照
    if value % 2 == 1:
        # グローバルなis_oddを書き換え
        is_odd = True

check_odd(1)
print(is_odd) # True

ただ身も蓋もないことを言ってしまうと、個人的にはこのようなグローバル領域に変数を置くこと自体避けるべきと考えます。上記のような副作用を防ぐ意図だけでなく、変数=状態の変遷という意味でも、特別なことがない限り変数はオブジェクトに管理させるべきです。

グローバル領域には、基本的に定数扱いの変数のみを置くべきだと思います。

# ホントはmathやnumpyのπやexpの使用を推奨
PI = 3.141592
NAPIER = 2.718281828459045

def gaussian(x, mu, sigma):
    return 1 / ((2 * PI) ** 0.5 * sigma) * (NAPIER ** ( - (x - mu) ** 2 / (2 * sigma * sigma)))

print(sum([gaussian(i, 0, 1) for i in range(-100,100)])) #1.000000109372639

前述の通り、pythonではglobal修飾子を明示的につけない限り、変数への代入記述はローカル変数に対するものと解釈されます。なのでglobalと書かなければ故意にグローバル変数を書き換えてしまうことはありません。仮にグローバル変数=定数という規約のもと、グローバル変数と同じ名前の変数への代入をより小さなスコープ内で行った場合、それはそのスコープ内での変数定義となります。

ちなみに変数の参照時には、最も内側のスコープから外側へと同名の変数を探索します(それでもなかったら、Build-Inの変数を参照します)。すなわち同一スコープ内に定義がされてない場合、勝手に外側のスコープ内の変数が参照されます。そのためグローバル変数は参照するだけならglobal修飾子を必要としません。

【python】リスト・ジェネレータ・集合・辞書内包表記

リスト・ジェネレータはともかく、辞書なんかはたまに忘れるのでメモ。

In [1]: array = [1, 2, 3, 4, 5, 6, 7]

# リスト
In [2]: [a*a for a in array]
Out[2]: [1, 4, 9, 16, 25, 36, 49]

# ジェネレータ
In [3]: (a*a for a in array)
Out[3]: <generator object <genexpr> at 0x1041c10a0>

# 集合
In [4]: {a*a for a in array}
Out[4]: {1, 4, 9, 16, 25, 36, 49}

# 辞書
In [5]: {a:a*a for a in array}
Out[5]: {1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49}

あと余談ですが、ジェネレータはリスト内包表記で生成するリストが肥大化してしまうような時におすすめです。

# リスト
# a=... の時点で全要素分のメモリを確保
# メモリを食うし、末尾近傍の要素は使わないかもしれない
a = [i*i for i in range(2 ** 10 * 10000000)]  

# ジェネレータ
# b=... の時点では要素の生成は発生しない
b = (i*i for i in range(2 ** 10 * 10000000)) 
b.next() # ここで初めて要素生成

【python】スライシングしたリストへの代入

pythonのシーケンスにはスライス機能と呼ばれる糖衣構文が用意されていますが、

In [1]: a = [1, 2, 3, 4, 5, 6, 7]
# 部分リストを取得可能
In [2]: a[2:5]
Out[2]: [3, 4, 5]

どうやらスライスによって一定範囲に対する代入操作も行えるようで、さらに代入するシーケンスのサイズが同一でなくても良いようです。

In [1]: a = [1, 2, 3, 4, 5, 6, 7]

In [2]: a
Out[2]: [1, 2, 3, 4, 5, 6, 7]
# スライス代入操作可能&サイズも任意
In [3]: a[2:5] = ['a', 'b']

In [4]: a
Out[4]: [1, 2, 'a', 'b', 6, 7]

【python】スクリプト実行中にインタプリタのバージョンを確認する

使用するインタプリタは3系で限定したいときなど、スクリプトを実行しているインタプリタのバージョンを知りたいときに sysモジュールが持つversion_infoメンバが使えます。

In [1]: import sys

# version_info
In [2]: sys.version_info
Out[2]: sys.version_info(major=2, minor=7, micro=11, releaselevel='final', serial=0)

# メジャーバージョン
In [3]: sys.version_info[0]
Out[3]: 2

# マイナーバージョン
In [4]: sys.version_info[1]
Out[4]: 7

# リビジョン、ビルド
In [5]: sys.version_info[2]
Out[5]: 11

# リリースレベル(使用段階であれば'alpha','beta'等)
In [6]: sys.version_info[3]
Out[6]: 'final'

# serial(なんだこれ)
In [7]: sys.version_info[4]
Out[7]: 0

使用例

使用するインタプリタを指定したり、

import sys
# 使用するインタプリタを3系に制限
if sys.version_info[0] < 3:
    raise Exception("This python code should be run with Python 3.")

インタプリタのバージョンによって仕様の異なる処理を分岐できたりします。

def divide(a, b):
    if sys.version_info[0] < 3:
        return double(a) / b
    return a / b

尤も、後者の例は広範なバージョンをサポートする必要が無い限りやめたほうがいいと思いますが。。。

【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