デコレータと対象関数が同名のケース
def bar (func): pass @bar def foo (): pass
(中身がpassなのはとりあえず横に置いといて)fooの前に@barを付けるのがデコレータなわけですが、これはつまり以下のコードと等価であると
def bar (func): pass def foo (): pass foo = bar(foo) # fooの前に@barつけるのと同じ意味
デコレータの説明でそういうのをよく見かけるんですが、どうやら完全に等価ってわけではないんですね。
というのもデコレータ関数名とデコレータ対象の関数名が同名の場合、結果が変わります。
まずはデコレータを使った場合
def foo (func): pass @foo def foo (): pass
$ py main.py
これは問題なく動作します。
では等価コードであるはずの以下のコードではどうでしょう
def foo (func): pass def foo (): pass foo = foo(foo)
$ py main.py Traceback (most recent call last): File "main.py", line 9, in <module> foo = foo(foo) TypeError: foo() takes 0 positional arguments but 1 was given
例外になります。
そりゃ当たり前の話でデコレータ用のfoo関数を再度foo関数を定義して上書きで消してしまってるのでfuncを受け取るfoo関数なんて無いよって例外になるわけです。
てかそもそもデコレータと同名の関数定義する時点でおかしいんじゃないの君って言われそうですが、そもそも何でこんなことが気になったのかというと先日の記事でプロパティというのを勉強したんですが、そのコードが
class Foo(): def __init__(self,name): self.__name = name @property def name(self): return self.__name @name.setter def name(self,name): self.__name = name
こうなってるわけなんですよ。@name.setterはnameメソッドと同名なので同じ問題が発生するわけです。
だから以下のように書くと
class Foo(): def __init__(self,name): self.__name = name @property def name(self): return self.__name def name(self,name): self.__name = name name = name.setter(name) # ここで例外
当然例外になるわけです。
ということはPython的にはデコレータと同名の関数を定義することは特に誤った使い方ってわけではないわけですよね。
単純な等価じゃないとしたら内部的にはどう解釈されてるんでしょうか。
よくわからないけど以下のような解釈になってるとか
def foo (func): pass deco_foo = foo def foo (): pass foo = deco_foo(foo) del deco_foo
既存にdeco_fooがないことが前提ですが、これなら等価と言えるのかも。うーんよくわからぬ。