函数是把一些语句集合在一起的程序结构, 用于把复杂的流程细分成不同的组件, 能够减少代码的冗余, 代码的复用和修改代码的代价.
函数可以 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 是顶层函数, 在顶层函数内部定义的函数是嵌套函数.
二, 函数的调用
函数通过表达式调用, 传入一些值, 并返回结果. 函数调用的格式是: 函数名 + (args), 小括号中是传递给参数的变量或值, 例如:
func_name(var1,var2...)
如果函数存在返回值, 使用变量来接收函数的返回值:
ret_value=fun_name(var1,var2...)
在调用函数时, 函数的行为依赖于类型, 这是因为函数是语句的集合, 语句包含操作符, 而操作符的行为是依赖于类型的,
例如, 函数 times 返回两个参数的乘积, 当传入数字时, 函数 times(2,4) 返回 8,* 的作用是计算乘积; 当传入字符类型时, 函数 times('ab',2) 返回'abab',* 号的作用是重复字符串. 换句话说, 函数 times()的作用取决于传递给它的值.
- def times(x,y):
- return x *y
调用函数时, 这种依赖于类型的行为称为多态, 就是说, 一个操作的作用取决于操作对象的类型. 函数的多态性, 使得函数可以自动适用于所有类别的对象类型.
如果传给函数的对象有预期的方法和表达式操作符, 那么函数就兼容对象. 如果传递的对象不支持预期的接口, Python 会在 * 表达式运行时检测到错误, 并自动抛出一个异常.
这种特性, 使得 Python 代码不应该关心特定的类型, 函数应该为对象编写接口, 而不是数据类型. 当然, 这种多态的编程模型意味着: 必须测试代码去检测执行结果是否错误, 而不是编写代码进行类型检查.
三, 变量的作用域
在 Python 代码中变量无处不在, 命名空间就是保存变量名的地方, 变量名能够访问 (可见) 的命名空间叫做作用域.
当在程序中使用变量名时, Python 创建, 改变或者查找变量都是在命名空间中进行的, 变量名被赋值的位置决定了变量名能够被访问的范围.
1, 变量的分类
Python 中的变量在第一次被赋值时创建, 并且必须经过赋值后才能使用. 由于没有变量的声明, Python 把变量名被赋值的地点关联为 (绑定为) 一个特定的命名空间.
根据变量的命名空间, 把变量大致分为三类:
模块是全局命名空间, 其名称是模块文件的名称, 位于模块内的顶层函数名和变量名叫做全局变量.
函数是局部命名空间, 其名称是函数的名称, 位于函数内的函数名和变量名叫做本地变量.
由于一个函数可以嵌套在其他函数内, 这使得函数的定义具有层次. 我们把一个不在本函数内定义的, 而是在上层函数中定义的本地变量叫做非本地变量, 也就是, 这个变量不是当前函数的本地变量, 而是上层函数的本地变量.
2, 变量的作用域
变量的作用域是指变量可见的范围, 一个变量的作用域总是由变量被赋值的地方决定的, 也就是说, 变量被赋值的地方决定了变量可见的范围.
如果一个变量赋值的地点是在 def 之内, 那么该变量是本地变量, 作用域在 def 之内, 在 def 之外, 本地变量是不可见的.
如果一个变量赋值的地点是在 def 之外, 那么该变量是全局变量, 作用域是全局的, 在函数内可以引用全局变量.
在默认情况下, 一个函数的所有变量名都是与函数的命名空间相关联的:
一个在 def 内定义的变量名只能被 def 的代码使用, 不能在函数的外部调用该变量名;
def 中的变量名与 def 之外的变量名并不冲突, 一个在 def 之外被赋值的变量 x 和在 def 中被赋值的变量 x 是不同的变量.
由于变量可以在三个地方分配, 那么变量的作用域实际上分为三类:
如果一个变量是在 def 之内赋值, 变量可见范围是在函数内, 变量的作用域是本地(local);
如果一个变量是在 def 之内赋值, 对该函数中嵌套的函数来说, 该变量的作用域是非本地的 (nonlocal), 或称作嵌套(enclosed) 作用域;
如果一个变量是在 def 之外赋值, 变量可见的范围是整个模块, 变量的作用域是全局(global).
3, 作用域法则
所有变量名都可以归纳为内置的 (builtin), 全局的(global) 和本地的(local).
内置的模块是 Python 预先定义好, 可以直接引用的.
模块定义的是全局作用域, 全局作用域的作用范围仅限于单个模块(文件), 也就是说, 在一个文件的顶层的变量名对于这个文件内部的代码而言是全局的.
在默认情况下, 在函数内部, 赋值的变量名除非声明为全局变量或非本地变量之外, 都是本地作用域内的. 函数还定义了嵌套的作用域, 使其内部使用的变量名本地化, 以便函数内部使用的变量名不会与函数外的变量名冲突. 每次对函数的调用都会创建一个新的本地作用域. 如果需要给一个嵌套的 def 中的变量名赋值, 从 Python 3.0 开始, 可以使用 nonlocal 语句声明来做到.
注意: 模块顶层的函数名是全局变量, 函数内部的 def 定义的是局部变量; 函数的参数是本地变量; 一个函数内部的任何类型的赋值都会把一个变量划定为本地的, 这意味着, 函数内部的赋值 (=) 语句, def 语句等, 定义的都是本地变量.
4, 变量名解析(LEGB 原则)
变量名的解析遵从 LEGB 原则, 当引用一个变量时, Python 按照以下顺序依次进行查找: 从本地变量中, 在任意上层函数的作用域, 在全局作用域, 最后在内置作用域中查找.
LEGB 法则解析变量名的详细机制:
当在函数中引用变量名时, Python 依次搜索 4 个作用域: 本地作用域(L), 然后是上一层结构中 def 的本地作用域(E), 再然后是全局作用域(G), 最后是内置作用域(B), 并且再第一处能够找到该变量名的地方停下来. 如果变量名再这次搜索中没有找到, Python 会报错.
当在函数中给一个变量名赋值时 (而不是在一个表达式中对其进行引用),Python 总是创建或改变本地作用域的变量名, 除非它已经在当前函数中声明为全局变量(global) 或者非本地变量(nonlocal).
把嵌套作用域定义为: 在当前的 def 语句之外, 在顶层 def 语句之内的作用域, 嵌套作用域的解析细节:
对变量 x 的引用, 首先在当前函数内查找变量名 x; 之后会向上层的函数中查找变量名 x, 从内向外依次查找嵌套作用域; 之后查找当前的全局作用域 (模块); 最后再到内置作用域内查找. 而全局声明将会直接从全局作用域(模块) 进行搜索.
对变量 x 的赋值, 如果变量 x 在函数内部声明为全局变量(global x), 那么赋值会修改全局变量 x 的值; 如果变量 x 在函数内被声明为非本地变量(nonlocal x), 那么赋值会修改最近的嵌套函数的本地作用域内的变量 x 的值.
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, 在函数内引用上层的非本地变量
Pytho 3.0 引入了 nonlocal 语句, 用于在一个函数内声明一个非本地的变量, 该变量定义于一个 def 语句中, 并且位于嵌套作用域的上层.
例如, 函数 foo1 定义了变量 var1 和 var2, 要想在函数 foo2 中改变它们的值, 必须在 foo2 中使用 nonlocal 语句把它们声明为非本地变量:
- def foo1:
- var1=1
- var2=2
- ...
- def foo2:
- nonlocal var1,var2,..
nonlocal 语句是一个声明语句, 用于把函数内的变量声明为非本地变量. 非本地变量是指不在本函数内定义的, 而是在上层函数中定义的本地变量.
nonlocal 语句的用法解析:
nonlocal 语句完全忽略当前函数的本地作用域, 这意味着, nonlocal 语句使得对该语句列出的名称的查找从上层函数的作用域开始, 而不是从语句声明的本地作用域开始.
nonlocal 语句列出的名称, 必须在一个嵌套的 def 中提前定义过, 否则, Python 将会产生一个错误, 也就是说, nonlocal 语句声明的变量只能是 def 中定义的本地变量, 而不能是模块的全局变量.
nonlocal 语句允许对非本地变量赋值, 修改其值.
在内嵌的函数中, 可以直接引用非本地变量, 不需要使用 nonlocal 语句声明.
nonlocal 语句提供了一种方式, 使得嵌套的函数能够提供可写的状态信息, 以便在随后调用嵌套的函数时, 能够记住这些信息. 简而言之, nonlocal 语句的引入使得 Python 允许修改非本地变量.
四, 闭合函数
Python 的闭合函数是指一个能够记住嵌套作用域的变量值的函数, 尽管那个作用域已经不存在了.
例如, 创建一个闭合函数 maker, 该函数生成并返回一个嵌套函数 action, 却并调用这个内嵌的函数.
- def maker(x):
- def action(y):
- return x*y
- return action
调用闭合函数, 得到的是生成的内嵌函数的一个引用. 当我们调用闭合函数, 它会返回内嵌函数的引用; 当调用内嵌函数 action 时, 我们发现尽管闭合函数已经返回并退出, 但是, 内嵌函数记住了闭合函数内部的变量 x 的值.
f=maker(2) f(3)
也就是说, 闭合函数的本地作用域的信息被保留了下来. 为了能够在内嵌的 def 中使用变量 x 的值, Python 自动记住了所需要的上层作用域的任意值.
注意: 如果 lambda 或者 def 在函数中定义, 嵌套在一个循环之中, 并且嵌套的函数引用了一个上层作用域的变量, 该变量被循环所改变, 所有在这个循环中产生的函数将会由相同的值 -- 在最后一次循环中完成时被引用变量的值.
- >>> 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. 要解决这类问题, 必须在函数 maker 调用时, 对 i 的值进行评估, 并保存起来.
- >>> def maker():
- ... acts=[]
- ... for i in range(5):
- ... acts.append(lambda x, i=i : i**x)
- ... return acts
为了让这类代码能够工作, 必须使用默认参数把当前的值传递给嵌套作用域的变量. 因为默认参数是在嵌套函数创建时评估的(而不是其稍后调用时), 所以, 每一个函数都记住了自己的变量 i 的值.
参考文档:
来源: https://www.cnblogs.com/ljhdo/p/10104834.html