首页 > 解决方案 > 使用类作为装饰器

问题描述

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

我正在关注: 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 是不够的。

标签: pythonpython-3.xclasspython-3.6python-decorators

解决方案


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


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

所以,如果你想改变你做 a 时发生的事情print(f),你必须f' 类型一个__str__(或__repr__)你想要的方法。您在其他任何地方所做的任何事情都不会产生任何影响。


初始化__init__实例时不会调用您的装饰器;它在类被初始化Foo时被调用。(或者,更确切地说,当装饰器在类上被调用时,在类创建之后)当你使用它来创建一个实例时就会发生这种情况。__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将会如此{}。如果你没有 a *args,那么你会得到 a TypeError,因为你没有任何显式的位置或关键字参数来匹配这些位置参数。


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

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

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__.


那么,如果我们想在任何类上使用它,而不事先知道它的属性是什么?我们可以使用该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}")

推荐阅读