7

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
bukzor
  • 37,539
  • 11
  • 77
  • 111
  • 1
    What exactly do you want? If you want to understand what `type` actually does, the best way may be to just read the [source code](http://hg.python.org/cpython/file/babb9479b79f/Objects/typeobject.c). If you want to implement your own object system in Python, making minimal use of the built-in features to do so, then that's achievable if you read up enough on metaclasses, special method lookup, descriptors, etc., but it won't interoperate with the built-in system quite like real classes do. If you want to make something fully compatible with built-in APIs like `type(thing)`... – user2357112 Mar 20 '14 at 23:22
  • I’m not really sure I understand what your question is. Do you want to know [how metaclasses work](http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python)? – poke Mar 20 '14 at 23:22
  • you're going to have to make so much use of things like `type.__new__`, there's little point in going through with it. – user2357112 Mar 20 '14 at 23:24
  • I'm trying to clarify the operation of `type` by re-implementing it in python, as in the above [`__getattribute__` example](http://docs.python.org/2/howto/descriptor.html#invoking-descriptors). I don't know how to say it more clearly =/ – bukzor Mar 20 '14 at 23:42
  • You can’t reimplement `type` in Python, because `type` is literally the lowest thing in Python’s the type system. So everything you would come up with, would already be using `type` multiple times. Even defining a class uses `type` internally. – poke Mar 20 '14 at 23:50
  • [Even PyPy](https://bitbucket.org/pypy/pypy/src/2d49948e8eba5debe0bea87199d29e96d8521b2e/lib-python/2.7/types.py?at=default) doesn't seem to do this. – Two-Bit Alchemist Mar 21 '14 at 00:03
  • 2
    @Two-BitAlchemist: you meant to look at this https://bitbucket.org/pypy/pypy/src/tip/pypy/objspace/std/typeobject.py – bukzor Mar 21 '14 at 00:23
  • You may not be trying to reimplement `type` via high level python as it is just like trying to reimplement binary bits in C. If what you want is a "pythonic" explanation of what `type` does, my answer tries to achieve that. – Augusto G Sep 05 '14 at 21:01

2 Answers2

1

__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:

Actual C code

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

Roughly equal Python Implementation

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

Final notes

  • I'd check the __new__ implementation (it's called type_new)
  • If you would like to learn how Python works internally, try learning the C API and then read the C source code.
  • I'm very new to the Python C source code, so I may have overlooked something. Please correct me anyone knows!
Augusto G
  • 76
  • 3
0

I am not aware of any Python analogs; however, if you want to know what type does exactly you'll need to dig into the c source.

Generally, it does what any metaclass does: makes adjustments to the resulting class based on various specifications.

For example:

--> huh = type('my_type', (), {'some_var':7})
--> huh
<class '__main__.my_type'>
--> h = huh()
--> huh.some_var
7

Here, type is creating a new class with the name of my_type and a class attribute named some_var with an initial value of 7.

If you want to see a useful, if somewhat complex, example of a metaclass in the stdlib, check out the new Enum data type in 3.4.

Ethan Furman
  • 63,992
  • 20
  • 159
  • 237