Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change signature of function called in __init__ of base class

In A.__init__ I call self.func(argument):

class A(object):
    def __init__(self, argument, key=0):
        self.func(argument)
    def func(self, argument):
        #some code here

I want to change the signature of A.func in B. B.func gets called in B.__init__ through A.__init__:

class B(A):
    def __init__(self, argument1, argument2, key=0):
        super(B, self).__init__(argument1, key)  # calls A.__init__
    def func(self, argument1, argument2):
        #some code here

Clearly, this doesn't work because the signature of B.func expects two arguments while A.__init__ calls it with one argument. How do I work around this? Or is there something incorrect with the way I have designed my classes?

key is a default argument to A.__init__. argument2 is not intended for key. argument2 is an extra argument that B takes but A does not. B also takes key and has default value for it.

Another constraint is that I would like not to change the signature of A.__init__. key will usually be 0. So I want to allow users to be able to write A(arg) rather than A(arg, key=0).

like image 870
Swetabh Avatar asked Mar 30 '16 16:03

Swetabh


1 Answers

Generally speaking, changing the signature of a method between subclasses breaks the expectation that the methods on subclasses implement the same API as those on the parent.

However, you could re-tool your A.__init__ to allow for arbitrary extra arguments, passing those on to self.func():

class A(object):
    def __init__(self, argument, *extra, **kwargs):
        key = kwargs.get('key', 0)
        self.func(argument, *extra)
    # ...

class B(A):
    def __init__(self, argument1, argument2, key=0):
        super(B, self).__init__(argument1, argument2, key=key)
    # ...

The second argument passed to super(B, self).__init__() is then captured in the extra tuple, and applied to self.func() in addition to argument.

In Python 2, to make it possible to use extra however, you need to switch to using **kwargs, otherwise key is always going to capture the second positional argument. Make sure to pass on key from B with key=key.

In Python 3, you are not bound by this restriction; put *args before key=0 and only ever use key as a keyword argument in calls:

class A(object):
    def __init__(self, argument, *extra, key=0):
        self.func(argument, *extra)

I'd give func() an *extra parameter too, so that it's interface essentially is going to remain unchanged between A and B; it just ignores anything beyond the first parameter passed in for A, and beyond the first two for B:

class A(object):
    # ...
    def func(self, argument, *extra):
        # ...

class B(A):
    # ...
    def func(self, argument1, argument2, *extra):
        # ...

Python 2 demo:

>>> class A(object):
...     def __init__(self, argument, *extra, **kwargs):
...         key = kwargs.get('key', 0)
...         self.func(argument, *extra)
...     def func(self, argument, *extra):
...         print('func({!r}, *{!r}) called'.format(argument, extra))
...
>>> class B(A):
...     def __init__(self, argument1, argument2, key=0):
...         super(B, self).__init__(argument1, argument2, key=key)
...     def func(self, argument1, argument2, *extra):
...         print('func({!r}, {!r}, *{!r}) called'.format(argument1, argument2, extra))
...
>>> A('foo')
func('foo', *()) called
<__main__.A object at 0x105f602d0>
>>> B('foo', 'bar')
func('foo', 'bar', *()) called
<__main__.B object at 0x105f4fa50>
like image 70
Martijn Pieters Avatar answered Sep 30 '22 07:09

Martijn Pieters