class Base:
def __init__(self, x, y, *args, **kwargs):
pass
class A(Base):
def __init__(self, x, y, z, *args, **kwargs):
super().__init__(x, y, *args, **kwargs)
class B(Base):
def __init__(self, x, y, t, *args, **kwargs):
super().__init__(x, y, *args, **kwargs)
class C(A, B):
def __init__(self, x, y, z, t, *args, **kwargs):
super().__init__(x, y, z, *args, **kwargs)
C(1, 2, 3, 4)
Traceback (most recent call last):
File "test.py", line 25, in <module>
C(1, 2, 3, 4)
File "test.py", line 22, in __init__
A.__init__(self, x, y, z, *args, **kwargs)
File "test.py", line 10, in __init__
super().__init__(x, y, *args, **kwargs)
TypeError: __init__() missing 1 required positional argument: 't'
I first try to add '*args' and '**kwargs', but it didn't work. I wonder why this happen? How to fix? Could someone explain this for me? Thank you!
You are discarding t altogether; *args is empty when you call C(1, 2, 3, 4); the value of the 4th parameter isn't automatically added to it, so B.__init__ will never see it.
Likewise, A.__init__ will cause B.__init__ to only be called with 2 arguments, which is where the error comes from: no 3rd argument for B.__init__.
The ordering of positional parameters can be an issue as well; the standard advice is to use keyword arguments exclusively, and let each __init__ extract the keyword arguments needed for its own parameters. For example,
class Base:
def __init__(self, *, x, y, **kwargs):
super().__init__(**kwargs)
self.x = x
self.y = y
class A(Base):
def __init__(self, *, z, **kwargs):
super().__init__(**kwargs)
self.z = z
class B(Base):
def __init__(self, *, t, **kwargs):
super().__init__(**kwargs)
self.t = t
class C(A, B):
pass
C(x=1, y=2, z=3, t=4)
Now there is no ambiguity about which positional arguments are consumed by which __init__ method: A.__init__ gets z and leaves x ,y, and t for classes higher up the MRO to deal with. B.__init__ extracts t, leaving x and y. Base extracts x and y, leaving nothing (as is correct) to be passed on to object.__init__.
(Base.__init__ uses super as well, because you don't know what other class Foo might use Base in a way that Base will not be the last class in Foo's MRO. Only object.__init__ does not use super(); it's sole purpose is to terminate the chain of super calls, as there's nothing in a bare object instance to initialize.)
Related
Below is a MWE of my attempt to change the signature of MyClass.func from (self, a, b, c) to (self, x, y, z). As you can see from the outputs, the change_sig decorator works on functions and also works on the bound method MyClass().func, but fails on the unbound method. Is there some sort of magic going on behind the scenes when I assign a callable attribute to a class that keeps this from working?
MWE:
import wrapt
import inspect
def change_sig(func):
adapter = '(self, x, y, z)'
#wrapt.decorator(adapter=adapter)
def wrapper(wrapped, instance, args, kwargs):
pass
wrapped_func = wrapper(func)
return wrapped_func
#change_sig
def func(self, a, b, c):
pass
class MyClass:
#change_sig
def func(self, a, b, c):
pass
print('inspect.signature(func):', inspect.signature(func))
print('inspect.signature(MyClass.func):', inspect.signature(MyClass.func))
print('inspect.signature(MyClass().func):', inspect.signature(MyClass().func))
print('inspect.signature(change_sig(MyClass.func)):', inspect.signature(change_sig(MyClass.func)))
MyClass.wrapped_func = change_sig(MyClass.func)
print('inspect.signature(MyClass.wrapped_func):', inspect.signature(MyClass.wrapped_func))
Output:
inspect.signature(func): (self, x, y, z)
inspect.signature(MyClass.func): (self, a, b, c)
inspect.signature(MyClass().func): (x, y, z)
inspect.signature(change_sig(MyClass.func)): (self, x, y, z)
inspect.signature(MyClass.wrapped_func): (self, a, b, c)
This was traced to a bug in wrapt, which is being addressed: https://github.com/GrahamDumpleton/wrapt/issues/148
I have a parent class:
class BaseClass:
def __init__(self, foo, bar=10):
self.foo = foo
And an inherited class from it:
class InheritedClass(BaseClass):
def __init__(self, x, y, bar=10):
super().__init__(True, bar=bar)
self.x = x
self.y = y
At the moment, bar has a default value in both the parent and the inherited class, which I have to keep track of to make sure they are both synced. How can I change the InheritedClass constructor to instead use the default value for bar defined in the BaseClass?
I've tried:
class InheritedClass(BaseClass):
def __init__(self, x, y, bar=None):
if bar is None:
super().__init__(True)
else:
super().__init__(True,bar)
self.x = x
self.y = y
which provides the behavior I'm looking for, but doesn't seem very scalable. Is there a better way?
Don't expose the parent's arguments at all in the signature; just pass what you receive on (if anything) on to the next class in the MRO.
class BaseClass:
def __init__(self, foo, bar=10, **kwargs):
super().__init__(**kwargs)
# Do something with bar
self.foo = foo
class InheritedClass(BaseClass):
def __init__(self, x, y, **kwargs)
kwargs['foo'] = True
super().__init__(**kwargs)
self.x = x
self.y = y
You have to accept and pass on arbitrary keywords anyway to use super properly, so you may as well do the same thing for the parent class's arguments, even though you "know" them already.
This also means always using keyword arguments for __init__, as you can't really predict how your class's positional arguments will interact with another class on the MRO. (Positional arguments can only be consumed on a first-come, first-served basis, and you simply don't know in the case of multiple inheritance when your class's method will be called.)
foo = InheritedClass(bar=9, x=3, y=10)
You can use variable arguments instead:
class InheritedClass(BaseClass):
def __init__(self, x, y, *args, **kwargs):
super().__init__(True, *args, **kwargs)
self.x = x
self.y = y
Your approach is not bad and yes there is a better way to do it maybe better than mine but here is what I come up with hope this helps.
Sample:
class BaseClass:
def __init__(self, foo, bar=10):
self.foo = foo
self.bar = bar
class InheritedClass(BaseClass):
def __init__(self, x, y, bar=None):
super().__init__(True, bar=bar)
if bar is None: bar = self.bar
self.x = x
self.y = y
class A():
def __init__( self, x, y):
self.x = x
self.y = y
class B():
def __init__( self, z=0):
self.z = z
class AB(A,B):
def __init__( self, x, y, z=0):
?
How can I make the constructor of AB call the constructors for A and B with the proper arguments?
I've tried
class AB(A,B):
def __init__( self, x, y, z=0):
A.__init__(x,y)
B.__init__(z)
but this gives me an error.
Other answers suggested adding self to the first parameter.
But usually invocations of __init__ in parent classes are made by super.
Consider this example:
class A(object):
def __init__(self, x):
print('__init__ is called in A')
self.x = x
class B(object):
def __init__(self, *args, **kwargs):
print('__init__ is called in B')
super(B, self).__init__(*args, **kwargs)
class AB(B, A):
def __init__(self, *args, **kwargs):
print('__init__ is called in AB')
super(AB, self).__init__(*args, **kwargs)
AB class contains an order in which constructors and initializators should be called:
>>> AB.__mro__
(<class '__main__.AB'>, <class '__main__.B'>, <class '__main__.A'>, <type 'object'>)
See, that first AB's __init__ is invoked, then B's, then A's, and then object's.
Let's check:
>>> ab = AB(1)
__init__ is called in AB
__init__ is called in B
__init__ is called in A
But these calls through this chain are made by super. When we type super(AB, self), it means: find then next class after AB in __mro__ chain of self.
Then we should invoke super in B, looking for the next class in the chain after B: super(B, self).
It's important to use super and not call manually A.__init__(self,...), etc., as it may lead to problems later. Read this for more info.
So, if you stick with super, then there is a problem. __init__ methods in your classes expect different parameters. And you can't know for sure the order in which super will be invoking methods in these classes. The order is determined by C3 algorithm at the time of class creation. In subclasses another classes may get in-between of the call chain. So you can't have different parameters in __init__, as in this case you will have always to consider all inheritance chain to understand how __init__ methods will be called.
For example, consider adding C(A) and D(B) classes and CD subclass of them. Then A will no longer be invoked after B, but after C.
class A(object):
def __init__(self, *args, **kwargs):
print('__init__ is called in A')
super(A, self).__init__(*args, **kwargs)
class B(object):
def __init__(self, *args, **kwargs):
print('__init__ is called in B')
super(B, self).__init__(*args, **kwargs)
class AB(B,A):
def __init__(self, *args, **kwargs):
print('__init__ is called in AB')
super(AB, self).__init__(*args, **kwargs)
class C(A):
def __init__(self, *args, **kwargs):
print('__init__ is called in C')
super(C, self).__init__(*args, **kwargs)
class D(B):
def __init__(self, *args, **kwargs):
print('__init__ is called in D')
super(D, self).__init__(*args, **kwargs)
class CD(D,C):
def __init__(self, *args, **kwargs):
print('__init__ is called in CD')
super(CD, self).__init__(*args, **kwargs)
class ABCD(CD,AB):
def __init__(self, *args, **kwargs):
print('__init__ is called in ABCD')
super(ABCD, self).__init__(*args, **kwargs)
>>> abcd = ABCD()
__init__ is called in ABCD
__init__ is called in CD
__init__ is called in D
__init__ is called in AB
__init__ is called in B
__init__ is called in C
__init__ is called in A
So I think it's a good idea to think about using delegation instead of inheritance here.
class AB(object):
def __init__(self, x, y, z=0):
self.a = A(x,y)
self.b = B(z)
So, you just create a and b instances of A and B classes inside AB object. And then may use them as you need through methods by referring to self.a and self.b.
To use or not delegation depends on your case which is not clear from your question. But it may be an option to consider.
You didn't pass self.
class AB(A, B):
def __init__(self, x, y, z=0):
A.__init__(self, x, y)
B.__init__(self, z)
Note that if this inheritance hierarchy gets more complicated, you'll run into problems with constructors not executing or getting reexecuted. Look into super (and the problems with super), and don't forget to inherit from object if you're on 2.x and your class doesn't inherit from anything else.
Okay, so I've got a class where one of the attributes is a callback function. Problem is, whenever I call it from within the class (e.g. as self.function_attr(), it gets passed self as the first argument. Here's an idea of what I'm working with:
def callback(a, b):
# do something with a, b
class A:
def __init__(self, callback):
self.callback = callback
self.callback(1, 2) # Raises a TypeError: takes exactly 2 arguments (3 given)
I'm not willing to write each callback function to take self as a first argument. I wrote a decorator that works around the issue:
def callback_decorator(func):
def newfunc(self, *args, **kw):
return func(*args, **kw)
return newfunc
but I'm wondering if there's anything better.
Basically, my question is, how can I call instance attributes of my class which are functions without them being passed self as the first argument?
You just need to make it a staticmethod when you bind it to the class.
def callback(a, b):
# do something with a, b
class A:
def __init__(self, callback):
# now it won't get passed self
self.callback = staticmethod(callback)
self.callback(1, 2)
or
class A:
def __init__(self, callback):
self.callback(1, 2)
# now it won't get passed self
callback = staticmethod(callback)
As far as I know, a wrapper (like your decorator) is the simplest way to go. Since you already have an object in which to store the function, I wouldn't bother with a decorator. (Note I've inherited from object, which is something you should probably be doing unless you specifically want old-style class behaviour.)
class A(object):
def __init__(self, callback):
self._callback = callback
self.callback(1,2)
def callback(self, *args, **kwargs):
return self._callback(*args, **kwargs)
This behaves as you'd expect:
>>> def f(x, y):
... print "X: %s, Y: %s" % (x,y)
...
>>> mya = A(f)
X: 1, Y: 2
I have a class:
class A(object):
def __init__(self,a,b,c,d,e,f,g,...........,x,y,z)
#do some init stuff
And I have a subclass which needs one extra arg (the last W)
class B(A):
def __init__(self.a,b,c,d,e,f,g,...........,x,y,z,W)
A.__init__(self,a,b,c,d,e,f,g,...........,x,y,z)
self.__W=W
It seems dumb to write all this boiler-plate code, e.g passing all the args from B's Ctor to the inside call to A's ctor, since then every change to A's ctor must be applied to two other places in B's code.
I am guessing python has some idiom to handle such cases which I am unaware of. Can you point me in the right direction?
My best hunch, is to have a sort of Copy-Ctor for A and then change B's code into
class B(A):
def __init__(self,instanceOfA,W):
A.__copy_ctor__(self,instanceOfA)
self.__W=W
This would suit my needs since I always create the subclass when given an instance of the father class, Though I am not sure whether it's possible...
Considering that arguments could be passed either by name or by position, I'd code:
class B(A):
def __init__(self, *a, **k):
if 'W' in k:
w = k.pop('W')
else:
w = a.pop()
A.__init__(self, *a, **k)
self._W = w
Edit: based on Matt's suggestion, and to address gnibbler's concern re a positional-argument approach; you might check to make sure that the additional subclass-specific argument is being specified—similar to Alex's answer:
class B(A):
def __init__(self, *args, **kwargs):
try:
self._w = kwargs.pop('w')
except KeyError:
pass
super(B,self).__init__(*args, **kwargs)
>>> b = B(1,2,w=3)
>>> b.a
1
>>> b.b
2
>>> b._w
3
Original answer:
Same idea as Matt's answer, using super() instead.
Use super() to call superclass's __init__() method, then continue initialising the subclass:
class A(object):
def __init__(self, a, b):
self.a = a
self.b = b
class B(A):
def __init__(self, w, *args):
super(B,self).__init__(*args)
self.w = w
In situations where some or all of the arguments passed to __init__ have default values, it can be useful to avoid repeating the __init__ method signature in subclasses.
In these cases, __init__ can pass any extra arguments to another method, which subclasses can override:
class A(object):
def __init__(self, a=1, b=2, c=3, d=4, *args, **kwargs):
self.a = a
self.b = b
# …
self._init_extra(*args, **kwargs)
def _init_extra(self):
"""
Subclasses can override this method to support extra
__init__ arguments.
"""
pass
class B(A):
def _init_extra(self, w):
self.w = w
Are you wanting something like this?
class A(object):
def __init__(self, a, b, c, d, e, f, g):
# do stuff
print a, d, g
class B(A):
def __init__(self, *args):
args = list(args)
self.__W = args.pop()
A.__init__(self, *args)