微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

用装饰器装饰 Python 类作为类

如何解决用装饰器装饰 Python 类作为类

需要一些帮助来实现/理解装饰器作为一个类在 Python 中是如何工作的。我发现的大多数例子要么装饰一个类,但作为一个函数实现,要么作为一个类实现,但装饰一个函数。我的目标是创建作为类实现的装饰器并装饰类。

更具体地说,我想创建一个 @Logger 装饰器并在我的一些类中使用它。这个装饰器所做的只是在类中注入一个 self.logger 属性,所以每次我用 @Logger 装饰一个类时,我都可以在它的方法self.logger.debug()

一些初步问题:

  1. 装饰器的 __init__ 接收什么作为参数?我只会接收装饰类和一些最终的装饰器参数,这实际上是大多数情况下会发生的情况,但请查看下面 DOMElementFeatureExtractor输出。为什么它会收到所有这些参数?
  2. __call__ 方法怎么样?它会收到什么?
  3. 如何为装饰器 (@Logger(x='y')) 提供参数?会传递给 __init__ 方法吗?
  4. 我真的应该在 __call__ 方法中返回类的实例吗? (只有这样我才能让它工作)
  5. 链接装饰器怎么样?如果之前的装饰器已经返回了一个类的实例,这将如何工作?为了能够@Logger @Counter MyClass:,我应该在下面的示例中修复什么?

请看一下这个示例代码。我已经创建了一些虚拟示例,但最后你可以看到我真实项目中的一些代码

你可以在最后找到输出

对于理解作为类实现的 Python 类装饰器的任何帮助,我们将不胜感激。

谢谢

from abc import ABC,abstractmethod

class ConsoleLogger:
  def __init__(self):
    pass
  
  def info(self,message):
    print(f'INFO {message}')

  def warning(self,message):
    print(f'WARNING {message}')
   
  def error(self,message):
    print(f'ERROR {message}')

  def debug(self,message):
    print(f'DEBUG {message}')

class Logger(object):
    """ Logger decorator,adds a 'logger' attribute to the class """
    def __init__(self,cls,*args,**kwargs):
      print(cls,**kwargs)
      self.cls = cls
      
    def __call__(self,**kwargs):
      print(self.cls.__name__)
      
      logger = ConsoleLogger()
      
      setattr(self.cls,'logger',logger)
      
      return self.cls(*args,**kwargs)

class Counter(object):
    """ Counter decorator,counts how many times a class has been instantiated """
    count = 0
    def __init__(self,**kwargs):
       self.cls = cls
      
    def __call__(self,**kwargs):
      count += 1
      
      print(f'Class {self.cls} has been initialized {count} times')
      
      return self.cls(*args,**kwargs)
      
@Logger
class A:
  """ Simple class,no inheritance,no arguments in the constructor """
  def __init__(self):
    self.logger.info('Class A __init__()')

class B:
  """ Parent class for B1 """
  def __init__(self):
    pass

@Logger
class B1(B):
  """ Child class,still no arguments in the constructor """
  def __init__(self):
    super().__init__()
    
    self.logger.info('Class B1 __init__()')
    
class C(ABC):
  """ Abstract class """
  def __init__(self):
    super().__init__()
    
  @abstractmethod
  def do_something(self):
    pass
  
@Logger
class C1(C):
  """ Concrete class,implements C """
  def __init__(self):
    self.logger.info('Class C1 __init__()')
  
  def do_something(self):
    self.logger.info('something')

@Logger
class D:
  """ Class receives parameter on intantiation """
  def __init__(self,color):
    self.color = color
    
    self.logger.info('Class D __init__()')
    self.logger.debug(f'color = {color}')

class AbstractGenerator(ABC):
  def __init__(self):
    super().__init__()
    
    self.items = None
    self.next_item = None
    
  @abstractmethod
  def __iter__(self):
    pass
  
  def __next__(self):
    pass
  
  def __len__(self):
    pass

  def __getitem__(self,key):
    pass
  
class AbstractDOMElementExtractor(AbstractGenerator):
  def __init__(self,parameters,content):
    super().__init__()
    
    self.parameters = parameters
    self.content = content
    
@Logger
class DOMElementExtractor(AbstractDOMElementExtractor):
  def __init__(self,content):
    super().__init__(parameters,content)
  
  def __iter__(self):
    self.logger.debug('__iter__')
  
  def __next__(self):
    self.logger.debug('__next__')  

  def __len__(self):
    self.logger.debug('__len__')

  def __getitem__(self,key):
    self.logger.debug('__getitem__')
    
class DOMElementFeatureExtractor(DOMElementExtractor):
  def __init__(self,content)

class DocumentProcessor:
  def __init__(self):
    self.dom_element_extractor = DOMElementExtractor(parameters={},content='')
  
  def process(self):
    self.dom_element_extractor.__iter__()
    
a = A()
b1 = B1()
c1 = C1()
c1.do_something()
d = D(color='Blue')

document_processor = DocumentProcessor()
document_processor.process()

输出

<class '__main__.A'>
<class '__main__.B1'>
<class '__main__.C1'>
<class '__main__.D'>
<class '__main__.DOMElementExtractor'>
DOMElementFeatureExtractor (<__main__.Logger object at 0x7fae27c26400>,) {'__module__': '__main__','__qualname__': 'DOMElementFeatureExtractor','__init__': <function DOMElementFeatureExtractor.__init__ at 0x7fae27c25840>,'__classcell__': <cell at 0x7fae27cf09d8: empty>}
A
INFO Class A __init__()
B1
INFO Class B1 __init__()
C1
INFO Class C1 __init__()
INFO something
D
INFO Class D __init__()
DEBUG color = Blue
DOMElementExtractor
DEBUG __iter__

解决方法

不会是一个完整的答案,但我认为回顾装饰者的基础知识会有所帮助。这就是装饰的样子:

@Logger
class A:
  # A's code

根据定义,它相当于这样做:

class A
  # A's code

A = Logger(A) # Logger has to be callable because...it's called

来源经常说装饰器“修改”,但这实际上只是预期用途。从技术上讲,您需要的只是 A 有一个定义(所以是一个函数、方法或类)和 Logger 是可调用的。如果 Logger 返回 "Hello,World",那就是 A

好的,让我们假设我们暂时没有装饰 A 并考虑一下 Logger(A) 需要什么才能“修改”。好吧,A 是一个类,您调用一个类来创建实例:A(*args)。因此,Logger(A)(*args) 也必须是 A 的实例。但是 Logger(A) 不是类 A,它是 Logger 的一个实例。幸运的是,您可以通过在类中定义 __call__ 方法使实例可调用。 Logger__call__ 方法调用存储在其 cls 属性中的类并返回实例。

至于装饰器中的参数,它也有助于思考它的等价物。您有兴趣这样做:

@Logger(x='y')
class A:
  # A code

所以它等价于:

class A:
  # A code

A = Logger(x = 'y')(A)

请注意,Logger 本身A 作为参数。它将 'y' 作为参数并返回 另一个A 作为参数的可调用对象。因此,如果 Logger 是一个类,那么 Logger(x = 'y') 将是一个 Logger 实例。如果类具有 __call__ 方法,则类的实例也可以用作装饰器!

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。