描述符的使用
Usage of descriptors
第一次尝试使用描述符。
目标:0 到 100(含)之间的任何整数都将是此描述符的合法值。非整数和大于 100 或小于 0 的数字应导致抛出异常,指示类型 and/or 值错误。
我的代码:
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/
这不是描述符本身的问题。问题是,在 __set__
中,您检查现有的 self.value
,而不是检查您获得的值。由于它被初始化为None
,你总是会在第一次赋值时失败。
描述符本身也存在(不相关的)问题。 getter 和 setter 应该来自 obj.__dict__
,而不是来自描述符对象本身的 read/write。否则,所有实例将共享一个公共值
这真的不是描述符的问题!您只是将描述符上的初始 self.value
属性设置为 None
:
def __init__(self, initial_value=None):
# ...
self.value = initial_value # this is None by default!
然后您尝试将设置为 None
的 self.value
属性值转换为整数:
# self.value starts out as None; int(None) fails.
if self.pct_min <= int(self.value) <= self.pct_max:
您可能想将此应用到 __set__
的 initial_value
参数!
您的描述符实现确实还有几个问题:
-
__set__
setter method 不应该 return 任何东西。它应该为绑定对象设置新值,仅此而已。 Python 忽略 return 由 __set__
编辑的任何内容。
您正在将数据存储在描述符实例本身上。您的 class 只有描述符对象的 一个副本 ,因此当您在 Foo()
的不同实例之间使用描述符时,您会看到完全相同的数据。您希望将数据存储在描述符绑定到的对象上,因此在 __set__
方法中,在 obj
上,或者至少在一个可以让您将值与 obj
。 obj
是描述符绑定到的 Foo()
的特定实例。
__get__
getter method 不采用 'initial value',它采用您要绑定的对象,以及该对象的类型 (class) .在 class 上访问时,obj
是 None
并且只设置了类型。
不要混用 print()
和创建异常实例。 ValueError(print(...))
没有多大意义,print()
写入控制台然后 returns 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
请注意,上面使用实例上的特定属性名称来设置值。如果您在同一个 class 上有多个描述符实例,那将是一个问题!
如果您想避免这种情况,您的选择是:
- 使用某种映射存储,在键中使用其他一些唯一标识符来跟踪什么描述符存储了绑定对象上的什么值。
- 将数据存储在描述符实例上,以实例的唯一标识符为键。
- 将属性名称存储在存储此描述符的 class 上,在描述符实例本身上,并将您的实例属性名称基于此。您可以通过实现
descriptor.__set_name__()
hook method 来做到这一点,当创建使用您的描述符的 class 时,Python 会调用它。此挂钩需要 Python 3.6 或更新版本。对于 Foo
class 示例,将通过 Foo
和 'participation'
. 调用
请注意,您应该不 使用属性名称(participation
在您的 Foo
示例中),至少不要直接使用。使用 obj.participation
将触发描述符对象本身,因此 Percentage.__get__()
或 Percencage.__set__()
。对于数据描述符(实现 __set__
或 __delete__
方法的描述符),您可以直接使用 vars(obj)
或 obj.__dict__
来存储属性。
第一次尝试使用描述符。
目标:0 到 100(含)之间的任何整数都将是此描述符的合法值。非整数和大于 100 或小于 0 的数字应导致抛出异常,指示类型 and/or 值错误。
我的代码:
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/
这不是描述符本身的问题。问题是,在 __set__
中,您检查现有的 self.value
,而不是检查您获得的值。由于它被初始化为None
,你总是会在第一次赋值时失败。
描述符本身也存在(不相关的)问题。 getter 和 setter 应该来自 obj.__dict__
,而不是来自描述符对象本身的 read/write。否则,所有实例将共享一个公共值
这真的不是描述符的问题!您只是将描述符上的初始 self.value
属性设置为 None
:
def __init__(self, initial_value=None):
# ...
self.value = initial_value # this is None by default!
然后您尝试将设置为 None
的 self.value
属性值转换为整数:
# self.value starts out as None; int(None) fails.
if self.pct_min <= int(self.value) <= self.pct_max:
您可能想将此应用到 __set__
的 initial_value
参数!
您的描述符实现确实还有几个问题:
-
__set__
setter method 不应该 return 任何东西。它应该为绑定对象设置新值,仅此而已。 Python 忽略 return 由__set__
编辑的任何内容。 您正在将数据存储在描述符实例本身上。您的 class 只有描述符对象的 一个副本 ,因此当您在
Foo()
的不同实例之间使用描述符时,您会看到完全相同的数据。您希望将数据存储在描述符绑定到的对象上,因此在__set__
方法中,在obj
上,或者至少在一个可以让您将值与obj
。obj
是描述符绑定到的Foo()
的特定实例。__get__
getter method 不采用 'initial value',它采用您要绑定的对象,以及该对象的类型 (class) .在 class 上访问时,obj
是None
并且只设置了类型。不要混用
print()
和创建异常实例。ValueError(print(...))
没有多大意义,print()
写入控制台然后 returnsNone
。只需使用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
请注意,上面使用实例上的特定属性名称来设置值。如果您在同一个 class 上有多个描述符实例,那将是一个问题!
如果您想避免这种情况,您的选择是:
- 使用某种映射存储,在键中使用其他一些唯一标识符来跟踪什么描述符存储了绑定对象上的什么值。
- 将数据存储在描述符实例上,以实例的唯一标识符为键。
- 将属性名称存储在存储此描述符的 class 上,在描述符实例本身上,并将您的实例属性名称基于此。您可以通过实现
descriptor.__set_name__()
hook method 来做到这一点,当创建使用您的描述符的 class 时,Python 会调用它。此挂钩需要 Python 3.6 或更新版本。对于Foo
class 示例,将通过Foo
和'participation'
. 调用
请注意,您应该不 使用属性名称(participation
在您的 Foo
示例中),至少不要直接使用。使用 obj.participation
将触发描述符对象本身,因此 Percentage.__get__()
或 Percencage.__set__()
。对于数据描述符(实现 __set__
或 __delete__
方法的描述符),您可以直接使用 vars(obj)
或 obj.__dict__
来存储属性。