OOP پیشرفته – Inheritance و MRO
در این فصل با مفاهیم پیشرفته شیگرایی در پایتون آشنا میشویم: وراثت چندگانه (Multiple Inheritance)، MRO (Method Resolution Order)، super()، Mixin classes و الگوهای پیشرفته OOP که در پروژههای بزرگ استفاده میشوند.
در این فصل با مفاهیم پیشرفته شیگرایی در پایتون آشنا میشویم: وراثت چندگانه (Multiple Inheritance)، MRO (Method Resolution Order)، super()، Mixin classes و الگوهای پیشرفته OOP که در پروژههای بزرگ استفاده میشوند.
مرور سریع وراثت ساده
قبل از ورود به مباحث پیشرفته، یک مرور سریع روی وراثت ساده:
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "صدای حیوان"
class Dog(Animal):
def speak(self):
return f"{self.name} میگوید: واق!"
dog = Dog("رکس")
print(dog.speak()) # رکس میگوید: واق!
super() – کلید وراثت حرفهای
تابع super() به ما اجازه میدهد متدهای کلاس والد را فراخوانی کنیم بدون اینکه نام آن را به صراحت بنویسیم:
class Vehicle:
def __init__(self, brand, model):
self.brand = brand
self.model = model
def info(self):
return f"{self.brand} {self.model}"
class Car(Vehicle):
def __init__(self, brand, model, doors):
super().__init__(brand, model) # فراخوانی __init__ والد
self.doors = doors
def info(self):
# افزودن به اطلاعات والد
return f"{super().info()} با {self.doors} در"
car = Car("پراید", "111", 4)
print(car.info()) # پراید 111 با 4 در
super().__init__() در ابتدای __init__ کلاس فرزند استفاده کنید تا والد بهدرستی مقداردهی شود.
وراثت چندگانه (Multiple Inheritance)
پایتون وراثت از چند کلاس را پشتیبانی میکند:
class Flyable:
def fly(self):
return f"{self.name} پرواز میکند"
class Swimmable:
def swim(self):
return f"{self.name} شنا میکند"
class Duck(Flyable, Swimmable):
def __init__(self, name):
self.name = name
donald = Duck("دانلد")
print(donald.fly()) # دانلد پرواز میکند
print(donald.swim()) # دانلد شنا میکند
MRO – Method Resolution Order
وقتی چند کلاس والد وجود دارد، پایتون چطور تصمیم میگیرد متد را از کدام یک فراخوانی کند؟ پاسخ: MRO (الگوریتم C3 Linearization).
class A:
def method(self):
return "A"
class B(A):
def method(self):
return "B"
class C(A):
def method(self):
return "C"
class D(B, C):
pass
d = D()
print(d.method()) # B (از چپ به راست)
print(D.__mro__)
# (D, B, C, A, object)
print(D.mro()) # شکل لیست
# [D, B, C, A, object]
مثال Diamond Inheritance
یکی از مشکلات کلاسیک وراثت چندگانه، مسئله الماس است:
class Animal:
def __init__(self):
print("Animal.__init__")
super().__init__()
class Mammal(Animal):
def __init__(self):
print("Mammal.__init__")
super().__init__()
class WingedAnimal(Animal):
def __init__(self):
print("WingedAnimal.__init__")
super().__init__()
class Bat(Mammal, WingedAnimal):
def __init__(self):
print("Bat.__init__")
super().__init__()
bat = Bat()
# Bat.__init__
# Mammal.__init__
# WingedAnimal.__init__
# Animal.__init__
print(Bat.__mro__)
# (Bat, Mammal, WingedAnimal, Animal, object)
به لطف MRO، Animal.__init__ فقط یک بار اجرا میشود (نه دو بار).
Mixin Classes
Mixin یک کلاس کوچک است که فقط برای ترکیب با کلاسهای دیگر طراحی شده و خودش بهتنهایی استفاده نمیشود. الگوی بسیار رایج در Django و Flask:
class JSONSerializableMixin:
"""قابلیت تبدیل به JSON اضافه میکند"""
def to_json(self):
import json
return json.dumps(self.__dict__, ensure_ascii=False)
class LoggableMixin:
"""قابلیت log اضافه میکند"""
def log(self, message):
print(f"[{self.__class__.__name__}] {message}")
class TimestampMixin:
"""قابلیت زمانگذاری اضافه میکند"""
def __init__(self, *args, **kwargs):
from datetime import datetime
self.created_at = datetime.now().isoformat()
super().__init__(*args, **kwargs)
class User(TimestampMixin, JSONSerializableMixin, LoggableMixin):
def __init__(self, name, email):
super().__init__()
self.name = name
self.email = email
user = User("علی", "ali@example.com")
user.log("کاربر ساخته شد")
print(user.to_json())
# {"created_at": "2026-05-01T...", "name": "علی", "email": "ali@example.com"}
کلاسهای انتزاعی (ABC)
ماژول abc برای تعریف کلاسهای انتزاعی (Abstract Base Classes) استفاده میشود:
from abc import ABC, abstractmethod
class Payment(ABC):
"""کلاس انتزاعی - نمیتوان از آن instance ساخت"""
@abstractmethod
def pay(self, amount):
"""متد انتزاعی - باید در فرزندان پیادهسازی شود"""
pass
@abstractmethod
def refund(self, amount):
pass
def confirm(self):
"""متد عادی که در همه فرزندان مشترک است"""
return "تراکنش تایید شد"
class ZarinPalPayment(Payment):
def pay(self, amount):
return f"پرداخت {amount} از طریق زرینپال"
def refund(self, amount):
return f"بازگشت {amount} از زرینپال"
# payment = Payment() # خطا! نمیتوان از کلاس abstract instance ساخت
zp = ZarinPalPayment()
print(zp.pay(50000)) # پرداخت 50000 از طریق زرینپال
print(zp.confirm())
صفات کلاس vs نمونه
class Counter:
count = 0 # صفت کلاس - بین همه نمونهها مشترک
def __init__(self, name):
self.name = name # صفت نمونه
Counter.count += 1 # افزایش شمارنده کلاس
@classmethod
def get_count(cls):
return cls.count
c1 = Counter("اول")
c2 = Counter("دوم")
c3 = Counter("سوم")
print(Counter.count) # 3
print(Counter.get_count()) # 3
__slots__ – بهینهسازی حافظه
به طور پیشفرض، هر شیء پایتون یک __dict__ دارد که صفات را در آن ذخیره میکند. با __slots__ میتوان این را غیرفعال کرد و حافظه کمتری مصرف کرد:
class Point:
__slots__ = ("x", "y") # فقط این صفات مجاز
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(3, 4)
print(p.x, p.y) # 3 4
# p.z = 5 # خطا! AttributeError
# مقایسه حافظه
import sys
class Normal:
def __init__(self, x):
self.x = x
n = Normal(1)
p = Point(1, 2)
print(sys.getsizeof(n.__dict__)) # ~232 bytes
# p اصلاً __dict__ ندارد
@dataclass – کلاسهای دادهمحور
دکوراتور dataclass از پایتون 3.7 برای ساخت سریع کلاسهای دادهای:
from dataclasses import dataclass, field
from typing import List
@dataclass
class Product:
name: str
price: float
tags: List[str] = field(default_factory=list)
discount: float = 0.0
def final_price(self):
return self.price * (1 - self.discount)
p = Product("فرش دستباف", 5_000_000, tags=["کاشان", "پشمی"], discount=0.1)
print(p)
# Product(name='فرش دستباف', price=5000000, tags=['کاشان', 'پشمی'], discount=0.1)
print(p.final_price()) # 4500000.0
frozen dataclass – شیء غیرقابل تغییر
@dataclass(frozen=True)
class Coordinate:
lat: float
lng: float
c = Coordinate(35.6892, 51.3890)
# c.lat = 36 # خطا! FrozenInstanceError
# قابل استفاده در set و dict (hashable است)
points = {Coordinate(1, 2), Coordinate(3, 4)}
بهترین شیوهها
- همیشه از
super().__init__()در کلاسهای فرزند استفاده کنید - ترتیب کلاسهای والد اهمیت دارد (چپ به راست در MRO)
- برای ترکیب قابلیتها، Mixin بهتر از وراثت چندگانه عمیق است
- برای کلاسهای ساده دادهای، از
@dataclassاستفاده کنید - اگر کلاس دادهای زیاد میسازید،
__slots__حافظه را کاهش میدهد - کلاسهای انتزاعی (ABC) برای تعریف interface استفاده کنید
جمعبندی
- وراثت چندگانه قابلیت ترکیب چند کلاس را میدهد
- MRO ترتیب جستجوی متدها در زنجیره وراثت است
- super() برای فراخوانی متدهای والد بدون نام صریح
- Mixin برای افزودن قابلیتهای کوچک قابل استفاده مجدد
- @dataclass و __slots__ برای کدهای تمیزتر و سریعتر