Why use super() instead of __init__()? - python

If we have class A, defined as follows,
class A:
def __init__(self, x):
self.x = x
why do most people use
class B(A):
def __init__(self, x, y):
super().__init__(x)
self.y = y
instead of
class B(A):
def __init__(self, x, y):
A.__init__(self, x)
self.y = y
? I think A.__init__(self, x) is better then super().__init__(x) because it supports multiple inheritance (and I didn't find a way to do it with super()):
class A:
def __init__(self, x):
self.x = x
class B:
def __init__(self, y):
self.y = y
class C(A, B):
def __init__(self, x, y, z):
A.__init__(self, x)
B.__init__(self, y)
self.z = z
When I try to use super() in the previous example, like this:
class A:
def __init__(self, x):
self.x = x
class B:
def __init__(self, y):
self.y = y
class C(A, B):
def __init__(self, x, y, z):
super().__init__(self, x)
super().__init__(self, y)
self.z = z
class C doesn't have attribute y (try: c = C(1, 2, 3) and print(c.y) in next line).
What am I doing wrong?

I you use super().__init__(), Python will automatically call all constructors of your base classes on its own in the right order (in your second example first A, then Bconstructors).
This is one of the beauty of Python, it handles multiple inheritance nicely :).

Related

Python method that returns instance of class or subclass while keeping subclass attributes

I'm writing a Python class A with a method square() that returns a new instance of that class with its first attribute squared. For example:
class A:
def __init__(self, x):
self.x = x
def square(self):
return self.__class__(self.x**2)
I would like to use this method in a subclass B so that it returns an instance of B with x squared but all additional attributes of B unchanged (i. e. taken from the instance). I can get it to work by overwriting square() like this:
class B(A):
def __init__(self, x, y):
super(B, self).__init__(x)
self.y = y
def square(self):
return self.__class__(self.x**2, self.y)
If I don't overwrite the square() method, this little code example will fail because I need to pass a value for y in the constructor of B:
#test.py
class A:
def __init__(self, x):
self.x = x
def square(self):
return self.__class__(self.x**2)
class B(A):
def __init__(self, x, y):
super(B, self).__init__(x)
self.y = y
#def square(self):
# return self.__class__(self.x**2, self.y)
a = A(3)
a2 = a.square()
print(a2.x)
b = B(4, 5)
b2 = b.square()
print(b2.x, b2.y)
$ python test.py
9
Traceback (most recent call last):
File "test.py", line 20, in <module>
b2 = b.square()
File "test.py", line 6, in square
return self.__class__(self.x**2)
TypeError: __init__() takes exactly 3 arguments (2 given)
Overwriting the method once isn't a problem. But A potentially has multiple methods similar to square() and there might be more sub(sub)classes. If possible, I would like to avoid overwriting all those methods in all those subclasses.
So my question is this:
Can I somehow implement the method square() in A so that it returns a new instance of the current subclass with x squared and all other attributes it needs for the constructor taken from self (kept constant)? Or do I have to go ahead and overwrite square() for each subclass?
Thanks in advance!
I'd suggest implementing .__copy__() (and possibly .__deepcopy__ as well) methods for both classes.
Then your squared can be simple method:
def squared(self):
newObj = copy(self)
newObj.x = self.x **2
return newObj
It will work with inheritance, assuming all child classes have correctly implemented __copy__ method.
EDIT: fixed typo with call to copy()
Full working example:
#test.py
from copy import copy
class A:
def __init__(self, x):
self.x = x
def square(self):
newObj = copy(self)
newObj.x = self.x **2
return newObj
def __copy__(self):
return A(self.x)
class B(A):
def __init__(self, x, y):
super(B, self).__init__(x)
self.y = y
def __copy__(self):
return B(self.x, self.y)
a = A(3)
a2 = a.square()
print(a2.x)
b = B(4, 5)
b2 = b.square()
print(b2.x, b2.y)
check if the object contains y then return the right class instance:
class A:
x: int
def __init__(self, x):
self.x = x
def square(self):
if hasattr(self, 'y'):
return self.__class__(self.x ** 2, self.y)
return self.__class__(self.x**2)
class B(A):
y: int
def __init__(self, x, y):
super(B, self).__init__(x)
self.y = y
# def square(self):
# return self.__class__(self.x**2, self.y)

TypeError: Vector() takes no arguments

I am trying to Implement the set_x, set_y, init, and str methods in the class Vector above, the output of this test should be:
#Vector: x=4, y=4
#Vector: x=5, y=5
#Vector: x=3, y=7
#Vector: x=3, y=7
.
class Vector:
def __init__(self, x, y): self.x = x self.y = y
def set_x(self,x): set_x = x
def set_y(self,y): set_y = y
def __str__(self): return ("Vector: x=%s, y=%s", set_x, set_y)
#__init__ and __str__ v1=Vector(4,4) print(v1)
#Important Remark
#v1.x,v1.y =4,4 # should return an error since x and y are private
# test set_x and set_y v1.set_x(5) v1.set_y(5) print(v1)
v1.set_x(1) v1.set_y(9) print(v1)
# test __init__ again print(Vector(1,9))
I believe this is what you are trying to do:
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def set_x(self, x):
self.x = x
def set_y(self, y):
self.y = y
def __str__(self):
return f"Vector: x={self.x}, y={self.y}"
v1 = Vector(1, 9)
print(v1)

Python class property update not propagating to parent class

When I update an instance property, that change is not reflected in the __str__ method of the parent class. This makes sense, as super().__init__ is called during the __init__ call, and when I make an update to the property, it's not getting called again.
class BulkData:
def __init__(self, fields):
self.fields = fields
def __str__(self):
return ''.join(str(f) for f in self.fields)
class Node(BulkData):
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
fields = [self.x, self.y, self.z]
super().__init__(fields)
Example:
n1 = Node(1,2,3)
n1.x = 5
print(n1)
> '123'
I would like the result to be '523'.
You mention in your question "property" but those are just attributes. One way to achieve that is to actually use a property:
class BulkData:
def __init__(self, fields):
self.fields = fields
def __str__(self):
return ''.join(self.fields)
class Node(BulkData):
def __init__(self, x, y, z):
self._x = x
self._y = y
self._z = z
fields = [self._x, self._y, self._z]
super().__init__(fields)
#property
def x(self):
return self._x
#x.setter
def x(self, val):
self._x = val
self.fields[0] = val
And now:
>>> n1 = Node('1','2','3')
>>> n1.x = '5'
>>> print(n1)
523
You can define actual Python properties that reflect the state of fields:
class BulkData:
def __init__(self, fields, floats=None):
if floats is None:
floats = []
self.fields = fields
self.floats = floats
def __str__(self):
return ''.join([str(x) for x in self.fields])
class Node(BulkData):
def __init__(self, x, y, z):
super().__init__([x, y, z])
#property
def x(self):
return self.fields[0]
#x.setter(self, v):
self.fields[0] = v
#property
def y(self):
return self.fields[1]
#y.setter(self, v):
self.fields[1] = v
#property
def z(self):
return self.fields[2]
#z.setter(self, v):
self.fields[2] = v
Instead of instance attributes, you define property instances that "wrap" a particular slot of the underlying fields attribute.
If you are thinking, that's a lot of code... you're right. Let's fix that.
class FieldReference(property):
def __init__(self, pos):
def getter(self):
return self.fields[pos]
def setter(self, v):
self.fields[pos] = v
super().__init__(getter, setter)
class Node(BulkData):
def __init__(self, x, y, z):
super().__init__([x, y, z])
x = FieldReference(0)
y = FieldReference(1)
z = FieldReference(2)
Here, we subclass property and hard-code the getter and setter functions to operate on the position specified by the argument to the property. Note that self in getter and setter are distinct from self in __init__, but getter and setter are both closures with access to __init__'s argument pos.
You might also want to read the Descriptor how-to, specifically the section on properties to understand why FieldReference is defined the way it is.

What is the safest way to pass parameters with defaults in class inheritance?

Suppose I have some Python class:
class A:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
And now, I want to define a child class whose y value is set by default, say with value 1. The only change in this new class is this default for y, then I guess this formulation will work
class B(A):
def __init__(self, **kwargs):
y = kwargs.pop("y", 1)
assert y == 1
super.__init__(y=y, **kwargs)
What is the classic form to pass defaults in inheritance?
It depends on what you want. Your current code throws an error if y is anything other than 1, so it's pointless to make it a parameter at all. Just do:
class B(A):
def __init__(self, *args, **kwargs):
super().__init__(*args, y=1, **kwargs)
This passes all parameters through to A, whether passed positionally or as keywords, and fixes y to 1. You'll get an error if you try to pass y to B.
Alternatively, if you want y to default to 1 but still be able to supply it as an argument to B:
class B(A):
def __init__(self, *args, y=1, **kwargs):
super().__init__(*args, y=y, **kwargs)
The second code sample won't work. It requires keyword arguments which is surprising compared to the first sample and with assert it requires y == 1, it doesn't default to it. Also super must be called.
Usual way (with reordering of parameters) is:
class B(A):
def __init__(self, x, z, y=1):
super().__init__(x, y, z)
Alternatively without reordering:
class B(A):
def __init__(self, x, yz, z=None):
if z is None:
super().__init__(x, 1, yz)
else:
super().__init__(x, yz, z)

Which is the better way to inheritance?

Is there any difference between this:
class Vehicle():
def __init__(self, x, y):
self.y = y
self.x = x
class Car(Vehicle):
def __init__(self, x, y):
Vehicle.__init__(self, x, y)
class Scooter(Vehicle):
def __init__(self, x, y):
Vehicle.__init__(self, x, y)
and this:
class Vehicle():
def __init__(self, x, y):
self.y = y
self.x = x
class Car(Vehicle):
pass
class Scooter(Vehicle):
pass
Because without def __init__ in child classes I got the same thing, I mean __init__ doesn't provide any effect.
You should't do either of them. The best way to do it is using super.
class Vehicle():
def __init__(self, x, y):
self.y = y
self.x = x
class Car(Vehicle):
def __init__(self, x, y):
super(Car, self).__init__(x, y)
# super().__init__(x, y) # for python3
Check this blog post by Raymond Hettinger (core python contributor) on why you should be using super
You need __init__ method when you want to do child specific initialisation. Assume your child classes require another argument to be passed to the constructor and is unique to that class, in that case __init__ method is really required.
class Vehicle():
def __init__(self, x, y):
self.y = y
self.x = x
class Car(Vehicle):
def __init__(self, x, y, z):
Vehicle.__init__(self, x, y)
self.z = z
class Scooter(Vehicle):
def __init__(self, x, y, z):
Vehicle.__init__(self, x, y)
self.z = z
I think this would be best explained with an example.
Now take this scenario:
>Vehicle ---> Car,Bike,Boat,Aeroplane,Train
>[All are vehicles right]
>Things they have in common would be (say for ex.) **Price** and **Color**
However things they won't have in common would be?
>**Wheels**. The total number of wheels may differ.
>
> Car-4 Bike-2 Boat-0 Aeroplane-(**Not sure**) Train-(**Many I
guess**?)
But you get the point right? So When I want to just have a Vehicle object I don't want (or I can't tell the number of wheels) In that case I can initialize only with just price and color
However when I know the specific type of Vehicle say Car now I can __init__ it with number of wheels. Now this is where object specific initializations play a major role.
A full example code of the above sample:
class Vehicle():
def __init__(self, x, y):
self.color = y
self.price = x
def Horn(self):
print("Pommm...Pommmmm!!")
class Car(Vehicle):
def __init__(self, x, y,wheel):
Vehicle.__init__(self, x, y)
self.wheel = "Four Wheels man: 4"
class Scooter(Vehicle):
def __init__(self, x, y,wheel):
Vehicle.__init__(self, x, y)
self.wheel = "Just Two man : 2"
VehObj = Vehicle("5000$","Black")
VehObj.Horn()
print(VehObj.color,VehObj.price)
#However note this
carObj = Car("5000$","Black",4)
print(carObj.color,carObj.price,carObj.wheel)
#Look at this
sObj = Scooter("5000$","Black",2)
print(sObj.color,sObj.price,sObj.wheel)
Output:
Pommm...Pommmmm!!
Black 5000$
Black 5000$ Four Wheels man: 4
Black 5000$ Just Two man : 2
Hope that cleared you up.
If you don't provide an __init__ method in the child classes, they will just use the __init__ method defined in their parent class (aka inheritance). In the former case, you are overriding the __init__ method for the child classes but you are simply calling the __init__ method of the parent class. So if you don't do that (like the later case) it will be the same. The later case automatically inherits the __init__ method.
Other ways to write the same thing would be:
class Car(Vehicle): #This is the best way to do it though
def __init__(self, x, y):
super()__init__(x, y)
Or
class Car(Vehicle):
def __init__(self, x, y):
self.x = x
self.y = y
TLDR; They are equivalent.
Calling the init method of super class is optional if you don't wont to edit the __init__ method of superclass.
but if you want to edit the superclass method you need custom init
class Vehicle():
def __init__(self, x, y):
self.y = y
self.x = x
class Car(Vehicle):
def __init__(self, x, y, z):
Vehicle.__init__(self, x, y)
self.z = z

Categories

Resources