【问题标题】:Using a class as a decorator使用类作为装饰器
【发布时间】:2019-01-01 09:49:35
【问题描述】:

这是我第一次尝试,所以...

我正在关注:https://www.python-course.eu/python3_decorators.php

目标是当我像这样初始化 Foo 的实例时:

f = Foo(10, [1, 2, 3, 4, 5])

print(f) 的输出需要如下所示:

Instance of Foo, vars = {'x': 10, 'y': [1, 2, 3, 4, 5]}

装饰器类的作用是作为一种自动的repr

我有这个:

class ClassDecorator(object):
    def __init__(self, cls):
        self.cls = cls
        print("An instance of Foo was initialized")

    def __call__(self, cls, *args, **kwargs):
        print("Instance of Foo, vars = ", kwargs)

@ClassDecorator
class Foo(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y

f = Foo(10, [1, 2, 3, 4, 5])
print(f)

产量:

An instance of Foo was initialized
Instance of Foo, vars =  {}
None

我不认为我想要 *args,但没有它我的程序会出错:

Traceback (most recent call last):
An instance of Foo was initialized
  File "C:/Users/Mark/PycharmProjects/main/main.py", line 17, in <module>
    f = Foo(10, [1, 2, 3, 4, 5])
TypeError: __call__() takes 2 positional arguments but 3 were given

我理解缺少参数的问题,但是如果 10 是 'x' 而 [1, 2, 3, 4, 5] 是 'y',那么我就是不明白为什么 **kwargs 不够.

【问题讨论】:

  • 你的类没有覆盖__repr__,它干扰了Foo的初始化。你打印 "An instance of Foo was initialized" 实际上是 ClassDecorator 的实例被初始化的地方,Foo 永远不会被初始化。你没有得到ClassDecorator.__call__cls 参数,它只会得到*args**kwargs
  • 为什么这会让你想使用一个类作为装饰器?
  • 装饰器的一个有问题的用例。
  • 您的问题是什么?你在问为什么没有*args**kwargs 不足以转发?如果是这样,所有关于类装饰器和__repr__ 的东西都无关紧要。或者您的问题是关于如何编写一个向其参数添加方法的类装饰器?还是别的什么?
  • 如果问题是关于转发的:**kwargs 只收集不匹配的关键字参数,不收集不匹配的位置参数。否则它就无法做到——你的def 签名(或其他任何地方)中没有任何内容可以让 Python 了解你的期望。

标签: python python-3.x class python-3.6 python-decorators


【解决方案1】:

我认为您在这里遗漏了一些不同的基本概念。


首先,当您print 一个对象时,例如print(f),它所做的就是调用f 类型的__str__ 方法。如果没有人定义__str__,则调用默认的object.__str__,它只返回f.__repr__

因此,如果您想更改执行print(f) 时发生的情况,您必须f 的类型提供__str__(或__repr__)方法你想要什么。您在其他任何地方所做的任何事情都不会产生任何影响。


Foo 的实例被初始化时,你的装饰器的__init__ 不会被调用;它在 class 被初始化时被调用。 (或者,更确切地说,当在类上调用装饰器时,就在类创建之后)当您使用它来创建实例时,会发生__call__

另外,__call__ 不会从任何地方获得额外的cls 参数,它只会获得self,就像任何其他常规方法一样。这就是为什么我们必须首先将装饰类存储为self.cls


装饰器的重点是返回一个可以像装饰对象一样使用的对象。你的ClassDecorator 可以像一个类一样被调用——但是当你这样做时它什么也不返回,而不是返回一个实例。这意味着不可能创建实例;如果你尝试,你会得到None。这就是为什么print(f) 打印None


最后,转发参数通常需要*args**kwargs*args 将任何意外的位置参数收集到一个元组中,**kwargs 将任何意外的关键字参数收集到一个字典中。当有人调用Foo(10, [1, 2, 3, 4, 5]) 时,有两个位置参数,没有关键字参数。所以args 将是(10, [1, 2, 3, 4, 5])kwargs 将是{}。如果您没有*args,那么您将获得TypeError,因为您没有任何显式的位置或关键字参数来匹配这些位置参数。


所以,这是一个解决所有问题的示例。我将通过修改装饰类来做到这一点,但请记住,这不是唯一的方法,每种方法都有优缺点。

首先,我将展示如何编写一个装饰器,它只对实例变量为xy 的类执行您想要的操作。

class ClassDecorator(object):
    def __init__(self, cls):
        def _str(self):
            typ = type(self).__name__
            vars = {'x': self.x, 'y': self.y}
            return(f"Instance of {typ}, vars = {vars}")
        cls.__str__ = _str
        self.cls = cls
        print(f"The class {cls.__name__} was created")

    def __call__(self, *args, **kwargs):
        print(f"An instance of {self.cls.__name__} was created with args {args} and kwargs {kwargs}")
        return self.cls(*args, **kwargs)

现在,当你使用它时,它会做你想做的事:

>>> @ClassDecorator
... class Foo(object):
...     def __init__(self, x, y):
...         self.x = x
...         self.y = y
The class Foo was created
>>> f = Foo(10, [1, 2, 3, 4, 5])
An instance of Foo was created with args (10, 20) and kwargs {}
>>> print(f)
Instance of Foo, vars = {'x': 10, 'y': 20}
>>> f
<__main__.Foo at 0x127ecdcf8>

当然,如果你想覆盖最后一件事,你想定义__repr__,就像定义__str__一样。


那么,如果我们想在 any 类上使用它,而事先不知道它的属性是什么?我们可以使用inspect 模块来查找属性:

def _str(self):
    typ = type(self).__name__
    vars = {name: value for name, value in inspect.getmembers(self) if not name.startswith('_')}
    return(f"Instance of {typ}, vars = {vars}")

【讨论】:

  • 感谢您的详细解释。我很感激。
猜你喜欢
  • 2021-07-16
  • 2019-02-20
  • 2021-05-09
  • 2020-10-04
  • 2014-02-18
  • 1970-01-01
  • 2017-07-28
  • 2016-10-14
  • 2011-09-30
相关资源
最近更新 更多