property动态属性、属性描述符、元类

《Python3高级核心技术97讲,bobby》学习笔记,第七章:property动态属性、属性描述符、元类。
383阅读 · 2020-6-12 23:22发布

7.1 property动态属性

  • @property装饰器可以让一个函数变为属性描述符。
  • @x.setter装饰器可以修饰x函数为x属性设置值。
  • 代码示例:

    from datetime import date, datetime
      class User:
          def __init__(self,name,birthday):
              self.name = name
              self.birthday = birthday
              self._age = 0
    
          @property
          def age(self):
              return datetime.now().year - self.birthday.year
    
          @age.setter
          def age(self,value):
              self._age = value
    
      if __name__ == "__main__":
          user = User("test",date(year=2000, month=1, day=1))
          user.age = 30
          print(user._age)
          print(user.age)
    

7.2 __getattr__和__getattribute__魔法函数

  • __getattr__:查找不到属性的时候调用该魔法函数。
  • __getattribute__:查找属性的时候会调用该函数。
  • 在执行user.age属性的时候,会先调用__getattribute__,在__getattribute__抛出AttributeError异常时,会调用__getattr__。

7.3 属性描述符和属性查找过程

  • 属性描述符:实现__get__、__set__和__delete__中任意一个魔法方法,就成为属性描述符。
  • 可以通过属性描述符对属性的值进行检查(校验)。代码示例:

    from datetime import date, datetime
      import numbers
    
      class IntField:
          def __get__(self, instance, value):
              return self.value
    
          def __set__(self, instance, value):
              if not isinstance(value,numbers.Integral):
                  raise ValueError("int value need")
              self.value = value
    
          def __delete__(self, instance):
              pass
    
      class User:
          age = IntField()
    
      if __name__ == "__main__":
          user = User()
          user.age = "aa"
          print(user.age)
    
  • 如果实现了get和set则该属性描述符为数据描述符,否则为非数据属性描述符。
  • user.age等同于getattr(user, 'age')。

属性查找过程:user = User(),user.age顺序如下

  1. 如果"age"出现在User或其基类的__dict__中,且age是data descriptor(数据描述符),那么调用其__get__方法,否则
  2. 如果"age"出现在user的__dict__中,那么直接返回obj.__dict__['age'],否则
  3. 如果"age"出现在User或其基类的__dict__中
    • 如果age是non-date descriptor(非数据描述符),那么调用其__get__方法,否则
    • 返回__dict__['age']
  4. 如果User有__getattr__方法,调用__getattr__方法,否则
  5. 抛出AttributeError

7.4 __new__和__init__的区别

  • __new__:控制对象的生成过程。
  • __init__:在生成对象之后,可以对对象进行操作(完善对象)。
  • __new__的参数传递的是类,__init__传递的参数是对象。
  • 如果__new__方法不返回对象,则不会调用__init__函数。
  • 代码示例:

    class User:
          def __new__(cls, *args, **kwargs):
              print("new")
              return super().__new__(cls)
    
          def __init__(self,name):
              print("init")
              self.name = name
    
      if __name__ == "__main__":
          user = User(name="test")
    

7.5 自定义元类

  • 元类是创建类的类。
  • 类继承了type就是元类,元类用于控制类的实例化过程。
  • 类通过metaclass属性来指定元类。
  • 类的实例化过程:

  • 如果有metaclass属性,则调用metaclass创建实例。

  • 如果当前类没有,则会向基类查找metaclass属性(基类有metaclass则调用基类的metaclass创建实例)。
  • 如果基类没有metaclass,则会在模块中查找。
  • 如果都找不到则会调用type创建类(全局唯一)。

  • 代码示例:

    class MetaClass(type):
          def __new__(cls,*args,**kwargs):
              print('metaclass new')
              return super().__new__(cls, *args, **kwargs)
    
      class User(metaclass=MetaClass):
          def __init__(self, name):
              self.name = name
    
          def __str__(self):
              return self.name
    
      if __name__ == "__main__":
          my_obj = User('test')
          print(my_obj.name)
    

7.6 通过元类实现orm

  • 实现类似django中model的orm操作。
  • 定义一个类,该类代表数据库中的一张表。通过元类和属性描述符映射数据库中的字段和操作。

思路和实现的步骤如下:

  1. 先创建和django类似的类,该类映射数据库中的字段。

    class User:
         Name = CharField(db_column="name",max_length=10)  # 对应mysql中char类型
         Age = IntField(db_column="age",min_value=0, max_value=100)  # 对应mysql中int类型
    
         class Meta:  # 表的基础信息
             db_table = "user"
    
     if __name__ == '__main__':
         # 后续通过下面的方式就可以对数据库进行新增操作
         user = User()
         user.name = "tom"
         user.age = 30
         user.save()
    
  2. 创建对应的CharField和IntField类(数据描述符),分别实现这两种字段类型的逻辑。

    import numbers
    
     class CharField:
         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 charfiled")
             self.max_length = max_length
    
         def __get__(self, instance, owner):
             return self._value
    
         def __set__(self, instance, value):
             if not isinstance(value, str):  # 检查后续传递的值是否为int类型
                 raise ValueError("string value need")
             if len(value) > self.max_length:
                 raise ValueError("value len excess len of max_length")
             self._value = value
    
     class IntField:
         # 数据描述符
         def __init__(self,db_column, min_value=None,max_value=None):  # 支持配置最小值和最大值
             self._value = None  # 属性描述符传递的值
             self.db_column = db_column
             # 判断min_value是否为int,是否大于0
             if min_value is not None:
                 if not isinstance(min_value,numbers.Integral):
                     raise ValueError("min_value must be int")
                 if min_value < 0:
                     raise ValueError("min_value must be positive int")
             # 判断max_value是否为int,是否大于0
             if max_value is not None:
                 if not isinstance(max_value,numbers.Integral):
                     raise ValueError("max_value must be int")
                 if max_value < 0:
                     raise ValueError("max_value must be positive int")
             # 判断max_value是否大于min_value
             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")
             self.min_value = min_value
             self.max_value = max_value
    
         def __get__(self, instance, owner):
             return self._value
    
         def __set__(self, instance, value):
             if not isinstance(value, numbers.Integral):  # 检查后续传递的值是否为int类型
                 raise ValueError("int value need")
             if value < self.min_value or value > self.max_value:
                 raise ValueError("value must between min_value and max_value")
             self._value = value
    
  3. 通过元类把属性按照表字段和表属性进行分类,使用基类定义多个数据库操作属性。

    • 写法优化:在元类中对new的可变参数进行拆包:name(类名)、bases(基类)、attrs(属性)
      class ModelMetaClass(type):
            def __new__(cls, name, bases, attrs, **kwargs):
                pass
      
    • 封装字段属性和表属性:将数据表字段相关属性和数据表属性分别单独存储,通过是否有继承基类来判断是否为数据库字段属性(创建Field类,CharField和IntField继承后通过instance可判断是否为数据库字段属性)。

      class Field:
            pass
      
        class CharField(Field):
            ...省略...
      
        class IntField(Field):
            ...省略...
      
        class ModelMetaClass(type):
            def __new__(cls, name, bases, attrs, **kwargs):
                fields = {}  # 将字段相关存储到field
                for key,value in attrs.items():
                    if isinstance(value, Field):  # 如果对象继承了Field则表示是自定义的数据库字段类
                        fields[key] = value
                attrs_meta = attrs.get("Meta", None)
                _meta = {}  # 将表字段属性存储到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
                attrs["fields"] = fields
                attrs["_meta"] = _meta
                del attrs["Meta"]
                return super().__new__(cls, name, bases, attrs, **kwargs)
      
    • 初始化和定义Model的基本属性:使用__init__魔法方法让类支持创建对象时就设置属性,也可以增加数据库操作属性:定义一个BaseModel,BaseModle指定了元类,User类继承BaseModel(根据属性查找顺序,依然会使用元类生成对象)

      class ModelMetaClass(type):
            def __new__(cls, name, bases, attrs, **kwargs):
                if name == "BaseModel":  # 如果是BaseModel,没有fields等字段,所以无需做后续处理
                    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)  # 给self添加属性
                super().__init__()
      
            def save(self):
                pass
      
        class User(BaseModel):
            ...省略...
      
        if __name__ == '__main__':
            user = User(name="tom",age=30)
            #user.name = "tom"
            #user.age = 30
            user.save()
      
  4. 编写数据库操作方法:save()。(其他的方法编写即可,类似哈)

    class BaseModel(metaclass=ModelMetaClass):
         def __init__(self, *args, **kwargs):
             for key,value in kwargs.items():
                 setattr(self, key, value)  # 给self添加属性
             return super().__init__()
    
         def save(self):
             fields = []  # 存放insert中字段名
             values = []  # 存放insert中的值
             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))
             sql = "insert {db_table}({fields}) value({values})".format(db_table=self._meta["db_table"], fields=",".join(fields), values=",".join(values))
             print(sql)
    
  5. 全部代码: ```python import numbers

    class Field:

     pass
    
    

    class ModelMetaClass(type):

     def __new__(cls, name, bases, attrs, **kwargs):
         if name == "BaseModel":  # 如果是BaseModel,没有fields等字段,所以无需做后续处理
             return super().__new__(cls, name, bases, attrs, **kwargs)
         fields = {}  # 将字段相关存储到field
         for key,value in attrs.items():
             if isinstance(value, Field):  # 如果对象继承了Field则表示是自定义的数据库字段类
                 fields[key] = value
         attrs_meta = attrs.get("Meta", None)
         _meta = {}  # 将表字段属性存储到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
         attrs["fields"] = fields
         attrs["_meta"] = _meta
         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)  # 给self添加属性
         return super().__init__()
    
     def save(self):
         fields = []  # 存放insert中字段名
         values = []  # 存放insert中的值
         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))
         sql = "insert {db_table}({fields}) value({values})".format(db_table=self._meta["db_table"], fields=",".join(fields), values=",".join(values))
         print(sql)
    
    

    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 charfiled")
         self.max_length = max_length
    
     def __get__(self, instance, owner):
         return self._value
    
     def __set__(self, instance, value):
         if not isinstance(value, str):  # 检查后续传递的值是否为str类型
             raise ValueError("string value need")
         if len(value) > self.max_length:
             raise ValueError("value len excess len of max_length")
         self._value = value
    
    

    class IntField(Field):

     # 数据描述符
     def __init__(self,db_column, min_value=None,max_value=None):  # 支持配置最小值和最大值
         self._value = None  # 属性描述符传递的值
         self.db_column = db_column
         # 判断min_value是否为int,是否大于0
         if min_value is not None:
             if not isinstance(min_value,numbers.Integral):
                 raise ValueError("min_value must be int")
             if min_value < 0:
                 raise ValueError("min_value must be positive int")
         # 判断max_value是否为int,是否大于0
         if max_value is not None:
             if not isinstance(max_value,numbers.Integral):
                 raise ValueError("max_value must be int")
             if max_value < 0:
                 raise ValueError("max_value must be positive int")
         # 判断max_value是否大于min_value
         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")
         self.min_value = min_value
         self.max_value = max_value
    
     def __get__(self, instance, owner):
         return self._value
    
     def __set__(self, instance, value):
         if not isinstance(value, numbers.Integral):  # 检查后续传递的值是否为int类型
             raise ValueError("int value need")
         if value < self.min_value or value > self.max_value:
             raise ValueError("value must between min_value and max_value")
         self._value = value
class User(BaseModel):
    u_name = CharField(db_column="name1",max_length=10)  # 对应mysql中char类型
    u_age = IntField(db_column="age2",min_value=0, max_value=100)  # 对应mysql中int类型

    class Meta:
        db_table = "user"

if __name__ == '__main__':
    # 后续通过下面的方式就可以对数据库进行新增操作
    user = User(u_name="aaa",u_age=30)
    #user.name = "tom"
    #user.age = 30
    user.save()
```