Python通过装饰器并使用cprofile对函数进行性能分析

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python通过装饰器并使用cprofile对函数进行性能分析相关的知识,希望对你有一定的参考价值。

参考技术A Python中提供了很多接口方便我们能够灵活进行性能分析,包括cProfile模块中的Profile类和pstat模块中的Stats类。

--cprofile是一种确定性分析器,只测量CPU时间,并不关心内存的消耗情况和其他与内存相关联的信息

--它是基于Isprof的用C语言实现的扩展应用,运行开销比较合理,适合分析运行时间较长的程序

--enable(): 开始进行性能分析并收集数据

--disableI(): 停止性能分析

--create_stats(): 停止收集数据,并为已经收集的数据创建stats对象

--print_stats():创建stats对象并打印分析结果

--dump_stats(filename): 把当前性能分析的内容写入文件filename中

--runcall(func, *args, **kwargs): 收集被调用函数func的性能分析信息

--用来分析cProfile输出的文件内容

--pstas模块为开发者提供了Stats类,可以读取和操作stats文件

(Stats类可以接受stats文件名,也可以直接接受cProfile.Profile对象作为数据源。)

--strip_dirs(): 删除报告中所有函数文件名的路径信息

--dump_stats(filename): 把stats中的分析数据写入文件(也可以写成cProfile.Profile.dump_stats())

--sort_stats(*keys): 对报告列表进行排序,函数会一次按照传入的参数排序

--reverse_order(): 逆反当前的排序

--print_stats(*restrictions): 把信息打印到标准输出。*restrictions用于控制打印结果的形式,比如(10,1.0,".*.py.*")表示打印所有py文件的信息的前10行结果

--第一行表示运行这个函数一共使用0.043秒,执行了845次函数调用

--第二行表示结果是按什么顺序排列的(这里表示按照调用次数来进行排列的)

--ncalls: 表示函数调用的次数(有两个数值表示有递归调用,总调用次数/原生调用次数)

--tottime: 函数内部调用时间(不包括他自己调用的其他函数时间)

--percall: tottime/ncalls

--cumtime: 表示累计调用时间(函数执行玩的总时间),它包含了函数自己内部调用的函数时间

--filename:lineno(function): 函数所在的文件,行号,函数名称

上面的函数do_cProfile(do=False, order='tottime')是一个带参数的装饰器,通过do的值来进行性能分析的开关控制,通过order的值来选择输出结果按照什么方式进行排序。

比如我们对函数A和函数B进行性能分析

如果不给装饰器传入参数的话就是默认的False和tottime

https://zhuanlan.zhihu.com/p/24495603

https://blog.csdn.net/weixin_40304570/article/details/79459811

使用装饰器作为类来装饰 Python 类

【中文标题】使用装饰器作为类来装饰 Python 类【英文标题】:Decorating a Python class with a decorator as a class 【发布时间】:2021-07-16 04:15:18 【问题描述】:

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

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

一些初步问题:

    装饰器的__init__ 接收什么作为参数?我将只接收装饰类和一些最终的装饰器参数,这实际上是大多数情况下发生的情况,但请查看下面的输出 DOMElementFeatureExtractor。为什么它会收到所有这些参数? __call__ 方法呢?它会收到什么? 如何为装饰器提供参数(@Logger(x='y'))?是否会传递给__init__ 方法? 我真的应该在__call__ 方法中返回一个类的实例吗? (只有这样我才能让它工作) 链接装饰器怎么样?如果之前的装饰器已经返回了该类的一个实例,那将如何工作?为了能够@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, *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()

输出:

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

【问题讨论】:

我很好奇这个class AbstractDOMElementExtractor(AbstractGenerator): 是如何工作的,因为它没有实现基类中定义的抽象方法。 还有,为什么需要一个装饰器作为一个类? 我相信这里的答案可能会对您的一些问题有所帮助***.com/questions/10294014/… 关于将装饰器作为一个类的需求,没有必要,只是喜欢与类一起工作并有兴趣了解它是如何工作的。 关于AbstractDOMElementExtractor,因为该类没有在任何地方实例化,我想它可以将实现推迟到该类继承并实际实例化的任何地方,在这种情况下DOMElementExtractor 【参考方案1】:

不会是一个完整的答案,但我认为复习装饰器的基础知识会很有帮助。这就是装饰的样子:

@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__ 方法,则类的实例也可以用作装饰器!

【讨论】:

以上是关于Python通过装饰器并使用cprofile对函数进行性能分析的主要内容,如果未能解决你的问题,请参考以下文章

如何在基类的函数上使用装饰器并在继承基类时让装饰器在该函数上工作

使用装饰器作为类来装饰 Python 类

从函数中调用 Python cProfile。 (或其他在 Django 中使用 cProfile 的方式)

使用 Argument Parser 的函数上的 cProfile

python性能分析之cProfile模块

在python中将函数传递给cProfile的正确方法是啥?