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).需要调用的方法()