用 Python的装饰器实现类似 Java 的注解

发布 : 2020-09-02

用 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] # args[0] 即为需要添加元数据的函数。
func.__meta__ = {'anno':None}
return func

@anno()
def f1():pass

print(f1.__meta__)
#>>> {'anno':None}

@anno
def f2():pass

print(f2.__meta__)
#>>> {'anno':None}

添加检查

在 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 # 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__)
#>>> {'anno3': {'foo': 'zhiding', 'bar': 'b'}, 'anno2': {}, 'anno1': {'foo': 'f', 'bar': 'b'}}

程序可以正常执行。

总结

本文的程序在实现和 Java 的注解相似的部分的情况下,将 __meta__ 注入到需要装饰的对象。__meta__可以在运行时被容器或者其他程序读取。为IOC和AOP提供了一种实现方式。

本文作者 : Rothleer
原文链接 : https://rothleer.github.io/2020/09/02/%E7%94%A8%20Python%E7%9A%84%E8%A3%85%E9%A5%B0%E5%99%A8%E5%AE%9E%E7%8E%B0%E7%B1%BB%E4%BC%BC%20Java%20%E7%9A%84%E6%B3%A8%E8%A7%A3/
版权声明 : 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!

博客已萌萌哒运行(●'◡'●)ノ♥
Theme - BMW | Made With 💗 | Powered by GodBMW