
函数、传参、返回值、域
更新: 2025/2/24 字数: 0 字 时长: 0 分钟
在前面学习的过程中,我们无时无刻都在使用函数,例如 input
输入函数、print
输出函数、len
长度函数等,这些函数都是 Python 的内置函数,也就是 Python 内部已经定义好的函数,我们尽管拿来使用即可。但如果我们要自定义函数该如何编写呢?经过下面的学习,相信你一定会有所收获。
函数
函数,就是功能单一、相对独立且会被重复使用的封装代码。假定游戏某关的通关操作是 上下左右 上下左右 上下左右
,我们一共要操作 12 次才能通关,假如把 上下左右
设置称为 1 个功能模块,那我们调用 3 次模块就可以通关了,这样就大大提高了我们的操作效率,同时也提高了操作的复用性,我们就认为这样的功能模块就是一个函数。将来想使用这些功能的时候,直接通过调用函数来做到,不再需要复制粘贴代码。
函数声明
上面说过,函数对功能单一的代码块的封装,其最终目的就是让代码更加简洁、易读。如果我们要自定义函数实现某些功能,就必须按照以下格式进行声明:
def 函数名(形参: 类型标注=形参默认值) -> 返回值类型标注:
"""
函数功能说明
:param 形参: 对函数形参进行说明(对自变量进行说明)
:return: 对函数的返回值进行说明(对因变量进行说明)
"""
函数体
def
:声明函数的关键字。- 函数名:函数名称,不能使用关键字,不能使用内置函数名,最好见名知义(命名规范参照 PEP 8)。
()
:必须有的固定格式。- 形参:函数接收外部传递的数据(若没有数据接收,可以为空)。
- 类型标注:告知形参的传入类型(可省略)。
- 形参默认值:形参的默认值(若没有默认值,可省略)。
- 返回值类型标注:告知函数的返回值类型(可省略)。
- 函数功能说明:对函数的功能和参数以及返回值进行说明(可省略,最好还是写)。
- 函数体:实现函数功能的代码块。
重要
自定义函数需要遵循单一职责原则,即一个函数只能负责一个功能。如果一个函数实现了多个功能,而调用者只需使用其中一个功能时,那么其他多余功能将会延长函数的执行时间,这反而是一种降低效率的做法。
注意
函数一定要无副作用,也就是说函数必须在不改动对象数据的前提下,返回结果。比如说,通过一个函数给列表中的元素排序,使用 列表.sotr()
方法就不行,因为该方法是在原列表的基础上操作的,虽然他可以排序出结果,但它动了原本的数据,后面函数就没有原列表的数据使用了,因此这里只能使用 sorted()
方法对列表进行排序,返回排序后的新列表。
函数调用
调用格式:调用函数就必须按照以下格式才能正确调用。
函数名(实参)
- 函数名:被调用的函数必须是已经先前定义好的。
- 实参:传递给函数接内部的数据,若没有数据传递可以不写。
用前定义:函数一定要在被调用前定义,否则就会报函数未定义的错。
han() # 报错:此步骤han函数还未被定义。
def han(): # 注释:这里才开始定义han函数。
print('开始')
任意次数:一个定义好的函数可以被调用任意次数。
def han(): # 注释:定义han函数。
print('开始')
han() # 输出:开始。注释:han()调用一次han函数。
han() # 输出:开始。注释:han()调用一次han函数。
h = han() # 输出:开始。注释:赋值中的han()也会调用一次han函数,最后将han函数的返回值赋值给变量h。
引入执行:在函数的内部可以引入其它的函数,由于只是引入,并不是执行,因此被引入的函数可以是未定义的状态,也可以是已定义的状态。当外部函数被调用执行时,内部引入的函数必须是已定义的状态,否则会在内部报函数未定义的错误。
def han1(): # 注释:定义han1函数。
han() # 注释:在han1函数内部可以引入此时未定义的han函数。
print('结束')
han1() # 报错:此时han1函数内部引用的han函数还是未定义的状态,不能被调用。
def han(): # 注释:定义han函数。
print('开始', end='')
han1() # 输出:开始结束。注释:此时han1函数内部引用的han函数是已定义的状态,可以被调用。
具体流程:首先每个函数都有自己的栈结构,在调用前会将函数放到调用栈(FILO,先进后出),当一个函数调用另一个函数的时候,会先保存现场(保存当前函数的栈结构),然后调用函数(入栈到另一个函数的栈结构),调用完成后恢复现场(出栈回到原先函数的栈结构)。
函数属性
在 Python 中所有的数据类型都是皆对象,包括函数也是对象,因此函数有自己的标识(identity)、类型(type)、值(value)。具体如下:
def han(): # 注释:定义一个名为han的函数,han也是一个函数对象的引用,指向内存中的函数代码。
print('开始')
print(han) # 输出:<function han at 0x000002267E0737B8>。注释:打印函数名han,输出函数对象的引用。
print(id(han)) # 输出:2364346415032。注释:返回函数对象的标识符,十六进制值就是上面的0x000002267E0737B8。
print(type(han)) # 输出:<class 'function'>。注释:对象han是一个函数类型对象。
han() # 输出:开始。注释:通过函数对象的引用访问对象所在地址运行函数内容。
a = han # 注释:将han函数对象的引用赋值给变量a。
print(id(a)) # 输出:2364346415032。注释:变量a的标识符和上面一样,说明它和han引用的是相同的函数对象。
a() # 输出:开始。注释:变量a是函数对象的引用,调用a就是在调用函数对象,所以输出了'开始'。
建议
在 Python 中,多个变量可以引用同一个函数对象,并通过不同的变量名来调用函数。
传参
传参,字面意思就是给函数传递参数。例如,圆的面积计算函数为 ,其中常量 是定值(可以写死,不用传递),而半径 就是需要传递的参数。可以说,编程中的函数和数学中的 函数概念一样,这里 代表“函数”,这里 代表“自变量”对应函数代码中的“输入”,这里 代表“因变量”对应函数代码中的“输出”。
形参实参
在最开始定义函数和调用函数的时候,提到了形参和实参,它们的含义如下:
- 形参(“形式参数”的简称)就是函数定义时括号内的变量名。这些变量名代表了函数在执行时需要接收的数据。在函数体内部,你可以使用这些形参来进行计算、逻辑判断等操作。
- 实参(“实际参数”的简称)就是调用函数时传递给函数的值或变量。当函数被调用时,实参会被赋值给相应的形参,然后函数体内部就可以使用这些值来进行计算或操作了。实参可以是常量、变量、表达式或甚至是另一个函数的返回值。
# 注释:a, b, c都在定义func1函数的括号里面,因此都是形参。
def func1(a, b, c):
print('a=' + str(a))
print('b=' + str(b))
print('c=' + str(c))
# 注释:10, 20, 30都在调用func1函数的括号里面,因此都是实参。
func1(10, 20, 30)
警告
传参本质就是实参给形参赋值的过程,一定要保证每个形参都有对应的实参或默认值,否则就会报错。
传递方式
位置传参:实参按照位置顺序依次给形参赋值。注意实参的个数必须和形参的个数完全一样,否则会报错。
def func1(a, b, c):
print('a=' + str(a), end=', ')
print('b=' + str(b), end=', ')
print('c=' + str(c))
func1(10, 20, 30) # 输出:a=10, b=20, c=30。注释:按照位置给形参赋值。
关键字传参:实参按照相同的名称给形参赋值,且关键字传参必须放在所有位置传参的后面。注意实参的个数必须和形参的个数完全一样,否则会报错。
def func1(a, b, c):
print('a=' + str(a), end=', ')
print('b=' + str(b), end=', ')
print('c=' + str(c))
func1(c=30, a=10, b=20) # 输出:a=10, b=20, c=30
func1(10, 20, c=30) # 输出:a=10, b=20, c=30
func1(c=30, 10, 20) # 报错:关键字传参必须放在所有位置传参的后面。
默认传参:形参在有实参赋值的情况下,使用实参赋值。在没有实参赋值的情况下,使用默认值。有默认参数的形参必须放在所有位置传参的后面。
# 注释:这里c=30即形参c的默认值是30,而且它必须放在位置形参a、b的后面。
def func1(a, b, c=30):
print('a=' + str(a), end=', ')
print('b=' + str(b), end=', ')
print('c=' + str(c))
func1(10, 20) # 输出:a=10, b=20, c=30。注释:按照位置传参,这里没有给形参c赋值,形参c就等于默认值30。
func1(10, 20, 40) # 输出:a=10, b=20, c=40。注释:按照位置传参,这里给形参c赋值40,形参c就不使用默认值。
不定传参:当实参个数不确定时,将形参初始化为元组或字典类型,且不定传参必须放在位置传参或默认传参的后面。注意实参的个数和形参的个数可以不一样。
*args
是一种约定俗成的写法,作用就是接收传递给函数的可变数量的非关键字参数,这些参数会被打包成元组,并且可以在函数体内通过args
来访问。名称中*
就是前面学习的解包符,作用就是解包元组,args
是默认名称可自定义更换。**kwargs
是一种约定俗成的写法,作用就是接收传递给函数的可变数量的关键字参数,这些参数被会打包成字典,并且可以在函数体内通过kwargs
来访问。名称中**
就是前面学习的解包符,作用就是解包字典,kwargs
是默认名称可自定义。
def foo(x, y=1, *args, **kwargs):
# 注释:形参*args, **kwargs带星号,输出args、kwargs不带星号。
print(x, y, args, kwargs)
foo() # 报错:形参x缺少实参赋值。
foo(1) # 输出:1 1 () {}。注释:实参1位置传参赋值给形参x,形参y没有实参赋值使用默认值1,args没有位置传参赋值为空元组,kwargs没有关键字传参赋值为空字典。
foo(1, 2) # 输出:1 2 () {}。注释:实参2位置传参赋值给形参y,不使用默认值。
foo(1, 2, 3) # 输出:1 2 (3,) {}。注释:实参3位置传参赋值给形参*args以元组的方式呈现。
foo(1, 2, 3, 4) # 输出:1 2 (3, 4) {}。注释:实参3、4位置传参赋值给形参*args以元组的方式呈现。
foo(1, 2, 3, 4, a=5) # 输出:1 2 (3, 4) {'a': 5}。注释:实参a=5关键字传参赋值给形参**kwargs以字典的方式呈现。
foo(1, 2, 3, 4, a=5, b=6) # 输出:1 2 (3, 4) {'a': 5, 'b': 6}。注释:实参a=5,b=6关键字传参赋值给形参**kwargs以字典的方式呈现。
提醒
在函数传参过程中还有一种方式,就是在形参中有一个单独的 *
号,它不接受任何实参,它的作用就是表示在 *
前面的形参必须通过位置传参接收实参,而在 *
后面的形参则必须通过关键字传参接收实参。由于这种传参方式用的不多,这里不过多介绍。
参数位置
在传参的过程中,一定要规范化参数的所在位置,否则会报错。
def func(b=2, a): # 报错:默认传参必须放在所有位置传参后面。
def func(a, b=2): # 注释:正确
def func(a, *args, b=2): # 报错:*args为不定传参,必须放在所有位置传参、关键字传参后面。
def func(a, b=2, *args): # 注释:正确
def func(a, b=2, **kwargs, *args): # 报错:**kwargs必须要写在*args后面。
def func(a, b=2, *args, **kwargs): # 注释:正确
返回值
函数在运行过程中或运行结束时返回的值被称之为返回值。特点如下:
- 任何函数都有一个默认的返回值
None
,可以通过关键字指定返回值。 - 除交互终端除外,调用函数时,返回值不会被输出,需要通过
print
输出。
def han():
print('开始', end='')
print(han()) # 输出:开始None。注释:首先执行han函数里面的print输出'开始',接着执行han函数外面的print输出han函数的返回值'None'。
关键字 return
:结束函数运行,并返回后面的值。使用方式如下:
- 关键字
return
必须配合在函数内部使用,不能独立使用或在函数外部使用。 - 关键字
return
会结束整个函数的运行,因此return
后面的代码不会执行。 - 关键字
return
在结束函数后,会返回后面的值(可以是空值、单个值或多个值),因此函数的最终结果就是return
后面的返回值。若返回空值则是None
;若返回单个值则就是其本身;若返回多个值则值与值之间必须用英文逗号,
分隔,最后返回类型是元祖。
# 函数返回值例一
def han():
print('开始', end='') # 注释:输出'开始'。
return # 注释:结束函数,返回None。
print(han()) # 输出:开始None。注释:先执行print里面的han函数输出'开始',接着执行return结束函数,由于后面是空,因此返回了None,最后print输出返回值'None'。
# 函数返回值例二
def han():
print('开始', end='') # 注释:输出'开始'。
return '结束' # 注释:结束函数,返回'结束'。
print(han()) # 输出:开始结束。注释:先执行print里面的han函数输出'开始',接着执行return结束函数并返回了后面的'结束',最后print输出返回值'结束'。
# 函数返回值例三
def han():
print('开始', end='') # 注释:输出'开始'。
return '结束', 123 # 注释:结束函数,返回元组('结束', 123)。
print(456) # 注释:上面的关键字return已经结束了函数,这行代码不会执行。
print(han()) # 输出:开始('结束', 123)。注释:首先执行han函数里面的print输出'开始',接着执行return结束函数并返回了后面的元组('结束', 123),最后执行han函数外面的print输出函数的返回值('结束', 123)。
h = han() # 输出:开始。注释:han()调用一次han函数输出'开始',返回了元组('结束', 123)并将其赋值给了变量h。
print(h[1] + 456) # 输出:579。注释:变量h的值为元组('结束', 123),因此h[1]+456就等于123+456,结果为579。
作用域
作用域:变量或函数在程序中可被引用的范围。
- 函数或类会开辟新的作用域,原因是函数和类有自己的栈结构。而
if
、for
、while
等语句没有自己的栈结构,因此也就不会开辟新的作用域。 - 内层作用域可以通过名称直接引用外层作用域的函数或变量,而外层作用域则需要通过关键字或返回对象才能引用内层作用域的函数或变量。
- 作用域是可引用范围,不是可被赋值定义范围。
- 不同作用域的函数体现为:嵌套函数
- 不同作用域的变量可以划为分为两类:全局变量、局部变量
嵌套函数
嵌套函数:在函数内部定义其他的函数。
嵌套特点:内层函数的作用域在定义时的同一层缩进范围内,在作用域内可以通过函数名称直接调用,在作用域外则需通过返回的函数对象进行调用。
# 嵌套函数例一
def han(): # 注释:定义han函数,开辟han函数的作用域。
print('开始', end='')
def han1(): # 注释:在han函数的作用域内定义han1函数,所以han1函数的作用域在han函数范围内。
print('结束')
han() # 输出:开始。注释:han函数内部定义了han1函数,但是没有在han函数内部调用,因此不会输出'结束'。
han1() # 报错:han1函数的作用域是han函数内,在han函数外han1函数是未定义的状态。
# 嵌套函数例二
def han(): # 注释:定义han函数,开辟han函数的作用域。
print('开始', end='')
han1() # 注释:在han函数的作用域引入未定义的han1函数
def han1(): # 注释:在han函数的作用域定义han1函数
print('结束')
han() # 报错:调用han函数执行到han1()调用han1函数,但han1函数还未被定义。
# 嵌套函数例三
def han(): # 注释:定义han函数,开辟han函数的作用域。
print('开始', end='')
def han1(): # 注释:在han函数的作用域定义han1函数
print('结束')
han1() # 注释:在han函数的作用域引入已定义的han1函数
han() # 输出:开始结束。注释:han函数内部定义了han1函数且后面在内部有调用han1函数,因此输出了'结束'。
# 嵌套函数例四
def han(): # 注释:定义han函数,开辟han函数的作用域。
print('开始', end='')
def han1(): # 注释:在han函数的作用域定义han1函数。
print('结束')
return han1 # 注释:返回han1函数对象。
han2 = han() # 输出:'开始'。注释:han()调用一次han函数,返回han1函数对象赋值给han2变量。
han2() # 输出:'结束'。注释:han2()就是调用han1函数对象。
全局变量
全局变量:从变量定义开始到文件代码末尾都可以被引用的变量。
a = 1 # 注释:变量a被赋值时无缩进,因此是全局变量。
if True:
print(a) # 输出:1。注释:if判断内可以引用全局变量a。
a += 1 # 注释:if判断不开辟新的作用域,全局变量a可以在if判断内被重新赋值定义。
print(a) # 输出:2
for x in range(1):
print(a) # 输出:2。注释:for循环内也可以引用全局变量a。
a += 1 # 注释:for循环不开辟新的作用域,因此全局变量a可以在for循环内被重新赋值定义。
print(a) # 输出:3
def one():
print(a) # 输出:3。注释:one函数首先查找函数内部的作用域是否存在局部变量a,如果没有找到则引用全局变量a。
b = a + 1 # 注释:引用全局变量a的值进行算术运算。
print(a, b) # 输出:3, 4
one()
def two(): # 注释:定义two函数,开辟two函数的作用域。
a += 1 # 报错:在two函数作用域里面对变量a进行赋值操作时,Python会认为a是局部变量,但在赋值操作前并未定义局部变量a,因此Python抛出了一个UnboundLocalError异常。
two()
局部变量
局部变量:从变量定义开始到作用域的末尾都可以被引用的变量。
a = 0 # 注释:变量a被赋值时无缩进,因此是全局变量。
b = 1 # 注释:变量b被赋值时无缩进,因此是全局变量。
def func():
a = 1 # 注释:变量a在func函数的作用域内赋值,因此是func函数内的局部变量,其作用域是func函数内,和上面的全局变量a类似于“同名不同人”。
print(a) # 输出:1。注释:这里输出变量a有一个分作用域查找的过程,首先查找func函数内的局部变量a,发现有就输出。
print(b) # 输出:1。注释:这里输出变量b有一个分作用域查找的过程,首先查找func函数内的局部变量b,发现没有,向外层查找,发现有全局变量b,进行输出。
func()
print(a) # 输出:0。注释:在func函数作用域外,因此是输出全局变量a。
print(b) # 输出:1。注释:在func函数作用域外,因此是输出全局变量b。
重要
Python 在函数内部查找变量时遵循 LEGB 顺序查找规则,即 Local 局部、Embeded 嵌套作用域、Global 全局作用域、Built-in 内置作用域。如果这些作用域都没有找到变量,就会报一个 NameError: name '变量名' is not defined
变量未定义的错误。
关键字 global
关键字 global
:指定在函数内部使用的全局变量。
- 在函数内部指定的全局变量,只代表着内部使用指定变量是全局变量,并不代表着声明该全局变量。如果外部没有该全局变量,并且在函数或类中没有对指定的全局变量进行赋值定义情况下,该全局变量就是未定义的状态。
def func1():
global a # 注释:定义在func1函数内操作的变量a是全局变量a。
print(a) # 报错:整个代码未定义全局变量a。
func1()
- 在外部没有同名全局变量的情况下,在函数或类中指定的全局变量,必须执行该函数或类进行赋值定义后才能成为可以全局使用的变量,否则变量就是未定义的状态。
def func1():
global a # 注释:定义在func1函数内操作的变量a是全局变量a。
a = 1 # 注释:对全局变量a进行赋值操作。
print(a)
def func2():
print(a) # 注释:由于函数fun2内部未定义变量a,因此这里输出的是全局变量a。
func2() # 报错:func2函数里输出的是全局变量a,然而未定义全局变量a。
print(a) # 报错:输出变量a前面无缩进,输出的是全局变量a,然而未定义全局变量a。
func1() # 输出:1。注释:调用func1函数定义了全局变量a。
print(a) # 输出:1。注释:在函数作用域外,输出的是全局变量a。
func2() # 输出:1。注释:func2函数里输出的变量a是全局变量。
- 外部定义的全局变量和在函数或类中指定的全局变量同名时,被认为是同一对象,在定义全局变量的函数或类中可以对其进行赋值定义。
a = 1 # 注释:变量a被赋值时无缩进,因此是全局变量。
def func1():
global a # 注释:定义在func1函数内操作的变量a是全局变量a,和上面全局变量a是同一对象。
a += 1 # 注释:对全局变量a进行赋值操作。
print(a) # 注释:输出全局变量a。
def func2():
print(a) # 注释:由于函数fun2内部未定义局部变量a,因此这里输出的是全局变量a。
func2() # 输出:1。注释:func2函数里输出的变量a是全局变量。
print(a) # 输出:1。注释:输出变量a前面无缩进,输出的是全局变量a。
func1() # 输出:2。注释:全局变量a在func1函数内被重新赋值。
print(a) # 输出:2。注释:输出变量a前面无缩进,输出的是全局变量a。
func2() # 输出:2。注释:func2函数里输出的是全局变量a。
关键字 nonlocal
关键字 nonlocal
:在嵌套函数中,指定内部函数使用的变量为外部函数的局部变量,而非自身内部的局部变量和全局变量。
# 例一
def func1(): # 注释:定义func1函数,开辟func1函数的作用域。
a = 1 # 注释:定义func1函数作用域内的局部变量a。
print(a) # 输出:1。注释:注释:print在func1作用域内,首先查找func1函数内的局部变量a,发现有进行输出。
def func2(): # 注释:定义func2函数,开辟func2函数的作用域。
print(a) # 输出:1。注释:print在func2作用域内,首先查找func2函数内的局部变量a,发现未定义,向外层查找,发现func1函数内的局部变量a,进行输出。
func2()
print(a) # 输出:1。注释:print在func1作用域内,首先func1函数内的局部变量a,发现有进行输出。
func1()
print(a) # 报错:输出变量a前面无缩进,输出全局变量a,然而未定义全局变量a。
# 例二
def func1(): # 注释:定义func1函数,开辟func1函数的作用域。
a = 1 # 注释:定义func1函数作用域内的局部变量a。
print(a) # 输出:1。注释:输出上面局部变量a。
def func2(): # 注释:定义func2函数,开辟func2函数的作用域。
a += 1 # 报错:在func2函数的作用域内对变量a进行赋值操作,就认为变量a是func2内的局部变量,然而func2内未定义局部变量a。
print(a)
func2() # 注释:调用func2函数。
print(a) # 注释:前面报错,走不到这里来。
func1()
print(a) # 报错:输出变量a前面无缩进,输出全局变量a,然而未定义全局变量a。
# 例三
def func1(): # 注释:定义func1函数,开辟func1函数的作用域。
a = 1 # 注释:定义func1函数作用域内的局部变量a。
print(a) # 输出:1。注释:输出上面局部变量a。
def func2(): # 注释:定义func2函数,开辟func2函数的作用域。
nonlocal a # 注释:指定在func2内操作的变量a均为外部函数func1的局部变量a。
a += 1 # 注释:对func1函数作用域内的局部变量a赋值。
print(a) # 输出:2。注释:输出func1函数作用域内的局部变量a。
func2()
print(a) # 输出:2。注释:输出func1函数作用域内的局部变量a。
func1() # 注释:调用func1函数。
print(a) # 报错:输出变量a前面无缩进,输出全局变量a,然而未定义全局变量a。
内置函数 globals
在 Python 中还内置了 globals
函数,它的作用就是返回的就是全局作用域中的内容。代码案例如下:
a = 0
b = 1
print(globals()) # 输出:{'__name__': '__main__', '__doc__': None..., 'a': 0, 'b': 1}
def func():
a = 1
print(globals()) # 输出:{'__name__': '__main__', '__doc__': None..., 'a': 0, 'b': 1}
func()
建议
内置的 globals
函数输出的是全局作用域内容,所以在不改变全局作用域内容的情况下,不管写在哪个位置,它所输出的内容都是一样的。
内置函数 locals
在 Python 中还内置了 locals
函数,它的作用就是返回当前作用域当中的内容,如果 locals
函数写在了全局作用域范围内,那么返回的就是全局作用域中的内容。代码案例如下:
a = 0
b = 1
print(globals()) # 输出:{'__name__': '__main__', ..., 'a': 0, 'b': 1}
print(locals()) # 输出:{'__name__': '__main__', ..., 'a': 0, 'b': 1}。注释:写在了全局作用域范围,返回的就是全局作用域中的内容。
def func():
a = 1
print(globals()) # 输出:{'__name__': '__main__', ..., 'a': 0, 'b': 1, 'func': <function func ...}
print(locals()) # 输出:{'a': 1}
func()
如果我们使用关键字 global
指定在函数内部使用的是全局变量,那么该变量就不会出现在函数内部的 locals
函数的输出中。代码案例如下:
a = 0
b = 1
print(globals()) # 输出:{'__name__': '__main__', ..., 'a': 0, 'b': 1}
print(locals()) # 输出:{'__name__': '__main__', ..., 'a': 0, 'b': 1}
def func():
global a
a = 1
print(globals()) # 输出:{'__name__': '__main__', ..., 'a': 1, 'b': 1, 'func': <function func ...}
print(locals()) # 输出:{}。注释:函数内部使用的是全局变量,该变量就不会出现在这里的的输出中。
func()