如果
__dict__
被覆盖,是否可以获得对象的 true __dict__
?还有比下面更简单的解决方案吗?
我遇到了这个例子,其中
__dict__
被覆盖并感到好奇。我以为 python 正在使用 __dict__
来识别对象的属性,但事实证明它可以被覆盖并且属性仍然有效。所以,原来的__dict__
仍然在那里。
class MyClass:
__dict__ = {}
obj = MyClass()
obj.hacky = 5
# 5, {}
print(obj.hacky, obj.__dict__)
寻找解决方案我找到了这个。虽然它有点 hacky,但它有效 - 它交换
__class__
来访问原始 __dict__
,然后将其交换回来。它甚至可以与 __class__
、__setattr__
和 __dict__
一起用于其他用途。
def get_true_dict(obj: object) -> dict:
cls = type(obj)
newcls = type('',(),{}) # one line empty class
# __class__ is <attribute '__class__' of 'object' objects>
object.__dict__['__class__'].__set__(obj, newcls)
# obj.__class__ = newcls # works only if `__class__` and `__setattr__` are not "bad"
res = obj.__dict__
# no safety as class is fine
obj.__class__ = cls
return res
# normal
class X: ...
x = X()
x.a = 42
assert get_true_dict(x) is x.__dict__
# blocked setattr and __dict__ and __class__
d = {}
class X:
__dict__ = d
__class__ = 42
__setattr__ = lambda *_: 1/0
x = X()
assert x.__dict__ is d
assert get_true_dict(x) is not x.__dict__
d = get_true_dict(x)
d['a'] = 42
assert x.a == 42
d['b'] = 24
assert x.b == 24
print(d)
还有一个更晦涩的选项可以通过使用
ctypes
和偏移量来获取它,但它似乎只有在之前已经访问过 obj.__dict__
时才有效,否则返回 ValueError: PyObject is NULL
。除非有一些解决方法来初始化 __dict__
,否则此解决方案的缺陷违背了 get_true_dict
方法的全部目的。
def get_true_dict(obj: object) -> dict:
import ctypes
# -24 = -48 + 24
offset = type(obj).__dictoffset__ + obj.__sizeof__()
res = ctypes.py_object.from_address(id(obj) + offset)
return res.value
class X: ...
x = X()
x.__dict__ # <<< comment this and it will change the behaviour and will result in
# ValueError: PyObject is NULL too
x.a = 42
assert get_true_dict(x) is x.__dict__
d = {}
class X:
__dict__ = d
x = X()
x.a = 42
assert x.__dict__ is d
# ValueError: PyObject is NULL
assert get_true_dict(x) is not x.__dict__
d = get_true_dict(x)
PS 代码片段,作者:@denballakh