That’s why you can’t remove parent methods in Python

George Shuklin
4 min readSep 15, 2017

--

If you inherit your class from another class you may want to remove some methods you inherited from that parent class. But you can’t. Here is why.

Why want to remove?

It’s a valid question. If you want just to stop this method to do anything, just redefine it:

class Base(object):
def bad_function(name, age=42):
print("Doing something bad!")
class Child(Base):
def bad_function(*args, **kwargs):
print("It's OK")
c = Child()
c.bad_function()

Use pass to completely disable it. *args and **kwargs combo allows you to completely match any signature.

But sometimes this is not enough. For example, if you have any kind of introspection (tests, automatic code/docs generation, etc), you may find that redefining is not enough. Regardless of doing ‘nothing’, bad_function will be listed in automatically generated bindings, API description or even executed as a test. So you may want to take all methods from the parent but not those one or two bad_function’s.

Well, you can’t do this. I’ll show you two ugly ways to force Python to play like you can, but in reality, you can’t. More importantly, I’ll explain why there is no beautiful way to do this.

When you inherit from some class, your new class doesn’t copy anything from the parent. It just saves parent class(es) into __base__ and __bases__ variables.

Inheritance magic happens at the call time. When inherited method is called, it’s not found in the current class, and Python inspects all __bases__ classes down to the root (usually, object) and calls first found method with a given name.

You simply can not remove something not present. You may put something instead, but there is no way to remove non-existing item, it’s already ‘not exists’. At the same time, at the introspection level, all methods are combined in one list. So that bad_function will be listed as method of your class nevertheless. This is the inheritance.

Ugly way number one: mutilate your parents

Just kidding: you should love your dad and mom.

You can remove parents method. It will disappear instantly for everyone: your class, an instance of you class, parent class, all instances of parent class, all other classes ever inherited for it (with any level on inheritance), all their instances.

class Base(object):
def bad_function(name, age=42):
do_bad_things()
class Child(Base):
def __init__(self):
del Base.bad_function
c = Child()
try:
c.bad_function()
except AttributeError:
print("removed!")

This is absolute disaster for practical use. All instances of Base will loose access to bad_function, and you will have hard time to guess who will be affected. Except if you put it back afterwards:

class Base(object):
def bad_function(name, age=42):
print("Something bad")
class Child(Base):
def __init__(self):
self.save = Base.bad_function
del Base.bad_function
def restore(self):
setattr(Base, 'bad_function', self.save)
c = Child()
try:
c.bad_function()
except AttributeError:
print("removed!")
c.restore()
c.bad_function()

This time we’ll restore back bad_function into Base and all it’s children (including Child class and it’s instances).

Did I promise you something ugly? Now you have it!

Ugly way number two: Falsify ancestry

When we want to inherit some class, we usually want this due to yummy methods inside, may be such methods we have no idea about. The most typical case is when we want to write new test but want to keep all magic of previous class intact, except for tests.

This method will create own parent with all methods but those we don’t want. Important notice: If those methods was inherited from our grand parents (i.e. was inherited by our parents) we’ll not able to filter them out.

class Base(object):
def bad_function(self):
print("bad function is present in %s" % self.__class__)
def good_function(self):
print("Good!")
class Child(Base):
def __init__(self):
BAN_LIST = ['bad_function']
parent = Base
class new_parent(parent.__base__):
pass
parent_list = dir(parent)
new_parent_list = dir(new_parent)
candidates = set(parent_list) - set(new_parent_list)
for candidate in candidates:
if candidate not in BAN_LIST:
setattr(
new_parent,
candidate,
parent.__getattribute__(parent, candidate)
)
Child.__bases__ = (new_parent, )
c = Child()
try:
c.bad_function()
except AttributeError:
print("removed!")
c.good_function()
b = Base()
b.bad_function()

What we do here?

  • Define new fake parent class
  • Copy in a new fake parent class only methods which are new into our parent.
  • Skip those we don’t want to copy
  • Set a __base__ attribute for our own class pointing to our fake new_parent.

It’s even more ugly. Do not use this in production.

Conclusion

It’s not possible to remove inherited methods from the class, as there is nothing to remove. You may mangle your parents, or persuade yourself that you have a different imaginary parent, but the truth will stay: your parent has this method and you inherit it no matter what.

P.S. metaclasses and dir deception

At the last moment before gave up, I asked a question on SO, and the answer helped me a lot. I wanted to inherit from existing unittest tests but I wanted to avoid their ‘test_*’ functions. Unittest uses dir function to find test methods in a class, and with help of SO people I was able to do this:

class MetaClass(type):
def __dir__(cls):
return cls.__dict__.keys()
@six.add_metaclass(MetaClass)
class SimpleExampleTest(ParentTest):
def test_only_my_test(self):
pass

This nasty line (cls.__dict__.keys()) replaces normal dir function output for my class (SimpleExampleTest) and it returns only object which are defined in the class itself, not it’s parents. Parents methods still can be called, but they are invisible to introspection.

It’s not the precise ‘delete’ thing, but it may be the least ugly code for this task.

Upd: Please, check out this comment to this article. It provides an excellent additional hints and a better code example.

--

--

George Shuklin

I work at Servers.com, most of my stories are about Ansible, Ceph, Python, Openstack and Linux. My hobby is Rust.