nd2readerのソースコードを見ていたら@staticmethodが散見された。文法を知らなかったので調べた。
1. デコレータの定義について
関数fに対して、デコレータを使うとfという表記でg(f)を呼べるようになる。デコレータ用の便利関数を書いておけばユーザは自分の関数を修飾した関数を作ることが出来る。
def save(func): def decorated_func(x): print("calculated {} and result is {}".format(func.__name__, func(x))) #with open("log.txt", "w") as f: #f.write("calculated {} and result is {}\n".format(func.__name__, func(x))) return func(x) return decorated_func def memoize(func): cache = {} def decorated_func(*args): if args not in cache: cache[args] = func(*args) return cache[args] return decorated_func @save def f(x): return x+1 @memoize def g(x): print("g is called") return x # calculated f and result is 4 a = f(3) # decorated_func print(f.__name__) # g is calledが表示される g(1) # 2回目以降は何も表示されない g(1)
とは言ったものの関数の名前(f.__name__)がdecorated_funcに変わってしまっている。そこで@functools.wrapsをつける。
def save(func): @functools.wrap(func) def decorated_func(x): ... return decorated_func # 以下と同様 def save(func) def decorated_func(x): ... decorated_func.__name__ = func.__name__ decorated_func.__doc__ = func.__doc__ return decorated_func
memoizeと同様の機能はfunctools.lru_cacheにすでに実装されているので、
@lru_cache(maxsize=None) def fib(n): if n < 2: return n return fib(n-1) + fib(n-2)
とすることが出来る。
- 参考:
2. setter、getter、property
propertyはAPIの内部実装を隠してメンバ変数のようにアクセスさせるのに便利そう。
class Hoge: def __init__(self, y): self._y = y @property def x(self): return self._y**2 hoge = Hoge(1) hoge.x = 2 # エラー from math import sqrt class Hoge2: def __init__(self, y): self.__y = abs(y) @property def x(self): pass @x.getter def x(self): return self.__y**2 @x.setter def x(self, x): if x < 0: raise ValueError("x must be larger than 0") self.__y = sqrt(x) hoge = Hoge2(3) print(hoge.x) print(hoge._HOGE__y)
- 参考:
- 次に気になった時のためのメモ
3. staticmethod、classmethod
staticmethod、classmethodどちらもクラス、インスタンスの両方から呼び出せる。classmethodは第1引数に暗黙的にクラスを受け取るが、staticmethodは明示的な引数のみ。使うタイミングがはっきりしないが、selfを取らないことを明確に出来ること、メンバ関数なので継承できることがありそう。
class Base: @staticmethod def say_anything(s: str): print(s) @classmethod def hello(cls): print("hello from {}.".format(cls.__name__)) class Derv(Base): pass # 同じ Base.say_anything("hello world!") Derv.say_anything("hello world!") # 継承先でクラス変数を参照 Base.hello() # hello from Base. Derv.hello() # hello from Derv. # インスタンスからも呼ぶことが出来る。(例は省略) # もう少し具体的な例(classmethodをファクトリ関数に使う例) class Date: day = 0 month = 0 year = 0 def __init__(self, day, month, year): # 省略 # y/m/dの形式かチェック @staticmethod def is_date_valid(s: str): pass # y/m/dの形式からDateオブジェクトを作る @classmethod def from_string(cls, s: str): return cls(map(int, str.split("/"))) class DateTime(Date): pass datetime1 = DateTime(10, 10, 1990) datetime2 = DateTime.millenium(10, 10) # mileniumはstaticmethod isinstance(datetime1, DateTime) # True isinstance(datetime2, DateTime) # False # classmethodをシングルトン的な(?)クラスに使う場合 class Logger: DEBUG = 5 INFO = 4 WARN = 3 ERROR = 2 FATAL = 1 _level = DEBUG def __init__(self): Logger._level = Logger.DEBUG @classmethod def isLevel(cls, level) : return cls._level >= level @classmethod def debug(cls, message) : if cls.isLevel(Logger.DEBUG) : print "DEBUG: " + message
- どちらもnamespace的に用いられる感じがある
- staticmethodは副作用がないことの明示
- classmethodは
- ファクトリ関数を作りたいとき(継承された先のクラスのインスタンスになる)
- あるいはstaticなメンバ変数を持ちたい時
- シングルトンとの違いは?
書かないにも関わらずプログラムの設計考えるのは楽しい
参考:
追記
柔軟に色々やろうと言う文脈になるとPythonによる黒魔術入門が参考になりそう(メタプログラミングとは?)。