Constructor overloading in python with default arguments - python

I defined a class in python as following.
class myclass:
def __init__(self,edate,fdate=""):
print("Constructors with default arguments...")
def __init__(self):
print("Default Constructor")
I created an object for this class,
obj = myclass()
It works fine. And I expected the following object creation will work,
obj1 = myclass("01-Feb-2019")
But It throws an error saying,
Traceback (most recent call last):
File "class.py", line 9, in <module>
obj = myclass("01-Feb-2019")
TypeError: __init__() takes 1 positional argument but 2 were given
But If I change the definition of the class as follows,
class myclass:
def __init__(self):
print("Default Constructor")
def __init__(self,edate,fdate=""):
print("Constructors with default arguments...")
Now obj1 = myclass("01-Feb-2019") works. But obj = myclass() throws the following error,
Traceback (most recent call last):
File "class.py", line 10, in <module>
obj = myclass()
TypeError: __init__() missing 1 required positional argument: 'edate'
Could we define a constructor overloading in Python? Could I define a constructor which accepts the empty argument as well as one argument?

As others have written, Python does not support multiple constructors *). However you can emulate them easily as follows:
class MyClass:
def __init__(self, edate=None, fdate=""):
if edate:
print("Constructors with default arguments...")
else:
print("Default Constructor")
Then you can do
obj1 = MyClass("01-Feb-2019")
=> Constructors with default arguments...
obj2 = MyClass()
=> Default Constructor
*) except if you go for multi-dispatch - making use of Python's powerful inspection features
Note that assigning default values in the method declaration should be done very reluctantly as it may work differently than one thinks coming from another language. The proper way to define default values would be using
None and assign default values like this
class MyClass:
def __init__(self, edate=None, fdate=None):
if edate:
fdate = "" if fdate is None else fdate
...

Unlike Java or C#, you cannot define multiple constructors. However, you can define a default value if one is not passed.

Python doesn't have multiple constructors - see Multiple constructors in python?

Use multipledispatch(link) module to overload methods
Installation:
python3 -m pip install multipledispatch
A Dispatcher object stores and selects between different
implementations of the same abstract operation. It selects the
appropriate implementation based on a signature, or list of types. We
build one dispatcher per abstract operation.
The dispatch decorator hides the creation and manipulation of
Dispatcher objects from the user.
Using #dispatch wrapper to overload methods:
from multipledispatch import dispatch
class Shape:
#dispatch(int)
def __init__(self, length):
self.length = length
self.dimension = 1
#dispatch(float)
def __init__(self, length):
self.length = length
self.dimension = 1
#dispatch(int, int)
def __init__(self, length, width):
self.length = length
self.width = width
self.dimension = 2
self._getArea = lambda: self.length * self.width
#dispatch(float, float)
def __init__(self, length, width):
self.length = length
self.width = width
self.dimension = 2
self._getArea = lambda: self.length * self.width
#dispatch(int, int, int)
def __init__(self, length, width, height):
self.length = length
self.width = width
self.height = height
self.dimension = 3
self._getArea = lambda: 2 * (self.length * self.width + self.length * self.height +
self.width * self.height)
self._getVolume = lambda: self.length * self.width * self.height
#dispatch(float, float, float)
def __init__(self, length, width, height):
self.length = length
self.width = width
self.height = height
self.dimension = 3
self._getArea = lambda: 2 * (self.length * self.width + self.length * self.height +
self.width * self.height)
self._getVolume = lambda: self.length * self.width * self.height
def getLength(self):
return self.length
def getArea(self):
if self.dimension == 1:
raise Exception(f"Shapes with {self.dimension} dimensions have no area")
return self._getArea()
def getVolume(self):
if self.dimension < 3:
raise Exception(f"Shapes with {self.dimension} dimensions have no volume")
return self._getVolume()
if __name__ == "__main__":
line = Shape(3)
curve = Shape(5.5)
rectangle = Shape(5.5, 100.1)
cuboid = Shape(23, 22, 10)
print(line.getLength())
print(curve.getLength())
print(rectangle.getArea())
print(cuboid.getVolume(), cuboid.getArea())
#will raise Exception since rectangles don't have volume
print(rectangle.getVolume())
Output:
3
5.5
550.55
5060 1912
Traceback (most recent call last):
....
Exception: Shapes with 2 dimensions have no volume

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)

'str' object not callable error How do I make my classes overload?

I am trying to overload my operators in this class I made. I made this plant class and I am trying to print it out. I am still pretty new to this so my understanding probably isn't the best but any help you can provide will be amazing. Here is my mmain.py:
import Fungi_
import Animal
import inheritance
import Monera
import Plant
grass = Plant.Ferns("Good", "large")
print(grass.scent() + grass.size())
Here is my class and subclasses,
from inheritance_assignment import living_thing
class Plant(living_thing):
def __init__(self, type):
self.type = type
class Equiseta(Plant):
def __init__(self, type, color):
self.type = type
self.color = color
class Lycopodia(Plant):
def __init__(self, color, size):
self.color = color
self.size = size
class Gymnosperms(Plant):
def __init__(self, size, color):
self.size = size
self.color = color
class Anginosperm(Plant):
def __init__(self, length, color):
self.length = length
self.color = color
class Ferns(Plant):
def __init__(self, scent, size):
self.scent = scent
self.size = size
class Mosses(Plant):
def __init__(self, wetness, size):
self.wetness = wetness
self.size = size
Here is the error it gives me
Traceback (most recent call last):
File "main.py", line 8, in <module>
print(grass.scent() + grass.size())
TypeError: 'str' object is not callable
Any Information will be grand! Thanks in advance.
Classes in python have methods (functions) and attributes (variables). In your Ferns class you defined size and scent as attributes, but you are attempting to access them in the print statement as if they are methods by appending () to their names. Assuming you want to concatenate the strings associated with scent and size then this syntax should work:
print(grass.scent + grass.size)
Goodlarge

_init__() takes 1 positional argument but 2 were given

I'm testing some code for a course OOP, but I run into a problem. I am programming a circle and a cylinder, with the circle class also in the init of the cylinder. I have 2 arguments for the cylinder, but when I give 2 arguments, it's said that I only need 1 and if I give one argument than it gaves the output one is missing.
with the variable a it works, but the error is in variable b. What do I wrong
import math
class CCircle:
def __init__(self):
self._radius = 0
#property
def area(self):
return self._radius**2 * math.pi
#area.setter
def area(self, value):
self._radius = math.sqrt(value / math.pi)
#property
def circumference(self):
return self._radius * 2 * math.pi
#circumference.setter
def circumference(self, value):
self._radius = value / (2 * math.pi)
class CCylinder:
def __init__(self, radius, height):
self._circle = CCircle(radius)
self._height = height
#property
def circumference(self):
return self._circle.circumference
#property
def ground_area(self):
return self._circle.area
#property
def total_area(self):
return self._circle.area + self._height * self._circle.circumference
#property
def volume(self):
return self._circle.area * self._height
a = CCircle()
b = CCylinder(1,4)
init() takes 1 positional argument but 2 were given
You should have your CCircle class start like this
class CCircle:
def __init__(self, radius=0):
self._radius = radius
so that you get the default radius of 0 that you seem to want, but can also initialize it with a radius value like you're doing in the init code of your CCylinder class.
The problem is with this line:
self._circle = CCircle(radius)
but __init__ for the CCircle class does not take any arguments (except for self) so this is causing the error.
You might have had a package folder locally at the place where the .py file is , delete that and that should solve your issue

Using **kwargs on super() gives me Attribute Error

Just trying to create a simple toy example to calculate the surface area of a pyramid:
class Rectangle:
def __init__(self, length, width, **kwargs):
self.length = length
self.width = width
#super().__init__(**kwargs)
def area(self):
return self.length * self.width
def perim(self):
return 2 * (self.length + self.width)
class Square(Rectangle):
def __init__(self, length, **kwargs):
super().__init__(length = length, width = length, **kwargs)
class Triangle:
def __init__(self, base, height, **kwargs):
self.base = base
self.height = height
super().__init__(**kwargs)
def tri_area(self):
return 0.5 * self.base * self.height
class Pyramid(Square, Triangle):
def __init__(self, base, slant, **kwargs):
kwargs["height"] = slant
kwargs["length"] = base
super().__init__(base = base, **kwargs)
def surf_area(self):
return super().area() + 4 * super().tri_area()
p = Pyramid(2,4)
p.surf_area()
But this gives me an error of
AttributeError Traceback (most recent call last)
<ipython-input-154-d97bd6ab2312> in <module>
1 p = Pyramid(2,4)
----> 2 p.surf_area()
<ipython-input-153-457095747484> in surf_area(self)
6
7 def surf_area(self):
----> 8 return super().area() + 4 * super().tri_area()
<ipython-input-151-8b1d4ef9dca9> in tri_area(self)
5 super().__init__(**kwargs)
6 def tri_area(self):
----> 7 return 0.5 * self.base * self.height
AttributeError: 'Pyramid' object has no attribute 'base'
The online resources don't seem to give much of a conceptual understanding of **kwargs too well (or they're written in too much a labrintyine manner for a beginner). Does this somehow have to do with the fact that **kwargs as an iterable need to be exhausted completely before going to the next call?
I can sort of understand why you'd be confused. The main trick to realise is this: absolute bottom line, kwargs is not something magical. It is just a dictionary holding key value pairs. If a call requires more positional arguments than provided, it can look into keyword arguments and accept some values. However, kwargs does not invoke some magic that associates all names provided as a self.<some_name_here> .
So, first to just get a visual understanding of what's going on, what you should do when you don't understand a piece of code is making sure it runs how you think it does. Let's add a couple print statements and see what's happening.
Version 1:
class Rectangle:
def __init__(self, length, width, **kwargs):
self.length = length
self.width = width
print(f"in rectangle. length = {self.length}, width = {self.width}")
def area(self):
return self.length * self.width
class Square(Rectangle):
def __init__(self, length, **kwargs):
print("in square")
super().__init__(length = length, width = length, **kwargs)
class Triangle:
def __init__(self, base, height, **kwargs):
print("in triangle")
self.base = base
self.height = height
super().__init__(**kwargs)
def tri_area(self):
return 0.5 * self.base * self.height
class Pyramid(Square, Triangle):
def __init__(self, base, slant, **kwargs):
print("in pyramid")
kwargs["height"] = slant
kwargs["length"] = base
super().__init__(base = base, **kwargs)
def surf_area(self):
print(f"area : {super().area()}")
print(f"tri_area : {super().tri_area()}")
return super().area() + 4 * super().tri_area()
p = Pyramid(2,4)
p.surf_area()
Output:
in pyramid
in square
in rectangle. length = 2, width = 2
area : 4
#and then an error, note that it occurs when calling super().tri_area()
#traceback removed for brevity.
AttributeError: 'Pyramid' object has no attribute 'base'
I suspect this already breaks some assumptions you had about how the code runs. Notice that the triangle's init was never called. But let's get rid of the parts that work fine, and add an additional print statement. I will also take the liberty of calling it with a different value for first argument, something that pops out easier.
Version 2:
class Rectangle:
def __init__(self, length, width, **kwargs):
self.length = length
self.width = width
print(f"in rectangle. length = {self.length}, width = {self.width}")
print(f"kwargs are: {kwargs}")
class Square(Rectangle):
def __init__(self, length, **kwargs):
print("in square")
super().__init__(length = length, width = length, **kwargs)
class Triangle:
def __init__(self, base, height, **kwargs):
print("in triangle")
self.base = base
self.height = height
super().__init__(**kwargs)
def tri_area(self):
return 0.5 * self.base * self.height
class Pyramid(Square, Triangle):
def __init__(self, base, slant, **kwargs):
print("in pyramid")
kwargs["height"] = slant
kwargs["length"] = base
super().__init__(base = base, **kwargs)
def surf_area(self):
print(f"tri_area : {super().tri_area()}")
return super().tri_area()
p = Pyramid(10000,4)
p.surf_area()
Output:
in pyramid
in square
in rectangle. length = 10000, width = 10000
kwargs are: {'base': 10000, 'height': 4}
#error with traceback
AttributeError: 'Pyramid' object has no attribute 'base'
Bottom line: the kwargs holds a key with the name base, but this has no relation to self.base. However, my recommendation is to get rid of the whole class structure, and spend some time playing around with any basic function, get rid of the extra stuff.
Say, a demonstration:
def some_func(a, b, **kwargs):
print(a, b)
print(kwargs)
some_func(1, 2)
some_func(1, 2, c=42)
some_func(a=1, c=42, b=2)
def other_func(a, b, **look_at_me):
print(a, b)
print(look_at_me)
other_func(1, 2)
other_func(1, 2, c=42)
other_func(a=1, c=42, b=2)
These two chunks produce the same outputs. No magic here.
Output:
1 2
{}
1 2
{'c': 42}
1 2
{'c': 42}
When you added the classes into the mix, and inheritance, there's too many things happening at once. It is easier to miss what happens, so it's a good idea to use smaller code samples.

Multiple constructors in python

How to combine the following 2 classes into one class, Rectangle, so that a Rectangle object can be created either by rect = Rectangle(side_a, side_b) or rect = Rectangle(side_a, area)?
class Rectangle1:
def __init__(self, side_a, side_b):
self.side_a = side_a
self.side_b = side_b
self.area = self.side_a * self.side_b
class Rectangle2:
def __init__(self, side_a, area):
self.side_a = side_a
self.area = area
self.side_b = self.area / side_a
As demonstrated here.
class Rectangle:
def __init__(self, a, b):
""" Create a new rectangle with sides of length a and b.
"""
self.side_a = side_a
self.side_b = side_b
self.area = self.side_a * self.side_b
#classmethod
def from_sides(cls, a, b):
return cls(a, b)
#classmethod
def from_area(cls, a, o):
return cls(a, o/a)
You can then create rectangles as
r1 = Rectangle.from_sides(s1, s2)
r2 = Rectangle.from_area(s1, a)
You cannot overload methods with methods of the same name. Well, you can, but only the last one is then visible.
The other option is keyword-only arguments.
With Python 3, you could write:
class Rectangle:
def __init__(self, side_a, *, side_b=None, area=None):
self.side_a = side_a
if side_b is None and area is None:
raise Exception("Provide either side_b or area")
if side_b is not None and area is not None:
raise Exception("Provide either side_b or area, not both")
if side_b is not None:
self.side_b = side_b
self.area = self.side_a * self.side_b
else:
self.area = area
self.side_b = self.area / side_a
using * in the middle forces the user to use keyword argument passing from that point, not allowing positionnal, which prevents the mistakes. And the (rather clumsy) manual checking for None logic ensures that one and only one keyword parameter is passed to the constructor. The inside is complex, but the interface is safe to use, that's the main point here.
r = Rectangle(10,area=20)
r2 = Rectangle(10,side_b=20)
r3 = Rectangle(10,20) # doesn't run, need keyword arguments
This may not be what you're looking for, but here's what I came up with:
class Rectangle:
def __init__(self, side_a, side_b = None, area = None):
self.side_a = side_a
if area == None:
self.area = side_a * side_b
self.side_b = side_b
else:
self.side_b = area / side_a
self.area = area
You could do
class Combined:
def __init__(self, a, b=None, area=None):
self.a = a
self.area = self.a * b if b else area
self.b = self.area / self.a
I would share a basic example to use default as well as parametrized constructor in python. You need to declare as well as assign variable in function definition itself for default constructor.
class Person:
def __init__(self,name="",age=0):
self.name=name
self.age=age
def __int__(self,name,age):
self.name=name
self.age=age
def showdetails(self):
print("\nName is "+str(self.name)+" and age is "+str(self.age))
p1=Person("Sam",50)
p1.showdetails()
p2=Person()
p2.showdetails()
Output:
Name is Sam and age is 50
Name is and age is 0

Categories

Resources