Подтвердить что ты не робот

Как автоматически регистрировать класс при его определении

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

registry = {}

def register( cls ):
   registry[cls.__name__] = cls() #problem here
   return cls

@register
class MyClass( Base ):
   def __init__(self):
      super( MyClass, self ).__init__() 

К сожалению, этот код генерирует ошибку NameError: global name 'MyClass' is not defined.

Что происходит в строке #problem here Я пытаюсь создать экземпляр MyClass, но декоратор еще не вернулся, так что его не существует.

Является ли это каким-то образом, используя метаклассы или что-то в этом роде?

4b9b3361

Ответ 1

Да, мета-классы могут это сделать. Метод meta class < __new__ возвращает класс, поэтому просто зарегистрируйте этот класс перед его возвратом.

class MetaClass(type):
    def __new__(cls, clsname, bases, attrs):
        newclass = super(MetaClass, cls).__new__(cls, clsname, bases, attrs)
        register(newclass)  # here is your register function
        return newclass

class MyClass(object):
    __metaclass__ = MetaClass

Предыдущий пример работает в Python 2.x. В Python 3.x определение MyClass немного отличается (в то время как MetaClass не отображается, потому что оно не изменяется - за исключением того, что super(MetaClass, cls) может стать super(), если вы хотите):

#Python 3.x

class MyClass(metaclass=MetaClass):
    pass

[edit: исправлено отсутствующий аргумент cls до super().__new__()]

[edit: добавлен пример Python 3.x]

[edit: исправленный порядок аргументов super() и улучшенное описание различий 3.x]

Ответ 2

Проблема на самом деле не вызвана указанной вами строкой, а вызовом super в методе __init__. Проблема остается, если вы используете метакласс, как было предложено dappawit; причина, по которой пример из этого ответа работает, просто заключается в том, что dappawit упростил ваш пример, опуская класс Base и, следовательно, вызов super. В следующем примере ни ClassWithMeta, ни DecoratedClass не работают:

registry = {}
def register(cls):
    registry[cls.__name__] = cls()
    return cls

class MetaClass(type):
    def __new__(cls, clsname, bases, attrs):
        newclass = super(cls, MetaClass).__new__(cls, clsname, bases, attrs)
        register(newclass)  # here is your register function
        return newclass

class Base(object):
    pass


class ClassWithMeta(Base):
    __metaclass__ = MetaClass

    def __init__(self):
        super(ClassWithMeta, self).__init__()


@register
class DecoratedClass(Base):
    def __init__(self):
        super(DecoratedClass, self).__init__()

В обоих случаях проблема одинакова; функция register вызывается (либо метаклассом, либо непосредственно в качестве декоратора) после создания объекта класса, но до того, как он привязан к имени. Здесь super получает gnarly (в Python 2.x), потому что он требует, чтобы вы обращались к классу в вызове super, который вы можете только разумно сделать, используя глобальное имя и доверяя, что он будет связанное с этим именем к моменту вызова вызова super. В этом случае это доверие неуместно.

Я думаю, что метакласс - это неправильное решение. Метаклассы предназначены для создания семейства классов, которые имеют общее обычное поведение, точно так же, как классы предназначены для создания семейства экземпляров, которые имеют общее обычное поведение. Все, что вы делаете, это вызов функции в классе. Вы не определяете класс для вызова функции в строке, и вы не должны определять метакласс для вызова функции в классе.

Таким образом, проблема заключается в фундаментальной несовместимости между: (1) использованием крючков в процессе создания класса для создания экземпляров класса и (2) с использованием super.

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

Другой способ - не зацепить register в создание класса. Добавление register(MyClass) после каждого определения вашего класса довольно эквивалентно добавлению @register перед ними или __metaclass__ = Registered (или тому, что вы называете метаклассом) в них. Линия вниз вниз намного менее самодокументирована, чем хорошая декларация вверху класса, хотя, поэтому это не очень удобно, но опять-таки это может быть разумным решением.

Наконец, вы можете обратиться к хакам, которые неприятны, но, вероятно, будут работать. Проблема в том, что имя просматривается в глобальной области модуля непосредственно перед тем, как оно было привязано к нему. Таким образом, вы можете обмануть, как показано ниже:

def register(cls):
    name = cls.__name__
    force_bound = False
    if '__init__' in cls.__dict__:
        cls.__init__.func_globals[name] = cls
        force_bound = True
    try:
        registry[name] = cls()
    finally:
        if force_bound:
            del cls.__init__.func_globals[name]
    return cls

Вот как это работает:

  • Сначала проверяем, находится ли __init__ в cls.__dict__ (в отличие от того, имеет ли он атрибут __init__, который всегда будет истинным). Если он унаследовал метод __init__ из другого класса, мы, вероятно, хорошо (потому что суперкласс будет уже привязан к его имени обычным способом), и магия, которую мы собираемся сделать, не делает 't работаем в object.__init__, поэтому мы хотим избежать попытки, если класс использует по умолчанию __init__.
  • Мы просматриваем метод __init__ и получаем его func_globals словарь, в котором будут искать глобальные запросы (например, найти класс, упомянутый в вызове super). Обычно это глобальный словарь модуля, в котором изначально был определен метод __init__. Такой словарь должен вставить в него cls.__name__, как только возвращается register, поэтому мы просто вставляем его раньше.
  • Наконец, создадим экземпляр и вставьте его в реестр. Это находится в блоке try/finally, чтобы убедиться, что мы удаляем созданное нами связывание, независимо от того, создает ли экземпляр исключения; это очень маловероятно (поскольку 99.999% времени, когда имя вот-вот будет отскок в любом случае), но лучше всего сохранить такую ​​странную магию как можно более изолированную, чтобы свести к минимуму вероятность того, что когда-нибудь какая-то другая странная магия плохо взаимодействует с он.

Эта версия register будет работать независимо от того, вызывается ли она как декоратор или метакласс (что, по-прежнему я считаю, не является хорошим использованием метакласса). Есть некоторые неясные случаи, когда он будет терпеть неудачу:

  • Я могу представить себе странный класс, который не имеет метода __init__, но наследует тот, который вызывает self.someMethod, а someMethod переопределяется в определяемом классе и вызывает вызов super. Вероятно, маловероятно.
  • Метод __init__ мог быть определен в другом модуле изначально, а затем использован в классе, выполнив __init__ = externally_defined_function в блоке класса. Атрибут func_globals другого модуля, хотя это означает, что наше временное связывание скроет любое определение этого имени класса в этом модуле (oops). Опять же, маловероятно.
  • Возможно, другие странные случаи, о которых я не думал.

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

Ответ 3

Вызов базового класса напрямую должен работать (вместо использования super()):

  def __init__(self):
        Base.__init__(self)