Python全栈(四)高级编程技巧之4.元类编程、迭代器和生成器

◇◆丶佛笑我妖孽 提交于 2020-02-03 05:44:37

一、__getattr__和__getattribute__魔法函数

from datetime import date


class User:
    def __init__(self, name, birthday):
        self.name = name
        self.birthday = birthday


if __name__ == "__main__":
    user = User("corley", date(year=2020, month=1, day=1))
    print(user.name)

打印

corley

当打印不存在的属性时,会报错:

from datetime import date


class User:
    def __init__(self, name, birthday):
        self.name = name
        self.birthday = birthday


if __name__ == "__main__":
    user = User("corley", date(year=2020, month=1, day=1))
    print(user.age)

打印

Traceback (most recent call last):
  File "xxx/demo.py", line 18, in <module>
    print(user.age)
AttributeError: 'User' object has no attribute 'age'

此时需要加入__getattr__

from datetime import date


class User:
    def __init__(self, name, birthday):
        self.name = name
        self.birthday = birthday

    def __getattr__(self, item):
        print("not find attr")


if __name__ == "__main__":
    user = User("corley", date(year=2020, month=1, day=1))
    print(user.age)

打印

not find attr
None

可得:
None表示打印user.age时调用__getattr__方法,该方法没有返回值,所以打印None;
__getattr__魔法方法是在查找不到属性的时候调用。
同时可打印__getattr__的参数item:

from datetime import date


class User:
    def __init__(self, name, birthday):
        self.name = name
        self.birthday = birthday

    def __getattr__(self, item):
        print(item, "not find attr")


if __name__ == "__main__":
    user = User("corley", date(year=2020, month=1, day=1))
    print(user.age)

打印

age not find attr
None

即表示没有找到User对象的age属性。
在初始化方法的参数中增加info字典,初始化实例时info里添加age键值对:

from datetime import date


class User:
    def __init__(self, name, birthday, info = {}):
        self.name = name
        self.birthday = birthday

    def __getattr__(self, item):
        print(item, "not find attr")


if __name__ == "__main__":
    user = User("corley", date(year=2020, month=1, day=1),info={'age':18})
    print(user.age)

打印

age not find attr
None

仍然查找不到age属性,要想能查找到age属性,需要改进代码:

from datetime import date


class User:
    def __init__(self, name, birthday, info = {}):
        self.name = name
        self.birthday = birthday
        self.info = info

    def __getattr__(self, item):
        return self.info[item]


if __name__ == "__main__":
    user = User("corley", date(year=2020, month=1, day=1),info={'age':18})
    print(user.age)

打印

18

此时即可访问到字典中的属性age。
如果要访问的属性在字典中不存在时,会报和字典的箭不存在一样的错误:

from datetime import date


class User:
    def __init__(self, name, birthday, info = {}):
        self.name = name
        self.birthday = birthday
        self.info = info

    def __getattr__(self, item):
        return self.info[item]


if __name__ == "__main__":
    user = User("corley", date(year=2020, month=1, day=1),info={'age':18})
    print(user.ages)

打印

Traceback (most recent call last):
  File "xxx/demo.py", line 16, in <module>
    print(user.ages)
  File "xxx/demo.py", line 11, in __getattr__
    return self.info[item]
KeyError: 'ages'

解决办法:

  • 方法一:改进__getattr__方法里访问字典的方式
from datetime import date


class User:
    def __init__(self, name, birthday, info = {}):
        self.name = name
        self.birthday = birthday
        self.info = info

    def __getattr__(self, item):
        return self.info.get(item)


if __name__ == "__main__":
    user = User("corley", date(year=2020, month=1, day=1),info={'age':18})
    print(user.ages)

打印

None

如果找不到相应的键,会返回None。

  • 方法二:增加__getattribute__方法
from datetime import date


class User:
    def __init__(self, name, birthday, info = {}):
        self.name = name
        self.birthday = birthday
        self.info = info

    def __getattr__(self, item):
        return self.info.get(item)

    def __getattribute__(self, item):
        return "corley"


if __name__ == "__main__":
    user = User("corley", date(year=2020, month=1, day=1),info={'age':18})
    print(user.age)
    print(user.ages)
    print(user.birthday)

打印

corley
corley
corley

易知:
不管访问User对象的什么属性,不管属性是否存在,都返回的是__getattribute__方法的返回内容;
进一步可知,__getattribute__方法在__getattr__方法之前执行,不要轻易重写。

二、属性描述符

1.属性描述符分析

class User:
    def __init__(self, age):
        self.age = age

    def get_age(self):
        return str(self.age) + 'years old'

    def set_age(self, age):
        if not isinstance(age, int):
            raise TypeError('Type Error')
        self.age = age

该类可以对年龄属性进行判断,不过不是整型,则抛出异常;
但是显然这只是对一个属性进行判断,如果User类中有多个属性都需要判断,那么就需要写多个方法,就很麻烦,我们需要对方法进行复用。这个时候就要用到属性描述符。
属性描述符
在类中实现 __get____set____del__ 中的一个方法,即可构成属性描述符。

class IntField(object):
    def __get__(self, instance, owner):
        print('__get__')

    def __set__(self, instance, value):
        print('__set__')

    def __del__(self, instance):
        pass


class User:
    age = IntField()


user = User()
#set
user.age = 30
#get
print(user.age)

打印

__set__
__get__
None
Exception ignored in: <function IntField.__del__ at 0x0000023DDEDC6C18>
TypeError: __del__() missing 1 required positional argument: 'instance'

并没有打印出30,但是__get____set__方法被调用,并且先调用__set__方法。

class IntField(object):
    def __get__(self, instance, owner):
        print('__get__')

    def __set__(self, instance, value):
        print('__set__')
        print(instance)
        print(value)

    def __del__(self, instance):
        pass


class User:
    age = IntField()


user = User()
#set
user.age = 30
#get
print(user.age)

打印

__set__
Exception ignored in: <function IntField.__del__ at 0x000001FCFE446C18>
<__main__.User object at 0x000001FCFE488388>
30
TypeError: __del__() missing 1 required positional argument: 'instance'
__get__
None

易知,__set__方法的参数value即User对象属性赋值的值,在执行user.age = 30时调用__set__方法方法并传参数值。
进一步改进–加入属性值判断:

class IntField(object):
    def __get__(self, instance, owner):
        print('__get__')
        return self.value

    def __set__(self, instance, value):
        print('__set__')
        if not isinstance(value,int):
            raise TypeError('Type Error')
        self.value = value

    def __del__(self, instance):
        pass


class User:
    age = IntField()


user = User()
#set
user.age = 30
#get
print(user.age)

打印

__set__
__get__
30
Exception ignored in: <function IntField.__del__ at 0x0000018F39F76C18>
TypeError: __del__() missing 1 required positional argument: 'instance'

此时能打印出正常的属性值,这就是属性描述符。
属性描述符分为数据描述符(有__get__方法和__set__方法)和非数据描述符(只有__get__方法,较少),这里是数据描述符。
非数据描述符的定义如下:

class NoneDataIntField:
    def __get__(self, instance, owner):
        pass

如果传入的不是整型值,就会报错:

class IntField(object):
    def __get__(self, instance, owner):
        print('__get__')
        return self.value

    def __set__(self, instance, value):
        print('__set__')
        if not isinstance(value,int):
            raise TypeError('Type Error')
        self.value = value

    def __del__(self, instance):
        pass


class User:
    age = IntField()


user = User()
#set
user.age = '30'
#get
print(user.age)

打印

__set__
Traceback (most recent call last):
  File "xxx/demo.py", line 56, in <module>
    user.age = '30'
  File "xxx/demo.py", line 43, in __set__
    raise TypeError('Type Error')
TypeError: Type Error
Exception ignored in: <function IntField.__del__ at 0x0000027003A66C18>
TypeError: __del__() missing 1 required positional argument: 'instance'

据此可对整型进行判断。

2.属性查找顺序

user = User(), 那么user.age 顺序如下:

(1) 如果"age"是出现在User或其基类的__dict__中, 且age是data descriptor,那么调用其__get__方法,
否则

(2) 如果"age"出现在user的__dict__中, 那么直接返回 obj.dict[‘age’],否则

(3) 如果"age"出现在User或其基类的__dict__中

(3.1) 如果age是non-data descriptor,那么调用其__get__方法, 否则

(3.2) 返回 dict[‘age’]

(4)如果User有__getattr__方法,调用__getattr__方法,否则

(5) 抛出AttributeError

优先级最高的是属性描述符,下面进行简单的证明:

class IntField(object):
    def __get__(self, instance, owner):
        print('__get__')
        return self.value

    def __set__(self, instance, value):
        print('__set__')
        if not isinstance(value,int):
            raise TypeError('Type Error')
        self.value = value

    def __del__(self, instance):
        pass


class User:
    age = IntField()


user = User()
#set
user.age = 30
user.__dict__['age'] = 18
#get
print(user.age)

打印

Exception ignored in: <function IntField.__del__ at 0x0000023854326C18>
TypeError: __del__() missing 1 required positional argument: 'instance'
__set__
__get__
30

易知,即便user.__dict__['age']user.age后重新赋值18,返回的还是30,所以优先级最高的是属性描述符。

三、自定义元类

元类
是创建类的类,即type类。

1.动态创建类

def create_class(name):
    if name == 'user':
        class User:
            def __str__(self):
                return 'user'
        return User
    elif name == 'student':
        class Student:
            def __str__(self):
                return 'student'
        return Student

if __name__ == '__main__':
    myclass = create_class('user')
    obj = myclass()
    print(obj)
    print(type(obj))

打印

user
<class '__main__.create_class.<locals>.User'>

可以动态地创建User或者Student类。

2.使用type创建类

查看type()函数的源码:

def __init__(cls, what, bases=None, dict=None): # known special case of type.__init__
    """
    type(object_or_name, bases, dict)
    type(object) -> the object's type
    type(name, bases, dict) -> a new type
    # (copied from class doc)
    """
    pass

可知type()函数有两种用法:
(1)type(object) -> the object's type
参数传入一个对象,返回这个对象的类型;
(2)type(name, bases, dict) -> a new type
给定一些参数,返回一个新类型,即创建类:

  • 第一个参数:name表示类名称,字符串类型
  • 第二个参数:bases表示继承对象(父类),元组类型,单元素使用逗号
  • 第三个参数:dict表示属性构成的字典,这里可以填写类属性、类方式、静态方法,采用字典格式,key为属性名,value为属性值

由第二种用法可知,type还可以动态的创建类type(类名,由父类组成的元组,包含属性的字典)
例如

User = type('User',(),{})
obj = User()
print(obj)

打印

<__main__.User object at 0x000001F17D88AB88>

向类中添加属性:

User = type('User',(),{'name':'corley'})
obj = User()
print(obj)
print(obj.name)

打印

<__main__.User object at 0x00000221F1D06688>
corley

向类中添加方法:
像添加属性一样添加方法

def info(self):
    return self.name


User = type('User',(),{'name':'corley','info':info})
obj = User()
print(obj.info())

打印

corley

当方法名改变时,对象实例调用的方法名不需要改变,因为调用方法时调用的是字典里对应的键:

def infos(self):
    return self.name


User = type('User',(),{'name':'corley','info':infos})
obj = User()
print(obj.info())

依然能正常运行,打印

corley

再如

def infos(self):
    return self.name

def get_age(self):
    self.age = 18
    return self.age

def __init__(self):
    self.sex = 'male'


User = type('User',(),{'name':'corley','info':infos,'age':get_age,'sex':__init__})
obj = User()
print(obj.info())
print(obj.age())
print(obj.sex)
print(obj.sex())

打印

corley
18
<bound method __init__ of <__main__.User object at 0x0000020619AE8408>>
None

可知,魔法方法和普通方法不一样,不适合在type()创建类时使用。
继承关系:

def infos(self):
    return self.name

def get_age(self):
    self.age = 18
    return self.age


class BaseClass(object):
    def test(self):
        return 'base class'

User = type('User',(BaseClass,),{'name':'corley','info':infos,'age':get_age})
user = User()
print(user.test())

打印

base class

再考虑继承多个类:

def infos(self):
    return self.name

def get_age(self):
    self.age = 18
    return self.age


class BaseClass(object):
    def test(self):
        return 'base class'


class BaseClass1(object):
    def test1(self):
        return 'base class1'

User = type('User',(BaseClass,BaseClass1,),{'name':'corley','info':infos,'age':get_age})
user = User()
print(user.test())
print(user.test1())

打印

base class
base class1

魔法方法也可以继承:

class BaseClass(object):
    def test(self):
        return 'base class'

    def __str__(self):
        return 'This is a test'


class BaseClass1(BaseClass):
    def test1(self):
        return 'base class1'

b = BaseClass1()
print(b)

打印

This is a test

四、metaclass属性

如果一个类中定义了metalass = xxx,Python就会用元类的方式来创建类。
在Python2和Python3中不同:
在Python2中:

#把创建的类的所有属性大写
def upper_attr():
    pass

class Foo(object):
    __metaclass__ = upper_attr

在Python3中:

#把创建的类的所有属性大写
def upper_attr(class_name, class_parents,class_attr):
    new_attr = {}
    for name,value in class_attr.items():
        print(name)
        if not name.startswith('_'):
            new_attr[name.upper()] = value
    return type(class_name,class_parents,new_attr)

class Foo(object, metaclass=upper_attr):
    name = 'corley'


f = Foo()
print(hasattr(Foo,'name'))
print(hasattr(Foo,'NAME'))

打印

__module__
__qualname__
name
False
True

显然可得,Foo类有NAME属性,没有name属性;
在类的实例化过程中,metaclass是优于object的,所以根据upper_attr创建对象的。
进一步验证:

class Demo(object):
    def __new__(cls, *args, **kwargs):
        pass


class MetaClass(type):
    def __new__(cls, *args, **kwargs):
        pass


class User(Demo, metaclass=MetaClass):
    pass


obj = User()

进行断点测试Debug,结果如下
元类断点测试
可知,实例化User类时,直接进入MetaClass,没有进入Demo,显然元类的优先级高于继承的父类。
即类进行实例化时,首先寻找metaclass,如果没有再寻找普通的继承关系。

五、迭代器和生成器

1.迭代器

迭代
通过for循环遍历对象的每一个元素的过程。
Python的for语法功能非常强大,可以遍历任何可迭代的对象。
在Python中,listtuplestringdictsetbytes等都是可以迭代的数据类型。
迭代器
是一种可以被遍历的对象,并且能作用于next()函数。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。
迭代器只能往后遍历不能回溯,不像列表,随时可以取后面的数据,也可以返回头取前面的数据。

from collections.abc import Iterable,Iterator

#可迭代
print(isinstance(list(),Iterable))
#迭代器
print(isinstance(list(),Iterator))

打印

True
False

说明列表是可迭代的,但不是迭代器。
解释
列表实现了__iter__方法,但是没有实现__next__方法。
将列表变成迭代器-调用iter()方法:

l = [1,2,3,4]
it = iter(l)
print(it)
print(type(it))

打印

<list_iterator object at 0x0000026CA6F2CD48>
<class 'list_iterator'>

显然,it是迭代器类型。
next()方法来遍历取值:

l = [1,2,3,4]
it = iter(l)
print(it)
print(type(it))
print(next(it))
print(next(it))
print(next(it))
print(next(it))
print(next(it))

打印

<list_iterator object at 0x00000178A8BBD848>
<class 'list_iterator'>
1
2
3
4
Traceback (most recent call last):
  File "xxx/demo.py", line 186, in <module>
    print(next(it))
StopIteration

易知,当遍历完之后,再遍历,会报错。
还可以用for循环来遍历:

l = [1,2,3,4]
it = iter(l)

for i in it:
    print(i)

打印

1
2
3
4

2.生成器

有时候,序列或集合内的元素的个数非常巨大,如果全制造出来并放入内存,对计算机的压力是非常大的,这时候需要生成器来解决。
从Python2.2起,生成器提供了一种简洁的方式帮助返回列表元素的函数来完成简单和有效的代码。
生成器类似于返回值为数组的一个函数,这个函数可以接受参数,可以被调用,但是,不同于一般的函数会一次性返回包括了所有数值的数组,生成器一次只能产生一个值,这样消耗的内存数量将大大减小,而且允许调用函数可以很快的处理前几个返回值,因此生成器看起来像是一个函数,但是表现得却像是迭代器。它基于yield指令,允许暂停函数并立即返回结果,此函数保存其执行上下文,如果需要,可立即继续执行。
生成器是一个特殊的程序,可以被用作控制循环的迭代行为,Python中生成器是迭代器的一种,使用yield返回值函数,每次调用yield会暂停,而可以使用next()函数和send()函数恢复生成器。

g = (x for x in range(10))
print(g)
print(next(g))
print(next(g))
print('for start')
for i in g:
    print(i)

打印

<generator object <genexpr> at 0x00000218AB6137C8>
0
1
for start
2
3
4
5
6
7
8
9

生成器的遍历取值是和迭代器类似的。
定义斐波拉契数列:

def fibonacci():
    a = 0
    b = 1
    for i in range(5):
        print(a)
        a, b = b, a + b

fibonacci()

打印

0
1
1
2
3

使用生成器:

def fibonacci():
    print('--func start--')
    a = 0
    b = 1
    for i in range(5):
        print('--1--')
        yield a
        print('--2--')
        a, b = b, a + b
        print('--3--')
    print('--func end--')

f = fibonacci()
print(next(f))
print(next(f))
print(next(f))
print(next(f))
print(next(f))

打印

--func start--
--1--
0
--2--
--3--
--1--
1
--2--
--3--
--1--
1
--2--
--3--
--1--
2
--2--
--3--
--1--
3

由执行结果可知,每调用一次next()函数,会执行到yield a部分,打印出当前的a值暂停,直到下一次再执行next()函数时,才会执行yield a下面的print('--2--')print('--3--')
循环5次后,并没有执行print('--func end--'),需要再调用一次next()函数才会打印 –func end–,但是此时由于生成器已遍历到尾部,所以会报错,如下

def fibonacci():
    print('--func start--')
    a = 0
    b = 1
    for i in range(5):
        print('--1--')
        yield a
        print('--2--')
        a, b = b, a + b
        print('--3--')
    print('--func end--')

f = fibonacci()
print(next(f))
print(next(f))
print(next(f))
print(next(f))
print(next(f))
print(next(f))

打印

Traceback (most recent call last):
  File "xxx/demo.py", line 210, in <module>
    print(next(f))
StopIteration
--func start--
--1--
0
--2--
--3--
--1--
1
--2--
--3--
--1--
1
--2--
--3--
--1--
2
--2--
--3--
--1--
3
--2--
--3--
--func end--

对改程序进行断点测试Debug,更直观地查看执行过程如下:
生成器

应用:生成器读取大文件

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

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)

打印

123
abc
987
zyx

其中,demo.txt的内容为:

123{|}abc{|}987{|}zyx

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!