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)