元类编程

一、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()
原文地址:https://www.cnblogs.com/yangmingxianshen/p/11288738.html