Проблема на самом деле не в указанной вами строке, а в вызове 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
становится грубым (в 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
другого модуля означает, что наша временная привязка сожжет любое определение имени этого класса в этом модуле (упс). Опять маловероятно.
- Возможно, другие странные случаи, о которых я даже не думал.
Вы можете попытаться добавить больше хаков, чтобы сделать их немного более надежными в этих ситуациях, но природа Python заключается в том, что такие хаки возможны и что невозможно сделать их абсолютно пуленепробиваемыми.