函数
目录
1. 函数简介
一等对象:
- 在运行时创建
- 能赋值给变量或数据结构中的元素
- 能作为参数传给函数
- 能作为函数的返回结果
一等函数:
- Python 函数是一等对象,又称一等函数
- 函数对象本身是 function 类 的实例
2. 函数式风格编程
2.1 高阶函数
简介:
- 定义: 接受函数为参数,或者把函数作为结果返回的函数是高阶函数
- 典型函数: map、 filter、 reduce
- 替代操作: 列表推导或生成器表达式具有 map 和 filter 两个函数的功能,而且更易于阅读
版本特性:
- Python 3 中, map 和 filter 返回生成器(一种迭代器)
- Python 2 中,这两个函数返回列表
- Python 2 中, reduce 是内置函数,Python 3 中放到 functools 模块
常用函数:
- reduce(function, iterable,initializer)
- 作用:把某个操作连续应用到序列的元素上,累计之前的结果,把一系列值归约成一个值
- initializer:
- 如果 iterable 为空, initializer 是返回的结果;否则,在归约中使用它作为第一个参数
- 因此应该使用恒等值,对 +、 | 和 ^ 来说,initializer 应该是 0;而对 * 和 & 来说,应该是 1
- all(iterable): 如果 iterable 的每个元素都是真值,返回 True; all([]) 返回 True
- any(iterable): 只要 iterable 中有元素是真值,就返回 True; any([]) 返回 False
2.2 匿名函数
lambda:
- 语法:
- Python 简单的句法限制了 lambda 函数的定义体只能使用纯表达式
- 换句话说,lambda 函数的定义体中不能赋值,也不能使用 while 和 try 等 Python 语句
- 应用: 作为参数传给高阶函数,除此之外 Python 很少使用匿名函数
- 限制: 由于句法上的限制,非平凡的 lambda 表达式要么难以阅读,要么无法写出
- 附注: lambda 句法只是语法糖,与 def 语句一样, lambda 表达式会创建函数对象
2.3 支持函数式编程的包
operator
|
|
- 算术运算符函数:
- 以 i 开头、后面是另一个运算符的那些名称(如iadd、 iand 等),对应的是增量赋值运算符
- 如果第一个参数是可变的,那么这些运算符函数会就地修改它;
- 否则,作用与不带 i 的函数一样,直接返回运算结果。
- 工厂函数
- itemgetter: 序列元素获取,会自行构建函数
- itemgetter(1) == lambda fields: fields[1]
- itemgetter(1, 0) == lambda fields: fields[1],fields[0]
- itemgetter 使用 [] 运算符,因此不仅支持序列, 还支持映射和任何实现 __getitem__ 方法的类
- attrgetter: 对象属性获取,会自行构建函数
- attrgetter 与 itemgetter 作用类似,创建的函数根据名称提取对象的属性(使用 .运算符)
- 如果把多个属性名传给 attrgetter,它也会返回提取的值构成的元组
- 此外,如果参数名中包含 .(点号), attrgetter 会深入嵌套对象,获取指定的属性
- methodcaller: 对象方法调用,会自行创建函数
- methodcaller 创建的函数会在对象上调用参数指定的方法
|
|
functools
functools.partial
- 作用: 冻结参数,部分应用一个函数
- 参数: 第一个参数是一个可调用对象,后面跟着任意个要绑定的定位参数和关键字参数
functools.partialmethod
- 作用: Python 3.4 新增,与 partial 一样,不过是用于处理方法
|
|
3. 可调用对象
判定: 判断对象能否调用,可以使用内置的 callable() 函数
Python 7 种可调用对象:
- 用户定义的函数: 使用 def 语句或 lambda 表达式创建
- 内置函数: 使用 C 语言(CPython)实现的函数,如 len 或 time.strftime
- 内置方法: 使用 C 语言实现的方法,如 dict.get
- 方法: 在类的定义体中定义的函数
- 类: - 调用类时会运行类的 __new__ 方法创建一个实例,然后运行 __init__ 方法, 初始化实例,最后把实例返回给调用方 - 因为 Python 没有 new 运算符,所以调用类相当于调用函数 - 通常,调用类会创建那个类的实例,不过覆盖 __new__ 方法的话,也可能出现其他行为
- 类的实例: 如果类定义了 __call__ 方法,那么它的实例可以作为函数调用
- 生成器函数: - 使用 yield 关键字的函数或方法 - 调用生成器函数返回的是生成器对象 - 生成器函数在很多方面与其他可调用对象不同
自定义的可调用类型
- 实现: 只需实现实例方法 __call__
- 实践: 实现 __call__ 方法的类是创建函数类对象的简便方式
|
|
4. 函数自省
4.1 函数特有的属性
|
|
表5-1: 类的实例没有而函数有的属性列表
名称 | 类型 | 说明 |
---|---|---|
__annotations__ | dict | 参数和返回值的注解 |
__call__ | method-wrapper | 实现 () 运算符;即可调用对象协议 |
__closure__ | tuple | 函数闭包,即自由变量的绑定(通常是 None) |
__code__ | code | 编译成字节码的函数元数据和函数定义体 |
__defaults__ | tuple | 形式参数的默认值 |
__get__ | method-wrapper | 实现只读描述符协议(参见第 20 章) |
__globals__ | dict | 函数所在模块中的全局变量 |
__kwdefaults__ | dict | 仅限关键字形式参数的默认值 |
__name__ | str | 函数名称 |
__qualname__ | str | 函数的限定名称 (https://www.python.org/dev/peps/pep-3155/ ) |
__dict__: 存储用户自定义的属性
4.2 获取函数参数信息
参数内省:
- __defaults__: 元组,保存着定位参数和关键字参数的默认值
- __kwdefaults__: 元组,保存着仅限关键字参数的默认值
- __code__: code 对象引用,有很多属性,包括参数的名称
使用示例:
|
|
- __code__.co_varnames
- 包括参数名称和函数内创建的局部边量
- 参数名称是前 N 个字符串, N 的值由__code__.co_argcount 确定
- 不包含前缀为 * 或 ** 的变长参数
- 参数的默认值只能通过它们在 __defaults__ 元组中的位置确定, 因此要从后向前扫描才能把参数和默认值对应起来
inspect 模块
|
|
inspect:
- 作用: 函数参数自省
- 附注: 特殊的 inspect._empty 值表示没有默认值
inspect 接口:
- signature 函数: 返回一个 inspect.Signature 对象
- Signature 对象:
- 有一个 parameters属性,这是一个有序映射, 把参数名和 inspect.Parameter 对象对应起来
- 有个 bind 方法,它可以把任意个参数绑定到签名中的形参上,所 用的规则与实参到形参的匹配方式一样。框架可以使用这个方法在真正调用函数前验证参数
- Parameter 对象: 有 name、 default 和 kind 属性
- kind是 _ParameterKind 类中的 5 个值之一
- POSITIONAL_OR_KEYWORD: 可以通过定位参数和关键字参数传入的形参
- VAR_POSITIONAL: 定位参数元组
- VAR_KEYWORD: 关键字参数字典
- KEYWORD_ONLY: 仅限关键字参数( Python 3 新增)
- POSITIONAL_ONLY: 仅限定位参数;目前, Python 声明函数的句法不支持, 但是有些使用 C 语言实现且不接受关键字参数的函数(如 divmod)支持
- kind是 _ParameterKind 类中的 5 个值之一
- Parameter 对象还有一个 annotation(注解)属性,值通常是 inspect._empty, 但是可能包含 Python 3 新的注解句法提供的函数签名元数据
Signature.bind 用法
|
|
框架应用
|
|
HTTP 微框架 Bobo
- bobo.query 装饰器把一个普通的函数与框架的请求处理机制集成起来了
- 内省 hello 函数,发现它需要一个名为 person 的参数,然后从请求中获取那个名称对应的参数, 将其传给hello 函数
5. 函数参数传递与解析
|
|
参数传递与解析:
- 调用函数时使用 * 和 ** “展开” 可迭代对象,映射到单个参数
- *content 将捕获所有多余的定位参数
- **attrs 将捕获所有多余的关键词参数
- cls 参数只能作为关键字参数传入 – python3 新特性
6. 函数注解
注解:
- 版本: Python 3
- 作用:
- 为函数声明中的参数和返回值附加元数据
- 注解不会做任何处理,只是存储在函数的 __annotations__ 属性
- 注解对Python 解释器没有任何意义。注解只是元数据
- 为 IDE 和 lint 程序等工具中的静态类型检查功能提供额外的类型信息
- 语法:
- 函数声明中的各个参数可以在 : 之后增加注解表达式
- 如果参数有默认值,注解放在参数名和 = 号之间
- 如果想注解返回值,在 ) 和函数声明末尾的 : 之间添加 -> 和一个表达式
- 注解中最常用的类型是类(如 str 或 int)和字符串(如’int > 0’)
|
|
6.2 提取注解
|
|
延伸阅读
Python:
Python3 专有特性:
- PEP 3102—Keyword-Only Arguments https://www.python.org/dev/peps/pep-3102/
- PEP 3107—Function Annotations https://www.python.org/dev/peps/pep-3107/
注解的使用:
- What are good uses for Python3’s‘ Function Annotations’ http://stackoverflow.com/questions/3038033/what-are-good-uses-for-python3s-function-annotations
- What good are Python function annotations? http://stackoverflow.com/questions/13784713/what-good-are-python-function-annotations
functools.partial:
- Python: Why is functools.partial necessary?
- http://stackoverflow.com/questions/3252228/python-why-is-functools-partial-necessary
inspect:
- PEP 362—Function Signature Object https://www.python.org/dev/peps/pep-0362/
blog:
Python 函数式编程:
- Python Functional Programming HOWTO http://docs.python.org/3/howto/functional.html
实用工具
fn.py:
- https://github.com/kachayev/fn.py
- 是为 Python 2 和 Python 3 提供函数式编程支持的包
- 提供了 Python“所缺少的函数式特性
- 这个包提供的 @recur.tco 装饰器为 Python 中的无限递归实现了尾调用优化
- 此外, fn.py 还提供了很多其他函数、数据结构和诀窍
Bobo:
- 或许是第一个称得上是面向对象的 Web 框架
- 进一步学习它最近的重写版本,先从“ Introduction” http://bobo.readthedocs.io/en/latest/ 入手
- Bobo 的一些早期历史 http://discuss.fogcreek.com/joelonsoftware/default.asp?cmd=show&ixPost=94006
书籍:
《Python 语言参考手册》:
- https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy 对 7 种可调用类型和其他所有内置类型做了介绍
杂谈
关于 Bobo
- 1997 年, Bobo 开创了对象发布概念: 直接把 URL 映射到对象层次结构上,无需配置路由
- Bobo 还能通过分析处理请求的方法或函数的签名来自动处理 HTTP 查询
Zope:
- Zope 是 Plone CMS、 SchoolTool、 ERP5 和其他大型Python 项目的基础
ZODB( Zope Object Database)
- 事务型对象数据库,提供了 ACID(atomicity, consistency, isolation, and durability, 原子性、一致性、隔离性和耐久性),它的设计目的是简化 Python 的使用
Krishnamurthi 指出,不要试图把语言归为某一类;相反,把它们视作特性的聚合更有用
Python 函数式编程
- 从另一门函数式语言( Haskell)中借用列表推导之后, Python 对 map、 filter,以及 lambda 表达式的需求极大地减少了
- 除了匿名函数句法上的限制之外,影响函数式编程惯用法在 Python 中广泛使用的最大 障碍是缺少尾递归消除( tail-recursion elimination),这是一项优化措施,在函数的定义体 “末尾”递归调用,从而提高计算函数的内存使用效率
- Guido 在另一篇博客文章(TailRecursion Elimination, http://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html) 中解释了为什么这种优化措施不适合 Python
- 这篇文章详细讨论了技术论证,不过前三个也是最重要的原因与易用性有关 Python 作为一门易于使用、学习和教授的语言并非偶然,有 Guido 在为我们把关
- 综上,从设计上看,不管函数式语言的定义如何, Python 都不是一门函数式语言。 Python 只是从函数式语言中借鉴了一些好的想法
匿名函数缺陷:
- 匿名函数都有一个严重的缺点: 没有名称
- 匿名函数嵌套的层级太深,不利于调试和处理错误