读《流畅的Python》:一等函数
前言
一等对象(first-class objects
)是指拥有如下特性的程序实体:
- 在运行时创建
- 能赋值给变量或数据结构中的元素
- 能作为参数传给函数
- 能作为函数的返回结果
Python中的函数拥有这几个特性,所以被称作一等函数(functions as first-class objects
,简称first-class functions
)。
把函数视作对象
1 |
|
高阶函数
接受函数为参数,或把函数作为结果返回的函数是高阶函数,比如上文提到的print()
和fooo()
。再比如接收函数作为key
参数的sorted()
:
1 |
|
函数式编程语言一般会提供map
、filter
、reduce
三个高阶函数。Python也提供这三个函数,但列表推导(list comprehensions
)和生成器表达式(generator expressions
)在完成和map
、filter
相同的功能时代码可读性更好:
1 |
|
如果是执行求和操作,也可以用内置的sum
函数替代reduce
,性能和可读性更好:
1 |
|
和sum
和reduce
的思路类似,all
和any
也是两个内置的归约函数:
all(iterable)
:如果每个元素都为真,返回True
。all([])
返回True
。any(iterable)
:如任意一个元素为真,返回True
。any([])
返回False
。
匿名函数
用lambda
关键字创造的函数叫做匿名函数。匿名函数只能使用纯表达式,函数内不能赋值,也不能使用while
、try
等语句。匿名函数一般只作为参数传递给高阶函数,就像上一节的示例那样。
从Pythonic
的角度来说,匿名函数最好不要超过一行,也不推荐将匿名函数赋值给其它变量。
可调用对象
能被调用运算符(即()
)应用的对象被称为可调用对象,这点可以通过内置的callable
函数来判断。Python数据模型文档列出了7种可调用对象:
- 用户定义的函数(
User-defined functions
),使用def
语句和lambda
表达式创建的函数。 - 方法(
Instance methods
),在类的定义体中定义的函数。 - 生成器函数(
Generator functions
),使用yield
关键字的函数或方法。调用生成器函数会返回生成器对象。 - 内置函数(
Built-in functions
)、内置方法(Built-in methods
),用C语言实现的函数/方法,如len()
、alist.append()
。 - 类(
Classes
),调用类时会运行类的__new__
方法创建实例,然后运行__init__
方法初始化实例。 - 类的实例(
Class Instances
),定义了__call__
方法的类的实例。
当然,《流畅的Python》基于Python3.4,后续的Python版本还引入了其它种类的可调用对象,如协程函数(Coroutine functions
)和异步生成器函数(Asynchronous generator functions
),详见前文的文档链接。
1 |
|
函数内省
内省指程序在运行时检查对象类型的一种能力,在Python中,函数提供许多属性来实现内省。函数特有的属性主要有如下几种:
名称 | 类型 | 说明 |
---|---|---|
__annotations__ |
dict | 参数和返回值的注解 |
__call__ |
method-wrapper | 实现() 运算符 |
__closure__ |
tuple | 函数闭包,即自由变量的绑定 |
__code__ |
code | 编译成字节码的函数元数据和函数定义体 |
__defaults__ |
tuple | 形参的默认值 |
__get__ |
method-wraper | 实现只读描述符协议 |
__globals__ |
dict | 函数所在模块的全局变量 |
__kwdefaults__ |
dict | 仅限关键字形参的默认值 |
__name__ |
str | 函数名称 |
__qualname__ |
str | 函数的限定名称,如Random.choice |
函数参数、获取关于参数的信息
详见上一篇文章
函数注解
Python3提供的新语法函数注解
可以为函数声明的参数和返回值附加元数据,如:
1 |
|
函数中的参数可以在:
之后增加注解表达式。如参数有默认值,则表达式放在参数和=
号之间。函数末尾的)
和:
之间也可以放入表达式,用于注解返回值。注解不会做任何处理,只是储存在函数的__annotations__
属性里:
1 |
|
注解只是元数据,可以供IDE、框架、装饰器、静态代码分析工具等使用。标准库中只有上一节提到的inspect.signature
会用到注解,如:
1 |
|
用注解实现参数校验
Python大牛Raymond Hettinger
在一个StackOverflow问答里展示了一种方便的、基于注解的参数校验机制,摘录如下:
1 |
|
1 |
|
其基本思想是用Callable
对象充当函数参数的注解,然后在函数被调用时调用Callable
对象来校验参数。换句话说,上面的函数f
可以改写成如下形式:
1 |
|
由此可见前文参数校验机制的精妙。
支持函数式编程的模块
在operator
和functools
等模块的支持下,Python也可以很方便地实现函数式编程风格。
operator模块
operator
模块为许多算数运算符提供了对应的函数,如add
(对应a + b
)、mul
(对应a * b
)等等。比如计算1~n
的和与n
的阶乘:
1 |
|
operator
模块还提供了itemgetter
和attrgetter
两个函数,可以从序列中提取元素或读取对象的属性。比如:
1 |
|
最后介绍methodcaller
函数,这个函数创建的函数会调用对象上的指定方法,如:
1 |
|
用functools.partial冻结参数
除了前文已经多次提到的reduce
,functools
模块还提供了许多有用的函数,比如可以冻结函数参数的partial
。上一节提到的append_lemon
其实就起到了类似partial
的作用,但partial
的功能更强大。比如前文提到的自定义排序,可以用更通用的形式重写:
1 |
|
1 |
|
在需要用相同(或类似)的参数反复调用某个函数时,使用functools.partial
冻结参数可以有效降低工作量和bug出现的几率。functools
还提供了wraps
、lru_cache
等比较实用的装饰器,这方面的内容可以参考笔者先前的文章。