# 第八章:元类编程

# 8.1 property 动态属性


    from datetime import date

    class User:
        def __init__(self, name, birthday):
            self.name = name
            self.birthday = birthday
            self._age = 0   # _ 一种编程规范

        @property
        def age(self):
            return date.today().year - self.birthday.year

        @age.setter
        def age(self, value):
            self._age = value

        def get_age(self):
            return self._age


    if __name__ == '__main__':
        user = User('linda', date(1987, 11, 14))
        print(user.age)     # @property 用变量的方式去封装逻辑
        user.age = 100      # @age.setter 接收参数
        print(user.get_age())   # self._age 实例内部有存储的变量 _age
  • 对外展示 user.age; 内部存储 self._age
  • 动态属性 property 内部有更多的逻辑操作空间
  • user.age = 100 仔细体会内部处理过程

# 8.2 getattr、__getattribute__魔法函数

# getattr


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

        def __getattr__(self, item):
            return 'Not found attribute %s' % item

    if __name__ == '__main__':
        user = User('linda')
        print(user.age) # Not found attribute age
  • getattr, 在查找不到属性的时候调用
  • 类似 else 机制

    class User:
        def __init__(self, info=None):
            if not info:
                info = {}
            self.info = info

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


    if __name__ == '__main__':
        user = User({'name': 'linda', 'age': 18})
        print(user.name)
        print(user.age)
  • 神奇的代理操作

# getattribute


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

        def __getattribute__(self, item):
            return 'get_attribute'


    if __name__ == '__main__':
        user = User('linda')
        print(user.name)    # get_attribute
        print(user.test)    # get_attribute
        print(user.other)   # get_attribute
  • 只要调用属性,就会触发 getattribute
  • 把持了整个属性调用入口,尽量不要重写这个方法
  • 写框架时会涉及到

# 8.3 属性描述符和属性查找过程

property 实现在数据获取和设置时增加额外逻辑处理,并对外提供简单接口

在批量属性操作,如验证,则需要每个属性都要写一遍,代码重复

  • 数据属性描述符:实现 getset 方法
  • 非数据属性描述符: 实现 get 方法

    import numbers

    class IntField:
        def __init__(self):
            self._data = None

        def __get__(self, instance, owner):
            print(instance)     # <__main__.User object at 0x000002B88B270288>
            print(owner)        # <class '__main__.User'>
            print(type(instance) is owner)          # True
            print(instance.__class__ is owner)      # True
            return self._data

        def __set__(self, instance, value):
            if not isinstance(value, numbers.Integral):
                raise ValueError('Need int value')
            # 重点来了,如何保存 value 呢,instance or self
            # 如果 instance.attribute 又会触发 __set__ 描述符
            self._data = value

        def __delete__(self, instance):
            pass


    class User:
        age = IntField()
        num = IntField()


    if __name__ == '__main__':
        user = User()
        user.age = 18
        print(user.__dict__)    # {} "age" 并没有进入到 __dict__

        print(user.age)

转变原先简单的属性获取顺序


    user 某个类实例,user.age 等价于 getattr(user, 'age')

    首先调用 __getattribute__
        如果定义了 __getattr__ 方法,调用 __getattribute__ 抛出异常 AttributeError 触发__getattr__
        而对于描述符(__get__)的调用,则是发生在 __getattribute__内部

    user = User(), 调用 user.age 顺序如下:
    (1) 如果 'age' 是出现在 User 或基类的 __dict__ 中,且 age 是data descriptor,那么调用其 __get__(instance, owner) 方法,否则
    (2) 如果 'age' 出现在 user 的 __dict__ 中,那么直接返回 user.__dict__['age'],否则
    (3) 如果 'age' 出现在 User 或基类的 __dict__ 中
        (3.1) 如果 age 是 non-data descriptor, 那么调用其 __get__ 方法,否则
        (3.2) 返回 User.__dict__['age']
    (4) 如果 User 有 __getattr__ 方法,调用 __getattr__ 方法,否则
    (5) 抛出异常 AttributeError
  • 属性描述符优先级最高

    class NonDataIntFiled:
        def __get__(self, instance, owner):
            print(instance)
            print(owner)
            return 100

    class User:
        age = NonDataIntFiled()

    if __name__ == '__main__':
        user = User()
        # user.__dict__['age'] = 18
        # user.age = 18
        # print(user.__dict__)
        print(user.age)

# 8.4 newinit 的区别

  • 自定义类中 new: 用来控制对象的生成过程,返回 self 对象,如果没有返回值,则不会调用 init
  • 自定义类中 init: 用来完善对象,如初始化
  • newinit 之前调用

    class User(object):

        # 新式类才有,生成对象 user 之前加逻辑
        def __new__(cls, *args, **kwargs):
            # args = ('linda', )
            # kwargs = {'age': 20}
            # 与自定义 metaclass 中的 __new__ 有区别
            print('from __new__')
            self = super().__new__(cls)
            return self

        def __init__(self, name, age=18):
            self.name = name
            self.age = age
            print('from __init__')


    if __name__ == '__main__':
        user = User('linda', age=20)

PS: 统一描述

  • 元类 -> 类对象
  • 类 -> 实例

# 8.5 自定义元类

  • class 关键字 可以字面创建类

    def create_class(name):

        if name == 'user':
            class User:
                def __str__(self):
                    return 'User'
            return User

        elif name == 'company':
            class Company:
                def __str__(self):
                    return 'Company'
            return Company

    MyClass = create_class('user')
    obj = MyClass()
    print(obj)
    print(type(obj))    # <class '__main__.create_class.<locals>.User'>
  • type 可以动态创建类,动态添加属性和方法

    def func(self):
        return 'I am from func.'

    class Base:
        def answer(self):
            return 'I am from Base.answer.'

    # type 动态创建类
    User = type('User', (Base, ), {'name': 'user', 'func': func})
    user = User()
    print(user.name)
    print(user.func())
    print(user.answer())
    print(type(user))

元类创建类的类 metaclass(type) -> class -> instance


    class MetaClass(type):
        # 用来控制 User 的创建过程 与 User 中的 __new__ 有区别
        def __new__(cls, name, bases, attrs, **kw):
            return super().__new__(cls, name, bases, attrs, **kw)


    class User(object, metaclass=MetaClass):

        def __init__(self, name):
            self.name = name

        def bar(self):
            print('from bar.')

python 在实例化的过程 user = User()

(1) 首先寻找 metaclass,来创建 User,否则 (2) 再次寻找基类 BaseUser 的 metaclass,来创建 User,否则 (3) 接着寻找模块 metaclass,来创建 User,否则 (4) 最后默认 type 为 metaclass 来创建 User

# 8.6 通过元素实现ORM

首先明确需求


    # 简单定义
    class User:
        name = CharFiled(db_column="", max_length=32)
        age = IntFiled(db_column="", min_value=0, max_value=100)
        class Meta:
            db_table = 'user'

    # ORM
    user = User()
    user.name = 'linda'
    user.age = 18
    user.save()

迷你版 ORM


    from collections import OrderedDict


    class Field:
        pass


    class IntField(Field):
        def __init__(self, db_column, min_value=0, max_value=100):
            self.db_column = db_column
            self.min_value = min_value
            self.max_value = max_value
            self._value = None

        def __get__(self, instance, owner):
            return self._value

        def __set__(self, instance, value):
            if not isinstance(value, int):
                raise TypeError('need int value')
            if value < self.min_value or value > self.max_value:
                raise ValueError('need [%s, %s] value' % (self.min_value, self.max_value))
            self._value = value


    class CharField(Field):
        def __init__(self, db_column, max_length=32):
            self.db_column = db_column
            self.max_length = max_length
            self._value = None

        def __get__(self, instance, owner):
            return self._value

        def __set__(self, instance, value):
            if not isinstance(value, str):
                raise TypeError('need str value')
            if len(value) > self.max_length:
                raise ValueError('len need lower than %s' % self.max_length)
            self._value = value


    # 元类注入一系列属性
    class MetaClass(type):
        def __new__(cls, name, bases, attrs, **kw):
            # BaseModel 也会调用 Metaclass,但没有定义 name,age 等属性,可特殊判断
            if name == 'BaseModel':
                return super().__new__(cls, name, bases, attrs, **kw)

            fields = {}
            for key, value in attrs.items():
                if isinstance(value, Field):
                    fields[key] = value

            attrs_meta = attrs.get('Meta', None)
            _meta = {}
            db_table = name.lower()
            if attrs_meta is not None:
                table = getattr(attrs_meta, 'db_table', None)
                if not table:
                    db_table = table

            _meta['db_table'] = db_table
            attrs['_meta'] = _meta
            attrs['fields'] = fields
            if attrs.get('Meta'):
                del attrs['Meta']
            return super().__new__(cls, name, bases, attrs, **kw)


    class BaseModel(metaclass=MetaClass):
        def __init__(self, **kw):
            for key, value in kw.items():
                setattr(self, key, value)
            super().__init__()

        def save(self):
            fields = OrderedDict(self.fields)
            fields_str = ", ".join([value.db_column for value in fields.values()])
            values_str = ', '.join([str(getattr(self, field)) if not isinstance(value, CharField)
                                    else "'%s'" % str(getattr(self, field))
                                    for field, value in fields.items()])
            sql = "insert into %s (%s) values (%s)" % (self._meta['db_table'], fields_str, values_str)
            print(sql)
            # insert into user (name1, age) values ('linda', 20)


    # 自定义类时写少量属性,元类帮助我们注入很多通用属性
    class User(BaseModel):
        name = CharField('name1', max_length=16)
        age = IntField('age', min_value=0, max_value=100)

        class Meta:
            db_table = 'user'


    if __name__ == '__main__':
        user = User(name='linda')
        user.age = 20
        user.save()

ORM 设计思想

  • 数据属性描述符(set, get) 实现验证操作
  • 自定义元类(MetaClass(type)) 实现参数注入
  • 自定义 ORM 类(BaseModel) 获取元类注入的参数 进行额外操作
  • 自定义元类 注入 objects
  • 需特别注意调用层级顺序,newinit 之前,所以 init 中可以使用元类注册测参数
上次更新: 8/26/2022, 2:06:10 PM