python的描述器descriptor详解
作者:菜鸟小超
基本说明
Python的描述器(descriptor)是一种Python对象,可以通过定义一组特定的方法来管理另一个对象的访问。描述器可以用于控制属性的读取、写入和删除等操作,同时还可以用于实现计算属性、类属性、属性别名等高级功能。
在Python中,描述器是通过实现__get__()
、__set__()
和__delete__()
方法的对象来定义的。当一个描述器被绑定到一个类的属性上时,Python会自动将其转化为描述器对象,并在访问该属性时调用对应的描述器方法。
class Descriptor: def __get__(self, instance, owner): print("Getting the value") return self.value def __set__(self, instance, value): print("Setting the value") self.value = value class MyClass: attr = Descriptor() obj = MyClass() obj.attr = 42 print(obj.attr)
描述器是一种强大的Python语言特性,可以用于实现各种高级功能,例如:
- 计算属性:描述器可以根据其他属性的值动态计算出一个属性的值,而不是存储属性的值。这可以帮助我们简化代码,并且可以在不改变接口的情况下改变属性的实现方式。
- 类属性:描述器可以让我们将属性绑定到类上,而不是绑定到实例上。这可以让我们在所有实例之间共享属性值,并且可以在运行时动态更改属性的值。
- 属性别名:描述器可以让我们定义一个属性的别名,让一个属性具有多个名称。这可以帮助我们简化代码,并且可以在不改变接口的情况下更改属性的名称。
- 数据验证:描述器可以让我们在设置属性值之前验证输入数据,确保它们符合我们的预期格式和类型。这可以提高代码的健壮性,并且可以帮助我们避免一些常见的错误。
示例Demo1
假设我们有一个Temperature
类,用于表示温度。该类有一个名为celsius
的属性,表示摄氏温度。我们希望实现以下功能:
- 计算属性
fahrenheit
,表示华氏温度,它应该是一个只读属性,可以通过摄氏温度自动计算得出。 - 限制
celsius
属性的取值范围在-273.15℃到1000℃之间,如果尝试设置超出此范围的值,应该引发ValueError
异常。
下面是使用描述器实现以上功能的示例代码:
class Celsius: def __init__(self, value=0.0): self._value = value def __get__(self, instance, owner): return self._value def __set__(self, instance, value): if value < -273.15 or value > 1000.0: raise ValueError("Temperature out of range") self._value = value class Temperature: celsius = Celsius() @property def fahrenheit(self): return self.celsius * 1.8 + 32
在这个示例中,我们定义了一个Celsius
类,它是一个描述器,用于限制Temperature
类的celsius
属性的取值范围。在Celsius
类中,我们实现了__get__()
和__set__()
方法,分别在读取和设置celsius
属性时被调用。在__set__()
方法中,我们检查输入的值是否在允许的范围内,如果不是,则引发一个异常。
然后,我们定义了一个Temperature
类,它有一个celsius
属性,它被绑定到Celsius
类的实例上。我们还定义了一个只读属性fahrenheit
,它可以通过celsius
属性自动计算得出。
现在,我们可以创建一个Temperature
对象,并设置其celsius
属性,如下所示:
t = Temperature() t.celsius = 25.0 print(t.celsius) # 输出 25.0 print(t.fahrenheit) # 输出 77.0
在这个示例中,我们创建了一个Temperature
对象,并将其celsius
属性设置为25.0。然后,我们打印了celsius
和fahrenheit
属性的值,它们分别是25.0和77.0,符合我们预期的结果。
如果我们尝试设置一个超出允许范围的值,例如-300.0,会引发一个异常,如下所示:
t.celsius = -300.0 # 引发 ValueError: Temperature out of range
示例Demo2
下面是一个示例,演示如何使用描述器将属性绑定到类上。
假设我们有一个名为Counter
的类,它用于计数器操作,可以用于记录创建的对象数或者其他类级别的计数。
我们可以使用描述器将计数器属性绑定到Counter
类上,如下所示:
class Counter: _count = 0 class CountDescriptor: def __get__(self, instance, owner): return owner._count def __set__(self, instance, value): owner = type(instance) owner._count = value count = CountDescriptor() def __init__(self): type(self)._count += 1
在这个示例中,我们定义了一个Counter
类,它有一个名为count
的属性,它被绑定到了一个内部的CountDescriptor
描述器类上。
在CountDescriptor
类中,我们实现了__get__()
和__set__()
方法,它们分别在读取和设置count
属性时被调用。在__get__()
方法中,我们返回Counter
类的_count
属性的值。在__set__()
方法中,我们通过获取实例的类型(即Counter
类),并设置其_count
属性的值来设置计数器的值。
然后,在Counter
类的__init__()
方法中,我们在创建对象时自动增加计数器的值。
现在,我们可以创建多个Counter
对象,并访问它们的count
属性,如下所示:
c1 = Counter() c2 = Counter() print(c1.count) # 输出 2 print(c2.count) # 输出 2 Counter.count = 100 print(c1.count) # 输出 100 print(c2.count) # 输出 100
在这个示例中,我们创建了两个Counter
对象,并分别将它们存储在c1
和c2
变量中。然后,我们打印它们的count
属性,它们的值都是2,表示我们已经创建了两个Counter
对象。
接着,我们将Counter
类的count
属性设置为100,这将改变所有对象的计数器值。然后,我们再次打印c1
和c2
的count
属性,它们的值都变成了100,说明我们成功地将属性绑定到了类上,实现了所有实例之间共享属性值的效果。
示例Demo3
下面是一个示例,演示如何使用描述器实现属性别名。
假设我们有一个Person
类,它有一个名为name
的属性,表示人的名字。现在我们想要为name
属性定义一个别名,叫做full_name
,以便在某些情况下,我们可以使用full_name
属性来代替name
属性。
我们可以使用描述器来实现这个功能,如下所示:
class NameAlias: def __init__(self, name): self.name = name def __get__(self, instance, owner): return getattr(instance, self.name) def __set__(self, instance, value): setattr(instance, self.name, value) class Person: def __init__(self, name): self.name = name full_name = NameAlias("name")
在这个示例中,我们定义了一个NameAlias
描述器类,它用于实现属性别名功能。在NameAlias
类中,我们实现了__get__()
和__set__()
方法,它们分别在读取和设置full_name
属性时被调用。在__get__()
方法中,我们使用getattr()
函数获取对象的name
属性值,并返回它。在__set__()
方法中,我们使用setattr()
函数设置对象的name
属性值为传入的值。
然后,在Person
类中,我们定义了一个名为full_name
的属性,它被绑定到了一个NameAlias
实例上,用于实现属性别名。在Person
类的__init__()
方法中,我们初始化name
属性的值。现在,我们可以创建一个Person
对象,并访问它的name
和full_name
属性,如下所示:
p = Person("Alice") print(p.name) # 输出 "Alice" print(p.full_name) # 输出 "Alice" p.full_name = "Bob" print(p.name) # 输出 "Bob" print(p.full_name) # 输出 "Bob"
在这个示例中,我们创建了一个Person
对象,并将其存储在p
变量中。然后,我们打印它的name
和full_name
属性,它们的值都是"Alice",表示它们是等价的。接着,我们将p
的full_name
属性设置为"Bob",这将同时改变name
属性的值。然后,我们再次打印name
和full_name
属性,它们的值都是"Bob",说明我们成功地实现了属性别名的功能。
示例Demo4
下面是一个示例,演示如何使用描述器实现数据验证功能。
假设我们有一个Person
类,它有一个名为age
的属性,表示人的年龄。现在我们想要对age
属性的输入数据进行验证,以确保它的值在0到150之间。
我们可以使用描述器来实现这个功能,如下所示:
class AgeValidator: def __get__(self, instance, owner): return instance._age def __set__(self, instance, value): if not isinstance(value, int) or value < 0 or value > 150: raise ValueError("Invalid age") instance._age = value class Person: def __init__(self, name, age): self.name = name self.age = age age = AgeValidator()
在这个示例中,我们定义了一个AgeValidator
描述器类,它用于实现数据验证功能。在AgeValidator
类中,我们实现了__get__()
和__set__()
方法,它们分别在读取和设置age
属性时被调用。在__set__()
方法中,我们首先检查输入的值是否是整数,并且是否在0到150之间。如果输入数据不符合要求,我们将引发一个ValueError
异常。否则,我们将设置instance._age
属性的值为传入的值。
然后,在Person
类中,我们定义了一个名为age
的属性,它被绑定到了一个AgeValidator
实例上,用于实现数据验证功能。在Person
类的__init__()
方法中,我们初始化name
和age
属性的值。现在,我们可以创建一个Person
对象,并尝试设置其age
属性的值,如下所示:
p = Person("Alice", 25) print(p.age) # 输出 25 p.age = 200 # 引发 ValueError: Invalid age
在这个示例中,我们创建了一个Person
对象,并将其存储在p
变量中。然后,我们打印它的age
属性,它的值是25。接着,我们尝试将p
的age
属性设置为200,这将引发一个ValueError
异常,因为200超出了允许的范围。这说明我们成功地使用描述器实现了数据验证功能,可以避免一些常见的错误。
在这个示例中,Person
类中有两个名为age
的定义,一个是在__init__()
方法中进行初始化的实例属性,另一个是通过描述器AgeValidator
绑定到Person
类上的类属性。
当我们在__init__()
方法中设置self.age = age
时,它实际上是在创建一个实例属性,而不是类属性。这个实例属性只对当前对象有效,而不对所有Person
对象都有效。
而当我们在Person
类中定义了一个名为age
的描述器属性时,它实际上是在创建一个类属性,这个属性可以被所有Person
对象所共享,它定义了age
属性的读写行为,可以对属性值进行验证,保证数据的正确性。
因此,这两个age
属性虽然名字相同,但它们的作用范围和用途不同。实例属性是用来存储每个对象的不同数据,而类属性则是用来实现属性的共享和控制。描述器可以让我们将类属性绑定到一个自定义的属性读写行为上,从而实现更灵活的属性操作。
到此这篇关于python的描述器descriptor详解的文章就介绍到这了,更多相关python的描述器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!