Python核心编程笔记2 函数式编程
不学好函数式编程, 总觉得人生有遗憾
什么是函数
- python函数可以返回一个值或对象, 但返回一个容器对象的时候看起来像是返回了多个值:
return 'abc', 'def'
实际上是返回了一个有两个元素的元组而已 - 函数有标准调用和关键字调用两种形式. 有关可变长度参数调用参考后文
创建函数
|
|
python和c++不太一样的地方: 不存在前向引用的问题. 在foo()
中对bar()
进行的调用出现在bar()
之前, 但foo()
本身不是在bar()
声明之前被调用的, 在调用foo()
时bar()
已经存在了, 因此调用成功
函数属性(attribute)
- 函数也拥有自己的名空间, 使用函数名+
.
标识
|
|
- 内嵌函数
|
|
注意内嵌函数的整个函数体都在外部函数的名空间内, 因此如果没有任何对bar()
的外部引用, 那么除了函数体内, 任何地方都不能对其进行调用
装饰器(decorator)
装饰器的产生背景
2.2版本时类方法被引入python, 实现很笨拙:
|
|
使用装饰器之后, 可以用以下语法替换掉上面的
|
|
甚至还可以将装饰器和函数调用一样堆叠起来:
|
|
带参数的装饰器
|
|
装饰器举例
装饰器及闭包接受一个函数, 返回一个修改后的函数对象, 将其重新赋予原来的标识符, 并永久失去对原始函数的访问.
- 以下例子对函数增加了输出调用时间的功能
|
|
- 以下例子增加了传入参数并输出的功能
|
|
这里体现了装饰器的一个好处: 装饰器内的函数在装饰器的名空间内, 因此可以直接调用传入的text
而不需要写在参数列表里
可变长度参数
函数接受参数的顺序:
- 必须参数
- 默认参数
- 可变长度参数: 用
*args
表示, 当然名字任意, 但星号必须 - 关键字变量参数(字典): 用
**kw
表示, 规则同上
一个小例子:
|
|
注意字典是无序的
函数式编程(functional programming)
lambda表达式
|
|
内建函数
|
|
例子就懒得举了…自行搜索
偏函数
偏函数(partial function) 将任意数量(顺序)参数的函数转化成另一个带剩余参数的函数对象. 下面是一个带关键字参数的FPA:
|
|
变量作用域/闭包
A closure occurs when a function has access to a local variable from an enclosing scope that has finished its execution.
一个闭包的例子:
|
|
- 在调用
make_counter()
之后, 一个函数栈帧被创造, 其中counter()
和i
都在函数的名空间之内. - 接着函数返回
counter()
. 然而因为counter()
refer了i
变量, 因而在make_counter()
返回之后,i
依然保持, 没有随着函数栈帧的返回而被销毁. - 这种携带了整个enclosing函数的名空间的函数就是一个闭包.
闭包的后期绑定(late binding):
|
|
为何返回的不是0 2 4 6
而是6 6 6 6
? 原因是定义一个函数,函数内的变量并不是立刻就把值绑定了,而是等调用的时候再查找这个变量. 此谓后期绑定.
因为调用func(2)
的时候lambda表达式的名空间里只有x
而没有i
, 在foo()
返回之后i
的值是3, 因此全部返回4.
根据闭包的性质, 如果lambda表达式内部的名空间拥有i
, 则可以返回0 2 4 6
:
|
|
此时在创建lambda匿名函数的时候就要获取默认参数的值,放到 lambda 的名空间中
生成器(Generator)
从语法上讲, 生成器是一个带yield
语句的函数, 当生成器的next()或__next__()
(python2或3)被调用时, 他会准确地从离开的地方继续.
以下为生成器的几个主要方法:
generator.__next__()
Starts the execution of a generator function or resumes it at the last executed yield expression.
When a generator function is resumed with a next() method, the current yield expression always evaluates to None.
The execution then continues to the next yield expression, where the generator is suspended again, and the value of the expression_list is returned to next()’s caller.
If the generator exits without yielding another value, a StopIteration exception is raised.
This method is normally called implicitly, e.g. by a for loop, or by the built-in next() function.
generator.send(value)
Resumes the execution and “sends” a value into the generator function. The value argument becomes the result of the current yield expression.
The send() method returns the next value yielded by the generator, or raises StopIteration if the generator exits without yielding another value.
When send() is called to start the generator, it must be called with None as the argument, because there is no yield expression that could receive the value.
对于生成器, 需要注意以下几点:
- 除了一开始, 对generator调用
next()
时, 当前yield
语句返回None
- 接着运行直到遇见下一次
yield
, 将该表达式的值作为函数返回值 send()
改变的是恢复之后的yield
语句的返回值, 返回的是generator生成的下一个值
两个例子比较:
|
|
看一看调用count.send(9)
之后发生了什么:
val
返回了9,if
语句成立,count = 9
- 循环继续, 遇到
yield
并返回9
|
|
为什么此处返回4呢? 看一看发生了什么:
- 调用
next()
之后, 函数返回1, 此时x = 1
- 调用
send(3)
之后,yield
语句返回3, 即y = 3
x += 3
之后为4, 接下来返回4, 注意此时x = 4
- 调用
send(10)
之后,yield
语句返回10, 即y = 10
x += 10
之后为14, 最后返回14
此处,生成器函数 adder
用 yield
表达式,将处理好的 x
发送给生成器的调用者;与此同时,生成器的调用者通过 send
函数,将外部信息作为生成器函数内部的 yield
表达式的值,保存在 y
当中,并参与后续的处理。
这一特性是使用 yield
在 Python 中使用协程的基础。
参考文章: