Search code examples
pythonclasstuplesmetaclass

Why do I get an Error when creating an instanz of a class that has a metaclass


class  Meta(type):
    def __new__(cls, class_name, bases, attrs):

        a={}
        for name, val in attrs.items():
            if name.startswith("__"):
                a[name] = val
            else:
                a[name.upper()] = val

        return(class_name, bases, a)

class D(metaclass=Meta):
    x = 5
    y = 8

    def hello (self):
        print("hi")

c = D()
c.hello

Why am I getting this error:

'tuple' object is not callable line 20, in c = D()

Cant finde the reason can someone help me out?

(code based on the teachings of the video at https://www.youtube.com/watch?v=NAQEj-c2CI8 )


Solution

  • The last line inside your metaclasse __new__method, which reads

    return(class_name, bases, a)
    

    Returns a plain tuple - it should (and indeed, on the video you comment where you got the inspiration for your studies) call either type or type.__new__: there produce a new class that is then ready to be used:

    return type(class_name, bases, a)
    

    Although the video is a nice first introduction to playt around with metaclasses, (1) this is an advanced topic, and one actually can make a whole career in Python without ever needing to write a metaclass;

    (2) on calling type instead of type.__new__, the video is not technically correct: it works, but it has a different effect in that the resulting class, although being modified upon its creation, will have the regular type as its metaclass - by calling type directly, all the references to the metaclass itself (Meta in your snippet) are discarded. If instead you call super().__new__(cls, class_name, bases, a) , the created class will be an instance of the metaclass (conveyed under the name cls in this call), and all subclasses of the created class (D in the example), will also have the same metaclass and be created through this __new__ method.

    One extra detail on your experiment: you are testing for attributes that starts with a double underscore ("__") - The Python compilation process automatically mangle all such names, (but names "sandwiched" between a pair of "__") to include the class name itself as a prefix - so if D would have a __x attribute, it would show up as _D__x in the attrs dict received in the metaclass -

    I see in the linked video that is the example there; however this part of the code is never tested or verified: no __ attributes are demonstrated to be preserved. It will preserve reserved "magic" names as __init__, though. As stated earlier, the video is not 100% good for teaching, as the author itself seems to be just showing some of his own experimenting.

    And one last comment regarding the specifc error you are getting: whatever the metaclass __new__ method returns is used by Python as the object bound to the name on the class statement (D in this case). So, D becames an "ordinary" variable containing a tuple, instead of a new class. When you execute D(), Python tries to call the object, and you get the error that tuples are not callable.