学习目标

掌握函数的定义和调用方法
掌握函数参数与返回值
掌握变量的作用域
掌握lambda表达式
掌握递归函数

函数的定义和作用

函数是组织好的,可重复使用的,用来实现单一或相关联功能的代码段。
函数能提高应用的模块性和代码的重复利用率。
Python有很多内置函数,也可以根据实际需要定义符合我们要求的函数,这被叫做用户自定义函数。
函数解决的问题:

  • 如果程序的功能比较多,规模比较大,把所有的代码都写在一个程序文件里,就会使文件中的程序变得庞杂,使人们阅读和维护程序变得困难。
  • 有时程序中要多次实现某一功能,就要多次重复编写实现此功能的程序代码,这会使程序冗长、不精练。

函数是用来完成一定的功能的,可以看作是实现特定功能的小方法或小程序。
函数可以简单地理解为:编写了一些语句,为了方便重复使用这些语句,把这些语句组合在一起,给它起一个名字。使用的时候只要调用这个名字,就可以实现这些语句的功能了。

  1. def 函数名([参数列表]):
  2. 函数体

:::info

  • 函数代码快以def关键词开头,之后是函数名,def和函数名中间要敲一个空格。
  • 圆括号内用于定义函数参数,称为形式参数或简称为形参,参数是可选的,函数可以没有参数。如果有多个参数,参数之间用逗号隔开。参数就像一个占位符,当调用函数时,就会将一个值传递给参数,这个值被称为实际参数或实参。在Python中,函数形参不需要声明其类型。
  • 函数体,指定函数应当完成什么操作,是由语句组成,要有缩进。
  • 如果函数执行完之后有返回值,称为带返回值的函数,函数也可以没有返回值。带有返回值的函数,需要使用以关键词return开头的返回语句来返回一个值,执行return语句意味着函数执行的终止。
  • 在定义函数时,开头部分的注释通常描述函数的功能和参数的相关说明,但这些注释并不是定义函数时必需的,可以使用内置函数help()来查看函数开头部分的注释内容。 :::
    • 函数形参不需要声明其类型,也不需要指定函数返回值类型。
    • 即使该函数不需要接受任何参数,也必须保留一对空的圆括号。
  1. def min(num1, num2):
  2. if num1 < num2:
  3. result = num1
  4. else:result = num2
  5. return result

函数调用

要使函数发挥功能,必须调用函数,调用函数的程序被称为调用者。调用函数的方式是函数名(实参列表),实参列表中的参数个数要与形参个数相同,参数类型也要一致。
当程序调用一个函数时,程序的控制权就会转移到被调用的函数上。当执行完函数的返回值语句或执行到函数结束时,被调用函数就会将程序控制权交还给调用者。
根据函数是否有返回值,调用函数有两种方式:

  1. 带有返回值的函数调用

对这种函数的调用通常当做一个值处理,例如:
smaller = min(2, 3)
print(min(2, 3))

  1. 没有返回值的函数调用

如果函数没有返回值,对函数的调用是通过将函数调用当作一条语句来实现的。

  1. def printStr(str1):
  2. # 打印任何传入的字符串
  3. print(str1)
  4. printStr('hello world')
  5. '''
  6. hello world
  7. '''
  1. def sum(num1, num2):
  2. result = 0
  3. for i in range(num1, num2 + 1):
  4. result += i
  5. return result
  6. def main():
  7. print('Sum form 1 to 10 is', sum(1, 10))
  8. print('Sum form 11 to 20 is', sum(11, 20))
  9. print('Sum form 21 to 30 is', sum(21, 30))
  10. main()
  11. '''
  12. Sum form 1 to 10 is 55
  13. Sum form 11 to 20 is 155
  14. Sum form 21 to 30 is 255
  15. '''

这个程序包含sum函数和main函数,在Python中main也可以写成其他任何合适的标识符。程序脚本在最后一行调用main函数。习惯上,程序里通常定义一个包含程序主要功能的名为main的函数。 解释器从第一行开始一行一行地读取程序语句,读到第1行的函数头时,将函数以及函数体(第1到5行)存储在内存中。然后解释器将main函数的定义(第7到9行)读取到内存。最后解释器读取到第12行时,调用main函数,即main函数被执行。

注意:这里的main函数是定义在sum函数之后。在Python中,函数在内存中被调用,在调用某个函数之前,该函数必须已经调入内存,否则系统会出现要调用的函数未调入的错误。

形参与实参

声明函数时建立的参数为形式参数,简称形参。
调用函数时,提供函数需要的参数的值,即实际参数,简称实参。
实际参数值默认按位置顺序依次传递给形式参数,如果参数个数不对,则会产生错误。
在定义函数时,如果有多个形参,则需要使用逗号分隔。同时,对于绝大多数情况,在函数内部直接修改形参的值不会影响实参。

  1. def plus(a, b):
  2. print(a)
  3. a += b
  4. print(a)
  5. a, b = 3, 2
  6. plus(a, b)
  7. print(a, b)
  8. '''
  9. 3
  10. 5
  11. 3 2
  12. '''
  1. # 修改列表元素值
  2. def modify(v):
  3. v[0] += 1
  4. a = [2]
  5. modify(a)
  6. print(a)
  7. '''
  8. [3]
  9. '''
  1. # 为列表添加元素
  2. def append(v, item):
  3. v.append(item)
  4. append(a, 4)
  5. print(a)
  6. '''
  7. [3, 4]
  8. '''
  1. # 修改字典元素的值
  2. def change(d):
  3. d['age'] = 30
  4. b = {'name':'Wang', 'age':20, 'sex':'Male'}
  5. change(b)
  6. print(b)
  7. '''
  8. {'name': 'Wang', 'age': 30, 'sex': 'Male'}
  9. '''

Python函数参数定义时并不指定参数类型,为减少函数内部数据处理时可能发生的错误,让程序更加稳健,可以采用isinstance()函数来显式判断参数的类型,一旦用户输入的类型不符合处理要求,进入错误处理逻辑,这样编写的程序会更加严密。

  1. from math import pi as PI
  2. def CircleArea(r):
  3. if isinstance(r, int) or isinstance(r, float):
  4. return PI * r * r
  5. else:
  6. return('You must give me an integer or float as radius.')
  7. print(CircleArea(3))
  8. '''
  9. 28.274333882308138
  10. '''

函数参数传递

Python中一切都是对象,调用函数时的参数传递就是传可变对象还是传不可变对象。函数调用时传递的参数类型分为可变类型和不可变类型。

  • 不可变类型:若a是数字、字符串、元组这三种类型中的一种,则函数调用fun(a)时,传递的只是a的值,在fun(a)内部修改a的值,只是修改另一个复制的对象,不会影响a本身。
  • 可变类型:若a是列表、字典这两种类型中的一种,则函数调用fun(a)时,传递的是a所指的对象,在fun(a)内部修改a的值,fun(a)外部的a也会受影响。

在Python中,定义函数时不需要指定参数的类型,形参的类型完全由调用者传递的实参本身的类型来决定。
函数形参的表现形式主要有:位置参数、关键字参数、默认值参数、可变长度参数。

1. 位置参数

位置参数是最常用的形式,调用函数时实参与形参的顺序必须严格一致,并且实参和形参的数量必须相同。

  1. def menu(food, cigarette, wine):
  2. print('主食:', food, '香烟:', cigarette, '白酒:', wine)
  3. menu('面条', '红塔山', '郎酒')
  4. '''
  5. 主食: 面条 香烟: 红塔山 白酒: 郎酒
  6. '''

2. 默认值参数

定义带有默认值参数的函数的语法格式:
functionName(..., 参数名 = 默认值)
在定义函数的时候,为形参设置了默认值。在调用设置了默认值参数的函数时,如果没有给设置了默认值的形式参数传递实参,那么这个形参就将使用默认值。

注意:在定义带有默认值参数的函数时,默认值参数必须出现在函数形参列表的最右端,任何一个默认值参数右边都不能再出现非默认值参数。

  1. def add(x = 3, y = 5):
  2. return(x + y)
  3. print(add(9))
  4. '''
  5. 14
  6. '''

3. 关键字参数

通过关键字参数可以按照参数名字传递值,明确指定哪个值传递给哪个参数,实参的顺序可以和形参的顺序不一致,但并不影响参数值的传递结果。
还可以混合位置参数和关键参数,但是要确保位置参数在关键参数的右边。

  1. def menu(food, cigarette, wine):
  2. print('主食:',food, '香烟:', cigarette, '白酒:', wine)
  3. menu(wine = '茅台', food = '米饭', cigarette = '好猫')
  4. menu('米粉', wine = '五粮液', cigarette = '云烟')
  5. '''
  6. 主食: 米饭 香烟: 好猫 白酒: 茅台
  7. 主食: 米粉 香烟: 云烟 白酒: 五粮液
  8. '''

4. 可变长度参数

  1. functionName(arg1, *tupleArg, **dictArg)

:::info

  • tupleArg和dictArg称为可变长度参数。
  • tupleArg前面的“*”表示这个参数是一个元组参数,用来接收任意多个实参并将其放在一个元组中。
  • dictArg前面的“**”表示这个参数是个字典参数(键值对参数),用来接收类似于关键词参数一样显示赋值形式的多个实参并将其放入字典中。
  • 可以把tupleArg、dictArg看成两个默认参数,调用带有可变长度参数的函数时,多余的非关键字参数放在元组参数tupleArg中,多余的关键字参数放字典参数dictArg中。 ::: ```python def varLength(arg1, tupleArg, *dictArg): print(‘arg1 = ‘, arg1) print(‘tupleArg = ‘, tupleArg) print(‘dictArg = ‘, dictArg) varLength(‘hello world’, ‘Python’, ‘C’, a = 1, b = 2)

‘’’ arg1 = hello world tupleArg = (‘Python’, ‘C’) dictArg = {‘a’: 1, ‘b’: 2} ‘’’

  1. ```python
  2. def print_args(arg1, arg2, *pargs, **kargs):
  3. print('arg1 is ', arg1)
  4. print('arg2 is ', arg2)
  5. print('pages is: ', pargs)
  6. print('kargs is: ', kargs)
  7. print_args('主食', '面条', '白酒', '汾酒', 香烟 = '芙蓉王')
  8. '''
  9. arg1 is 主食
  10. arg2 is 面条
  11. pages is: ('白酒', '汾酒')
  12. kargs is: {'香烟': '芙蓉王'}
  13. '''

5. 序列解包参数

使用序列解包参数调用的函数通常是一个位置参数函数,序列解包参数由一个“*”和序列连接而成,Python解释器将自动将序列解包成多个元素,并一一传递给各个位置参数。
创建列表、元组、集合、字典以及其他可迭代对象,称为“序列打包”,因为值被“打包到序列中”。“序列解包”是指将多个值的序列解开,然后放到变量的序列中。

  1. dict1 = {'one':1, 'two':2, 'three':3}
  2. # 字典解包默认的是解包字典的键
  3. x, y, z = dict1
  4. print(x, y, z)
  5. '''
  6. one two three
  7. '''
  1. dict1 = {'one':1, 'two':2, 'three':3}
  2. # 用字典对象的items()方法解包字典的“键值对”
  3. x1, y1, z1 = dict1.items()
  4. print(x1, y1, x1)
  5. '''
  6. ('one', 1) ('two', 2) ('one', 1)
  7. '''
  1. def print1(x, y, z):
  2. print(x, y, z)
  3. tuple1 = ('姓名', '性别', '籍贯')
  4. # *将tuple1解开成3个元素并分别赋给x,y,z
  5. print1(*tuple1)
  6. '''
  7. 姓名 性别 籍贯
  8. '''

函数的返回值

函数可以没有返回值。
return语句用来从一个函数中返回并结束函数的执行,同时返回一个任意类型的值。
不论return语句出现在函数的什么位置,一旦得到执行将直接结束函数的执行。
如果函数没有return语句或者执行了不返回任何值的return语句,即返回空值。
Python支持同时返回多个值,多个值以元组的形式返回。

  1. def adddiv(a, b):
  2. a, b = a + b, a / b
  3. # 返回一个元组,包含a和b的值
  4. return a, b
  5. # 元组的拆分
  6. add, sub = adddiv(10, 3)
  7. print('参数之和是:%d,参数相除是:%.2f'%(add, sub))
  8. '''
  9. 参数之和是:13,参数相除是:3.33
  10. '''

主函数

Python没有通常意义上的主函数,在程序中经常会放置以下代码,用以表示主要的代码执行:
if __name__ == '__main__'
name为系统变量,为当前模块的名称。

lambda表达式

lambda表达式可以用来声明匿名函数,也就是没有函数名字的临时使用的函数。
在使用函数作为参数的时候,如果传入的函数比较简单或者使用次数较少,直接定义这些函数就显得比较浪费,这时就可以使用lambda表达式。
lambda <variables>:<expression>
其中variables是函数的参数,expression是函数的返回值,它们之间用冒号:分隔。
在lambda关键字之后、冒号左边的是参数列表,可以没有参数,也可以有多个参数,由逗号隔开,冒号右边是该lambda表达式的返回值。
lambda表达式必须是单行的。
lambda表达式可以直接在程序中使用,也可以将这一表达式赋值给一个变量。
lambda表达式的计算结果就是函数的返回值。
lambda表达式只可以包含一个表达式,不允许包含选择、循环等语法结构,该表达式的计算结果可以看作是函数的返回值,不允许包含复合语句,但在表达式中可以调用其他函数。
lambda函数拥有自己的命名空间,且不能访问自有参数列表之外或全局命名空间里的参数。

  1. f = lambda x, y, z : max(x, y, z)
  2. print(f(10, 20, 30))
  3. g = lambda x : x * 2
  4. print(g(3))
  5. # 定义一个lambda表达式,求三个数的和
  6. f = lambda x, y, z : x + y + z
  7. print(f(1, 2, 3))
  8. '''
  9. 30
  10. 6
  11. 6
  12. '''
  1. '''
  2. 假如要编写函数实现计算多项式1 + 2 * x + y ** 2 + z * y的值,可以简单的定义一个lambda函数来完成这个功能。
  3. '''
  4. polynominal = lambda x, y, z : 1 + 2 * x + y ** 2 + z * y
  5. polynominal(1, 2, 3)
  6. '''
  7. 13
  8. '''

函数模块化

大型的软件程序往往需要由多个py文件构成,其中每个py文件就算作一个模块。
可以将多个函数的定义放在一个模块中,然后,将模块导入到其他程序中,这些程序就可以使用模块中定义的函数。
默认情况下,*.py文件的文件名即为模块名。

  1. # 直接导入模块
  2. import 模块名
  3. # 导入的同时为模块起别名
  4. import 模块名 as 模块别名
  1. # 导入模块time,并将模块time重命名为t
  2. import time as t
  3. # 获取当前的时间
  4. t.ctime()
  5. '''
  6. 'Sun May 22 15:25:50 2022'
  7. '''

变量的作用域

变量起作用的代码范围称为变量的作用域。在Python中,使用一个变量时并不需要预先声明它,但在真正使用它之前,它必须被绑定到某个内存对象(也即变量被定义、赋值),变量名绑定将在当前作用域中引入新的变量,同时屏蔽外层作用域中的同名变量。

局部变量与全局变量

函数内部定义的变量一般为局部变量。
不属于任何函数的变量一般为全局变量。
引用局部变量比全局变量速度快,因此在程序设计时优先考虑使用局部变量。
全局变量可能会增加不同函数之间的隐式耦合度,因此在实际编程过程中不适宜大量使用。
局部变量只在该函数内起作用,当函数运行结束后,在该函数内部定义的局部变量将被自动删除而不可访问。
在函数内部定义的全局变量在函数结束后仍然存在并且可以访问。
如果想要在函数内部修改一个定义在函数外的变量值,可以通过global来声明或定义,能够同时作用于函数内外。

  1. num = 100
  2. def func():
  3. # 声明num是全局变量
  4. global num
  5. num = 200
  6. print('在函数内输出num:', num)
  7. func()
  8. print('在函数外输出num:', num)
  9. '''
  10. 在函数内输出num: 200
  11. 在函数外输出num: 200
  12. '''

不属于任何函数的变量一般为全局变量,它们在所有函数之外创建,可以被所有的函数访问,也即模块层次中定义的变量,每一个模块都是一个全局作用域。
在模块文件顶层声明的变量具有全局作用域,模块的全局变量就像是一个模块对象的属性。
在函数内部直接使用global关键字将一个变量声明为全局变量,即使在函数外没有定义该全局变量,在调用这个函数之后,将自动增加新的全局变量。

  1. # 函数体外定义的全局变量
  2. name = ['Chinese', 'Math']
  3. name1 = ['Java', 'Python']
  4. name2 = ['C', 'C++']
  5. def f1():
  6. # append方法可改变外部全局变量的值
  7. name.append('English')
  8. print('函数内name:%s' %name)
  9. name1 = ['Physics', 'Chemistry']
  10. print('函数内name1:%s' %name1)
  11. # 如需重新给name2赋值,需使用global声明全局变量
  12. global name2
  13. name2 = '123'
  14. print('函数内name2:%s' %name2)
  15. f1()
  16. print('函数外输出name:%s' %name)
  17. print('函数外输出name1:%s' %name1)
  18. print('函数外输出name2:%s' %name2)
  19. '''
  20. 函数内name:['Chinese', 'Math', 'English']
  21. 函数内name1:['Physics', 'Chemistry']
  22. 函数内name2:123
  23. 函数外输出name:['Chinese', 'Math', 'English']
  24. 函数外输出name1:['Java', 'Python']
  25. 函数外输出name2:123
  26. '''

变量的嵌套作用域

嵌套作用域也包含在函数中,嵌套作用域和局部作用域是相对的,嵌套作用域相对于更上层的函数而言也是局部作用域。
与局部作用域的区别在于,对一个函数而言,局部作用域是定义在此函数内部的局部作用域,而嵌套作用域是定义在此函数的上一层父级函数的局部作用域。
搜索变量名的优先级:局部作用域 > 嵌套作用域 > 全局作用域
即变量名解析机制是:在局部找不到,便会去局部外的局部找,再找不到就会去全局找。

函数的递归调用

如果一个函数在内部调用自身,这个函数就是递归函数。
适当地运用递归有时能够有效减少程序设计的复杂性,可以通过有限的语句来定义复杂问题,使得程序编写和维护都变得更加容易。
递归函数的设置一般会需要终止条件和递归条件,从而避免无限循环。
整个问题的求解可以分为两部分:

  • 第一部分是一些特殊情况,有直接的解法
  • 第二部分是与原问题相似,但比原问题的规模小,并且依赖第一部分的结果

递归的两个基本要素:

  • 边界条件:确定递归到何时终止,也称为递归出口
  • 递归模式:大问题是如何分解为小问题的,也称为递归体 ```python def fib(n): if n ==1 or n == 2:
    1. return 1
    else:
    1. return fib(n - 1) + fib(n - 2)

res = fib(20) print(res)

‘’’ 6765 ‘’’

  1. <a name="A823C"></a>
  2. # 常用的内置函数
  3. <a name="Jz8pP"></a>
  4. ## map()
  5. `map(func, seq1[, seq2, ...])`<br />map()函数接收一个函数function和一个或多个序列iterable,并通过把function依次作用到iterable的每个元素上,得到新的序列并返回。
  6. 1. 当序列seq只有一个时,将函数func作用于这个seq的每个元素上,并得到一个新的seq。
  7. ```python
  8. L = [1, 2, 3, 4, 5]
  9. # 将L中的每个元素加5
  10. print(list(map((lambda x : x + 5), L)))
  11. # 将L中的每个元素转换为字符串
  12. list(map(str, L))
  13. '''
  14. [6, 7, 8, 9, 10]
  15. ['1', '2', '3', '4', '5']
  16. '''
  1. 当序列seq多于一个时,每个seq的同一位置的元素同时传入多元的func函数(有几个列表,func就应该是几元函数),把得到的每一个返回值存放在一个新的序列中。 ```python

    定义一个二元函数

    def add(a, b): return a + b

a = [1, 2, 3]
b = [4, 5, 6]

将a,b两个列表同一位置的元素相加求和

list(map(add, a, b))

‘’’ [5, 7, 9] ‘’’

  1. 在实际使用中,常常把map()函数与lambda表达式相结合使用,实现对序列类型数据的灵活方便处理。
  2. ```python
  3. a = [1, 2, 3, 4]
  4. print(list(map(lambda x : x + 1, a)))
  5. '''
  6. [2, 3, 4, 5]
  7. '''

例:已知一个数列的通项为f(x) = x3 + 5x2 - 2x + 1。其中 的x对应一组数据[3, 5, 8, 10, 11, 15, 22],求该数列。

  1. def f(x):
  2. return x ** 3 + 5 * x ** 2 - 2 * x + 1
  3. x_list = [3, 5, 8, 10, 11, 15, 22]
  4. print(list(map(lambda x : f(x), x_list)))
  5. '''
  6. [67, 241, 817, 1481, 1915, 4471, 13025]
  7. '''

reduce()

reduce()函数在库functools里,reduce()函数的语法格式:
reduce(fuction, sequence[, initializer])
参数:

  • function — 函数,有两个参数;
  • sequence — 序列对象;
  • initializer — 可选,初始参数

注意:reduce()使用时需要引入functools模块。

  1. reduce(function, sequnece)

先将sequence的第一个元素作为function函数的第一个参数和sequnece的第二个元素作为function函数第二个参数进行function函数运算,然后将得到的返回结果作为下一次function函数的第一个参数和序列sequence的第三个元素作为function的第二个参数进行function函数运算,依次进行下去直到sequence中的所有元素都得到处理。

  1. from functools import reduce
  2. def add(x, y):
  3. return x + y
  4. # 计算列表和:1 + 2 + 3 + 4 + 5
  5. reduce(add, [1, 2, 3, 4, 5])
  6. '''
  7. 15
  8. '''
  1. reduce(function, sequnece, initializer)

先将初始参数initializer的值作为function函数的第一个参数和sequence的第一个元素作为function的第二个参数进行function函数运算,然后将得到的返回结果作为下一次function函数的第一个参数和序列sequence的第二个元素作为function的第二个参数进行function函数运算,得到的结果再与第3个数据用function进行函数运算,依次进行下去直到sequnece中的所有元素都得到处理。

  1. from functools import reduce
  2. reduce(lambda x, y : x + y, [2, 3, 4, 5, 6], 1)
  3. '''
  4. 21
  5. '''

filter()

filter(func, iterabe)
filter()函数用于过滤序列,即用函数func过滤掉iterable序列中不符合条件的元素,返回由符合条件元素组成的新序列。第一个参数为函数,第二个参数为序列,序列的每个元素作为参数传递给函数进行判断,最后将返回True的元素放到新序列中。

  1. def is_odd(n):
  2. return n % 2 == 1
  3. newlist = filter(is_odd, range(20))
  4. list(newlist)
  5. '''
  6. [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
  7. '''

总结

函数定义时建立的参数为形式参数
函数实际使用时输入的参数就是实参
变量的作用域反应变量在不同程序结构层次内的有效性
lambda表达式用来定义匿名函数
函数递归是一种常见的编程方式