我有一个子类,我希望它不包含基类上存在的类属性。
我试过这个,但它不起作用:
>>> class A(object):
... x = 5
>>> class B(A):
... del x
Traceback (most recent call last):
File "<pyshell#1>", line 1, in <module>
class B(A):
File "<pyshell#1>", line 2, in B
del x
NameError: name 'x' is not defined
我怎样才能做到这一点?
仔细想想你为什么要这样做;你可能没有。考虑不要让B从A继承。
子类化的想法是专门化一个对象。特别是,类的子节点应该是父类的有效实例:
>>> class foo(dict): pass
>>> isinstance(foo(), dict)
... True
如果你实现了这种行为(例如x = property(lambda: AttributeError)
),你就破坏了子类化概念,这就是坏事。
您可以使用delattr(class, field_name)
将其从类定义中删除。
完整示例:
# python 3
class A:
def __init__(self):
self.field_to_keep = "keep this in B"
self.field_to_delete = "don't want this in B"
class B(A):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
delattr(self, 'field_to_delete')
您无需删除它。只是覆盖它。
class B(A):
x = None
或者根本不参考它。
或者考虑不同的设计(实例属性?)。
也许你可以将x
设置为property
,并在有人试图访问它时引发AttributeError。
>>> class C:
x = 5
>>> class D(C):
def foo(self):
raise AttributeError
x = property(foo)
>>> d = D()
>>> print(d.x)
File "<pyshell#17>", line 3, in foo
raise AttributeError
AttributeError
没有一个答案对我有用。
例如,delattr(SubClass, "attrname")
(或其精确等价物,del SubClass.attrname
)不会“隐藏”父方法,因为这不是方法解析的工作方式。它将失败与AttributeError('attrname',)
相反,因为子类没有attrname
。当然,用None
替换属性实际上并不会删除它。
让我们考虑一下这个基类:
class Spam(object):
# Also try with `expect = True` and with a `@property` decorator
def expect(self):
return "This is pretty much expected"
我知道只有两种方法可以将它子类化,隐藏expect
属性:
AttributeError
from __get__
的描述符类。在属性查找时,会出现异常,通常与查找失败无法区分。
最简单的方法是声明一个提升AttributeError
的属性。这基本上是@JBernardo所建议的。
class SpanishInquisition(Spam):
@property
def expect(self):
raise AttributeError("Nobody expects the Spanish Inquisition!")
assert hasattr(Spam, "expect") == True
# assert hasattr(SpanishInquisition, "expect") == False # Fails!
assert hasattr(SpanishInquisition(), "expect") == False
但是,这仅适用于实例,而不适用于类(hasattr(SpanishInquisition, "expect") == True
断言会被破坏)。
如果您希望上面的所有断言都成立,请使用:
class AttributeHider(object):
def __get__(self, instance, owner):
raise AttributeError("This is not the attribute you're looking for")
class SpanishInquisition(Spam):
expect = AttributeHider()
assert hasattr(Spam, "expect") == True
assert hasattr(SpanishInquisition, "expect") == False # Works!
assert hasattr(SpanishInquisition(), "expect") == False
我相信这是最优雅的方法,因为代码清晰,通用且紧凑。当然,如果删除属性是他们真正想要的,那么应该三思而后行。__getattribute__
magic method。您可以在子类中执行此操作(或者在下面的示例中使用mixin,因为我只想编写一次),这会隐藏子类实例上的属性。如果要隐藏子类中的方法,则需要使用元类。
class ExpectMethodHider(object):
def __getattribute__(self, name):
if name == "expect":
raise AttributeError("Nobody expects the Spanish Inquisition!")
return super().__getattribute__(name)
class ExpectMethodHidingMetaclass(ExpectMethodHider, type):
pass
# I've used Python 3.x here, thus the syntax.
# For Python 2.x use __metaclass__ = ExpectMethodHidingMetaclass
class SpanishInquisition(ExpectMethodHider, Spam,
metaclass=ExpectMethodHidingMetaclass):
pass
assert hasattr(Spam, "expect") == True
assert hasattr(SpanishInquisition, "expect") == False
assert hasattr(SpanishInquisition(), "expect") == False
这看起来比上面的方法更糟糕(更冗长,更通用),但也可以考虑这种方法。
注意,这不适用于特殊(“魔术”)方法(例如__len__
),因为那些绕过__getproperty__
。有关更多详细信息,请查看Python文档的Special Method Lookup部分。如果这是您需要撤消的内容,只需覆盖它并调用object
的实现,跳过父代。毋庸置疑,这仅适用于“新式类”(继承自object
的类),因为那里不支持魔术方法和描述符协议。希望这些都是过去的事情。
我也有同样的问题,我认为我有一个有效的理由删除子类中的class属性:我的超类(称之为A)有一个只读属性提供了属性的值,但是我的子类(称之为B),该属性是一个读/写实例变量。我发现Python正在调用属性函数,即使我认为实例变量应该覆盖它。我可以使用一个单独的getter函数来访问底层属性,但这似乎是一个不必要的,不优雅的接口命名空间的混乱(好像这真的很重要)。
事实证明,答案是用A的原始公共属性创建一个新的抽象超类(称之为S),并且A和B派生自S.因为Python有鸭子打字,所以B确实不重要不扩展A,我仍然可以在相同的地方使用它们,因为它们隐含地实现了相同的接口。
试图这样做可能是一个坏主意,但......
它似乎不是通过“正确的”继承来做到这一点,因为默认情况下查找B.x
的工作方式。当获得B.x
时,x
首先在B
查找,如果没有找到它在A
搜索,但另一方面,当设置或删除B.x
时,只搜索B
。所以举个例子
>>> class A:
>>> x = 5
>>> class B(A):
>>> pass
>>> B.x
5
>>> del B.x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: class B has no attribute 'x'
>>> B.x = 6
>>> B.x
6
>>> del B.x
>>> B.x
5
在这里我们看到,首先我们似乎无法删除B.x
,因为它不存在(A.x
存在并且是评估B.x
时所服务的)。然而,通过将B.x
设置为6,B.x
将存在,它可以被B.x
检索并被del B.x
删除,它不再存在,所以在此之后A.x
将作为对B.x
的响应。
你能做的另一方面是使用元类来使B.x
提升AttributeError
:
class NoX(type):
@property
def x(self):
raise AttributeError("We don't like X")
class A(object):
x = [42]
class B(A, metaclass=NoX):
pass
print(A.x)
print(B.x)
现在当然,纯粹主义者可能会大喊这会破坏LSP,但事情并非那么简单。如果你认为你已经通过这样做创建了一个子类型,这一切都归结为。 issubclass
和isinstance
方法说是,但LSP说不(并且许多程序员会假设“是”,因为你从A
继承)。
LSP意味着如果B
是A
的子类型,那么每当我们可以使用B
时我们就可以使用A
,但是由于我们在做这个构造时不能这样做,我们可以得出结论B
实际上不是A
的子类型因此LSP没有违反。