python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > python的描述器

python的描述器descriptor详解

作者:菜鸟小超

这篇文章主要介绍了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的描述器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文