描述符的使用

时间:2018-08-08 18:01:38

标签: python python-3.x python-3.6 descriptor

第一次尝试使用描述符。

目标: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

对于这一行:if self.pct_min <= int(self.value)<= self.pct_max:

我收到此错误:

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/

2 个答案:

答案 0 :(得分:2)

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

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

然后,您尝试将设置为self.value的{​​{1}}属性值转换为整数:

None

您可能希望将此应用于# self.value starts out as None; int(None) fails. if self.pct_min <= int(self.value) <= self.pct_max: 的{​​{1}}参数!

您的描述符实现确实还有其他问题:

  • __set__ setter method不应返回任何内容。它应该为绑定对象设置新值,仅此而已。 Python会忽略initial_value返回的任何内容。
  • 您正在将数据存储在描述符实例本身上。您的类只有描述符对象的单个副本,因此,在__set__的不同实例之间使用描述符时,您会看到完全相同的数据。您想将数据存储在描述符所绑定的对象上,因此在__set__方法中,在Foo()上或至少在一个可以将值与{{ 1}}。 __set__是描述符绑定到的obj的特定实例。

  • __get__ getter method不带有“初始值”,它带有要绑定到的对象以及该对象的类型(类)。在类上访问时,objobj,并且只设置了类型。

  • 请勿混用Foo()并创建异常实例。 obj没有多大意义,None写入控制台,然后返回print()。只需使用ValueError(print(...))

我正在做以下假设:

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

然后以下代码将起作用:

print()

演示:

None

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

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

  • 使用某种映射存储,该存储使用键中的其他唯一标识符来跟踪哪个描述符在绑定对象上存储了什么值。
  • 在描述符实例上存储数据,并以该实例的唯一标识符为键。
  • 将属性的名称存储在存储该描述符的类上,并存储在描述符实例本身上,并基于此实例属性名。您可以通过实现descriptor.__set_name__() hook method来实现此目的,在创建使用您的描述符的类时,Python会调用{{3}}。该挂钩需要Python 3.6或更高版本。对于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 进行调用。

请注意,您至少应该不要直接使用属性名称(在您的Foo示例中为Foo)。使用'participation'将触发描述符对象本身,因此participationFoo。对于数据描述符(实现obj.participationPercentage.__get__()方法的描述符),可以直接使用Percencage.__set__()__set__来存储属性。

答案 1 :(得分:1)

描述符本身不是问题。问题在于,在__set__中,您没有检查所获得的值,而是检查了现有的self.value。由于此字段已初始化为None,因此在第一次分配时您总是会失败。

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