Python全栈(四)高级编程技巧之4.元类编程、迭代器和生成器 - Go语言中文社区

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


一、__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())

打印

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/CUFEECR/article/details/104141713
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢