1 前言
古语有云:人生苦短,快用 Python。
众所周知,Python 是一门开发效率非常高的语言。当使用这个以开发效率著称的语言去封装 HTTP API 时,如果还是为每个 API 写一遍实现,就太不 Pythonic 了。
本文将介绍如何将 “声明即实现” 这个方案带入到 Python 中,主要用到的技术是装饰器(Decorator)和 inspect
模块。
2 装饰器 先简单介绍一下装饰器。
装饰器是用来装饰函数的,一个特殊的函数。它只有一个参数,是被装饰的函数对象,返回值应为一个函数或可调用对象(Callable Object):
1 2 3 4 5 6 7 def decorator (func ): def wrapper (*args, **kwargs ): result = func(*args, **kwargs) return result return wrapper
在使用装饰器时,只需要在目标函数的上一行,加上”@
+ 装饰器名称” 即可:
1 2 3 @decorator def foobar (): return None
是的,看起来就像 Java 的注解一样。
装饰器也是可以配合参数使用,这时候就要多套一层函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 def decorator (*some_args, **some_other_args ): def wrapper_creator (func ): def wrapper (*args, **kwargs ): result = func(*args, **kwargs) return result return wrapper return wrapper_creator @decorator("foo" , "bar" ) def foobar (): return None
尽管我在装饰器的演示代码中调用了原始函数,实际上开发者可以自行选择是否这样做,甚至可以完全不鸟原始函数。
当调用一个被装饰过的函数时,实际上调用的是装饰器返回的函数对象,因此装饰器返回的函数对象,其形参和返回值应与目标函数相同。
不过在 Python 中,有 (*args, **kwargs)
这样的通用形参写法,而且由于 Python 是弱类型语言,因此对返回值类型的要求也不严格,这就给实现通用装饰器提供了便利。
3 实现 在对装饰器有个基本的认识之后,就可以开始进行 “声明即实现” 这个方案了。
3.1 声明 首先,仍是声明 HTTP API 的封装类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class FoobarClient : def set_name (self, name ): pass def set_profile (self, first_name, last_name, email, bio ): pass def set_avatar (self, url ): pass def say_hello (self ): pass
3.2 装饰器 3.2.1 定义 创建带参数的装饰器 http_api
,接受一个 path
参数,用于传入 HTTP API 的 URL Path:
1 2 3 4 5 6 7 8 9 def http_api (path ): api_url = 'https://api.foo.bar/%s' % path def wrapper_creator (func ): def wrapper (*args, **kwargs ): pass return wrapper return wrapper_creator
装饰器的具体实现留在后面,这里先用装饰器把所有 API 函数都装饰一遍:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class FoobarClient : @http_api(path='name/set' ) def set_name (self, name ): pass @http_api(path='profile/set' ) def set_profile (self, first_name, last_name, email, bio ): pass @http_api(path='avatar/set' ) def set_avatar (self, url ): pass @http_api(path='action/say_hello' ) def say_hello (self ): pass
装饰完成后,对每个 API 函数的调用,都会走到装饰器内的 wrapper
函数中。
3.2.2 实现 接下来开始进行装饰器的实现,也就是对 wrapper
函数的实现。
第一个需要解决的问题,就是怎样拿到函数参数和 API 参数的对应关系。在 Java 中,是通过反射实现这一点的,而在 Python 中,一个函数就搞定了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import inspectdef http_api (path ): api_url = 'https://api.foo.bar/%s' % path def wrapper_creator (func ): def wrapper (*args, **kwargs ): call_args = inspect.getcallargs(func, *args, **kwargs) print ('api_url => %s' % api_url) print ('call_args => %r' % call_args) pass return wrapper return wrapper_creator
这里先加上两行调试代码,把 api_url
和 call_args
打印出来,然后调用几个 API 函数试验一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 client = FoobarClient() client.set_name('deadblue' ) client.set_profile( first_name='Tomo' , last_name='Kunagisa' , email='[email protected] ' , bio='This Ugly Yet Beautiful World' )
可以看到,call_args
就是一个形参名->参数值
的字典。也就是说,在定义 API 方法时,只要让函数的形参名和 HTTP API 的参数名保持一致即可。
由于被装饰的 API 函数,都是类(Class)上的方法(Method),因此第一个形参都是 self
,即类实例自身。在把 call_args
转换成 HTTP 表单(Form)时,记得过滤掉它。
至此,就拿到了调用 HTTP API 的所有必要信息,最后只要封装一个通用的发送请求方法,再通过装饰器调用即可。这里直接将这个方法定义在 FoobarClient
上:
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 def http_api (path ): api_url = 'https://api.foo.bar/%s' % path def wrapper_creator (func ): def wrapper (*args, **kwargs ): call_args = inspect.getcallargs(func, *args, **kwargs) client = call_args['self' ] del call_args['self' ] return client.call_http_api(api_url, call_args) return wrapper return wrapper_creator class FoobarClient : api_key = None def __init__ (self, api_key ): self.api_key = api_key def call_http_api (self, url, args ): return None
发送 HTTP 请求和解析 JSON 结果的代码就不用赘述了,这应该是大多数 Python 程序员的基本技能了。
最终,通过封装好的 FoobarClient 调用 HTTP API 的代码为:
1 2 3 client = FoobarClient('you-api-key' ) client.set_name('deadblue' ) client.say_hello()
4 总结 同样是 AOP 的编程思想,Python 带来了一个完全不同的编程体验。
装饰器是一个设计的非常巧妙的语法糖,配合通用形参,可以演化出多种多样的玩法。
等有空的时候,准备专门写一篇来介绍它。
咕!
5 参考