1. Python中的描述器是什么
描述器(descriptor)是Python中一个高级、强大的对象属性访问方法,应用于class内部,其目的是捕捉对class中属性的访问,并且控制对它的操作。它可以有效地对class中的属性进行管理,满足业务需求,避免代码重复,规范属性使用。
1.1 描述器的作用
描述器有如下的作用:
控制对属性的访问操作
对属性或方法的值进行过滤、验证或者转换
重用代码并且允许自定义属性,提高代码的重用性
处理Python的魔术方法,如__getattribute__、__getattr__
1.2 描述器的分类
描述器按照它的作用分类:
数据描述器(data descriptor):具有__get__、__set__或者__delete__方法的描述器
非数据描述器(non-data descriptor):具有__get__方法但没有__set__和__delete__方法的描述器
数据描述器和非数据描述器的区别在于,访问属性时后者的__get__方法优先级高于实例对象属性,但当属性被定义时,在实例属性中找到时则只会调用实例属性。而数据描述器则是优先调用__get__方法,无论实例属性有没有被定义。
2. 描述器的实现方法
2.1 实现一个非数据描述器
以下是一个非数据描述器的示例代码。实现了一个计数器,记录访问它的次数:
class Counter:
def __init__(self):
self.counter = 0
def __get__(self, instance, owner):
self.counter += 1
return self.counter
class MyClass:
count = Counter()
c1 = MyClass()
c1.count # 1
c1.count # 2
c2 = MyClass()
c2.count # 3
c2.count # 4
在这个示例代码中,定义了一个Counter类,它有一个计数器,每次调用它的__get__方法都会使计数器加1。然后定义了一个MyClass类,它的类属性count实例化了Counter对象。count一旦被访问,就会执行Counter对象的__get__方法,使计数器加1。
2.2 实现一个数据描述器
以下是一个数据描述器的示例代码。实现了一个温度转换器,将华氏度转换成摄氏度:
class Celsius:
def __get__(self, instance, owner):
return instance._temperature
def __set__(self, instance, value):
instance._temperature = (value - 32) / 1.8
class Temperature:
temperature = Celsius()
t = Temperature()
t.temperature = 86
print(t.temperature) # 30.0
在这个示例代码中,定义了一个Celsius类,它有__get__和__set__两个方法。当被访问时,__get__方法将会把温度从华氏度转换成摄氏度并返回。__set__方法将会根据传入的华氏度数值,计算并设置_temperature属性的摄氏度数值。Temperature类的实例化对象t中的temperature属性,实例化了Celsius对象。属性被赋值时,执行Celsius对象的__set__方法,在赋值前把华氏度转换成摄氏度。当访问它时,会返回摄氏度的温度。
3. 如何使用描述器
以下是一个描述器的应用示例:
假设我们有一个数据库,并且需要在其中保存一些记录。记录需要根据业务需求进行筛选,并且需要支持排序等操作。每个记录由多个字段组成,且字段的类型可能不同。这时候,我们可以考虑使用描述器来对记录的字段进行管理。
首先,我们需要定义一些描述器类:
class StringField:
def __init__(self, name, length):
self.name = name
self.length = length
def __get__(self, instance, owner):
return instance.__dict__[self.name]
def __set__(self, instance, value):
if not isinstance(value, str) or len(value) > self.length:
raise ValueError('Invalid value for {}: {}'.format(self.name, value))
instance.__dict__[self.name] = value
class IntegerField:
def __init__(self, name):
self.name = name
def __get__(self, instance, owner):
return instance.__dict__[self.name]
def __set__(self, instance, value):
if not isinstance(value, int):
raise ValueError('Invalid value for {}: {}'.format(self.name, value))
instance.__dict__[self.name] = value
然后,我们定义一个Record类,作为记录的类:
class Record:
name = StringField('name', 20)
age = IntegerField('age')
def __init__(self, name, age):
self.name = name
self.age = age
最后,我们来测试一下这些代码:
r = Record('Julia', 25)
print(r.name)
print(r.age)
r.name = 'Jenny'
r.age = 30
print(r.name)
print(r.age)
r.age = '30' # ValueError: Invalid value for age: 30
在这个示例代码中,我们定义了两个描述器类StringField和IntegerField,它们分别用于记录字符串类型的名称和整数类型的年龄。在Record类中,实例化了这两个描述器对象,并且定义了一个构造函数,可以通过这个构造函数来初始化这些属性的值。在测试代码中,我们创建并实例化了一个Record对象,并对它的属性进行访问和修改。当属性的值不符合要求时,会抛出ValueError异常。
4. 总结
通过本文介绍,我们可以看到Python中的描述器可以帮助我们有效地管理属性,并且可以重用代码,避免代码重复。通过实现一个计数器和一个温度转换器的示例代码,我们可以更清楚地理解非数据描述器和数据描述器的工作原理和应用场景。通过一个记录数据库记录的示例代码,我们可以看到描述器的强大应用,能够帮助我们轻松地管理和检查属性的值。