Может ли метакласс быть вызываемым?

Чтобы поймать ваш взгляд:

Я думаю, что документация может быть неправильной!

Согласно документации Python 2.7.12, 3.4.3. Настройка создания класса:

__metaclass__ Эта переменная может быть любой вызываемой, принимающей аргументы для имени, базы и словаря. При создании класса вместо встроенного type() используется вызываемый объект.

Новое в версии 2.2.

Однако эта статья утверждает:

В: Ух ты! Могу ли я использовать объект любого типа в качестве __metaclass__?

A: Нет. Это должен быть подкласс типа базового объекта. ...

Итак, я провел эксперимент самостоятельно:

class metacls(list):    # <--- subclassing list, rather than type
    def __new__(mcs, name, bases, dict):
        dict['foo'] = 'metacls was here'
        return type.__new__(mcs, name, bases, dict)

class cls(object):
    __metaclass__ = metacls
    pass

Это дает мне:

Traceback (most recent call last):
  File "test.py", line 6, in <module>
    class cls(object):
  File "test.py", line 4, in __new__
    return type.__new__(mcs, name, bases, dict)
TypeError: Error when calling the metaclass bases
    type.__new__(metacls): metacls is not a subtype of type

Так документ действительно неправильный?


person nalzok    schedule 28.07.2016    source источник


Ответы (1)


Нет, подойдет любой callable. В вашем случае метод type.__new__() имеет ограничение, которое вы нарушаете; это не имеет ничего общего с тем, что вы назначаете __metaclass__.

Функция является вызываемой:

def metaclass_function(name, bases, body):
    return type(name, bases, body)

Этот просто возвращает результат type() (не type.__new__()), но это просто вызываемый. Возвращаемое значение используется в качестве класса. Вы действительно можете вернуть что угодно:

>>> class Foo(object):
...     __metaclass__ = lambda *args: []
...
>>> Foo
[]

Здесь вызываемый объект создал экземпляр списка, поэтому Foo привязан к списку. Не очень полезно, но __metaclass__ просто вызывается для создания чего-то, и это что-то используется напрямую.

В вашем примере первый аргумент type.__new__() не является типом, и именно этот вызов завершается ошибкой. mcs привязан к list, а не (подкласс) type. type.__new__() вправе устанавливать такие ограничения.

Теперь, поскольку метакласс все еще привязан к объекту класса (type(ClassObj) возвращает его) и используется при разрешении поиска атрибутов в объекте класса (где атрибут недоступен в MRO класса), обычно это хорошая идея сделать его подклассом type, потому что это дает вам правильную реализацию таких вещей, как __getattribute__. Именно по этой причине type.__new__() вводят ограничение на то, что может быть передано в качестве первого аргумента; это тот первый аргумент, который type() присоединяется к возвращаемому объекту класса.

person Martijn Pieters    schedule 28.07.2016
comment
@sunqingyao: нет, bound означает назначено. Присвоение Foo = [] выполнено. Оператор class в основном рассматривается как функция; тело выполняется как в функции, а все локальные имена извлекаются в словарь. Затем объект класса создается путем вызова bodydict.get('__metaclass__', type) (так что либо значение __metaclass__, либо, если отсутствует, type) и передачи имени класса, баз классов и словаря тела. Всё, что возвращается, назначается имени класса. Связывание — это технический термин; другие вещи тоже связываются, например import или for. - person Martijn Pieters; 28.07.2016
comment
Но говоря, что mcs связан с list, вы, кажется, имеете в виду, что mcs является экземпляром подкласса list. - person nalzok; 28.07.2016
comment
@sunqingyao: нет, mcs = list, а не list(), потому что это то, что передается в to__new__. См. object.__new__() документацию; __new__ — это статический метод, в котором текущий класс передается в качестве первого аргумента. - person Martijn Pieters; 28.07.2016
comment
А поскольку каждый объект является подклассом object, object.__new__ не накладывает никаких ограничений на свой первый аргумент. - person nalzok; 28.07.2016
comment
@sunqingyao: object.__new__ накладывает ограничения. Просто попробуйте object.__new__(dict) для примера. - person Martijn Pieters; 28.07.2016
comment
Думаю, я также понимаю, почему первый аргумент type.__new__ не может быть list: это потому, что экземпляр list не является типом и, следовательно, не классом. Таким образом, метакласс не является мета-тогда. - person nalzok; 28.07.2016
comment
Хм... потому что dict делает экземпляры, созданные метаклассом, больше не типами: мы не можем создать подкласс или получить экземпляр из экземпляра dict. - person nalzok; 28.07.2016
comment
Давайте сделаем вывод: и type.__new__, и object.__new__ принимают только экземпляры type, чьи экземпляры все еще являются типами (или классами) в качестве их первого аргумента. Я прав? - person nalzok; 28.07.2016
comment
@sunqingyao: не экземпляры, а подклассы (или на самом деле «подтипы»). Подробнее см. type_new_wrapper(). . type.__new__ требует, чтобы первый аргумент был подклассом type, object.__new__ объекта, но, что более важно, PyType_Check должен быть истинным, что является быстрым способом проверить, является ли что-то подклассом type. - person Martijn Pieters; 28.07.2016
comment
Я думаю, что PyType_Check — это быстрый способ проверить, является ли что-то экземпляром type (чтобы убедиться, что это тип/класс), а PyType_IsSubtype проверяет, является ли что-то подклассом type. - person nalzok; 29.07.2016
comment
@sunqingyao: да, PyType_Check тесты для экземпляров type, также известных как классы. - person Martijn Pieters; 29.07.2016