6.1 懒惰是一种美德
为避免代码中出现大量重复代码,我们需要使用函数的相关知识。
6.2 抽象和结构
使用函数(或其他抽象相关)不仅仅是为了让代码量变少,也是为了让代码更易于别人理解。
6.3 自定义函数
使用def关键字来创建函数,举例,裴波那契数(每一个值是前两个值之和):
def fibs(num):
result = [0, 1]
for i in range(num - 2):
result.append(result[-2]+result[-1])
return result
print(fibs(10))
你还可以使用callable()来检验一个函数是否可以调用
6.3.1 给函数编写文档
除了使用#添加注释外,我们还可以给函数添加专有的注释:
def fibs(num):
'求裴波那契数列'
result = [0, 1]
for i in range(num - 2):
result.append(result[-2]+result[-1])
return result
#使用_doc_来查看函数注释
print(fibs._doc_)
#也可以使用help函数来查看函数相关信息
print(help(fibs))
6.3.2 其实并不是函数的函数
其实,,函数不一定有返回值。
6.4 参数魔法
关于参数,如果你理解c中的指针和函数,此部分将会很好理解
6.4.1 值从哪里来
在函数定义时,也就是是函数名后面的参数,我们一般叫做形参,使用函数时传进的数为实参。当你在定义函数时,通常不用担心值从何处来,你只需要考虑接受正确的值并处理。
6.4.2 我能修改参数吗
我决定从三种情况来谈一下这个问题。
#普通变量,在函数中的变化并不影响原来的值
>>> def fun(n):
n = "ABC"
>>> name = "DEF"
>>> fun(name)
>>> name
'DEF'
#字符串,元组等时不可变的,当然你在函数中修改他们也没有用
#可变的数据结构,如列表等,在函数中的改变会影响函数外的值
>>> def f(n):
n[0] = 'A'
>>> names = ['a','b','c']
>>> f(names)
>>> names
['A', 'b', 'c']
当然,这种规则下,有些操作时不够方便的,比如我想要在函数中修改普通变量的值,也改变函数外该变量的值,我们通常会用返回值将修改后的数据提供给函数外变量。
6.4.3 关键字参数和默认值
其实我们前面使用的参数多是位置参数(即系统根据你提供变量的位置来一一对应),不过这种方法并不适用于所有情况,我们还会经常用到关键字参数,即使用名称指定参数。
# 位置参数:
def hello(gretting, name):
print('{},{}!'.format(gretting, name))
hello('Hi', 'Tom')
# 关键字参数:假如你打算打招呼的方式默认是“Hello”,你可以这样。
def hello_2(gretting ='hello', name='world'):
print('{},{}'.format(gretting, name))
hello_2()
hello_2('Hi', 'Tony')
hello_2(name='Tom')
当然,你可以结合关键字参数和位置参数一起用(虽然不推荐这么用)
6.4.4 收集参数
如果有时候你不知道用户会输入多少参数,那么可以用*收集起来(好吧,这和前面的赋值有点像)
def print_str(*str):
print(str)
print_str(1, 5, 7,24 ,56)
(好吧,我承认这函数没卵用)
这里的输出结果是一个元组(),而不是列表[]。
同样的,*变量可以放在任何位置收集参数,当然,在*变量后面有参数时,需要指定关键字,防止出现歧义。
星号不会收集关键字参数,如果你非常想收集,你可以使用两个星号**,但这样你得到的会是一个字典。
6.4.5 分配参数
既然有收集,就会有分配参数。同样是使用星号。
def add(x, y):
return x+y
n = (1, 2)
print(add(*n))
当然,这种做法看起来有点蠢,但它是有其特定的使用环境的,我们将在第九章中讲到。
6.5 作用域
当你创建了一个函数时,就创建了一个命名空间,函数中定义的变量等可以认为都是存储在这个命名空间中的,这些变量的调用不会影响到全局变量。当参数中的局部变量与全局变量同名时,以局部变量为准,如果你想指明使用全局变量,可以使用globals()['全局变量名'],或者global 变量名。
另外,很神奇的是,python是支持函数嵌套使用的(c语言:mmp)
6.6 递归
递归即自己调用自己,这看上去有点难以理解,不过当你使用起来,你会喜欢上这种简洁的写法。递归函数一般会有下面两部分:
(1)基线条件:满足这一条件函数将直接返回一个值。(这样就避免了无限调用的可能)
(2)递归条件:包含一个或多个调用,通常是解决问题的一部分。
当然,这些对于第一次接触的人来说,还是有些懵,那我们就来看一下两个典型的例子:
6.6.1 两个经典案例:阶乘和幂
阶乘:当然,你可以用循环的思想来写,像下面这样
def factorial(n):
result = n
for i in range(1,n):
result *= i
return result
这里我们换一种思路,用递归来实现:
def factorial(n):
if n == 0: #基线条件,满足即退出函数
return 1
else:
return n*factorial(n-1)
我们再来定义幂的运算(就是和内置函数pow一样的效果)
def power(x, n):
if n == 0:
return 1
else:
return x * power(x, n-1)
当然,你可以明显的看到,递归大部分情况是可以用循环代替的,而且循环在时间复杂度可能更好一点,但是当你掌握了递归,你就会爱上这种简洁的表达方式。