Python 函数之二(返回值、作用域、LEGB、销毁)
一、函数返回值
先看几个例子
# return 语句之后可以执行么?
def showplus(x):
print(x)
return x + 1
print('~~end~~')
showplus(5)
# 多条 return 语句都会执行么?
def showplus(x):
print(x)
return x + 1
return x + 2
showplus(5)
# 下例多个 return 可执行么?
def guess(x):
if x > 3:
return "> 3"
else:
return "<= 3"
print(guess(10))
# 下面函数执行的结果是什么
def fn(x):
for i in range(x):
if i > 3:
return i
else:
print("{} is not greater than 3".format(x))
print(fn(5)) # 打印 4
print(fn(3)) # 打印 None
总结
- Python 函数使用
return
语句返回 “返回值” - 所有函数都有返回值,若没有
return
语句,隐式调用return None
-
return
语句并不一定是函数的语句块的最后一条语句 - 一个函数可存在多个
return
语句,但只有一条可被执行,若没有一条returm
语句被执行,隐式调用return None
- 若有必要,可显示调用
return None
,可简写为return
- 若函数执行了
return
语句,函数就会返回,当前被执行的return
语句之后的其他语句就不会被执行了 - 返回值的作用:结束函数调用、返回 “返回值”
能够一次返回多个值么?
def showvalues():
return 1, 3, 5
showvalues()
- 函数不能同时返回多个值
-
return 1, 3, 5
看似返回多个值,隐式的被python
封装成了一个 元组 -
x, y, z = showlist()
使用解构提取返回值更为方便
二、函数 作用域
2.1 作用域
一个标识符的可见范围,这就是标识符的作用域,一般常说的是变量的作用域
def foo():
x = 100
print(x) # 可以访问到么?
上例中 x
不可被访问到,会抛出异常 NameError: name 'y' is not defined
,原因在于函数是一个封装,它会开辟一个 作用域,x
变量被限制在这个作用域中,所以在函数外部 x
变量 不可见
注意:每一个函数都会开辟一个作用域
2.2 作用域分类
- 全局作用域
- 在整个程序运行环境中都可见
- 全局作用域中的变量称为全局变量
- 局部作用域
- 在函数、类等内部可见
- 局部作用域中的变量称为局部变量,其使用范围不能超过其所在局部作用域
# 局部变量
def fn1():
x = 1 # 局部作用域,x 为局部变量,使用范围在 fn1 内
def fn2():
print(x)
print(x)
# 全局变量
x = 5 # 全局变量,也在函数外定义
def foo():
print(x)
foo()
- 一般来讲外部作用域变量在函数内部可见,可以使用
- 反过来,函数内部的局部变量,不能在函数外部看到
2.3 函数嵌套
在一个函数中定义了另一个函数
def outer():
def inner():
print("inner")
print("outer")
inner()
outer() # 可行么?
inner() # 可行么?
内部函数 inner
不能在外部直接使用,会抛 NameError
异常,因为它在函数外部不可见
其实, inner
不过就是一个标识符,就是一个函数 outer
内部定义的变量而已
嵌套结构作用域
对比下面嵌套结构,代码执行的效果
def outer1():
o = 65
def inner():
print("inner {}".format(o))
print(chr(o))
inner()
print("outer {}".format(o))
outer1()
def outer2():
o = 65
def inner():
o = 97
print("inner {}".format(o))
print(chr(o))
inner()
print("outer {}".format(o))
outer2()
从执行的结果来看
- 外层变量在内部作用域可见
- 内层作用域
inner
中,若定义了o = 97
,相当于在当前函数inner
作用域中 重新定义了一个新的变量o
,但 这个o
并不能覆盖外部作用域outer2
中的变量o
,只不过对于inner
函数来说,其只能可见自己的作用域中定义的变量o
了
2.4 一个复制语句的问题
再看下例
示例.png
仔细观察函数 2 返回的错误指向 x += 1
,原因是什么呢?
x = 5
def foo():
x += 1
foo() # 报错如下
示例.png
原因分析
-
x += 1
其实是x = x + 1
- 相当于在
foo
内部定义了一个局部变量x
,那么foo
内部所有x
都是这个局部变量x
了 -
x = x + 1
相当于使用了局部变量x
,但是这个x
还没有完成赋值,就被右边拿来做加 1 操作了
如何解决这个常见问题?
2.5 global
语句
x = 5
def foo():
global x # 全局变量
x += 1
print(x)
foo()
- 使用
global
关键字的变量,将foo
内的x
声明为使用 外部的全局作用域 中定义的x
- 全局作用域中必须有
x
的定义
若全局作用域中没有 x
定义会怎样?
注意,下面实验若在 ipython、jupyter 中做,上下文运行环境中有可能有 x
的定义,稍微不注意,就测试不出效果
# 有错么?
def foo():
global x
x += 1
print(x)
foo()
# 有错么?
def foo():
global x
x = 10
x += 1
print(x)
foo()
print(x) # 可以么?
使用 global
关键字定义的变量,虽然在 foo
函数中声明,但是这将告诉当前 foo
函数作用域,这个 x
变量将使用外部全局作用域中的 x
即使实在 foo
中又写了 x=10
,也不会在 foo
这个局部作用域中定义局部变量 x
了
使用了 global
,foo
中的 x
不再是局部变量了,它是全局变量
总结
-
x+=1
这种是特殊形式产生的错误的原因?先应用后赋值,而 Python 动态语言是赋值才算定义,才能被引用。解决办法,在这条语句前增加x=0
之类的赋值语句,或使用global
告诉内部作用域,去全局作用域查找变量定义 - 内部作用域使用
x = 10
之类的赋值语句会重新定义局部作用域使用的变量x
,但是,一旦这个作用域宠使用global
声明x
为全局的,那么x=5
相当于在为全局作用域的变量x
赋值
global
使用原则
- 外部作用域变量会在内部作用域可见,但也不要在这个内部的局部作用域中直接使用,因为函数的目的就是为了封装,尽量与外界隔离
- 若函数需要使用外部全局变量,请尽量使用函数的形参定义,并在调用传实参解决
- 一句话:不用
global
。学些它就是为了深入理解变量作用域
2.6 闭包
自由变量:未在本地作用域中定义的变量,例如定义在内层函数外的外层函数的作用域中的变量
闭包:就是一个概念,出现在嵌套函数中,至的是 内层函数引用到了外层函数的自由变量,就形成了闭包。
def counter():
c = [0]
def inc():
c[0] += 1
return c[0]
return inc
foo = counter()
print(foo(), foo())
c = 100
print(foo())
上例代码分析
- 第七行会执行
counter()
并 返回inc
对应的函数对象,注意这个函数对象并不释放,因为有foo
记住 - 第四行会报错么?
- 不会报错,因为修改的是
counter()
的本地变量c
中index
为 0 的元素的值,而不是重新定义c
变量
- 不会报错,因为修改的是
- 第八行打印什么结果?
- 打印
1 2
- 打印
- 第十行打印什么结果?
- 打印
3
- 第九行的
c
和counter
中的c
不一样,而inc()
引用的是自由变量counter()
中的变量c
- 打印
这是 Python 2 中实现闭包的方式,Python 3 还可使用 nonlocal
关键字
def counter():
count = 0
def inc():
count += 1
return count
return inc
foo = counter()
print(foo(), foo())
上例一定出错,使用 global
可解决
def counter():
global count
count = 0
def inc():
global count
count += 1
return count
return inc
foo = counter()
print(foo(), foo())
上例使用 global
解决,这是全局变量的实现,而不是闭包了
若对这个普通变量使用闭包,Python 3 中可使用 nonlocal
关键字
2.7 nonlocal
语句
nonlocal
:将变量标记为不在本地作用域定义,而是在 上级的某一级局部作用域 中定义,但 不能是全局作用域 中定义
def counter():
count = 0
def inc():
nonlocal count # 声明变量 count 不是本地变量
count += 1
return count
return inc
foo = counter()
print(foo(), foo())
count
是外层函数的局部变量,被内部函数引用
内部函数使用 nonlocal
关键字声明 count
变量在上级作用域而非本地作用域中定义
代码中内层函数应用外部局部作用域中的自由变量,形成闭包
上例是错误的,nonlocal
声明变量 a
不在当前作用域,但是往外就是全局作用域了,所以报错
三、默认值的作用域
示例.png示例.png
为何第二次调用 foo()
打印的值 [1, 1]
- 因为函数也是对象,每个函数定义被执行后,就生成一个函数对象和函数名这个标识符关联
- Python 把函数的默认值放在了函数对象的属性中,这个属性就伴随着这个函数对象的整个生命周期
- 查看
foo.__defaults__
属性为元祖
def foo(xyz=[], m=123, n='abc'):
xyz.append(1)
print(xyz)
print(id(foo), foo.__defaults__)
foo()
print(id(foo), foo.__defaults__)
foo()
print(id(foo), foo.__defaults__)
函数地址并没有变,就是说 foo()
这个函数对象没有变过,调用它,他的属性 __defaults__
中使用元祖保存默认值 xyz
的默认值为引用类型,引用类型的元素变动,并不是元祖的变化
# 非引用类型缺省值
def foo(xyz, m=123, n='abc'):
m = 456
n = 'def'
print(xyz)
print(foo.__defaults__)
foo('rookie')
print(foo.__defaults__)
属性 __defaults__
中使用元祖保存所有位置参数默认值,不会因为在函数体内改变了局部变量(形参)的值而发生改变
# keyword-only 参数的缺省值
def foo(xyz, m=123, *, n='abc', t=[1, 2]):
m = 456
n = 'def'
t.append(300)
print(xyz, m, n, t)
print(foo.__defaults__, foo.__kwdefaults__)
foo('rookie')
print(foo.__defaults__, foo.__kwdefaults__)
属性 __defaults__
中使用元组保存所有位置参数默认值
属性 __kwdefaults__
中使用字典保存所有 keyword-only
参数的默认值
def x(a=[]):
a += [5]
print(x.__defaults__)
x()
x()
print(x.__defaults__)
def y(a=[]):
a = a = [5]
print(y.__defaults__)
y()
y()
print(y.__defaults__)
输出结果不一致
-
+
表示两个列表合并并返回一个新列表 -
+=
表示就地修改一个列表,在其后追加一个列表。就是extend
方法
四、变量名解析原则 LEGB
- Local,本地作用域、局部作用域的
local
命名空间。函数调用时创建,调用结束消亡 - Enclosing,Python 2.2 时引入了嵌套函数,实现了闭包,这个就是嵌套函数的外部函数的命名空间
- Global,全局作用域,即一个模块的命名空间。模块被
import
时创建,解释器退出时消亡 - Build-in,内置模块的命名空间,生命周期从 Python 解释器启动时创建到解释器退出时消亡。例如
print(open)
,print
和open
都是内置的变量
所以一个名词的查找顺序就是 LEGB
示例.png
五、函数的销毁
- 定义一个函数就是生成一个函数对象,函数名指向的就是函数对象
- 可使用
del
语句删除函数,使其引用计数减一 - 可使用同名标识符覆盖原有定义,本质上也是使其引用计数减一
- Python 程序结束时,所有对象销毁
- 函数也是对象,也不例外,是否销毁,还是看引用计数是否减为 0