I don't really understand how the base metaclass works (aka type). Does anyone know of a pure-python analogue for its functionality?
The python docs often do this for C-level code that is hard to fully describe in english (for example, see the explaination of __getattribute__), but not for type.
I do know how to get started. Since defining the behavior of type using a subclass of type would be a bit like saying "type works the way type works", I define a duck-typed metaclass. It works some, but not enough.
class MetaClassDuck(object):
@classmethod
def __new__(self, mcs, name, bases, attrs):
"""Create a new class object."""
newcls = super(MetaClassDuck, self).__new__(mcs)
newcls.__dict__.update(attrs)
newcls.__name__ = name
newcls.__bases__ = bases
return newcls
def __call__(cls, *args, **kwargs):
"""Calling a class results in an object instance."""
###########################################################
# Fill in the blank:
# I don't see a way to implement this without type.__new__
###########################################################
return newobj
class MyClass(object):
__metaclass__ = MetaClassDuck
one = 1
_two = 2
@property
def two(self):
return self._two
# This bit works fine.
assert type(MyClass) is MetaClassDuck
assert MyClass.one == 1
assert isinstance(MyClass.two, property)
myobj = MyClass()
# I crash here:
assert myobj.one == 1
assert myobj.two == 2
class MyClass2(MyClass):
three = 3
assert type(MyClass2) is MetaClassDuck
assert MyClass2.one == 1
assert isinstance(MyClass2.two, property)
assert MyClass2.three == 3
myobj2 = MyClass2()
assert myobj2.one == 1
assert myobj2.two == 2
assert myobj2.three == 3
__new__ is in charge of creating the new instance, not __call__. __call__ just passes on the instance creation work to __new__, and returns what __new__ returns, calling __init__ if needed.
The best way to answer this type (pun intended) of question is digging in the C code. Just download the source code, untar it and vim Objects/typeobject.c or whatever you use to read and fiddle with code.
If you look at it, you'll find C implementations of all the components of the type metaclass. __new__is grotesquely big, FIY.
def __call__(cls, *args, *kwds): would look like:
static PyObject *
type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
PyObject *obj;
if (type->tp_new == NULL) {
PyErr_Format(PyExc_TypeError,
"cannot create '%.100s' instances",
type->tp_name);
return NULL;
}
obj = type->tp_new(type, args, kwds);
if (obj != NULL) {
# /* Ugly exception: when the call was type(something),
# don`t call tp_init on the result. */
if (type == &PyType_Type &&
PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 1 &&
(kwds == NULL ||
(PyDict_Check(kwds) && PyDict_Size(kwds) == 0)))
return obj;
# /* If the returned object is not an instance of type,
# it won`t be initialized. */
if (!PyType_IsSubtype(obj->ob_type, type))
return obj;
type = obj->ob_type;
if (PyType_HasFeature(type, Py_TPFLAGS_HAVE_CLASS) &&
type->tp_init != NULL &&
type->tp_init(obj, args, kwds) < 0) {
Py_DECREF(obj);
obj = NULL;
}
}
return obj;
}
# added by me to help the Stackoverflow's syntax highlighter properly render comments
This is just a pythonic explanation of what I understand type.__call__ does. This is not a reimplementation of it!
I may have overlooked some aspects, as I'm fairly new to the PyC API, so feel free to correct me. But I'd implement it as follows:
def __call__(cls, *args, **kwds):
#We`ll be naming the class reference cls here, in the C code it's called type.
try:
obj = cls.__new__(cls, args, kwds)
except AttributeError:
#The code first checks whether there is a __new__ method, we just catch the AttributeError
#exception.
raise TypeError('cannot create {} instances', cls.__name__)
else:
#The last if block checks that no errors occurred *inside* cls.__new__
#(in the C code: type->tp_new)
cls.__init__(obj, args, kwds)
#The last if block checks whether any exception occurred while calling __init__
#(return NULL or return -1 tells the calling function that an error/exception occurred,
#IDK the difference between the two.)
return obj
__new__ implementation (it's called type_new)If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With