簡體   English   中英

使用裝飾器將 Python class 裝飾為 class

[英]Decorating a Python class with a decorator as a class

需要一些幫助來實現/理解作為 class 的裝飾器如何在 Python 中工作。 Most examples I've found are either decorating a class, but implementend as a function, or implemented as a class, but decorating a function. 我的目標是創建作為類和裝飾類實現的裝飾器。

更具體地說,我想創建一個@Logger裝飾器並在我的一些課程中使用它。 這個裝飾器所做的只是在 class 中注入一個self.logger屬性,所以每次我用@Logger裝飾一個 class 時,我都可以在它的方法中使用self.logger.debug()

一些初步的問題:

  1. 裝飾器的__init__接收什么作為參數? 我只會收到裝飾的 class 和一些最終的裝飾器參數,這實際上是大多數情況下發生的情況,但請查看下面的 output 以獲得DOMElementFeatureExtractor 為什么它會收到所有這些參數?
  2. __call__方法呢? 它會收到什么?
  3. 如何為裝飾器( @Logger(x='y') )提供參數? 它會被傳遞給__init__方法嗎?
  4. 我真的應該在__call__方法中返回 class 的實例嗎? (只有這樣我才能讓它工作)
  5. 鏈接裝飾器呢? 如果之前的裝飾器已經返回了 class 的實例,那將如何工作? 為了能夠@Logger @Counter MyClass:我應該在下面的示例中修復什么?

請看一下這個示例代碼。 我創建了一些虛擬示例,但最后你可以看到我真實項目中的一些代碼。

您可以在最后找到 output。

任何幫助理解 Python 類裝飾器實現為 class 將不勝感激。

謝謝

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, *args, **kwargs)
      self.cls = cls
      
    def __call__(self, *args, **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, cls, *args, **kwargs):
       self.cls = cls
      
    def __call__(self, *args, **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, parameters, 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, parameters, content):
    super().__init__(parameters, 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()

Output:

<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有一個定義(因此 function、方法或類)和Logger是可調用的。 如果Logger返回"Hello, World" ,這就是A結果。

好的,讓我們假設我們沒有對A進行一點裝飾,並考慮一下Logger(A)需要什么來“修改”。 好吧, A是 class,您調用class 來創建實例: A(*args) 因此, Logger(A)(*args)也必須是A實例。 但是Logger(A)不是 class A ,它是Logger的一個實例。 幸運的是,您可以通過在其 class 中定義__call__方法來使實例可調用。 Logger__call__方法調用存儲在其cls屬性中的 class 並返回實例。

至於裝飾器中的參數,考慮它的等價物也很有幫助。 你有興趣這樣做:

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

所以它等價於:

class A:
  # A code

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

請注意, Logger本身沒有A作為參數。 它以'y'作為參數並返回另一個A作為參數的可調用對象。 因此,如果Logger是 class,則Logger(x = 'y')將是Logger實例。 如果 class 具有__call__方法,則 class 的實例也可以用作裝飾器!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM