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
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.