Есть ли способ доступа к __dict__ (или что-то в этом роде), который включает базовые классы?

Предположим, что мы имеем следующую иерархию классов:

class ClassA:

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

class ClassB(ClassA):

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

Если я исследую __ dict __ в ClassB, я вижу только атрибут bar:

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

    if name.startswith("__"):
        continue

    print(name)

Выводится строка

Я могу использовать собственные средства для получения атрибутов не только указанного типа, но и его предков. Однако, мой вопрос заключается в том, есть ли у меня в 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)

... возвращает оба бара и foo.

Обратите внимание, что я упрощаю некоторые вещи: коллизии имен, используя элементы(), когда для этого примера я мог бы использовать dict, пропуская все, что начинается с __, игнорируя возможность того, что у двух предков есть общий предок и т.д.

EDIT1 - Я попытался сделать пример простым. Но я действительно хочу как имя атрибута, так и ссылку на атрибут для каждого класса и класса предков. Один из приведенных ниже ответов дает мне лучший ответ, я отправлю код лучше, когда я его заработаю.

EDIT2 - Это делает то, что я хочу, и очень красноречиво. Это основано на ответе Эли ниже.

def get_attributes(type):

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

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

    return attributes

Он возвращает имена атрибутов и их ссылки.

EDIT3 - один из ответов, приведенных ниже, используется с помощью inspect.getmembers. Это кажется очень полезным, потому что он, как и dict, работает только на классах предков.

Поскольку большая часть того, что я пыталась сделать, это найти атрибуты, помеченные определенным дескриптором, и включить классы предков, вот какой-то код, который поможет сделать это, если он кому-то поможет:

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

Ответ 1

Вы должны использовать модуль 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 здесь.

Ответ 2

Вы хотите использовать dir:

for attr in dir(ClassB):
    print attr

Ответ 3

К сожалению, нет единого составного объекта. Каждый доступ к атрибуту для (нормального) объекта 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).

Ответ 4

Вот функция, которую я написал, в тот же день. Лучший ответ - использовать модуль inspect, так как использование __dict__ дает нам ВСЕ функции (наши + наследуемые) и (ВСЕ?) Элементы данных AND и свойства. Где inspect дает нам достаточно информации, чтобы отсеять то, что мы не хотим.

def _inspect(a, skipFunctionsAlways=True, skipMagic = True):
    """inspects object attributes, removing all the standard methods from 'object',
    and (optionally) __magic__ cruft.

    By default this routine skips __magic__ functions, but if you want these on
    pass False in as the skipMagic parameter.

    By default this routine skips functions, but if you want to see all the functions,
    pass in False to the skipFunctionsAlways function. This works together with the
    skipMagic parameter: if the latter is True, you won't see __magic__ methods.
    If skipFunctionsAlways = False and skipMagic = False, you'll see all the __magic__
    methods declared for the object - including __magic__ functions declared by Object

    NOT meant to be a comprehensive list of every object attribute - instead, a
    list of every object attribute WE (not Python) defined. For a complete list
    of everything call inspect.getmembers directly"""

    objType = type(object)
    def weWantIt(obj):
        #return type(a) != objType
        output= True
        if (skipFunctionsAlways):
            output = not ( inspect.isbuiltin(obj) ) #not a built in 

        asStr = ""
        if isinstance(obj, types.MethodType):
            if skipFunctionsAlways:  #never mind, we don't want it, get out.
                return False
            else:
                asStr = obj.__name__
                #get just the name of the function, we don't want the whole name, because we don't want to take something like:
                #bound method LotsOfThings.bob of <__main__.LotsOfThings object at 0x103dc70>
                #to be a special method because it module name is special
                #WD-rpw 02-23-2008

                #TODO: it would be great to be able to separate out superclass methods
                #maybe by getting the class out of the method then seeing if that attribute is in that class?
        else:
            asStr = str(obj)

        if (skipMagic):
            output = (asStr.find("__") == -1 ) #not a __something__

        return (output)

    for value in inspect.getmembers( a, weWantIt ):
        yield value

Ответ 5

{k: getattr(ClassB, k) for k in dir(ClassB)}

Собственные значения (вместо <property object...>) будут представлены при использовании экземпляра ClassB.

И, конечно, вы можете отфильтровать это, добавив в конце такие вещи, как if not k.startswith('__').