用 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提供了一种实现方式。