Есть ли способ доступа к __dict__ (или что-то подобное), который включает в себя базовые классы? - PullRequest
11 голосов
/ 16 декабря 2011

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

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)

Вывод: 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.

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

Ответы [ 5 ]

9 голосов
/ 16 декабря 2011

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

5 голосов
/ 16 декабря 2011

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

for attr in dir(ClassB):
    print attr
2 голосов
/ 16 декабря 2011

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

1 голос
/ 16 декабря 2011

Вот функция, которую я написал, в тот день. Лучший ответ - использование модуля inspect, так как использование __dict__ дает нам ВСЕ функции (наши + унаследованные) и (ВСЕ?) Члены-данные И свойства. Где 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's 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
0 голосов
/ 11 июля 2017
{k: getattr(ClassB, k) for k in dir(ClassB)}

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...