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语言特性,可以用于实现各种高级功能,例如:

  1. 计算属性:描述器可以根据其他属性的值动态计算出一个属性的值,而不是存储属性的值。这可以帮助我们简化代码,并且可以在不改变接口的情况下改变属性的实现方式。
  2. 类属性:描述器可以让我们将属性绑定到类上,而不是绑定到实例上。这可以让我们在所有实例之间共享属性值,并且可以在运行时动态更改属性的值。
  3. 属性别名:描述器可以让我们定义一个属性的别名,让一个属性具有多个名称。这可以帮助我们简化代码,并且可以在不改变接口的情况下更改属性的名称。
  4. 数据验证:描述器可以让我们在设置属性值之前验证输入数据,确保它们符合我们的预期格式和类型。这可以提高代码的健壮性,并且可以帮助我们避免一些常见的错误。

示例Demo1

假设我们有一个Temperature类,用于表示温度。该类有一个名为celsius的属性,表示摄氏温度。我们希望实现以下功能:

  1. 计算属性fahrenheit,表示华氏温度,它应该是一个只读属性,可以通过摄氏温度自动计算得出。
  2. 限制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。然后,我们打印了celsiusfahrenheit属性的值,它们分别是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对象,并分别将它们存储在c1c2变量中。然后,我们打印它们的count属性,它们的值都是2,表示我们已经创建了两个Counter对象。

接着,我们将Counter类的count属性设置为100,这将改变所有对象的计数器值。然后,我们再次打印c1c2count属性,它们的值都变成了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对象,并访问它的namefull_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变量中。然后,我们打印它的namefull_name属性,它们的值都是”Alice”,表示它们是等价的。接着,我们将pfull_name属性设置为”Bob”,这将同时改变name属性的值。然后,我们再次打印namefull_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__()方法中,我们初始化nameage属性的值。现在,我们可以创建一个Person对象,并尝试设置其age属性的值,如下所示:

p = Person("Alice", 25)
print(p.age)  # 输出 25
p.age = 200  # 引发 ValueError: Invalid age

在这个示例中,我们创建了一个Person对象,并将其存储在p变量中。然后,我们打印它的age属性,它的值是25。接着,我们尝试将page属性设置为200,这将引发一个ValueError异常,因为200超出了允许的范围。这说明我们成功地使用描述器实现了数据验证功能,可以避免一些常见的错误。

在这个示例中,Person类中有两个名为age的定义,一个是在__init__()方法中进行初始化的实例属性,另一个是通过描述器AgeValidator绑定到Person类上的类属性。

当我们在__init__()方法中设置self.age = age时,它实际上是在创建一个实例属性,而不是类属性。这个实例属性只对当前对象有效,而不对所有Person对象都有效。

而当我们在Person类中定义了一个名为age的描述器属性时,它实际上是在创建一个类属性,这个属性可以被所有Person对象所共享,它定义了age属性的读写行为,可以对属性值进行验证,保证数据的正确性。

因此,这两个age属性虽然名字相同,但它们的作用范围和用途不同。实例属性是用来存储每个对象的不同数据,而类属性则是用来实现属性的共享和控制。描述器可以让我们将类属性绑定到一个自定义的属性读写行为上,从而实现更灵活的属性操作。

到此这篇关于python的描述器descriptor详解的文章就介绍到这了,更多相关python的描述器内容请搜索aitechtogether.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持aitechtogether.com!

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
青葱年少的头像青葱年少普通用户
上一篇 2023年12月27日
下一篇 2023年12月27日

相关推荐