61

I have a subclass and I want it to not include a class attribute that's present on the base class.

I tried this, but it doesn't work:

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

How can I do this?

Ram Rachum
  • 84,019
  • 84
  • 236
  • 374
  • 17
    This breaks [LSP](http://en.wikipedia.org/wiki/Liskov_substitution_principle). Why do you want to do this? – Marcelo Cantos May 19 '11 at 10:22
  • 2
    The `x` class variable of `B` that you want to delete is really stored in `A.__dict__`, so if you managed to delete it, you would also delete `A.x`. Therefore, the closest you can come is _hiding_ `A.x` it by giving `B` an `x` class variable too, as @Keith suggests in his answer. – Lauritz V. Thaulow May 19 '11 at 11:08
  • 1
    @JBernardo: There isn't much of a connection between LSP and static typing. LSP is a sound principle because it makes class hierarchies behave in a predictable and coherent manner. This applies equally to statically- and dynamically-typed languages, regardless of whether they provide the means to violate LSP. – Marcelo Cantos May 19 '11 at 16:29
  • 1
    It does not necessarily violate LSP. For example, if the class attribute is only used internally and all those internal methods are being overridden in the subclass. – DylanYoung Jun 04 '21 at 19:41

7 Answers7

69

You can use delattr(class, field_name) to remove it from the class definition.

lenz
  • 5,658
  • 5
  • 24
  • 44
Bhushan
  • 2,256
  • 3
  • 22
  • 28
  • 4
    I'm not subclassing - I'm deleting a setting from a settings class in my test to check correct error handling - so this is exactly what I need! – sage Dec 27 '13 at 21:12
  • 2
    It would be helpful to have a fully coded example. Can this be filled out? I'm not sure where this goes in the code. – broccoli2000 Sep 07 '16 at 19:34
  • @broccoli2000 Anywhere you want as long as it's before you need to use the class. – DylanYoung Jun 04 '21 at 19:43
21

You don't need to delete it. Just override it.

class B(A):
   x = None

or simply don't reference it.

Or consider a different design (instance attribute?).

Keith
  • 42,110
  • 11
  • 57
  • 76
  • 27
    **This doesn't answer the question at all.** Setting an attribute to `None` does _not_ delete that attribute, which should be fairly obvious. Deleting attributes is what `del` and `delattr()` do. [Bhushan](https://stackoverflow.com/users/1594534/bhushan)'s succinct [response](https://stackoverflow.com/a/15920132/2809027) appears to be the only actual answer to this question. – Cecil Curry Feb 02 '16 at 06:02
  • @CecilCurry the deeper question is why does the OP think he needs to delete it? Anyway, it's a bad design. – Keith Feb 06 '16 at 19:17
  • 10
    @Keith, perhaps the base class was poorly designed and the user wants to fix it without copying the entire source code. As an example, Django's AuthenticationForm is supposedly a "base class for authenticating users", but it defines username and password form fields, which makes it useless for non-password authentication. Deleting those attributes preserves all of the base class' methods for reuse while getting rid of the junk that shouldn't have been in a base class in the first place. – Dave Apr 20 '16 at 21:09
19

None of the answers had worked for me.

For example delattr(SubClass, "attrname") (or its exact equivalent, del SubClass.attrname) won't "hide" a parent method, because this is not how method resolution work. It would fail with AttributeError('attrname',) instead, as the subclass doesn't have attrname. And, of course, replacing attribute with None doesn't actually remove it.

Let's consider this base class:

class Spam(object):
    # Also try with `expect = True` and with a `@property` decorator
    def expect(self):
        return "This is pretty much expected"

I know only two only ways to subclass it, hiding the expect attribute:

  1. Using a descriptor class that raises AttributeError from __get__. On attribute lookup, there will be an exception, generally indistinguishable from a lookup failure.

    The simplest way is just declaring a property that raises AttributeError. This is essentially what @JBernardo had suggested.

    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
    

    However, this only works for instances, and not for the classes (the hasattr(SpanishInquisition, "expect") == True assertion would be broken).

    If you want all the assertions above to hold true, use this:

    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
    

    I believe this is the most elegant method, as the code is clear, generic and compact. Of course, one should really think twice if removing the attribute is what they really want.

  2. Overriding attribute lookup with __getattribute__ magic method. You can do this either in a subclass (or a mixin, like in the example below, as I wanted to write it just once), and that would hide attribute on the subclass instances. If you want to hide the method from the subclass as well, you need to use metaclasses.

    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
    

    This looks worse (more verbose and less generic) than the method above, but one may consider this approach as well.

    Note, this does not work on special ("magic") methods (e.g. __len__), because those bypass __getproperty__. Check out Special Method Lookup section of the Python documentation for more details. If this is what you need to undo, just override it and call object's implementation, skipping the parent.

Needless to say, this only applies to the "new-style classes" (the ones that inherit from object), as magic methods and descriptor protocols aren't supported there. Hopefully, those are a thing of the past.

drdaeman
  • 11,159
  • 7
  • 59
  • 104
12

Maybe you could set x as property and raise AttributeError whenever someone try to access it.

>>> 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
JBernardo
  • 32,262
  • 10
  • 90
  • 115
  • 2
    This is the *only* actually working solution at the moment. Except, of course, that it "removes" attributes only on subclass *instances*, and not on the subclasses themselves. – drdaeman May 05 '17 at 21:47
8

Think carefully about why you want to do this; you probably don't. Consider not making B inherit from A.

The idea of subclassing is to specialise an object. In particular, children of a class should be valid instances of the parent class:

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

If you implement this behaviour (with e.g. x = property(lambda: AttributeError)), you are breaking the subclassing concept, and this is Bad.

Katriel
  • 120,462
  • 19
  • 136
  • 170
  • 74
    I generally hate non-answers reducing to "Don't do this awesome and interesting thing, because I'm a responsible adult and... !" This is no exception. While preserving [Liskov substitutibility](https://en.wikipedia.org/wiki/Liskov_substitution_principle) is vital to sane development, every rule was intended to be broken. We're all adults, here. **Thanks.** – Cecil Curry Feb 02 '16 at 05:56
  • 2
    I generally agree with @CecilCurry , because I encountered similar answers to my other questions before, and I hate them. Yet in this particular case I would say deleting a parent attribute is not awesome and interesting in the first place, so a kindly reminder of [LSP](https://en.wikipedia.org/wiki/Liskov_substitution_principle) is not nonsense. I ended up upvoting both this answer and CecilCurry 's comment. – RayLuo Oct 22 '16 at 01:41
  • 5
    I find answers pointing out to widely accepted principals of programming very beneficial, because I am learning. I also know every rule has some sort of flexibility so take them as guidelines. – mehmet Nov 04 '16 at 14:20
  • Sometimes you need to do a bunch of "wrong" things to get to the right design. So I'm with @CecilCurry. – darda Jan 23 '23 at 19:49
5

I'm had the same problem as well, and I thought I had a valid reason to delete the class attribute in the subclass: my superclass (call it A) had a read-only property that provided the value of the attribute, but in my subclass (call it B), the attribute was a read/write instance variable. I found that Python was calling the property function even though I thought the instance variable should have been overriding it. I could have made a separate getter function to be used to access the underlying property, but that seemed like an unnecessary and inelegant cluttering of the interface namespace (as if that really matters).

As it turns out, the answer was to create a new abstract superclass (call it S) with the original common attributes of A, and have A and B derive from S. Since Python has duck typing, it does not really matter that B does not extend A, I can still use them in the same places, since they implicitly implement the same interface.

acjay
  • 34,571
  • 6
  • 57
  • 100
4

Trying to do this is probably a bad idea, but...

It doesn't seem to be do this via "proper" inheritance because of how looking up B.x works by default. When getting B.x the x is first looked up in B and if it's not found there it's searched in A, but on the other hand when setting or deleting B.x only B will be searched. So for example

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

Here we see that first we doesn't seem to be able to delete B.x since it doesn't exist (A.x exists and is what gets served when you evaluate B.x). However by setting B.x to 6 the B.x will exist, it can be retrieved by B.x and deleted by del B.x by which it ceases to exist so after that again A.x will be served as response to B.x.

What you could do on the other hand is to use metaclasses to make B.x raise 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)

Now of course purists may yell that this breaks the LSP, but it's not that simple. It all boils down to if you consider that you've created a subtype by doing this. The issubclass and isinstance methods says yes, but LSP says no (and many programmers would assume "yes" since you inherit from A).

The LSP means that if B is a subtype of A then we could use B whenever we could use A, but since we can't do this while doing this construct we could conclude that B actually isn't a subtype of A and therefore LSP isn't violated.

skyking
  • 13,817
  • 1
  • 35
  • 57