impressyourcat 2018-01-04
我们先来看一段在Django项目中常用的代码:
设置数据库models代码:
class Students(models.Model):
name = models.CharField()
age = models.IntegerField()这里有几个神奇的地方,涉及到了python中最神秘的几个特性。
先看下有哪些神奇的地方:
这里用的是python的两个语法特性:
我们来一步一步解开神秘面纱。
Python虽然是强类型的脚本语言,但是在定义变量时却无法指定变量的类型。
例如,我们在Student类中定义一个age字段,合法值一般为包含0的正整数,但是在python中无正整数的类型,只能自己来校验。
class Student:
def __init__(self, name, age):
if isinstance(name,str):
self.name = name
else:
raise TypeError("Must be a string")
if isinstance(int, age):
self.age = age
else:
raise TypeError("Must be an int")但是,如果更新年龄时就会遇到问题,无法重用校验逻辑。
有没有简洁的方法呢?
使用property也是一个方法,可以针对每个属性来设置,但是如果一个类有多个属性,代码就会非常的多,并且产生大量的冗余,就像这样。
class Student:
def __init__(self, name, age, class_no, address, phone):
self._name = None
self._age = None
self.__class_no = None
self._address = None
self._phone = None
self.name = name
self.age = age
self.class_no = class_no
self.address = address
self.phone = phone
@property
def name(self):
return self._name
@name.setter
def name(self, value):
if not isinstance(value, str):
raise ValueError("Must be string")
self._name = value
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if isinstance(value, int) and value > 0:
self._age = value
else:
raise ValueError("age value error")
@property
def address(self):
return self._address
@address.setter
def address(self, value):
if not isinstance(value, str):
raise ValueError("Must be string")
self._address = valueView Code代码冗余太多,每个检查str的都要复制一遍代码。
描述符提供了优雅、简洁、健壮和可重用的解决方案。简而言之,一个描述符就是一个对象,该对象代表了一个属性的值。
这就意味着如果一个Student对象有一个属性“name”,那么描述符就是另一个能够用来代表属性“name”持有值的对象。
描述符协议中“定义了__get__”、“__set__”或”__delete__” 这些特殊方法,描述符是实现其中一个或多个方法的对象。
class NameProperty:
def __init__(self, name=""):
self.name = name
def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__.get(self.name)
def __set__(self, instance, value):
if not isinstance(value, str):
raise TypeError("name must be string")
instance.__dict__[self.name] = value
class Student:
name = NameProperty('name')
age = None
heghth = None
weight = None
def __init__(self, name):
self.name = name
def __str__(self):
return self.name
@property
def age(self):
return self.age
@age.setter
def age(self, value):
if not isinstance(value, int):
raise ValueError("must be int")
self.age = value
s = Student("Stitch")
print(s)
s.name = 'name'
print(s.name)View Code这个版本存在一个问题,就是name = NameProperty("sss"),必须设置一个名称,才可以使用。这个与我们使用django的models时不太一样,在使用models时,不写参数也可以的。
不用输入变量名称。
class NameProperty:
index =def __init__(self):
self.name = str(self.__class__.index) # 使用类的变量
self.__class__.index += 1
def __get__(self, instance, owner):
return getattr(instance, self.name)
def __set__(self, instance, value):
if not isinstance(value, str):
raise TypeError("name must be string")
instance.__dict__[self.name] = value
class Student:
name = NameProperty()
age = None
def __str__(self):
return self.name
s = Student()
s.name = "www"
print(s)
s2 = Student()
s2.name = "http"
print(s2)
print(s.name)View Code这个版本还存在一个问题,如果一个类型有多个字段使用了NameProperty时,错误提示时,无法表示出此变量的名称,只能表示出一个index值。用户看到这个时,无法判断是那个变量出了问题。
元类是python的中一个难点,在大部分场景下都不会用到。但是在编写框架方面却是必不可缺少的利器。
使用元类来控制类的行为:
class NameProperty:
index =def __init__(self):
self.storage_name = str(self.__class__.index) # 使用类的变量
self.__class__.index += 1
def __get__(self, instance, owner):
return getattr(instance, self.storage_name)
def __set__(self, instance, value):
if not isinstance(value, str):
raise TypeError("%s must be string" % self.storage_name)
instance.__dict__[self.storage_name] = value
class EntityMeta(type):
def __init__(cls, name, bases, attr_dict):
super().__init__(name, bases, attr_dict)
for key, attr in attr_dict.items():
if isinstance(attr, NameProperty):
type_name = type(attr).__name__
attr.storage_name = '{} property {}'.format(type_name, key)
class Student(metaclass=EntityMeta):
name = NameProperty()
age = None
nicky_name = NameProperty()
def __str__(self):
return self.name
s = Student()
s.name = "www"
print(s)
s2 = Student()
s2.name = "test"
s2.nicky_name = 4444
print(s2)
print(s2.nicky_name)View Code执行输出为:
raise TypeError("%s must be string" % self.storage_name)
TypeError: NameProperty property nicky_name must be st语法解释:
版本三相比版本二,最大的变化在于Student类继承了自定义元类EntityMeta。
如果对于python面向对象编程有了解的话,python的所有类都继承自type,type是所有类的元类。。
在这里,我们自定义的元类EntityMeta,具备一个功能就是判断类属性是否为NameProperty类型,如果为这个类型,则这个类型的实例属性storage_name值赋值为类名和属性名。
模仿Django的models实现:
import abc
class NameProperty:
index =def __init__(self):
self.storage_name = str(self.__class__.index) # 使用类的变量
self.__class__.index += 1
def __get__(self, instance, owner):
return getattr(instance, self.storage_name)
def __set__(self, instance, value):
# instance.__dict__[self.storage_name] = value
setattr(instance, self.storage_name, value)
class Validated(abc.ABC, NameProperty):
def __set__(self, instance, value):
value = self.validate(instance, value)
super().__set__(instance, value)
@abc.abstractclassmethod
def validate(self, instance, value):
"""return validated value or raise ValueError"""
class ChartField(Validated):
def validate(self, instance, value):
if not isinstance(value, str):
raise TypeError("{} must be str".format(self.storage_name))
return value
class IntegerField(Validated):
def __init__(self, min_value=None):
self.min_value = min_value
def validate(self, instance, value):
if not isinstance(value, int):
raise TypeError("{} must be int".format(self.storage_name))
if self.min_value and value < self.min_value:
raise ValueError("{} must larger min_value".format(self.storage_name))
return value
class EntityMeta(type):
def __init__(cls, name, bases, attr_dict):
super().__init__(name, bases, attr_dict)
for key, attr in attr_dict.items():
if isinstance(attr, Validated):
type_name = type(attr).__name__
attr.storage_name = "{} property {}".format(type_name, key)
class Entity(metaclass=EntityMeta):
pass
class Student(Entity):
name = ChartField()
age = IntegerField(min_value=0)
nicky_name = ChartField()
def __init__(self, name, age, nicky_name):
self.name = name
self.age = age
self.nicky_name = nicky_name
def __str__(self):
return self.name
s2 = Student("test", 12, "toddy")
s2.age = -1
print(s2.nicky_name)
s2.nicky_name = 4444View Code执行结果:
raise ValueError("{} must larger min_value".format(self.storage_name))
ValueError: IntegerField property age must larger min_value这样,完全模仿了models的定义。
类的初始化和后续属性赋值,都会自动调用__set__来设置并校验。
通过实例读取属性时,通常返回的是实例中定义的属性。读取顺序如下:
先记住这个顺序,后面理解描述需要。属性描述符都是定义在类中的,而不是在对象中。
某个类,只要是内部定义了方法 __get__, __set__, __delete__ 中的一个或多个(set,delete必须有一个),就可以称为描述符。
方法的原型为:
① __get__(self, instance, owner)
② __set__(self, instance, value)
③ __del__(self, instance)
描述符只绑定到类上,在实例上不生效。
描述的调用实质为:type(objectA).__dict__[“key”].__get__(None, objectB),objectB为描述符,objectA为定义类。
元类,就是创建类的类。一般类都继承自object类,默认会创建一些方法。
元类决定了类出初始化后有哪些特征和行为。如果我们想自定义一个类,具备某种特殊的行为,则需要自定义元类。
Django的django-rest-framework框架的serializer也是用的这个语法实现的。
编号 | 标题 | 链接 |
1 | 元类 | https://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python |
2 | 描述符 | http://python.jobbole.com/81899/ |
3 | 《流畅的python》 | 元类部分 |