百科问答小站 logo
百科问答小站 font logo



你写过哪些真正生产可用的 Python 装饰器? 第1页

  

user avatar   huo-hua-de-41 网友的相关建议: 
      

分享一个炼丹的模块注册装饰器。这个装饰器的作用在于,可以使用外部config,快速切换代码中需要用到的模块,非常适合快速实验或者并行实验。

首先的基本的类。

       class PluginManager:     """Plugin manager"""      def __init__(self):         self.plugin_container: Dict[str:Dict[str:object]] = {}      def register(self, plugin_type: str, plugin_name: str, plugin_cls):         if plugin_type not in self.plugin_container:             self.plugin_container[plugin_type] = {}          self.plugin_container[plugin_type][plugin_name] = plugin_cls      def get(self, plugin_type: str, plugin_name: str):         if plugin_type in self.plugin_container and plugin_name in self.plugin_container[plugin_type]:             return self.plugin_container[plugin_type][plugin_name]         else:             return None     

然后封装两个函数,其中register_plugin便是装饰器。

       DefaultPluginManager = PluginManager()  def register_plugin(plugin_type: str, plugin_name: str):     def decorator(cls):         DefaultPluginManager.register(plugin_type, plugin_name, cls)         return cls      return decorator  def get_plugin(plugin_type: str, plugin_name: str):     return DefaultPluginManager.get(plugin_type, plugin_name)     

写好一个模块,通过装饰器,把它注册一下。例如下面的BCELoss

       import plugin  @plugin.register_plugin("model_loss", 'BCELoss') class BCELoss:      @classmethod     def build(cls, cfg):         return nn.BCEWithLogitsLoss(reduction='sum')     

使用的时候,从注册表里取出来即可

       loss = plugin.get_plugin("model_loss", "BCELoss")     

user avatar   zhang-san-5-26-12 网友的相关建议: 
      

也欢迎关注我的知乎账号 @石溪 ,将持续发布机器学习数学基础及Python数据分析编程应用等方面的精彩内容。

装饰器是python里的一个非常有意思的部分,他用于封装函数代码,显式的将封装器应用到被封装的函数上,从而使得他们选择加入到装饰器指定的功能中。对于在函数运行前处理常见前置条件(例如确认授权),或在函数运行后确保清理(输出清除或异常处理),装饰器都非常有用。

是不是感觉听不明白,太绕了!

简单来说,装饰器就是实现了一个通用的功能,然后将这个通用的功能应用到不同的、需要使用这个功能的函数上,从而避免每次都在不同函数上反复写相同的功能代码。

装饰器的本质是一个函数,他接受被装饰的函数作为位置参数,装饰器通过使用该参数来执行某些操作,然后返回一个函数引用,这个函数可以是原始函数,或者是另外一个函数。

我们举例子说明,装饰器是这样的函数,他们接受被装饰的可调用函数作为唯一的参数,并且返回一个可调用函数,

       registry = [] def register(decorated):     registry.append(decorated)     return decorated  def foo():     return 3  foo = register(foo) print(registry[0])  <function foo at 0x00000000025D51E0>     

register方法是一个简单的装饰器,它把被装饰的函数添加到一个列表中,然后这里是将未改变的被装饰函数返回,可以看出,装饰器一般是传入被装饰函数的引用,然后经过一些指定的处理,最后返回值也是一个函数引用。

还有一种更简单的语法形式:

装饰器的语法糖:我们这里看到的对foo进行装饰的方法是运用

foo = register(foo)语句,还有一种简单的用法是在声明函数的位置应用装饰器,从而使得代码更容易阅读,并且让人立刻意识到使用了装饰器

       registry = [] def register(decorated):     registry.append(decorated)     return decorated  @register def foo(x=3):     return x  @register def bar(x=5):     return 5  for func in registry:     print(func())  3 5     

再看一个更复杂、更一般化的装饰器函数。装饰器的本质是在执行原有函数(被装饰的函数)的同时,再加上一些额外的功能。

       def requires_ints(decorated):     def inner(*args, **kwargs):         kwarg_values = [i for i in kwargs.values()]         for arg in list(args) + kwarg_values:             if not isinstance(arg, int):                 raise TypeError('{} only accepts integers as arguments'.format(decorated.__name__))         return decorated(*args, **kwargs)     return inner     

在这个装饰器函数requires_ints我们可以看到,他定义了一个内嵌函数inner,这个内嵌函数的参数首先收集被装饰函数的所有参数,然后对其进行判断,判断其是否为整数类型(这就是装饰器添加的额外功能),然后再调用被装饰的函数本身,最后将这个内嵌函数返回。因此当我们再用原函数名进行调用的时候,原来的被装饰函数的引用就能指向这个新的内嵌函数,就能在实现原函数功能的基础上,加上附加的功能了。

同时,我们再提炼一下这里面的几个重难点:

第一,requires_ints中,decorated这个变量是内嵌作用域的变量,在他调用退出后,返回的inner函数是可以记住这个变量的。

第二,python不支持函数的参数列表的多态,即一个函数名只能对应唯一的参数列表形式。

第三,在内嵌函数内部调用被装饰函数的时候,使用了解包参数,关于这*args, **kwargs,的参数形式,前面章节中细讲过。

那我们也用这个装饰器来装饰一个函数。

       @requires_ints def foo(x,y):     print(x+y)  foo(3,5)  8     

这里将名称foo赋给inner函数,而不是赋给原来被定义的函数,如果运行foo(3,5),将利用传入的这两个参数运行inner函数,inner函数执行类型检查,然后运行被装饰方法,如果传入的不是整形数,例如下面这个例子,那么装饰器的附加功能就会进行类型检查:

       @requires_ints def foo(x,y):     print(x+y)  foo('a',5)  Traceback (most recent call last):   File "E:/12homework/12homework.py", line 15, in <module>     foo('a',5)   File "E:/12homework/12homework.py", line 7, in inner raise TypeError('{} only accepts integers as arguments'.format(decorated.__name__)) TypeError: foo only accepts integers as arguments     

其次内嵌的函数和被装饰的函数的参数形式必须完全一样,这里用的*args, **kwargs概况函数参数的一般形式,因此也是完全对应的。

最后说说装饰器参数

最后来介绍这个复杂一些的话题,装饰器参数。之前我们列举的常规例子里,装饰器只有一个参数,就是被装饰的方法。但是,有时让装饰器自身带有一些需要的信息,从而使装饰器可以用恰当的方式装饰方法十分有用。

这些参数并不是和被装饰的函数并列作为参数签名,而是在原有装饰器的基础上额外再增加一层封装,那么,实质是这个接受其他参数的装饰器并不是一个实际的装饰器,而是一个返回装饰器的函数。

最终返回的内嵌函数inner是最终使用indent和sort_keys参数的函数,这没有问题

       import json  def json_output(indent=None, sort_keys=False):     def actual_decorator(decorated):         def inner(*args, **kwargs):             result = decorated(*args, **kwargs)             return json.dumps(result, indent=indent, sort_keys=sort_keys)         return inner     return actual_decorator  @json_output(indent=8) def do_nothing():     return {'status':'done','func':'yes'}  print(do_nothing())  {         "status": "done",         "func": "yes" }     

我们在这里详细解释说明的是操作顺序,看上去我们使用的是@json_output(indent=8),作这和之前的装饰器语法糖看上去有些不同,实际上这个不是最终的装饰器函数,通过调用json_output(indent=8),返回函数指针actual_decorator,这个函数才是真正放在@后的装饰器函数,原始的被装饰函数最终获得了内涵更丰富的inner函数对象,完成了装饰过程,值得一提的是,所谓的装饰器参数最终传给了最内层的inner函数。

记住,在定义装饰器函数后,真正的装饰器函数只有一个参数,那就是被装饰的函数指针,而有其他参数的函数实质上只是装饰器的外围函数,他可以依据参数对装饰器进行进一步的定制。一句话:一个函数不可能接受被装饰的方法,又接受其他参数

在语法糖中@func这种不带括号的,就是直接使用装饰器函数进行装饰,如果是@func()带括号的,实质上是先调用func()函数返回真正的装饰器,然后再用@进行调用。

关于Python编程和数据分析更全面的内容,欢迎关注我在CSDN上的专栏《python数据分析编程基础》。

当然还有《机器学习中的数学-全集》系列专栏,欢迎大家阅读,配合食用,效果更佳~

有订阅的问题可咨询微信:zhangyumeng0422




  

相关话题

  如何评价中科院计算所发布的「木兰」编程语言体系? 
  VBA会被Python代替吗? 
  我发现设计模式一个很奇妙的情况,不知各位知友遇过没? 
  GitHub 上有哪些,简单、易学的 Python 项目? 
  华为自研编程语言「仓颉」试用报名开启,有哪些值得关注的信息? 
  编写基于机器学习的程序,有哪些编写和调试的经验和窍门? 
  如何看待「大部分程序员只会写三年代码」的说法? 
  有哪些事实没有一定计算机知识的人不会相信? 
  smarty 应该由谁来写? 
  C++的运行时多态,性能损失有多大? 

前一个讨论
你身边从「生化环材土木」转行到CS朋友现在都混的怎么样了?
下一个讨论
广东将研究深圳经港珠澳大桥至珠海、澳门通道,释放了什么信号?有哪些信息值得关注?





© 2024-06-02 - tinynew.org. All Rights Reserved.
© 2024-06-02 - tinynew.org. 保留所有权利