3

Is there any way to remove an attribute from a subclass that is present in the parent?

In the following example

class A(object):
    foo = 1
    bar = 2

class B(A):
    pass

# <desired code here>

b = B()
assert hasattr(b, 'bar') == False

Is there any code we can write to make the assertion pass?

MRocklin
  • 55,641
  • 23
  • 163
  • 235
  • 6
    Doing so violates the Liskov substitution principle. In other words, it's a horrible idea and B should not be a subtype of A. –  May 25 '13 at 12:10
  • I am just interested: Why would you want to do that? – cyroxx May 25 '13 at 12:11
  • @delnan for some reason whenever anyone mentions that I always think of the [Blinovitch limitation effect](http://en.m.wikipedia.org/wiki/Blinovitch_Limitation_Effect) from Doctor Who. – Daniel Roseman May 25 '13 at 12:25
  • For now just call it curiosity. I appreciate the warning but I remain curious. – MRocklin May 25 '13 at 12:51
  • One use case for this is if you want to use a `Form` class exposed by a Django library, but without one of its fields. In Django, form fields are defined by certain class attributes. See for example [this SO question](http://stackoverflow.com/questions/15557257/django-remove-a-field-from-a-form-subclass). – cjerdonek Dec 11 '13 at 09:02

3 Answers3

6
class A(object):
    foo = 1
    bar = 2


class B(A):
    @property
    def bar(self):
        raise AttributeError


>>> b = B()
>>> b.bar

Traceback (most recent call last):
  File "<pyshell#17>", line 1, in <module>
    b.bar
  File "<pyshell#15>", line 4, in bar
    raise AttributeError
AttributeError
jamylak
  • 128,818
  • 30
  • 231
  • 230
  • One very minor difference is that `bar` is still present in the output of `dir(b)`, although `'bar' in b.__dict__` is false and I can't find another way to "access" `b.bar`. – chepner Jun 12 '13 at 02:12
1

This works for me whe I don't want a specific attribute ('bar' in this case) to be listed in dir(A).

class A(object):
    
    foo = 1
    bar = 2


class B(A):
    def ___init__(self):
    
        self.delete()
    
    def delete(self):
        delattr(self, 'bar')

Basically, create a method (delete) in the subclass B that deletes that attribute and put that in the constructor.

0

Yes, using the magic of descriptors. See my blog post about it. Short version:

class nosubclasses(object):
    def __init__(self, f, cls):
        self.f = f
        self.cls = cls
    def __get__(self, obj, type=None):
        if type == self.cls:
            if hasattr(self.f, '__get__'):
                return self.f.__get__(obj, type)
            return self.f
        raise AttributeError

Example:

In [2]: class MyClass(object):
   ...:     x = 1
   ...:

In [3]: MyClass.x = nosubclasses(MyClass.x, MyClass)

In [4]: class MySubclass(MyClass):
   ...:     pass
   ...:

In [5]: MyClass.x
Out[5]: 1

In [6]: MyClass().x
Out[6]: 1

In [80]: MySubclass.x
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-80-2b2f456dd101> in <module>()
----> 1 MySubclass.x

<ipython-input-51-7fe1b5063367> in __get__(self, obj, type)
      8                 return self.f.__get__(obj, type)
      9             return self.f
---> 10         raise AttributeError

AttributeError:

In [81]: MySubclass().x
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-81-93764eeb9948> in <module>()
----> 1 MySubclass().x

<ipython-input-51-7fe1b5063367> in __get__(self, obj, type)
      8                 return self.f.__get__(obj, type)
      9             return self.f
---> 10         raise AttributeError

AttributeError:

But as the commenter @delnan pointed out, this violates the Liskov substitutability principle. The motivation in my blog post was warranted, because the attribute did not describe the object itself. But in general, this breaks the whole point of being able to subclass in the first place, which is really the whole point of having classes at all.

By the way, the difference between my answer and @jamylak's is that in @jamylak's answer, attributes are removed on a per-subclass basis. If you made a class C(A), it would still have the bar attribute. In my answer, the class itself (well, actually the attribute), disallows subclasses from having the attribute, so that in one fell swoop, all subclasses don't have it.

asmeurer
  • 86,894
  • 26
  • 169
  • 240