14  函数

数学上的函数,一般写为 y = f(x) 的形式,你提供一个输入 x ,函数还你一个输出 y 。 编程中的函数,是实现特定功能的一段代码,可重复使用。

函数的用处显而易见,如:

14.1 函数定义

函数定义以关键字def开头,语法如下:

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 True

return 语句可以返回一个或多个值。返回多个值时,多个值作为元组返回,可以解包赋值给多个变量。

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 参数传递方式

调用函数时,实参的值被传递给形参,不同编程语言有不同的实现方式:

  1. 值传递(pass by value):调用函数时,复制实参的值,用此值对形参初始化,函数内形参的改变不改变实参的值。

  2. 引用传递(pass by reference):调用函数时,传递实参的引用(内存地址),形参的改变会改变实参的值。

  3. 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 位置参数,关键字参数

根据传参时采用的具体形式,参数可以分为:

  1. 位置参数(positional argument),按位置传递的参数。不按顺序传参会出错。
  2. 关键字参数(keyword argument):传参时在实参前面使用参数名称 =。关键字参数之间可以打乱传参顺序。

在函数定义中,关于关键字参数与位置参数,有规则如下:

  1. 位置参数必须位于关键字参数之前。
  2. 如果要显式区分位置参数与关键字参数,可用/*符号:
    • / 之前只能出现位置参数,称为强制位置参数(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 带*的参数

  1. *形参名称 接受任意数量的位置参数,将其打包为元组,默认值为空元组。
  2. **形参名称 接受任意数量的关键字参数,将其打包为字典,默认值为空字典。
  3. **形参名称 如果存在,它应当是最后一个参数。
  4. 在函数体中,不要再写***,直接用星号后的形参名称。
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 有默认值的参数

  1. 在参数定义中,可设定参数的默认值。
  2. 有默认值的参数后面必须都是有默认值的参数,直到出现*符号。
  3. 有默认值的参数的默认值只计算一次。默认值是可变对象时,这会导致多次调用共享同一个默认值,可能会产生错误。
# 有默认值的参数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)

根据从命令行接收到的第1个参数,决定绘制三角形的规模。

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后,直接使用其中的变量了。

  1. 如果全局变量与局部变量同名,则在局部变量的作用范围内,全局变量被“屏蔽”(mask),即该变量名被认为是局部变量,而不是全局变量。
  2. 在函数体中被直接使用、没有被赋值过的变量,被认为是全局变量;在函数体内被赋值的变量,被认为是局部变量。
  3. 如果要在函数体内为函数外的全局变量赋值,应该用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人。 每个人都把自己的答案告诉自己后面的人。答案传递给你的时候,你也就知道了自己前面有几个人

能用递归算法解决的典型问题:

  1. 获取自己在当前队伍中的排名。
  2. 求阶乘 n!=n \times (n − 1) \times\cdots\times2 \times 1
  3. 求斐波那契数列的各项: f(1) = f(2) = 1, f(n) = f(n − 1) + f(n − 2)。
  4. 汉诺塔
  5. 数独

编写递归函数,有 3 个步骤:

  1. 明确函数功能。
  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 练习

  1. 编写函数,解9\times9的数独。

提示:考虑递归。