Почему метакласс не имеет доступа к атрибутам, унаследованным от подкласса класса, определенного метаклассом?

Класс Foo определяется метаклассом Meta. Метакласс перебирает атрибуты класса и выводит их на экран.

Класс Bar подклассы Foo. Однако метакласс не печатает атрибуты, унаследованные от Bar.

Почему у метакласса нет доступа к атрибутам Foo, унаследованным от Bar? Что я не понимаю в системе метаклассов Python?

Вот пример кода в 2.7:

class Meta(type):
    def __init__(cls, name, bases, attrs):
        print "bases = {}".format(bases)
        items = {k:v for k,v in attrs.iteritems() if not k.startswith('__')}
        for k,v in items.iteritems():
            print k, v

class Foo(object):
    __metaclass__ = Meta
    hi = 1

# This prints:
# bases = (<type 'object'>,)
# hi 1

class Bar(Foo):
    pass

# This prints:
# bases = (<class '__main__.Foo'>,)

Foo.hi
#prints 1
Bar.hi
#prints 1

person Matthew Moisen    schedule 28.04.2015    source источник


Ответы (2)


Параметр attrs для __init__ содержит только атрибуты для этого класса, но не для его баз.

Объект Bar не имеет атрибута hi. Вместо этого, когда вы запросите Bar.hi, поиск начнется с Bar, выясните, что у него нет hi, а затем посмотрите в базе Foo, чтобы найти его.

person orlp    schedule 28.04.2015

Как говорит @orlp, attrs содержит только словарь класса для создаваемого класса. Однако у вас все еще есть доступ к hi, потому что он находится в атрибуте __dict__ одной из баз Foo. То есть вы можете сделать что-то похожее на то, что у вас есть, но пройтись по базовым классам и распечатать записи в каждом словаре базового класса.

Другой подход заключается в использовании dir(), который должен приблизительно возвращать список всех атрибутов, которые имеет класс. Я говорю примерно, потому что класс может реализовать __getattr__ или __getattribute__ для возврата атрибутов "на лету", а это означает, что у класса может не быть четко определенного набора атрибутов для возврата dir() — см. полный отказ от ответственности здесь. Но во многих распространенных случаях будет работать что-то вроде следующего:

class Meta(type):
    def __init__(cls, name, bases, attrs):
        print "bases = {}".format(bases)
        for attr in dir(cls):
            if not attr.startswith('_'):
                print attr, getattr(cls, attr)

class Foo(object):
    __metaclass__ = Meta
    hi = 1

class Bar(Foo):
    pass

Что печатает:

bases = (<type 'object'>,)
hi 1
bases = (<class '__main__.Foo'>,)
hi 1
person jme    schedule 28.04.2015