Deleting existing class variable yield AttributeError
It seems like python register the x
variable as a paramameter of the A class:
Then, when you try to delete it from the B
class, there is some conflict with the delattr
method, like mentionned in the link that @David Herring provided...
A workaround could be deleting the parameter from the A
class explicitly:
delattr(A, "x")
As you concluded in your simplified version, what goes on is simple: the attribute "x" is not in the class, it is in the superclasses, and normal Python attribute lookup will fetch it from there for reading - and on writting, that is, setting a new cls.x
will create a local x in he subclass:
In [310]: class B(A):
...: pass
...:
In [311]: B.x
Out[311]: 1
In [312]: del B.x
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-312-13d95ac593bf> in <module>
----> 1 del B.x
AttributeError: x
In [313]: B.x = 2
In [314]: B.__dict__["x"]
Out[314]: 2
In [315]: B.x
Out[315]: 2
In [316]: del B.x
In [317]: B.x
Out[317]: 1
If you need to suppress atributes in inherited classes, it is possible, though, through a custom __getattribute__
method (not __getattr__
) in the metaclass. There is even no need for other methods on the metaclass (though you can use them, for, for example, editing a list of attributes to suppress)
class MBase(type):
_suppress = set()
def __getattribute__(cls, attr_name):
val = super().__getattribute__(attr_name)
# Avoid some patologic re-entrancies
if attr_name.startswith("_"):
return val
if attr_name in cls._suppress:
raise AttributeError()
return val
class A(metaclass=MBase):
x = 1
class B(A):
_suppress = {"x",}
If one tries to get B.x
it will raise.
With this startegy, adding __delattr__
and __setattr__
methods to the metaclass enables one to del attributes that are defined in the superclasses just on the subclass:
class MBase(type):
_suppress = set()
def __getattribute__(cls, attr_name):
val = super().__getattribute__(attr_name)
# Avoid some patologic re-entrancies
if attr_name.startswith("_"):
return val
if attr_name in cls._suppress:
raise AttributeError()
return val
def __delattr__(cls, attr_name):
# copy metaclass _suppress list to class:
cls._suppress = set(cls._suppress)
cls._suppress.add(attr_name)
try:
super().__delattr__(attr_name)
except AttributeError:
pass
def __setattr__(cls, attr_name, value):
super().__setattr__(attr_name, value)
if not attr_name.startswith("_"):
cls._suppress -= {attr_name,}
class A(metaclass=MBase):
x = 1
class B(A):
pass
And on the console:
In [400]: B.x
Out[400]: 1
In [401]: del B.x
In [402]: B.x
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
...
In [403]: A.x
Out[403]: 1