سازنده __init__، متدها و وراثت در پایتون

🗓 آخرین بهروزرسانی:
در درس قبل با کلاس و شیء آشنا شدیم؛ اما ویژگیهای هر شیء را پس از ساخت بهصورت دستی مقداردهی میکردیم. در برنامههای واقعی، این روش تکراری و خطاپذیر است. سازنده __init__ کمک میکند هنگام ساخت هر شیء، دادههای اولیه آن را بهشکل منظم دریافت کنیم. متدها نیز رفتارهای مرتبط را داخل کلاس قرار میدهند و وراثت امکان استفاده مجدد و توسعه کلاسهای موجود را فراهم میکند.
این مطلب، درس هفدهم از دوره رایگان پایتون سطح متوسط است. در درس قبلی مفهوم Class، Object، ویژگیهای کلاسی و نمونهای و هویت اشیاء را یاد گرفتیم. اکنون کلاسهای کاربردیتری میسازیم که داده و رفتار را کنار یکدیگر نگه میدارند.
مشکل مقداردهی دستی ویژگیها
در روش ساده درس قبل، برای هر محصول چند خط تکراری مینوشتیم:
class Product:
pass
product_1 = Product()
product_1.name = "Keyboard"
product_1.price = 1200000
product_1.stock = 8
product_2 = Product()
product_2.name = "Mouse"
product_2.price = 650000
product_2.stock = 12این روش چند مشکل دارد:
- ممکن است یک ویژگی فراموش شود.
- ممکن است نام ویژگی اشتباه نوشته شود.
- ساخت تعداد زیادی شیء طولانی و تکراری میشود.
- اعتبارسنجی دادهها در یک محل مشخص انجام نمیشود.
- ساختار اشیاء یکسان تضمین نمیشود.
برای حل این مشکلات از متد ویژه __init__ استفاده میکنیم.
سازنده __init__ چیست؟
__init__ متدی ویژه است که پس از ساختهشدن یک نمونه جدید، برای مقداردهی اولیه آن اجرا میشود. این متد معمولاً ویژگیهای ضروری شیء را دریافت و ذخیره میکند.
class Product:
def __init__(self, name, price, stock):
self.name = name
self.price = price
self.stock = stockاکنون هنگام ساخت محصول باید مقادیر لازم را ارسال کنیم:
product_1 = Product("Keyboard", 1200000, 8)
product_2 = Product("Mouse", 650000, 12)
print(product_1.name)
print(product_2.price)با اجرای Product(...)، پایتون نمونه جدید را میسازد و سپس آن را بهعنوان آرگومان نخست به __init__ میفرستد.
آیا __init__ شیء را میسازد؟
در گفتوگوی روزمره معمولاً __init__ را «سازنده» مینامیم، اما از نظر فنی این متد بیشتر مسئول مقداردهی اولیه شیء ساختهشده است. فرایند ساخت واقعی نمونه با متد سطح پایینتر __new__ انجام میشود.
در بیشتر برنامههای معمولی نیازی به بازنویسی __new__ نداریم و استفاده از __init__ کافی است.
پارامتر self چیست؟
self مرجعی به همان شیئی است که متد روی آن اجرا میشود. این نام یک کلمه کلیدی اجباری پایتون نیست، اما قرارداد استاندارد و بسیار مهم زبان است.
class Product:
def __init__(self, name):
self.name = name
product = Product("Monitor")
print(product.name)هنگام اجرای کد بالا، پایتون تقریباً چنین ارتباطی برقرار میکند:
Product.__init__(product, "Monitor")ما هنگام فراخوانی معمول کلاس، مقدار self را دستی ارسال نمیکنیم؛ پایتون آن را خودکار مدیریت میکند.
تفاوت پارامتر و ویژگی نمونه
در این خط:
self.name = namenameپارامتر موقت متد است.self.nameویژگی ذخیرهشده روی شیء است.
نام آنها میتواند متفاوت باشد، اما استفاده از نام یکسان خواناتر است:
class User:
def __init__(self, username):
self.name = usernameدر این مثال، مقدار پارامتر username در ویژگی name ذخیره شده است.
ساخت چند شیء مستقل با __init__
class Product:
def __init__(self, name, price, stock):
self.name = name
self.price = price
self.stock = stock
product_1 = Product("Keyboard", 1200000, 8)
product_2 = Product("Mouse", 650000, 12)
product_1.stock = 5
print(product_1.stock)
print(product_2.stock)هر بار فراخوانی کلاس، شیء مستقلی با ویژگیهای مخصوص خود ایجاد میکند.
مقدار پیشفرض در __init__
پارامترهای سازنده نیز مانند تابع میتوانند مقدار پیشفرض داشته باشند:
class Product:
def __init__(self, name, price, stock=0, active=True):
self.name = name
self.price = price
self.stock = stock
self.active = active
product_1 = Product("Keyboard", 1200000)
product_2 = Product("Mouse", 650000, stock=12)
print(product_1.stock)
print(product_2.stock)پارامترهای بدون مقدار پیشفرض باید پیش از پارامترهای دارای مقدار پیشفرض قرار بگیرند.
استفاده از آرگومانهای کلیدی
برای خوانایی بیشتر میتوان هنگام ساخت شیء از آرگومانهای کلیدی استفاده کرد:
product = Product(
name="Laptop",
price=35000000,
stock=4,
active=True
)این روش در کلاسهایی با پارامترهای متعدد احتمال اشتباه در ترتیب مقادیر را کاهش میدهد.
اعتبارسنجی در سازنده
میتوانیم پیش از ذخیره دادهها، آنها را بررسی کنیم:
class Product:
def __init__(self, name, price, stock=0):
if not name.strip():
raise ValueError("نام محصول نباید خالی باشد")
if price < 0:
raise ValueError("قیمت نمیتواند منفی باشد")
if stock < 0:
raise ValueError("موجودی نمیتواند منفی باشد")
self.name = name.strip()
self.price = price
self.stock = stockاکنون ساخت شیء نامعتبر متوقف میشود:
try:
product = Product("Keyboard", -100, 5)
except ValueError as error:
print(error)این کار کمک میکند هر شیء از لحظه ساختهشدن در وضعیت معتبر قرار داشته باشد.
متد در پایتون چیست؟
متد تابعی است که داخل کلاس تعریف میشود. متدهای نمونه معمولاً اولین پارامتر خود را self میگیرند و میتوانند ویژگیهای همان شیء را بخوانند یا تغییر دهند.
class Product:
def __init__(self, name, price, stock=0):
self.name = name
self.price = price
self.stock = stock
def show_info(self):
print("Name:", self.name)
print("Price:", self.price)
print("Stock:", self.stock)فراخوانی متد:
product = Product("Keyboard", 1200000, 8)
product.show_info()پایتون شیء product را خودکار به پارامتر self متد ارسال میکند.
متدی که مقدار برمیگرداند
متد لازم نیست فقط چیزی چاپ کند. بهتر است در بسیاری از موارد مقدار را با return برگرداند تا در بخشهای دیگر برنامه قابل استفاده باشد:
class Product:
def __init__(self, name, price, stock=0):
self.name = name
self.price = price
self.stock = stock
def is_available(self):
return self.stock > 0
product = Product("Mouse", 650000, 0)
if product.is_available():
print("Available")
else:
print("Out of stock")متدی برای تغییر وضعیت شیء
class Product:
def __init__(self, name, price, stock=0):
self.name = name
self.price = price
self.stock = stock
def add_stock(self, quantity):
if quantity <= 0:
raise ValueError("تعداد باید بیشتر از صفر باشد")
self.stock += quantity
def sell(self, quantity=1):
if quantity <= 0:
raise ValueError("تعداد باید بیشتر از صفر باشد")
if quantity > self.stock:
raise ValueError("موجودی کافی نیست")
self.stock -= quantityاستفاده:
product = Product("Keyboard", 1200000, 5)
product.add_stock(3)
product.sell(2)
print(product.stock)بهجای تغییر مستقیم موجودی از بخشهای مختلف برنامه، منطق آن در متدهای کلاس متمرکز شده است.
متدهای نمونه، کلاسی و ایستا
| نوع متد | پارامتر نخست رایج | کاربرد |
|---|---|---|
| Instance Method | self | کار با وضعیت یک شیء مشخص |
| Class Method | cls | کار با خود کلاس یا ساخت نمونه به روش جایگزین |
| Static Method | ندارد | تابع کمکی مرتبط با مفهوم کلاس |
متد کلاسی با classmethod
متد کلاسی با دکوراتور @classmethod تعریف میشود و بهجای نمونه، خود کلاس را در پارامتر cls دریافت میکند:
class Product:
currency = "Toman"
def __init__(self, name, price):
self.name = name
self.price = price
@classmethod
def change_currency(cls, new_currency):
cls.currency = new_currency
Product.change_currency("Rial")
print(Product.currency)ساخت نمونه جایگزین با classmethod
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
@classmethod
def from_string(cls, text):
name, price = text.split(",")
return cls(name.strip(), int(price))
product = Product.from_string("Keyboard, 1200000")
print(product.name)
print(product.price)این الگو را Alternative Constructor مینامند؛ یعنی راه دیگری برای ساخت نمونه فراهم میشود.
متد ایستا با staticmethod
متد ایستا به نمونه یا کلاس دسترسی خودکار ندارد، اما از نظر مفهومی با کلاس مرتبط است:
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
@staticmethod
def is_valid_price(value):
return isinstance(value, (int, float)) and value >= 0
print(Product.is_valid_price(1200000))
print(Product.is_valid_price(-50))اگر یک تابع کمکی به هیچ وضعیت نمونه یا کلاس نیاز ندارد، میتواند متد ایستا باشد. با این حال، اگر ارتباط مفهومی روشنی با کلاس ندارد، بهتر است یک تابع معمولی در ماژول باشد.
متد ویژه __str__
هنگام چاپ یک شیء، خروجی پیشفرض معمولاً خوانا نیست. با تعریف __str__ نمایش مناسبی برای کاربر ایجاد میکنیم:
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
def __str__(self):
return f"{self.name} - {self.price:,} Toman"
product = Product("Keyboard", 1200000)
print(product)متد __str__ باید یک رشته برگرداند.
متد ویژه __repr__
__repr__ برای نمایش فنیتر و مناسب توسعهدهنده استفاده میشود:
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
def __repr__(self):
return f"Product(name={self.name!r}, price={self.price!r})"
product = Product("Keyboard", 1200000)
print(repr(product))وجود !r باعث میشود نمایش فنی مقدار با repr() تولید شود.
وراثت چیست؟
وراثت یا Inheritance به یک کلاس اجازه میدهد ویژگیها و متدهای کلاس دیگری را دریافت کند. کلاس اصلی را کلاس والد یا Base Class و کلاس جدید را کلاس فرزند یا Derived Class مینامیم.
ساختار کلی:
class Parent:
pass
class Child(Parent):
passکلاس فرزند میتواند قابلیتهای والد را استفاده کند و همزمان ویژگیها و رفتارهای جدید خود را داشته باشد.
اولین مثال وراثت
class User:
def __init__(self, name, email):
self.name = name
self.email = email
def show_info(self):
return f"{self.name} - {self.email}"
class Customer(User):
pass
customer = Customer("Sara", "sara@example.com")
print(customer.name)
print(customer.show_info())کلاس Customer سازنده و متد show_info را از User به ارث برده است.
بررسی رابطه وراثت
print(isinstance(customer, Customer))
print(isinstance(customer, User))
print(issubclass(Customer, User))یک شیء فرزند هم نمونه کلاس فرزند و هم نمونه کلاس والد محسوب میشود.
افزودن رفتار جدید در کلاس فرزند
class User:
def __init__(self, name, email):
self.name = name
self.email = email
def show_info(self):
return f"{self.name} - {self.email}"
class Customer(User):
def place_order(self, product_name):
return f"{self.name} ordered {product_name}"
customer = Customer("Sara", "sara@example.com")
print(customer.show_info())
print(customer.place_order("Python Course"))بازنویسی متد یا Method Overriding
کلاس فرزند میتواند متدی همنام با متد والد تعریف کند. در این حالت نسخه فرزند هنگام فراخوانی روی شیء فرزند اجرا میشود:
class User:
def get_role(self):
return "User"
class Admin(User):
def get_role(self):
return "Admin"
user = User()
admin = Admin()
print(user.get_role())
print(admin.get_role())این قابلیت به کلاس فرزند اجازه میدهد رفتار عمومی والد را برای نیاز خود تخصصی کند.
استفاده از super()
super() برای دسترسی به پیادهسازی کلاس بعدی در زنجیره وراثت استفاده میشود. کاربرد رایج آن، اجرای سازنده والد در سازنده فرزند است:
class User:
def __init__(self, name, email):
self.name = name
self.email = email
class Customer(User):
def __init__(self, name, email, customer_id):
super().__init__(name, email)
self.customer_id = customer_id
customer = Customer(
"Sara",
"sara@example.com",
"C-1001"
)
print(customer.name)
print(customer.email)
print(customer.customer_id)super().__init__(name, email) منطق مقداردهی ویژگیهای مشترک را به کلاس والد میسپارد و از تکرار کد جلوگیری میکند.
چرا نام کلاس والد را مستقیم فراخوانی نکنیم؟
این کد در وراثت ساده ممکن است کار کند:
User.__init__(self, name, email)اما استفاده از super() انعطافپذیرتر است، وابستگی کلاس فرزند به نام مستقیم والد را کاهش میدهد و در ساختارهای وراثت چندگانه با ترتیب حل متدها بهتر همکاری میکند.
گسترش متد والد با super()
class User:
def show_info(self):
return "User account"
class Admin(User):
def show_info(self):
parent_text = super().show_info()
return f"{parent_text} - Administrator"
admin = Admin()
print(admin.show_info())در این مثال، رفتار والد حذف نشده؛ نتیجه آن دریافت و تکمیل شده است.
وراثت چندسطحی
class User:
def login(self):
return "User logged in"
class Staff(User):
def open_dashboard(self):
return "Staff dashboard"
class Manager(Staff):
def approve_request(self):
return "Request approved"
manager = Manager()
print(manager.login())
print(manager.open_dashboard())
print(manager.approve_request())کلاس Manager قابلیتهای Staff و User را در اختیار دارد.
وراثت چندگانه
پایتون اجازه میدهد یک کلاس از چند کلاس والد ارث ببرد:
class Printable:
def print_document(self):
return "Printing document"
class Savable:
def save_document(self):
return "Saving document"
class Report(Printable, Savable):
pass
report = Report()
print(report.print_document())
print(report.save_document())وراثت چندگانه میتواند مفید باشد، اما در ساختارهای پیچیده فهم ترتیب جستوجوی متدها دشوار میشود. برای پروژههای آموزشی و بسیاری از برنامهها بهتر است ساختار وراثت ساده و روشن نگه داشته شود.
ترتیب حل متدها یا MRO
اگر متدی در چند کلاس والد وجود داشته باشد، پایتون براساس Method Resolution Order مشخص میکند کدام نسخه اجرا شود:
class A:
def identify(self):
return "A"
class B(A):
def identify(self):
return "B"
class C(A):
def identify(self):
return "C"
class D(B, C):
pass
object_d = D()
print(object_d.identify())
print(D.mro())متد mro() ترتیب جستوجوی کلاسها را نمایش میدهد. در این مثال، جستوجو از D به B، سپس C و بعد A ادامه پیدا میکند.
ترکیب یا Composition در برابر وراثت
وراثت رابطه «نوعی از» را بیان میکند:
- مشتری نوعی کاربر است.
- مدیر نوعی کارمند است.
ترکیب رابطه «دارای» را نشان میدهد:
- سفارش دارای مشتری است.
- خودرو دارای موتور است.
class Customer:
def __init__(self, name):
self.name = name
class Order:
def __init__(self, order_id, customer):
self.order_id = order_id
self.customer = customer
customer = Customer("Sara")
order = Order("O-501", customer)
print(order.customer.name)برای استفاده مجدد از کد همیشه نباید سراغ وراثت رفت. اگر رابطه منطقی «نوعی از» وجود ندارد، ترکیب اغلب انتخاب مناسبتری است.
اصل مسئولیت واحد در طراحی کلاس
هر کلاس بهتر است یک مسئولیت اصلی و مشخص داشته باشد. برای نمونه، کلاس محصول نباید همزمان مسئول ارسال ایمیل، اتصال پایگاه داده و تولید گزارش مالی باشد.
تقسیم پیشنهادی:
class Product:
pass
class ProductRepository:
pass
class EmailService:
pass
class SalesReport:
passاین جداسازی آزمایش، نگهداری و تغییر هر بخش را سادهتر میکند.
پروژه عملی: سیستم محصولات فروشگاه
در این پروژه، یک کلاس پایه محصول و دو کلاس تخصصی برای محصول دیجیتال و فیزیکی میسازیم:
class Product:
currency = "Toman"
def __init__(self, code, name, price):
if not code.strip():
raise ValueError("کد محصول نباید خالی باشد")
if not name.strip():
raise ValueError("نام محصول نباید خالی باشد")
if price < 0:
raise ValueError("قیمت نمیتواند منفی باشد")
self.code = code.strip()
self.name = name.strip()
self.price = price
def get_final_price(self):
return self.price
def get_type(self):
return "General product"
def __str__(self):
final_price = self.get_final_price()
return (
f"{self.code} | {self.name} | "
f"{final_price:,} {self.currency}"
)
class DigitalProduct(Product):
def __init__(self, code, name, price, file_size_mb):
super().__init__(code, name, price)
if file_size_mb <= 0:
raise ValueError("حجم فایل باید بیشتر از صفر باشد")
self.file_size_mb = file_size_mb
def get_type(self):
return "Digital product"
def get_download_info(self):
return f"Download size: {self.file_size_mb} MB"
class PhysicalProduct(Product):
def __init__(self, code, name, price, weight_grams):
super().__init__(code, name, price)
if weight_grams <= 0:
raise ValueError("وزن باید بیشتر از صفر باشد")
self.weight_grams = weight_grams
def get_shipping_cost(self):
base_cost = 80000
extra_units = max(0, self.weight_grams - 500) // 500
return base_cost + extra_units * 30000
def get_final_price(self):
return self.price + self.get_shipping_cost()
def get_type(self):
return "Physical product"
products = [
DigitalProduct(
"D-101",
"Python Course",
900000,
1250
),
PhysicalProduct(
"P-201",
"Python Book",
480000,
750
)
]
for product in products:
print(product.get_type())
print(product)
if isinstance(product, DigitalProduct):
print(product.get_download_info())
if isinstance(product, PhysicalProduct):
print("Shipping:", product.get_shipping_cost())
print("-" * 50)تحلیل پروژه
- کلاس
Productاطلاعات و رفتارهای مشترک را نگه میدارد. - دو کلاس فرزند با
super()سازنده والد را اجرا میکنند. - متد
get_typeدر هر فرزند بازنویسی شده است. - محصول فیزیکی متد
get_final_priceرا برای افزودن هزینه ارسال بازنویسی میکند. - متد
__str__از رفتار چندریختیget_final_priceاستفاده میکند. isinstance()قابلیت اختصاصی هر نوع محصول را تشخیص میدهد.
چندریختی چیست؟
چندریختی یا Polymorphism یعنی اشیاء کلاسهای مختلف یک رابط مشترک داشته باشند، اما هرکدام رفتار متناسب خود را اجرا کنند.
در پروژه بالا، همه محصولات متد get_final_price() دارند:
for product in products:
print(product.get_final_price())برای محصول دیجیتال، قیمت اصلی برگردانده میشود؛ برای محصول فیزیکی، هزینه ارسال نیز اضافه میشود. حلقه لازم نیست جزئیات پیادهسازی هر کلاس را بداند.
اشتباهات رایج در __init__ و متدها
- فراموشکردن پارامتر
selfدر متد نمونه - ارسال دستی
selfهنگام فراخوانی معمول متد - استفاده از متغیر محلی بهجای
self.attribute - برگرداندن مقدار از
__init__؛ این متد بایدNoneبرگرداند - قرار دادن منطق بسیار سنگین یا عملیات شبکه در سازنده
- چاپ مستقیم در متدی که بهتر است مقدار برگرداند
- تغییر مستقیم ویژگیها از همه بخشهای برنامه بهجای استفاده از متدهای کنترلشده
اشتباهات رایج در وراثت
- استفاده از وراثت فقط برای حذف چند خط کد تکراری
- تعریف رابطه والد و فرزند بدون رابطه منطقی «نوعی از»
- فراموشکردن اجرای سازنده والد
- فراخوانی مستقیم نام کلاس والد بهجای
super()بدون دلیل مشخص - ساخت زنجیره وراثت بسیار عمیق
- استفاده بیدلیل از وراثت چندگانه
- بازنویسی متد با ورودی یا خروجی کاملاً ناسازگار
- نادیدهگرفتن MRO در ساختارهای پیچیده
تمرینهای درس
- کلاس
BankAccountبا ویژگیهای صاحب حساب و موجودی بسازید و متدهای واریز و برداشت معتبر اضافه کنید. - کلاس
Employeeو فرزندManagerبسازید. مدیر باید پاداش جداگانه داشته باشد. - کلاس
Bookبا متد__str__تعریف کنید. - یک متد کلاسی برای ساخت کتاب از رشتهای مانند
title,author,priceبنویسید. - کلاسهای
CircleوRectangleرا با متد مشترکareaبسازید و چندریختی را در یک حلقه آزمایش کنید. - مثالی طراحی کنید که در آن ترکیب از وراثت منطقیتر باشد.
- پروژه محصولات را توسعه دهید و تخفیف، موجودی و اعتبارسنجی بیشتری به آن اضافه کنید.
جمعبندی
در این درس یاد گرفتیم سازنده __init__ چگونه ویژگیهای اولیه هر شیء را دریافت و ذخیره میکند و پارامتر self چگونه به نمونه جاری اشاره دارد. سپس متدهای نمونه، متدهای کلاسی، متدهای ایستا و متدهای ویژه __str__ و __repr__ را بررسی کردیم.
در بخش وراثت، کلاس والد و فرزند، بازنویسی متد، استفاده از super()، وراثت چندسطحی و چندگانه، MRO، چندریختی و تفاوت وراثت با ترکیب را یاد گرفتیم.
اکنون ابزارهای اصلی طراحی کلاسهای واقعی را در اختیار دارید. در درس پایانی دوره متوسط، این مفاهیم را همراه فایل JSON، مدیریت خطا، توابع و ساختارهای داده در پروژه مدیریت مخاطبان به کار میبریم.
ادامه مسیر یادگیری پایتون
برای یادگیری منظم، آموزشهای مرتبط زیر را بهترتیب دنبال کنید:
📌 همچنین بخوانید: بازار کار پایتون در ایران، دستورهای break، continue و pass در پایتون و ساخت ربات تلگرام با پایتون
برای ذخیره پیشرفت در همه دستگاهها و دریافت گواهی پایان مسیر، رایگان ثبتنام کن یا وارد شو.