Dunder methods ( double underscore methods ) are special methods surrounded by double underscores. They are predefined methods that can be used when creating custom objects. The example below illustrates what can be done with these methods.
class Point:
def __init__(self, x, y, name = 'P'):
self.x = x
self.y = y
self.name = name
def __setattr__(self, name, value):
self.__dict__[name] = value
def __repr__(self):
return f"({self.name}, x = {self.x}, y = {self.y})"
def __str__(self):
return f"({self.name} has coordinates x = {self.x}, y = {self.y})"
def __eq__(self,other):
if isinstance(other, Point):
return self.x == other.x and self.y == other.y
else:
return False
def __hash__(self):
return hash((self.x, self.y))
class Polygon:
def __init__(self, *points):
self.points = list(points)
def __iter__(self):
return iter(self.points)
def __len__(self):
return len(self.points)
def __dir__(self):
return ['__iter__','__add__','__len__','__getitem__']
def __bool__(self):
return len(self) > 1
def __add__(self, point:Point):
if isinstance(point, Point):
self.points.append(point)
else:
raise TypeError("Only instances of Point can be added")
def __getitem__(self, index):
return self.points[index]
def __setitem__(self, index, point:Point):
if isinstance(point, Point):
self.points[index] = point
else:
raise TypeError("Only instances of Point can be added")
def __delitem__(self, index):
del self.points[index]
def __repr__(self) :
point_list = ', '.join(repr(point) for point in self.points)
return f"Polygon([{point_list}])"
class Triangle(Polygon):
def __init__(self, point1:Point, point2:Point, point3:Point, name='Triangle'):
super().__init__(point1, point2, point3)
Now this illustrates how those objects and methods can be used :
# __init__
A = Point(1, 1, 'point A')
B = Point(2, 1, 'point_B')
C = Point(0, 2, 'point_C')
D = Point(0, 0, 'point_D')
polygon_1 = Polygon(A,B,C)
#__setattr__
__setattr__(name = 'color', value = 'blue')
print(A.color)
# __repr__ and __str__
print(A.__repr__())
print(A.__str__())
#__eq__
print(A.__eq__(B))
#__hash__
my_dict={A:"value for A",B:'value for B'}
#__iter__
for point in polygon_1:
print(point.x)
#__len__
print(len(polygon_1))
#__bool__
print(polygon_1.__bool__())
#__add__
polygon_1.__add__(C)
#__getitem __
print(polygon_1[0].x)
#__delitem__
polygon_1.__delitem__(2)
#__setitem__
polygon_1.__setitem__(2, D)
#__dir__
print(dir(polygon_1))
# inheritance
Tr1=Triangle(A,B,C)
print(len(Tr1))
This is how to document a class and its methods :
class Point:
"""A class representing a point in 2D space.
This class defines a point with x and y coordinates and an optional name.
Args:
x (float): The x-coordinate of the point.
y (float): The y-coordinate of the point.
name (str): An optional name for the point (default is 'point').
Attributes:
x (float): The x-coordinate of the point.
y (float): The y-coordinate of the point.
name (str): The name of the point.
"""
def __init__(self, x: float, y: float, name: str = 'point'):
"""Initialize a point .
Args:
x (float): The x-coordinate of the point.
y (float): The y-coordinate of the point.
name (str): An optional name for the point (default is 'point').
"""
self.x = x
self.y = y
self.name = name
@property in Python is a decorator that allows you to define a method as an attribute, providing a way to control how attributes are accessed and modified
Among other things, the `@property` decorator allows for:
class Point:
def __init__(self, x, y, name = 'P'):
self._x = x
self.y = y
@property
def x(self):
print('x accessed')
return self._x
@x.setter
def x(self,x):
if isinstance(x,int):
print('x modified')
self._x = x
else :
print('integer expected, x not modified')
@property
def distance_to_0(self) :
return np.sqrt(self._x**2 + self.y**2)
P = Point(2,4)
P.x = 3
P.x
print(P.x)
print(P.distance_to_0)
# prints :
x modified
x accessed
x accessed
3
5.0