Python笔记(四)-- 函数

函数中的各种参数,lambda表达式,递归函数,汉诺塔示例。

空函数

有啥卵用?骚年,还记得大明湖畔(java中)的抽象函数么?

1
2
def nop():
pass

函数的参数类型检查:

if not isinstance(x, (int, float)): raise TypeError('bad operand type')

1
2
3
4
5
6
7
def my_abs(x):
if not isinstance(x, (int, float)):
raise TypeError('bad operand type')
if x >= 0:
return x
else:
return -x

参数

positional arguments(位置参数):就是普通的参数了,我猜是因为传参时其位置必须一一对应而得名。
key-word arguments(关键字参数):形式为kwarg=value,(如果出现在函数定义中,这也就是默认参数)或通过词典拆包**dict来传递。
arbitrary argument lists(可变参数表): 形式为*aname
NOTE:在函数参数列表中的顺序为:位置参数,可变参数表,关键字参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
""" Arbitrary Argument Lists """
>>> def concat(*args, sep="/"):
... return sep.join(args)
...
>>> concat("earth", "mars", "venus")
'earth/mars/venus'
>>> concat("earth", "mars", "venus", sep=".")
'earth.mars.venus'
# 可变参数之后是关键字参数,必须以键值对的形式传入,否则会被认为是可变参数,或者报错SyntaxError: positional argument follows keyword argument
>>> concat("earth", "mars", "venus", ".")
'earth/mars/venus/.'
# Unpacking Argument Lists
>>> def parrot(voltage, state='a stiff', action='voom'):
... print("-- This parrot wouldn't", action, end=' ')
... print("if you put", voltage, "volts through it.", end=' ')
... print("E's", state, "!")
...
>>> d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
>>> parrot(**d)
-- This parrot wouldn't VOOM if you put four million volts through it. E's bleedin' demised !

关键字参数可以传入0个或者任意个含参数名的参数,这些参数名在函数定义中并没有出现,这些参数在函数内部自动封装成一个字典(dict).

1
2
3
4
5
6
def portrait(name, **kw):
print('name is', name)
for k,v in kw.items():
print(k, v)
portrait('Mike', age=24, country='China', education='bachelor')

如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city和job作为关键字参数。这种方式定义的函数如下:

1
2
def person(name, age, *, city, job):
print(name, age, city, job)

调用方式如下:

1
2
>>> person('Jack', 24, city='Beijing', job='Engineer')
Jack 24 Beijing Engineer

如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了:
def person(name, age, *args, city, job):
print(name, age, args, city, job)
命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错:

1
2
3
4
>>> person('Jack', 24, 'Beijing', 'Engineer')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: person() takes 2 positional arguments but 4 were given

由于调用时缺少参数名city和job,Python解释器把这4个参数均视为位置参数,但person()函数仅接受2个位置参数。

通过可变参数和关键字参数,任何函数都可以用 universal_func(*args, **kw) 表达。

参数小结:

默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误!
要注意定义可变参数和关键字参数的语法:

*args是可变参数,args接收的是一个tuple;
**kw是关键字参数,kw接收的是一个dict。

以及调用函数时如何传入可变参数和关键字参数的语法:

可变参数既可以直接传入:func(1, 2, 3),又可以先组装list或tuple,再通过*args传入:func(*(1, 2, 3))
字参数既可以直接传入:func(a=1, b=2),又可以先组装dict,再通过**kw传入:func(**{'a': 1, 'b': 2})

使用*args**kw是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。
命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。
定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符,否则定义的将是位置参数。
如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符`
`了。

Lambda Expressions

1
2
3
4
5
6
7
8
9
10
11
12
def make_incrementor(n):
return lambda x: x + n
# lambda新建了一个匿名函数,相当于如下代码
# def test(x):
# return x + n
# return test
f = make_incrementor(42) # 将42赋值给n,并将返回的lambda匿名函数赋值给f
print(f)
print(f(0))
print(f(1))

The above example uses a lambda expression to return a function. Another use is to pass a small function as an argument:

1
2
3
4
>>> pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
>>> pairs.sort(key=lambda pair: pair[0])
>>> pairs
[(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]

递归函数

在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。

1
2
3
4
def fact(n):
if n==1:
return 1
return n * fact(n - 1)

递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。

使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。可以试试fact(1000):

1
2
3
4
5
6
7
>>> fact(1000)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in fact
...
File "<stdin>", line 4, in fact
RuntimeError: maximum recursion depth exceeded in comparison

一些语言可以通过尾递归来解决这个问题,但是Python标准的解释器没有针对尾递归做优化,任何递归函数都存在栈溢出的问题。

汉诺塔–递归函数的应用实例

请编写move(n, a, b, c)函数,它接收参数n,表示3个柱子ABC中第1个柱子A的盘子数量,然后打印出把所有盘子从A借助B移动到C的方法。
画图分析一下:
Hannoi
从图中的分析可以看出f(n,a,b,c)=f(n-1,a,c,b)+f(1,a,b,c)+f(n-1,b,a,c),于是使用递归函数,代码如下:

1
2
3
4
5
6
7
8
9
10
def move(n, a, b, c):
if n == 1:
print(a + '-->' + c)
return
move(n-1, a, c, b)
move(1, a, b, c)
move(n-1, b, a, c)
move(7, 'A', 'B', 'C')

参考资料

  1. 官方文档– Defining Functions
  2. 莫烦Python
  3. 廖雪峰的Python教程