Ответ 1
Как и всякая другая форма определения вложенного класса, вложенный метакласс может быть более "компактным и удобным" (если вы в порядке, не повторное использование этого метакласса, кроме как наследования) для многих видов "производственного использования", но может быть несколько неудобным для отладки и интроспекции.
В принципе, вместо того, чтобы присвоить метаклассу правильное имя верхнего уровня, вы получите все настраиваемые метаклассы, определенные в модуле, который будет неустранимым друг от друга на основе их атрибутов __module__
и __name__
это то, что использует Python для формирования их repr
, если это необходимо). Рассмотрим:
>>> class Mcl(type): pass
...
>>> class A: __metaclass__ = Mcl
...
>>> class B:
... class __metaclass__(type): pass
...
>>> type(A)
<class '__main__.Mcl'>
>>> type(B)
<class '__main__.__metaclass__'>
IOW, если вы хотите изучить "какой тип является классом A" (метакласс является типом класса, помните), вы получите ясный и полезный ответ - он Mcl
в основном модуле. Однако, если вы хотите изучить "какой тип является классом B", ответ не так полезен: он говорит, что он __metaclass__
в модуле main
, но это даже не правда:
>>> import __main__
>>> __main__.__metaclass__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute '__metaclass__'
>>>
... там нет такой вещи; что предание вводит в заблуждение и не очень полезно; -).
Перечень классов по существу '%s.%s' % (c.__module__, c.__name__)
- простое, полезное и согласованное правило, но во многих случаях, например, оператор class
не является уникальным в области модуля или вообще не находится в области модуля (но, скорее, внутри функции или тела класса) или даже не существует (классы, естественно, могут быть построены без оператора class
, явно называя их метаклассом), это может быть несколько обманчивым (и лучшим решением является избежать, по возможности, в тех особых случаях, за исключением случаев, когда их можно получить существенным преимуществом). Например, рассмотрим:
>>> class A(object):
... def foo(self): print('first')
...
>>> x = A()
>>> class A(object):
... def foo(self): print('second')
...
>>> y = A()
>>> x.foo()
first
>>> y.foo()
second
>>> x.__class__
<class '__main__.A'>
>>> y.__class__
<class '__main__.A'>
>>> x.__class__ is y.__class__
False
с двумя операторами class
в той же области видимости, второй повторяет имя (здесь, A
), но существующие экземпляры ссылаются на первую привязку имени по объекту, а не по имени - поэтому оба класса объекты остаются, один доступен только через атрибут type
(или __class__
) его экземпляров (если есть - если нет, этот первый объект исчезает) - два класса имеют одинаковое имя и модуль (и, следовательно, одни и те же представление), но они разные объекты. Классы, вложенные в тела классов или функций или созданные путем прямого вызова метакласса (включая type
), могут вызывать подобное замешательство, если вы когда-либо вызываете отладку или интроспекцию.
Итак, вложенность метакласса в порядке, если вам никогда не придется отлаживать или иным образом анализировать этот код, и с ним можно жить, если тот, кто это делает, понимает эти причуды (хотя это никогда не будет так удобно, как использование приятного, реальное имя, конечно же, как и отладка функции, закодированной с помощью lambda
, не может быть так удобна, как отладка, закодированная с помощью def
). По аналогии с lambda
vs def
вы можете обоснованно утверждать, что анонимное "вложенное" определение в порядке для метаклассов, которые настолько просты, без проблем, что никакая отладка или интроспекция никогда не понадобится.
В Python 3 "вложенное определение" просто не работает - там метакласс должен быть передан в качестве аргумента ключевого слова классу, как в class A(metaclass=Mcl):
, поэтому определение __metaclass__
в теле не имеет эффект. Я считаю, что это также говорит о том, что определение вложенного метакласса в коде Python 2, вероятно, подходит только в том случае, если вы точно знаете, что код никогда не будет перенесен на Python 3 (поскольку вы делаете этот порт намного сложнее, и вам нужно будет де-гнездование определения метакласса для этой цели) - "throwaway" code, другими словами, которого не будет в течение нескольких лет, когда какая-то версия Python 3 приобретет огромные, непревзойденные преимущества скорости, функциональности или третьих сторон, поддержка Python 2.7 (последняя версия Python 2).
Кодекс, который вы ожидаете отбросить, как показывает история вычислений, имеет прекрасную привычку удивлять вас полностью и быть еще около 20 лет спустя (хотя, возможно, код, который вы писали примерно в то же время "на протяжении веков" совершенно забыт;-). Это, несомненно, предполагает отказ от вложенного определения метаклассов.