第一次尝试使用描述符。
目标: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/
答案 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不带有“初始值”,它带有要绑定到的对象以及该对象的类型(类)。在类上访问时,obj
是obj
,并且只设置了类型。
请勿混用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'
将触发描述符对象本身,因此participation
或Foo
。对于数据描述符(实现obj.participation
或Percentage.__get__()
方法的描述符),可以直接使用Percencage.__set__()
或__set__
来存储属性。
答案 1 :(得分:1)
描述符本身不是问题。问题在于,在__set__
中,您没有检查所获得的值,而是检查了现有的self.value
。由于此字段已初始化为None
,因此在第一次分配时您总是会失败。
描述符本身也存在一个(不相关的)问题。应当从obj.__dict__
而不是从描述符对象本身读取/读取getter和setter。否则,所有实例将共享一个公共值