python元类及迭代生成器
__getattr__和 __getattribute__函数
概念
- __getattr__是当类调用一个不存在的属性是自动调用的魔法函数,其中self是类本身,item是不存在的属性名
- __getattribute__是不论何时都优先调用,所以除非特殊情况,最好不要使用
实例
- 下面通过一个简单的实例来展示这两个魔法函数的区别,首先是
通过上述结果,我们可以看出只有在调用name这个不存在的属性时,魔法函数才被调用 - 接下来我们看一下使用__getattribute__的情况
从结果可以看出,无论类是否存在这个属性,都调用了魔法函数__getattribute__ - 最后我们看一下同时定义这两个魔法函数会发生什么
可以看出结果和上面一样,说明无论何时__getattribute__都是优先级最高
属性描述符
概念
- 属性描述符协议:它是一种特定的类,指的是定义了__get__、__set__、__delete__三个方法中的任意一个就被称作描述符,它的作用就是创建一个类,作为另一个类的类属性
- 如果一个对象同时定义了__get__和__set__方法,它被称做数据描述符(data descriptor)。
- 只定义__get__方法的对象则被称为非数据描述符(non-data descriptor)。
实例
- 定义一个IntField类为描述符类
- 创建IntField类的实例,作为另一个User类的属性
由于IntField里面定义了一个类型的检查,我们尝试给user对象不赋整数对象,看看定义的IntField描述符是否起作用
创建元类
概念
- 元类就是创建类的类
- 我们可以通过type函数来创建一个类
- 第一个参数:类名
- 第二个参数:父类
- 第三个参数:属性,采用字典的方式,key是属性名,value是属性值,也可以传入方法
实例
- 通过type函数来创建一个类
metaclass属性
概念
- metaclass的英文直译过来就是元类,这既是一个概念也可以认为是Python当中的一个关键字,不管怎么理解,对它的内核含义并没有什么影响。我们可以不必纠结,就认为它是类的类的意思即可。在这个用法当中,支持我们自己定义一个类,使得它是后面某一个类的元类。
- 我们可以理解为python里面的所有类都是通过元类来创建的,即通过type函数创建的
- 通过元类创建类的方式与type类似,它的三个参数与type里面的一致
- __call__魔法方法,允许将类实例当做函数来调用
实例
- 通过元类在列表类里面增加add方法
- 元类的应用,假设我们设计了一种工厂模式,通过工厂模式来进行类的实例化,我们想要限制只能通过工厂模式来进行实例化,可以通过元类来实现
- 首先我们设计了这样一种工厂模式
- 通过创建一个元类,实现我们想要的功能
我们定义一个如上所示的元类,然后让每个类继承自元类
最后运行我们可以看到,不通过工厂直接进行实例化被禁止了
迭代器和生成器
迭代器
概念
- 可迭代对象:可以通过for循环来遍历所有取值的对象即为可迭代对象,从代码的角度上来说,重写了__iter__魔法方法的对象,都是可迭代对象。
- 迭代器:首先迭代器是一个可迭代对象,与可迭代对象的区别是可以通过next函数取值,即重写了__next__魔法方法,且只能往后取值,不能回溯。
- __iter__魔法方法:等价于iter(),允许一个可迭代对象,调用next方法
实例
- 通过迭代器的只是手动实现for循环
- 首先将被遍历的对象转换为迭代器
- 然后依次使用next函数,直至遍历结束
- 接收到循环结束的异常,结束循环
生成器
概念
- 生成器也是一个迭代器
- 生成器与迭代器不同的地方在于生成器并不是依次生成所有的数,而是只在运行的时候生成当时运行时的值,过后即删除,因此更加节省内存
- 下面通过一段代码看看迭代器与生成器占用的内存大小
import os
import psutil
# 显示当前 python 程序占用的内存大小
def show_memory_info(hint):
pid = os.getpid()
p = psutil.Process(pid)
info = p.memory_full_info()
memory = info.uss / 1024. / 1024
print('{} memory used: {} MB'.format(hint, memory))
def test_iterator():
show_memory_info('initing iterator')
list_1 = [i for i in range(1000000)]
print(list_1)
show_memory_info('after iterator initiated')
print(sum(list_1))
show_memory_info('after sum called')
def test_generator():
show_memory_info('initing generator')
list_2 = (i for i in range(1000000))
print(list_2)
show_memory_info('after generator initiated')
print(sum(list_2))
show_memory_info('after sum called')
test_iterator()
test_generator()
- 运行的结果如下
- 创建生成器的方式有两种,一种是通过元组的列表推导式,另一种是通过yield
- yield类似于return,运行到这里函数会结束,但与return不同的是,它会保留当前运行状态,知道下一次next函数调用,会继续往后运行,下面通过一个例子阐述与return不同之处。
实例
- 假设有一个列表,从中找出指定元素对应的下标
- 这里介绍一个enumerate函数,可以同时得到列表的下标和值
上面是通过生成器的方式实现的,那么可不可以用return来实现呢?
这样来看是可以的,那如果给定的列表中有重复的数字呢,return是否可以实现?
从结果可以看出,只能找到第一个9的位置,这是因为函数运行到return即结束,而下次运行时会从头开始,那么如果使用yield可不可以呢?
我们可以看到使用yield是可以找到两个9的位置的,原因是函数虽然运行到yield停止了,但是保留了当前的状态,而下次运行时会从当前状态继续下去,故而可以找到两个9的位置,也是yield与return不同的地方。 - 下面一个例子是读取大文件
- 文件300G,文件比较特殊,一行 分隔符 {|}
具p体实现如下:
def readlines(f,newline):
buf = ""
while True:
while newline in buf:
pos = buf.index(newline)
yield buf[:pos]
buf = buf[pos + len(newline):]
chunk = f.read(4096*10)
if not chunk:
yield buf
break
buf += chunk
with open('demo.txt') as f:
for line in readlines(f,"{|}"):
print(line)
- 我们这里选取一段简单的文本asdasdasdsa{|}asdasdsadasd{|}aczxczxcerw4tery7rjhghbgf{|}
- 运行结果如下: