First things first. __init__
is required to return None
. The Python docs say "no value may be returned", but in Python "dropping off the end" of a function without hitting a return statement is equivalent to return None
. So explicitly returning None
(either as a literal or by returning the value of an expression resulting in None
) does no harm either.
So the __init__
method of DeclarativeMeta
that you quote looks a little odd to me, but it doesn't do anything wrong. Here it is again with some comments added by me:
def __init__(cls, classname, bases, dict_):
if '_decl_class_registry' in cls.__dict__:
# return whatever type's (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's __init__ returns
return type.__init__(cls, classname, bases, dict_)
This could more succinctly and cleanly be written as:
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_)
I have no idea why the SqlAlchemy developers felt the need to return whatever type.__init__
returns (which is constrained to be None
). Perhaps it's proofing against a future when __init__
might return something. Perhaps it's just for consistency with other methods where the core implementation is by deferring to the superclass; usually you'd return whatever the superclass call returns unless you want to post-process it. However it certainly doesn't actually do anything.
So your print result
printing None
is just showing that everything is working as intended.
Next up, lets take a closer look at what metaclasses actually mean. A metaclass is just the class of a class. Like any class, you create instances of a metaclass (i.e. classes) by calling the metaclass. The class block syntax isn't really what creates classes, it's just very convenient syntactic sugar for defining a dictionary and then passing it to a metaclass invocation to create a class object.
The __metaclass__
attribute isn't what does the magic, it's really just a giant hack to communicate the information "I would like this class block to create an instance of this metaclass instead of an instance of type
" through a back-channel, because there's no proper channel for communicating that information to the interpreter.1
This will probably be clearer with an example. Take the following class block:
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
This is roughly syntactic sugar for doing the following2:
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_)
So a "class having an attribute __metaclass__
having [the metaclass] as value" IS an instance of the metaclass! They are exactly the same thing. The only difference is that if you create the class directly (by calling the metaclass) rather than with a class block and the __metaclass__
attribute, then it doesn't necessarily have __metaclass__
as an attribute.3
That invocation of metaclass
at the end is exactly like any other class invocation. It will call metaclass.__new__(classname, bases, dict_)
to get create the class object, then call __init__
on the resulting object to initialise it.
The default metaclass, type
, only does anything interesting in __new__
. And most uses for metaclasses that I've seen in examples are really just a convoluted way of implementing class decorators; they want to do some processing when the class is created, and thereafter not care. So they use __new__
because it allows them to execute both before and after type.__new__
. The net result is that everyone thinks that __new__
is what you implement in metaclasses.
But you can in fact have an __init__
method; it will be invoked on the new class object after it has been created. If you need to add some attributes the the class, or record the class object in a registry somewhere, this is actually a slightly more convenient place to do it (and the logically correct place) than __new__
.
1 In Python3 this is addressed by adding metaclass
as a "keyword argument" in the base-class list, rather than as a an attribute of the class.
2 In reality it's slightly more complicated due to the need for metaclass compatibility between the class being constructed and all the bases, but this is the core idea.
3 Not that even a class with a metaclass (other than type
) created the usual way necessarily has to have __metaclass__
as an attribute; the correct way to check the class of a class is the same way as checking the class of anything else; use cls.__class__
, or apply type(cls)
.