什么是函数
函数是一种代码抽象的方式,通过调用函数,我们可以快捷地实现某些运算
调用函数
在python中内置了许多常见的函数,我们可以直接进行调用,而哪些函数可以在官网查询到,或者我们也可以在交互环境中使用help()
来获得该函数的更多信息
以取绝对值abs
函数为例:
>>> abs(200)
200
>>> abs(-100)
100
而如果输入了错误的信息,可以根据返回的报错信息进行修改即可
类似地,在max()
中,我们可以输入多个参数,然后该函数会返回这串数字中的最大值
>>> max(2,4,6)
6
当我们想要进行数据类型转换时,我们可以使用int()
函数把其他的数据类型转换为整数
>>> int(123)
123
>>> int('123')
123
>>> int(12.34)
12
可以看到,字符串'123',浮点数12.34被转换为了整数
相似地,我们可以使用float()
函数将一个值转换为浮点数
>>> float('12.34')
12.34
我们可以使用str()
函数将一个值转换为字符串
>>> str(12.34)
'12.34'
>>> str(100)
'100'
我们可以使用bool()
将一个值转换为布尔值(即True或False),而在python中,非零数值会被认为是True,空字符串会被认为是False
>>> bool(1)
True
>>> bool('')
False
最终,使用hex()
函数我们可以将一个整数转换为十六进制字符串
>>> n1 = 255
>>> print(hex(n1))
0xff
另外,我们可以为函数赋名
>>> a = int
>>> a(12.34)
12
定义函数
在python中,我们使用def
语句来定义函数,然后在缩紧块中编写函数体,函数的返回值用return
来返回
def my_abs(x):
if x >= 0:
return x
else:
return -x
print(my_abs(-100))
如果我们将其保存为了my_abs.py
文件,我们也可以使用import来导入直接运用该函数
至于import的用法后面会讲到
同样地,我们也可以定义一个什么都不干的空函数
def nop():
pass
这个函数什么都不做,会直接pass掉,一般用来当还没想好怎么写函数代码时先让代码跑起来
同样的,pass也能在其他地方用到
if x >= 18:
pass
但是需要注意的是,我们自己定义的my_abs
函数和官方的abs
函数相比,缺少了返回报错的参数检查,所以我们可以为自己的函数进行一些修改添加
def my_abs(x):
if not isinstance(x, (int, float)):
raise TypeError("bad operand type for abs(): '" + type(x).__name__ + "'")
if x >= 0:
return x
else:
return -x
当用户传入的x
不是整数或浮点数时,将会抛出名为bad operand type for abs()
的报错,isinstance
用来检测x
是否是int
,float
的数据类型
同样地,函数也可以返回多个值
import math
def move(x, y, step, angle=0):
nx = x + step * math.cos(angle)
ny = y - step * math.sin(angle)
return nx, ny
我们通过定义函数move,便能够实现给出坐标、位移和角度,从而计算出新的坐标
import math
def move(x, y, step, angle=0):
nx = x + step * math.cos(angle)
ny = y - step * math.sin(angle)
return nx, ny
x, y = move(100, 100, 60, math.pi / 6)
print(x, y)
此时我们说到的x,y是一个tuple
练习使用定义函数方法解一元二次方程:
import math
def quadratic(a, b, c):
d = b * b - 4 * a * c
if d > 0:
x1 = (-b + math.sqrt(d)) / (2 * a)
x2 = (-b - math.sqrt(d)) / (2 * a)
return (x1, x2)
elif d == 0:
x = -b / (2 * a)
return (x,)
else:
return None
print('quadratic(2, 3, 1) =', quadratic(2, 3, 1))
print('quadratic(1, 3, -4) =', quadratic(1, 3, -4))
if quadratic(2, 3, 1) != (-0.5, -1.0):
print('测试失败')
elif quadratic(1, 3, -4) != (1.0, -4.0):
print('测试失败')
else:
print('测试成功')
函数的参数
位置参数
当我们想要计算一个平方的函数时候,我们可以定义如下一个函数
def pingfang(x):
return x * x
那么我们想要计算一个数字的立方的时候,我们可以修改一下这个函数,做到
def pingfang(x):
return x * x * x
但是如果我们要计算三次方,四次方...n次方呢
所以如果我们能够定义一个函数power(x, n)
,能够用来计算x的n次方岂不是很好
于是我们有了
def power(x, n):
s = 1
while n > 0:
n = n - 1
s = s * x
return s
在这段代码中,先将s赋值为1用于存储结果,然后while n > 0: 进入循环,表示只要 n 还没变成 0,就继续计算;
s = s * x:每次循环都将 s 乘上 x,等价于累乘 x,重复 n 次。
n = n - 1:指数 n 逐渐减少,确保循环在 n 次后终止。
当n为0时候跳出while,返回s即为多次方值
修改后的power(x, n)
函数有两个参数:x
和n
,这两个参数都是位置参数,调用函数时,传入的两个值按照位置顺序依次赋给参数x
和n
。
默认参数
在这个函数中,我们仍然面临着一个问题,即即使要计算我们最常见的二次方,也需要手动输入2,这实在是太麻烦了,那么我们如何为函数提供一个默认参数呢
def power(x, n=2):
s = 1
while n > 0:
n = n - 1
s = s * x
return s
在这个函数中 n=2 是一个默认参数,这意味着:
• 如果调用 power(x) 时不传递 n,它会默认使用 n=2,即计算 x²。
• 如果调用 power(x, n) 时提供了 n,那么 n 的值就会被覆盖,而不会强制等于 2。
在设置默认参数时,我们仍有一些需要注意的事情:
-
必选参数在前,默认参数在后,否则Python的解释器会报错
-
当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面。变化小的参数就可以作为默认参数
-
默认参数必须指向不变对象!
def add_end(L=[]): L.append('END') return L
在这个函数中,作用是传入一个列表,添加END后再返回,但是如果我们以默认值执行,将会发现很快返回值便会变成
>>> add_end() ['END', 'END'] >>> add_end() ['END', 'END', 'END']
似乎函数记住了上次输出后的结果...
这究竟是怎么回事呢?
原来,每次计算之后,相当于对L进行了重新赋值,此时的L变成了一个可变对象
那么如何解决呢,只需要让我们指向一个不变对象即可
def add_end(L = None): if L is None: L = [] L.append('END') return L
这样问题便解决了
为什么要设计
str
、None
这样的不变对象呢?因为不变对象一旦创建,对象内部的数据就不能修改,这样就减少了由于修改数据导致的错误。此外,由于对象不变,多任务环境下同时读取对象不需要加锁,同时读一点问题都没有。我们在编写程序时,如果可以设计一个不变对象,那就尽量设计成不变对象。
可变参数
可变参数即意为传入的参数数量是可变的,例如我们传入a,b,c...
,想要计算a^2 + b^2 + c^2 + ……
时
由于参数个数不确定,我们可以把传入的数字作为一个list或tuple传入
def cal(numbers):
sum = 0
for n in numbers:
sum = sum + n ** 2
return sum
这时候我们需要组装好一个list或tuple进行输入
>>> calc([1, 2, 3])
14
>>> calc((1, 3, 5, 7))
84
那么我们将输入变为可变参数
def cal(*numbers):
sum = 0
for n in numbers:
sum = sum + n ** 2
return sum
经过修改之后,我们便可以有以下计算的代码了
b = input('输入求和数字(可输入多个,用空格分隔): ').split()
b = [float(num) for num in b] # 将字符串转换为浮点数列表
def cal(*numbers):
total_sum = 0
for n in numbers:
total_sum += n ** 2 # 计算平方和
return total_sum
a = cal(*b) # 传入解包的参数
print(a)
关键字参数
关键词参数允许我们传入0个或包含任意个包含参数名的参数,这些关键词参数会自动组装成一个dict
例如这个代码中,我们使用**kw
来将剩余内容自动组装为字典
def person(name,age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
我们在交互环境中尝试一下
>>> def person(name,age, **kw):
... print('name:', name, 'age:', age, 'other:', kw)
...
>>> person('Andy', 19, city='Hebei')
name: Andy age: 19 other: {'city': 'Hebei'}
可以看到city
和Hebei
被自动组装成了一个字典
关键字参数可以用来收集必须参数之外的内容
同时,关键词参数也支持我们将字典引入
>>> def person(name,age, **kw):
... print('name:', name, 'age:', age, 'other:', kw)
...
>>> addon = {'job':'teacher'}
>>> person('Jack', 20, job=addon['job'])
name: Jack age: 20 other: {'job': 'teacher'}
我们直接引入了addon
字典
函数的递归
在函数中,如果一个函数内部调用函数自身,那么就构成了一个递归,这个函数就是递归函数
例如我们想要计算n!=n*(n-1)*(n-2)*...*2*1
,用函数fact(n)
来表示
那我们可以看出fact(n) = n * fact(n-1)
,只有当n=1
时候才需要特殊考虑
那么我们就可以用递归的方法写出fact(n)
def fact(n):
if n == 1:
return 1
return n * fact(n-1)
>>> fact(1)
1
>>> fact(5)
120
>>> fact(100)
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
>>> 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
File "<stdin>", line 4, in fact
[Previous line repeated 996 more times]
RecursionError: maximum recursion depth exceeded
我们在交互环境中进行测试,发现在计算1000时候出现了报错
原来在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出
那么如何解决呢,我们可以使用尾递归优化,但是很可惜在python中并不支持尾递归优化,栈层数多了依然会栈溢出
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)
这便是将上面函数改写为尾递归函数之后的格式,可以看到我们在最后一步才进行乘法运算