python的函数编程

admin 2022年1月6日01:34:17评论28 views字数 10345阅读34分29秒阅读模式

函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。

详情可看此wiki: http://funhacks.net/explore-python/Function/

0x1函数基础

0x1.1定义函数

在 Python 中,定义函数使用 def 语句。一个函数主要由三部分构成:

  • 函数名
  • 函数参数
  • 函数返回值

如果函数没有 return 语句,则自动 return None
可以定义返回多个值的函数

1
2
3
4
def add_one(x, y, z):
return x+1, y+1, z+1

print(add_one(1,2,3))

0x1.2函数参数

在 Python 中,定义函数和调用函数都很简单,但如何定义函数参数和传递函数参数,则涉及到一些套路了。总的来说,Python 的函数参数主要分为以下几种:

  • 必选参数
  • 默认参数
  • 可变参数
  • 关键字参数

0x1.2.1必选参数

必选参数可以说是最常见的了,顾名思义,必选参数就是在调用函数的时候要传入数量一致的参数,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
>>> def add(x, y):        # x, y 是必选参数
... print x + y
...
>>> add() # 啥都没传,不行
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: add() takes exactly 2 arguments (0 given)
>>> add(1) # 只传了一个,也不行
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: add() takes exactly 2 arguments (1 given)
>>> add(1, 2) # 数量一致,通过
3
```
## 0x1.2.2默认参数
默认参数是指在定义函数的时候提供一些默认值,如果在调用函数的时候没有传递该参数,则自动使用默认值,否则使用传递时该参数的值。
看看例子就明白了:
```bash
>>> def add(x, y, z=1): # x, y 是必选参数,z 是默认参数,默认值是 1
... print x + y + z
...
>>> add(1, 2, 3) # 1+2+3
6
>>> add(1, 2) # 没有传递 z,自动使用 z=1,即 1+2+1
4

可以看到,默认参数使用起来也很简单,但有两点需要注意的是:

  • 默认参数要放在所有必选参数的后面
  • 默认参数应该使用不可变对象

比如,下面对默认参数的使用是错误的:

1
2
3
4
5
6
7
8
9
10
11
>>> def add(x=1, y, z):      # x 是默认参数,必须放在所有必选参数的后面
... return x + y + z
...
File "<stdin>", line 1
SyntaxError: non-default argument follows default argument
>>>
>>> def add(x, y=1, z): # y 是默认参数,必须放在所有必选参数的后面
... return x + y + z
...
File "<stdin>", line 1
SyntaxError: non-default argument follows default argument

再来看看为什么默认参数应该使用不可变对象。

我们看一个例子:

1
2
3
>>> def add_to_list(L=[]):
... L.append('END')
... return L

在上面的函数中,L 是一个默认参数,默认值是 [],表示空列表。

我们来看看使用:

1
2
3
4
5
6
7
8
9
10
>>> add_to_list([1, 2, 3])          # 没啥问题
[1, 2, 3, 'END']
>>> add_to_list(['a', 'b', 'c']) # 没啥问题
['a', 'b', 'c', 'END']
>>> add_to_list() # 没有传递参数,使用默认值,也没啥问题
['END']
>>> add_to_list() # 没有传递参数,使用默认值,竟出现两个 'END'
['END', 'END']
>>> add_to_list() # 糟糕了,三个 'END'
['END', 'END', 'END']

为啥呢?我们在调用函数的时候没有传递参数,那么就默认使用 L=[],经过处理,L 应该只有一个元素,怎么会出现调用函数两次,L 就有两个元素呢?

原来,L 指向了可变对象 [],当你调用函数时,L 的内容发生了改变,默认参数的内容也会跟着变,也就是,当你第一次调用时,L 的初始值是 [],当你第二次调用时,L 的初始值是 [‘END’],等等。

所以,为了避免不必要的错误,我们应该使用不可变对象作为函数的默认参数。

0x1.2.3可变参数

在某些情况下,我们在定义函数的时候,无法预估函数应该制定多少个参数,这时我们就可以使用可变参数了,也就是,函数的参数个数是不确定的。

看看例子:

1
2
3
4
5
6
>>> def add(*numbers):
... sum = 0
... for i in numbers:
... sum += i
... print 'numbers:', numbers
... return sum

在上面的代码中,numbers 就是一个可变参数,参数前面有一个 * 号,表示是可变的。在函数内部,参数 numbers 接收到的是一个 tuple。

在调用函数时,我们可以给该函数传递任意个参数,包括 0 个参数:

1
2
3
4
5
6
7
8
9
10
11
12
>>> add()           # 传递 0 个参数
numbers: ()
0
>>> add(1) # 传递 1 个参数
numbers: (1,)
1
>>> add(1, 2) # 传递 2 个参数
numbers: (1, 2)
3
>>> add(1, 2, 3) # 传递 3 个参数
numbers: (1, 2, 3)
6

上面的 * 表示任意参数,实际上,它还有另外一个用法:用来给函数传递参数。

看看例子:

1
2
3
4
5
6
7
8
9
10
11
>>> def add(x, y, z):        # 有 3 个必选参数
... return x + y + z
...
>>> a = [1, 2, 3]
>>> add(a[0], a[1], a[2]) # 这样传递参数很累赘
6
>>> add(*a) # 使用 *a,相当于上面的做法
6
>>> b = (4, 5, 6)
>>> add(*b) # 对元组一样适用
15

再看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
>>> def add(*numbers):       # 函数参数是可变参数
... sum = 0
... for i in numbers:
... sum += i
... return sum
...
>>> a = [1, 2]
>>> add(*a) # 使用 *a 给函数传递参数
3
>>> a = [1, 2, 3, 4]
>>> add(*a)
10

0x1.2.4关键字参数

可变参数允许你将不定数量的参数传递给函数,而关键字参数则允许你将不定长度的键值对, 作为参数传递给一个函数。

让我们看看例子:

1
2
3
4
5
6
7
8
>>> def add(**kwargs):
return kwargs
>>> add() # 没有参数,kwargs 为空字典
{}
>>> add(x=1) # x=1 => kwargs={'x': 1}
{'x': 1}
>>> add(x=1, y=2) # x=1, y=2 => kwargs={'y': 2, 'x': 1}
{'y': 2, 'x': 1}

在上面的代码中,kwargs 就是一个关键字参数,它前面有两个 * 号。kwargs 可以接收不定长度的键值对,在函数内部,它会表示成一个 dict。

和可变参数类似,我们也可以使用 **kwargs 的形式来调用函数,比如:

1
2
3
4
5
6
7
8
>>> def add(x, y, z):
... return x + y + z
...
>>> dict1 = {'z': 3, 'x': 1, 'y': 6}
>>> add(dict1['x'], dict1['y'], dict1['z']) # 这样传参很累赘
10
>>> add(**dict1) # 使用 **dict1 来传参,等价于上面的做法
10

再看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> def sum(**kwargs):               # 函数参数是关键字参数
... sum = 0
... for k, v in kwargs.items():
... sum += v
... return sum
>>> sum() # 没有参数
0
>>> dict1 = {'x': 1}
>>> sum(**dict1) # 相当于 sum(x=1)
1
>>> dict2 = {'x': 2, 'y': 6}
>>> sum(**dict2) # 相当于 sum(x=2, y=6)
8

0x1.2.5参数组合

在实际的使用中,我们经常会同时用到必选参数、默认参数、可变参数和关键字参数或其中的某些。
但是,需要注意的是,它们在使用的时候是有顺序的,依次是必选参数、默认参数、可变参数和关键字参数。

比如,定义一个包含上述四种参数的函数:

1
2
3
4
5
6
>>> def func(x, y, z=0, *args, **kwargs):
print 'x =', x
print 'y =', y
print 'z =', z
print 'args =', args
print 'kwargs =', kwargs

在调用函数的时候,Python 会自动按照参数位置和参数名把对应的参数传进去。让我们看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
>>> func(1, 2)                     # 至少提供两个参数,因为 x, y 是必选参数
x = 1
y = 2
z = 0
args = ()
kwargs = {}
>>> func(1, 2, 3) # x=1, y=2, z=3
x = 1
y = 2
z = 3
args = ()
kwargs = {}
>>> func(1, 2, 3, 4, 5, 6) # x=1, y=2, z=3, args=(4, 5, 6), kwargs={}
x = 1
y = 2
z = 3
args = (4, 5, 6)
kwargs = {}
>>> func(1, 2, 4, u=6, v=7) # args = (), kwargs = {'u': 6, 'v': 7}
x = 1
y = 2
z = 4
args = ()
kwargs = {'u': 6, 'v': 7}
>>> func(1, 2, 3, 4, 5, u=6, v=7) # args = (4, 5), kwargs = {'u': 6, 'v': 7}
x = 1
y = 2
z = 3
args = (4, 5)
kwargs = {'u': 6, 'v': 7}

我们还可以通过下面的形式来传递参数:

1
2
3
4
5
6
7
8
>>> a = (1, 2, 3)
>>> b = {'u': 6, 'v': 7}
>>> func(*a, **b)
x = 1
y = 2
z = 3
args = ()
kwargs = {'u': 6, 'v': 7}

0x2内置高阶函数

在函数式编程中,我们可以将函数当作变量一样自由使用。一个函数接收另一个函数作为参数,这种函数称之为高阶函数(Higher-order Functions)。
看一个简单的例子:

1
2
def func(g, arr):
return [g(x) for x in arr]

上面的代码中,func 是一个高阶函数,它接收两个参数,第 1 个参数是函数,第 2 个参数是数组,func 的功能是将函数 g 逐个作用于数组 arr 上,并返回一个新的数组,比如,我们可以这样用:

1
2
3
4
5
6
7
8
def double(x):
return 2 * x

def square(x):
return x * x

arr1 = func(double, [1, 2, 3, 4])
arr2 = func(square, [1, 2, 3, 4])

不难判断出,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
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> def square(x):
... return x * x

>>> map(square, [1, 2, 3, 4])
[1, 4, 9, 16]

>>> map(lambda x: x * x, [1, 2, 3, 4]) # 使用 lambda
[1, 4, 9, 16]

>>> map(str, [1, 2, 3, 4])
['1', '2', '3', '4']

>>> map(int, ['1', '2', '3', '4'])
[1, 2, 3, 4]

再看一个例子:

1
2
3
4
5
6
7
8
9
10
def double(x):
return 2 * x
def triple(x):
return 3 *x
def square(x):
return x * x
funcs = [double, triple, square] # 列表元素是函数对象
# 相当于 [double(4), triple(4), square(4)]
value = list(map(lambda f: f(4), funcs))
print(value)

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
2
3
4
5
6
7
8
9
10
11
12
13
>>> reduce(lambda x, y: x * y, [1, 2, 3, 4])  # 相当于 ((1 * 2) * 3) * 4
24
>>> reduce(lambda x, y: x * y, [1, 2, 3, 4], 5) # ((((5 * 1) * 2) * 3)) * 4
120
>>> reduce(lambda x, y: x / y, [2, 3, 4], 72) # (((72 / 2) / 3)) / 4
3
>>> reduce(lambda x, y: x + y, [1, 2, 3, 4], 5) # ((((5 + 1) + 2) + 3)) + 4
15
>>> reduce(lambda x, y: x - y, [8, 5, 1], 20) # ((20 - 8) - 5) - 1
6
>>> f = lambda a, b: a if (a > b) else b # 两两比较,取最大值
>>> reduce(f, [5, 8, 1, 10])
10

0x2.3filter

filter 函数用于过滤元素,它的使用形式如下:

1
filter(function, sequnce)

解释:将 function 依次作用于 sequnce 的每个 item,即 function(item),将返回值为 True 的 item 组成一个 List/String/Tuple (取决于 sequnce 的类型,python3 统一返回迭代器) 返回。

看一些例子。

1
2
3
4
5
6
7
8
9
10
>>> even_num = list(filter(lambda x: x % 2 == 0, [1, 2, 3, 4, 5, 6]))
>>> even_num
[2, 4, 6]
>>> odd_num = list(filter(lambda x: x % 2, [1, 2, 3, 4, 5, 6]))
>>> odd_num
[1, 3, 5]
>>> filter(lambda x: x < 'g', 'hijack')
'ac' # python2
>>> filter(lambda x: x < 'g', 'hijack')
<filter object at 0x1034b4080> # python3

0x2.4sorted

sorted() 也是一个高阶函数,接收一个函数来实现自定义的排序。

例如按绝对值排序代码:

1
2
3
4
5
6
7
8
print(sorted([-34,5,-2,43],key=abs))
```
# 0x3匿名函数

在 Python 中,我们使用 def 语句来定义函数,比如:
```bash
def double(x):
return 2 * x

除了用上面的方式定义函数,Python 还提供了一个关键字 lambda,让我们可以创建一个匿名函数,也就是没有名称的函数。它的形式如下:

lambda 参数: 表达式
关键字 lambda 说明它是一个匿名函数,冒号 : 前面的变量是该匿名函数的参数,冒号后面是函数的返回值,注意这里不需使用 return 关键字。

我们将上面的 double 函数改写成一个匿名函数,如下:

1
lambda x: 2 * x

那怎么调用匿名函数呢?可以直接这样使用:

1
2
>>> (lambda x: 2 * x)(8)
16

由于匿名函数本质上是一个函数对象,也可以将其赋值给另一个变量,再由该变量来调用函数,如下:

1
2
3
4
5
>>> f = lambda x: 2 * x   # 将匿名函数赋给变量 f  
>>> f
<function <lambda> at 0x7f835a696578>
>>> f(8)
16

使用场景
lambda 函数一般适用于创建一些临时性的,小巧的函数。比如上面的 double 函数,我们当然可以使用 def 来定义,但使用 lambda 来创建会显得很简洁,尤其是在高阶函数的使用中。

看一个例子:

1
2
def func(g, arr):
return [g(x) for x in arr]

现在给一个列表 [1, 2, 3, 4],利用上面的函数,对列表中的元素加 1,返回一个新的列表,你可能这样用:

1
2
3
4
def add_one(x):
return x + 1

arr = func(add_one, [1, 2, 3, 4])

这样做没什么错,可是 add_one 这个函数太简单了,使用 def 定义未免有点小题大作,我们改用 lambda:

1
arr = func(lambda x: x + 1, [1, 2, 3, 4])

是不是很简洁、易懂?

0x4闭包

闭包的一些注意点:

  • 闭包是携带自由变量的函数,即使创建闭包的外部函数的生命周期结束了,闭包所引用的自由变量仍会存在。
  • 闭包在运行可以有多个实例。
  • 尽量不要在闭包中引用循环变量,或者后续会发生变化的变量。

0x4.1闭包理解

在 Python 中,函数也是一个对象。因此,我们在定义函数时,可以再嵌套定义一个函数,并将该嵌套函数返回,比如:

1
2
3
4
5
6
from math import pow

def make_pow(n):
def inner_func(x): # 嵌套定义了 inner_func
return pow(x, n) # 注意这里引用了外部函数的 n
return inner_func # 返回 inner_func

上面的代码中,函数 make_pow 里面又定义了一个内部函数 inner_func,然后将该函数返回。因此,我们可以使用 make_pow 来生成另一个函数:

1
2
3
4
5
>>> pow2 = make_pow(2)  # pow2 是一个函数,参数 2 是一个自由变量
>>> pow2
<function inner_func at 0x10271faa0>
>>> pow2(6)
36.0

我们还注意到,内部函数 inner_func 引用了外部函数 make_pow 的自由变量 n,这也就意味着,当函数 make_pow 的生命周期结束之后,n 这个变量依然会保存在 inner_func 中,它被 inner_func 所引用。

1
2
3
4
5
6
7
>>> del make_pow         # 删除 make_pow
>>> pow3 = make_pow(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'make_pow' is not defined
>>> pow2(9) # pow2 仍可正常调用,自由变量 2 仍保存在 pow2 中
81.0

像上面这种情况,一个函数返回了一个内部函数,该内部函数引用了外部函数的相关参数和变量,我们把该返回的内部函数称为闭包(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
2
3
4
5
6
7
8
9
10
11
12
13
from math import sqrt

class Point(object):
def __init__(self, x, y):
self.x, self.y = x, y

def get_distance(self, u, v):
distance = sqrt((self.x - u) ** 2 + (self.y - v) ** 2)
return distance

>>> pt = Point(7, 2) # 创建一个点
>>> pt.get_distance(10, 6) # 求到另一个点的距离
5.0

用闭包来实现:

1
2
3
4
5
6
7
8
9
def point(x, y):
def get_distance(u, v):
return sqrt((x - u) ** 2 + (y - v) ** 2)

return get_distance

>>> pt = point(7, 2)
>>> pt(10, 6)
5.0

可以看到,结果是一样的,但使用闭包实现比使用类更加简洁。

0x4.3闭包的误区

闭包的概念很简单,但实现起来却容易出现一些误区,比如下面的例子:

1
2
3
4
5
6
7
def count():
funcs = []
for i in [1, 2, 3]:
def f():
return i
funcs.append(f)
return funcs

在该例子中,我们在每次 for 循环中创建了一个函数,并将它存到 funcs 中。现在,调用上面的函数,你可能认为返回结果是 1, 2, 3,事实上却不是:

1
2
3
4
5
6
7
>>> f1, f2, f3 = count()
>>> f1()
3
>>> f2()
3
>>> f3()
3

为什么呢?原因在于上面的函数 f 引用了变量 i,但函数 f 并非立刻执行,当 for 循环结束时,此时变量 i 的值是3,funcs 里面的函数引用的变量都是 3,最终结果也就全为 3。

因此,我们应尽量避免在闭包中引用循环变量,或者后续会发生变化的变量。

那上面这种情况应该怎么解决呢?我们可以再创建一个函数,并将循环变量的值传给该函数,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def count():
funcs = []
for i in [1, 2, 3]:
def g(param):
f = lambda : param # 这里创建了一个匿名函数
return f
funcs.append(g(i)) # 将循环变量的值传给 g
return funcs

>>> f1, f2, f3 = count()
>>> f1()
1
>>> f2()
2
>>> f3()
3

0x5偏函数(partial)

  • partial() 接受参数 (function, arg1, arg2, …, kwarg1=value1,
    kwarg2=value2)。

  • partial 的功能:固定函数参数,返回一个新的函数。

  • 当函数参数太多,需要固定某些参数时,可以使用 functools.partial 创建一个新的函数。

Python 提供了一个 functools 的模块,该模块为高阶函数提供支持,partial 就是其中的一个函数,该函数的形式如下:

1
functools.partial(func[,*args][, **kwargs])

这里先举个例子,看看它是怎么用的。

假设有如下函数:

1
2
def multiply(x, y):
return x * y

现在,我们想返回某个数的双倍,即:

1
2
3
4
5
6
>>> multiply(3, y=2)
6
>>> multiply(4, y=2)
8
>>> multiply(5, y=2)
10

上面的调用有点繁琐,每次都要传入 y=2,我们想到可以定义一个新的函数,把 y=2 作为默认值,即:

1
2
def double(x, y=2):
return multiply(x, y)

现在,我们可以这样调用了:

1
2
3
4
5
6
>>> double(3)
6
>>> double(4)
8
>>> double(5)
10

事实上,我们可以不用自己定义 double,利用 partial,我们可以这样:

1
2
3
from functools import partial

double = partial(multiply, y=2)

partial 接收函数 multiply 作为参数,固定 multiply 的参数 y=2,并返回一个新的函数给 double,这跟我们自己定义 double 函数的效果是一样的。

所以,简单而言,partial 函数的功能就是:把一个函数的某些参数给固定住,返回一个新的函数。

需要注意的是,我们上面是固定了 multiply 的关键字参数 y=2,如果直接使用:

1
double = partial(multiply, 2)

则 2 是赋给了 multiply 最左边的参数 x,不信?我们可以验证一下:

1
2
3
4
5
6
7
8
from functools import partial

def subtraction(x, y):
return x - y

f = partial(subtraction, 4) # 4 赋给了 x
>>> f(10) # 4 - 10
-6

0x6装饰器

详细可看:http://funhacks.net/explore-python/Functional/decorator.html

参考文章:
函数编程: http://funhacks.net/explore-python/Function/

FROM :blog.cfyqy.com | Author:cfyqy

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年1月6日01:34:17
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   python的函数编程http://cn-sec.com/archives/721870.html

发表评论

匿名网友 填写信息