生成器(选修)
生成器
在 Python 中,使用了 yield 的函数被称为生成器(generator)。
跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。
- 迭代器则通过 next 的 return 将值返回;
- 与迭代器不同的是,生成器会自动记录当前的状态, 而迭代器则需要进行额外的操作来记录当前的状态。
之前的 collatz 猜想,简单循环的实现如下:
collatz:
- 奇数 n:返回 3n + 1
- 偶数 n:返回 n / 2
- 直到 n 为 1 为止:
def collatz(n):
sequence = []
while n != 1:
if n % 2 == 0:
n /= 2
else:
n = 3 * n + 1
sequence.append(n)
return sequence
for x in collatz(5):
print(x)
生成器的版本如下:
def collatz(n):
while n != 1:
if n % 2 == 0:
n /= 2
else:
n = 3 * n + 1
yield n
for x in collatz(5):
print(x)
迭代器的版本如下:
class Collatz(object):
def __init__(self, start):
self.value = start
def __iter__(self):
return self
def next(self):
if self.value == 1:
raise StopIteration
elif self.value % 2 == 0:
self.value = self.value / 2
else:
self.value = 3 * self.value + 1
return self.value
for x in collatz(5):
print(x)
事实上,生成器也是一种迭代器:
x = collatz(5)
x
它支持 next 方法,返回下一个 yield 的值:
next(x)
next(x)
__iter__
方法返回的是它本身:
x.__iter__()
return和yield有什么区别?
yield 是暂停的意思(它有程序中起着类似红绿灯中等红灯的作用);yield是创建迭代器,可以用for来遍历,有点事件触发的意思
return 在方法中直接返回值;是函数返回值,当执行到return,后续的逻辑代码不在执行
相同点: 都是定义函数过程中返回值
不同点:yield是暂停函数,return是结束函数; 即yield返回值后继续执行函数体内代码,return返回值后不再执行函数体内代码。
yield返回的是一个迭代器(yield本身是生成器-生成器是用来生成迭代器的);return返回的是正常可迭代对象(list,set,dict等具有实际内存地址的存储对象)
如果要返回的数据是通过for等循环生成的迭代器类型数据(如列表、元组),return只能在循环外部一次性地返回,yeild则可以在循环内部逐个元素返回。
yiled from 还可以使一个生成器可以委派子生成器,建立双向通道
def g1(x):
yield range(x, 0, -1)
yield range(x)
print(list(g1(5)))
#[range(5, 0, -1), range(0, 5)]
def g2(x):
yield from range(x, 0, -1)
yield from range(x)
print(list(g2(5)))
#[5, 4, 3, 2, 1, 0, 1, 2, 3, 4]
迭代器和生成器有什么区别?
在 Python 中,使用了 yield 的函数被称为生成器(generator)。跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。
调用一个生成器函数,返回的是一个迭代器对象:迭代是Python最强大的功能之一,是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。迭代器有两个基本的方法:iter() 和 next()
__new__和 __init__的区别?
执行顺序的不同:只有在__new__返回一个cls的实例时后面的__init__才能被调用
功能上的不同:当创建一个新实例时调用__new__,初始化一个实例时用__init__
返回值的不同:__new__方法会返回一个创建的实例,而__init__什么都不返回
闭包
闭包可以用在许多地方。它的最大用处有两个:
1.可以读取函数内部的变量 2.让这些变量的值始终保存在内存中 3.单向访问,函数内可以访问,但是全局不能访问
闭包原理(命名空间与作用域)
命名空间
- 全局命名空间:创建的存储“变量名与值的关系”的空间叫做全局命名空间
- 局部命名空间:在函数的运行中开辟的临时的空间叫做局部命名空间
- 内置命名空间:内置命名空间中存放了python解释器为我们提供的名字:input,print,str,list,tuple...它们都是我们熟悉的,拿过来就可以用的方法。
三种命名空间之间的加载顺序和取值顺序:
- 加载顺序:内置(程序运行前加载)-->全局(从上到下顺序加载进来的)-->局部(调用的时候加载)--->内置
- 取值:在局部调用:局部命名空间--->全局命名空间--->内置命名空间
- 在全局范围找:全局----内置----局部 使用: 全局不能使用局部的,局部的可以使用全局的
作用域
作用域:就是作用范围,为了函数内的变量不会影响到全局。作用域分为两种:
- 全局作用域:全局命名空间与内置命名空间的名字都属于全局范围在整个文件的任意位置都能被引用,全局有效
- 局部作用域:局部命名空间,只能在局部范围内生效 站在全局看:使用名字的时候:如果全局有,用全局的。如果全局没有,用内置的。
- globals方法:查看全局作用域的名字【print(globals())】
- locals方法:查看局部作用域的名字【print(locals())】
<br>
下面看2个示例
闭包失败示例
name = 1 #变量在函数外部,inner可以访问,但是全局也能访问。直接闭包失败
def func():
def inner():
print(name)
print(inner.__closure__)
return name
return inner
p = func()
print(p())#输出的__closure__为None :不是闭包函数
print(name)
闭包成功示例
def func():
name = 1 #变量在函数内部,inner可以访问,但是全局不能访问。闭包成功!此时加上nonlocal
def inner():
nonlocal name
# nonlocal非局部声明变量 是python3.2的语法,简单说就是让内部函数中的变量在上一层函数中生效
# 非局部声明变量指代的已有标识符是最近外面函数的 已声明变量,但是不包括全局变量。这个是很重要的,因为绑定的默认行为是首先搜索本地命名空间。nonlocal声明的变量只对局部起作用,离开封装函数,那么该变量就无效。
name += 1
print(inner.__closure__)
return name
return inner
p = func()
print(p())
print(p())
print(p())
print(name)
单例模式
单例模式就是确保一个类只有一个实例.当你希望整个系统中,某个类只有一个实例时,单例模式就派上了用场.
比如,某个服务器的配置信息存在在一个文件中,客户端通过AppConfig类来读取配置文件的信息.如果程序的运行的过程中,很多地方都会用到配置文件信息,则就需要创建很多的AppConfig实例,这样就导致内存中有很多AppConfig对象的实例,造成资源的浪费.其实这个时候AppConfig我们 希望它只有一份,就可以使用单例模式.
使用闭包函数实现单例模式
def single(cls, *args, **kwargs):
instance = {}
def get_instance():
if cls not in instance:
instance[cls] = cls(*args, **kwargs)
return instance[cls]
return get_instance
@single
class Apple:
pass
# 测试新建2个不同实例,id是否一致
a = Apple()
print(id(a))
b = Apple()
print(id(b))