python什么时候从实例__ -dict__中倒回____________________________?

发布于 2025-02-02 14:07:34 字数 361 浏览 3 评论 0原文

请参阅以下片段:

class Foo:
    class_var = "hi"

foo = Foo()
assert foo.class_var is Foo.class_var
assert "class_var" in Foo.__dict__
assert "class_var" not in foo.__dict__

所有断言在这里都通过,尽管我不确定身份主张是否通过是否令人惊讶。

python何时以及如何从实例__ dict __ dict __中回到类__ dict __

Please see the below snippet:

class Foo:
    class_var = "hi"

foo = Foo()
assert foo.class_var is Foo.class_var
assert "class_var" in Foo.__dict__
assert "class_var" not in foo.__dict__

All assertions here pass, though I am not sure if it's surprising that the identity assertion passes.

When and how does Python fall back onto a class __dict__ from an instance __dict__?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

落花随流水 2025-02-09 14:07:35

据我所知,何时将foo.class_var称为以下步骤:

  • Python开始在Foo对象的命名空间中寻找class_var。
  • 如果找到它,它将返回它。
  • 但是,在这种情况下,它找不到它,因此它以foo的类型为foo。
  • 它在foo中找到并返回。

As far as I know when foo.class_var is called the following steps happen:

  • Python starts to look for class_var in the namespace of the foo object.
  • If it finds it, it returns it.
  • In this case however it doesn't find it so it looks in the type of foo, which is Foo.
  • It finds it in Foo and returns it.
绮筵 2025-02-09 14:07:35

您应该知道“类属性”和“实例属性”之间的区别,在您的示例中,您具有class_var是类属性,在第一个servert语句中,它检查是否class_varfoo的实例,它通过,因为foo是类型foo,让我们在此处尝试另一个更清晰的示例:

class Foo:
    var1 = "Hello"
    def __init__(self,var2):
        self.var2 = var2
foo = Foo("Hello")
assert foo.var1 is Foo.var1
assert "var1" in Foo.__dict__
assert "var2" in foo.__dict__
assert "var1" in foo.__dict__

请注意,最后一个断言语句将引起错误,因为var1这是类属性,而不是实例属性。

You should know the difference between a "class attribute" and an "instance attribute", in your example, you have class_var which is class attribute, in the first assert statement, it check if class_var is in the instance of Foo , it pass, that because foo is of type Foo let's try another more clear examples here:

class Foo:
    var1 = "Hello"
    def __init__(self,var2):
        self.var2 = var2
foo = Foo("Hello")
assert foo.var1 is Foo.var1
assert "var1" in Foo.__dict__
assert "var2" in foo.__dict__
assert "var1" in foo.__dict__

notice something, the last assert statement is gonna raise an error, because var1 here is a class attribute, not instance attribute.

软糖 2025-02-09 14:07:35

我喜欢以一个示例来解释这一点,因为人们喜欢自己的经历。 带有子类的示例

class A:
    x = 1
    w = -1


class B(A):
    y = 2
    w = -2

    def __init__(self):
        self.z = 3
        self.w = -3


def get_dict(obj):
    """Get a __dict__ only with normal user defined keys"""
    return {k: v for k, v in obj.__dict__.items() if not k.startswith('__')}


b = B()

您可以想象getAttr(b,name)被简化了,例如:

if name in b.__dict__:
    return b.__dict__[name]
elif name in B.__dict__:
    return B.__dict__[name]
elif name in A.__dict__:
    return A.__dict__[name]
else:
    raise AttributeError(f"'B' object has no attribute '{name}'")

探索实例的所有__ dict __ dict __和所有类:

>>> get_dict(A)
{'x': 1, 'w': -1}
>>> get_dict(B)
{'y': 2, 'w': -2}
>>> get_dict(b)
{'z': 3, 'w': -3}

# change the values
>>> A.w = 10
>>> B.w = 20
>>> b.w = 30

>>> A.x, B.y, b.z
(1, 2, 3)
>>> A.w, B.w, b.w
(10, 20, 30)

>>> get_dict(A)
{'x': 1, 'w': 10}
>>> get_dict(B)
{'y': 2, 'w': 20}
>>> get_dict(b)
{'z': 3, 'w': 30}

cristifat的答案是精确的,但对于每个人来说都不够容易。

I like to explain it by an example becase people like their own experience. Example with a subclass:

class A:
    x = 1
    w = -1


class B(A):
    y = 2
    w = -2

    def __init__(self):
        self.z = 3
        self.w = -3


def get_dict(obj):
    """Get a __dict__ only with normal user defined keys"""
    return {k: v for k, v in obj.__dict__.items() if not k.startswith('__')}


b = B()

You can imagine that getattr(b, name) is implemented simplified like:

if name in b.__dict__:
    return b.__dict__[name]
elif name in B.__dict__:
    return B.__dict__[name]
elif name in A.__dict__:
    return A.__dict__[name]
else:
    raise AttributeError(f"'B' object has no attribute '{name}'")

Explore all __dict__ of the instance and all classes:

>>> get_dict(A)
{'x': 1, 'w': -1}
>>> get_dict(B)
{'y': 2, 'w': -2}
>>> get_dict(b)
{'z': 3, 'w': -3}

# change the values
>>> A.w = 10
>>> B.w = 20
>>> b.w = 30

>>> A.x, B.y, b.z
(1, 2, 3)
>>> A.w, B.w, b.w
(10, 20, 30)

>>> get_dict(A)
{'x': 1, 'w': 10}
>>> get_dict(B)
{'y': 2, 'w': 20}
>>> get_dict(b)
{'z': 3, 'w': 30}

The CristiFat's answer is precise, but maybe not easy enough for everybody.

清泪尽 2025-02-09 14:07:34

根据(已经提到的) (重点是我的):

自定义类

自定义类类型通常是由类定义创建的(请参见类定义)。 类具有字典对象实现的命名空间。类属性引用被翻译为此字典中的查找,例如cx被转化为c .__ dict __ [“ x”] (尽管有很多很多可以找到其他方法的钩子)。当那里找不到属性名称时,属性搜索将在基类中继续。

...

类实例

通过调用类对象创建类实例(请参见上文)。 类实例的命名空间以词典的形式实现,这是搜索属性引用的第一个位置。当那里找不到属性,并且实例的类具有该名称的属性,搜索将继续使用类属性

...

调用描述符

...

属性访问的默认行为是从对象的字典中获取,设置或删除属性。 例如,ax有一个以a .__ dict __ ['x']开始的查找链] ,并继续通过类型(a) 的基础类别不包括元素。

但是,如果查找值是定义描述符方法之一的对象,则Python可以覆盖默认行为并调用描述符方法。在优先链中发生这种情况的地方取决于定义了哪些描述符方法以及如何称呼它们。

类定义内定义的属性(但是在初始化器(或其他方法)之外)称为类属性,并且绑定到类本身而不是其实例 。就像 static 的成员来自 c ++ java [python.docs] EM> 强调 仍然是我的):

程序员的注释:类定义中定义的变量是类属性;它们通过实例共享。实例属性可以在使用self.name = value的方法中设置。 类和实例属性都可以通过“ self.name”访问,并且实例属性以这种方式访问​​访问同名的类属性 。类属性可以用作示例属性,但是使用可变值可能会导致意外结果。 描述符可用于创建具有不同实现详细信息的实例变量。

因此,属性查找顺序可以像以下(按升序顺序进行曲线,当找到属性名称时,只需返回其值(因此忽略其余的条目))。 (内置) __ getAttribute __ 方法:

  1. 描述符(如果有的话)

  2. 实例命名空间 foo .__ dict __ dict __ )))

  3. 实例类命名空间 foo .__ dict __ dict __ )))

  4. 实例类base类名称空间(e .__ dict __ for e in in foo .__ mro .__ mro __

  5. custom __getAttr__方法可能返回

    的任何东西

通常发生的事情,就像 python 是可以更改的高度自定义的( eg eg ) __ slots __ )。

对于确切的行为,您可以检查源代码( [github]:python/cpython-- (main)cpython/objects ):

  • typeObject.c type_getattro (Optionaly: super_getattro em>, slot_tp_getattro

  • object.c _pyobject_genericgetAttrwithDict

以下是一个示例,可以清除(希望)。

code00.py

#!/usr/bin/env python

import sys
from pprint import pformat as pf


def print_dict(obj, header="", indent=0, filterfunc=lambda x, y: not x.startswith("__")):
    if not header:
        header = getattr(obj, "__name__", None)
    if header:
        print("{:}{:}.__dict__:".format("  " * indent, header))
    lines = pf({k: v for k, v in getattr(obj, "__dict__", {}).items() if filterfunc(k, v)}, sort_dicts=False).split("\n")
    for line in lines:
        print("{:}{:}".format("  " * (indent + 1), line))
    print()


class Descriptor:
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, cls):
        print("{:s}.__get__".format(self.name))

    def __set__(self, instance, value):
        print("{:s}.__set__ - {:}".format(self.name, value))

    def __delete__(self, instance):
        print("{:s}.__delete__".format(self.name))


class Demo:
    cls_attr0 = 3.141593
    cls_attr1 = Descriptor("cls_attr1")

    '''
    def __getattribute__(self, name):
        print("__getattribute__:", self, name)
        return super().__getattribute__(name)
    '''

    '''
    def __getattr__(self, name):
        print("__getattr__:", self, name)
        return "something dummy"
    '''

    def __init__(self):
        self.inst_attr0 = 2.718282


def main(*argv):
    print("ORIGINAL")
    demos = [Demo() for _ in range(2)]
    demo0 = demos[0]
    demo1 = demos[1]
    print_dict(Demo)
    print_dict(demo0, header="demo0")
    print("\ndemo0 attrs:", demo0.cls_attr0, demo0.cls_attr1, demo0.inst_attr0)
    print_dict(demo1, header="\ndemo1")
    print("\ndemo1 attrs:", demo1.cls_attr0, demo1.cls_attr1, demo1.inst_attr0)

    print("\nALTER 1ST INSTANCE OBJECT")
    demo0.inst_attr0 = -3
    demo0.cls_attr0 = -5

    print_dict(Demo)
    print_dict(demo0, header="demo0")
    print("\ndemo0 attrs:", demo0.cls_attr0, demo0.cls_attr1, demo0.inst_attr0)
    print_dict(demo1, header="\ndemo1")
    print("\ndemo1 attrs:", demo1.cls_attr0, demo1.cls_attr1, demo1.inst_attr0)

    print("\nALTER CLASS")
    Demo.cls_attr0 = -7
    Demo.cls_attr1 = -9
    print_dict(Demo, header="Demo")
    print_dict(demo1, header="demo0")
    print("\ndemo0 attrs:", demo0.cls_attr0, demo0.cls_attr1, demo0.inst_attr0)
    print_dict(demo1, header="\ndemo1")
    print("\ndemo1 attrs:", demo1.cls_attr0, demo1.cls_attr1, demo1.inst_attr0)


if __name__ == "__main__":
    print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                   64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    rc = main(*sys.argv[1:])
    print("\nDone.")
    sys.exit(rc)

输出

  [cfati@cfati-5510-0:e:\ work \ dev \ stackoverflow \ q072399556]> ”
Python 3.9.9(标签/v3.9.9:CCB0E6A,2021,18:08:50)[MSC V.1929 64 BIT(AMD64)] 064BIT

原来的
演示.__ dict __:
  {'Cls_attr0':3.141593,
   'cls_attr1':< __ main __。描述符对象0x00000171b24fd0>}

demo0 .__ dict __:
  {'inst_attr0':2.718282}

cls_attr1 .__获取__

demo0 attrs:3.141593无2.718282

demo1 .__ dict __:
  {'inst_attr0':2.718282}

cls_attr1 .__获取__

demo1 attrs:3.141593无2.718282

Alter 1st实例对象
演示.__ dict __:
  {'Cls_attr0':3.141593,
   'cls_attr1':< __ main __。描述符对象0x00000171b24fd0>}

demo0 .__ dict __:
  {'inst_attr0':-3,'cls_attr0':-5}

cls_attr1 .__获取__

demo0 attrs:-5无-3

demo1 .__ dict __:
  {'inst_attr0':2.718282}

cls_attr1 .__获取__

demo1 attrs:3.141593无2.718282

Alter类
演示.__ dict __:
  {'Cls_attr0':-7,'Cls_attr1':-9}

demo0 .__ dict __:
  {'inst_attr0':2.718282}


demo0 attrs:-5 -9 -3

demo1 .__ dict __:
  {'inst_attr0':2.718282}


demo1 attrs:-7 -9 2.718282

完毕。
 

According to (already mentioned) [Python.Docs]: Data model (emphasis is mine):

Custom classes

Custom class types are typically created by class definitions (see section Class definitions). A class has a namespace implemented by a dictionary object. Class attribute references are translated to lookups in this dictionary, e.g., C.x is translated to C.__dict__["x"] (although there are a number of hooks which allow for other means of locating attributes). When the attribute name is not found there, the attribute search continues in the base classes.

...

Class instances

A class instance is created by calling a class object (see above). A class instance has a namespace implemented as a dictionary which is the first place in which attribute references are searched. When an attribute is not found there, and the instance’s class has an attribute by that name, the search continues with the class attributes.

...

Invoking Descriptors

...

The default behavior for attribute access is to get, set, or delete the attribute from an object’s dictionary. For instance, a.x has a lookup chain starting with a.__dict__['x'], then type(a).__dict__['x'], and continuing through the base classes of type(a) excluding metaclasses.

However, if the looked-up value is an object defining one of the descriptor methods, then Python may override the default behavior and invoke the descriptor method instead. Where this occurs in the precedence chain depends on which descriptor methods were defined and how they were called.

Attributes defined inside a class definition (but outside the initializer (or other methods)) are called class attributes, and are bound to the class itself rather than its instances. It's like static members from C++ or Java. [Python.Docs]: Compound statements - Class definitions states (emphasis still mine):

Programmer’s note: Variables defined in the class definition are class attributes; they are shared by instances. Instance attributes can be set in a method with self.name = value. Both class and instance attributes are accessible through the notation “self.name”, and an instance attribute hides a class attribute with the same name when accessed in this way. Class attributes can be used as defaults for instance attributes, but using mutable values there can lead to unexpected results. Descriptors can be used to create instance variables with different implementation details.

So, the attribute lookup order can be summarized like below (traverse in ascending order, when attribute name found simply return its value (therefore ignoring the remaining entries)). The first steps performed by the (builtin) __getattribute__ method:

  1. Descriptors (if any - note that their presence could also be triggered indirectly (by other features))

  2. Instance namespace (foo.__dict__)

  3. Instance class namespace (Foo.__dict__)

  4. Instance class base classes namespaces (e.__dict__ for e in Foo.__mro__)

  5. Anything that a custom __getattr__ method might return

The above is what typically happens, as Python being highly customizable that can be altered (e.g. __slots__).

For an exact behavior, you could check the source code ([GitHub]: python/cpython - (main) cpython/Objects):

  • typeobject.c: type_getattro (optionally: super_getattro, slot_tp_getattro)

  • object.c: _PyObject_GenericGetAttrWithDict

Here's an example that will clear things up (hopefully).

code00.py:

#!/usr/bin/env python

import sys
from pprint import pformat as pf


def print_dict(obj, header="", indent=0, filterfunc=lambda x, y: not x.startswith("__")):
    if not header:
        header = getattr(obj, "__name__", None)
    if header:
        print("{:}{:}.__dict__:".format("  " * indent, header))
    lines = pf({k: v for k, v in getattr(obj, "__dict__", {}).items() if filterfunc(k, v)}, sort_dicts=False).split("\n")
    for line in lines:
        print("{:}{:}".format("  " * (indent + 1), line))
    print()


class Descriptor:
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, cls):
        print("{:s}.__get__".format(self.name))

    def __set__(self, instance, value):
        print("{:s}.__set__ - {:}".format(self.name, value))

    def __delete__(self, instance):
        print("{:s}.__delete__".format(self.name))


class Demo:
    cls_attr0 = 3.141593
    cls_attr1 = Descriptor("cls_attr1")

    '''
    def __getattribute__(self, name):
        print("__getattribute__:", self, name)
        return super().__getattribute__(name)
    '''

    '''
    def __getattr__(self, name):
        print("__getattr__:", self, name)
        return "something dummy"
    '''

    def __init__(self):
        self.inst_attr0 = 2.718282


def main(*argv):
    print("ORIGINAL")
    demos = [Demo() for _ in range(2)]
    demo0 = demos[0]
    demo1 = demos[1]
    print_dict(Demo)
    print_dict(demo0, header="demo0")
    print("\ndemo0 attrs:", demo0.cls_attr0, demo0.cls_attr1, demo0.inst_attr0)
    print_dict(demo1, header="\ndemo1")
    print("\ndemo1 attrs:", demo1.cls_attr0, demo1.cls_attr1, demo1.inst_attr0)

    print("\nALTER 1ST INSTANCE OBJECT")
    demo0.inst_attr0 = -3
    demo0.cls_attr0 = -5

    print_dict(Demo)
    print_dict(demo0, header="demo0")
    print("\ndemo0 attrs:", demo0.cls_attr0, demo0.cls_attr1, demo0.inst_attr0)
    print_dict(demo1, header="\ndemo1")
    print("\ndemo1 attrs:", demo1.cls_attr0, demo1.cls_attr1, demo1.inst_attr0)

    print("\nALTER CLASS")
    Demo.cls_attr0 = -7
    Demo.cls_attr1 = -9
    print_dict(Demo, header="Demo")
    print_dict(demo1, header="demo0")
    print("\ndemo0 attrs:", demo0.cls_attr0, demo0.cls_attr1, demo0.inst_attr0)
    print_dict(demo1, header="\ndemo1")
    print("\ndemo1 attrs:", demo1.cls_attr0, demo1.cls_attr1, demo1.inst_attr0)


if __name__ == "__main__":
    print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                   64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    rc = main(*sys.argv[1:])
    print("\nDone.")
    sys.exit(rc)

Output:

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q072399556]> "e:\Work\Dev\VEnvs\py_pc064_03.09_test0\Scripts\python.exe" code00.py
Python 3.9.9 (tags/v3.9.9:ccb0e6a, Nov 15 2021, 18:08:50) [MSC v.1929 64 bit (AMD64)] 064bit on win32

ORIGINAL
Demo.__dict__:
  {'cls_attr0': 3.141593,
   'cls_attr1': <__main__.Descriptor object at 0x00000171B0B24FD0>}

demo0.__dict__:
  {'inst_attr0': 2.718282}

cls_attr1.__get__

demo0 attrs: 3.141593 None 2.718282

demo1.__dict__:
  {'inst_attr0': 2.718282}

cls_attr1.__get__

demo1 attrs: 3.141593 None 2.718282

ALTER 1ST INSTANCE OBJECT
Demo.__dict__:
  {'cls_attr0': 3.141593,
   'cls_attr1': <__main__.Descriptor object at 0x00000171B0B24FD0>}

demo0.__dict__:
  {'inst_attr0': -3, 'cls_attr0': -5}

cls_attr1.__get__

demo0 attrs: -5 None -3

demo1.__dict__:
  {'inst_attr0': 2.718282}

cls_attr1.__get__

demo1 attrs: 3.141593 None 2.718282

ALTER CLASS
Demo.__dict__:
  {'cls_attr0': -7, 'cls_attr1': -9}

demo0.__dict__:
  {'inst_attr0': 2.718282}


demo0 attrs: -5 -9 -3

demo1.__dict__:
  {'inst_attr0': 2.718282}


demo1 attrs: -7 -9 2.718282

Done.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文