Are you allowed to use a method inside the init method? - python

I just started learning about OOP in python3 and we made this little class today.
class Square:
def __init__(self, side):
self.side = side
def show_perimeter(self):
print(self.side * 4)
test_square = Square(10)
test_square.show_perimeter()
>> 40
Now I'm thinking if it's possible to get the value of the perimeter while creating the object, something like...
class Square:
def __init__(self, side):
self.side = side
self.permiter = get_perimeter(self)
def get_perimeter(self):
return self.side * 4
test_square = Square(10)
print(test_square.perimeter)
Is this something you're able to do?
If so, is it a good practice?
If not, what would be the best way to get the perimeter by just using the side?

There is nothing special about the __init__ method other than it is invoked automatically as part of the construction of an object (__new__ creates the object, and __init__ initializes it). Therefore, you can do whatever you need to do within it. However, you need to make sure that you don't inadvertently do things that would cause operations on a partially initialized object. The use of #property below can solve most of these edge cases.
The only difference here is it is better form to call self.method() rather than method(self) in most cases.
class Square:
def __init__(self, side):
self.side = side
self.permiter = self.get_perimeter()
def get_perimeter(self):
return self.side * 4
test_square = Square(10)
print(test_square.perimeter)
However, I'd like to point out that a property might be better in this case:
class Square():
def __init__(self, side):
self.side = side;
#property
def perimeter(self):
return self.side * 4
x = Square(10)
print(x.perimeter)
>>> 40
In this case, the #property decorator converts the perimeter method to a property which can be accessed just like it is another attribute, but it is calculated at the time it is asked for.

It's allowed, but it's also a lot more dangerous than people usually realize.
Say you have the following class:
class Thing:
def __init__(self):
self._cached_size = self.compute_size()
def compute_size(self):
return 1000
That seems to work fine on its own, but then if you try to subclass it:
class SubThing(Thing):
def __init__(self, more_stuff):
super().__init__()
self.more_stuff = more_stuff
def compute_size(self):
return super().compute_size() + self.more_stuff
SubThing(5)
Everything goes to pieces, because Thing.__init__ calls SubThing.compute_size, and SubThing.compute_size assumes self.more_stuff is ready, but it's not ready:
Traceback (most recent call last):
File "./prog.py", line 14, in <module>
File "./prog.py", line 9, in __init__
File "./prog.py", line 3, in __init__
File "./prog.py", line 12, in compute_size
AttributeError: 'SubThing' object has no attribute 'more_stuff'
You should essentially never call methods in __init__ that you expect to be overridden, since your object is in a very precarious, half-constructed state, especially the parts of its state that subclasses are responsible for.
If you want to use your own class's version of a method and ignore overrides (dodging the problem of subclass state), you can call it directly:
class Thing:
def __init__(self):
self._cached_size = Thing.compute_size(self)
def compute_size(self):
return 1000
Some people might recommend having subclasses initialize their state before calling super().__init__, but that leads into a really nastily coupled mess of different classes depending on specific parts of other classes being ready. It's not going to decrease the number of problems you have.

You should reference the method in the same class with self.
class Square:
def __init__(self, side):
self.side = side
self.perimeter = self.get_perimeter()
def get_perimeter(self):
return self.side * 4

Related

Is it impossible to reduce the arguments needed for Child Class compared to its Parent class?

This is my first post here and I'm new to python and excited to learn. I hope my post here is clear enough for everyone.
Is there anyone that knows if it's possible to reduce the arguments needed for a child class from its parents' inheritance?
Here's the example:
# this is a code to estimate any polygon's perimeter (parent)
# the second one is code to estimate a square's area (child)
class RegularPolygon:
`def __init__(self,
no_of_sides=0,
side_length=0):`
`self.no_of_sides = no_of_sides`
`self.side_length = side_length`
`def perimeter(self):`
`perimeter = self.no_of_sides * self.side_length`
`return perimeter`
`class Square(RegularPolygon):`
`def __init__(self,
no_of_sides=4,
side_length=0):`
`super().__init__(no_of_sides, side_length)`
`def area(self):`
`area = self.side_length ** 2`
`return area`
I'm trying call child class and to estimate square. Because it's a square it always has 4 sides, so no_of_sides = will always 4. I'm thinking of making it automatic hence I can call class Square with just one arguments.
Here's what i expect from them:
`item = Square(5)`
`item.area()`
>>> 20
`item.perimeter()`
>>>20
I tried giving basic value to their arguments (parent, child, parent&child) but still always got wrong result.
Is there anyway?
Each class's constructor can have whatever signature you want; just make sure that the child calls the parent with the appropriate arguments.
class RegularPolygon:
def __init__(self, no_of_sides, side_length):
self.no_of_sides = no_of_sides
self.side_length = side_length
def perimeter(self):
return self.no_of_sides * self.side_length
class Square(RegularPolygon):
def __init__(self, side_length):
RegularPolygon.__init__(self, 4, side_length)
In this case it is probably better to remove those __init__ arguments altogether.
Your Square class just needs to take in the arguments it needs (side_length), and not let users of the class override the no_of_sides of 4.
class Square(RegularPolygon):
def __init__(self, side_length):
super().__init__(no_of_sides=4, side_length=side_length)

Maintaining staticmethod calls if class is renamed

I have a class with a static method which is called multiple times by other methods. For example:
class A:
def __init__(self):
return
#staticmethod
def one():
return 1
def two(self):
return 2 * A.one()
def three(self):
return 3 * A.one()
Method one is a utility function that belongs inside the class but isn't logically an attribute of the class or the class instance.
If the name of the class were to be changed from A to B, do I have to explicitly change every call to method one from A.one() to B.one()? Is there a better way of doing this?
I pondered this question once upon a time and, while I agree that using a refactoring utility is probably the best way to go, as far as I can tell it is technically possible to achieve this behaviour in two ways:
Declare the method a classmethod.
Use the __class__ attribute. Leads to rather messy code, and may well be deemed unsafe or inefficient for reasons I am not aware of(?).
class A:
def __init__(self):
return
#staticmethod
def one():
return 1
#classmethod
def two(cls):
return 2 * cls.one()
def three(self):
return 3 * self.__class__.one()
a = A()
print(a.two())
print(a.three())

Declaring Subclass without passing self

I have an abstract base class Bicycle:
from abc import ABC, abstractmethod
class Bicycle(ABC):
def __init__(self, cadence = 10, gear = 10, speed = 10):
self._cadence = cadence
self._gear = gear
self._speed = speed
#abstractmethod
def ride(self):
pass
def __str__(self):
return "Cadence: {0} Gear: {1} Speed: {2}".format(self._cadence,
self._gear, self._speed)
and a subclass MountainBike:
from Bicycle import Bicycle
class MountainBike(Bicycle):
def __init__(self):
super().__init__(self)
def ride(self):
return "Riding my Bike"
The following code will cause a recursion error, but if I remove self from the super().__init__(self), the call to __str__(self): works.
Question:
I only discovered this error when I implemented the __str__(self):
In Python 3.x when calling the parent constructor from the child with no arguments, is passing self, necessary?
Suppose MountainBike now sets the cadence, gear, speed this means in my subclass the constructor will look like this:
class MountainBike(Bicycle):
def __init__(self, cadence, gear, speed):
super().__init__(cadence,gear,speed)
notice, self isn't being passed in the super because to my knowledge, it can throw the variable assignments off. Is this assumption correct?
self is passed implicitly to the super call, so adding it explicitly sends it twice:
def __init__(self):
super().__init__(self)
That ends up calling Bicycle(self, self), which is the same as Bicycle(self, cadence=self).
Later on, you have probably tried convert your instance to str (e.g. to print it), so this was called:
def __str__(self):
return "Cadence: {0} Gear: {1} Speed: {2}".format(self._cadence,
self._gear, self._speed)
That code tried to convert self._cadence to a string and self._cadence is self because of the previous error, so it continues in an endless recursion (until the recursion exception).
Note that super() takes two forms: with arguments and without arguments, so there are two correct ways to fix the code.
The Python 3 way (without arguments):
def __init__(self):
super().__init__()
The old Python 2 way, which is more explicit:
def __init__(self):
super(MountainBike, self).__init__()
Both do the same, i.e. they give you the bound __init__ method which already has the implicit self.
See also here: https://docs.python.org/3/library/functions.html#super

Calling `super()` in parent class

I'm reading Raymond Hettinger's Python’s super() considered super! About a page in, there's this example:
class Shape:
def __init__(self, shapename, **kwds):
self.shapename = shapename
super().__init__(**kwds)
class ColoredShape(Shape):
def __init__(self, color, **kwds):
self.color = color
super().__init__(**kwds)
cs = ColoredShape(color='red', shapename='circle')
Why is it necessary to call super() in Shape here? My understanding is that this calls object.__init__(**kwds) since Shape implicitly inherits from object.
Even without that statement, we've already
established shapename already in the parent's __init__,
established the child class's color in an explicit method override,
then invoked the parent's __init__ with super() in ColoredShape.
As far as I can tell, dropping this line produces the same behavior & functionality:
class Shape: # (object)
def __init__(self, shapename, **kwds):
self.shapename = shapename
# super().__init__(**kwds)
class ColoredShape(Shape):
def __init__(self, color, **kwds):
self.color = color
super().__init__(**kwds)
def check(self):
print(self.color)
print(self.shapename)
cs = ColoredShape(color='red', shapename='circle')
cs.check()
# red
# circle
What is the purpose of super() within Shape here?
The point is cooperative multiple inheritance. The point of the whole entire article is cooperative multiple inheritance, really.
You look at Shape and you don't see any parents besides object. Sure, but that doesn't mean there aren't any siblings, or anything else on the MRO after Shape. super() isn't just for superclasses; it searches for the next implementation of the method in the method resolution order. For example, one of the later classes in the article is
class MovableColoredShape(ColoredShape, MoveableAdapter):
pass
In this case, Shape.__init__ needs to call super().__init__, or MoveableAdapter.__init__ and all further __init__ calls will be skipped.
I see that #user2357112 has already provided a correct answer. I was working on an example that I'd though I'd leave here because it's pretty much what user2357112 is describing. Consider a mixin class like this:
class PositionMixin:
def __init__(self, x=0, y=0, **kwds):
super().__init__(**kwds)
self.x = x
self.y = y
Let's say you apply that to your ColoredShape class:
class ColoredShape(Shape, PositionMixin):
def __init__(self, color, **kwds):
self.color = color
super().__init__(**kwds)
If Shape doesn't call super.__init__, then when you do this:
myshape = ColoredShape('red', shapename='circle', x=1, y=1)
print(myshape.x, myshape.y)
You get:
Traceback (most recent call last):
File "supertest.py", line 18, in <module>
print (myshape.x, myshape.y)
AttributeError: 'ColoredShape' object has no attribute 'x'
The call to super.__init__ in shape is necessary to call the __init__ method on PositionMixin.

Using setter through decorators RecursionError

I was looking at some link about Python.
https://medium.com/the-renaissance-developer/python-101-object-oriented-programming-part-1-7d5d06833f26
And there are decorators used in there to create(?) properties and a setter method for it. Below is the code:
class Vehicle:
def __init__(self, number_of_wheels, type_of_tank, seating_capacity, maximum_velocity):
self.number_of_wheels = number_of_wheels
self.type_of_tank = type_of_tank
self.seating_capacity = seating_capacity
self.maximum_velocity = maximum_velocity
#property
def number_of_wheels(self):
return self.number_of_wheels
#number_of_wheels.setter
def number_of_wheels(self, number):
self.number_of_wheels = number
And this is the usage from the link as well.
tesla_model_s = Vehicle(4, 'electric', 5, 250)
print(tesla_model_s.number_of_wheels) # 4
tesla_model_s.number_of_wheels = 2 # setting number of wheels to 2
print(tesla_model_s.number_of_wheels) # 2
So, I was trying to understand why should I use it, instead of, you know, directly accessing class variable itself. I tried to run the code but I got RecursionError.
File "C:/Users/Brandon/Desktop/Python/tryit.py", line 16, in number_of_wheels
self.number_of_wheels = number
[Previous line repeated 491 more times]
RecursionError: maximum recursion depth exceeded while calling a Python object
Finally my question is, how can this code work(what is wrong with it maybe?) and more importantly why should I use setter instead of accessing class variable directly?
ps. Any pointers like links to read or keywords for search are welcomed.
The property and attribute should not have the same name, otherwise, the setters and getters will keep calling themselves over and over. Conventionally, you would prepend a leading underscore to privatize (though it's not private) the attribute you're creating a property for.
class Vehicle(object):
def __init__(self, number_of_wheels, type_of_tank, seating_capacity, maximum_velocity):
self._number_of_wheels = number_of_wheels
...
#property
def number_of_wheels(self):
return self._number_of_wheels
#number_of_wheels.setter
def number_of_wheels(self, number):
self._number_of_wheels = number
Also remember to subclass object in Python 2, to make your class work with property.

Categories