【问题标题】:Decorator and inheritance: what happens when the decorator is applied to parent class装饰器和继承:当装饰器应用于父类时会发生什么
【发布时间】:2021-07-19 22:49:21
【问题描述】:

需要一些帮助来理解装饰器的行为...

这里有一些代码:

import random

class MyDecorator(object):
    """ Logger decorator, adds a 'logger' attribute to the class """
    def __init__(self, *args, **kwargs):
      print(random.randint(1, 100), *args, **kwargs)
      
      self.cls = args[0]

    def __call__(self, *args, **kwargs):
      # Do something, inject some attribute
      setattr(self.cls, 'x', 1234)
      
      # Return an instance of the class
      return self.cls(*args[1:], **kwargs)

@MyDecorator
class A:
  def __init__(self):
    print(f'A {self.x}')
  
@MyDecorator
class B:
  """ Parent class """
  def __init__(self):
    print(f'B {self.x}')


class B1(B):
  """ Child class """
  def __init__(self):
    super().__init__()
    
    self.logger.info('Class B1 __init__()')
    

# Here the decorator is applied directly to the class that is going to be instantiated
# Decorator's __init__() receives the class as arg[0], so I can store it and use it when __call__()'ed
a = A()

# Here the decorator is not applied to the class being instantiated, rather to its parent class
# It looks like the decorator's __init__() is being called twice:
# - first time it do recceives the class to which it is applied (in this case, B)
# - second time it receives 3 arguments: a string containing the name of the child class, a tuple containing an instance of the decorator class itself and then some dict containing internal Python controls (I think)
b1 = B1()
    

输出:

82 <class '__main__.A'>
47 <class '__main__.B'>
52 B1 (<__main__.MyDecorator object at 0x7fd4ea9b0860>,) {'__module__': '__main__', '__qualname__': 'B1', '__doc__': ' Child class ', '__init__': <function B1.__init__ at 0x7fd4e9785a60>, '__classcell__': <cell at 0x7fd4eaa0f828: empty>}
A 1234
Traceback (most recent call last):
  File "main.py", line 39, in <module>
    b1 = B1()
  File "main.py", line 12, in __call__
    setattr(self.cls, 'x', 1234)
AttributeError: 'str' object has no attribute 'x'

所以,我的问题是:

  1. 当我将装饰器应用于父类而不是其子类时会发生什么?看起来装饰器正在为父/子调用,并且在每种情况下都传递了不同的参数集
  2. 在这种情况下,我将如何解决“通过装饰器进行类实例化”? (一切都在像 A 这样的情况下正常工作,其中装饰器直接应用于正在实例化的类)
  3. 我真的应该返回类的实例吗?如果我需要链接一些装饰器会发生什么?在这种情况下,我的 __call__ 方法应该是什么样子
@MyDecorator1
@MyDecorator2
class A():

感谢您的帮助!

【问题讨论】:

  • 这里的问题是你已经用 MyDecorator 的实例替换了 AB
  • 根据How to Ask,请一次只问一个问题。除了多个问题之外,您的代码还有很多问题,只是被您的代码同时执行多个不相关但不完整的事情所掩盖。
  • 正式地,应用装饰器是在评估classdef 语句后执行的调用,装饰器的结果替换语句的结果。这就是@ 所做的一切。实例化、继承、调用实例化转发、参数集、类作为装饰器等所有恶作剧都使事情变得复杂。您尝试做或测试的一件事是什么?
  • 您的 MyDecorator 实例似乎被用作元类...不知道为什么。
  • @F.F.Knob 真的,不要使用基于类的装饰器。使用基于函数的,例如def Logger(cls): cls.logger = logger 然后确保return cls。问题是,当您执行@MyDecorator 时,它会实例化MyDecorator 的一个实例,这就是分配给名称AB 的内容,所以当您执行class B1(B): ... 时,然后B 不是B类,它是MyDecorator的一个实例。当您从非类继承时会发生奇怪的事情

标签: python decorator python-decorators metaclass


【解决方案1】:

所以,当你这样做时:

@MyDecorator
class A:
  def __init__(self):
    print(f'A {self.x}')

您可以将其视为语法糖

class B:
  def __init__(self):
    print(f'B {self.x}')

B = MyDecorator(B)

但问题是,现在B MyDecorator 的一个实例,也就是说,现在B 不指代一个类。所以,当你从B 继承时:

class B1(B):
    pass

奇怪的事情发生了。相关的奇怪的事情——最终导致错误的事情——是when deciding on the metaclass to use

3.3.3.3。确定合适的元类

  • 如果没有给出基数和显式元类,则使用type()

  • 如果给出了一个显式的元类并且它不是type()的实例,那么它直接作为元类使用;

  • 如果type() 的实例作为显式元类给出,或者定义了基类,则使用最派生的元类。

从显式指定的元类(如果有)和所有的元类(即type(cls))中选择派生最多的元类 指定的基类。派生最多的元类是一个 所有这些候选元类的子类型。

即第三个要点发生了,在这种情况下,type(instance_of_my_decorator) 用作元类,即MyDecorator。所以最终,通过不同的途径,B1 现在还引用了MyDecorator 的(另一个)实例,它得到了字符串"B1" 作为构造函数的第一个参数传递,因此在MyDecorator.__init__ 中:

 self.cls = args[0]

正在分配那个字符串,所以当你到达时:

setattr(self.cls, 'x', 1234)

__call__ 中,它失败并出现上述错误。

由于您要做的就是为类分配一个属性,因此最简单的解决方案是不使用基于类的方法,而是使用函数:

def MyDecorator(cls):
    setattr(self.cls, 'x', 1234)
    return cls # important

当然,这似乎有点矫枉过正。您可能应该像往常一样将属性设置为类属性,作为类定义语句的一部分,但如果您坚持使用装饰器,上述方法 起作用。当然,这不会被继承。

【讨论】:

  • 感谢您抽出宝贵时间!真的帮了我很多
猜你喜欢
  • 2014-03-07
  • 2011-03-23
  • 2018-04-17
  • 2011-03-01
  • 2015-06-18
  • 2019-06-28
  • 2011-09-18
  • 2019-12-07
  • 2023-04-09
相关资源
最近更新 更多