变量作用域
全局变量
- 标识符的作用域是定义为其声明在程序里的可应用范围,也就是变量的可见性
- 在一个模块中最高级别的变量有全局作用域
- 全局变量的一个特征是除非被删除掉,否则他们会存活到脚本运行结束,且对于所有的函数,他们的值都是可以被访问的
全局变量的使用
>>> x = 10 # 定义全局变量x
>>> def func1(): # 定义函数func1(),函数内部可以直接使用变量x
... print(x)
...
>>> func1() #调用函数func1(),结果为10
局部变量
- 局部变量只是暂时的存在,仅仅只依赖于定义他们的函数现阶段是否处于活动
- 当一个函数调用出现时,其局部变量就进入声明它们的作用域。在那一刻,一个新的局部变量名为那个对象创建了
- 一旦函数完成,框架被释放,变量将会离开作用域
局部变量只在函数内部起作用
>>> def func2(): #定义函数func2(), 其中的变量a为局部变量,只在函数内部有效
... a = 10
... print(a)
...
>>> def func3(): #定义函数func2(), 其中的变量a为局部变量,只在函数内部有效
... a = 'hello'
... print(a)
...
>>> func2() #调用函数func2(),结果为10
>>> func3() #调用函数func3(), 结果为hello
>>> a #查看a的值,没有被定义,函数内部的a为局部变量,只在该函数内部有效
如果局部变量与全局变量有相同的名称,那么函数运行时,局部变量的名称将会把全局变量的名称遮盖住
>>> x = 100 # 定义全局变量x
>>> def func5(): # 声明函数func5(), 函数内有局部变量x=200
... x = 200
... print(x)
...
>>> func5() # 局部变量
200
>>> x # 查看x【全局变量】,没有发生变化
100
global 语句
- 因为全局变量的名字能被局部变量给遮盖掉
- 为了明确地引用一个已命名的全局变量,必须使用 global 语句
>>> x = 100 #定义全局变量x
>>> def func6(): #定义函数func6()
... global x #引用全局变量x
... x = 200 #为全局变量x赋值为200
... print(x) #打印变量x的值
...
>>> func6() #调用函数func6()
>>> x
查找变量或函数的顺序
- 首先在函数的内部去查找
- 函数内部没有,然后去全局去查找,看是否定义
- 全局也没有,最后会去内建函数中查找
# 验证python查找变量或函数的顺序,定义函数func7(),统计字符'abcd'的长度
>>> def func7():
... print(len('abcd'))
...
>>> func7() #调用函数,结果为4,正确
>>> len #全局查看是否有len,没有,不存在
# 先在函数func7()内部查找方法len(),再在全局查找,最后在内建中查找len()
生成器
Python 使用生成器对延迟操作提供了支持。所谓延迟操作,是指在需要的时候才产生结果,而不是立即产生结果。这也是生成器的主要好处。
Python有两种不同的方式提供生成器:
-
生成器函数:
- 常规函数定义,但是,使用 yield 语句而不是 return 语句返回结果
- yield 语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行
# 生成器函数 # return: 函数执行的终止 # yield: 表示函数执行的暂停 def func02(): a = 1 yield a b = "hello" yield b c = [1, 2] yield c gen2 = func02() print("gen2:", gen2) # <generator object func02 at 0x7f94da357a98> print(gen2.__next__()) # 1 print(gen2.__next__()) # hello for item in gen2: print("for:", item) # [1, 2] # gen2.__next__() # 报错
-
生成器表达式:
- 类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表
# 列表推导式 import random list01 = [random.randint(1, 5) for i in range(5)] print(list01) # [1, 3, 4, 1, 2] # 生成器表达式 gen01 = (random.randint(1, 5) for i in range(5)) print(gen01) # <generator object <genexpr> at 0x7f18cc1c8830> # 生成器当中的元素只能获取一遍 # for 生成器.__next__() print("data:", gen01.__next__()) # 第一个数据 print("data:", gen01.__next__()) # 第二个数据 for item in gen01: print("for:", item) # print("data:", gen01.__next__()) # 报错:StopIteration
- 生成器的好处在于延迟操作,需要的时候再使用,所以会节省空间
sum([i for i in range(100000000)]) sum((i for i in range(100000000)))
-
注意事项
- 生成器的唯一注意事项就是:生成器只能遍历一次
- 自定义生成器函数的过程
- 在函数内部,有很多 yield 返回中间结果;
- 程序向函数取值时,当函数执行到第1个yield时,会暂停函数运行并返回中间结果;
- 当主程序再次调用函数时,函数会从上次暂停的位置继续运行,当遇到第2个yield,会再次暂停运行函数并返回数据;
- 重复以上操作,一直到函数内部的yield全部执行完成为止
练习 :文件生成器
需求:通过生成器完成以下功能
- 使用函数实现生成器 yield
- 函数接受一个文件对象作为参数(读文件)
- 生成器函数每次返回文件的 10 行数据
# 程序 = 数据结构(列表) + 算法
# 算法:
# 1. 读取文件,按行读(readline, readlines)
# 2. 读一行给列表添加一个元素(行)
# 3. 判断列表长度是否为10
# 是: 通过yield返回列表,清空列表 不是:接着读取文件
# 4. 文件读取完毕,再次判断列表是否为空,如果不为空,将剩余行数返回
def gen_file(fobj): # fobj: 文件管家,形式参数
lines = [] # 每次存储10行记录
for item in fobj.readlines(): # for item in fobj:
lines.append(item) # 读一行给列表添加一个元素
if len(lines) == 10:
yield lines # 通过yield返回列表
lines.clear() # 清空列表
if len(lines) != 0: # 判断列表是否为空
yield lines # 将剩余行数返回
if __name__ == '__main__':
fr = open("/etc/passwd", mode="r")
gen = gen_file(fr) # fobj = fr, gen: 生成器
for item in gen: # 通过for获取生成器中的元素
print(item)
print("============================")
fr.close()
# with打开文件的方式
# with open("/etc/passwd", mode="r") as fr:
# gen = gen_file(fr)
# for item in gen: # 通过for获取生成器中的元素
# print(item)
# print("============================")