描述符的使用

2024-05-14 15:31:59 发布

您现在位置:Python中文网/ 问答频道 /正文

第一次尝试使用描述符。你知道吗

目标:0到100(含)之间的任何整数都将是此描述符的合法值。非整数和大于100或小于0的数字应导致引发异常,表明类型和/或值错误。你知道吗

我的代码:

class Percentage():
    def __init__(self, initial_value=None):
        self.pct_min = 0
        self.pct_max = 100
        self.value = initial_value

    def __get__(self, obj, initial_value):
        return self.value

    def __set__(self, obj, initial_value):
        if self.pct_min <= int(self.value) <= self.pct_max:
            return self.value
        elif self.value < self.pct_min:
            raise ValueError(print("Value to low"))
        else:
            raise ValueError(print("Value to high"))


class Foo(object):
    participation = Percentage()



f1 = Foo()
f1.participation = 30

f2 = Foo()
f2.participation = 70

print(f1.participation)  # should print 30
print(f2.participation)  # should print 70

对于此行:如果自身百分比最小值<;=整数(自我价值)<;=最大自身百分比地址:

我得到这个错误:

TypeError: int() argument must be a string, a bytes-like object or a number, not 'NoneType'

我正在通过PythonTutor运行它,看起来我没有传递整数,但是我不明白我在哪里失败了。你知道吗

我也以此为指导:https://www.blog.pythonlibrary.org/2016/06/10/python-201-what-are-descriptors/


Tags: selffoovaluedef错误整数min描述符
2条回答

这不是描述符本身的问题。问题是,在__set__中,不是检查得到的值,而是检查现有的self.value。因为它被初始化为None,所以在第一次赋值时总是失败的。你知道吗

描述符本身也有一个(不相关的)问题。getter和setter应该从obj.__dict__读取/写入,而不是从描述符对象本身读取/写入。否则,所有实例将共享一个公共值

这其实不是描述符的问题!您只需将描述符的初始self.value属性设置为None

def __init__(self, initial_value=None):
    # ...
    self.value = initial_value  # this is None by default!

然后尝试将设置为Noneself.value属性值转换为整数:

# self.value starts out as None; int(None) fails.
if self.pct_min <= int(self.value) <= self.pct_max:

您可能希望将此应用于initial_value__set__参数!你知道吗

您的描述符实现确实还有几个问题:

  • ^{} setter method不应返回任何内容。它应该为绑定对象设置新的值,就这样。Python忽略__set__返回的任何内容。你知道吗
  • 您正在描述符实例本身上存储数据。您的类只有描述符对象的一个副本,所以当您在Foo()的不同实例之间使用描述符时,您会看到完全相同的数据。您希望在描述符绑定到的对象上存储数据,因此在__set__方法中,在obj上,或者至少在一个允许您将值与obj关联的地方。obj是描述符绑定到的Foo()的特定实例。

  • ^{} getter method不接受“初始值”,它接受要绑定到的对象以及该对象的类型(类)。在类上访问时,objNone,并且只设置类型。

  • 不要混用print()和创建异常实例。ValueError(print(...))没有什么意义,print()写入控制台,然后返回None。只需使用ValueError(...)

我做了以下假设:

  • 设置无效值时,需要引发异常。你知道吗
  • 获取值时,您不想进行任何验证。你知道吗
  • 当绑定对象上没有值时,应该使用描述符上设置的初始值。你知道吗
  • 值本身可以直接存储在实例上。你知道吗

那么下面的代码就可以工作了:

class Percentage:
    def __init__(self, initial_value=None):
        self.pct_min = 0
        self.pct_max = 100
        self.initial_value = initial_value

    def __get__(self, obj, owner):
        if obj is None:
            # accessed on the class, there is no value. Return the
            # descriptor itself instead.
            return self

        # get the value from the bound object, or, if it is missing, 
        # the initial_value attribute
        return getattr(obj, '_percentage_value', self.initial_value)

    def __set__(self, obj, new_value):
        # validation. Make sure it is an integer and in range
        new_value = int(new_value)
        if new_value < self.pct_min:
            raise ValueError("Value to low")
        if new_value > self.pct_max:
            raise ValueError("Value to high")

        # validation succeeded, set it on the instance
        obj._percentage_value = new_value

演示:

>>> class Foo(object):
...     participation = Percentage()
...
>>> f1 = Foo()
>>> f1.participation is None
True
>>> f1.participation = 110
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 24, in __set__
ValueError: Value to high
>>> f1.participation = -10
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 22, in __set__
ValueError: Value to low
>>> f1.participation = 75
>>> f1.participation
75
>>> vars(f1)
{'_percentage_value': 75}
>>> Foo.participation  # this calls Participation().__get__(None, Foo)
<__main__.Percentage object at 0x105e3a240>
>>> Foo.participation.__get__(f1, Foo)  # essentially what f1.participation does
75

注意,上面使用实例上的特定属性名来设置值。如果在同一个类上有多个描述符实例,这将是一个问题!你知道吗

如果要避免这种情况,您可以选择:

  • 使用某种映射存储,它使用键中的其他唯一标识符来跟踪哪些描述符在绑定对象上存储了哪些值。你知道吗
  • 将数据存储在描述符实例上,由实例的唯一标识符设置密钥。你知道吗
  • 将属性名存储在存储该描述符的类、描述符实例本身上,并将实例属性名基于该名称。您可以通过实现^{} hook method来实现这一点,当创建使用您的描述符的类时,Python会调用它。此挂钩需要Python3.6或更新版本。对于Foo类示例,将使用Foo'participation'调用它。你知道吗

注意,您应该而不是使用属性名(participationFoo示例中),至少不能直接使用。使用obj.participation将触发描述符对象本身,因此Percentage.__get__()Percencage.__set__()。但是,对于数据描述符(实现__set____delete__方法的描述符),可以直接使用vars(obj)obj.__dict__来存储属性。你知道吗

相关问题 更多 >

    热门问题