Python:删除子类中的类属性

问题描述:

我有一个子类,我想它而不是包括一个类属性存在于基类。Python:删除子类中的类属性

我尝试这样做,但它不工作:

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

我怎样才能做到这一点?

+14

这打破了[LSP](http://en.wikipedia.org/wiki/Liskov_substitution_principle)。你为什么想这样做? – 2011-05-19 10:22:50

+1

您想要删除的'B'类'x'类变量确实存储在'A .__ dict__'中,所以如果您设法删除它,您也会删除'A.x'。因此,最接近的是_hiding_'A.x',通过给'B'一个'x'类变量,就像@Keith在他的回答中所表明的那样。 – 2011-05-19 11:08:06

+1

@JBernardo:LSP和静态类型之间没有太多联系。 LSP是一个合理的原则,因为它使得类层次结构以可预测和一致的方式运行。这同样适用于静态和动态类型的语言,不管它们是否提供违反LSP的手段。 – 2011-05-19 16:29:46

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

子类化的想法是专门化一个对象。具体而言,一类的儿童应在父类的有效实例:

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

如果实施该行为(例如带x = property(lambda: AttributeError)),你打破了子类的概念,这是不好的。

+18

我通常讨厌非答案减少到“不要做这个真棒和有趣的事情,因为我是一个负责任的成年人...... !”这也不例外。虽然保留[Liskov替代品](https://en.wikipedia.org/wiki/Liskov_substitution_principle)对于理智的发展至关重要,但每条规则都打算被打破。我们都是大人,在这里。 **谢谢。** – 2016-02-02 05:56:32

+0

我一般同意@CecilCurry,因为我之前遇到过类似的答案,我讨厌他们。然而,在这种特殊情况下,我会说删除一个父属性首先不是真棒和有趣的,所以提醒[LSP](https://en.wikipedia.org/wiki/Liskov_substitution_principle)不是无稽之谈。我最终提出了这个答案和CecilCurry的评论。 – RayLuo 2016-10-22 01:41:44

+0

我找到答案指出广泛接受的编程原理非常有益,因为我正在学习。我也知道每条规则都有一定的灵活性,所以要把它们作为指导方针。 – mehmet 2016-11-04 14:20:45

您不需要删除它。只需重写它。

class B(A): 
    x = None 

或根本不参考它。

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

+1

+1,因为它回答了问题,并建议设计更改。 – FlipMcF 2013-10-04 15:36:19

+3

**这根本没有回答这个问题。**将一个属性设置为'None'并不会删除该属性,这应该是相当明显的。删除属性是'del'和'delattr()'做的。 [Bhushan](https://*.com/users/1594534/bhushan)的简洁[回复](https://*.com/a/15920132/2809027)似乎是此问题的唯一实际答案。 – 2016-02-02 06:02:09

+0

@CecilCurry更深层的问题是为什么OP认为他需要删除它?无论如何,这是一个糟糕的设计。 – Keith 2016-02-06 19:17:27

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

'AttributeError'会更一致。 – 2011-05-19 12:18:43

+0

你说得对。编辑。 – JBernardo 2011-05-19 12:21:35

+0

这是目前仅*实际工作的解决方案。当然,除了“仅删除”子类*实例*上的属性,而不是子类本身。 – drdaeman 2017-05-05 21:47:42

我也有同样的问题,我想我有一个合法的理由来删除子类中的类属性:我的超类(称为A)有一个只读属性,提供了属性,但在我的子类(称为B)中,该属性是一个读/写实例变量。我发现Python调用了属性函数,即使我认为实例变量应该重写它。我可以创建一个单独的getter函数来访问底层属性,但这看起来像是一个不必要和不雅的界面名称空间混乱(如果真的很重要)。

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

您可以使用delattr(class, field_name)从类定义中删除它。

+3

我不是继承 - 我从我的测试中的设置类中删除一个设置,以检查正确的错误处理 - 所以这正是我需要的! – sage 2013-12-27 21:12:06

+0

有一个完整的代码示例会很有帮助。这可以填写吗?我不确定代码中的内容。 – broccoli2000 2016-09-07 19:34:26

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

这似乎并没有被通过,因为如何查找B.x作品由默认的“正确”的继承做到这一点。当得到B.xx首先在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。

没有答案为我工作。

例如delattr(SubClass, "attrname")(或其确切的等价物,del SubClass.attrname)不会“隐藏”父方法,因为这不是方法解析的工作方式。由于子类不具有attrname,所以它会因AttributeError('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继承者),因为魔术方法和描述符的协议不受支持那里。希望这些都是过去的事情。