有没有办法访问包含基类的 __dict__(或类似的东西)?

14 投票
5 回答
6682 浏览
提问于 2025-04-17 08:28

假设我们有以下的类结构:

class ClassA:

    @property
    def foo(self): return "hello"

class ClassB(ClassA):

    @property
    def bar(self): return "world"

如果我在ClassB上查看__dict__,我只看到bar这个属性:

for name,_ in ClassB.__dict__.items():

    if name.startswith("__"):
        continue

    print(name)

输出是bar

我可以自己想办法获取指定类型及其父类的属性。不过,我想问的是,Python里有没有现成的方法可以做到这一点,而不需要我自己重新发明轮子。

def return_attributes_including_inherited(type):
    results = []
    return_attributes_including_inherited_helper(type,results)
    return results

def return_attributes_including_inherited_helper(type,attributes):

    for name,attribute_as_object in type.__dict__.items():

        if name.startswith("__"):
            continue

        attributes.append(name)

    for base_type in type.__bases__:
        return_attributes_including_inherited_helper(base_type,attributes)

运行我的代码如下...

for attribute_name in return_attributes_including_inherited(ClassB):
    print(attribute_name)

...会返回bar和foo两个属性。

需要注意的是,我在简化一些内容:比如属性名冲突、在这个例子中我可以直接用dict而不是items()、跳过以__开头的属性、忽略两个父类可能有共同的祖先等等。

编辑1 - 我试着保持例子简单。但我其实想要每个类和父类的属性名和属性引用。下面的一个回答让我找到了更好的方向,等我把代码弄好再分享。

编辑2 - 这个方法实现了我想要的,而且非常简洁。它基于下面Eli的回答。

def get_attributes(type):

    attributes = set(type.__dict__.items())

    for type in type.__mro__:
        attributes.update(type.__dict__.items())

    return attributes

它返回了属性名和它们的引用。

编辑3 - 下面的一个回答建议使用inspect.getmembers。这个方法看起来非常有用,因为它不仅适用于当前类,还能操作父类。

由于我想要找到带有特定描述符的属性,并且包括父类,这里有一些代码可以帮助实现这个目标,希望对其他人也有帮助:

class MyCustomDescriptor:

    # This is greatly oversimplified

    def __init__(self,foo,bar):
        self._foo = foo
        self._bar = bar
        pass

    def __call__(self,decorated_function):
        return self

    def __get__(self,instance,type):

        if not instance:
            return self

        return 10

class ClassA:

    @property
    def foo(self): return "hello"

    @MyCustomDescriptor(foo="a",bar="b")
    def bar(self): pass

    @MyCustomDescriptor(foo="c",bar="d")
    def baz(self): pass

class ClassB(ClassA):

    @property
    def something_we_dont_care_about(self): return "world"

    @MyCustomDescriptor(foo="e",bar="f")
    def blah(self): pass

# This will get attributes on the specified type (class) that are of matching_attribute_type.  It just returns the attributes themselves, not their names.
def get_attributes_of_matching_type(type,matching_attribute_type):

    return_value = []

    for member in inspect.getmembers(type):

        member_name = member[0]
        member_instance = member[1]

        if isinstance(member_instance,matching_attribute_type):
            return_value.append(member_instance)

    return return_value

# This will return a dictionary of name & instance of attributes on type that are of matching_attribute_type (useful when you're looking for attributes marked with a particular descriptor)
def get_attribute_name_and_instance_of_matching_type(type,matching_attribute_type):

    return_value = {}

    for member in inspect.getmembers(ClassB):

        member_name = member[0]
        member_instance = member[1]

        if isinstance(member_instance,matching_attribute_type):
            return_value[member_name] = member_instance

    return return_value

5 个回答

4

很遗憾,Python中并没有一个单一的复合对象。每次访问一个普通Python对象的属性时,首先会检查这个对象的obj.__dict__,然后再查看它所有父类的属性。虽然有一些内部缓存和优化,但你无法直接访问到一个单一的对象。

不过,有一个可以让你的代码更好的建议,就是使用cls.__mro__,而不是cls.__bases__cls.__mro__包含了这个类的所有祖先,并且是按照Python查找的顺序排列的,所有共同的祖先只出现一次。这样一来,你的类型搜索方法就可以不需要递归了。简单来说...

def get_attrs(obj):
    attrs = set(obj.__dict__)
    for cls in obj.__class__.__mro__:
        attrs.update(cls.__dict__)
    return sorted(attrs)

... 这大致上实现了默认的dir(obj)功能。

6

你想要使用 dir 这个功能:

for attr in dir(ClassB):
    print attr
13

你应该使用Python的inspect模块来进行任何类似的自省功能。

.
.
>>> class ClassC(ClassB):
...     def baz(self):
...         return "hiya"
...
>>> import inspect
>>> for attr in inspect.getmembers(ClassC):
...   print attr
... 
('__doc__', None)
('__module__', '__main__')
('bar', <property object at 0x10046bf70>)
('baz', <unbound method ClassC.baz>)
('foo', <property object at 0x10046bf18>)

想了解更多关于inspect模块的信息,可以点击这里

撰写回答