我正在通过Python的元类操纵类的创建。但是,尽管一个类由于其父类而具有属性,但我无法将其删除。
class Meta(type):
def __init__(cls, name, bases, dct):
super().__init__(name, bases, dct)
if hasattr(cls, "x"):
print(cls.__name__, "has x, deleting")
delattr(cls, "x")
else:
print(cls.__name__, "has no x, creating")
cls.x = 13
class A(metaclass=Meta):
pass
class B(A):
pass
上面的代码的执行在创建类AttributeError
时产生一个B
:
A has no x, creating
B has x, deleting
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-3-49e93612dcb8> in <module>()
10 class A(metaclass=Meta):
11 pass
---> 12 class B(A):
13 pass
14 class C(B):
<ipython-input-3-49e93612dcb8> in __init__(cls, name, bases, dct)
4 if hasattr(cls, "x"):
5 print(cls.__name__, "has x, deleting")
----> 6 delattr(cls, "x")
7 else:
8 print(cls.__name__, "has no x, creating")
AttributeError: x
为什么不能删除现有属性?
编辑:我认为我的问题与试图通过实例删除类变量的delattr on class instance produces unexpected AttributeError不同。相反,我尝试通过类(别名实例)删除类变量(别名实例)。因此,给定的修复程序在这种情况下不起作用。
EDIT2 :olinox14是正确的,这是“删除父类的属性”的问题。问题可以减少为:
class A:
x = 13
class B(A):
pass
del B.x
答案 0 :(得分:3)
似乎python将x
变量注册为A类的参数:
然后,当您尝试将其从B
类中删除时,与delattr
方法存在一些冲突,就像@David Herring提供的the link中提到的那样... < / p>
一种解决方法可能是从A
类中明确删除参数:
delattr(A, "x")
答案 1 :(得分:2)
正如您在简化版本中所得出的结论,发生的事情很简单:属性“ x”不在类中,而是在超类中,并且常规的Python属性查找将从那里获取以进行读取-并进行编写。 ,即设置一个新的cls.x
将在其子类中创建一个本地x:
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
如果您需要在继承的类中禁止属性,则可以通过元类中的自定义__getattribute__
方法(而不是__getattr__
)来实现。元类上甚至不需要其他方法(尽管您可以使用它们,例如,编辑要抑制的属性列表)
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",}
如果有人尝试获得B.x
,它将提高。
通过这种策略,将__delattr__
和__setattr__
方法添加到元类中,就可以删除仅在子类上的超类中定义的属性:
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
在控制台上:
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