我是Python新手并查看其文档我遇到了以下示例协议示例,在我看来是不正确的。
看起来像是
class IntField:
def __get__(self, instance, owner):
return instance.__dict__[self.name]
def __set__(self, instance, value):
if not isinstance(value, int):
raise ValueError(f'expecting integer in {self.name}')
instance.__dict__[self.name] = value
# this is the new initializer:
def __set_name__(self, owner, name):
self.name = name
class Model:
int_field = IntField()
以下是我的考虑因素。
属性int_field
是一个类范围的属性不是吗?
所以owner.__dict__
会有这么一把钥匙。但是,在方法__get__
中,使用了没有该密钥的instance.__dict__
。因此,从使用该方法的角度来看,该方法不处理类范围的属性,而是处理实例范围的属性。
另一方面,方法__set__
也不处理类范围的属性,但创建了一个实例范围的属性,因为使用了
instance.__dict__[self.name] = value
所以看起来这个类的每个实例都创建了自己的实例范围的属性。此外,对类的引用甚至不会传递给方法。
我是对的还是我错过了一些我还不知道的东西?
为了使我的考虑更清楚,逻辑上的示例等同于以下
class MyClass:
int_field = 10
instance_of = MyClass();
instance_of.__dict__["int_field"] = 20
print( MyClass.int_field )
print( instance_of.int_field )
程序输出
10
20
除了名称之外,实例属性int_field
与类范围属性int_field
没有任何共同之处。
文档中的示例也是如此。直观地,可以预期它是附加到描述符的类范围属性。但事实并非如此。描述符只是借用了类范围属性的名称。另一方面,类范围的属性确实可以附加到描述符。
因此,我认为文档中的示例只会让读者感到困惑。
答案 0 :(得分:3)
这个例子很好。
但是,在方法
__get__
中,使用了没有该密钥的instance.__dict__
。
实际设置instance.int_field
后, 将成为这样一个密钥,它将调用属性设置器并分配密钥。
另一方面,方法
__set__
也不处理类范围的属性,但创建了一个实例范围的属性
setter不应该创建类属性。它为getter正在寻找的实例dict键指定。
答案 1 :(得分:2)
我想我可以看到你在这里错过了哪一个谜题:subl . -a
中的self
是描述符!这就是我们在__get__
中设置状态的原因。通常在自身上设置状态(即在描述符上),因为instance.__dict__
的每个实例都将共享该状态。
示例中没有任何矛盾,但此处使用的名称有些含糊不清。也许一些重命名会有所帮助:
Model
我还消除了类(class IntField:
def __get__(self, obj, type_):
# typically: self is an IntField(), obj is a Model(), type_ is Model
return obj.__dict__[self.the_internal_name]
def __set__(self, obj, value):
if not isinstance(value, int):
raise ValueError(f'expecting integer, but received {type(value)}')
obj.__dict__[self.the_internal_name] = value
def __set_name__(self, type_, name):
# this is called at class definition time, i.e. descriptor init time
self.the_internal_name = name + '_internal'
class Model:
int_field = IntField()
)上的名称,描述符上的名称('int_field'
)以及在实例的字典中使用的名称( 'the_internal_name'
)。
上面代码中的所有'int_field_internal'
都引用了描述符实例。还有一个描述符实例,它处理self
的所有实例的属性访问。 Model
(文档称为type_
)将为owner
。 Model
将是一个实例obj
。
示例中还缺少一段重要的代码。通常将一些逻辑放入Model()
,以便允许访问描述符对象本身:
__get__
让我们尝试一下:
def __get__(self, obj, type_=None):
print(f'Descriptor was accessed with obj {obj} and type_ {type_}')
if obj is None:
# the descriptor was invoked on the class instead of an instance
# returning self here allows 'class attribute' access!
return self
return obj.__dict__[self.the_internal_name]
因此,正如您所看到的,描述符实例(您可以将其视为类属性,如果需要)处理>>> m = Model()
>>> m.__dict__
{}
>>> m.int_field = 123
>>> m.__dict__
{'int_field_internal': 123}
>>> m.int_field
Descriptor was accessed with obj <__main__.Model object at 0x7fffe8186080> and type_ <class '__main__.Model'>
123
>>> Model.int_field
Descriptor was accessed with obj None and type_ <class '__main__.Model'>
<__main__.IntField at 0x7fffe8174748>
>>> Model.int_field.__dict__
Descriptor was accessed with obj None and type_ <class '__main__.Model'>
{'the_internal_name': 'int_field_internal'}
实例上的int_field
名称以及{ {1}}本身。在实例上访问时,它会在实例上的Model
名称中获取/设置状态。由于我已经消除了名称的歧义,您还可以访问Model
作为正常属性获取/设置绕过描述符的相同值,如果您愿意(这是属性的常见做法) ,使用内部int_field_internal
)。
在一般用法中,我们在实例dict中使用与描述符&#34;标签&#34;相同的名称。为此,因为......为什么不呢?此阴影对实例名称的正常属性访问,因为描述符协议具有优先级。但你仍然可以绕过描述符,如下所示:
m.int_field_internal
看看你是否可以猜出_name
将返回什么,为什么,并尝试一下!然后尝试创建一个这样的新模型:
print(m.int_field) # this access goes through the descriptor object
print(m.__dict__['int_field']) # this is 'raw' access
然后你会看到如何使用Model.int_field.the_internal_name
。描述符允许您在常规属性访问期间(无论是获取,设置还是删除...)进行任何您想要的操作。您可以根据需要使用它来管理类,实例或描述符本身的状态 - 这就是为什么它可能令人困惑的解释,但这也是它的强大和灵活性的地方功能来自。