Encapsulation
Encapsulation refers to the bundling of data and methods within a single unit, typically a class. This concept involves hiding internal details from external access, ensuring that certain attributes or functions remain inaccessible outside their defined scope.
The primary motivation for encapsulation is to protect sensitive information by restricting direct access to internal components. This approach enhances security and prevents unintended interference with the object's state.
Inheritance
Inheritance enables the creation of new classes based on existing ones. The newly created class is known as a subclass or derived class, while the original is referred to as a superclass or base class. A subclass inherits all public and protected members of its parent class.
Inheritance addresses redundancy among classes rather than objects, allowing shared functionality to be defined once in a base class and reused across multiple derived classes.
In Python 2, classes are classified into classic and new-style depending on whether they inherit from object. However, Python 3 treats all classes as new-style by default, eliminating the distinction between classic and new-style classes.
The method resolution order (MRO) determines how methods are looked up when invoked. Python uses the C3 linearization algorithm to compute MRO, which ensures consistent behavior according to three rules:
- Subclasses are checked before their parents
- Parents are checked in the order listed in the inheritance tuple
- If multiple valid choices exist, the first parent is selected
When performing attribute lookup, Python follows the MRO sequence starting from the instance, then through the class hierarchy.
Single Inheritance
Single inheritance occurs when a class inherits from exactly one parenet class:
class Base:
pass
class Derived(Base):
pass
Attribute resolution in single inheritance proceeds in this order:
- Instance namespace
- Class namespace of the instance’s class
- Parent class namespace
Multiple Inheritance
Multiple inheritance allows a class to inherit from more than one parent class:
class Parent1:
pass
class Parent2:
pass
class Child(Parent1, Parent2):
pass
In Python 2, classic classes followed a depth-first search pattern for attribute resolution. In contrast, Python 3 uses breadth-first search with new-style classes.
For non-diamond inheritance patterns, the MRO follows a predictable sequence. For example:
class E:
def method(self):
print('E')
class F:
def method(self):
print('F')
class B(E):
def method(self):
print('B')
class C(F):
def method(self):
print('C')
class D:
def method(self):
print('D')
class A(B, C, D):
pass
print(A.mro())
# Output: [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.F'>, <class '__main__.D'>, <class 'object'>]
Super and MRO Usage
The super() function provides access to inherited methods from a parent class. It respects the MRO and ensures correct method resolution:
class Parent:
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
class Chinese(Parent):
def __init__(self, name, age, gender, money):
super().__init__(name, age, gender)
self.money = money
def speak_english(self):
print(f'{self.name} has {self.money} RMB')
person = Chinese('qwe', 12, 'male', '1233')
person.speak_english()
To inspect the MRO of a class:
class A:
def method(self):
super().method()
class B:
def method(self):
print('B')
class C(A, B):
pass
print(C.mro())
# [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
Polymorphism
Polymorphism means that a single interface can represent different types. It allows objects of different types to be treated uniformly through a common interface.
Abstract classes define contracts that subclasses must fulfill:
import abc
class Animal(metaclass=abc.ABCMeta):
@abc.abstractmethod
def speak(self):
pass
@abc.abstractmethod
def sound(self):
pass
# This would raise an error if instantiated
# animal = Animal()
The duck typing principle emphasizes behavior over type. Any object implementing required methods can be used interchangeably:
class Person(Animal):
def speak(self):
pass
def sound(self):
pass
class Dog(Animal):
def speak(self):
pass
def sound(self):
pass
class Pig(Animal):
def speak(self):
pass
def sound(self):
pass
# Polymorphic function
def make_animal_speak(animal):
return animal.speak()
# Works with any animal-like object
make_animal_speak(Person())
make_animal_speak(Dog())
make_animal_speak(Pig())
Derivation
Derived classes can introduce new attributes and override parent methods. Attribute lookup prioritizes child attributes over parent ones:
class Base:
planet = 'Earth'
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
class Chinese(Base):
nation = 'China'
def __init__(self, name, age, gender, money):
super().__init__(name, age, gender)
self.money = money
def speak_english(self):
print(f'{self.name} has {self.money} RMB')
person = Chinese('qwe', 12, 'male', '1233')
person.speak_english()
Using super() ensures proper method chaining through the MRO. While both explicit parent calls and super() achieve similar results, the latter is preferred in modern Python due to better maintainability and support for complex inheritance hierarchies.