6

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

8
  • Why not provide a default value to argument2? Commented Mar 30, 2016 at 16:27
  • Is this Python 2 or Python 3? Commented Mar 30, 2016 at 16:33
  • 1
    judging by the super call, it's python2 Commented Mar 30, 2016 at 16:35
  • 3
    "In B, I want to change the signature of func" - sounds like you're breaking Liskov substitutability there. Are you sure this is a good idea? Also, calling methods from __init__ that are meant to be overridden is dangerous, since those methods may depend on subclass initialization that hasn't happened yet. Commented Mar 30, 2016 at 16:41
  • 1
    There is some confusion among the answers, I think. Do you expect argument1 and argument2 to be passed on to B.func() when super(B, self).__init__(argument1, argument2) is called? What is the role of key=0 in A.__init__() in this respect, should it capture the value of argument2 at all? Commented Mar 30, 2016 at 21:16

3 Answers 3

6

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> 
Sign up to request clarification or add additional context in comments.

3 Comments

I did not downvote, but it might be helpful to encourage different design practices. Could the first class use super to refer to the first classes implementation of the method?
@NoctisSkytower: not sure what you mean? super(A, self).func(argument) won't work, no, as search starts at the next class in the MRO.
@MartijnPieters A quick experiment led me to one solution to help the base class.
0

It seems to be that there is a problem in your design. The following might fix your particular case but seems to perpetuate bad design even further. Notice the Parent.method being called directly.

>>> class Parent: def __init__(self, a, b=None): Parent.method(self, a) self.b = b def method(self, a): self.location = id(a) >>> class Child(Parent): def __init__(self, a): super().__init__(a, object()) def method(self, a, b): self.location = id(a), id(b) >>> test = Child(object()) 

Please consider adding a default argument to the second parameter of the method you are overriding. Otherwise, design your class and call structure differently. Reorganization might eliminate the problem.

6 Comments

Now Child.method() is never called, and the extra argument object() is captured by b.
@MartijnPieters My assumption may be incorrect, but it is that Child.method should not be called from Parent.__init__. Depending on the writer's intent, the Child.method may be designed to be called internally or externally in other places instead. If neither the parent or child methods should be called externally, renaming method to __method in both classes might be the way to go. Without further context, it is difficult to know what the best solution might be. There is even the option to rename it _method instead to indicate internal use. The proper answer is not obvious.
From the use of argument1 and argument2 as both local variables in B.__init__ and as the names for B.func(), there is an implied expectation that B.func() is called from A.__init__(). I've commented on the question to get that clarified.
@MartijnPieters: You are right. I intend to call B.func() when an object of class B is being initialized.
@NoctisSkytower: Could you say a bit more about why you think this is bad design? My use case is that func creates an object that is related to the class. A kind of related table. This related object can be different - sometimes it has 2 tables instead of 1. I hope this clarifies my intention somewhat.
|
0

actually I would resort to put an extra boolean argument in A's __init__ to control the call of the func, and just pass False from B's __init__

class A(object): def __init__(self, argument, key=0, call_func=True): if call_func: self.func(argument) class B(A): def __init__(self, argument): argument1, argument2 = argument, 'something else' super(B, self).__init__(argument1, argument2, call_func=False) 

3 Comments

So why override func() in B then? And you still capture argument2 in key..
unaware of the usage of argument1, argument2 and key, I didn't try to make sense of the code, but, as far as I understood, that he needs to override func() in B but he had a problem that func() is being called as part of the A's init!
@0xcurb: I want to call func as it initializes an object I need to associate with class A. I would prefer not to rewrite the code inside __init__ of A. In my case A also calls it's parent's constructor.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.