Python学习笔记:函数
学自廖雪峰巨佬的Python3教程:https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/00143167832686474803d3d2b7d4d6499cfd093dc47efcd000
1.要调用一个函数,就需要知道函数的名称和参数,相关信息可以查看官方文档https://docs.python.org/3/library/functions.html,也可以使用help(function_name)在交互式命令行处查看函数的相关帮助信息。
2.自定义函数需要使用def语句,依次写出函数名、括号、括号中的参数和冒号,然后通过缩进编写函数体,函数的返回值用return语句返回,如果没有return语句,函数执行完毕后也会返回结果,但是结果为None,return None可以简写为return。
3.如果将自定义函数的定义保存为.py文件,那么可以在该文件的当前目录下启动Python解释器,用from [file name] import [function name]语句来导入自定义的函数,注意文件名不含.py扩展名。
4.如果想定义一个什么事都不做的空函数,则函数体可以用pass作为占位符,其他语句也可以使用pass作为占位符,比如if语句后面可以跟pass,如果缺少了pass又没有其他语句,则会报错。
5.数据类型检查可以用内置函数isinstance()实现,异常可以通过raise抛出
def my_abs(x): if not isinstance(x, (int, float)): raise TypeError('bad operand type') if x >= 0: return x else: return -x
6.Python的函数可以有多个返回值,但本质上返回的是一个tuple,然后里面有多个元素而已
7.可以给Python函数的参数指定默认值,但必选参数要在前,默认参数要在后,否则Python的解释器会报错,当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面,变化小的参数就可以设定默认参数
#n为默认参数,默认值为2 def power(x, n=2): s = 1 while n > 0: n = n - 1 s = s * x return s #如果有多个默认参数,而又想不按顺序指定某个参数的值时,需要用以下形式 enroll('Adam', 'M', city='Tianjin')
8.定义默认参数要牢记一点,默认参数必须指定不变对象
默认参数很有用,但使用不当,也会掉坑里。默认参数有个最大的坑,演示如下:
先定义一个函数,传入一个list,添加一个END再返回:正常调用时,结果似乎不错:def add_end(L=[]): L.append('END') return L
当你使用默认参数调用时,一开始结果也是对的:>>> add_end([1, 2, 3]) [1, 2, 3, 'END'] >>> add_end(['x', 'y', 'z']) ['x', 'y', 'z', 'END']
但是,再次调用>>> add_end() ['END']
add_end()
时,结果就不对了:>>> add_end() ['END', 'END'] >>> add_end() ['END', 'END', 'END']
原因是:
Python函数在定义的时候,默认参数L
的值就被计算出来了,即[]
,因为默认参数L
也是一个变量,它指向对象[]
,每次调用该函数,如果改变了L
的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]
了。
要修改上面的例子,可以用None来实现:
def add_end(L=None): if L is None: L = [] L.append('END') return L
9.在Python函数中,还可以定义可变参数,即传入的参数个数是可变的。一般来说,这种情况可以用list或tuple加遍历来实现,但如果利用可变参数,也是可以的,只需要在参数名前加一个*号,就可以传入任意个参数,包括0个。可变参数本质上接收的是一个tuple
#使用list来实现 def calc(numbers): sum = 0 for n in numbers: sum = sum + n * n return sum #使用可变参数实现 def calc(*numbers): sum = 0 for n in numbers: sum = sum + n * n return sum
10.Python函数中,定义关键字参数,用以允许你传入0个或任意个含参数名的参数
#使用**进行关键字参数的定义 def person(name, age, **kw): print('name:', name, 'age:', age, 'other:', kw) #可以传入任意个数的关键字个数 >>> person('Bob', 35, city='Beijing') name: Bob age: 35 other: {'city': 'Beijing'} >>> person('Adam', 45, gender='M', job='Engineer') name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'} #构建dict传参 >>> extra = {'city': 'Beijing', 'job': 'Engineer'} >>> person('Jack', 24, **extra) name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
如果需要知道传入了哪些参数,就需要在函数内部进行检查
def person(name, age, **kw): if 'city' in kw: # 有city参数 pass if 'job' in kw: # 有job参数 pass print('name:', name, 'age:', age, 'other:', kw)
如果要限制关键字参数的名字,则在参数列表中添加一个*号,*后面的参数被视为命名关键字参数
#定义命名关键字参数 def person(name, age, *, city, job): print(name, age, city, job) #调用方式如下 >>> person('Jack', 24, city='Beijing', job='Engineer') Jack 24 Beijing Engineer
11.函数里可以写递归,只需要指定边界值和递归公式。使用递归函数需要防止栈溢出,在计算机中,函数调用是通过(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。因此可以使用尾递归的方式来进行优化(这只是算法层面的优化然而Python解释器压根没有做相关优化所以写了也没卵用该溢出还是溢出)
#阶乘递归实现 def fact(n): if n==1: return 1 return n * fact(n - 1) #阶乘的尾递归实现 def fact(n): return fact_iter(n, 1) def fact_iter(num, product): if num == 1: return product return fact_iter(num - 1, num * product) #fact(5)对应的fact_iter(5,1)调用如下 ===> fact_iter(5, 1) ===> fact_iter(4, 5) ===> fact_iter(3, 20) ===> fact_iter(2, 60) ===> fact_iter(1, 120) ===> 120
12.汉诺塔问题的递归实现
文章的最后,巨佬留下了一个问题,汉诺塔的递归实现,这里也想以自己的方式来阐述一下题解:
算法分析如下:
在n=1的情况下,只需要把盘子从柱A移动到C,A->C,这是递归的边界条件
在n=2的情况下,我们需要进行三步操作,A->B,A->C,B->C,那么这里可以仔细推敲一下
n=2时,有两个盘子,小盘和大盘
第一步,将小盘从A移动到B,如果将柱子顺序变为A-C-B,这一步就跟n=1的时候的移动是一样的,同样是小盘从第一个柱子移动到第三个柱子;
第二步,将大盘从A->C
第三步,将小盘从B->C,这一步,如果将柱子的顺序变为B-A-C,这一步也是跟n=1的时候的移动是一样的,同样是小盘从第一个柱子移动到第三个柱子。
因此本质上,2阶汉诺塔相当于执行了以下步骤:
(1)在A-C-B的柱子顺序下执行了一阶汉诺塔的操作
(2)移动最大盘到A->C
(3)在B-A-C的柱子顺序下执行了一阶汉诺塔的操作
推广到3阶的时候,小环和中环可以视为一个整体,实际上就变成了执行二阶汉诺塔的方法;推广到n阶的时候,我们可以将1到n-1个盘视为一个整体,n为最大盘,这就是汉诺塔的递归算法。