开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第14天,点击查看活动详情
元类编程
一,动态属性property
使用装饰器 @property 即可进行动态属性的创建,以及管理属性
场景一:
如果之前我存入数据库的数据只有用户的生日,而没有年龄,但是我就是想通过属性调用的方式来获取用户生日
from datetime import datetime, date
class User:
def __init__(self, name, birthday):
self.name = name
self.birthday = birthday
@property
def age(self):
return datetime.now().year - self.birthday.year
if __name__ == '__main__':
user = User('xyb', date(1999, 12, 1))
print(user.age)
场景二:
我在改变一个实例的属性的时候,我希望他只接受 str 类型的参数,否则在赋值的时候就报错
class User:
def __init__(self, name):
self.name = name
@property
def name(self):
return self._name # 这里的self._name是为了不陷入死循环
@name.setter
def name(self, value):
if isinstance(value, str):
self._name = value
else:
raise TypeError('Excepted a string')
@name.deleter
def name(self):
raise AttributeError('Can\'t delete attribute')
if __name__ == '__main__':
user = User('xyb') # 这里__init_-方法创建实例的时候会自动调用我们创建的方法
user.name = 'aaa'
print(user.name)
del user.name # 报错,元素被我们定义不能删除
二,__ getattr __ , __ getattribute __魔法函数
__getattr__在查找不到属性的时候调用,可以添加很多灵活的逻辑,对字典取值
class A:
def __init__(self, info):
self.info = info
def __getattr__(self, item):
"""查找不到属性的时候调用"""
return self.info[item]
if __name__ == '__main__':
a = A({'name': 'xyb', 'age': 20})
print(a.name)
__getattribute__只要访问任何属性,就会调用这个属性,我们这样就能掌控所有的属性入口
# 能不从写,尽量不写这个方法,写框架的时候回用到
三,属性描述符和属性查找过程
当我们想要对实例的属性进行一些值的判断操作,以便允许或抛出异常
前面介绍了 property 动态属性,可以让我们对属性的值进行操作,或者凭空生成实例的属性,但是我们如果在需要对实例的属性进行大量操作,动态属性就会不适用了,我们想要的是优化的代码,而不是大量重复的代码。这个时候,属性描述符和闭包的作用就很大了
-
使用属性描述符对实例属性进行操作
class Integer: def __init__(self, name): self.name = name def __get__(self, instance, cls): """ 没有把这个类当成描述符来使用时,会触发if条件 :param instance: 实例本身 :param cls: :return:查询的属性值 """ if instance is None: return self else: return instance.__dict__[self.name] def __set__(self, instance, value): """ 在实例的属性被赋值的时候才会调用,即self.x = x这种 :param instance: 实例本身 :param value: 实例化的值 :return: None """ if not isinstance(value, int): raise TypeError('Except an int') instance.__dict__[self.name] = value def __delattr__(self, instance): """ 删除属性时触发 :param instance: 实例本身 :return: None """ del instance.__dict__[self.name] class Point: x = Integer('x') y = Integer('y') def __init__(self, x, y): self.x = x self.y = y pass if __name__ == '__main__': p = Point(1, 's') -
使用闭包进行对实例进行操作(启用起来和属性描述符没什么两样)
def type_property(name, except_type): storage_name = '_' + name @property def prop(self): return getattr(self, storage_name) @prop.setter def prop(self, value): if not isinstance(value, except_type): TypeError('{} must be a {}'.format(name, except_type)) setattr(self, storage_name, value) return prop class Person: name = type_property('name', str) age = type_property('age', int) def __init__(self, name, age): self.name = name self.age = age if __name__ == '__main__': from functools import partial String = partial(type_property, str) Integer = partial(type_property, int) p = Person('xyb', 20) print(p.__dict__) print(p.age) -
查找过程
class Integer: # 数据描述符 def __init__(self, name): self.name = name def __get__(self, instance, owner): pass def __set__(self, instance, value): pass class NonDataInteger: # 数据描述符 def __init__(self, name): self.name = name def __get__(self, instance, owner): pass数据描述符,数据描述符和属性查找的过程是不同的
user = User(),那么user.age顺序如下:
首先调用 __ getattribute __,而对于描述符 (get) 的调用,则是发生在 getattribute 内部的。
- 到数据描述符中调用
__get__方法。 - 查找自身实例的 age 属性
- 查找父类和基类的
__dict__age 属性 - 调用非数据描述符的
__get__方法。 - 如果有
__getattr__方法,调用其方法 - 都没有抛出异常
- 到数据描述符中调用
四,__ new __ 和 __ init __的区别
__new__是在对象生成之前调用,返回一个实例(创建实例交给父类做)
__init__是在对象生成之后调用,对实例进行初始化
如果__ new__方法不返回实例,那么不会调用__ init__
class User:
def __new__(cls, *args, **kwargs):
print('in new')
ob = super().__new__(cls)
print(ob)
return ob
def __init__(self, name):
print('in init')
print(self)
self.name = name
if __name__ == '__main__':
user = User('xyb')
print(user.name)
...
in new
<__main__.User object at 0x000001FB70E5B1D0>
in init
<__main__.User object at 0x000001FB70E5B1D0>
xyb
五,元类编程
类本身也是对象,type创建的类的类
引子:
通过一个函数来动态的创建类
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__':
user = create_class('user')()
print(user)
通过 type 动态创建类
class BaseClass:
def answer(self):
return 'i am baseclass'
def get_name(self):
return self.name
if __name__ == '__main__':
User = type('User', (BaseClass, ), {'name': 'user', 'get_name': get_name})
my_obj = User()
print(my_obj.get_name())
print(my_obj.answer())
创建元类控制类的生成
什么是元类?
元类就是创建类的类。对象 <-- class(对象) <-- type
type 在全局是唯一的,也是一个元类,所有的类的创建,都是由 type 去创建的类对象
-
但是,python 在创建类之前,会首先寻找 MetaClass 这个属性,让这个元类帮他创建类对象
-
MateClass 的优先度大于继承的类的元类
把
__new__方法剥离出来,单独写一个元类来处理__new__方法,,这样我们就能很好的控制整个创建实例的过程,比方说没有实例抽象基类的方法,我们就可以在没创建类的时候在元类里面抛异常
class MetaClass(type):
# 继承了 type 就是一个元类,用来控制实例的生成
def __new__(cls, *args, **kwargs):
return super().__new__(cls, *args, **kwargs)
class User(metaclass=MetaClass):
def __init__(self, name):
self.name = name
def __str__(self):
return 'user'
if __name__ == '__main__':
user =User('xyb')
print(user)
六,通过元类实现orm
orm 就是通过 python 一个类来映射到数据库数据库的一张表,对类操作,能将数据写入表中
class Field:
pass
class IntField(Field):
def __init__(self, name, db_column, min_value=None, max_value=None):
self.name = name
self.db_column = db_column
self.min_value = min_value
self.max_value = max_value
if min_value:
if not isinstance(min_value, int):
raise TypeError('min_value excepted an int')
elif min_value < 0:
raise ValueError('min_value must be positive int')
if max_value:
if not isinstance(min_value, int):
raise TypeError('min_value excepted an int')
elif max_value < 0:
raise ValueError('max_value must be positive int')
if max_value and min_value:
if max_value < min_value:
raise ValueError('max_value must be bigger than min_value')
def __get__(self, instance, owner):
if instance is None:
return self
else:
return instance.__dict__[self.name]
def __set__(self, instance, value):
if not isinstance(value, int):
raise TypeError('Except an str')
if value < self.min_value or value > self.max_value:
raise ValueError('Value must between min_value and max_value')
instance.__dict__[self.name] = value
class CharField(Field):
def __init__(self, name, db_column, max_len):
self.name = name
self.column = db_column
self.max_len = max_len
if not isinstance(max_len, int):
raise TypeError('max_len must be an int')
elif max_len < 0:
raise ValueError('max_len must be bigger than 0')
def __get__(self, instance, owner):
if instance is None:
return self
else:
return instance.__dict__[self.name]
def __set__(self, instance, value):
if not isinstance(value, str):
raise TypeError('Except an str')
if len(value) > self.max_len:
raise ValueError('value must be smaller than max_len')
instance.__dict__[self.name] = value
class ModelMetaClass(type):
def __new__(cls, name, base, attrs, **kwargs):
if name == 'BaseModel':
return super().__new__(cls, name, base, 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, base, 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))
sql = "insert {db_table}({fields}) value({values})".format(db_table=self._meta['db_table'], fields=','.join(fields), values=','.join(values))
class User(BaseModel):
name = CharField('name', db_column="w", max_len=10)
age = IntField('age', db_column="a", min_value=0, max_value=100)
class Meta:
db_table = 'user'
if __name__ == '__main__':
user = User()
user.name = 'xyb'
user.age = 28