Python:删除子类中的类属性

问题描述 投票:37回答:7

我有一个子类,我希望它不包含基类上存在的类属性。

我试过这个,但它不起作用:

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

我怎样才能做到这一点?

python class inheritance namespaces
7个回答
9
投票

仔细想想你为什么要这样做;你可能没有。考虑不要让B从A继承。

子类化的想法是专门化一个对象。特别是,类的子节点应该是父类的有效实例:

>>> class foo(dict): pass
>>> isinstance(foo(), dict)
... True

如果你实现了这种行为(例如x = property(lambda: AttributeError)),你就破坏了子类化概念,这就是坏事。


41
投票

您可以使用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')

18
投票

您无需删除它。只是覆盖它。

class B(A):
   x = None

或者根本不参考它。

或者考虑不同的设计(实例属性?)。


6
投票

也许你可以将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

6
投票

没有一个答案对我有用。

例如,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属性:

  1. 使用raises 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 我相信这是最优雅的方法,因为代码清晰,通用且紧凑。当然,如果删除属性是他们真正想要的,那么应该三思而后行。
  2. 覆盖属性查找with __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的类),因为那里不支持魔术方法和描述符协议。希望这些都是过去的事情。


5
投票

我也有同样的问题,我认为我有一个有效的理由删除子类中的class属性:我的超类(称之为A)有一个只读属性提供了属性的值,但是我的子类(称之为B),该属性是一个读/写实例变量。我发现Python正在调用属性函数,即使我认为实例变量应该覆盖它。我可以使用一个单独的getter函数来访问底层属性,但这似乎是一个不必要的,不优雅的接口命名空间的混乱(好像这真的很重要)。

事实证明,答案是用A的原始公共属性创建一个新的抽象超类(称之为S),并且A和B派生自S.因为Python有鸭子打字,所以B确实不重要不扩展A,我仍然可以在相同的地方使用它们,因为它们隐含地实现了相同的接口。


1
投票

试图这样做可能是一个坏主意,但......

它似乎不是通过“正确的”继承来做到这一点,因为默认情况下查找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,但事情并非那么简单。如果你认为你已经通过这样做创建了一个子类型,这一切都归结为。 issubclassisinstance方法说是,但LSP说不(并且许多程序员会假设“是”,因为你从A继承)。

LSP意味着如果BA的子类型,那么每当我们可以使用B时我们就可以使用A,但是由于我们在做这个构造时不能这样做,我们可以得出结论B实际上不是A的子类型因此LSP没有违反。

© www.soinside.com 2019 - 2024. All rights reserved.