Python 3.6文档中的描述符协议示例是否不正确?
我是Python新手,在查看其文档时,我遇到了下面的描述符协议示例,我认为它是不正确的 看起来像Python 3.6文档中的描述符协议示例是否不正确?,python,python-3.6,descriptor,Python,Python 3.6,Descriptor,我是Python新手,在查看其文档时,我遇到了下面的描述符协议示例,我认为它是不正确的 看起来像 class IntField: def __get__(self, instance, owner): return instance.__dict__[self.name] def __set__(self, instance, value): if not isinstance(value, int): raise Val
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_字段是一个类范围的属性,不是吗
所以,主人。迪克特将拥有这样一把钥匙。但是,在方法uuu get uuuuuu中使用了一个实例。uuuu dict uuuuu没有该键。因此,从使用该方法的角度来看,该方法不处理类范围的属性,而是处理实例范围的属性
另一方面,方法集合也不处理类范围的属性,而是创建实例范围的属性,因为使用了
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_字段除了名称之外,与类范围的属性int_字段没有任何共同之处
文档中的示例也是如此。直观地说,我们可以期望附加到描述符的是类范围的属性。但事实并非如此。描述符只是借用类范围属性的名称。另一方面,类范围的属性确实可以附加到描述符
因此,在我看来,文档中的示例只是让读者感到困惑。示例很好
但是,在方法uuu get uuuuuu中使用了一个实例。uuuu dict uuuuu没有该键
一旦您实际设置了instance.int_字段,就会有这样一个键,该字段将调用属性setter并分配该键
另一方面,方法集合也不处理类范围的属性,而是创建实例范围的属性
setter不应该创建class属性。它分配给getter正在寻找的实例dict键。我想我可以看到您在这里缺少的谜题的哪一部分:“\uu get\uuuu”中的自我是描述符!这就是我们在实例中设置状态的原因。通常不会在self(即描述符)上设置状态,因为这样模型的每个实例都将共享该状态 示例中没有矛盾之处,但此处使用的名称有点模棱两可。也许改名会有所帮助:
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”上的名称和实例dict“int_field_internal”中使用的名称之间的歧义
上面代码中的所有self都引用描述符实例。还有一个描述符实例处理模型所有实例的属性访问。文档称为所有者的类型将是Model。obj将是一个实例、模型
示例中还缺少一段重要的代码。为了允许访问描述符对象本身,通常会在_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'}
看看你是否能猜出Model.int_field.the_internal_name将返回什么,为什么,然后试试看!然后尝试创建如下新模型:
class Model2:
field_one = IntField()
field_two = IntField()
然后您将看到如何使用集合名称。描述符允许您在常规属性访问期间进行任何您想要的操作,无论是get、set还是delete。。。。您可以在类、实例或描述符本身上使用它来管理状态(如您所愿),这就是为什么解释起来会很混乱的原因,但这也是此功能的强大性和灵活性的来源 设置实例属性是关键。这就是它应该做的。我认为你误解了描述符协议。描述符劫持属性访问中的点。将描述符视为类属性或实例属性并没有多大帮助。是的,在这个级别上,属性的准确定义变得很重要,而且最好从底层存储和属性访问的角度来考虑,而不是将属性视为一个具体的东西。@VladFrommosco:Look,您对Python中属性的工作方式有很多非常坚定的先入之见,但在处理描述符或其他覆盖常规属性查找的内容时,这些先入之见完全是错误的。@VladFrommosco,因为我了解描述符协议,描述符属于一个类,不管它们管理的属性是什么。这与非静态行为的方法完全相同,它们属于类,但对实例进行操作。您似乎混淆了自己关于Python应该如何工作的错误想法,以及Python实际上是如何工作的。当你这么说的时候
ader可以预期,将要计算的是类范围的属性,这仅仅是您的前提,它来自于使用非Python语言。我认为没有什么好的,因为混合了两种属性。在我看来,这个例子是矛盾的,只会让读者感到困惑。@VladFrommosco:在这个层次上,类属性和实例属性是混乱的概念。模型中有一个“int_field”键。_dict__;指的是IntField的一个实例。这并不意味着int_字段是类属性;这意味着IntField实例将处理IntField、其子类及其实例上的int_字段属性名称的属性访问。该示例并不自相矛盾。它可能与一些简化的、不完整的Python属性访问模型相矛盾,但它本身或完整的属性访问机制并不矛盾,我不能同意。使用描述符方法,您可以实现所有实例都将访问类范围的属性,而两个实例都不会创建自己的属性。这是一个错误的例子。使用描述符方法,您可以实现所有实例都将访问一个类范围的属性,而两个实例都不会创建自己的属性-好吧,您可以这样做,但这会有点奇怪,并且不会使其他用途出错。我可以把香蕉塞进鼻子里,但这并不意味着用香蕉做其他事情是错误的。
class Model2:
field_one = IntField()
field_two = IntField()