デコレータと対象関数が同名のケース

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がないことが前提ですが、これなら等価と言えるのかも。うーんよくわからぬ。