用 Python的装饰器实现类似 Java 的注解 摘要 本文基于 Python 的装饰器功能,实现 Python 中实现了类 Java 的注解。使得容器可以在运行时读取注解内包含的信息。且在使用上兼容 Java 的写法。
介绍 什么是装饰器? 首先需要了解所谓的“装饰器”是什么?装饰器是一种语法糖,如下列代码所示,在函数上添加@dec相当于func = dec(func)。python 装饰器实现了23种设计模式之一的装饰器模式,在不修改原有函数的情况下,为函数代码的情况下为函数添加功能。尽管 Java 注解从代码上看非常类似于装饰器,然而二者之间并没有什么联系。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def  dec (func) :    print(func.__name__)     return  func @dec # <=> func = dec(func) def  func () :    print('func exec!' ) """ >>>  func()func func exec! """ 
 
什么是注解? Java 中的注解可以作用在类,方法,变量,参数上。注解会成为元数据[1]的一部分,容器可以通过读取元数据来操控程序。
从装饰器到注解? Python 的装饰器能修饰的范围并不如 Java 那么丰富,只能修饰方法,函数,类。Python 的解释器在导入函数的过程中会执行装饰器中的代码。而注解的作用是,为函数添加元数据。Python 并不需要像 Java 那样通过反射的方法来获取对象的元数据。为了编码方便我们采用为被修饰对象添加__meta__ 方法来保存需要的元数据。
装饰器的代码会在代码被引入的时候自动的执行。在被修饰的对象中添加元数据。
1 2 3 4 5 6 7 def  anno (func) :    func.__meta__ = {'anno' :None } 	return  func @anno def  func () :    print(‘func exec ’) 
 
编写注解 在 Python 中并不存在接口,因此我们使用带默认参数的函数代替Java 中的@interface。如下所示:
1 2 def  anno (foo='f' ,bar='b' ) :    pass  
 
实现不定参数,兼容Java语法 由于装饰器的特性,@anno 并不等价于 @anno() ,但是我们希望装饰器能够实现 @anno <=> @anno()。使得注解兼容于 Java 的语法。
这里可以利用 Python 不定参数。通过检测不定参数的长度,检查执行的对象是否为要装饰的函数。若是没有参数,则让对象返回自己。同理,可以应用 Python 的魔法方法来获取默认参数的名称以及值,来为元数据添加其他属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def  anno (*args,**kwargs) :    if  len(args)==0 :         return  anno     elif  len(args)==1 :         func = args[0 ]          func.__meta__ = {'anno' :None }         return  func @anno() def  f1 () :pass print(f1.__meta__) @anno def  f2 () :pass print(f2.__meta__) 
 
添加检查 在 Java 中自定义注解可以指定修饰的对象,通过 type 函数来检测需要修饰的对象是否为指定对象。
1 2 3 4 5 object_type = 'function'  def  anno (func) :    if  type(func).__name__ != object_type:         raise  Exception(f'Type of {func.__name__}  is not {object_type} ' ) 	return  func 
 
多个注解 如上述代码,__meta__ 对象本身是一个字典,因此多个装饰器之间添加属性并不会相互影响。而同个装饰器则会覆盖。同时通过字典检查是否存在重复添加的注解。
将上述功能融合到一个类中: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 class  target :    TYPE_CLASS = 'type'      TYPE_FUNCTION = 'function'      TYPE_METHOD = 'method'      def  __init__ (self, type) :         self.type = type         self.temporary_stroage_params = None      def  __call__ (self, target_annotation) :         def  f (*args, **kwargs) :             if  len(args) == 1  and  len(kwargs) == 0 :                 obj = args[0 ]                 if  type(obj).__name__ != self.type:                     raise  Exception('Anno target is Wrong!' )                 self.add_to_meta(obj, target_annotation)                 return  obj             elif  len(args) == 0 :                 if  len(kwargs) != 0 :                     self.temporary_stroage_params = kwargs                 return  f             else : 				raise  Exception('Annotation Modification Type Exception' )         return  f     def  add_to_meta (self, obj, target_annotation) :         ts_params = self.temporary_stroage_params           default_params_name = target_annotation.__code__.co_varnames         default_params_value = target_annotation.__defaults__         params = {}         anno = target_annotation.__name__         size = target_annotation.__code__.co_argcount         for  i in  range(size):             param_name = default_params_name[i]             if  ts_params is  not  None  and  param_name in  ts_params:                 params[param_name] = ts_params[param_name]             else :                 params[param_name] = default_params_value[i]         if  hasattr(obj, '__meta__' ):             obj.__meta__[anno] = params         else :             if  anno in  obj.__meta__:                 raise  Exception('Annotation Modification Repetition Exception' )             obj.__meta__ = {anno: params}         self.temporary_stroage_params = None  
 
测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @target(type=target.TYPE_FUNCTION) def  anno1 (foo='f' , bar='b' ) :    pass  @target(type=target.TYPE_FUNCTION) def  anno2 () :    pass  @target(type=target.TYPE_FUNCTION) def  anno3 (foo='f' , bar='b' ) :    pass  @anno1 @anno2() @anno3(foo='zhiding') def  func () :    print('exec' ) print(func.__meta__) 
 
程序可以正常执行。
总结 本文的程序在实现和 Java 的注解相似的部分的情况下,将 __meta__ 注入到需要装饰的对象。__meta__可以在运行时被容器或者其他程序读取。为IOC和AOP提供了一种实现方式。