函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。
详情可看此wiki: http://funhacks.net/explore-python/Function/
0x1函数基础
0x1.1定义函数
在 Python 中,定义函数使用 def 语句。一个函数主要由三部分构成:
- 函数名
- 函数参数
- 函数返回值
如果函数没有 return 语句,则自动 return None
可以定义返回多个值的函数
1 |
def add_one(x, y, z): |
0x1.2函数参数
在 Python 中,定义函数和调用函数都很简单,但如何定义函数参数和传递函数参数,则涉及到一些套路了。总的来说,Python 的函数参数主要分为以下几种:
- 必选参数
- 默认参数
- 可变参数
- 关键字参数
0x1.2.1必选参数
必选参数可以说是最常见的了,顾名思义,必选参数就是在调用函数的时候要传入数量一致的参数,比如:
1 |
>>> def add(x, y): # x, y 是必选参数 |
可以看到,默认参数使用起来也很简单,但有两点需要注意的是:
- 默认参数要放在所有必选参数的后面
- 默认参数应该使用不可变对象
比如,下面对默认参数的使用是错误的:
1 |
>>> def add(x=1, y, z): # x 是默认参数,必须放在所有必选参数的后面 |
再来看看为什么默认参数应该使用不可变对象。
我们看一个例子:
1 |
>>> def add_to_list(L=[]): |
在上面的函数中,L 是一个默认参数,默认值是 [],表示空列表。
我们来看看使用:
1 |
>>> add_to_list([1, 2, 3]) # 没啥问题 |
为啥呢?我们在调用函数的时候没有传递参数,那么就默认使用 L=[],经过处理,L 应该只有一个元素,怎么会出现调用函数两次,L 就有两个元素呢?
原来,L 指向了可变对象 [],当你调用函数时,L 的内容发生了改变,默认参数的内容也会跟着变,也就是,当你第一次调用时,L 的初始值是 [],当你第二次调用时,L 的初始值是 [‘END’],等等。
所以,为了避免不必要的错误,我们应该使用不可变对象作为函数的默认参数。
0x1.2.3可变参数
在某些情况下,我们在定义函数的时候,无法预估函数应该制定多少个参数,这时我们就可以使用可变参数了,也就是,函数的参数个数是不确定的。
看看例子:
1 |
>>> def add(*numbers): |
在上面的代码中,numbers 就是一个可变参数,参数前面有一个 * 号,表示是可变的。在函数内部,参数 numbers 接收到的是一个 tuple。
在调用函数时,我们可以给该函数传递任意个参数,包括 0 个参数:
1 |
>>> add() # 传递 0 个参数 |
上面的 * 表示任意参数,实际上,它还有另外一个用法:用来给函数传递参数。
看看例子:
1 |
>>> def add(x, y, z): # 有 3 个必选参数 |
再看一个例子:
1 |
>>> def add(*numbers): # 函数参数是可变参数 |
0x1.2.4关键字参数
可变参数允许你将不定数量的参数传递给函数,而关键字参数则允许你将不定长度的键值对, 作为参数传递给一个函数。
让我们看看例子:
1 |
>>> def add(**kwargs): |
在上面的代码中,kwargs 就是一个关键字参数,它前面有两个 *
号。kwargs 可以接收不定长度的键值对,在函数内部,它会表示成一个 dict。
和可变参数类似,我们也可以使用 **kwargs
的形式来调用函数,比如:
1 |
>>> def add(x, y, z): |
再看一个例子:
1 |
>>> def sum(**kwargs): # 函数参数是关键字参数 |
0x1.2.5参数组合
在实际的使用中,我们经常会同时用到必选参数、默认参数、可变参数和关键字参数或其中的某些。
但是,需要注意的是,它们在使用的时候是有顺序的,依次是必选参数、默认参数、可变参数和关键字参数。
比如,定义一个包含上述四种参数的函数:
1 |
>>> def func(x, y, z=0, *args, **kwargs): |
在调用函数的时候,Python 会自动按照参数位置和参数名把对应的参数传进去。让我们看看:
1 |
>>> func(1, 2) # 至少提供两个参数,因为 x, y 是必选参数 |
我们还可以通过下面的形式来传递参数:
1 |
>>> a = (1, 2, 3) |
0x2内置高阶函数
在函数式编程中,我们可以将函数当作变量一样自由使用。一个函数接收另一个函数作为参数,这种函数称之为高阶函数(Higher-order Functions)。
看一个简单的例子:
1 |
def func(g, arr): |
上面的代码中,func 是一个高阶函数,它接收两个参数,第 1 个参数是函数,第 2 个参数是数组,func 的功能是将函数 g 逐个作用于数组 arr 上,并返回一个新的数组,比如,我们可以这样用:
1 |
def double(x): |
不难判断出,arr1 是 [2, 4, 6, 8],arr2 是 [1, 4, 9, 16]。
map/reduce/filter 是 Python 中较为常用的内建高阶函数,它们为函数式编程提供了不少便利。
注意在 python2 和 python3 中,map/reduce/filter 的返回值类型有所不同,
python2 返回的是基本数据类型,而 python3 则返回了迭代器;
0x2.1 map
map 函数的使用形式如下:
1 |
map(function, sequence) |
解释:对 sequence
中的 item
依次执行 function(item)
,并将结果组成一个 List
返回,也就是:
1 |
[function(item1), function(item2), function(item3), ...] |
看一些简单的例子。
1 |
>>> def square(x): |
再看一个例子:
1 |
def double(x): |
0x2.2reduce
reduce 函数的使用形式如下:
1 |
reduce(function, sequence[, initial]) |
解释:先将 sequence 的前两个 item 传给 function,即 function(item1, item2),函数的返回值和 sequence 的下一个 item 再传给 function,即 function(function(item1, item2), item3),如此迭代,直到 sequence 没有元素,如果有 initial,则作为初始值调用。
也就是说:
1 |
reduece(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4) |
看一些例子,就能很快理解了。
1 |
>>> reduce(lambda x, y: x * y, [1, 2, 3, 4]) # 相当于 ((1 * 2) * 3) * 4 |
0x2.3filter
filter 函数用于过滤元素,它的使用形式如下:
1 |
filter(function, sequnce) |
解释:将 function 依次作用于 sequnce 的每个 item,即 function(item),将返回值为 True 的 item 组成一个 List/String/Tuple (取决于 sequnce 的类型,python3 统一返回迭代器) 返回。
看一些例子。
1 |
>>> even_num = list(filter(lambda x: x % 2 == 0, [1, 2, 3, 4, 5, 6])) |
0x2.4sorted
sorted() 也是一个高阶函数,接收一个函数来实现自定义的排序。
例如按绝对值排序代码:
1 |
print(sorted([-34,5,-2,43],key=abs)) |
除了用上面的方式定义函数,Python 还提供了一个关键字 lambda,让我们可以创建一个匿名函数,也就是没有名称的函数。它的形式如下:
lambda 参数: 表达式
关键字 lambda 说明它是一个匿名函数,冒号 : 前面的变量是该匿名函数的参数,冒号后面是函数的返回值,注意这里不需使用 return 关键字。
我们将上面的 double 函数改写成一个匿名函数,如下:
1 |
lambda x: 2 * x |
那怎么调用匿名函数呢?可以直接这样使用:
1 |
>>> (lambda x: 2 * x)(8) |
由于匿名函数本质上是一个函数对象,也可以将其赋值给另一个变量,再由该变量来调用函数,如下:
1 |
>>> f = lambda x: 2 * x # 将匿名函数赋给变量 f |
使用场景
lambda 函数一般适用于创建一些临时性的,小巧的函数。比如上面的 double 函数,我们当然可以使用 def 来定义,但使用 lambda 来创建会显得很简洁,尤其是在高阶函数的使用中。
看一个例子:
1 |
def func(g, arr): |
现在给一个列表 [1, 2, 3, 4],利用上面的函数,对列表中的元素加 1,返回一个新的列表,你可能这样用:
1 |
def add_one(x): |
这样做没什么错,可是 add_one 这个函数太简单了,使用 def 定义未免有点小题大作,我们改用 lambda:
1 |
arr = func(lambda x: x + 1, [1, 2, 3, 4]) |
是不是很简洁、易懂?
0x4闭包
闭包的一些注意点:
- 闭包是携带自由变量的函数,即使创建闭包的外部函数的生命周期结束了,闭包所引用的自由变量仍会存在。
- 闭包在运行可以有多个实例。
- 尽量不要在闭包中引用循环变量,或者后续会发生变化的变量。
0x4.1闭包理解
在 Python 中,函数也是一个对象。因此,我们在定义函数时,可以再嵌套定义一个函数,并将该嵌套函数返回,比如:
1 |
from math import pow |
上面的代码中,函数 make_pow 里面又定义了一个内部函数 inner_func,然后将该函数返回。因此,我们可以使用 make_pow 来生成另一个函数:
1 |
>>> pow2 = make_pow(2) # pow2 是一个函数,参数 2 是一个自由变量 |
我们还注意到,内部函数 inner_func 引用了外部函数 make_pow 的自由变量 n,这也就意味着,当函数 make_pow 的生命周期结束之后,n 这个变量依然会保存在 inner_func 中,它被 inner_func 所引用。
1 |
>>> del make_pow # 删除 make_pow |
像上面这种情况,一个函数返回了一个内部函数,该内部函数引用了外部函数的相关参数和变量,我们把该返回的内部函数称为闭包(Closure)。
在上面的例子中,inner_func 就是一个闭包,它引用了自由变量 n。
0x4.2闭包的作用
- 闭包的最大特点就是引用了自由变量,即使生成闭包的环境已经释放,闭包仍然存在。
- 闭包在运行时可以有多个实例,即使传入的参数相同。
1
2
3
4>>> pow_a = make_pow(2)
>>> pow_b = make_pow(2)
>>> pow_a == pow_b
False
利用闭包,我们还可以模拟类的实例。
这里构造一个类,用于求一个点到另一个点的距离:
1 |
from math import sqrt |
用闭包来实现:
1 |
def point(x, y): |
可以看到,结果是一样的,但使用闭包实现比使用类更加简洁。
0x4.3闭包的误区
闭包的概念很简单,但实现起来却容易出现一些误区,比如下面的例子:
1 |
def count(): |
在该例子中,我们在每次 for 循环中创建了一个函数,并将它存到 funcs 中。现在,调用上面的函数,你可能认为返回结果是 1, 2, 3,事实上却不是:
1 |
>>> f1, f2, f3 = count() |
为什么呢?原因在于上面的函数 f 引用了变量 i,但函数 f 并非立刻执行,当 for 循环结束时,此时变量 i 的值是3,funcs 里面的函数引用的变量都是 3,最终结果也就全为 3。
因此,我们应尽量避免在闭包中引用循环变量,或者后续会发生变化的变量。
那上面这种情况应该怎么解决呢?我们可以再创建一个函数,并将循环变量的值传给该函数,如下:
1 |
def count(): |
0x5偏函数(partial)
-
partial() 接受参数 (function, arg1, arg2, …, kwarg1=value1,
kwarg2=value2)。 -
partial 的功能:固定函数参数,返回一个新的函数。
-
当函数参数太多,需要固定某些参数时,可以使用 functools.partial 创建一个新的函数。
Python 提供了一个 functools 的模块,该模块为高阶函数提供支持,partial 就是其中的一个函数,该函数的形式如下:
1 |
functools.partial(func[,*args][, **kwargs]) |
这里先举个例子,看看它是怎么用的。
假设有如下函数:
1 |
def multiply(x, y): |
现在,我们想返回某个数的双倍,即:
1 |
>>> multiply(3, y=2) |
上面的调用有点繁琐,每次都要传入 y=2,我们想到可以定义一个新的函数,把 y=2 作为默认值,即:
1 |
def double(x, y=2): |
现在,我们可以这样调用了:
1 |
>>> double(3) |
事实上,我们可以不用自己定义 double,利用 partial,我们可以这样:
1 |
from functools import partial |
partial 接收函数 multiply 作为参数,固定 multiply 的参数 y=2,并返回一个新的函数给 double,这跟我们自己定义 double 函数的效果是一样的。
所以,简单而言,partial 函数的功能就是:把一个函数的某些参数给固定住,返回一个新的函数。
需要注意的是,我们上面是固定了 multiply 的关键字参数 y=2,如果直接使用:
1 |
double = partial(multiply, 2) |
则 2 是赋给了 multiply 最左边的参数 x,不信?我们可以验证一下:
1 |
from functools import partial |
0x6装饰器
详细可看:http://funhacks.net/explore-python/Functional/decorator.html
参考文章:
函数编程: http://funhacks.net/explore-python/Function/
FROM :blog.cfyqy.com | Author:cfyqy
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论