Python高级编程技巧-元类编程及迭代器生成器

1.0 __getattr__和__getattribute__魔法方法

1
2
3
4
5
6
7
8
9
10
11
12
13
#__getattr__当访问的属性不存在时,自动触发的一个方法.
class User(object):
    def __init__(self,name,info):
        self.name = name
        self.info = info
    def __getattr__(self, item):#当实例访问属性时,没有这个
        # 属性就会触发这个方法
        return self.info[item]
        print(item)

a = User('李四',{'age':19})
print(a.name)
print(a.age)

getattribute__则是无条件的优先执行,所以如果不是特殊情况最好不要用__getattribute

2.0 属性描述符

  • 属性描述符是一个强大的通用协议。它是properties, methods, static methods, class methods 和super()的调用原理。
  • 属性描述符是实现了特定协议的类,只要实现了__get__,__set__和__delete__三个方法中的任意一个,这个类就是描述符,它能实现对多个属性运用相同存取逻辑的一种方式,通俗来说就是:创建一个实例,作为另一个类的类属性。
  • 如果一个对象同时定义了__get__和__set__方法,它被称做数据描述符(data descriptor)。
  • 只定义__get__方法的对象则被称为非数据描述符(non-data descriptor)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class IntField(object):
    def __set__(self,instance,value):
        print("__set__")
    def __get__(self,instance,owner):
        print("__get__")
    def __delete__(self,instance):
        print("__delete__")
class User(object):
    age = IntField()

a = User()
a.age
a.get = 30
print(a.get)

使用属性类型创建描述符
除了使用类当作一个属性描述符,我们之前学习的 property(),就是可以轻松地为任意属性创建可用的描述符。创建 property() 的语法是 property(fget=None, fset=None, fdel=None, doc=None)

描述符查找顺序
? 当为数据描述符时, get__优先级高于__dict
? 当为非数据描述符时,dict__优先级高于__get

3.0 元类

3.1元类介绍

元类实际上就是创建类的类

3.2 实现如下

? 定义创建类的函数create_class
? 如果给create_class传的参数为user,则创建User类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def create_class(name):
    if name == "user":
        class User(object):
            def __str__(self):
                return "user"
        return User
    elif name == "person":
        class Person(object):
            def __str__(self):
                return "person"

        return Person
myclass = create_class("user")
obj = myclass()
print(obj)

3.3 type()创建元类

  • 第一个参数:name,表示类的名称,字符串类型。
  • 第二个参数:bases表示继承对象(父类),元组类型,单元素使用逗号。
  • 第三个参数:attr表示属性,这里可以填写类属性,类方式,静态方法,采用字典格式,key为属性名,value为属性值。
    type(name,bases,attr)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def __init__(self,name):
    self.name = name

def info(self):
    return self.age
@staticmethod
def stat_func():
    print("i am static")
User = type("User",(),{'age':18,'__init__':__init__,'info':info,'stat_func':stat_func})
zs = User('c')         #相当实例化
print(zs.name)
zs.age = zs.info() #访问实例方法
print(zs.age)
zs.stat_func()
###
###
class BaseClass1(object):
    def test(self):
        return "BaseClass1"
class BaseClass2(object):
    def test(self):
        return "BaseClass2"
obj = type("User",(BaseClass2,BaseClass1),{})
class_wo = obj()
print(class_wo.test()) #输出结果BaseClass2

3.4 metaclass属性

如果一个类中定义了__metalass__ = xxx,Python就会用元类的方式来创建类,就可以控制类的创建行为
比如,以下代码,再不改变类属性的抒写情况下,将属性名规定为大写访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
'''
2.创建upper_attr
'''
def upper_attr(cls_name,cls_par,cls_attr):
    # print(cls_name)
    # print(cls_par)
    # print(cls_attr)
    '''
    4将属性名规定为大写
    遍历属性
    如果属性是非_开头,将其转为大写
    '''
    new_attr = {}
    for name,value in cls_attr.items():
        if not name.startswith("_"):
            new_attr[name.upper()] = value.upper()
    #3.返回一个新的字典new_attr
    return type(cls_name,cls_par,new_attr)
'''
1.创建MyClass类,指定metaclass = upper_attr
这里的metaclass,其实也相当与type('name',(),{})
'''
class MyClass(object,metaclass=upper_attr):
    name = "ls"
my = MyClass()
print(my.NAME) #结果打印  LS

4.0 python迭代器

4.1 迭代器指的是迭代取值的工具,迭代是指一个重复的过程,每一次重复都是基于上一次结果而来

迭代提供了一种通用的不依赖索引的迭代取值方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from collections import Iterable,Iterator
print(isinstance("abc",Iterable)) #返回True ,可迭代对象
print(isinstance([1,2,3],Iterable)) #返回True,可迭代对象
print(isinstance(123,Iterable)) #返回False,不是可迭代对象
'''
可迭代对象,不一定是迭代器
'''
# print(isinstance([1,2,3],Iterator)) #返回False
li = [1,2,4]
lis = iter(li) #iter函数可以转为迭代器
print(type(lis)) #<class 'list_iterator'>
#print(lis[0])#报错,迭代器是不可以通过下标索引值取值的
print(next(lis))
print(next(lis))
print(next(lis))
print(next(lis)) #超出范围报错
for i in lis:
     print(i)

4.2 可迭代对象

可以用for循环遍历的对象都是可迭代对象

4.3 判断是否可迭代

除了看内置是否含有__iter__方法来判断该对象是否是一个可迭代的对象之外,我们还可以使用 isinstance() 判断一个对象是否是 Iterable 对象。

4.4 迭代器对象

  • 有内置的__next__()方法的对象,执行该方法可以不依赖索引取值。
  • 有内置的__iter__()方法的对象,执行迭代器的__iter__()方法得到的依然是迭代器本身。
  • iter()
    可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。
    那我们可以通过iter()方法将可迭代的对象,转为迭代器。
1
2
3
4
5
6
7
8
lit = [1,2,3]
ls = iter(lit)
print(type(ls))
# print(ls[0]) #报错,无法通过索引取值
print(next(ls)) #返回1
‘’‘
当用next全部取值完,在使用for,遍历,就无法取到值
’‘’

4.5 可迭代对象与迭代器区别

  • 可用于for循环的都是可迭代类型
  • 作用于next()都是迭代器类型
  • list、dict、str等都是可迭代的但不是迭代器,因为next()函数无法调用它们。- 可以通过iter()函数将它们转为迭代器
  • python的for循环本质就是通过不断调用next()函数实现的

5.0 生成器

5.1 生成器定义

在Python中,一边循环一边计算的机制,称为生成器:generator。

5.2 为什么要有生成器

  • 列表所有数据都在内存中,如果有海量数据的话会非常消耗内存。
    比如说:我们仅仅需要访问前面几个元素,但后面绝大多元素占用的内存就会浪费了。

  • 那么生成器就是在循环的过程中根据算法不断推算出后续的元素,这样就不用创建整个完整的列表,从而节省大量的空间。

  • 总而言之,就是当我们想要使用庞大数据,又想让它占用的空间少,那就使用生成器

5.3 如何创建生成器

生成器表达式:
生成器表达式来源于迭代和列表解析的组合,生成器和列表解析类似,但是它使用()而不是[]

1
2
3
4
5
6
7
g = (i for i in range(10))
# print(g)
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))

5.4 生成器的好处

使用生成器,可以大量减少内存消耗

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import os
import psutil

def show_info(start):
    pid = os.getpid()
    p = psutil.Process(pid)
    info = p.memory_full_info()
    memory = info.uss/1024./1024
    print(f"{start}一共使用了{memory:.2f}MB")
def func():
    show_info("initial")
    # a = [i for i in range(10000000)]#使用了396.05MB
    a = (i for i in range(10000000)) #使用了8.73MB,
    # 这就是为什么需要使用生成器
    show_info("created")
func()
show_info("finished")

5.5 生成器函数

  • 当一个函数中包含yield关键字,那么这个函数就不再是一个普通的函数,而是一个generator。
  • 调用函数就是创建了一个生成器对象。其工作原理就是通过重复调用next()或者__next__()方法,直到捕获一个异常。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
'''
函数 含有yield关键字时,会变成生成器
注意:
? yield返回一个值,并且记住这个返回值的位置,下次遇到next()调用时,代码从yield的下一条语句开始执行。与return的差别是,return也是返回一个值,但是直接结束函数。
'''
def createNums():
    print("-----func start-----")
    a,b = 0,1
    for i in range(10):
        print("--1--")
        yield b
        print("--2--")
        a,b = b, a+b
        print("--3--")
    print("----func end ----")
g = createNums()
print(next(g))
print(next(g))
print(next(g))
print(next(g))

5.7 读取大文件

文件300G, 比较特殊,一行 分隔符 {|}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)

5.8 迭代器与生成器

? 生成器能做到迭代器能做的所有事
? 而且因为生成器自动创建了iter()和next()方法,生成器显得简洁,而且高效