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

3
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返回不同的结果?
看起来,test_wrapped.method的第一个参数是self,但test.method不是。为什么它们之间会不同呢?

2个回答

2
区别不在于装饰器的工作方式,而在于调用的方式。当实例调用其类定义的方法(如test_wrapped所示)时,它总是将self作为第一个参数传递。同时,当对象调用其自身的属性(该属性恰好是函数,但不存在于其类中)时,它会在不传递self的情况下进行调用。考虑以下简单的类:
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是直接在实例上定义的,而不是在类上定义的。

1

逐步浏览您的代码:

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

  2. 手动调用 Deco,并将其提供给 test,使用行 test = Deco('deco')(test)

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

  4. 然后,创建一个名为 test_wrappedTestClassWrapped。创建它时,它再次进入 __call__,将其作为 test_class 参数传递给它。重要的是,您已经传递了 TestWrappedClass 的定义,而不是其实例。在这里设置 method 将修改之后您稍后创建的每个 TestWrappedClass 实例,并且甚至可以在不实例化任何内容的情况下访问它。尝试调用 TestClassWrapped.method("abc"):它将打印出 abc 而无需实例化 TestClassWrapped。有趣的是,以这种方式设置时,它不是作为属性设置的,而是作为方法!尝试打印 type(test_wrapped.method)。这就是我认为混淆的来源。

  5. 对于 print(test_wrapped.method()),您必须记住,实例化类的每个方法都将 self 作为其第一个参数。这意味着 test_wrapped.method() 将返回 self:因此为什么 test_wrapped == test_wrapped.method()。请注意,这不适用于从类定义中调用的方法,如我之前展示的。TestClassWrapped.method("abc") 必须采取某种形式的参数(例如 abc),否则它将抱怨缺少参数。

因此,这就是为什么test.method('whatisit')返回'whatisit'并且不需要将self作为参数,以及为什么test_wrapped.method()确实返回self的原因。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接