7.元类编程

1. property动态属性

>>> from datetime import date, datetime


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

>>>     @property     # get
>>>     def age(self):
>>>         return datetime.now().year - self.birthday.year

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


>>> if __name__ == '__main__':
>>>     user = User('cannon', date(year=1993, month=6, day=7))
>>>     print(user.age)
25

>>>     user.age = 99
>>>     print(user._age)
99

2. __ getattr __ 、__ getattribute __魔法函数

__ getattr __:查找不到属性的时候 调用
>>> from datetime import date


>>> class User:
>>>     def __init__(self, name, birthday):
>>>         self.name = name
>>>         self.birthday = birthday
>>>         self._age = 0
>>>     def __getattr__(self, item):   #__getattr__  查找不到属性的时候 调用
>>>         return 'not find attr'


>>> if __name__ == '__main__':
>>>     user = User('cannon', date(year=1993, month=6, day=7))
>>>     print(user.age)   # 调用__getattr__
not find attr
__ getattribute __:# 查找属性时 无条件进入, 把持所有 属性的入口。 能不重写尽量不要重写
>>> from datetime import date


>>> class User:
>>>     def __init__(self, name, birthday):
>>>         self.name = name
>>>         self.birthday = birthday
>>>         self._age = 0
>>>     def __getattr__(self, item):   #__getattr__  查找不到属性的时候 调用
>>>         return 'not find attr'
>>>     def __getattribute__(self, item):  # 查找属性时 无条件进入, 把持所有 属性的入口。 能不重写尽量不要重写
>>>         return 'cannon'

>>> if __name__ == '__main__':
>>>     user = User('cannon', date(year=1993, month=6, day=7))
>>>     print(user.age)   # 调用__getattribute__
cannon

3. 属性描述符和属性查找过程

假设类中的属性,你想规定它只能是 int类型,如何高效地做到这一点?

我们需要属性描述符

>>> import numbers


>>> class IntField:   # __get__  __set__ __delete__中实现任意一个就会变成属性描述符
>>>     def __get__(self, instance, owner):
>>>         return self.value

>>>     def __set__(self, instance, value):
>>>         if not isinstance(value, numbers.Integral):
>>>             raise ValueError('int value need')
>>>         elif value < 0:
>>>             raise ValueError('value should > 0')
>>>         self.value = value

>>>     def __delete__(self, instance):
>>>         pass

>>> class User:
>>>     age = IntField()
    
>>> if __name__ == '__main__':
>>>     user = User()
>>>     user.age = 30   # age是属性描述符,所以__dict__为空。 否则会放入__dict__
>>>     print(user.__dict__)
{}

>>>     print(user.age)  # age是属性描述符, 他会优先调用__get__ 而不是从__dict__中去取值
30

>>>     user.age = 'abc'     # 不是指定的int类型,就会报错
Traceback (most recent call last):
  File "3_attr_desc.py", line 63, in <module>
    user.age = 'abc'
  File "3_attr_desc.py", line 10, in __set__
    raise ValueError('int value need')
ValueError: int value need

详解属性描述符执行时的调用顺序

如果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__方法
(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

4. __new__和__init__的区别

>>> class User:
>>>     def __new__(cls, *args, **kwargs):   
>>>         print('in new')

>>>     def __init__(self):
>>>         print('in init')

>>> if __name__ == '__main__':
>>>     user = User(name = 'cannon')   
in new  
上面代码中 可以看出 没有调用__init__, 为什么呢?
答:
__new__ 用于控制对象的生成过程, 在对象生成之前
__init__ 是用来完善对象的
如果__new__不返回对象  , 就不会调用__init__

代码举例:

>>> class User:
>>>     def __new__(cls, *args, **kwargs):   
>>>         print('in new')
>>>         return super(User, cls).__new__(cls)

>>>     def __init__(self, name):   # name参数传入,这里必须一致
>>>         print('in init')


# new 用于控制对象的生成过程, 在对象生成之前
# init 是用来完善对象的
# 如果new不返回  对象   就不会调用init
>>> if __name__ == '__main__':
>>>     user = User(name = 'cannon')   
in new  
in init
__new__一般情况不需要重写, 但在框架中会经常出现

5. 自定义元类metaclass

类也是对象 type是能创建类的类

1. 首先我们也来自己写一个 能动态创建类的函数:
>>> 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

>>> if __name__ == '__main__':
>>>     Myclass = create_class('user')   # Myclass 是动态创建的User
>>>     my_obj = Myclass()               # my_objUser类的实例化
>>>     print(my_obj)
user
2. 我们可以直接通过type 动态创建类:
 #  type动态创建类
>>> def say(self):
>>>     return self.name

>>> class BaseClass:
>>>     def answer(self):
>>>         return 'i am baseclass'

 # BaseClass 后面的, 不能漏。  say 后面不能加()
>>> User = type('User', (BaseClass, ), {'name':'user', 'say':say})  
>>> my_obj = User()
>>> print(my_obj.name)
>>> print(my_obj.say())
>>> print(my_obj.answer())

type的__init__函数(pycharm自动提炼的),可以明白看出应传入的参数。

    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
3. type创建类比较少用,最常用的方法是 通过metaclass的方法来创建
python的 实例化过程
1. 找metaclass (父类中也会去找),通过metaclass创建类
2. 没metaclass(包括所有父类中也没有),那么通过type 创建User的类对象
>>> count = 1
>>> class MetaClass(type):  # 元类要继承type
>>>     def __new__(cls, *args, **kwargs):
>>>         global count
>>>         print('metaclass', count)
>>>         count += 1
>>>         return super().__new__(cls, *args, **kwargs)

>>> class BaseClass(metaclass=MetaClass):
>>>     def answer(self):
>>>         return 'i am baseclass'

# 如果BaseClass中没有metaclass,这里会用type去创建.
# 这里BaseClass中有metaclass 所以metaclass优先
>>> class User(BaseClass):   
>>>     def __init__(self, name):
>>>         self.name = name
>>>     def __str__(self):
>>>         return 'User'
>>> if __name__ == '__main__':
>>>     my_obj = User(name='cannon')
>>>     print(my_obj.answer())

metaclass 1
metaclass 2
i am baseclass
User

6. 通过元类实现orm

综合运用自定义元类 和 属性描述符,实现类似django的orm

import numbers


class Field:
    pass


class IntField(Field):
    def __init__(self, db_column, min_value=None, max_value=None):
        self._value = None
        self.min_value = min_value
        self.max_value = max_value
        self.db_column = db_column
        if min_value is not None:
            if not isinstance(min_value, numbers.Integral):
                raise ValueError('min_value must be int')
            elif min_value < 0:
                raise ValueError('min_value must be positive int')

        if max_value is not None:
            if not isinstance(max_value, numbers.Integral):
                raise ValueError('max_value must be int')
            elif max_value < 0:
                raise ValueError('max_value must be positive int')

        if min_value is not None and max_value is not None:
            if min_value > max_value:
                raise ValueError('min_value must be smaller than max_value')

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

    def __set__(self, instance, value):
        if not isinstance(value, numbers.Integral):
            raise ValueError('int value need')
        elif value > self.max_value or value < self.min_value:
            raise ValueError('value must between min_value and max_value')
        self._value = value


class CharField(Field):
    def __init__(self, db_column, max_length=None):
        self._value = None
        self.db_column = db_column
        if max_length is None:
            raise ValueError(' you must spcify max_length for chaifield')
        self.max_length = max_length

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

    def __set__(self, instance, value):
        if not isinstance(value, str):
            raise ValueError('string value need')
        elif len(value) > self.max_length:
            raise ValueError('value len excess max_length')
        self._value = value


class ModelMetaClass(type):
    def __new__(cls, name, bases, attrs, **kwargs):
        if name == 'BaseModel':
            return super().__new__(name, bases, attrs, **kwargs)
        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 table is not None:
                db_table = table
        _meta['db_table'] = db_table
        attrs['_meta'] = _meta
        attrs['fields'] = fields

        del attrs['Meta']
        return super().__new__(cls, name, bases, attrs, **kwargs)


class BaseModel(metaclass=ModelMetaClass):
    def _init__(self, *args, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)
        return super().__init__()

    def save(self):
        fields = []
        values = []
        for key, value in self.fields.items():
            db_column = value.db_column
            if db_column is None:
                db_column = key.lower()
            fields.append(db_column)
            value = getattr(self, key)
            values.append(str(value))   # ageint  这里也得统一成str
        sql = "insert {db_table}({fields}) value({values})".format(
            db_table=self._meta['db_table'], fields=','.join(fields), values=','.join(values))
        pass


class User(BaseModel):
    name = CharField(db_column="name", max_length=10)
    age = IntField(db_column="age", min_value=0, max_value=100)

    class Meta:
        db_table = 'user'


if __name__ == '__main__':
    user = User()
    user.name = 'cannon'
    user.age = 25
    user.save()

猜你喜欢

转载自blog.csdn.net/weixin_41207499/article/details/80554390