pythonmetaclass

Can't delete attribute added by metaclass


I've been playing around with Python metaclasses, and I have encountered a rather strange instance where I cannot delete an attribute added to a class object by a metaclass. Consider the following two classes:

class meta(type):
    
    def __new__(mcls, name, bases, attrs):
        obj = super().__new__(mcls, name, bases, attrs)
        setattr(obj, "temp", "This is temporary")
        return obj
    
    def __call__(cls, *args, **kwargs):
        obj = type.__call__(cls, *args, **kwargs)
        # delattr(obj, "temp")
        return obj
        
class test(metaclass=meta):
    
    def __init__(self):
        print(self.temp)

If I create an instance of the "test" class, it will print "This is temporary", as expected. However, if I uncomment the delattr statement in the __call__ method, it throws an error complaining about the object having no attribute "temp". If I remove the delattr statement and run the following:

a = test()
print(a.temp)
print(hasattr(a, "temp"))
delattr(a, "temp")

It will print:

This is temporary
This is temporary
True
AttributeError: 'a' object has no attribute 'temp'

So it clearly recognises that there is an attribute there, it can print the value of the attribute, it just can't delete the attribute. Is this expected behaviour?

If it matters, this is python 3.12


Solution

  • Your __new__ method creates the temp attribute in the new class you are creating.

    While in the __call__ method, after getting the return of super().__call__(...) you have a reference to a new instance of that class. The instance has no direct temp attribute - instead, it is located by expressions like hasattr(a, "temp") because the attribute lookup searches the class for an attribute if it doesn't existig in the instance (so it finds the one stored in test.__dict__). However the delattr call doesn't scalate to the class: there is no temp in a.__dict__, so it errors out.

    The way to have an attribute in the class that is not "visible" in the instance would be to customize the class' __getattribute__ method.

    Or, as it is, it will happen if you add the attribute as a plain attribute on the metaclass itself (and don´t do anything on __new__): the same mechanism of fall backing the search of an attribute to the class if it doesn´t exist in the instance will be triggered: as the class is itself an instance of the metaclass, the "property" will behave properly - but the is no fallback from the instance to the class and them to the metaclass - so, instances of the class test won´t "see" an attribute defined on the metaclass.