Change signature of function called in __init__ of base class - python

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

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>

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.

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)

Related

Automatic override of parent methods

I'm trying to add a wrapper to each method in a class by subclassing it, and reassigning them in the constructor of the new class, however i'm getting the same reference for all subclassed methods, how is this possible?
class A:
def foo(self):
print("foo")
def bar(self):
print("bar")
class B(A):
def __init__(self):
super().__init__()
methods = [
(method_name, getattr(self, method_name)) for method_name in dir(self) if not method_name.startswith('_')
]
for (method_name, f) in methods:
def wrapper(*args, **kwargs):
print('wrapped')
return f(*args, **kwargs)
setattr(self, method_name, wrapper)
b = B()
b.foo()
>>> wrapped
>>> foo
b.bar()
>>> wrapped
>>> foo
This is a spin on a common python gotcha, late binding closures.
What is happening is the last value of f is being bound to all your wrapped methods.
A common workaround is binding your changing variable to a keyword argument or using functools.partial.
For your example you can use it as a keyword argument.
class A:
def foo(self, baz='foo'):
print(baz)
def bar(self, baz='bar'):
print(baz)
class B(A):
def __init__(self):
super().__init__()
methods = [
(method_name, getattr(self, method_name)) for method_name in dir(self) if not method_name.startswith('_')
]
for (method_name, f) in methods:
# here you can use an implied private keyword argument
# to minimize the chance of conflicts
def wrapper(*args, _f=f, **kwargs):
print('wrapped')
return _f(*args, **kwargs)
setattr(self, method_name, wrapper)
b = B()
b.foo()
b.foo('baz')
b.foo(baz='baz')
b.bar()
I added a few more calls to your method to demonstrate that it still works with different forms of calls.

Python inheritance structure and arguments

I am trying to design a class structure that allows the user to define their own class that overloads predefined methods in other classes. In this case the user would create the C class to overload the "function" method in D. The user created C class has common logic for other user created classes A and B so they inherit from C to overload "function" but also inherit from D to use D's other methods. The issue I am having is how to pass "value" from A and B to D and ignore passing it to C. What I currently have written will produce an error as C does not have "value" as an argument.
I know that I can add "value" (or *args) to C's init method and the super call but I don't want to have to know what inputs other classes need in order to add new classes to A and B. Also, if I swap the order of C and D I won't get an error but then I don't use C's overloaded "function". Is there an obvious way around this?
class D(SomethingElse):
def __init__(self, value, **kwargs):
super(D, self).__init__(**kwargs)
self.value = value
def function(self):
return self.value
def other_method(self):
pass
class C(object):
def __init__(self):
super(C, self).__init__()
def function(self):
return self.value*2
class B(C, D):
def __init__(self, value, **kwargs):
super(B, self).__init__(value, **kwargs)
class A(C, D):
def __init__(self, value, **kwargs):
super(A, self).__init__(value, **kwargs)
a = A(3)
print(a.function())
>>> 6
Essentially, there are two things you need to do to make your __init__ methods play nice with multiple inheritance in Python:
Always take a **kwargs parameter, and always call super().__init__(**kwargs), even if you think you are the base class. Just because your superclass is object doesn't mean you are last (before object) in the method resolution order.
Don't pass your parent class's __init__ arguments explicitly; only pass them via **kwargs. Your parent class isn't necessarily the next one after you in the method resolution order, so positional arguments might be passed to the wrong other __init__ method.
This is called "co-operative subclassing". Let's try with your example code:
class D:
def __init__(self, value, **kwargs):
self.value = value
super().__init__(**kwargs)
def function(self):
return self.value
class C:
# add **kwargs parameter
def __init__(self, **kwargs):
# pass kwargs to super().__init__
super().__init__(**kwargs)
def function(self):
return self.value * 2
class B(C, D):
# don't take parent class's value arg explicitly
def __init__(self, **kwargs):
# pass value arg via kwargs
super().__init__(**kwargs)
class A(C, D):
# don't take parent class's value arg explicitly
def __init__(self, **kwargs):
# pass value arg via kwargs
super().__init__(**kwargs)
Demo:
>>> a = A(value=3)
>>> a.value
3
>>> a.function()
6
Note that value must be passed to the A constructor as a keyword argument, not as a positional argument. It's also recommended to set self.value = value before calling super().__init__.
I've also simplified class C(object): to class C:, and super(C, self) to just super() since these are equivalent in Python 3.
So I'm trying to understand the point of A AND B. I'm guessing that maybe you want to mix in the superclass behavior and sometimes have local behavior. So suppose A is just mixing together behaviors, and B has some local behavior and state.
If you don't need your own state, you probably don't need an __init__. So for A and C just omit __init__.
class SomethingElse(object):
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
class D(SomethingElse):
def __init__(self, value, *args, **kwargs):
super(D, self).__init__(*args, **kwargs)
self.value = value
def function(self):
return self.value
def other_method(self):
return self.__dict__
class C(object):
#def __init__(self):
# super(C, self).__init__()
def function(self):
return self.value*2
class B(C, D):
def __init__(self, value, bstate, *args, **kwargs):
super(B, self).__init__(value, *args, **kwargs)
self.bstate = bstate
def __repr__(self):
return (self.__class__.__name__ + ' ' +
self.bstate + ' ' + str(self.other_method()))
class A(C, D):
pass
a = A(3)
b = B(21, 'extra')
a.function()
6
b.function()
42
repr(a)
'<xx.A object at 0x107cf5e10>'
repr(b)
"B extra {'args': (), 'bstate': 'extra', 'value': 21, 'kwargs': {}}"
I've kept python2 syntax assuming you might still be using it, but as another answer points out, python3 simplifies super() syntax, and you really should be using python3 now.
If you swap C and D you are changing the python method resolution order, and that will indeed change the method to which a call to A.function resolves.

Python - Initializing parent classes

I am trying to figure how to use super() to initialize the parent class one by one based on condition.
class A:
def __init__(self, foo):
self.foo = foo
class B:
def __init__(self, bar):
self.bar == bar
class C(A,B):
def __init__(self):
#Initialize class A first.
#Do some calculation and then initialize class B
How do I use super() in class C such that it only initializes class A first, then I do some calc and call super() to initialize class B
You cannot do what you ask for in C.__init__, as super doesn't give you any control over which specific inherited methods get called, only the order in which they are called, and that is controlled entirely by the order in which the parent classes are listed.
If you use super, you need to use it consistently in all the classes. (That's why it's called cooperative inheritance.) Note this means that C cannot inject any code between the calls to A.__init__ and B.__init__.
__init__ is particularly tricky to implement correctly when using super, because a rule of super is that you have to expected arbitrary arguments to be passed, yet object.__init__() doesn't take any arguments. You need each additional argument to be "owned" by a particular root class that is responsible for removing it from the argument list.
class A:
def __init__(self, foo, **kwargs):
# A "owns" foo; pass everything else on
super().__init__(**kwargs)
self.foo = foo
class B:
def __init__(self, bar, **kwargs):
# B "owns" bar; pass everything else on
super().__init__(**kwargs)
self.bar = bar
class C(A,B):
def __init__(self):
# Must pass arguments expected by A and B
super().__init__(foo=3, bar=9)
The MRO for C is [A, B, object], so the call tree looks something like this:
C.__init__ is called with no arguments
super() resolves to A, so A.__init__ is called with foo=3 and bar=9.
In A.__init__, super() resolves to B, so B.__init__ is called with bar=9.
In B.__init__, super() resolves to object, so object.__init__ is called with no arguments (kwargs being empty)
Once object.__init__ returns, self.bar is set to bar
Once B.__init__ returns, self.foo is set to foo
Once A.__init__ returns, C.__init__ finishes up
OK, the first sentence isn't entirely true. Since neither A nor B, as currently written, use super, you might be able to assume that an appropriate use of super will simply call one parent function and immediately return.
class A:
def __init__(self, foo):
self.foo = foo
class B:
def __init__(self, bar):
self.bar == bar
class C(A,B):
def __init__(self):
super(A, self).__init__(foo=3)
# Do some calculation
super(B, self).__init__(bar=9)
I'm not entirely certain, though, that this doesn't introduce some hard-to-predict bugs that could manifest with other subclasses of A, B, and/or C that attempt to use super properly.
You can actually refer the base classes explicitly:
class A:
def __init__(self, foo):
self.foo = foo
class B:
def __init__(self, bar):
self.bar == bar
class C(A,B):
def __init__(self):
A.__init__(self, 'foovalue')
# Do some calculation
B.__init__(self, 'barvalue')

getattr and multiple classes

Hi everyone I have two classes A and B and I want to grab a method from B to use in A.
My code is along the lines of:
class A(object):
__init__(self, args):
'blah'
def func2(self, args):
#method = B.func1(args)
# method = getattr(B, 'func1')
class B(object):
__init__(self):
'do stuff'
def func1(self, args):
'Do stuff here'
return
Is there a way to get func1 into A without removing the self attribute from func1?
Neither of the two method calls are working for me and I keep getting a type error
TypeError: unbound method func1 must be called with B instance as
first argument (got NoneType instance instead)
edit: found my solution
I found the solution to my question. When I passed values from B to A I needed to pass my instance for B as well. So in my init for A
class A(object):
__init__( args, B_arg):
And in class B
class B(object):
def passattributes():
c = A( args, self )
I'm going to guess that since you're trying to do what you're trying to do, the appropriate thing to do in your case is to define B.func1 as a classmethod, because you don't expect it to require an instance of class B.
#classmethod
def func1(cls, args):
'blah
You could create an instance of B in the A class:
class A(object):
def __init__(self):
'blah'
self.bInst = B()
def func2(self, args):
method = self.bInst.func1('func1')
class B(object):
def __init__(self):
'do stuff'
def func1(self, args):
print 'Do stuff here'
return
aInst = A()
aInst.func2('some arg')
Result:
Do stuff here

Inheritance in Python Such That All Base Functions Are Called

Basically, what I want is to do this:
class B:
def fn(self):
print 'B'
class A:
def fn(self):
print 'A'
#extendInherit
class C(A,B):
pass
c=C()
c.fn()
And have the output be
A
B
How would I implement the extendInherit decorator?
This is not a job for decorators. You want to completely change the normal behaviour of a class, so this is actually a job for a metaclass.
import types
class CallAll(type):
""" MetaClass that adds methods to call all superclass implementations """
def __new__(meta, clsname, bases, attrs):
## collect a list of functions defined on superclasses
funcs = {}
for base in bases:
for name, val in vars(base).iteritems():
if type(val) is types.FunctionType:
if name in funcs:
funcs[name].append( val )
else:
funcs[name] = [val]
## now we have all methods, so decorate each of them
for name in funcs:
def caller(self, *args,**kwargs):
""" calls all baseclass implementations """
for func in funcs[name]:
func(self, *args,**kwargs)
attrs[name] = caller
return type.__new__(meta, clsname, bases, attrs)
class B:
def fn(self):
print 'B'
class A:
def fn(self):
print 'A'
class C(A,B, object):
__metaclass__=CallAll
c=C()
c.fn()
A metaclass is a possible solution, but somewhat complex. super can do it very simply (with new style classes of course: there's no reason to use legacy classes in new code!):
class B(object):
def fn(self):
print 'B'
try: super(B, self).fn()
except AttributeError: pass
class A(object):
def fn(self):
print 'A'
try: super(A, self).fn()
except AttributeError: pass
class C(A, B): pass
c = C()
c.fn()
You need the try/except to support any order of single or multiple inheritance (since at some point there will be no further base along the method-resolution-order, MRO, defining a method named fn, you need to catch and ignore the resulting AttributeError). But as you see, differently from what you appear to think based on your comment to a different answer, you don't necessarily need to override fn in your leafmost class unless you need to do something specific to that class in such an override -- super works fine on purely inherited (not overridden) methods, too!
I personally wouldn't try doing this with a decorator since using new-style classes and super(), the following can be achieved:
>>> class A(object):
... def __init__(self):
... super(A, self).__init__()
... print "A"
...
>>> class B(object):
... def __init__(self):
... super(B, self).__init__()
... print "B"
...
>>> class C(A, B):
... def __init__(self):
... super(C, self).__init__()
...
>>> foo = C()
B
A
I'd imagine method invocations would work the same way.

Categories

Resources