DEV Community

≀Paulo Portela
≀Paulo Portela

Posted on

Introduction to Classes in Python

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode
Output:
3
4
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode
Output:
3 + 4i
Enter fullscreen mode Exit fullscreen mode

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})"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
Output:
ComplexNumber(3, 4)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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())
Enter fullscreen mode Exit fullscreen mode
Output:
5.0
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode
Output:
3.5355339059327373 + 3.5355339059327378i
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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))
Enter fullscreen mode Exit fullscreen mode
Output:
5.0
Enter fullscreen mode Exit fullscreen mode

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"))
Enter fullscreen mode Exit fullscreen mode
Output:
3
5
True
Enter fullscreen mode Exit fullscreen mode

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)