def 函数名([形参]):
函数体14 函数
数学上的函数,一般写为 y = f(x) 的形式,你提供一个输入 x ,函数还你一个输出 y 。 编程中的函数,是实现特定功能的一段代码,可重复使用。
函数的用处显而易见,如:
- 省去重复写代码的麻烦。
- 让程序更加模块化,提高代码可读性、可维护性。
- 对具体代码进行抽象与封装,有助于把注意力集中在程序的整体设计,而不是函数的实现细节上。
- 将大型项目分割成不同的子任务(函数),利于团队协作开发。
14.1 函数定义
函数定义以关键字def开头,语法如下:
形参指形式参数,指函数的输入,即 y = f(x) 中的x。 形参位于[]中,说明形式参数是可选的,一个函数可以有参数,也可以没有参数。
函数体是调用函数时会执行的代码,规定函数完成的具体工作。
# 没有参数的函数
def foo():
print("Hello World!")
# 返回两个数的平均值的函数
def my_average(a, b):
return (a+b)/2
# 没有return语句的函数
# 打印宽度为50的字符串的函数,n个*居中
def print_star(n):
print(("*"*n).center(50))
# 计算n阶调和数(1 + 1/2 + 1/3 + … + 1/n)的函数
def harmonic(n):
total = 0.0
for i in range(1, n+1): total += 1.0 / i
return total使用函数称为调用函数。调用函数时写函数名,后跟圆括号,括号内写实际参数。 调用函数前,必须先定义函数,即执行函数的def代码块。 运行标准库或第三方库的函数,需要先 import 相关模块/包/库,就是因为导入模块时,会执行模块中的 def 语句。
一般用赋值运算符将函数返回的值赋给变量。 return 语句用于返回值,并跳出函数,回到程序调用函数的地方。 函数可以有0个、1个或多个return 语句。
# 具有多个return的函数
def is_prime(n):
if n < 2:
return False
i = 2
while i * i <= n:
if n % i == 0:
return False
i += 1
return Truereturn 语句可以返回一个或多个值。返回多个值时,多个值作为元组返回,可以解包赋值给多个变量。
def foo():
return 1, 2, 3
foo()
a,b,c = foo()
print(a,b,c)(1, 2, 3)
1 2 3
没有return语句时,函数的返回值是None。 没有return语句的函数一般具有副作用。当然,既无返回值也无副作用的函数在语法上也是允许的。
# 既无返回值也无副作用的函数
def foo():
pass函数的副作用 (side effect) 指函数在执行过程中除了返回值之外,还对程序的其他部分产生可观察的影响,如:
- 修改外部变量 :改变函数作用域外的变量的值
- 执行 I/O 操作 :如读写文件、数据库操作、网络请求等
- 抛出异常 :改变了程序的正常控制流
- 打印输出
函数体的第一行,往往是三引号形式的函数说明,帮助用户了解此函数的用法,称为函数文档字符串。
可用函数名称.__doc__返回文档字符串。
def foo():
'''
这个函数
什么也不做
'''
pass
foo.__doc__'\n这个函数\n什么也不做\n'
14.2 参数
定义函数时圆括号中的参数称为形参(形式参数,parameter); 调用函数时圆括号中的参数称为实参(实际参数,argument)。
调用函数时,实参默认按位置顺序依次传递给形参,个数需匹配,否则报错。
14.2.1 参数传递方式
调用函数时,实参的值被传递给形参,不同编程语言有不同的实现方式:
值传递(pass by value):调用函数时,复制实参的值,用此值对形参初始化,函数内形参的改变不改变实参的值。
引用传递(pass by reference):调用函数时,传递实参的引用(内存地址),形参的改变会改变实参的值。
Python 的参数传递方式兼具值传递与引用传递的特点,是赋值传递(pass by assignment):用” 形参 = 实参” 的赋值方式传递参数:如果参数不可变,改变形参会创建新的对象,实参保持不变;如果参数可变,改变形参时,相应的改变也会反映在实参上。
# 例8.9 无效的自增函数
# 参数为不可变对象,函数内对形参的修改不会反映在实参对象上
i=100
def inc(j,n):
j += n
inc(i, 10)
print(i)100
# 例8.10 有效的自增函数
i=100
def inc(j,n):
j += n
return j
i =inc(i,10)
print(i)110
注意下面的函数,实参列表a的值,在执行shuffle(a)后被改变了。
# 例8.11 根据下标交换列表元素位置
def exchange(a, i, j):
'''
a是列表
i,j是a的索引
此函数将a的第i和第j个索引处的元素交换位置
'''
temp = a[i]
a[i] = a[j]
a[j] = temp
# 例8.12 列表元素洗牌
def shuffle(a):
import random
n = len(a)
for i in range(n):
r = random.randrange(i, n)
exchange(a, i, r)
a = list(range(10))
print(a)
shuffle(a)
print(a)[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 6, 9, 2, 8, 5, 3, 4, 7]
14.2.2 位置参数,关键字参数
根据传参时采用的具体形式,参数可以分为:
- 位置参数(positional argument),按位置传递的参数。不按顺序传参会出错。
- 关键字参数(keyword argument):传参时在实参前面使用
参数名称 =。关键字参数之间可以打乱传参顺序。
在函数定义中,关于关键字参数与位置参数,有规则如下:
- 位置参数必须位于关键字参数之前。
- 如果要显式区分位置参数与关键字参数,可用
/与*符号:/之前只能出现位置参数,称为强制位置参数(positional-only)*以及*标识符后面只能用关键字参数,称为强制命名参数(keyword-only)
def foo(位置参数,/,位置参数或关键字参数,*,关键字参数):
函数体位于/和*符号之间的参数,其是位置参数还是关键字参数, 不是在函数定义中规定的, 而是由函数调用时传入此参数的具体方式决定的。
函数定义中可以提供参数的默认值,形如形参名称=默认值,这种写法不暗示此形参是关键字参数。位置参数和关键字参数都可以有默认值。
传参规则的目的是确保参数传递准确,不会让程序弄错实参形参的对应关系。
以关键字形式传递的参数写明了参数名称,不会产生歧义,因此其可以不严格遵守定义中的顺序。
位置参数没有写明参数名称,因此必须严格按照函数定义中的顺序传递,否则容易在形参、实参配对的过程中引入错误。
规定关键字参数位于位置参数之后,是为了确保位置参数的传入顺序与函数定义严格匹配。
*形参之后只能跟关键字参数的规则,也是为了避免在函数传递过程中产生歧义。 因为*形参接受任意数量的位置参数,程序无法区分*形参之后的位置参数是*形参的一部分还是下一个参数,因此从语法上杜绝这种情况的发生。
# *之后必须使用关键字参数
# 因此下列函数的所有参数都是关键字参数
def my_sum(*, mid_score, end_score, mid_rate = 0.4):
score = mid_score * mid_rate + end_score * (1 - mid_rate)
print(format(score, '.2f'))
# 正确调用
my_sum(mid_score = 88, end_score = 79)
my_sum(end_score = 79, mid_score = 88)82.60
82.60
# 错误调用
my_sum(88, 79) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[10], line 2 1 # 错误调用 ----> 2 my_sum(88, 79) TypeError: my_sum() takes 0 positional arguments but 2 were given
# mid_rate参数具有默认值
def my_sum2(mid_score, end_score, mid_rate = 0.4):
score = mid_score * mid_rate + end_score * (1 - mid_rate)
print(f"{score:.2f}")
# 正确调用
# 三个参数都是位置参数
my_sum2(88, 79, 0.4)
# 3个参数都是关键字参数
my_sum2(mid_rate = 0.4, mid_score=88, end_score=79)
# 第三个参数使用默认值
# mid_score, end_score是位置参数,顺序传入
my_sum2(88, 79)
# mid_score, end_score是关键字参数,顺序可以随意
my_sum2(mid_score = 88, end_score = 79)
my_sum2(end_score = 79, mid_score = 88)82.60
82.60
82.60
82.60
82.60
# 错误调用
# 关键字参数必须在位置参数之后
my_sum2(mid_rate = 0.4, 88, 79)Cell In[12], line 3 my_sum2(mid_rate = 0.4, 88, 79) ^ SyntaxError: positional argument follows keyword argument
14.2.3 带*的参数
*形参名称接受任意数量的位置参数,将其打包为元组,默认值为空元组。**形参名称接受任意数量的关键字参数,将其打包为字典,默认值为空字典。**形参名称如果存在,它应当是最后一个参数。- 在函数体中,不要再写
*或**,直接用星号后的形参名称。
def foo(n1, *pos):
print(n1)
for i, x in enumerate(pos):
print(f'pos参数的第{i}个对象是{x}')
foo(1, 2, 3, 4, 5, 6)1
pos参数的第0个对象是2
pos参数的第1个对象是3
pos参数的第2个对象是4
pos参数的第3个对象是5
pos参数的第4个对象是6
# 求多个整数的最大值
def my_max(a, b, *c):
max_value = a
if max_value < b:
max_value = b
for n in c:
if max_value < n:
max_value = n
return max_value
print(my_max(1, 2))
print(my_max(1, 7, 11, 2, 5)) 2
11
def character_creator(**traits):
return(traits)
character_creator(name = '孙悟空',
alias = '齐天大圣',
age = None,
weapon = ['如意金箍棒'],
shentong = ['七十二变', '筋斗云']
){'name': '孙悟空',
'alias': '齐天大圣',
'age': None,
'weapon': ['如意金箍棒'],
'shentong': ['七十二变', '筋斗云']}
14.2.4 有默认值的参数
- 在参数定义中,可设定参数的默认值。
- 有默认值的参数后面必须都是有默认值的参数,直到出现
*符号。 - 有默认值的参数的默认值只计算一次。默认值是可变对象时,这会导致多次调用共享同一个默认值,可能会产生错误。
# 有默认值的参数n1后面只要有*,就可以再次出现无默认值参数
def foo(n1 = 1, *, n2):
print(n1+n2)
foo(n2 = 10) # 正确11
# 错误,因为n2必须以关键字形式传参
foo(10) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[17], line 2 1 # 错误,因为n2必须以关键字形式传参 ----> 2 foo(10) TypeError: foo() missing 1 required keyword-only argument: 'n2'
# 默认值为可变对象列表
# 三次调用的痕迹会累积在有默认值的参数中被返回
def f(a, L = []):
L.append(a)
return L
print(f(1))
print(f(2))
print(f(3))[1]
[1, 2]
[1, 2, 3]
# 设定默认参数为None,在函数体中将None替换为默认值,可避免上述现象
def f(a, L=None):
if L is None:
L = []
L.append(a)
return L
print(f(1))
print(f(2))
print(f(3))[1]
[2]
[3]
14.2.5 命令行参数
导入sys模块后,可用sys.argv[0], sys.argv[1], sys.argv[2],...表示在命令行以python 脚本名称.py的形式运行代码时,从命令行接受的第0、第1、第2、…个参数。
第0个参数是Python脚本名称。
注意这里的脚本名称使用了完整路径。
import sys
def print_star(n):
print(("*"*n).center(50))
lines = int(sys.argv[1])
for i in range(1, 2*lines, 2):
print_star(i)
14.3 变量作用域
变量只能在程序的一部分中可见/被使用。程序中某个变量可见的部分,称为该变量的作用域(scope)。
在函数内定义的变量是局部变量(local)。局部变量的作用域是定义该变量的代码块,作用域的起始点是定义该变量的代码。
在函数与类外部定义的变量是全局变量(global)。全局变量的作用域为定义此变量的模块,从定义该变量的位置开始,到文件结束。
当前脚本中的全局变量可直接访问。 通过import可访问其他模块中定义的全局变量。 全局变量应当尽可能用于表示常量,避免存储可变状态。滥用可变全局变量会降低模块化和可读性。 在编程语言中,表示常量的标识符一般全用大写字母表示。 不过Python并不会从语法上阻止你将全大写字母的标识符绑定到其他的对象,只是从视觉上提醒程序员这应当是一个常量。
TAX1 = 0.17
TAX2 = 0.2
TAX3 = 0.05
PI = 3.14将上述代码保存在global_variable.py文件中, 并将此文件置于当前工作目录中, 就可以在运行import global_variable后,直接使用其中的变量了。
- 如果全局变量与局部变量同名,则在局部变量的作用范围内,全局变量被“屏蔽”(mask),即该变量名被认为是局部变量,而不是全局变量。
- 在函数体中被直接使用、没有被赋值过的变量,被认为是全局变量;在函数体内被赋值的变量,被认为是局部变量。
- 如果要在函数体内为函数外的全局变量赋值,应该用
global关键字声明相关变量为全局变量,然后再赋值。
# 例8.26
num = 100
def f():
num = 105 # 这个num是局部变量
print(num)
f()
print(num) # 在函数之外,num还是全局变量105
100
下面的代码在函数体内给n赋值(n += 10),导致n被认定为局部变量。 但n += 10相当于n = n + 10,给n赋值要求计算n+10,这要求n已经被定义(赋值),而这在目前的代码中并没有实现,因此会引发错误。
# 例8.27
m = 100 # 全局变量m
n = 200 # 全局变量n
def f():
print(m+5) # 未经赋值直接使用,m被视为全局变量
n += 10 # 引发错误
f()105
--------------------------------------------------------------------------- UnboundLocalError Traceback (most recent call last) Cell In[22], line 8 5 print(m+5) # 未经赋值直接使用,m被视为全局变量 6 n += 10 # 引发错误 ----> 8 f() Cell In[22], line 6, in f() 4 def f(): 5 print(m+5) # 未经赋值直接使用,m被视为全局变量 ----> 6 n += 10 UnboundLocalError: cannot access local variable 'n' where it is not associated with a value
# 在函数体中用global声明的变量是全局变量,可由此实现在函数内部对全局变量修改
pi = 3.141592653589793
e = 2.718281828459045
def my_func():
global pi # 声明全局pi
pi = 3.14
print('global pi =', pi)
e = 2.718
print('local e =', e)
print('global pi =', pi)
print('global e =', e)
my_func()
print('global pi =', pi)
print('global e =', e)global pi = 3.141592653589793
global e = 2.718281828459045
global pi = 3.14
local e = 2.718
global pi = 3.14
global e = 2.718281828459045
Python 允许嵌套定义函数。 如果要函数中的函数为定义在上级函数体的局部变量赋值,应该用nonlocal关键字声明,表明变量不是所在块的局部变量,而是上级函数体中定义的变量。
def outer_func(): # 例8.29
tax_rate = 0.17
print('outer func tax rate =', tax_rate)
def innner_func():
nonlocal tax_rate
tax_rate = 0.05
print('inner func tax rate =', tax_rate)
innner_func()
print('outer func tax rate =', tax_rate)
outer_func()outer func tax rate = 0.17
inner func tax rate = 0.05
outer func tax rate = 0.05
用locals(),globals()函数,返回当前上下文的局部变量和全局变量字典。
a=1
b=2
def f(a, b):
x = 'abc'
y = 'xyz'
for i in range(2):
j = i
k = i**2
print(locals())
f(1,2)
print(globals()){'a': 1, 'b': 2, 'x': 'abc', 'y': 'xyz', 'i': 0, 'j': 0, 'k': 0}
{'a': 1, 'b': 2, 'x': 'abc', 'y': 'xyz', 'i': 1, 'j': 1, 'k': 1}
{'__name__': '__main__', '__builtin__': <module 'builtins' (built-in)>, '__builtins__': <module 'builtins' (built-in)>, '_ih': ['', '# 没有参数的函数\ndef foo():\n print("Hello World!")\n\n# 返回两个数的平均值的函数\ndef my_average(a, b): \n return (a+b)/2\n\n# 没有return语句的函数\n# 打印宽度为50的字符串的函数,n个*居中\ndef print_star(n): \n print(("*"*n).center(50))\n\n# 计算n阶调和数(1 + 1/2 + 1/3 + … + 1/n)的函数\ndef harmonic(n): \n total = 0.0\n for i in range(1, n+1): total += 1.0 / i\n return total', '# 具有多个return的函数\ndef is_prime(n):\n if n < 2:\n return False \n i = 2\n while i * i <= n:\n if n % i == 0: \n return False\n i += 1\n return True', 'def foo():\n return 1, 2, 3\n\nfoo()\n\na,b,c = foo()\nprint(a,b,c)', '# 既无返回值也无副作用的函数\ndef foo():\n pass', "def foo():\n ''' \n 这个函数\n 什么也不做\n '''\n pass\n\nfoo.__doc__", '# 例8.9 无效的自增函数\n# 参数为不可变对象,函数内对形参的修改不会反映在实参对象上\ni=100\ndef inc(j,n):\n j += n\n\ninc(i, 10)\nprint(i)', '# 例8.10 有效的自增函数\ni=100\ndef inc(j,n):\n j += n\n return j\n\ni =inc(i,10)\nprint(i)', "# 例8.11 根据下标交换列表元素位置\ndef exchange(a, i, j):\n '''\n a是列表\n i,j是a的索引\n 此函数将a的第i和第j个索引处的元素交换位置\n '''\n temp = a[i]\n a[i] = a[j]\n a[j] = temp\n\n# 例8.12 列表元素洗牌\ndef shuffle(a):\n import random\n n = len(a) \n for i in range(n): \n r = random.randrange(i, n)\n exchange(a, i, r)\n\na = list(range(10))\nprint(a)\nshuffle(a)\nprint(a)", "# *之后必须使用关键字参数\n# 因此下列函数的所有参数都是关键字参数\ndef my_sum(*, mid_score, end_score, mid_rate = 0.4):\n score = mid_score * mid_rate + end_score * (1 - mid_rate)\n print(format(score, '.2f'))\n\n# 正确调用\nmy_sum(mid_score = 88, end_score = 79) \nmy_sum(end_score = 79, mid_score = 88)", '# 错误调用\nmy_sum(88, 79) ', '# mid_rate参数具有默认值\ndef my_sum2(mid_score, end_score, mid_rate = 0.4):\n score = mid_score * mid_rate + end_score * (1 - mid_rate)\n print(f"{score:.2f}")\n\n# 正确调用\n# 三个参数都是位置参数\nmy_sum2(88, 79, 0.4)\n# 3个参数都是关键字参数\nmy_sum2(mid_rate = 0.4, mid_score=88, end_score=79)\n\n# 第三个参数使用默认值\n# mid_score, end_score是位置参数,顺序传入\nmy_sum2(88, 79)\n# mid_score, end_score是关键字参数,顺序可以随意\nmy_sum2(mid_score = 88, end_score = 79)\nmy_sum2(end_score = 79, mid_score = 88)', '# 错误调用\n# 关键字参数必须在位置参数之后\nmy_sum2(mid_rate = 0.4, 88, 79)', "def foo(n1, *pos):\n print(n1)\n for i, x in enumerate(pos):\n print(f'pos参数的第{i}个对象是{x}')\n\nfoo(1, 2, 3, 4, 5, 6)", '# 求多个整数的最大值\ndef my_max(a, b, *c):\n max_value = a \n if max_value < b:\n max_value = b\n for n in c: \n if max_value < n:\n max_value = n\n return max_value \n\nprint(my_max(1, 2)) \nprint(my_max(1, 7, 11, 2, 5)) ', "def character_creator(**traits):\n return(traits)\n\ncharacter_creator(name = '孙悟空',\nalias = '齐天大圣',\nage = None,\nweapon = ['如意金箍棒'],\nshentong = ['七十二变', '筋斗云']\n)", '# 有默认值的参数n1后面只要有*,就可以再次出现无默认值参数\ndef foo(n1 = 1, *, n2):\n print(n1+n2)\n\nfoo(n2 = 10) # 正确', '# 错误,因为n2必须以关键字形式传参\nfoo(10) ', '# 默认值为可变对象列表\n# 三次调用的痕迹会累积在有默认值的参数中被返回\ndef f(a, L = []):\n L.append(a)\n return L\n\nprint(f(1))\nprint(f(2))\nprint(f(3))', '# 设定默认参数为None,在函数体中将None替换为默认值,可避免上述现象\ndef f(a, L=None):\n if L is None:\n L = []\n L.append(a)\n return L\n\nprint(f(1))\nprint(f(2))\nprint(f(3))', 'TAX1 = 0.17\nTAX2 = 0.2\nTAX3 = 0.05\nPI = 3.14', '# 例8.26\nnum = 100 \ndef f():\n num = 105 # 这个num是局部变量\n print(num)\n\nf()\nprint(num) # 在函数之外,num还是全局变量', '# 例8.27\nm = 100 # 全局变量m\nn = 200 # 全局变量n\ndef f():\n print(m+5) # 未经赋值直接使用,m被视为全局变量\n n += 10 # 引发错误\n\nf()', "# 在函数体中用global声明的变量是全局变量,可由此实现在函数内部对全局变量修改\npi = 3.141592653589793\ne = 2.718281828459045\ndef my_func():\n global pi # 声明全局pi\n pi = 3.14\n print('global pi =', pi)\n e = 2.718 \n print('local e =', e)\n\nprint('global pi =', pi)\nprint('global e =', e)\nmy_func() \nprint('global pi =', pi)\nprint('global e =', e)", "def outer_func(): # 例8.29\n tax_rate = 0.17\n print('outer func tax rate =', tax_rate)\n def innner_func():\n nonlocal tax_rate\n tax_rate = 0.05\n print('inner func tax rate =', tax_rate)\n innner_func()\n print('outer func tax rate =', tax_rate)\n\nouter_func()", "a=1\nb=2\ndef f(a, b):\n x = 'abc'\n y = 'xyz'\n for i in range(2):\n j = i\n k = i**2\n print(locals())\n\nf(1,2)\nprint(globals())"], '_oh': {3: (1, 2, 3), 5: '\n这个函数\n什么也不做\n', 15: {'name': '孙悟空', 'alias': '齐天大圣', 'age': None, 'weapon': ['如意金箍棒'], 'shentong': ['七十二变', '筋斗云']}}, '_dh': [WindowsPath('C:/Users/HUAWEI/Desktop/Proj_quarto/PythonTutorial')], 'In': ['', '# 没有参数的函数\ndef foo():\n print("Hello World!")\n\n# 返回两个数的平均值的函数\ndef my_average(a, b): \n return (a+b)/2\n\n# 没有return语句的函数\n# 打印宽度为50的字符串的函数,n个*居中\ndef print_star(n): \n print(("*"*n).center(50))\n\n# 计算n阶调和数(1 + 1/2 + 1/3 + … + 1/n)的函数\ndef harmonic(n): \n total = 0.0\n for i in range(1, n+1): total += 1.0 / i\n return total', '# 具有多个return的函数\ndef is_prime(n):\n if n < 2:\n return False \n i = 2\n while i * i <= n:\n if n % i == 0: \n return False\n i += 1\n return True', 'def foo():\n return 1, 2, 3\n\nfoo()\n\na,b,c = foo()\nprint(a,b,c)', '# 既无返回值也无副作用的函数\ndef foo():\n pass', "def foo():\n ''' \n 这个函数\n 什么也不做\n '''\n pass\n\nfoo.__doc__", '# 例8.9 无效的自增函数\n# 参数为不可变对象,函数内对形参的修改不会反映在实参对象上\ni=100\ndef inc(j,n):\n j += n\n\ninc(i, 10)\nprint(i)', '# 例8.10 有效的自增函数\ni=100\ndef inc(j,n):\n j += n\n return j\n\ni =inc(i,10)\nprint(i)', "# 例8.11 根据下标交换列表元素位置\ndef exchange(a, i, j):\n '''\n a是列表\n i,j是a的索引\n 此函数将a的第i和第j个索引处的元素交换位置\n '''\n temp = a[i]\n a[i] = a[j]\n a[j] = temp\n\n# 例8.12 列表元素洗牌\ndef shuffle(a):\n import random\n n = len(a) \n for i in range(n): \n r = random.randrange(i, n)\n exchange(a, i, r)\n\na = list(range(10))\nprint(a)\nshuffle(a)\nprint(a)", "# *之后必须使用关键字参数\n# 因此下列函数的所有参数都是关键字参数\ndef my_sum(*, mid_score, end_score, mid_rate = 0.4):\n score = mid_score * mid_rate + end_score * (1 - mid_rate)\n print(format(score, '.2f'))\n\n# 正确调用\nmy_sum(mid_score = 88, end_score = 79) \nmy_sum(end_score = 79, mid_score = 88)", '# 错误调用\nmy_sum(88, 79) ', '# mid_rate参数具有默认值\ndef my_sum2(mid_score, end_score, mid_rate = 0.4):\n score = mid_score * mid_rate + end_score * (1 - mid_rate)\n print(f"{score:.2f}")\n\n# 正确调用\n# 三个参数都是位置参数\nmy_sum2(88, 79, 0.4)\n# 3个参数都是关键字参数\nmy_sum2(mid_rate = 0.4, mid_score=88, end_score=79)\n\n# 第三个参数使用默认值\n# mid_score, end_score是位置参数,顺序传入\nmy_sum2(88, 79)\n# mid_score, end_score是关键字参数,顺序可以随意\nmy_sum2(mid_score = 88, end_score = 79)\nmy_sum2(end_score = 79, mid_score = 88)', '# 错误调用\n# 关键字参数必须在位置参数之后\nmy_sum2(mid_rate = 0.4, 88, 79)', "def foo(n1, *pos):\n print(n1)\n for i, x in enumerate(pos):\n print(f'pos参数的第{i}个对象是{x}')\n\nfoo(1, 2, 3, 4, 5, 6)", '# 求多个整数的最大值\ndef my_max(a, b, *c):\n max_value = a \n if max_value < b:\n max_value = b\n for n in c: \n if max_value < n:\n max_value = n\n return max_value \n\nprint(my_max(1, 2)) \nprint(my_max(1, 7, 11, 2, 5)) ', "def character_creator(**traits):\n return(traits)\n\ncharacter_creator(name = '孙悟空',\nalias = '齐天大圣',\nage = None,\nweapon = ['如意金箍棒'],\nshentong = ['七十二变', '筋斗云']\n)", '# 有默认值的参数n1后面只要有*,就可以再次出现无默认值参数\ndef foo(n1 = 1, *, n2):\n print(n1+n2)\n\nfoo(n2 = 10) # 正确', '# 错误,因为n2必须以关键字形式传参\nfoo(10) ', '# 默认值为可变对象列表\n# 三次调用的痕迹会累积在有默认值的参数中被返回\ndef f(a, L = []):\n L.append(a)\n return L\n\nprint(f(1))\nprint(f(2))\nprint(f(3))', '# 设定默认参数为None,在函数体中将None替换为默认值,可避免上述现象\ndef f(a, L=None):\n if L is None:\n L = []\n L.append(a)\n return L\n\nprint(f(1))\nprint(f(2))\nprint(f(3))', 'TAX1 = 0.17\nTAX2 = 0.2\nTAX3 = 0.05\nPI = 3.14', '# 例8.26\nnum = 100 \ndef f():\n num = 105 # 这个num是局部变量\n print(num)\n\nf()\nprint(num) # 在函数之外,num还是全局变量', '# 例8.27\nm = 100 # 全局变量m\nn = 200 # 全局变量n\ndef f():\n print(m+5) # 未经赋值直接使用,m被视为全局变量\n n += 10 # 引发错误\n\nf()', "# 在函数体中用global声明的变量是全局变量,可由此实现在函数内部对全局变量修改\npi = 3.141592653589793\ne = 2.718281828459045\ndef my_func():\n global pi # 声明全局pi\n pi = 3.14\n print('global pi =', pi)\n e = 2.718 \n print('local e =', e)\n\nprint('global pi =', pi)\nprint('global e =', e)\nmy_func() \nprint('global pi =', pi)\nprint('global e =', e)", "def outer_func(): # 例8.29\n tax_rate = 0.17\n print('outer func tax rate =', tax_rate)\n def innner_func():\n nonlocal tax_rate\n tax_rate = 0.05\n print('inner func tax rate =', tax_rate)\n innner_func()\n print('outer func tax rate =', tax_rate)\n\nouter_func()", "a=1\nb=2\ndef f(a, b):\n x = 'abc'\n y = 'xyz'\n for i in range(2):\n j = i\n k = i**2\n print(locals())\n\nf(1,2)\nprint(globals())"], 'Out': {3: (1, 2, 3), 5: '\n这个函数\n什么也不做\n', 15: {'name': '孙悟空', 'alias': '齐天大圣', 'age': None, 'weapon': ['如意金箍棒'], 'shentong': ['七十二变', '筋斗云']}}, 'get_ipython': <bound method InteractiveShell.get_ipython of <ipykernel.zmqshell.ZMQInteractiveShell object at 0x0000023F01AED940>>, 'exit': <IPython.core.autocall.ZMQExitAutocall object at 0x0000023F01AEC050>, 'quit': <IPython.core.autocall.ZMQExitAutocall object at 0x0000023F01AEC050>, 'open': <function open at 0x0000023F7FB079C0>, 'ojs_define': <function ojs_define at 0x0000023F01CD54E0>, '__spec__': None, '_i': "def outer_func(): # 例8.29\n tax_rate = 0.17\n print('outer func tax rate =', tax_rate)\n def innner_func():\n nonlocal tax_rate\n tax_rate = 0.05\n print('inner func tax rate =', tax_rate)\n innner_func()\n print('outer func tax rate =', tax_rate)\n\nouter_func()", '_ii': "# 在函数体中用global声明的变量是全局变量,可由此实现在函数内部对全局变量修改\npi = 3.141592653589793\ne = 2.718281828459045\ndef my_func():\n global pi # 声明全局pi\n pi = 3.14\n print('global pi =', pi)\n e = 2.718 \n print('local e =', e)\n\nprint('global pi =', pi)\nprint('global e =', e)\nmy_func() \nprint('global pi =', pi)\nprint('global e =', e)", '_iii': '# 例8.27\nm = 100 # 全局变量m\nn = 200 # 全局变量n\ndef f():\n print(m+5) # 未经赋值直接使用,m被视为全局变量\n n += 10 # 引发错误\n\nf()', '_i1': '# 没有参数的函数\ndef foo():\n print("Hello World!")\n\n# 返回两个数的平均值的函数\ndef my_average(a, b): \n return (a+b)/2\n\n# 没有return语句的函数\n# 打印宽度为50的字符串的函数,n个*居中\ndef print_star(n): \n print(("*"*n).center(50))\n \n# 计算n阶调和数(1 + 1/2 + 1/3 + … + 1/n)的函数\ndef harmonic(n): \n total = 0.0\n for i in range(1, n+1): total += 1.0 / i\n return total', 'foo': <function foo at 0x0000023F064516C0>, 'my_average': <function my_average at 0x0000023F06450F40>, 'print_star': <function print_star at 0x0000023F06450FE0>, 'harmonic': <function harmonic at 0x0000023F06451080>, '_i2': '# 具有多个return的函数\ndef is_prime(n):\n if n < 2:\n return False \n i = 2\n while i * i <= n:\n if n % i == 0: \n return False\n i += 1\n return True', 'is_prime': <function is_prime at 0x0000023F01CD5E40>, '_i3': 'def foo():\n return 1, 2, 3\n\nfoo()\n\na,b,c = foo()\nprint(a,b,c)', '_': {'name': '孙悟空', 'alias': '齐天大圣', 'age': None, 'weapon': ['如意金箍棒'], 'shentong': ['七十二变', '筋斗云']}, '__': '\n这个函数\n什么也不做\n', '___': (1, 2, 3), '_3': (1, 2, 3), 'a': 1, 'b': 2, 'c': 3, '_i4': '# 既无返回值也无副作用的函数\ndef foo():\n pass', '_i5': "def foo():\n ''' \n 这个函数\n 什么也不做\n '''\n pass\n \nfoo.__doc__", '_5': '\n这个函数\n什么也不做\n', '_i6': '# 例8.9 无效的自增函数\n# 参数为不可变对象,函数内对形参的修改不会反映在实参对象上\ni=100\ndef inc(j,n):\n j += n\n\ninc(i, 10)\nprint(i)', 'i': 110, 'inc': <function inc at 0x0000023F06450D60>, '_i7': '# 例8.10 有效的自增函数\ni=100\ndef inc(j,n):\n j += n\n return j\n \ni =inc(i,10)\nprint(i)', '_i8': "# 例8.11 根据下标交换列表元素位置\ndef exchange(a, i, j):\n '''\n a是列表\n i,j是a的索引\n 此函数将a的第i和第j个索引处的元素交换位置\n '''\n temp = a[i]\n a[i] = a[j]\n a[j] = temp\n \n# 例8.12 列表元素洗牌\ndef shuffle(a):\n import random\n n = len(a) \n for i in range(n): \n r = random.randrange(i, n)\n exchange(a, i, r)\n\na = list(range(10))\nprint(a)\nshuffle(a)\nprint(a)", 'exchange': <function exchange at 0x0000023F06451760>, 'shuffle': <function shuffle at 0x0000023F06450E00>, '_i9': "# *之后必须使用关键字参数\n# 因此下列函数的所有参数都是关键字参数\ndef my_sum(*, mid_score, end_score, mid_rate = 0.4):\n score = mid_score * mid_rate + end_score * (1 - mid_rate)\n print(format(score, '.2f'))\n\n# 正确调用\nmy_sum(mid_score = 88, end_score = 79) \nmy_sum(end_score = 79, mid_score = 88)", 'my_sum': <function my_sum at 0x0000023F06450C20>, '_i10': '# 错误调用\nmy_sum(88, 79) ', '_i11': '# mid_rate参数具有默认值\ndef my_sum2(mid_score, end_score, mid_rate = 0.4):\n score = mid_score * mid_rate + end_score * (1 - mid_rate)\n print(f"{score:.2f}")\n\n# 正确调用\n# 三个参数都是位置参数\nmy_sum2(88, 79, 0.4)\n# 3个参数都是关键字参数\nmy_sum2(mid_rate = 0.4, mid_score=88, end_score=79)\n\n# 第三个参数使用默认值\n# mid_score, end_score是位置参数,顺序传入\nmy_sum2(88, 79)\n# mid_score, end_score是关键字参数,顺序可以随意\nmy_sum2(mid_score = 88, end_score = 79)\nmy_sum2(end_score = 79, mid_score = 88)', 'my_sum2': <function my_sum2 at 0x0000023F064B7E20>, '_i12': '# 错误调用\n# 关键字参数必须在位置参数之后\nmy_sum2(mid_rate = 0.4, 88, 79)', '_i13': "def foo(n1, *pos):\n print(n1)\n for i, x in enumerate(pos):\n print(f'pos参数的第{i}个对象是{x}')\n\nfoo(1, 2, 3, 4, 5, 6)", '_i14': '# 求多个整数的最大值\ndef my_max(a, b, *c):\n max_value = a \n if max_value < b:\n max_value = b\n for n in c: \n if max_value < n:\n max_value = n\n return max_value \n\nprint(my_max(1, 2)) \nprint(my_max(1, 7, 11, 2, 5)) ', 'my_max': <function my_max at 0x0000023F081F8B80>, '_i15': "def character_creator(**traits):\n return(traits)\n\ncharacter_creator(name = '孙悟空',\nalias = '齐天大圣',\nage = None,\nweapon = ['如意金箍棒'],\nshentong = ['七十二变', '筋斗云']\n)", 'character_creator': <function character_creator at 0x0000023F034982C0>, '_15': {'name': '孙悟空', 'alias': '齐天大圣', 'age': None, 'weapon': ['如意金箍棒'], 'shentong': ['七十二变', '筋斗云']}, '_i16': '# 有默认值的参数n1后面只要有*,就可以再次出现无默认值参数\ndef foo(n1 = 1, *, n2):\n print(n1+n2)\n\nfoo(n2 = 10) # 正确', '_i17': '# 错误,因为n2必须以关键字形式传参\nfoo(10) ', '_i18': '# 默认值为可变对象列表\n# 三次调用的痕迹会累积在有默认值的参数中被返回\ndef f(a, L = []):\n L.append(a)\n return L\n\nprint(f(1))\nprint(f(2))\nprint(f(3))', 'f': <function f at 0x0000023F081FA160>, '_i19': '# 设定默认参数为None,在函数体中将None替换为默认值,可避免上述现象\ndef f(a, L=None):\n if L is None:\n L = []\n L.append(a)\n return L\n\nprint(f(1))\nprint(f(2))\nprint(f(3))', '_i20': 'TAX1 = 0.17\nTAX2 = 0.2\nTAX3 = 0.05\nPI = 3.14', 'TAX1': 0.17, 'TAX2': 0.2, 'TAX3': 0.05, 'PI': 3.14, '_i21': '# 例8.26\nnum = 100 \ndef f():\n num = 105 # 这个num是局部变量\n print(num)\n\nf()\nprint(num) # 在函数之外,num还是全局变量', 'num': 100, '_i22': '# 例8.27\nm = 100 # 全局变量m\nn = 200 # 全局变量n\ndef f():\n print(m+5) # 未经赋值直接使用,m被视为全局变量\n n += 10 # 引发错误\n\nf()', 'm': 100, 'n': 200, '_i23': "# 在函数体中用global声明的变量是全局变量,可由此实现在函数内部对全局变量修改\npi = 3.141592653589793\ne = 2.718281828459045\ndef my_func():\n global pi # 声明全局pi\n pi = 3.14\n print('global pi =', pi)\n e = 2.718 \n print('local e =', e)\n\nprint('global pi =', pi)\nprint('global e =', e)\nmy_func() \nprint('global pi =', pi)\nprint('global e =', e)", 'pi': 3.14, 'e': 2.718281828459045, 'my_func': <function my_func at 0x0000023F081F8EA0>, '_i24': "def outer_func(): # 例8.29\n tax_rate = 0.17\n print('outer func tax rate =', tax_rate)\n def innner_func():\n nonlocal tax_rate\n tax_rate = 0.05\n print('inner func tax rate =', tax_rate)\n innner_func()\n print('outer func tax rate =', tax_rate)\n\nouter_func()", 'outer_func': <function outer_func at 0x0000023F081F8FE0>, '_i25': "a=1\nb=2\ndef f(a, b):\n x = 'abc'\n y = 'xyz'\n for i in range(2):\n j = i\n k = i**2\n print(locals())\n\nf(1,2)\nprint(globals())"}
14.4 递归函数
一个函数在调用的过程中调用自己,称为函数的递归调用。
# 这是递归函数,但不要执行,会死循环
def story():
print("从前有座山,山上有座庙,庙里有个老和尚,老和尚给小和尚讲故事。讲的故事是:\n");
story()递归一般用于解决能被分解为更小的、同类型的子问题的复杂问题。 当复杂问题层层化简到最基本的情况时,问题很容易被解决。 解决了较为简单的问题后,比它复杂一层的问题也能很容易被解决。
如:排在队伍中的你,想知道你前面有几个人。 你问排你前面的人,排你前面的人也问排他前面的人。 问到第 1 位,这个问题的答案是 0。第1人把他的答案告诉他后面的第2人。 第2人在第1人的答案上+1,即得到自己的答案,他把此答案告诉自己后面的第3人。 每个人都把自己的答案告诉自己后面的人。答案传递给你的时候,你也就知道了自己前面有几个人
能用递归算法解决的典型问题:
- 获取自己在当前队伍中的排名。
- 求阶乘 n!=n \times (n − 1) \times\cdots\times2 \times 1。
- 求斐波那契数列的各项: f(1) = f(2) = 1, f(n) = f(n − 1) + f(n − 2)。
- 汉诺塔。
- 数独。
编写递归函数,有 3 个步骤:
- 明确函数功能。
- 写出递归情况:在已经解决某一层问题的情况下,如何解决更难一层的问题。
- 写出基本情况:最简单的问题的解。(必须有基本情况,保证递归函数不会无限制地调用自身)
# 用递归函数求阶乘
def factorial(n):
if n == 1:
return 1
return n * factorial(n - 1)
for i in range(1,10):
print(f'{i}!={factorial(i)}')1!=1
2!=2
3!=6
4!=24
5!=120
6!=720
7!=5040
8!=40320
9!=362880
为避免无限递归函数调用耗尽内存,Python 的 sys 模块设置最大递归深度。
import sys
sys.getrecursionlimit() # 获取递归次数上限
sys.setrecursionlimit(2000) # 设置递归次数上限14.5 匿名函数
lambda关键字生成匿名函数,写法比函数的正式定义更简便。 用法是:
lambda+ 逗号分隔的形参 + 冒号:+ 函数体
匿名函数用于函数比较简单、只需要使用一次的场合。如某个函数需要一个函数参数。
# 例8.6
f = lambda x, y : x + y # f是函数
type(f)
f(12, 34)
# 将元组序列按每个元组的第1个索引的值排序
sorted([('Bob',75),('Adam',92),('Lisa',88)], key = lambda t:t[1])function
46
[('Bob', 75), ('Lisa', 88), ('Adam', 92)]
14.6 练习
- 编写函数,解9\times9的数独。
提示:考虑递归。