Python函数返回值、作用域、自由变量和闭包、默认值的作用域、函数销毁
函数返回值
举例:
1 | def f1(x): |
5
5
> 3
5
4
总结:
- Python函数使用return语句返回‘返回值’
- 所有函数多有返回值,如果没有return语句,就会隐式调用return None
- return 语句并不一定是函数的语句块的最后一条语句
- 一个函数可以存在多个return语句,但是最终只有一条可以被执行。如果没有一条return语句被执行,就会隐式调用 return None
如果有必要,可以显示调用 return None,也可以简写为 return
函数的返回值必须为一个对象,这个对象可以是数字,字符串、列表、元组、字典、函数等等,但是不能返回一个表达式
- 如果函数执行了return语句,函数就会返回,当前被执行的return语句之后的其他语句就不会被执行
- 函数返回值的作用: 结束函数调用、返回值
函数对象
当我们定义一个函数时,函数对象就像数学中的函数一样,具有其自身的属性
举例:
1 | def foo(a): |
<function foo at 0x00000136ECAB6D90>
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
函数属性简单概括:
- code 代码 有函数就有函数体,函数体也是函数的一个属性
- default 位置参数的默认值
- kwdefault 关键字参数的默认值
- name 函数自身的名字
- call 函数是否可以被调用
函数作用域
函数嵌套
- 在一个函数中定义另一个函数
1 | def outer(): |
outer
inner
- 函数有可见的范围,这就是作用域的概念
- 内部函数不能直接在外部使用,这会抛出NameError异常,因为它不可见
作用域
- 一个标识符的可见范围,就是这个标识符的作用域。一般就是常说的变量的作用域
- 举例
1 | x = 5 |
5
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-4-e6a3b9a7c9f5> in <module>
8
9 f1()
---> 10 2()
11
TypeError: 'int' object is not callable
全局作用域
- 在整个程序的运行环境中都是可见的
局部作用域
- 在函数、类的内部可见的
- 局部变量的使用范围不能超过其所在的局部作用域
举例:
1 | def outer1(): |
outer1:65,chr:A
inner:65,chr:A
********************
outer2:65,chr:A
inner:97,chr:a
从嵌套结构的例子看来:
- 外层变量的作用域可以向内层函数渗透
- 内层函数如果定义了一个变量名与外层函数相同的变量,外层函数的变量就不会传入内层
举例:
1 | x = 5 |
5
想要解决上面出现的问题,有两种解决方法
- 在函数前声明这个变量
如:
1 | x = 5 |
1
- 使用 global 关键字的变量,将函数内的x声明为使用外部的全局变量 x
1 | x = 5 |
6
global总结:
- x += 1 这种特殊形式产生的错误原因,先引用,后赋值,而Python动态语言是赋值才算定义,才能被引用
- 解决方法:在这条语句前声明增加 x = 0 之类的赋值语句,或直接使用 global告诉内部作用域 去全局作用域找这个变量
- 内部作用域使用x = 5之类的赋值语句会重新定义局部作用域使用的变量x,但是,一旦这个作用域中使用global声明x为全局的,那么x=5相当于在为全局作用域的变量x赋值
global是使用原则:
- 外部作用域变量会内部作用域可见,但也不要在这个内部的局部作用域中直接使用,因为函数的目的就是为了封装,尽量与外界隔离
- 如果函数需要使用外部全局变量,请使用函数的形参传参解决
- 一句话: 不用global。学习它就是为了深入理解变量作用域
闭包
自由变量: 未在本地作用域中定义的变量。例如嵌套函数中定义在内层函数外的外层函数中的变量
闭包: 只是一个概念,出现在嵌套函数中,指的是内层函数引用到外层函数的自由变量,就形成闭包
举例:
1 | def counter(): |
1 2
3
- 意是在嵌套函数中,内层函数的变量引用了外层函数的自由变量才叫闭包,所有说如果引用了全局变量,这不叫闭包。
- 闭包的作用,如经常用在棋子的走位,走下一步时,要记住上一步在什么位置,所以此时记录位置最好就不能定义在全局变量之中。
nonlocal 关键字
- 使用了nonlocal关键字,将变量标记为不在本地作用域定义,而在上级的某一级局部作用域中定义。
- 只能存在于嵌套函数的内层函数中,不能存在于普通函数,否则会报错
1 | def counter(): |
(1, 2)
- count 是外层函数的局部变量,被内层函数引用
- 内部函数使用nonlocal关键字声明count变量在上级作用域而非本地作用域中定义
默认值的作用域
默认值及变量保存的位置
举例:
1 | def foo(x=1,y=2,*args,z=3,**kwargs): |
1 2 3
(1, 2)
{'z': 3}
(None,)
('x', 'y', 'z', 'args', 'kwargs')
- 属性defaults中使用元组保存所有的位置参数默认值
- 属性kwdefaults中使用字典保存所有的位置参数默认值
- 属性code.co_varnames 中使用元组保存参数列表中的默认形参
- 默认值的作用域仅在本地有效,说明它是本地变量
- 使用可变类型作为默认值,因为是元组或者字典,就可能修改这个默认值
按需求定义默认值类型
- 影子拷贝默认值:
1 | def foo(xyz=[],u='abc',z=123): |
[1]
([], 'abc', 123)
[1]
([], 'abc', 123)
[10, 1]
([], 'abc', 123)
[10, 5, 1]
([], 'abc', 123)
采用影子拷贝创建一个新的对象,永远不能改变传入的参数
- 使用不可变类型创建list
1 | def foo(xyz=None,u='abc',z=123): |
[1]
(None, 'abc', 123)
[1]
(None, 'abc', 123)
[10, 1]
(None, 'abc', 123)
[10, 5, 1]
(None, 'abc', 123)
通过值的判断就可以灵活的选择创建或修改传入的对象
很多函数的定义,都可以看到None这个不可变的值作为默认参数
变量名解析原则LEGB
- Local,本地作用域、局部作用域的local命名空间。函数调用时创建,调用结束消亡
- Enclosing, Python2.2时引入了嵌套函数,实现了闭包,这个就是嵌套函数的外部函数的命名空间
- Global,全局作用域,即一个模块的命名空间。模块被import时创建,解释器退出时消亡
- Build-in,内置模块的命名空间,生命周期从python解释器启动时创建到解释器退出时消亡。例如 print(open), print和open都是内置的变量
- 所以一个变量名的查找顺序就是LEGB
销毁函数
全局函数销毁
1 | def foo(xyz=[],u='abc',z=123): |
[1] 2637469441832 ([1], 'abc', 123)
[1] 2637472253336 ([1], 'abc', 123)
全局函数的销毁函数
- 重新定义同名函数
- del语句删除函数对象
- 程序结束
局部函数
1 | def foo(xyz=[],u='abc',z=123): |
<function foo.<locals>.inner at 0x00000266159ED7B8>
<function foo.<locals>.inner at 0x00000266159ED268>
2637472650920 2637472649832 ([1], 'abc', 123) (100,)
局部函数的销毁函数
- 重新在上级作用域定义同名函数
- del语句删除函数名称,函数对象的引用计数减1
- 上级作用域销毁时