• 元类编程


    一、property

    先看下面例子:
    依赖于birthday设置User对象的属性

    class User:
        def __init__(self,name,birthday):
            self.name = name
            self.birthday = birthday
            #age依赖于当前时间,需要做一番计算
            self.age = datetime.today().year - self.birthday.year
    
    user = User("ming",date(year=1992,month=8,day=12))
    print(user.age)

    但是这样好吗?如果是更加复杂的计算就必须以函数的方式返回。

    from datetime import date,datetime
    class User:
        def __init__(self,name,birthday):
            self.name = name
            self.birthday = birthday
    
        def get_age(self):
            return datetime.today().year - self.birthday.year
    
    user = User("ming",date(year=1992,month=8,day=12))
    print(user.get_age())

    虽然可以实现功能,但是明明是属性值却还要调用方法?有没有解决方案了?
    通过property关键字就可以实现。

    from datetime import date,datetime
    class User:
        def __init__(self,name,birthday):
            self.name = name
            self.birthday = birthday
    
        @property
        def age(self):
            return datetime.today().year - self.birthday.year
    
    user = User("ming",date(year=1992,month=8,day=12))
    print(user.age)

    @property可以把一个函数当作一个属性来供用户操作
    既然是属性操作,还可以进行set和del操作。

    from datetime import date,datetime
    
    class User:
        def __init__(self,name,birthday):
            self.name = name
            self.birthday = birthday
            self._age = None
    
        @property
        def age(self):
            return datetime.today().year - self.birthday.year
    
        @age.setter
        def age(self,value):
            self._age = value
            
        @age.deleter
        def age(self):
            del self._age
    
    
    user = User("ming",date(year=1992,month=8,day=12))
    print(user.age)  #27
    print(user._age) #None
    #需要注意的是属性名不能和@property的方法名相同
    user._age = 28
    print(user.age)  #27
    print(user._age) #28

    二、__getattr__和__getattribute__

    __getattr__:当找不到相应的属性的时候所作的逻辑操作。
    如果没有定义__getattr__方法,对于找不到的属性会直接报错。

    from datetime import date,datetime
    class User:
        def __init__(self,name,birthday):
            self.name = name
            self.birthday = birthday
    
    user = User("ming",date(year=1992,month=8,day=12))
    print(user.age)  #AttributeError: 'User' object has no attribute 'age'

    如果设置了相应的方法找不到相应的属性会执行相应的逻辑。

    from datetime import date,datetime
    class User:
        def __init__(self,name,birthday):
            self.name = name
            self.birthday = birthday
    
        def __getattr__(self, item):
            return "no find attr"
    
    user = User("ming",date(year=1992,month=8,day=12))
    print(user.age)  #no find attr

    当然不单单只是返回报错。

    from datetime import date,datetime
    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)
    
    
    info = {"company_name":"正名",
            "position":"Director",
            "family_information":{"father":"farmer","monther":'farmer'},
            "Working company":["百度","阿里","苹果"]}
    user = User("ming",date(year=1992,month=8,day=12),info)
    
    print(user.position)  #Director

    __getattribute__:会比__getattr__更加霸道,所有的属性访问都会走下面的逻辑,不论是否存在。

    from datetime import date,datetime
    class User:
        def __init__(self,name,birthday):
            self.name = name
            self.birthday = birthday
    
        def __getattr__(self, item):
            return self.info.get(item)
    
        def __getattribute__(self, item):
            return "test"
    
    user = User("ming",date(year=1992,month=8,day=12))
    
    print(user.position)  #test
    #不存在的也可以

    __getattribute__在某些时候想要控制属性访问还是特别有用的。

    三、属性描述符和属性的查找范围

    user.age看似简单,但是内部实现的原理并不简单,通过下面内容应该可以清楚user.age的整个生命周期。

    1.数据属性描述符

    我们在自定义一个属性的时候该如何对属性本身做一些限制了?
    我们要知道,类里面的所有方法都是针对于对象本身的,而不是针对这个属性的。
    因此,我们需要将属性也变成一个自定义的对象,这样我们就能对属性进行操作

    在ORM中,我们使用的是如下方式创建以及定义表:

    class BusinessUnitType(models.Model):
        """
        业务单元分类
        """
        name = models.CharField(max_length=64) 

    下面我们可以模拟上述操作。

    class CharField:
        def __init__(self,max_length=32):
            self.max_length = max_length
    
    class User:
        name= CharField(max_length=6)
    
    user = User()
    user.name = "str"
    print(user.name)

    但是现在还没有添加限制,比如必须是字符串,比如最大长度为6.

    class CharField:
        def __init__(self,max_length):
            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("name必须是字符串")
            if len(value) > self.max_length:
                raise ValueError("超过规定长度")
            if not isinstance(self.max_length,int):
                raise ValueError("max_length 必须是整数")
            self.value = value
    
        def __delete__(self, instance):
            del self.value
    
    
    class User:
        name= CharField(max_length=6)

    打印测试:

    user = User()
    user.name = 1  #ValueError: name必须是字符串
    user.name = "uuuuuuuu"  #ValueError: 超过规定长度
    user.name = "yannc"  #ValueError: max_length 必须是整数
    print(user.name)

    现在我们回过头来看看instance和value是什么

    instance是一个user对象,竟然将外层内当参数传进来了,现在还没用过。
    value就是name对象,或者说是值。当我们不知道怎么操作这些方法的时候,可以使用debug打印下到底做了什么。

    现在回过头来想,我们就必须这样吗?
    我们写一个使用常用的方式:

    class NewPerson:
        def __init__(self,name=None):
            self.name = name
    
        def __set__(self, instance, value):
            print("123")
    
    newperson = NewPerson()
    newperson.name = "kebi"  #这个操作根本就不会经过__set__魔法方法,__set__属于对象操作。

    我们可以定义一个set_name方法,对name的赋值进行限制,估且不说name.set_name("kebi")这种形式好不好,
    但是每一个属性的设置方法我都要进行定义,你觉得麻烦不?就不能newperson.name=""这种形式吗?

    对于属性的操作难道我们就毫无办法吗?
    其实可以使用@property,这个可以实现。

    class Person:
        @property
        def name(self):
            return self._name
    
        @name.setter
        def name(self,value):
            self._name = value
    
    person = Person()
    person.name = "kibi"
    print(person.name)

    虽然可以实现,但是如果一个类的属性非常多,那么这个类就会显得非常臃肿。
    对于属性的操作我们在类里面随便定义一个方法都可以操作,但是你把属性的值改变了之后,还是当前属性吗?

    现在再来看看:

    class CharField:
        def __init__(self,max_length):
            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("name必须是字符串")
            if len(value) > self.max_length:
                raise ValueError("超过规定长度")
            if not isinstance(self.max_length,int):
                raise ValueError("max_length 必须是整数")
            self.value = value
    
        def __delete__(self, instance):
            del self.value
    
    class User:
        name= CharField(max_length=6)

    name对于User来说是一个属性,但是同时也是一个对象,给一个对象赋值name="kebi",name还是CharField对象吗?
    这就要说到属性操作符,CharField既是一个类,也是一个属性描述符,这个一个有点特别的类。
    为什么这样说了?name可以说是CharField的对象,但是又str的对象,CharField更像是一个辅助类。
    什么是属性描述符?只要是__get__、__set__、__delete__三个方法中的任意一个就是一个属性描述符。、

    2.非数据属性描述符

    当你只使用__get__方法来构造描述符的时候,你所构造的描述符就是一个非数据描述符。

    class CharField:
        def __init__(self,max_length):
            self.max_length = max_length
    
        def __get__(self, instance, owner):
            return self.value
    class User:
        name = CharField()

    3.属性查找过程

    class CharField:
        pass
        
    class User:
        name = CharField()

    对于数据属性描述符、非数据属性描述符、类属性和对象属性的优先级说明:
    如果user = User(),那么user.age的调用顺序如下:

    (1)如果age在User类或者基类中,并且是数据属性描述符,那么调用其__get__方法。

    关于属性描述符的定义:

    class CharField:
        def __get__(self, instance, owner):
            return self.value
        def __set__(self, instance, value):
            pass
        def __delete__(self, instance):
            pass
    
    class User:
        name= CharField()
        
    user = User()
    user.name = “kebi”

    (2)如果age在user的__dict__中,那么以return self.age的方式调用。
    出现在user的__dict__中,可能有三种情况:

    class User:
        def __init__(self,name):
            #方法1
            self.name = name
    
    user = User('ming')
    #方法2
    user.age = 28
    # 方法3:回溯机制
    user.__dict__['addr'] = "罗田"

    (3)如果age出现在User类或者基类__dict__中,并且是非数据属性描述符,那么调用其__get__方法。

    class CharField:
        def __get__(self, instance, owner):
            return self.value

    (4)如果age出现在User类或者基类__dict__中,不是描述符,以return self.age调用。

    (5)如果User有__getattr__方法,调用__getattr__

    (6)抛出AttributeError异常

    class User:
        pass
    
    user = User()
    print(user.name)  #AttributeError: 'User' object has no attribute 'name'

    优先级:

      数据属性描述符 > 对象属性 > 非数据属性描述符 > 常规类属性 > __getattr__ 

    下面是一个示例应该可以更好的阐述。

    from datetime import date,datetime
    
    class Name1():
        """数据属性描述符"""
        def __get__(self, instance, owner):
            pass
        def __set__(self, instance, value):
            pass
        def __delete__(self, instance):
            pass
    
    class Name3():
        """非数据属性描述符"""
        def __get__(self, instance, owner):
            pass
    
    class User:
        name1 = Name1()
        name3 = Name3()
        """普通类对象"""
        name4 = "kebi"
        def __init__(self,name,birthday):
            self.name2 = name
            self.birthday = birthday
    
        def __getattr__(self, item):
            return "56789"
    
        def __getattribute__(self, item):
            #第一步:调用数据属性描述符name1
            #第二步:调用self.name2
            #第三步:调用非数据属性描述符name3
            #第四步:调用类属性name4
            #第五步:调用__getattr__方法
            #第六步:抛出AttributeError
            return "123"
    
    user = User("ming",date(year=1992,month=8,day=12))

    从上述示例中,关于user.age获取操作都是在__getattribute__方法中实现。
    下面示例会证明上述过程:
    示例1:

    class User:
        def __getattr__(self, item):
            return "__getattr__"
    
    user = User()
    print(user.name)

    示例2:类属性 > __getattr__

    class User:
        name = "class_attr"
    
        def __getattr__(self, item):
            return "__getattr__"
    
    user = User()
    print(user.name)

    示例3:非数据描述符 > 类属性

    class Name:
        def __get__(self, instance, owner):
            return "Non-data attribute descriptor"
    
    class User:
        name = Name()
    
        def __getattr__(self, item):
            return "__getattr__"
    
    user = User()
    print(user.name)

    示例4:对象属性 > 非数据属性描述符

    class Name:
        def __get__(self, instance, owner):
            return "Non-data attribute descriptor"
    
    class User:
        def __init__(self):
            self.name = "object_attr"
    
    user = User()
    print(user.name)

    示例5:数据属性描述符 > 对象属性

    class Name:
        def __get__(self, instance, owner):
            return "data attribute descriptor"
        def __set__(self, instance, value):
            pass
    
    class User:
        name = Name()
        def __init__(self):
            self.name = "object_attr"
    
    user = User()
    print(user.name)

    最后来看一个示例:

    class Name:
        def __get__(self, instance, owner):
            return "Non-data attribute descriptor"
        def __set__(self, instance, value):
            pass
    
    class User:
        name = Name()
        def __init__(self):
            self.name = "object_attr"
    
        def __getattr__(self, item):
            return "__getattr__"
    
        def __getattribute__(self, item):
            return "123"
    user = User()
    print(user.name)  #"123"

    你可能会纳闷,为啥是这个?因为__getattribute__屏蔽了所有,
    整个属性调用本身就在__getattribute__中进行,你现在重写,不就是覆盖了原有操作了吗。

    还有一点需要注意的是,当你使用属性描述符设置值的时候,属性并不会出现在__dict__中。

    class Name:
        def __get__(self, instance, owner):
            return self.name
        def __set__(self, instance, value):
            self.name = value
    
    class User:
        name = Name()
    
    user = User()
    user.name = "maoxian"
    print(user.name)  #maoxian
    print(user.__dict__)  #{}

    当你使用__dict__来添加属性的时候,尤其是混合数据操作符的时候,可能会有以下异常:

    class Name:
        def __get__(self, instance, owner):
            return self.name
        def __set__(self, instance, value):
            self.name = value
    
    
    class User:
        name = Name()
    
    user = User()
    user.__dict__["name"] = "maoxian"
    print(user.__dict__)  #{'name': 'maoxian'}
    #当你在调用的user.nage的时候,优先还是先找数据属性描述符
    print(user.name)  #AttributeError: 'Name' object has no attribute 'name'
    #当然下面方法还是可用:
    user.__dict__['name']

    四、__init__和__new__的区别

     __init__:构造器,用来初始化对象的信息
    __new__:在对象创建之前被创建。如果__new__没有返回值,那么也不会执行__init__函数。

    class User:
        def __new__(cls, *args, **kwargs):  #这个cls代表当前类User
            print("new")
    
        def __init__(self):  #self代表当前对象
            print("init")
    
    user = User()
    #new  这里根本不会执行__init__函数

    给__new__一个返回值就可以。

    class User:
        def __new__(cls, *args, **kwargs):
            return super().__new__(cls)
    
        def __init__(self):
            print("init")
    
    user = User()

    __new__只有在新式类中实现

    五、自定义元类

    1.动态创建类的方式

    (1)通过函数创建

    def create_class(name):
        if name == "User":
            class User:
                def __str__(self):
                    return "User"
            return User
    
        else:
            class Person:
                def __str__(self):
                    return "User"
    
            return Person
    
    
    User = create_class("User")
    print(User)
    user = User()
    print(user)

    (2)通过type来动态创建类
    type中提供了三种创建方法:

    type(object_or_name, bases, dict)
    type(object) -> the object's type
    type(name, bases, dict) -> a new type

    下面是实现示例:
    创建一个类:

    User = type("User",(),{})
    print(User)

    定义一个属性:

    User = type("User",(),{"name":"mao"})
    user = User()
    print(user.name)

    定义一个方法:

    def see(self):  #这里必须要传递一个self参数
        print("I see a dog")
    
    User = type("User",(),{"name":"mao","see":see})
    user = User()
    user.see()

    定义一个父类:

    class Anlu:
        def go_to_anlu(self):
            print("go to anlu")
    User = type("User",(Anlu,),{"name":"mao","see":see})
    user = User()
    user.go_to_anlu()

    2.元类

    元类就是创建类的类。比如type就是一个元类

    a = 1
    print(type(a))  #<class 'int'>
    print(type(int))  #<class 'type'>
    
    b = [1,2,3]
    print(type(b))  #<class 'list'>
    print(type(list))  #<class 'type'>

    python中的int、str、list、dict、class都是type创建。

    六、通过元类实现一个简单的ORM

    在使用元类编程的时候,必须继承type类,一般在元类里面都是重写__new__方法,
    用于在对象初始化之前做一些操作,比如数据的整理。

    class IntegerField():
        def __init__(self,db_column,max_length=0):
            self._value = None
            self.max_length = max_length
            self.db_column = db_column
    
            if max_length:  #0 == Flase
                if not isinstance(max_length,numbers.Integral):
                    raise ValueError("max_length must be int")
                elif max_length < 0:
                    raise ValueError("max_length > 0")
            else:
                raise ValueError("max_length must bi have")
    
    
        def __get__(self, instance, owner):
            return self._value
    
    
        def __set__(self, instance, value):  #这个value = name
            if not isinstance(value,numbers.Integral):
                raise ValueError("value must be int")
            if value < 0:
                raise ValueError("value must greater than 0")
            elif value < pow(10,self.max_length) -1:
                raise ValueError("value should not be greater than max")
    
    
        def __delete__(self, instance):
            del self._value
    
    
    class CharField():
        def __init__(self,db_column,min_length=0,max_length=0):
            self._value = None  #_value是name的值,这个一般都会预留
            self.db_column = db_column
            self.min_length = min_length
            self.max_length = max_length
            if max_length:
                if not isinstance(max_length,numbers.Integral):
                    raise ValueError("max_length must be int")
                elif max_length < 0:
                    raise ValueError("max_length > 0")
            else:
                raise ValueError("max_length must bi have")
    
            if min_length:
                if not isinstance(min_length,numbers.Integral):
                    raise ValueError("min_length must be int")
                elif min_length < 0:
                    raise ValueError("min_length > 0")
                elif min_length > max_length:
                    raise ValueError("min_length less than max_length")
    
        def __get__(self, instance, owner):
            return self._value
    
        def __set__(self, instance, value):
            if not isinstance(value,str):
                raise ValueError("value must be str")
            if len(value) > self.max_length or len(value) < self.min_length:
                raise ValueError("Value length is error")
            self._value = value
    
        def __delete__(self, instance):
            del self._value
    
    
    class Student():
        name = CharField(db_column="",min_length=1,max_length=32)
        age = IntegerField(db_column="",max_length=32)
    
        class Meta:
            db_table = "student"

    上方代码通过属性描述符实现了对数据的检测功能。
    虽然上面有了ORM的雏形,但是还需要对一些初始信息进行封装。比如字段信息
    创建元类:ModelMetaClass

    class ModelMetaClass(type):
        def __new__(cls, name,bases,attrs, **kwargs):
            if name == "BaseModel":
                return super().__new__(cls, 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()
            #如果student中有Meta配置
            if attrs_meta:
                #取出db_table这个属性
                table = getattr(attrs_meta,"db_table",None)
                if table:
                    db_table = table  #存在就会重新赋值
            _meta["db_table"] = db_table
            #重新整理attrs中的属性
            attrs["_meta"] = _meta
            attrs["fields"] = fields
            del attrs["Meta"]
            return super().__new__(cls, name, bases, attrs, **kwargs)

    这就玩了吗?还需要一些初始化的改动,将初始化信息单独封装起来成BaseModel

    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 not db_column:
                    db_column = key.lower()
                fields.append(db_column)
                value = getattr(self,key)
                values.append(str(value))
            sql = "insert into {db_table} ({fields}) value({values})".format(db_table=self._meta["db_table"],
                                                                             fields=",".join(fields),
                                                                             values=",".join(values))
            pass

    下面是完整代码:

    import numbers
    
    class Field:
        pass
    
    class IntegerField(Field):
        # 属性描述符做字段验证
        def __init__(self,db_column,max_length=0):
            self._value = None
            self.max_length = max_length
            self.db_column = db_column
    
            if max_length:  #0 == Flase
                if not isinstance(max_length,numbers.Integral):
                    raise ValueError("max_length must be int")
                elif max_length < 0:
                    raise ValueError("max_length > 0")
            else:
                raise ValueError("max_length must bi have")
    
    
        def __get__(self, instance, owner):
            return self._value
    
    
        def __set__(self, instance, value):  #这个value = name
            if not isinstance(value,numbers.Integral):
                raise ValueError("value must be int")
            if value < 0:
                raise ValueError("value must greater than 0")
            elif value < pow(10,self.max_length) -1:
                raise ValueError("value should not be greater than max")
    
    
        def __delete__(self, instance):
            del self._value
    
    
    
    class CharField(Field):
        #属性描述符做字段验证
        def __init__(self,db_column,min_length=0,max_length=0):
            self._value = None  #_value是name的值,这个一般都会预留
            self.db_column = db_column
            self.min_length = min_length
            self.max_length = max_length
            if max_length:
                if not isinstance(max_length,numbers.Integral):
                    raise ValueError("max_length must be int")
                elif max_length < 0:
                    raise ValueError("max_length > 0")
            else:
                raise ValueError("max_length must bi have")
    
            if min_length:
                if not isinstance(min_length,numbers.Integral):
                    raise ValueError("min_length must be int")
                elif min_length < 0:
                    raise ValueError("min_length > 0")
                elif min_length > max_length:
                    raise ValueError("min_length less than max_length")
    
        def __get__(self, instance, owner):
            return self._value
    
        def __set__(self, instance, value):
            if not isinstance(value,str):
                raise ValueError("value must be str")
            if len(value) > self.max_length or len(value) < self.min_length:
                raise ValueError("Value length is error")
            self._value = value
    
        def __delete__(self, instance):
            del self._value
    
    class ModelMetaClass(type):
        #
        def __new__(cls, name,bases,attrs, **kwargs):
            if name == "BaseModel":
                return super().__new__(cls, name, bases, attrs, **kwargs)
            #重新整理字段,这样方便表结构的初始化
            fields = {}
            for key,value in attrs.items():
                if isinstance(value,Field):  #找出字段,为了方便验证重新定义Field类
                    fields[key] = value
            attrs_meta = attrs.get("Meta",None)
            _meta = {}
            db_table = name.lower()
            #如果student中有Meta配置
            if attrs_meta:
                #取出db_table这个属性
                table = getattr(attrs_meta,"db_table",None)
                if table:
                    db_table = table  #存在就会重新赋值
            _meta["db_table"] = db_table
            #重新整理attrs中的属性
            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):  #支持Student(name=xxx)这个赋值操作
            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 not db_column:
                    db_column = key.lower()
                fields.append(db_column)
                value = getattr(self,key)
                values.append(str(value))
            sql = "insert into {db_table} ({fields}) value({values})".format(db_table=self._meta["db_table"],
                                                                             fields=",".join(fields),
                                                                             values=",".join(values))
            pass
    
    class Student(BaseModel):
        name = CharField(db_column="",min_length=1,max_length=32)
        age = IntegerField(db_column="",max_length=32)
    
        class Meta:
            db_table = "student"
    
    
    student = Student()
    student.name = "kebi"
    print(student.name)
    student.save()
  • 相关阅读:
    《拼音字母》 蓝桥杯复试试题
    ZT:成熟是一种明亮而不刺眼的光辉
    如何Enable FireFox里的Java Plugin
    将App发布到WasLiberty的较稳妥方法
    记一个发HTML格式邮件的问题
    有些工作,做一辈子也不会成功
    论本事
    又一次遇到Data truncation: Data too longData truncation: Data too long问题
    查看Linux上MySQL版本信息
    很多人终身一事无成
  • 原文地址:https://www.cnblogs.com/yangmingxianshen/p/11288738.html
Copyright © 2020-2023  润新知