Python进阶7

元类编程

Python属性函数

引言

  • Python中我们对于自己设置的类,尽量要使其属性私有化,获得更好的封装性。
  • 如果要访问和修改私有属性,要为其设置setget方法。
  • Python中,可以使用特殊的装饰器将setget方法属性化,这样就能够使用更简洁的语法去调用这些方法。

使用案例

class Person:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age
        
    # 这里以 age 为例
    @property
    def age(self):
        return self.__age
        
    @age.setter
    def age(self, age):
        if age < 0:
            self.__age = age
        elif age > 120:
            self.__age = 120
        else:
            self.__age = age

me = Person("MetaTian", 20)

print(me.age)   # 直接作为属性进行调用,get方法
me.age += 1     # get 和 set 方法同时使用
print(me.age)       # get 方法

# result:
# 20
# 21

魔法函数getattr()和getattribute()

引言

  • 这两个函数是解释器在查找对象属性时要进行调用的
  • 如果没找到代码需要的属性,则会调用__getattr__()
  • 如果实现了__getattribute__(),则不管请求什么属性都会先调用这个魔法函数

使用案例

"""
如果没有实现 __getattr__(),调用未定义的属性后,会报错
"""
class Person:
    pass
    
me = Person()
print(me.age)

# result:
# AttributeError: 'Person' object has no attribute 'age'
class Person:
    # attr 是代码请求的属性
    def __getattr__(self, attr):
        return "{0} dose not exist".format(attr)
    
me = Person()
print(me.age)
print(me.name)

# result:
# age dose not exist
# name dose not exist
"""
实现了__getattribute__()魔法函数
不论请求什么属性,都返回同样的值
"""
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def __getattribute__(self, attr):
        return "value"
        

me = Person("MetaTian", 19)
print(me.age, me.name, me.gender)

# result:
# value value value

属性描述符

前言

  • 描述符是对多个属性运用相同的逻辑来进行存取的一种方式,它是实现了特定魔法函数的一个类
  • 只要实现了__get__()__set__()__delete__()三个魔法函数中的任意一个,这个类就是描述符
  • property最大的缺点就是它修饰属性的过程不能重复使用,如果要对多个属性进行非负检查(>=0),那必须对每个属性的set方法分别包装。描述符就是可以重用的属性。

使用案例

"""
定义了一个 “非负的” 描述符
"""
class NonNegative:
    def __init__(self, label):
        self.label = label    # 存储描述符在对象级别的名称
    
    def __get__(self, instance, owner):
        # 当进行属性调用时,obj.p
        # instance = obj
        # owner = type(obj)
        return instance.__dict__.get(self.label)
    
    def __set__(self, instance, value):
        # 当进行属性调用时,obj.p = val
        # instance = obj
        # value = val
        if not isinstance(value, int):
            raise ValueError("Int value allowed")
        if value < 0:
            raise ValueError("Negative value not allowed: %s" % value)

        instance.__dict__[self.label] = value    # 把通过检验的属性值反向设置给调用对象

    def __delete__(self, instance):
        pass

"""
使用描述符的一个类
"""
class Person:
    # 定义为类的属性,才能触发后面的属性检查
    age = NonNegative("age")    # label

    def __init__(self, name, age):
        self.name = name
        self.age = age    # 描述符会起作用,自动调用get和set魔法函数


me = Person("MetaTian", "21")
# result:
# ValueError: Int value allowed

him = Person("Rity", -18)
# result:
# ValueError: Negative value not allowed: -21

me = Person("MetaTian", 21)
him = Person("Rity", 21)

him.age += 1
me.age -= 1
print(him.age, me.age)

# result:
# 22 20

小结

描述符其实是目标类的一个属性,也就是说目标类中有一个描述符实例。当目标类的实例准备操作自身属性时,会首先将它交给类的个描述符实例进行管理(__get__(), __set__(), __delete__()),然后由它把属性设置到实例中(obj.__dict__[label])。

属性调用顺序

引言

  • Python的描述符有两种类型:数据描述符非数据描述符
  • 数据描述符:实现了__get__()__set__()魔法函数。
  • 非数据描述符:只实现了__get__()魔法函数。
  • 使用不同的描述符,属性查找的过程是不一样的。

详细

在使用me.age操作属性的时候,解释器对属性age的查找顺序是怎么样的呢?

如果me是某个对象的实例,那么对于me.age或与其等价的getattr(me, "age")属性操作方式,会首先调用__getattribute__(),如果调用过程中抛出了AttributeError,这时就会调用__getattr__()

如果age是一个属性描述符,则相关属性操作操作会委托给__get__()魔法函数,这个过程发生在__getattribute__()内部

此时,age属性的调用顺序如下:

  • 如果age出现在Person或其基类的__dict__中,而且age是一个数据描述符,那么直接调用__get__()方法
  • 如果age出现在me__dict__中,那么直接返回me.__dict__['age']
  • 如果age出现在Person或其基类的__dict__中:
    • 如果age非数据描述符,那么就调用__get__
    • 否则返回Person.__dict__['age']
  • 如果定义了__getattr__(),则调用__getattr__()
  • 否则,抛出AttributeError

__new __和 __init __的区别

引言

  • __new__()允许我们在的生成过程中加入自己的逻辑,是一个静态方法。
  • __init__()可以在生成对象之后加入自己的逻辑,一般是初始化属性。
  • __init__()的调用在__new__()之后。

使用案例

"""
new 用来控制对象的生成过程,在对象生成之前起作用
init 用来完善生成的对象,在对象生成之后调用
如果 new 中没有返回生成的对象,则 init 方法不会调用
"""
def Person:
    def __new__(cls, *args, **kwargs):
        print("---in new---")
        return super.__new__(cls)
        
    def __init__(self, name):
        print("---in init---")
        self.name = name

me = Person("MetaTian")

# result:
# ---in new---
# ---in init---

自定义元类

引言

  • 前面介绍过,Python中一切皆对象,也是一个对象,在Java中想要动态生成一个类,似乎并不容易,但是对于动态语言Python,这是比较容易实现和理解的。
def create_cls(name):
    if name = "User":       # 动态生成的 User 类
        class User:
            def __str__(self):
                return "user"
        return User
    elif name = "Person":       # 动态生成的 Person 类
        class Person:
            def __str__(self):
                return "person"
        return Person


ClsUser, ClsPson = create_cls("User"), create_cls("Person")
user, person = ClsUser(), ClsPson()
print(user, person)

# result:
# user person
  • 使用type动态地创建类会更加简洁。
  • 但是实际使用,往往很少直接使用type,而是用自己定义的元类

使用案例

"""
class type(name, bases, dict)
name: str, 要创建类的名称
bases: tuple, 创建类要继承的基类
dict: 创建类的属性集合
"""
# 必须有 self 作为参数
def say(self):
    print("I am a person")

Person = type("Person", (), {"name":"MetaTian", "say":say})
me = Person()

print(me.name)
me.say()

# result:
# MetaTian
# I am a person

Python中类的创建过程中,会首先寻找metaclass,通过metaclass去创建这个类,如果没有找到metaclass,则向上找基类,使用它们的meataclass,如果都没有,再直接调用type来创建这个类。

# 这是我们自己定义的一个元类
class MetaClass(type):
    def __new__(cls, *args, **kwargs):
        # 这里不加入其它逻辑,只构建一个框架,委托给 type 进行类的构建
        return super().__new__(cls, *args, **kwargs)    # 这里需要传递参数
    
# 用我们自己定义的元类来控制 Person 类的构建
class Person(metaclass=MetaClass):
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return "I am {name}".format(name=self.name)


me = Person("MetaTian")
print(me)

# result:
# I am MetaTian

通过元类实现ORM

引言

  • 什么是ORM

使用案例

"""
需求
我们想构建出一个 Person 类,构造其一个实例后
通过obj.save()方法,就可以将它存储在数据库中
"""
class Person:
    
    # 这里的属性对应数据库表中的每一项
    # 在表中的哪一列,这一列数据的约束条件是什么
    name = CharField(db_colunm=None, max_length=None)
    
    # Person 类中,定义了另外一个类,用来存放其他的一些信息
    class Meta:
        db_table = "person"     # 这里存放了对象对应的数据库表名,用来后面拼凑 sql 语句


me = Person("MetaTian")
me.save()   # 在数据库中插入一条记录

首先,我们来构建数据描述符,数据的存储以及类型检查都委托给它来完成,把简单的调用方式暴露出来即可。这里不仅仅要处理需存储的数据,数据在数据库中的一些属性也要进行处理,比如所在的column和存储格式限制。

"""
分析
类中的属性要采用描述符的方式,在存取数据的时候要进行类型检查
不仅要保存属性值的信息,还要存储属性在数据库表中的信息(max_length, db_column)
"""
class CharField:
    def __init__(self, max_length=None, db_column=None):
        self.db_column = dbcolumn
        self.max_length = max_length
        self._value = None      # 描述符初始化的时候不给默认值,一般由用户在后面赋值
        
        # 将这两个属性设置为必填项
        if max_length is None:
            raise ValueError("max_length info required")
        if db_column is None:
            raise ValueError("db_column info required")
    
    # 返回值
    def __get__(self, instance, owner):
        return self._value
    
    # 设置值
    def __set__(self, instance, value):
        if not isinstance(value, str):
            raise ValueError("String value required")
        if len(value) > self.max_length:
            raise ValueError("valuen length invalid")
        
        self._value = value     # 通过检查后,赋值保存
"""
现在我们有了如下结构:
还需要自己定义的一个元类来控制 Person 类的生成
"""
class CharField:
    pass
    
class Person:
    name = CharField(max_length=10, db_colunm="name")   # name 属性的存取检查已经完成
    
    class Meta:
        db_table = "person"     # 这个类对象和 person 这个数据库表对应

自定义的元类,要修改__new__()方法,在类的创建过程中加入一些我们自己的逻辑。

class ModelMetaClass(type):
    """
    这里对参数元组进行了拆解,便于更好的观察
    name: 要构建类对象的名称
    bases: 继承的基类
    attrs: 构建类对象中的属性,我们要从中提取出和业务逻辑有关的属性进行另外存储
    """
    def __new__(cls, name, bases, attrs, **kwargs):
        for k, v in attrs.items():
            print("{key}:{val}".format(key=k, val=v))
        return super().__new__(cls, name, bases, attrs, **kwargs)


# result:
# __qualname__:Person
# __module__:__main__
# Meta:   # 这是我们在 Person 中定义的类,存放信息
# name:<__main__.CharField object at 0x00000226B18B4F28>    # 这是我们需要的
class ModelMetaClass(type):
    def __new__(cls, name, bases, attrs, **kwargs):
    
        # 抽离出数据
        fields = {}
        for k, v in attrs.items():
            if isinstance(v, CharField):    # 数据域
                fields[k] = v
                
        for k in fields:
            del attrs[k]        # 清空原来的数据
        
        # 抽离出表名称
        attrs_meta = attrs.get("Meta", None)    # 关于字典 get 方法的使用,前面讲过
        db_table = name.lower()     # 数据库表名默认是类的小写形式
        if attrs_meta:      # 如果类存储了表信息,用类中定义的
            db_table = getattr(attrs_meta, "db_table")
            
        del attrs["Meta"]   # 表名称信息已经获得,这里就不需要了
            
        # 重组 attrs 参数
        _meta = {"db_table":db_table}
        attrs["_meta"] = _meta
        attrs["fields"] = fields
        
        for k, v in attrs.items():
            print("{key}:{val}".format(key=k, val=v))
    
        # 最后委托给 type ,完成类的创建
        return super().__new__(cls, name, bases, attrs, **kwargs)

# result:
# __module__:__main__
# fields:{'name': <__main__.CharField object at 0x000001AFCD994F98>}
# __qualname__:Person
# _meta:{'db_table': 'person'}
"""
现在我们有了如下结构:
"""
# 控制类的生成
class ModelMetaClass(type):
    pass
    

# 检查属性存取
class CharField:
    pass
    

class Person(metaclass=ModelMetaClass):
    name = CharField(max_length=10, db_colunm="name")   # name 属性的存取检查已经完成
    
    class Meta:
        db_table = "person"     # 这个类对象和 person 这个数据库表对应

最后,还要在Person类中添加我们需要的逻辑,比如初始化操作、写入数据库操作、查询操作等。因此要为每一个逻辑添加一个方法加入类中,但是会让这个类显得十分臃肿。我们构建这个类的目的就是希望它能和数据库的表结构尽量一一对应,简化我们的操作,同时不同类的操作需求是一样的,因此,可以考虑将这些操作逻辑抽象成一个父类。

class BaseModel(metaclass=ModelMetaClass):
    # 便于通用,这里要约定初始化的一种方式
    def __init__(self, *args, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)
        super.__init__()
        
    def save(self):
        fields, values = [], []
        for k, v in self.fields.items():
            fields.append(v.db_column)
            values.append(str(getattr(self, k)))    # 把所有的值都变成字符串,便于后面拼接
        
        sql = "insert {table}({fields}) value({values})".format(table=self._meta["db_table"], fields=",".join(fields), values=",".join(values))
    
        print(sql)
        # todo 和数据库连接有关的逻辑
        

class Person(BaseModel):
    name = CharField(max_length=10, db_colunm="name")   # name
    
    class Meta:
        db_table = "person"     # 这个类对象和 person 这个数据库表对应


me = Person(name="MetaTian")
me.save()

完整的代码

class CharField:
    def __init__(self, max_length=None, db_column=None):
        self.db_column = db_column
        self.max_length = max_length
        self._value = None
        
        if max_length is None:
            raise ValueError("max_length info required")
        if db_column is None:
            raise ValueError("db_column info required")
    
    def __get__(self, instance, owner):
        return self._value

    def __set__(self, instance, value):
        if not isinstance(value, str):
            raise ValueError("String value required")
        if len(value) > self.max_length:
            raise ValueError("valuen length invalid")
        
        self._value = value


class ModelMetaClass(type):
    def __new__(cls, name, bases, attrs, **kwargs):
        # BaseModel和其子类都要通过这个元类来进行创建
        # 子类才有相关的 Meta 信息,进行信息重组,这里进行过滤
        if name == "BaseModel":
            return super().__new__(cls, name, bases, attrs, **kwargs)

        # 抽离出数据
        fields = {}
        for k, v in attrs.items():
            if isinstance(v, CharField):
                fields[k] = v

        for k in fields:
            del attrs[k]
        
        # 抽离出表名称
        attrs_meta = attrs.get("Meta", None)    # 关于字典 get 方法的使用,前面讲过
        db_table = name.lower()     # 数据库表名默认是类的小写形式
        if attrs_meta:      # 如果类存储了表信息,用类中定义的
            db_table = getattr(attrs_meta, "db_table")
            
        # 重组 attrs 参数
        _meta = {"db_table":db_table}
        attrs["_meta"] = _meta
        attrs["fields"] = fields
        del attrs["Meta"]   # 表名称信息已经获得,这里就不需要了
    
        # 最后委托给 type ,完成类的创建
        return super().__new__(cls, name, bases, attrs, **kwargs)


class BaseModel(metaclass=ModelMetaClass):
    # 便于通用,这里要约定初始化的一种方式
    def __init__(self, *args, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)
        super().__init__()
        
    def save(self):
        fields, values = [], []
        for k, v in self.fields.items():
            fields.append(v.db_column)
            values.append(str(getattr(self, k, None)))    # 把所有的值都变成字符串,便于后面拼接
        
        sql = "insert {table}({fields}) value({values})".format(table=self._meta["db_table"], fields=",".join(fields), values=",".join(values))
        
        # todo 和数据库有关的逻辑
        print(sql)

class Person(BaseModel):
    name = CharField(max_length=10, db_column="name")   # name 属性的存取检查已经完成
    
    class Meta:
        db_table = "person"     # 这个类对象和 person 这个数据库表对应



me = Person(name="MetaTian")
me.save()

# result:
# insert person(name) value(MetaTian)

你可能感兴趣的:(Python进阶7)