1.生成器
使用生成器在迭代过程中,计算下一个值
生成器是一个特殊的迭代器,每个数据是被计算出来再返回的
1.1 简单生成器
生成器是依靠 yield 关键字实现的
# 生成num个数字的斐波那契数列
def func(num):
a = 0
b = 1
n = 0
print("外")
while n < num:
print("内yield前")
yield
n += 1
# 这样赋值不会影响,会同时计算
a, b = b, a+b
print(a)
print("内yield后")
g = func(10)
next(g)
next(g)
next(g)
out:
外
内yield前
1
内yield后
内yield前
1
内yield后
内yield前
yield 关键字会让代码停止在那一块。以此实现生成器。
生成器是迭代器,不是可迭代对象,所以要用next()方法调用
生成器执行顺序:
def func():
print("开始")
yield
print("中间")
yield "返回值"
print("最后")
g = func() # 调用生成器函数生成生成器的时候,不会执行函数内部方法
next(g) # 第一个next()是激活生成器的,也可以理解为执行代码,遇到第一个yield停止。
ret = next(g) # 遇到下一个yield停止
print(ret)
next(g) # 遇到下一个yield停止,没有抛出异常
out:
开始
中间
返回值
最后
Traceback (most recent call last):
File "D:/PyCharm Workspace/Python进阶/test.py", line 13, in <module>
next(g) # 遇到下一个yield停止,没有抛出异常
StopIteration
1.2 生成器表达式
以最简单的语出创建一个生成器
类比列表表达式:
l = [i for i in range(1,6)]
print(l)
out:
[1, 2, 3, 4, 5]
生成器表达式就是把上述表达式的中括号换成小括号
g = (i for i in range(1,6))
print(g)
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
out:
<generator object <genexpr> at 0x000001EDE230A408>
1
2
3
4
5
Traceback (most recent call last):
File "D:/PyCharm Workspace/Python进阶/test.py", line 10, in <module>
print(next(g))
StopIteration
生成器是为协程做准备的
1.3 嵌套生成器
# 生成1-9的数字
g1 = (i for i in range(1, 10))
# 生成1-9的数字的平方
g2 = (i*i for i in g1)
print(next(g2))
print(next(g2))
print(next(g2))
print(next(g2))
print(next(g2))
print(next(g2))
print(next(g2))
print(next(g2))
print(next(g2))
out:
1
4
9
16
25
36
49
64
81
1.4 增强生成器
1.4.1 生成器的.send()方法
.send()是生成器的方法,next()是 Python 内置的方法。
调用.send()方法,第一次激活的时候必须传入 None,常用next()激活,然后用send()传参
之后可以传入参数,在yield前面接受
def func():
print("开始")
num = yield "激活返回值"
print(num)
print("执行")
yield "返回值"
print("结束")
g = func()
try:
# print(next(g))
print(g.send(None))
# 第二次调用生成器的时候,开始可以传入数据,传入到当前yield,在yield前面接受
print(g.send(1))
print(g.send(2))
except StopIteration:
print("Stop")
out:
开始
激活返回值
1
执行
返回值
结束
Stop
1.4.2 查看生成器的状态
使用inspect模块下的getgeneratorstate方法。
| 状态码 | 含义 |
|---|---|
| GEN_CREATED | 等待开始执行 |
| GEN_RUNNING | 正在被解释器执行,等待遇到 yield(多线程才能观察到) |
| GEN_SUSPENDED | 在 yield 处暂停 |
| GEN_CLOSED | 执行结束 |
import inspect
def func():
print("开始")
num = yield "激活返回值"
print(num)
print("执行")
yield "返回值"
print("结束")
g = func()
try:
# print(next(g))
print(inspect.getgeneratorstate(g))
print(g.send(None))
print(inspect.getgeneratorstate(g))
# 第二次调用生成器的时候,开始可以传入数据,传入到当前yield,在yield前面接受
print(g.send(1))
print(inspect.getgeneratorstate(g))
print(g.send(2))
except StopIteration:
print("Stop")
print(inspect.getgeneratorstate(g))
out:
GEN_CREATED
开始
激活返回值
GEN_SUSPENDED
1
执行
返回值
GEN_SUSPENDED
结束
Stop
GEN_CLOSED
1.5 yield from
1.5.1 进行简单迭代
yield from 可以迭代数据:
# 字符串
astr='ABC'
# 列表
alist=[1,2,3]
# 字典
adict={"name":"wangbm","age":18}
# 生成器
agen=(i for i in range(4,8))
# 方法1
l = []
for i in (astr,alist,adict,agen):
for j in i:
l.append(j)
print(l)
# 方法2
def func(astr,alist,adict,agen):
for i in (astr, alist, adict, agen):
for j in i:
yield j
agen=(i for i in range(4,8)) #重置
g = func(astr,alist,adict,agen)
print(g)
print(list(g))
# 方法3
def func(astr,alist,adict,agen):
for i in (astr, alist, adict, agen):
yield from i
agen=(i for i in range(4,8)) #重置
g = func(astr,alist,adict,agen)
print(g)
print(list(g))
out:
['A', 'B', 'C', 1, 2, 3, 'name', 'age', 4, 5, 6, 7]
<generator object func at 0x000001E48760A4F8>
['A', 'B', 'C', 1, 2, 3, 'name', 'age', 4, 5, 6, 7]
<generator object func at 0x000001E48760A480>
['A', 'B', 'C', 1, 2, 3, 'name', 'age', 4, 5, 6, 7]
1.5.2 进阶
求send()进来的所有数的平均值
首先实现功能:
def func():
average = 0
count = 0
total = 0
while True:
number = yield average
count += 1
total += number
average = total / count
g = func()
next(g)
print(g.send(10))
print(g.send(20))
print(g.send(30))
out:
10.0
15.0
20.0
yield from 可以理解为一个管道
用来连接调用和委托生成器,可以帮助捕获异常
def func():
average = 0
count = 0
total = 0
while True:
number = yield average
count += 1
total += number
average = total / count
if average > 20:
break
return average,count,total
# 委托给func2生成器
def func2():
while True:
ret = yield from func()
print("func2 1111")
print(ret)
g = func2()
next(g)
print(g.send(10))
print(g.send(20))
print(g.send(30))
print(g.send(40))
print(g.send(50))
out:
10.0
15.0
20.0
func2 1111
(25.0, 4, 100)
0
func2 1111
(50.0, 1, 50)
0
2.魔法方法
2.1__getattribute__()
实例对象属性查找顺序:
__getattribute__()、实例对象、类、父类
__getattribute__()可以拦截属性的查询
一般里面写super().__getattribute__(item)
2.2__getattr__()
在上面的顺序中,最后调用的__getattr__(),如果没有会返回 None,防止报错(防止访问不存在的属性)
2.3__setattr__()
创建(实例化)一个新属性的时候调用
可以做一些处理。
2.4__delattr__()
删除实例对象的时候调用
调用父类 super 的或者直接使用del self.__dict__[key]
3.描述符
**描述符协议:**实现了__get__()、__set__()、__delete__()其中至少一个方法的类,就是一个描述符。
是用来描述实例属性的(限制)
以前可以使用 Property 属性来限制。
用法:
class Student:
def __init__(self, name, math, chinese, english):
self.name = name
self.math = math
self.chinese = chinese
self.english = english
@property
def math(self):
return self._math
@math.setter
def math(self, value):
if 0 <= value <= 100:
self._math = value
else:
raise ValueError("Valid value must be in [0, 100]")
3.1 使用描述符进行属性限制
__get__:用于访问属性。它返回属性的值,若属性不存在、不合法等都可以抛出对应的异常。__set__:将在属性分配操作中调用。不会返回任何内容。__delete__:控制删除操作。不会返回内容。
描述符是一个类,用描述符来进行类型判断(或范围判断)
class Type:
def __init__(self, key, exceptionClass):
self._key = key
self._exceptionClass = exceptionClass
def __set__(self, instance, value):
print("set")
if not isinstance(value, self._exceptionClass):
raise TypeError('参数类型错误')
self._value = value
instance.__dict__[self._key] = value
def __get__(self, instance, owner):
print("get")
return instance.__dict__[self._key]
def __delete__(self, instance):
print("delete")
del instance.__dict__[self._key]
class Student:
name = Type("name",str)
math = Type("math",int)
chinese = Type("math",float)
english = Type("math",int)
def __init__(self, name, math, chinese, english):
self.name = name
self.math = math
self.chinese = chinese
self.english = english
def __repr__(self):
return "<Student: {}, math:{}, chinese: {}, english:{}>".format(self.name, self.math, self.chinese, self.english)
zs = Student("张三", 100, 100.0, 100)
print(zs)
print(zs.name)
print(zs.chinese)
# delattr(zs,"chinese")
del zs.chinese
print(zs.chinese)
描述符里的方法的优先级都相当高。
除了 get 方法。
3.2 构造自定义容器
需要存储数据,查询数据,修改数据,删除数据操作;
并且想实现创建类似于序列和映射的类,可以称之为容器。
可以通过重写魔法方法__getitem__、__setitem__、__delitem__、__len__来实现。
就是自定义一个数据结构的样子。
课堂作业:
- 不会报错
- 容器最大存储量为 10
- 在存储数据到最大的时候会将最开始存储的数据删除掉,然后把新的数据存进去
class C:
def __init__(self):
self.start = 0
self.end = 0
self.maxLen = 10
self.dataList = []
def __getitem__(self, item):
try:
return self.dataList[item]
except Exception as e:
print(e.__repr__())
def __setitem__(self, key, value):
try:
if key < 0 or key > len(self.dataList):
raise IndexError
elif key >= 0 and key < len(self.dataList):
self.dataList[key] = value
elif len(self.dataList) < self.maxLen:
self.dataList.append(value)
else:
self.dataList = self.dataList[1:]
self.dataList.append(value)
except Exception as e:
print(e.__repr__())
def __delitem__(self, key):
pass
def __len__(self):
return len(self.dataList)
def __str__(self):
return str(self.dataList)
c = C()
for i in range(10):
c[i] = i
print(c)
c[10] = 10
print(c)
c[200] = 10
print(c[100])
out:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
IndexError()
IndexError('list index out of range')
None
4.上下文管理
帮助使用者完成一些代码
包括开始和结束的操作
使用者做执行的操作
一些常用的上下文管理的语句:
with …… as ……
结合起来就是上下文管理器
4.1 自定义简单的上下文管理器
class Resources():
def __enter__(self):
print("进入")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("离开")
def operate(self):
print('执行')
with Resources() as res:
res.operate()
res.operate()
print("执行结束")
print("结束")
out:
进入
执行
执行
执行结束
离开
结束
4.2 使用contextlib
定义一个类比较麻烦,可以使用contextlib来代替简单的操作类,使用 yield 生成器来制作。
返回的对象在 yield 后面。
import contextlib
@contextlib.contextmanager
def open_file(filename,mode):
print("上文管理")
f = open(filename,mode)
try:
yield f
print("下文管理")
except Exception as e:
print(e)
finally:
f.close()
with open_file("demo.txt","r") as f:
text = f.read()
print(text)
print("全部执行结束")
out:
上文管理
123,test
下文管理
全部执行结束
5.比较运算符
如果需要进行<=,>=之类的比较,需要实现 eq、lt、le、gt、ge 中的 eq
加上另外任意一个即可。(需要借助functools.total_ordering),如果不借助需要全部实现,这个会帮我们自动填装剩余的方法。
from functools import total_ordering
class House:
def __init__(self, name, area):
self.name = name
self.area = area
r1 = House("room1", "10000")
r2 = House("room2", "1000")
@total_ordering
class House:
def __init__(self):
self.rooms = []
def __str__(self):
return str(self.rooms)
def add_room(self, room):
self.rooms.append(room)
def get_area(self):
area = sum([int(i.area) for i in self.rooms])
return area
# __eq__ + 四种比较重的其中一种
def __eq__(self, other):
return self.get_area() == other.get_area()
def __le__(self, other):
return self.get_area() <= other.get_area()
h1 = House()
h1.add_room(r1)
h2 = House()
h2.add_room(r2)
print(h1 <= h2)
out:
False
6.str 和 repr
类里面会有__str__==__repr__,所以优先实现repr方法,调用print的时候,会优先调用str方法,如果没有就会调用repr,但是反之不会。
str是给人看的,
repr是给电脑看的,返回值一般是一段可以执行的代码
至少要实现一个repr来保证能print
7.call 魔法方法
让类的实例对象可以像函数一样去调用。
8.多继承
继承的时候,从左到右写,顺序就是从左到右
在__mro__属性里面可以查看。
在使用 super(上一级)方法的时候,不过不写参数。就从上一级开始执行。但是自己类内部的代码一定会执行。
如果需要使用指定类的方法,那么就要这么写:
super(指定类的子类,self).需要调用的方法()