作用域
一个标识符的可见范围,这就是标识符的作用域。一般常说的是变量的作用域。
def foo():
x=100
print(x)# 可以访问到吗?
上例中的 x 不可以访问到,会抛出异常 (NameError: name 'x' is not defined),原因在于函数是一个封装,它会开辟一个作用域,x变量被限制在这个作用域,所以函数外部x变量不可见。
注意: 没一个函数都会开辟一个作用域
作用域分类
全局作用域
在整个程序运行环境中都可见
全局作用域中的变量称为全局变量
局部作用域
在函数、类 等内部可见
局部作用域中的变量称为局部变量,其使用范围不能超过其所在局部作用域
也称为本地作用域local
# 局部变量
def fn1():
x = 1 # 局部作用域,x为局部变量,使用范围在fn1内
def fn2():
print(x) # x能打印吗?可见吗?为什么?
print(x) # x能打印吗?可见吗?为什么?
# 全局变量
x = 5 # 全局变量,也在函数外定义
def foo():
print(x) # 可见吗?为什么?
foo()
函数嵌套
在一个函数中定义了另外一个函数
def outer():
def inner():
print("inner")
inner()
print("outer")
outer() # 可以吗?
inner() # 可以吗?
内部函数 inner 不能在外部直接使用,会抛出NameError异常,因为它在函数外部不可见
其实,inner 不过就是一个标识符,就是一个函数outer内部定义的变量而已。
嵌套解构的作用域
def outer1():
o = 65
def inner():
print('inner', o, chr(o))
inner()
print('outer', o, chr(o))
outer1() # 执行后,打印什么
def outer2():
o = 65
def inner():
o = 97
print('inner', o, chr(o))
inner()
print('outer', o, chr(o))
outer2() # 执行后,打印什么
从执行的结果来看:
外层变量在内部作用域可见
内层作用域inner中,如果定义了 o = 97 ,相当于在当前函数inner作用域中重新定义了一个新的 变量o,但是,这个o并不能覆盖掉外部作用域outer2中的变量o。只不过对于inner函数来说,其 只能可见自己作用域中定义的变量o了
一个赋值语句的问题
报错:
原因分析:
- x+=1 其实是x=x+1
- 只要 有 “x=” 出现,这就是赋值语句。相当于在foo内部定义一个局部变量x,那么foo内部所有x都输这个局部变量x了
- x=x+1 相当于使用了局部变量x,但是这个x还没有完成赋值,就被右边拿来做加1操作了
x = 5
def foo(): # 函数被解释器解释,foo指向函数对象,同时解释器会理解x是什么作用域
print(x) # x 在函数解析时就被解释器判定为局部变量
x += 1 # x = x + 1
foo() # 调用时
如何解决这个常见的问题?
golbal 语句
x=5
def foo():
gloabal x
x+=1
print(x)
foo()
使用global关键字的变量,将foo内部x声明为使用外部的全局作用域中的定义x
全局作用域中必须有x的定义
总结
- x+=1 这种是特殊形式产生的错误的原因?先引用后赋值,而python 动态语言是赋值才算定义,才能被引用。解决办法,在这条语句前增加x=0之类的赋值语句,或者使用global 告诉内部作用域,去全局作用域查找变量定义
-
内部作用域使用 x = 10 之类的赋值语句会重新定义局部作用域使用的变量 x ,但是,一旦这个作用域中使用 global 声明 x 为全局的,那么 x=5 相当于在为全局作用域的变量 x 赋值
global 使用原则
- 外部作用域变量会在内部作用域可见,但也不要在这个内部的局部作用域中直接使用,因为函数的目的就是为了封装,尽量与外界隔离
- 如果函数需要使用外部全局变量,请尽量使用函数的形参定义,并在调用传实参解决
- 一句话,不用global。
闭包
自由变量:未在本地作用域中定义的变量。例如定义在内层函数外的外层函数的作用域中的变量
闭包:就是一个概念,出现在嵌套函数中,指的是内层函数引用到了外层函数的自由变量,就形成了闭包。很多语言都有这个概念,最熟悉就是JavaScript。
def counter():
c = [0]
def inc():
c[0] += 1 # 报错吗? 为什么 # line 4
return c[0]
return inc
foo = counter() # line 8
print(foo(), foo()) # line 9
c = 100
print(foo()) # line 11
nonlocal 语句
nonlocal:将变量标记为不在本地作用域定义,而是在上级的某一级局部作用域中定义,但不能是全局作用域中定义。
def counter():
count=0
def inc():
nonlocal count
count += 1
return count
return inc
foo =counter()
count 是外层函数的局部变量,被内部函数引用。
内部函数使用nonlocal关键字声明 count 变量在上级作用域而非本地作用域中定义。
代码中内层函数引用外部局部作用域中的自由变量,形成闭包,
函数的销毁
定义一个函数就是生成一个函数对象,函数名指向的就是函数对象。
可以使用del语句删除函数,使其引用计数减1.
可以使用同名标识符覆盖原有定义,本质上也是使其引用计数减一
python 程序结束时,所有对象销毁。
函数也是对象,也不例外,是否销毁,还是看引用计数是否减为0.
变量名解析原则LEGB
- local,本地作用域、局部作用域的local命名空间。函数调用时创建,调用时结束消亡
- Enclosing , python2.2 时引入了嵌套函数,实现了闭包,这个就是嵌套函数的外部函数的命名空间
- Global,全局作用域,即一个模块的命名空间。模块被import时创建,解释器退出时消亡
- Build-in,内置模块的命名空间,生命周期从python解释器启动时创建到解释器退出时消亡。例如 print(open),print和open都是内置的变量
内建函数 | 函数签名 | 说明 |
iter | iter(iterable) | 把一个可迭代对象包装成迭代器 |
next | next(iterable[,default]) | 取迭代器下一个元素 如果已经取完,继续抛stopiteration异常 |
reversed | reversed(seq) | 返回一个翻转元素的迭代器 |
enumerate | enumerate(seq,start=0) | 迭代一个可迭代对象,返回一个迭代器,每一个元素都是数字和元素构成的二元组 |
迭代器
- 特殊的对象,一定是可迭代对象,具备可迭代对象的特征
- 通过iter方法把一个可迭代对象封装成迭代器
- 通过next方法,获取迭代器对象的一个元素
- 生成器对象,就是迭代器 对象。但是迭代器对象未必是生成器对象
可迭代对象
- 能够通过迭代一次次返回不同的元素的对象
- 所谓相同,不是指值是否相同,而是元素在容器中是否是同一个,例如列表中值是可以重复的,['a', 'a'],虽然这个列表有2个元素,值一样,但是两个'a'是不同的元素
- 可以迭代,但是未必有序,未必可以索引
- 可迭代对象有:list、tuple、string、bytes、bytearray、range、set、dict、生成器、迭代器等
- 可以使用成员操作符in、not in
- 对于线性数据结构,in本质上是在遍历对象,时间O(n)