Why does python class method’s first argument change when used as decorator

笑着哭i 提交于 2021-02-11 05:59:20

问题


Consider the following code:

class C:
    def decorator(self):
        print(self)
        def inner(*args):
            return self(*args)
        return inner

    @decorator
    def decorated(self):
        ...


if __name__ == '__main__':
    c = C()
    c.decorator() # 1
    c.decorated() #2

The output is this:

<function C.decorated at 0x1121cd9d8>
<__main__.C object at 0x111f5a8d0>
  1. When the decorator function is called as a normal class function, the first argument it receives is a class instance.
  2. However, when decorated is called and the decorator function is used as a decorator, the first argument is a decorated function; the decorator function now behaves virtually the same as a normal function that’s defined outside of the class.

Intuitively, since the decorator function is defined as a function inside a class without the @staticmethod or @classmethod decorator, I thought the first argument it receives should always be the instance of the class. However this was not the case.

I don’t see this coding pattern often, but is this coding pattern widely used? What are the possible pitfalls of using this coding pattern?


回答1:


I made a little change to your code and I tried to explain what happens in the comments:

class C:
    def decorator(self, count=[0]):
        print(count, ":", self)
        count[0] += 1
        def inner(*args):
            return self(*args)
        return inner

    @decorator
    def decorated(self):
        print(self)

if __name__ == '__main__':
    c = C() #0 Applies the decorator like
            #  decorated = decorator(decorated)
            #  Above, self equals decorated function, and gets printed
            #  inside decorator function.
            #  The decorated function is now a function that returns decorated(*args), actually None, but prints self, at call moment (not now).
    c.decorator() #1 normal bound method call, self is c
    print("[2] : ", end="") # for visual reference
    c.decorated() #2 calls C.decorated(c) so now self is c again and decorated is the result of decorated(c) that returns None but prints self (equals c), with no print from the decorator bound method, that does not run.

Outputs:

[0] : <function C.decorated at 0x7f26945b4ee0>
[1] : <__main__.C object at 0x7f26945bddc0>
[2] : <__main__.C object at 0x7f26945bddc0>

I think this use is a bit confusing, so unless you have a concrete reason to do that... However I have seen some advanced code with decorators or metaclasses that can be difficult to grasp. It is always a matter of really needing something worth of it. I suggest you search for typical decorator and class decorator uses. Rarely seen looks different from widely used.




回答2:


It seem the "problem" its actually at init

class C:
    def decorator(self):
        print(self)
        def inner(*args):
            return self(*args)
        return inner
    @decorator
    def decorated(self):
        pass
if __name__ == '__main__':
    c = C()
    #c.decorator() # 1
    #c.decorated() #2

python test.py

<function C.decorated at 0x00000289DD5BE160>

So... at init of a class with decorated functions first it modifies the function (to launch the decorator instead of the decorated). Probably there is some documentation that can be read. I never used decorators inside the class, only stand alone.



来源:https://stackoverflow.com/questions/63967686/why-does-python-class-method-s-first-argument-change-when-used-as-decorator

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!