函数是一个程序结构,它把一些语句聚集在一起。用于将复杂的流程细分为不同的组件,可以减少代码冗余、代码复用和代码修改成本。
函数可以有 0 个、1 个或多个参数。向函数传递参数可以控制函数的流程。函数也可以返回代码执行的结果。从技术上讲,任何函数都会返回结果,不返回值的函数会自动返回 None 对象。如果调用者需要函数返回结果,则需要显式使用return语句。
一、函数的定义
Python 使用 def 语句创建一个函数对象并将其分配给一个变量名。 ()里面是函数的参数,参数是赋值给函数对象的。参数传值。
def fun_name (arg1,age2...):
fun_body
return 语句表示函数调用的结束,并将结果传递给调用者。 return 语句是可选的,如果不存在,则函数将在控制流执行完函数体时结束。
1、def是可执行语句
def语句是实时执行的,不仅def语句,Python中的所有语句都是实时执行的,没有独立的编译时间。 def 语句是一个可执行语句。在def执行之前,函数是不存在的,直到执行def语句才创建函数。
2、函数是对象,函数名是变量
def语句是赋值语句,函数是对象,函数名是变量,def语句设置函数名和函数对象的引用次数。
当 def 语句运行时,它会创建一个新的函数对象并为其分配一个变量名。
3、参数通过赋值传递
Python通过赋值(=)将参数传递给函数,这与普通的赋值语句(a=1,或a=b)的行为是一样的。将常量传递给参数时,参数指的是一个全新的对象;将变量传递给参数时,参数和变量是对象的共享引用。
4、函数可以嵌套
def 是一个语句,它可以出现在语句可以出现的任何地方,甚至嵌套在其他语句中。也就是说函数的定义可以嵌套在函数内部,例如:
def times(x,y):
z=x*y
def print_result():
print(z)
print_result()
函数print_result()是一个嵌入式函数,定义在函数times(x,y)内。
其实Python的函数是有层次结构的,最外层的def是顶层函数,顶层函数内部定义的函数是嵌套函数。
5、显式指定参数类型和返回值
Python 支持动态类型,它在运行时确定变量的类型。也可以使用类型提示(type hints),在定义变量的时候显式定义变量的类型。
例如定义函数时,明确指定入参的类型为MyType,函数返回值的类型为str:
# 输入参数类型提示为MyType,函数返回类型提示为str
def foo(name: MyType) -> str:
return str(name)
二、函数调用
函数通过表达式调用,传入一些值,并返回结果。函数调用的格式为:函数名+(args),括号内为传递给参数的变量或值,例如:
func_name(var1,var2...)
如果函数存在,返回值,使用变量接收函数的返回值:
ret_value=fun_name(var1,var2...)
函数被调用时,函数的行为取决于类型,因为函数是语句的集合,语句中包含操作符,而操作符的行为是依赖于类型的,
例如函数times返回两个参数的乘积,传入一个数字时函数times(2,4)返回8,*的作用是计算乘积;当字符传入type,函数times('ab',2)返回'abab',*的作用是重复字符串。也就是说,函数times()的作用取决于值传递给它。
def times(x,y):
return x *y
调用函数时这种依赖于类型的行为称为多态,这意味着,操作的效果取决于它所操作的对象的类型。函数的多态性允许函数自动应用于所有类的对象类型。
如果传递给函数的对象具有预期的方法和表达式来操作该函数与该对象兼容。如果传递的对象不支持预期的接口,Python会在*表达式运行时检测到错误并自动抛出异常。
这个特性使得 Python 代码不应该关心特定的类型,函数应该为对象而不是数据类型编写接口。当然,这种多态编程模型意味着必须对代码进行测试以检测执行中的错误,而不是进行类型检查。
三、变量的范围
变量在 Python 代码中无处不在。命名空间是保存变量名的地方。变量名可以访问(可见)的命名空间称为角色域。
在程序中使用变量名时,Python 在命名空间中创建、更改或查找变量。分配变量名的位置决定了可以访问变量名的范围。
1、变量的分类
Python 中的变量是在第一次赋值时创建的,必须先赋值后才能使用。由于没有变量声明,Python 将变量名分配给 分配的位置与特定命名空间相关联(绑定为)。
根据变量的命名空间,变量大致分为三类:
2、变量Scope的作用
变量的作用域是指变量的可见作用域。变量的作用域总是由变量赋值的地方决定的,也就是变量赋值的地方决定了变量的可见范围。
默认情况下,函数中的所有变量名都与函数的命名空间相关联:
由于变量可以在三个地方赋值,所以变量的作用域实际上分为三类:
3、范围法则
所有变量名都可以概括为内置(builtin)、全局(global)和本地(local)。
内置模块是Python预定义的全局搜索声明是什么意思,可以直接引用。
一个模块定义了一个全局作用域,全局作用域的作用域仅限于单个模块(文件),即文件最顶层的变量名是针对文件内部的代码的。全球。
默认情况下,在函数内部,分配的变量名在本地范围内,除非声明为全局或非本地。该函数还定义了嵌套作用域,用于本地化其中使用的变量名称,以便函数内部使用的变量名称不会与函数外部的变量名称冲突。对函数的每次调用都会创建一个新的本地范围。如果需要在嵌套的 def 中为变量名赋值,从 Python 3.0 开始,可以使用 nonlocal 语句来完成。
注意:模块顶层的函数名是全局变量,函数内部的def定义了局部变量;函数的参数是局部变量;函数内的任何类型的赋值都会将变量分隔为 Local,这意味着函数内的赋值 (=) 语句、def 语句等定义了局部变量。
4、变量名解析(LEGB原理)
变量名的解析遵循LEGB原则。当引用一个变量时,Python 会按以下顺序搜索:从局部变量开始,在任何上层函数的范围内查找,在全局范围内查找,最后在内置范围内查找。
LEGB规则解析变量名的详细机制:
定义嵌套作用域为:当前def语句外,顶级def语句内的作用域,嵌套作用域的解析细节:
5、在函数中引用全局变量
global 不是声明类型,而是声明命名空间是全局的,即告诉函数它打算声明一个或多个全局变量名。
对于全局变量名,这里总结一下用法:
比如x是全局变量,在函数func中使用global来声明x是全局变量,并给x赋值,即修改全局变量的值:
x=11
def func():
global x
x=12
使用global语句将变量声明为全局变量,这样就可以在函数内部修改全局变量的值,也就是说,global语句允许修改def中的全局变量的值。
global 语句包含关键字 global 后跟一个或多个以逗号分隔的变量名。在函数中分配或引用时,所有列出的变量都映射到全局变量名称。
global x,y,z
6、引用函数中的上层非局部变量
Python3.0引入了nonlocal语句,用于在函数内声明一个non-local变量,该变量在def语句中定义,位于嵌套作用域的上层。
例如,函数 foo1 定义了变量 var1 和 var2。要在函数 foo2 中改变它们的值,必须在 foo2 中使用 nonlocal 语句将它们声明为非局部变量:
def foo1:
var1=1
var2=2
...
def foo2:
nonlocal var1,var2,..
nonlocal 语句是用于将函数内的变量声明为非局部变量的声明语句。非局部变量是指没有在本函数中定义,但在上层函数中定义的局部变量。
nonlocal语句的用法分析:
nonlocal 语句为嵌套函数提供了一种方法,可以为嵌套函数的后续调用提供可写状态信息,并且能够记住这些信息。简而言之,nonlocal 语句的引入允许 Python 允许对非局部变量进行修改。
四、封闭函数
Python 中的封闭函数是指在嵌套范围内记住变量值的函数,即使该范围已不存在。
例如,创建一个封闭的函数生成器,它生成并返回一个嵌套函数操作,但调用嵌套函数。
def maker(x):
def action(y):
return x*y
return action
调用封闭函数,得到的是对生成的内联函数的引用。当我们调用封闭函数时,它会返回封闭函数的引用;在调用封闭函数action时,我们发现虽然封闭函数已经返回退出,但是封闭函数记住了封闭函数内部变量x的值。
f=maker(2)
f(3)
也就是说,保留了有关封闭函数的本地范围的信息。为了能够在嵌套的 def 中使用变量 x 的值,Python 会自动记住所需上层作用域的任何值。
注意:如果函数中定义了lambda或def,嵌套在循环中,而嵌套函数引用了上层作用域的变量,则该变量被循环改变,所有在this循环中产生的函数都会具有相同的值 - 最后一个循环完成时引用的变量的值。
>>> def maker():
... acts=[]
... for i in range(5):
... acts.append(lambda x:i**x)
... return acts
...
>>> acts=maker()
>>> acts[0](2)
16
因为在调用嵌套函数时会检查嵌套范围内的变量全局搜索声明是什么意思,所以它们实际上记住了相同的值(上次循环迭代中循环变量的值)。
也就是说,只有在调用acts[0](2)时,才检查变量i的值,变量i的值是上次迭代的值4。要解决此类问题,必须在调用函数生成器时计算并保存 i 的值。
>>> def maker():
... acts=[]
... for i in range(5):
... acts.append(lambda x, i=i : i**x)
... return acts
为了使这种类型的代码工作,必须使用默认参数将当前值传递给嵌套范围内的变量。因为默认参数是在创建嵌套函数时计算的(而不是在以后调用它时),所以每个函数都会记住自己的变量 i 值。