
Table of Contents(目次)
Techniques for Mastering Classes
In this lesson, we’ll learn how to design Python classes in a safer and more extensible way. We’ll go through special methods, encapsulation, the @property decorator, and how to use constructors and destructors.
Special (Magic) Methods
Basics of Special Methods
In Python, there’s a mechanism that allows your custom classes to work naturally with built-in operations like print() or ==. These are called special methods (or magic methods).
By defining __str__, you can make print(car) display a readable string, and by defining __eq__, you can compare two objects like car1 == car2.
class Car:
def __init__(self, brand, model):
self.brand = brand
self.model = model
def __str__(self):
return f'{self.brand} {self.model}'
def __eq__(self, other):
return self.brand == other.brand and self.model == other.model
Example Output
car1 = Car('Toyota', 'Corolla')
car2 = Car('Toyota', 'Corolla')
car3 = Car('Tesla', 'Model 3')
print(car1) # Toyota Corolla
print(car3) # Tesla Model 3
print(car1 == car2) # True
print(car1 == car3) # False
Common Types of Special Methods
Special methods used in this example:
__str__: Defines the string representation shown byprint()__eq__: Defines comparison using the==operator
Other frequently used special methods include:
__repr__: Used byrepr()or the interactive console__add__,__sub__: Arithmetic operators__len__: Works withlen()__getitem__,__setitem__: Enable index operations__iter__,__next__: Enable iteration
See the full list of special methods in the official documentation: https://docs.python.org/3.14/reference/datamodel.html#emulating-callable-objects
Encapsulation and Access Control
Some data inside a class should not be accessed directly from outside. By adding _ or __ to a variable name, you can control its accessibility.
_speedis treated as “for internal use” and should not be modified directly.__engine_onis hidden and cannot be accessed directly; it should only be changed viastart_engineorstop_engine.
This ensures safe management of engine states and speed within the Car class.
class Car:
def __init__(self, speed):
self._speed = speed # Internal use only
self.__engine_on = False # Hidden variable
def start_engine(self):
self.__engine_on = True
print(f'Started the engine of {self.brand} {self.model}')
def stop_engine(self):
self.__engine_on = False
print(f'Stopped the engine of {self.brand} {self.model}')
my_car = Car(50)
print(my_car._speed) # 50 (Accessible, but not recommended)
print(my_car.__engine_on) # Raises AttributeError: 'Car' object has no attribute '__engine_on'
Safely Managing Attributes with @property
Using @property allows you to unify the “getter” and “setter” operations for a variable under the same name. For example, you can retrieve and assign values as if handling a variable named speed. Unlike regular variables, you can implement checks during assignment, such as preventing negative values. This enables safe manipulation without directly touching the _speed variable.
- Getter method
- Add
@propertybefore the method definition. - The getter method returns the value.
- Add
- Setter method
- Add
@methodname.setter(in this case,@speed.setter). - Use the same method name as the getter.
- The assigned value is passed as an argument.
- You can add checks (e.g., raising an error if the value is invalid).
- Add
class Car:
def __init__(self, brand, model, speed):
self.brand = brand
self.model = model
self._speed = speed # Internal variable
@property
def speed(self):
# Getter method for retrieving the value
return self._speed
@speed.setter
def speed(self, value):
# Setter method for assigning a value
# Raise an error if the value is negative
if value < 0:
raise ValueError('Speed must be zero or higher.')
else:
self._speed = value
Example Execution
car = Car('Toyota', 'Corolla', 50)
print(car.speed) # 50
car.speed = 80
print(car.speed) # 80
car.speed = -10 # ValueError: Speed must be zero or higher.
Constructors and Destructors
A constructor (__init__) is automatically called when an instance is created and is used for initialization or setup. A destructor (__del__) is automatically called when an instance is destroyed and is used for cleanup or resource release.
For example, in a network class, __init__ might prepare a connection, while __del__ safely closes it.
class NetworkController:
def __init__(self, host, port):
self.host = host
self.port = port
print(f'Preparing connection to {self.host}:{self.port} (constructor)')
def __del__(self):
print(f'Safely disconnected from {self.host}:{self.port} (destructor)')
nc = NetworkController('192.168.1.10', 8080)
del nc
Example Output
Preparing connection to 192.168.1.10:8080 (constructor)
Safely disconnected from 192.168.1.10:8080 (destructor)
The destructor is executed either when you explicitly delete an object using the del statement or when Python automatically deletes the object after a function finishes. Even if you comment out the del nc line, the destructor will still run when the object goes out of scope.
Practical Example: Safe and Extensible Class Design
By handling shared behavior through methods, managing attributes safely with @property, and providing a clear display using __str__, you can design classes that are both safe and extensible.
class Car:
def __init__(self, brand, model, speed):
self.brand = brand
self.model = model
self._speed = speed
self.__engine_on = False
def start_engine(self):
self.__engine_on = True
def stop_engine(self):
self.__engine_on = False
@property
def speed(self):
return self._speed
@speed.setter
def speed(self, value):
# Raise an error if the value is negative
if value < 0:
raise ValueError('Speed must be zero or higher.')
else:
self._speed = value
def __str__(self):
return f'{self.brand} {self.model} ({self._speed}km/h)'






