Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/perl/9.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 3.6文档中的描述符协议示例是否不正确?_Python_Python 3.6_Descriptor - Fatal编程技术网

Python 3.6文档中的描述符协议示例是否不正确?

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

我是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_字段是一个类范围的属性,不是吗

所以,主人。迪克特将拥有这样一把钥匙。但是,在方法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()