在 class 实例和 class 定义上调用 class 装饰器有什么区别?

What's the difference between calling a class decorator on a class instance and on a class definition?

class Deco:
    def __init__(self, name):
        self.name = name
    def __call__(self, test_class):
        def inner_func(whatisit):
            return whatisit
        test_class.method = inner_func
        return test_class

class TestClass:
    def __init__(self, name):
        self.name = name
    
@Deco('deco')
class TestClassWrapped:
    def __init__(self, name):
        self.name = name

test = TestClass('test')
test = Deco('deco')(test)

test_wrapped = TestClassWrapped('test')

print(test.method('whatisit')) >> whatisist
print(test_wrapped == test_wrapped.method()) >> True

为什么 test.methodtest_wrapped.method return 不同的结果?
似乎 test_wrapped.method 中的第一个参数是 self,而不是 test.method。为什么两者不同?

逐步浏览您的代码:

  1. 您创建了一个名为 test 的常规 TestClass

  2. 您手动调用 Deco 并用 test 提供它,使用行 test = Deco('deco')(test).

  3. 这使您的代码通过 __call__ 函数,该函数修改传递的 class test 以将其 method 属性设置为嵌套功能。然后 returns 它,所以 test 现在包含一个成功修改的 TestClass :调用 test.method('whatisit') 将成功 return 'whatisit'。重要的是,您不是在此处访问方法:您是通过属性访问功能。 self 被传递给 Python 中 classes 的每个方法,但由于这不是方法,因此它不会在这里发挥作用。尝试打印 type(test.method),您会看到 <class 'function'> 而不是 <class 'method'>。重要的是,您已经传递了 TestClass 的实例,而不是 class 定义本身:并且只有这个名为 test 的实例设置了 method 属性。

  4. 然后您创建一个名为 test_wrappedTestClassWrapped。创建后,它再次进入 __call__,将其作为 test_class 参数传递 TestWrappedClass。重要的是,您传递的是 TestWrappedClass 的定义,而不是它的实例。在此处设置 method 将为您以后创建的每个 TestWrappedClass 实例修改它,甚至可以在不实例化任何东西的情况下访问它。尝试调用 TestClassWrapped.method("abc") :它将打印 abc 而根本不实例化 TestClassWrapped 。有趣的是,当以这种方式设置时,它不是设置为属性而是设置为方法!尝试打印 type(test_wrapped.method)。这就是我认为是混乱的根源。

  5. print(test_wrapped.method())的情况下,你必须记住实例化的classes的每个方法都将self作为它们的第一个参数。这意味着 test_wrapped.method() 将 return self :因此为什么 test_wrapped == test_wrapped.method()。请注意,这不适用于从 class 定义调用的方法,就像我之前展示的那样。 TestClassWrapped.method("abc") 必须采用某种参数(如 abc),否则它会抱怨缺少参数。

所以这就是为什么 test.method('whatisit') returns 'whatisit' 并且不将 self 作为参数,而 test_wrapped.method() 为什么 return self.

区别不在于装饰的工作方式,而在于呼叫的工作方式。当实例调用在其 class 上定义的方法时,如 test_wrapped 所做的那样,它始终将 self 作为第一个参数传递。同时,当一个对象调用它自己的一个属性时,恰好是一个函数,但在它的 class 上不存在,它会在不传递 self 的情况下调用它。考虑这个简单的 class:

class LoudTalker:
    def __init__(self, name):
        self.shout_hello = lambda: print("HELLO, I'M {}".format(name.upper()))

>>> john = LoudTalker("John")
>>> LoudTalker.shout_hello()
HELLO, I'M JOHN

请注意,john 没有将 self 传递给 shout_hello(这会引发错误 <lambda>() takes 0 positional arguments but 1 was given),因为 shout_hello 是直接在例如,不在 class.