Python - Base class' constructor is overriden - python

As explained in How does Python's super() work with multiple inheritance?, super can be used in multiple inheritance as well, as it will look for the attribute in both parents. But what attribute? The subclass already includes a super (if you look at the code below). How do I specify the attribute I want? I want Error's constructor.
class Error(object):
def __init__(self, values):
self.values = values
class AddDialog(sized_controls.SizedDialog, Error):
def __init__(self, *args, **kwargs):
Error.__init__(self, *args)
super(AddDialog, self).__init__(*args, **kwargs)

It is as easy as just trying it out:
class Error(object):
def __init__(self, values):
self.values = values
print('Error')
class SizedDialog(object):
def __init__(self, values):
self.values = values
print('SizedDialog')
class AddDialog(SizedDialog, Error):
def __init__(self, *args, **kwargs):
print('AddDialog')
Error.__init__(self, *args)
super(AddDialog, self).__init__(*args, **kwargs)
Now, super() is nothing else but going along the method resolution order (MRO) which you can get with mro():
>>> AddDialog.mro()
[__main__.AddDialog, __main__.SizedDialog, __main__.Error, object]
So, in your case you call the __init__() of Error explicitly first. Then super() will, in this specific case, find the __init__() of SizedDialog because it comes before Error in the MRO.
>>> a = AddDialog(10)
AddDialog
Error
SizedDialog
If you only use super() (no call to __init__() of Error), you get only the __init__() of SizedDialog:
class AddDialog(SizedDialog, Error):
def __init__(self, *args, **kwargs):
print('AddDialog')
super(AddDialog, self).__init__(*args, **kwargs)
>>> a = AddDialog(10)
AddDialog
SizedDialog
Finally, if you only call the __init__() of Error, it is the only __init__() that is called.
class AddDialog(SizedDialog, Error):
def __init__(self, *args, **kwargs):
print('AddDialog')
Error.__init__(self, *args)
>>> a = AddDialog(10)
AddDialog
Error
So your question:
But what attribute?
has the answer:
The one you call.
It does not matter if you hard-wire the class, as done with Error, or let super() find the appropriate parent class, i.e. the next one in the MRO.
The only difference is that super() might call the __init__()of the grandparent class if the parent class does not have an __init__().
But this is the intended behavior of super().

Related

Python how to add __init__ param to subclass

I have a subclass sharing the __ init __ of it's base class:
class SubClass(BaseClass)
def __init__(self, param, *args, **kwargs):
super().__init__(*args, **kwargs)
self.thing = param
The problem I have been having is the subclass __ init __ parameter "param" is being passed into the super().__init__(*args, **kwargs) as an extra parameter. This usually gives me an error like:
TypeError: __init__() takes from 1 to 2 positional arguments but 3 were given
I don't want that. I only want "param" to be used for these subclass instances. How do stop sending the extra param to the baseclass __ init __ while still being able to use it in the subclass __ init __? Example code to reproduce the issue:
from unittest import TestCase
class TestCaseSubClass(TestCase):
def __init__(self, param, *args, **kwargs):
super().__init__(*args, **kwargs) # Just use whatever is in TestCase's init + our stuff
self.thing = param
print(self.thing)
class TestClass(TestCaseSubClass(param='bdfbdfb')):
def test_stuff(self):
print('test stuff here')
Or with just raw python, no import, why cant I do this? (same error)
class A(object):
def __init__(self, athing='thing'):
self.thing = athing
print(self.thing)
class AB(A):
def __init__(self, param, *args, **kwargs):
super().__init__(*args, **kwargs)
self.param= param
print(self.param)
class ABC(AB(param='thh')):
pass
ABCinstance = ABC()
I'm interpreting this question as "how can I provide a default parameter to a subclass without defining an __init__ for it?". One possible way is to define the default value as a class attribute, which you access in the parent class' __init__:
from unittest import TestCase
class TestCaseSubClass(TestCase):
_default_param = None
def __init__(self, *args, **kwargs):
param = kwargs.pop("param", self._default_param)
super().__init__(*args, **kwargs) # Just use whatever is in TestCase's init + our stuff
self.thing = param
class TestClass(TestCaseSubClass):
_default_param = "bdfbdfb"
def test_stuff(self):
print('test stuff here')
x = TestClass()
print(x.thing) #"bdfbdfb"
y = TestClass(param="foo")
print(y.thing) #"foo"
This approach doesn't quite match the argument format in your question, since now param is a keyword-only argument, rather than a named positional argument. The principal practical difference is that you can't supply an argument for param unless you refer to it by name: z = TestClass("foo") won't do it, for example.
Based on the edits and comments to this question, another possible interpretation may be "How can I provide a parameter to a subclass that gets passed to the parent class, by any means necessary?", which has no requirement regarding default values. If you're willing to make param a mandatory parameter, then you simply need to pass the value in when creating a TestClass instance:
from unittest import TestCase
class TestCaseSubClass(TestCase):
def __init__(self, param, *args, **kwargs):
super().__init__(*args, **kwargs) # Just use whatever is in TestCase's init + our stuff
self.thing = param
class TestClass(TestCaseSubClass):
def test_stuff(self):
print('test stuff here')
x = TestClass("bdfbdfb")
print(x.thing) #"bdfbdfb"

__new__ is not getting called in object creation

case 1:
class Person:
def __new__(cls, *args, **kwargs):
print "called"
return super(Person, cls).__new__(cls, *args, **kwargs)
p=Person()
case 2:
class Person(object):
def __new__(cls, *args, **kwargs):
print "called"
return super(Person, cls).__new__(cls, *args, **kwargs)
p=Person()
In first case, the __new__() method is not called but in 2nd case it does.
If it doesn't get called, then how is Person object being created?
I guess it is something related to new and old style classes in Python2:
Old-style classes don't actually have a __new__ method because for them __init__ is the constructor, so basically if we would have:
class A:
def __new__(cls):
print "A.__new__ is called" # -> this is never called
A()
the body of __new__ will never be executed in this case because it is not the purpose for old-style classes.
In Python3, the behavior is the same, doesn't matter if you explicitly inherit from the object or not:
class Person1:
def __new__(cls, *args, **kwargs):
print("called")
return super(Person1, cls).__new__(cls, *args, **kwargs)
class Person2(object):
def __new__(cls, *args, **kwargs):
print("called")
return super(Person2, cls).__new__(cls, *args, **kwargs)
p1 = Person1()
p2 = Person2()
These should print "called" twice when invoked from 3.x.
I was looking for the documentation, and finally found it here:
https://staging2.python.org/dev/peps/pep-0253/
The type object has a new slot, tp_new, which can act as a factory for
instances of the type. Types are now callable, because the tp_call
slot is set in PyType_Type (the metatype); the function looks for the
tp_new slot of the type that is being called.
To add onto #devforfu's answer, in the old days, __new__ didn't exist. It was added with the addition of new-style classes.

Decorating a child class's __init__ method with super()

My class hierarchy is set up so that every child's __init__() must set self._init_has_run() to False, call the parent's __init__(), then do their own __init__(), and finally set self._init_has_run() to True. I have the following code:
class Parent:
def __init__(self, arg1, arg2):
pass # do stuff
def init(cls, fun):
def decorated_init(self, *args, **kwargs):
self._init_has_run = False
x = super()
super().__init__(*args, **kwargs)
fun(self, *args, **kwargs)
self._init_has_run = True
return decorated_init
class Child(Parent):
#Parent.init
def __init__(self, arg1, arg2):
pass # do stuff
Since there are a number of subclasses that follow the same general pattern for __init__(), and I can't figure out how to use metaclasses, I am using a decorator to consolidate the repetitive logic and then just applying that decorator to all descendant __init__() methods.
Python is throwing the following:
File "filename.py", line 82, in decorated_init
super().__init__(*args, **kwargs)
TypeError: object.__init__() takes no parameters
I confirmed through the debugger that the toggling of self._init_has_run works fine and super() is resolving to the Parent class, but when the decorator tries to call super().__init__(*args, **kwargs), why does Python try to call object.__init__() instead?
You can easily use metaclasses to do some pre/post-init stuff. Consider this example:
class Meta(type):
def __new__(meta, *args):
# This is something like 'class constructor'.
# It is called once for every new class definition.
# It sets default value of '_init_has_run' for all new objects.
# This is analog to `class Foo: _init_has_run = False`:
# new objects will all have _init_has_run set to False by default.
cls = super(Parent, meta).__new__(meta, *args)
cls._init_has_run = False
return cls
def __call__(cls, *args, **kwargs):
# This is called each time you create new object.
# It will run new object's constructor
# and change _init_has_run to False.
obj = type.__call__(cls, *args, **kwargs)
obj._init_has_run = True
return obj
class Child:
__metaclass__ = Meta
def __init__(self):
print 'init:', self._init_has_run
def foo(self):
print 'foo:', self._init_has_run
a = Child()
a.foo()
a = Child()
a.foo()
Output:
init: False
foo: True
init: False
foo: True
Hope this helps!

Get attribute from a super class in python

I have a base class, a bunch of subclasses, and for each of these subclasses, I have another set of sub-subclasses. For example:
class BaseClass(object):
def __init__(self):
with open(config.txt) as f
self.config_array = f.readlines()
class FirstOrderSubClass(BaseClass):
def __init__(self, name):
self.name = name
class SecondOrderSubClass(FirstOrderSubClass):
def __init__(self, name, version):
self.name = name
self.version = version
super(SecondOrderSubClass, self).__init__(self.name)
# needed to access self.config_array
print self.config_array
I need to get the __init__() method of the SecondOrderSubClass to make the following assignment: self.lines = self.config_array.
EDIT: added line print self.config_array. If I run the code I get:
TypeError: __getattr__() takes exactly 1 argument (2 given)
You cannot access self.config_array until BaseClass.__init__() has run to set the attribute.
Either fix FirstOrderSubClass to also invoke the base class __init__ or call it directly.
Fixing the FirstOrderSubClass is probably the best way to do so:
class FirstOrderSubClass(BaseClass):
def __init__(self, name):
super(FirstOrderSubClass, self).__init__()
self.name = name
However, your __init__ method signatures do not match so you cannot rely on cooperative behaviour here; as soon as you add a mix-in class in the hierarchy, things can and probably will break. See *Python's super() is considered super! by Raymond Hettinger, or it's followup PyCon presentation to explain why you want your signatures to match.
Calling the BaseClass.__init__ unbound method directly (passing in self explicitly) would also work:
class SecondOrderSubClass(FirstOrderSubClass):
def __init__(self, name, version):
super(SecondOrderSubClass, self).__init__(name)
self.version = version
BaseClass.__init__(self)
Note that there is no point in assigning to self.name there if you are going to ask FirstOrderSubClass.__init__ to do the exact same thing.
The proper way to use super() is for all your methods to at least accept all the same arguments. Since object.__init__() never does, this means you need a sentinel class that does not use super(); BaseClass will do nicely here. You can use *args and **kw to capture any additional arguments and just ignore those to make cooperative subclassing work:
class BaseClass(object):
def __init__(self, *args, **kw):
with open(config.txt) as f
self.config_array = f.readlines()
class FirstOrderSubClass(BaseClass):
def __init__(self, name, *args, **kw):
super(FirstOrderSubClass, self).__init__(*args, **kw)
self.name = name
class SecondOrderSubClass(FirstOrderSubClass):
def __init__(self, name, version, *args, **kw):
super(SecondOrderSubClass, self).__init__(name, *args, **kw)
self.version = version
You have to call the FirstOrderSubClass super method:
class BaseClass(object):
def __init__(self):
with open("config.example.txt",'w') as f:
f.write("Hello world")
with open("config.example.txt") as f:
self.config_array = f.readlines()
class FirstOrderSubClass(BaseClass):
def __init__(self, name):
super(FirstOrderSubClass,self).__init__()
self.name = name
class SecondOrderSubClass(FirstOrderSubClass):
def __init__(self, name, version):
self.name = name
self.version = version
super(SecondOrderSubClass, self).__init__(self.name)
# needed to access self.config_array
grandchild = SecondOrderSubClass("peter",2.0)
print grandchild.config_array
##>>>
##['Hello world']

Python **kwargs in parent and child constructor

If I write an inheritance relationship as follows:
class Pet(object):
def __init__(self, n):
print n
class Dog(Pet):
def __init__(self, **kwargs):
Pet.__init__(self, 5)
Then the output is 5. If, however, I wanted to do:
class Pet(object):
def __init__(self, **kwargs):
if not "n" in kwargs:
raise ValueError("Please specify the number I am to print")
print kwargs["n"]
class Dog(Pet):
def __init__(self, **kwargs):
Pet.__init__(self, kwargs)
Then I get the error TypeError: __init__() takes exactly one argument (two given)
How can I pass the extra arguments up the inheritance chain in this way?
Simply pass the arguments down as keyword arguments:
class Pet(object):
def __init__(self, **kwargs):
if not "n" in kwargs:
raise ValueError("Please specify the number I am to print")
print kwargs["n"]
class Dog(Pet):
def __init__(self, **kwargs):
Pet.__init__(self, **kwargs)
However, you should use super rather than hardcoding the superclass.
Change your definition of Dog to (Python 2.X):
class Dog(Pet):
def __init__(self, **kwargs):
super(Dog, self).__init__(**kwargs)
And in Python 3.X it's nicer, just:
class Dog(Pet):
def __init__(self, **kwargs):
super().__init__(**kwargs)
Figured it out: Use Pet.__init__(self, **kwargs)
It's called "unpacking" and it transforms the dictionary kwargs into a list of argument=value pairs.
Then **kwargs in the parent constructor is able to handle them. Just passing a dictionary would not work because **kwargs in the constructor is expecting a bunch of argument=value pairs but instead I was just passing it one value, the dictionary kwargs from the child.

Categories