python 解释__dict__属性

yrdbyhpb  于 2023-01-01  发布在  Python
关注(0)|答案(3)|浏览(91)

我真的对__dict__属性感到困惑。我已经搜索了很多,但仍然不确定输出。
当这个属性被用在一个对象、一个类或一个函数中时,有人能解释一下它从零开始的用法吗?

50pmv0ei

50pmv0ei1#

基本上它包含了描述对象的所有属性,可以用来修改或读取属性。引用__dict__的文档
用于存储对象(可写)属性的字典或其他Map对象。
记住,在Python中,所有的东西都是对象。当我说所有的东西时,我指的是所有的东西,比如函数、类、对象等等(你没看错吧,类。类也是对象)。例如:

def func():
    pass

func.temp = 1

print(func.__dict__)

class TempClass:
    a = 1
    def temp_function(self):
        pass

print(TempClass.__dict__)

意志输出

{'temp': 1}
{'__module__': '__main__', 
 'a': 1, 
 'temp_function': <function TempClass.temp_function at 0x10a3a2950>, 
 '__dict__': <attribute '__dict__' of 'TempClass' objects>, 
 '__weakref__': <attribute '__weakref__' of 'TempClass' objects>, 
 '__doc__': None}
dz6r00yl

dz6r00yl2#

Python文档将__dict__定义为:
用于存储对象(可写)属性的字典或其他Map对象。
然而,这个定义有点模糊,这导致了__dict__的许多错误用法。
实际上,当你阅读这个定义时,你能分辨出什么是"可写"属性,什么不是吗?

    • 示例**

让我们举几个例子来说明它是多么令人困惑和不准确。

class A:

    foo = 1

    def __init__(self):
        self.bar = 2

    @property
    def baz(self):
        return self._baz

    @bar.setter
    def baz(self, value):
        self._baz = value

>>> a = A()
>>> a.foo
1
>>> a.bar
2

给定上面的类A并知道__dict__的定义,您能猜出a.__dict__的值是什么吗?

  • fooa的可写属性吗?
  • bara的可写属性吗?
  • baza的可写属性吗?
  • _baza的可写属性吗?

答案如下:

>>> a.__dict__
{'bar': 2}

令人惊讶的是,foo并没有出现,事实上,尽管可以通过a.foo访问,但它是类A的属性,而不是示例a的属性。
现在,如果我们将其显式定义为a的属性,会发生什么情况?

>>> a.foo = 1
>>> a.__dict__
{'bar': 2, 'foo': 1}

从我们的Angular 来看,没有什么真正的改变,a.foo仍然等于1,但现在它显示在__dict__中。注意,我们可以通过删除a.foo来继续使用它,例如:

>>> del a.foo
>>> a.__dict__
{'bar': 2}
>>> a.foo
1

这里发生的事情是我们删除了instance属性,并且调用a.foo又回到了A.foo
现在我们来看一下baz,我们可以假设在a.__dict__中找不到它,因为我们还没有给它赋值。

>>> a.baz = 3

好了,现在我们定义了a.baz。那么,baz是一个可写属性吗?_baz呢?

>>> a.__dict__
{'bar': 2, '_baz': 3}

__dict__的Angular 来看,_baz是一个可写属性,但baz不是,原因是baz是类A的属性,而不是示例a的属性。

>>> A.__dict__['baz']
<property at 0x7fb1e30c9590>

a.baz只是在幕后调用A.baz.fget(a)的抽象层。
让我们对我们亲爱的朋友更加狡猾,挑战它对"可写"的定义。

class B:

    def __init__(self):
        self.foobar = 'baz'

    def __setattr__(self, name, value):
        if name == 'foobar' and 'foobar' in self.__dict__:
            # Allow the first definition of foobar
            # but prevent any subsequent redefinition
            raise TypeError("'foobar' is a read-only attribute")
        super().__setattr__(name, value)

>>> b = B()
>>> b.foobar
'baz'
>>> b.foobar = 'bazbar'
TypeError: 'foobar' is a read-only attribute
>>> # From our developer's perspective, 'foobar' is not a writable attribute
>>> # But __dict__ doesn't share this point of view
>>> b.__dict__
{'foobar': 'baz'}
    • __dict__到底是什么**

多亏了上面例子中的行为,我们现在可以更好地理解__dict__实际上做了什么,但是我们需要从开发人员的Angular 切换到计算机的Angular :

  • __dict__包含存储在程序内存中用于此特定对象的数据。*

就是这样,__dict__公开了内存中对象地址处实际存储的内容。
data model上的Python文档也将其定义为对象的命名空间:
类示例有一个作为字典实现的命名空间,字典是搜索属性引用的第一个地方。当在字典中找不到属性,而示例的类有一个同名的属性时,搜索将继续类属性。
但是,我相信将__dict__看作对象的内存表可以更好地直观地显示此名称空间中包含了什么,不包含什么。

    • 但是**有个陷阱...
    • 你以为我们已经玩够了__dict__**

__dict__不是处理对象内存占用的方法,而是一种方法。
的确有另一种办法:__slots__。我不会在这里详细说明它是如何工作的,如果你想了解更多,已经有a very complete answer about __slots__了。但对我们来说重要的是,如果定义了插槽:

class C:
    __slots__ = ('foo', 'bar')

    def __init__(self):
        self.foo = 1
        self.bar = 2

>>> c = C()
>>> c.foo
1
>>> c.bar
2
>>> c.__dict__
AttributeError: 'C' object has no attribute '__dict__'

我们可以对__dict__说再见了。

    • 那么,我应该何时使用__dict__?**

正如我们所看到的,__dict__必须从计算机的Angular 来看,而不是从开发人员的Angular 来看。通常情况下,我们所认为的对象的"属性"并不直接与内存中实际存储的内容相关联。特别是使用属性或__getattr__时,这增加了一个抽象层,使我们感到舒适。
虽然使用__dict__来检查对象的属性在大多数琐碎的情况下都可以工作,但是我们不能100%地依赖它,这对于用来编写泛型逻辑的东西来说是一个耻辱。
__dict__的用例应该仅限于检查对象的内存内容,但这并不常见。请记住,在定义插槽时,__dict__可能根本没有定义(或者缺少实际存储在内存中的一些属性)。
在Python的控制台中,快速检查一个类的属性和方法,或者一个对象的属性也是非常有用的(我知道我刚才说过我们不能依赖它,但是在控制台中,谁在乎它是否有时会失败,或者它是否准确)。

    • 谢谢,但是...我该如何浏览对象的属性?**

我们看到__dict__经常被误用,我们不能真正依靠它来检查对象的属性。但是,正确的方法是什么呢?有没有办法从开发人员的抽象Angular 来浏览对象的属性呢?

是的。有几种方法可以做内省,正确的方法将取决于你实际上想要得到什么。示例属性、类属性、属性、私有属性,甚至方法,......从技术上讲,这些都是属性,根据你的情况,你可能想要包括一些而排除另一些。上下文也很重要。也许您正在使用的库已经通过其API公开了您想要的属性。
但总的来说,您可以信赖inspect module

class D:

    foo = 1
    __slots = ('bar', '_baz')

    @property
    def baz(self):
        return self._baz

    @baz.setter
    def baz(self, value):
        self._baz = value

    def __init__(self):
        self.bar = 2
        self.baz = 3

    def pointless_method(self):
        pass

>>> import inspect
>>> dict((k, v) for k, v in inspect.getmembers(d) if k[0] != '_')
{'bar': 2, 'baz': 3, 'foo': 1}
>>> dict((k, getattr(d, k)) for k, v in inspect.getmembers(D) if inspect.isdatadescriptor(v) or inspect.isfunction(v))
{
 '__init__': <bound method D.__init__ of <__main__.D object at 0x7fb1e26a5b40>>,
 '_baz': 3,
 'bar': 2,
 'baz': 3,
 'pointless_method': <bound method D.pointless_method of <__main__.D object at 0x7fb1e26a5b40>>
}
w6mmgewl

w6mmgewl3#

**dict**可以获取对象中的示例变量(数据属性)作为字典。

因此,如果下面有Person类:

class Person:
    x1 = "Hello"
    x2 = "World"
    
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def test1(self):
        pass
        
    @classmethod
    def test2(cls):
        pass
    
    @staticmethod
    def test3():
        pass

obj = Person("John", 27)    
print(obj.__dict__) # Here

**dict**获取nameage,其值位于字典中,如下所示:

{'name': 'John', 'age': 27}

并且,如果示例化后添加新示例变量gender,如下所示:

# ...

obj = Person("John", 27)
obj.gender = "Male" # Here
print(obj.__dict__)

**dict**获取nameagegender,它们的值在字典中,如下所示:

{'name': 'John', 'age': 27, 'gender': 'Male'}

此外,如果使用dir(),如下所示:

# ...

obj = Person("John", 27)
obj.gender = "Male" 
print(dir(obj)) # Here

我们可以将对象中的所有内容作为列表获取,如下所示:

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', 
'__format__', '__ge__', '__getattribute__', '__gt__', '__hash__',
'__init__', '__init_subclass__', '__le__', '__lt__', '__module__', 
'__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', 
'__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 
'age', 'gender', 'name', 'test1', 'test2', 'test3', 'x1', 'x2']

而且,就我所研究的和我所问的this question而言,在Python中,没有函数可以只获取对象中的静态或特殊变量,或者普通、类、静态或特殊方法。

相关问题