Introduction
In this chapter, we'll explore the concept of classes in Python.
A class is a blueprint for creating objects, which are instances of the class. Classes define a set of attributes and methods that are common to all objects created from the class.
Class Methods
One of the most important methods in a class is the __init__ method, which is used to initialize the attributes of an object when it is created. Here is an example of a simple class that represents a complex number, with an __init__ method that takes two arguments, real and imaginary, and assigns them to the corresponding attributes of the object:
class ComplexNumber:
def __init__(self, real: float, imaginary: float) -> None:
self.real = real
self.imaginary = imaginary
We can create an instance of this class by calling the class name as if it were a function, passing in the arguments for the __init__ method:
c = ComplexNumber(real=3, imaginary=4)
print(c.real)
print(c.imaginary)
Output:
3
4
In addition to the __init__ method, there are several other special methods that we can define in a class to customize its behaviour. These methods have names that start and end with double underscores, such as __str__, __repr__, and __eq__.
The __str__ method is used to define a human-readable representation of the object and is called when we use the print function or the str function on the object. Here is an example of how we can define the __str__ method for our ComplexNumber class:
class ComplexNumber:
def __init__(self, real: float, imaginary: float) -> None:
self.real = real
self.imaginary = imaginary
def __str__(self) -> str:
return f"{self.real} + {self.imaginary}i"
Now, when we create an instance of the ComplexNumber class and print it, we get a nicely formatted string representation of the complex number:
c = ComplexNumber(real=3, imaginary=4)
print(c)
Output:
3 + 4i
The __repr__ method is similar to the __str__ method but is used to define a more formal, code-like representation of the object. It is called when we use the repr function on the object, or when we enter the object into the interactive interpreter. Here is an example of how we can define the __repr__ method for our ComplexNumber class:
class ComplexNumber:
def __init__(self, real: float, imaginary: float) -> None:
self.real = real
self.imaginary = imaginary
def __str__(self) -> str:
return f"{self.real} + {self.imaginary}i"
def __repr__(self) -> str:
return f"ComplexNumber({self.real}, {self.imaginary})"
Now, when we create an instance of the ComplexNumber class and enter it into the interactive interpreter (example: IDLE), we get a formal representation of the object that shows how it can be constructed:
c = ComplexNumber(real=3, imaginary=4)
c
Output:
ComplexNumber(3, 4)
Other special methods that we can define in a class include __eq__, which is used to compare two objects for equality, __setattr__ and __getattr__, which are used to customize attribute access, and __doc__, which contains the docstring of the class.
Regular Methods, Class Methods, and Static Methods
In addition to these special methods, we can also define regular methods in a class to perform operations on the object. For example, we can define a method to calculate the magnitude of a complex number:
class ComplexNumber:
def __init__(self, real: float, imaginary: float) -> None:
self.real = real
self.imaginary = imaginary
def __str__(self) -> str:
return f"{self.real} + {self.imaginary}i"
def __repr__(self) -> str:
return f"ComplexNumber({self.real}, {self.imaginary})"
def magnitude(self) -> float:
return (self.real ** 2 + self.imaginary ** 2) ** 0.5
We can call this method on an instance of the ComplexNumber class to calculate its magnitude:
c = ComplexNumber(real=3, imaginary=4)
print(c.magnitude())
Output:
5.0
In addition to regular methods, we can also define class methods and static methods in a class. Class methods are methods that are bound to the class and not the instance of the class. They are defined using the @classmethod decorator, and take the class itself as their first argument, rather than the instance. Here is an example of a class method that creates a ComplexNumber object from a polar representation, given the magnitude and angle:
class ComplexNumber:
def __init__(self, real: float, imaginary: float) -> None:
self.real = real
self.imaginary = imaginary
def __str__(self) -> str:
return f"{self.real} + {self.imaginary}i"
def __repr__(self) -> str:
return f"ComplexNumber({self.real}, {self.imaginary})"
def magnitude(self) -> float:
return (self.real ** 2 + self.imaginary ** 2) ** 0.5
@classmethod
def from_polar(cls, magnitude: float, angle: float) -> "ComplexNumber":
real = magnitude * math.cos(angle)
imaginary = magnitude * math.sin(angle)
return cls(real, imaginary)
We can call this class method on the ComplexNumber class itself, rather than on an instance, to create a new ComplexNumber object:
c = ComplexNumber.from_polar(magnitude=5, angle=math.pi / 4)
print(c)
Output:
3.5355339059327373 + 3.5355339059327378i
Static methods are similar to class methods, but they do not take the class or the instance as their first argument. They are defined using the @staticmethod decorator and can be used to define utility functions that are related to the class but do not depend on its state. Here is an example of a static method that calculates the Euclidean distance between two complex numbers:
class ComplexNumber:
def __init__(self, real: float, imaginary: float) -> None:
self.real = real
self.imaginary = imaginary
def __str__(self) -> str:
return f"{self.real} + {self.imaginary}i"
def __repr__(self) -> str:
return f"ComplexNumber({self.real}, {self.imaginary})"
def magnitude(self) -> str:
return (self.real ** 2 + self.imaginary ** 2) ** 0.5
@classmethod
def from_polar(cls, magnitude: float, angle: float) -> "ComplexNumber":
real = magnitude * math.cos(angle)
imaginary = magnitude * math.sin(angle)
return cls(real, imaginary)
@staticmethod
def distance(c1: "ComplexNumber", c2: "ComplexNumber") -> float:
return ((c1.real - c2.real) ** 2 + (c1.imaginary - c2.imaginary) ** 2) ** 0.5
We can call this static method on the ComplexNumber class itself, or on an instance, to calculate the distance between two complex numbers:
c1 = ComplexNumber(real=3, imaginary=4)
c2 = ComplexNumber(real=6, imaginary=8)
print(ComplexNumber.distance(c1=c1, c2=c2))
Output:
5.0
Built-in Functions
In addition to defining methods, we can also use several built-in functions to interact with the attributes of an object. The setattr function can be used to set the value of an attribute, the getattr function can be used to get the value of an attribute, and the hasattr function can be used to check if an object has a specific attribute. Here is an example of how we can use these functions with our ComplexNumber class:
c = ComplexNumber(real=3, imaginary=4)
print(getattr(c, "real"))
setattr(c, "real", 5)
print(c.real)
print(hasattr(c, "imaginary"))
Output:
3
5
True
Conclusion
In summary, classes in Python provide a powerful mechanism for defining custom data types, with their attributes and methods. By defining special methods, we can customize the behaviour of the class, and by defining regular methods, class methods, and static methods, we can define operations that can be performed on the objects. We can also use built-in functions to interact with the attributes of the objects.
Top comments (0)