tags:

views:

114

answers:

3

I am subclassing an object in order to override a method that I want to add some functionality to. I don't want to completely replace it or add a differently named method but remain compatible to the superclasses method by just adding an optional argument to the method. Is it possible to work with *args and **kwargs to pass through all arguments to the superclass and still add an optional argument with a default? I intutively came up with the following but it doesn't work:

class A(object):
    def foo(self, arg1, arg2, argopt1="bar"):
        print arg1, arg2, argopt1

class B(A):
    def foo(self, *args, argopt2="foo", **kwargs):
        print argopt2
        A.foo(self, *args, **kwargs)


b = B()
b.foo("a", "b", argopt2="foo")

Of course I can get it to work when I explcitly add all the arguments of the method of the superclass:

class B(A):
    def foo(self, arg1, arg2, argopt1="foo", argopt2="bar"):
        print argopt2
        A.foo(self, arg1, arg2, argopt1=argopt1)

What's the right way to do this, do I have to know and explicitly state all of the overridden methods arguments?

+4  A: 
class A(object):
    def foo(self, arg1, arg2, argopt1="bar"):
        print arg1, arg2, argopt1

class B(A):
    def foo(self, *args, **kwargs):
        argopt2 = kwargs.get('argopt2', default_for_argopt2)
        # remove the extra arg so the base class doesn't complain. 
        del kwargs['argopt2']
        print argopt2
        A.foo(self, *args, **kwargs)


b = B()
b.foo("a", "b", argopt2="foo")
Ned Batchelder
You can use pop instead of get + del.
iny
I always forget about dict.pop!
Ned Batchelder
Your example fails with `KeyError` when `argopt2` is not passed. Using `kwargs.pop('argopt2', default_for_argopt2)` is the simplest way to fix it.
Denis Otkidach
+1  A: 

When subclassing and overriding methods, one must always decide if using super() is a good idea, and this page is good for that.

I'm not saying that super() should be avoided, like the article author may be: I'm saying that super() has some very important prerequisits that must be followed if you don't want super() to come back and bite you.

Teddy
http://fuhm.org/super-harmful/ is a good read. Thank you, Teddy
unutbu
+2  A: 

What's the right way to do this, do I have to know and explicitly state all of the overridden methods arguments?

If you want to cover all cases (rather than just rely on the caller to always do things your way, e.g., always call you only with the extra argument passed by-name, never by position) you do have to code (or dynamically discover) a lot of knowledge about the signature of the method you're overriding -- hardly surprising: inheritance is a strong form of coupling, and overriding methods is one way that coupling presents itself.

You could dynamically discover the superclass's method arguments via inspect.getargspec, in order to make sure you call it properly... but this introspection technique can get tricky if two classes are trying to do exactly the same thing (once you know your superclass's method accepts *a and/or **kw you can do little more than pass all the relevant arguments upwards and hope, with fingers crossed, that the upstream method chain eventually does proper housecleaning before calling a version that's not quite so tolerant).

Such prices may be worth paying when you're designing a wrapper that's meant to be applied dynamically to callables with a wide variety of signatures (especially since in a decorator setting you can arrange to pay the hefty cost of introspection just once per function you're decorating, not every time the resulting wrapper is called). It seems unlikely to be a worthwhile technique in a case such as yours, where you'd better know what you're subclassing (subclassing is strong coupling: doing it blindly is definitely not advisable!), and so you might as well spell out the arguments explicitly.

Yes, if the superclass's code changes drastically (e.g., by altering method signatures), you'll have to revise the subclass as well -- that's (part of) the price of inheritance. The overall price's hefty enough that the new Go programming language does totally without it -- forcing you to apply the Gang of 4's excellent advice to prefer composition over inheritance. In Python complete abstinence from inheritance would just be impractical, but using it soberly and in moderation (and accepting the price you'll pay in terms of coupling when you do) remains advisable.

Alex Martelli
+1 for caveats on signature changes. But I think there is an exception: it's OK to change the signature of constructor. And the problem described in the question is applicable to constructor too.
Denis Otkidach