Ответ 1
Прежде всего. __init__
требуется , чтобы вернуть None
. Python docs говорят, что "никакое значение не может быть возвращено", но в Python "отбрасывание конца" функции без нажатия оператора return эквивалентно до return None
. Поэтому явное возвращение None
(либо в виде литерала, либо путем возврата значения выражения, приводящего к None
) тоже не наносит вреда.
Поэтому метод __init__
DeclarativeMeta
, который вы цитируете, выглядит немного странным для меня, но он не делает ничего плохого. Здесь снова добавлены некоторые комментарии, добавленные мной:
def __init__(cls, classname, bases, dict_):
if '_decl_class_registry' in cls.__dict__:
# return whatever type (our superclass) __init__ returns
# __init__ must return None, so this returns None, which is okay
return type.__init__(cls, classname, bases, dict_)
else:
# call _as_declarative without caring about the return value
_as_declarative(cls, classname, cls.__dict__)
# then return whatever type __init__ returns
return type.__init__(cls, classname, bases, dict_)
Это может быть сжато и чисто написано как:
def __init__(cls, classname, bases, dict_):
if '_decl_class_registry' not in cls.__dict__:
_as_declarative(cls, classname, cls.__dict__)
type.__init__(cls, classname, bases, dict_)
Я понятия не имею, почему разработчики SqlAlchemy считали, что нужно возвращать все type.__init__
(которые ограничены None
). Возможно, это защищает от будущего, когда __init__
может что-то вернуть. Возможно, это просто для согласованности с другими методами, где основная реализация - путем отсрочки на суперкласс; обычно вы возвращаете все вызовы суперкласса, если вы не хотите его обрабатывать. Однако он, конечно же, ничего не делает.
Итак, ваша печать print result
None
показывает, что все работает по назначению.
Далее, давайте более подробно рассмотрим, что означает метаклассы. Метакласс - это класс класса. Как и любой класс, вы создаете экземпляры метакласса (например, классы), вызывая метакласс. Синтаксис блока классов на самом деле не является тем, что создает классы, это просто очень удобный синтаксический сахар для определения словаря, а затем передача его вызову метакласса для создания объекта класса.
Атрибут __metaclass__
- это не то, что делает магию, это просто гигантский взлом для передачи информации "Я бы хотел, чтобы этот классный блок создавал экземпляр этого метакласса вместо экземпляра type
" через обратный канал, потому что нет надлежащего канала для передачи этой информации интерпретатору. 1
Это, вероятно, будет более ясным с примером. Возьмите следующий класс:
class MyClass(Look, Ma, Multiple, Inheritance):
__metaclass__ = MyMeta
CLASS_CONST = 'some value'
def __init__(self, x):
self.x = x
def some_method(self):
return self.x - 76
Это примерно синтаксический сахар для выполнения следующих 2:
dict_ = {}
dict_['__metaclass__'] = MyMeta
dict_['CLASS_CONST'] = 'some value'
def __init__(self, x):
self.x = x
dict_['__init__'] = __init__
def some_method(self):
return self.x - 76
dict_['some_method'] = some_method
metaclass = dict_.get('__metaclass__', type)
bases = (Look, Ma, Multiple, Inheritance)
classname = 'MyClass'
MyClass = metaclass(classname, bases, dict_)
Итак, "класс, имеющий атрибут __metaclass__
, имеющий [метакласс] как значение" IS экземпляр метакласса! Это точно то же самое. Единственное различие заключается в том, что если вы создаете класс напрямую (путем вызова метакласса), а не с помощью блока класса и атрибута __metaclass__
, то он не обязательно должен иметь __metaclass__
в качестве атрибута. 3
Этот вызов metaclass
в конце точно так же, как и любой другой вызов класса. Он вызовет metaclass.__new__(classname, bases, dict_)
, чтобы создать объект класса, затем вызовите __init__
в результирующем объекте для его инициализации.
Метакласс по умолчанию type
делает только что-нибудь интересное в __new__
. И большинство применений для метаклассов, которые я видел в примерах, на самом деле просто запутанный способ реализации декораторов классов; они хотят сделать некоторую обработку, когда класс создан, и после этого не заботятся. Поэтому они используют __new__
, потому что он позволяет им выполнять как до, так и после type.__new__
. В результате все думают, что __new__
- это то, что вы реализуете в метаклассах.
Но вы можете иметь метод __init__
; он будет вызываться в новом объекте класса после его создания. Если вам нужно добавить некоторые атрибуты в класс или записать объект класса в реестр где-нибудь, это на самом деле немного более удобное место для этого (и логически правильное место), чем __new__
.
1 В Python3 это адресовано добавлением metaclass
в качестве "аргумента ключевого слова" в список базового класса, а не как атрибут класса.
2 В действительности это немного усложняется из-за необходимости совместимости метакласса между построенным классом и всеми базовыми элементами, но это основная идея.
3 Не то, чтобы даже класс с метаклассом (кроме type
), созданный обычным способом, обязательно должен был иметь __metaclass__
как атрибут; правильный способ проверки класса класса аналогичен проверке класса чего-либо еще; используйте cls.__class__
или примените type(cls)
.