- 前言
- 目标读者
- 非目标读者
- 本书的结构
- 以实践为基础
- 硬件
- 杂谈:个人的一点看法
- Python 术语表
- Python 版本表
- 排版约定
- 使用代码示例
- 第一部分 序幕
- 第 1 章 Python 数据模型
- 第二部分 数据结构
- 第 2 章 序列构成的数组
- 第 3 章 字典和集合
- 第 4 章 文本和字节序列
- 第三部分 把函数视作对象
- 第 5 章 一等函数
- 第 6 章 使用一等函数实现设计模式
- 第 7 章 函数装饰器和闭包
- 第四部分 面向对象惯用法
- 第 8 章 对象引用、可变性和垃圾回收
- 第 9 章 符合 Python 风格的对象
- 第 10 章 序列的修改、散列和切片
- 第 11 章 接口:从协议到抽象基类
- 第 12 章 继承的优缺点
- 第 13 章 正确重载运算符
- 第五部分 控制流程
- 第 14 章 可迭代的对象、迭代器和生成器
- 14.1 Sentence 类第1版:单词序列
- 14.2 可迭代的对象与迭代器的对比
- 14.3 Sentence 类第2版:典型的迭代器
- 14.4 Sentence 类第3版:生成器函数
- 14.5 Sentence 类第4版:惰性实现
- 14.6 Sentence 类第5版:生成器表达式
- 14.7 何时使用生成器表达式
- 14.8 另一个示例:等差数列生成器
- 14.9 标准库中的生成器函数
- 14.10 Python 3.3 中新出现的句法:yield from
- 14.11 可迭代的归约函数
- 14.12 深入分析 iter 函数
- 14.13 案例分析:在数据库转换工具中使用生成器
- 14.14 把生成器当成协程
- 14.15 本章小结
- 14.16 延伸阅读
- 第 15 章 上下文管理器和 else 块
- 第 16 章 协程
- 第 17 章 使用期物处理并发
- 第 18 章 使用 asyncio 包处理并发
- 第六部分 元编程
- 第 19 章 动态属性和特性
- 第 20 章 属性描述符
- 第 21 章 类元编程
- 结语
- 延伸阅读
- 附录 A 辅助脚本
- Python 术语表
- 作者简介
- 关于封面
5.8 获取关于参数的信息
HTTP 微框架 Bobo 中有个使用函数内省的好例子。示例 5-12 是对 Bobo 教程中“Hello world”应用的改编,说明了内省怎么使用。
示例 5-12 Bobo 知道 hello 需要 person 参数,并且从 HTTP 请求中获取它
import bobo @bobo.query('/') def hello(person): return 'Hello %s!' % person
bobo.query 装饰器把一个普通的函数(如 hello)与框架的请求处理机制集成起来了。装饰器会在第 7 章讨论,这不是这个示例的关键。这里的关键是,Bobo 会内省 hello 函数,发现它需要一个名为 person 的参数,然后从请求中获取那个名称对应的参数,将其传给 hello 函数,因此程序员根本不用触碰请求对象。
安装 Bobo,然后启动开发服务器,执行示例 5-12 中的脚本(例如,bobo -f hello.py)。访问 http://localhost:8080/ 看到的消息是“Missing form variable person”,HTTP 状态码是 403。这是因为,Bobo 知道调用 hello 函数必须传入 person 参数,但是在请求中找不到同名参数。示例 5-13 在 shell 会话中使用 curl 展示了这个行为。
示例 5-13 如果请求中缺少函数的参数,Bobo 返回 403 forbidden 响应;curl -i 的作用是把首部转储到标准输出
$ curl -i http://localhost:8080/ HTTP/1.0 403 Forbidden Date: Thu, 21 Aug 2014 21:39:44 GMT Server: WSGIServer/0.2 CPython/3.4.1 Content-Type: text/html; charset=UTF-8 Content-Length: 103 <html> <head><title>Missing parameter</title></head> <body>Missing form variable person</body> </html>
然而,如果访问 http://localhost:8080/?person=Jim,响应会变成字符串 'Hello Jim!',如示例 5-14 所示。
示例 5-14 传入所需的 person 参数才能得到 OK 响应
$ curl -i http://localhost:8080/?person=Jim HTTP/1.0 200 OK Date: Thu, 21 Aug 2014 21:42:32 GMT Server: WSGIServer/0.2 CPython/3.4.1 Content-Type: text/html; charset=UTF-8 Content-Length: 10 Hello Jim!
Bobo 是怎么知道函数需要哪个参数的呢?它又是怎么知道参数有没有默认值呢?
函数对象有个 __defaults__ 属性,它的值是一个元组,里面保存着定位参数和关键字参数的默认值。仅限关键字参数的默认值在 __kwdefaults__ 属性中。然而,参数的名称在 __code__ 属性中,它的值是一个 code 对象引用,自身也有很多属性。
为了说明这些属性的用途,下面在 clip.py 模块中定义 clip 函数,如示例 5-15 所示,然后再审查它。
示例 5-15 在指定长度附近截断字符串的函数
def clip(text, max_len=80): """在max_len前面或后面的第一个空格处截断文本 """ end = None if len(text) > max_len: space_before = text.rfind(' ', 0, max_len) if space_before >= 0: end = space_before else: space_after = text.rfind(' ', max_len) if space_after >= 0: end = space_after if end is None: # 没找到空格 end = len(text) return text[:end].rstrip()
示例 5-16 审查示例 5-15 中定义的 clip 函数,查看 __defaults__、__code__.co_varnames 和 __code__.co_argcount 的值。
示例 5-16 提取关于函数参数的信息
>>> from clip import clip >>> clip.__defaults__ (80,) >>> clip.__code__ # doctest: +ELLIPSIS <code object clip at 0x...> >>> clip.__code__.co_varnames ('text', 'max_len', 'end', 'space_before', 'space_after') >>> clip.__code__.co_argcount 2
可以看出,这种组织信息的方式并不是最便利的。参数名称在 __code__.co_varnames 中,不过里面还有函数定义体中创建的局部变量。因此,参数名称是前 N 个字符串,N 的值由 __code__.co_argcount 确定。顺便说一下,这里不包含前缀为 * 或 ** 的变长参数。参数的默认值只能通过它们在 __defaults__ 元组中的位置确定,因此要从后向前扫描才能把参数和默认值对应起来。在这个示例中 clip 函数有两个参数,text 和 max_len,其中一个有默认值,即 80,因此它必然属于最后一个参数,即 max_len。这有违常理。
幸好,我们有更好的方式——使用 inspect 模块。
下面来看一下示例 5-17。
示例 5-17 提取函数的签名 2
2在 Python 3.5 中,本示例的 sig 的值是:<Signature (text, max_len=80)>。——编者注
>>> from clip import clip >>> from inspect import signature >>> sig = signature(clip) >>> sig # doctest: +ELLIPSIS <inspect.Signature object at 0x...> >>> str(sig) '(text, max_len=80)' >>> for name, param in sig.parameters.items(): ... print(param.kind, ':', name, '=', param.default) ... POSITIONAL_OR_KEYWORD : text = <class 'inspect._empty'> POSITIONAL_OR_KEYWORD : max_len = 80
这样就好多了。inspect.signature 函数返回一个 inspect.Signature 对象,它有一个 parameters 属性,这是一个有序映射,把参数名和 inspect.Parameter 对象对应起来。各个 Parameter 属性也有自己的属性,例如 name、default 和 kind。特殊的 inspect._empty 值表示没有默认值,考虑到 None 是有效的默认值(也经常这么做),而且这么做是合理的。
kind 属性的值是 _ParameterKind 类中的 5 个值之一,列举如下。
POSITIONAL_OR_KEYWORD
可以通过定位参数和关键字参数传入的形参(多数 Python 函数的参数属于此类)。
VAR_POSITIONAL
定位参数元组。
VAR_KEYWORD
关键字参数字典。
KEYWORD_ONLY
仅限关键字参数(Python 3 新增)。
POSITIONAL_ONLY
仅限定位参数;目前,Python 声明函数的句法不支持,但是有些使用 C 语言实现且不接受关键字参数的函数(如 divmod)支持。
除了 name、default 和 kind,inspect.Parameter 对象还有一个 annotation(注解)属性,它的值通常是 inspect._empty,但是可能包含 Python 3 新的注解句法提供的函数签名元数据(注解在下一节讨论)。
inspect.Signature 对象有个 bind 方法,它可以把任意个参数绑定到签名中的形参上,所用的规则与实参到形参的匹配方式一样。框架可以使用这个方法在真正调用函数前验证参数,如示例 5-18 所示。
示例 5-18 把tag 函数(见示例 5-10)的签名绑定到一个参数字典上 3
3在 Python 3.5 中,本示例的 bound_args 的值是:<BoundArguments (name='img', cls='framed', attrs={'title': 'Sunset Boulevard', 'src': 'sunset.jpg'})>。——编者注
>>> import inspect >>> sig = inspect.signature(tag) ➊ >>> my_tag = {'name': 'img', 'title': 'Sunset Boulevard', ... 'src': 'sunset.jpg', 'cls': 'framed'} >>> bound_args = sig.bind(**my_tag) ➋ >>> bound_args <inspect.BoundArguments object at 0x...> ➌ >>> for name, value in bound_args.arguments.items(): ➍ ... print(name, '=', value) ... name = img cls = framed attrs = {'title': 'Sunset Boulevard', 'src': 'sunset.jpg'} >>> del my_tag['name'] ➎ >>> bound_args = sig.bind(**my_tag) ➏ Traceback (most recent call last): ... TypeError: 'name' parameter lacking default value
❶ 获取 tag 函数(见示例 5-10)的签名。
❷ 把一个字典参数传给 .bind() 方法。
❸ 得到一个 inspect.BoundArguments 对象。
❹ 迭代 bound_args.arguments(一个 OrderedDict 对象)中的元素,显示参数的名称和值。
❺ 把必须指定的参数 name 从 my_tag 中删除。
❻ 调用 sig.bind(**my_tag),抛出 TypeError,抱怨缺少 name 参数。
这个示例在 inspect 模块的帮助下,展示了 Python 数据模型把实参绑定给函数调用中的形参的机制,这与解释器使用的机制相同。
框架和 IDE 等工具可以使用这些信息验证代码。Python 3 的另一个特性——函数注解——增进了这些信息的用途,参见下一节。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论