Класс декоратора Python для пространств имен с наследованием

Пытаюсь использовать классы-декораторы в базовом классе в Python 3, но не полностью понимаю поведение, которое я наблюдаю.

class tagged:
    """ Decorator itself. """
    def __init__(self, regClass, *args, **kwargs):
        """ called after class definition """
        print(self, regClass, regClass.name)

@tagged
class Base(object):
    name = "Base class"

#class Derived(Base):
#    name = "Derived class"

Первый класс работает как положено, и я вижу

__init__ <__main__.tagged object at 0x100632cc0> <class '__main__.Base'>
Base class

Но когда я раскомментирую Derived, он не пересылает свои аргументы декоратору, как я ожидал.

_init__ <__main__.tagged object at 0xb74c516c> <class '__main__.Base'>
Base class
__init__ <__main__.tagged object at 0xb74c51cc> Derived
Traceback (most recent call last):
  File "./prog.py", line 10, in <module>
  File "./prog.py", line 4, in __init__
AttributeError: 'str' object has no attribute 'name'

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


person kfsone    schedule 01.11.2014    source источник
comment
Я дал объяснение ниже, но многое еще неясно, почему вы пытаетесь сделать что-то таким образом. Во-первых, почему вы делаете свой декоратор классом, а не функцией?   -  person BrenBarn    schedule 01.11.2014
comment
@BrenBarn Я потратил кучу времени на чтение о декораторах, и использование класса, казалось, было рекомендуемым способом сделать это, а также другие вопросы S/O (stackoverflow.com/questions/7492068/)..   -  person kfsone    schedule 01.11.2014


Ответы (2)


У вас есть несколько проблем здесь. Главный из них заключается в том, что ваш декоратор tagged при использовании для декорирования класса возвращает экземпляр tagged, а не класса. Вот как работают декораторы: ваше базовое определение класса заменяется результатом вызова tagged(A). Декоратор не просто "вызывается после определения класса" - результат вызова декоратора заменяет исходный класс. Таким образом, ваш украшенный класс оказывается вовсе не классом, а экземпляром (из tagged).

В результате метакласс Derived оказывается tagged (поскольку это тип его базового «класса» Base, который, как уже упоминалось, на самом деле является экземпляром tagged), поэтому определение Derived пытается вызвать tagged.__init__ и терпит неудачу, потому что аргумент он передается, потому что regClass - это имя класса ("Производный") вместо класса. (Из вашего вопроса я предполагаю, что подробности того, почему вы получаете эту конкретную ошибку, вероятно, не имеют отношения к тому, что вы пытаетесь сделать.)

Не совсем понятно, что вы пытаетесь здесь сделать. Вы упомянули «пересылку своих аргументов декоратору», но декораторы классов так не работают. Когда вы украшаете класс, он автоматически не украшает и его подклассы.

person BrenBarn    schedule 01.11.2014
comment
FWIW, выходит за рамки вопроса: я экспериментирую с различными способами, которыми я могу добиться саморегистрации пространств имен (не экземпляров классов, а определений классов). По сути, у меня есть каталог модуля с init.py, который определяет Base, а затем, чтобы добавить новую команду в систему, вы создаете newcmd.py, у которого есть класс NewCommand (Base), и NewCommand автоматически отображается в синтаксический анализатор, но на самом деле экземпляр создается только тогда, когда вы запускаете механизм верхнего уровня и используете команду NewCommand. - person kfsone; 01.11.2014
comment
@kfsone: если вы хотите, чтобы классы автоматически регистрировались, изучите метаклассы. - person BrenBarn; 01.11.2014
comment
большинство механизмов метаклассов, которые я видел, требуют шаблона той или иной формы (каждое производное должно быть включено), я надеялся, что декораторы были реализованы как некоторая форма унаследованного атрибута, чтобы я мог достичь этого с помощью одного украшение, но я вернусь к тому проспекту, тай :) - person kfsone; 01.11.2014
comment
@kfsone: я думаю, у вас это наоборот. Если вы декорируете класс, ни один из его подклассов не будет тем самым декорирован, если только вы не декорируете их вручную. Но если у класса есть определенный метакласс, то все его подклассы также имеют этот метакласс, если только вы вручную не назначите им другой. Таким образом, в целом декораторы разрешены, а метаклассы отключены. Здесь, на SO, есть много вопросов как о декораторах, так и о метаклассах. Затем вы можете пойти дальше и задать еще один вопрос, если у вас все еще есть сомнения. - person BrenBarn; 01.11.2014

Чтобы усилить то, что сказал БренБарн в своем ответе, я изменил ваш код. «tagged» теперь является функцией, которая возвращает класс. Как вы можете убедиться, этот код работает без ошибок (подтверждено с помощью python 3.3). Выходные данные показывают, что имя «Base» теперь привязано к объекту класса и поэтому может быть унаследовано Derived. Но строка «Производный класс» никогда не печатается.

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

def tagged(regClass):
    """ Decorator itself. """
    class tagme:   
        def __init__(self):
            """ called after class definition """
            print(self)
            print(regClass)
            print(regClass.name)
    return tagme

@tagged
class Base(object):
    name = "Base class"

print("After defining base",Base)
b = Base()

class Derived(Base):
    name = "Derived class"

d = Derived()
person Paul Cornelius    schedule 01.11.2014