آموزش پایتون, پایتون سطح متوسط

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

سازنده __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 = name
  • name پارامتر موقت متد است.
  • 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 Methodselfکار با وضعیت یک شیء مشخص
Class Methodclsکار با خود کلاس یا ساخت نمونه به روش جایگزین
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 در ساختارهای پیچیده

تمرین‌های درس

  1. کلاس BankAccount با ویژگی‌های صاحب حساب و موجودی بسازید و متدهای واریز و برداشت معتبر اضافه کنید.
  2. کلاس Employee و فرزند Manager بسازید. مدیر باید پاداش جداگانه داشته باشد.
  3. کلاس Book با متد __str__ تعریف کنید.
  4. یک متد کلاسی برای ساخت کتاب از رشته‌ای مانند title,author,price بنویسید.
  5. کلاس‌های Circle و Rectangle را با متد مشترک area بسازید و چندریختی را در یک حلقه آزمایش کنید.
  6. مثالی طراحی کنید که در آن ترکیب از وراثت منطقی‌تر باشد.
  7. پروژه محصولات را توسعه دهید و تخفیف، موجودی و اعتبارسنجی بیشتری به آن اضافه کنید.

جمع‌بندی

در این درس یاد گرفتیم سازنده __init__ چگونه ویژگی‌های اولیه هر شیء را دریافت و ذخیره می‌کند و پارامتر self چگونه به نمونه جاری اشاره دارد. سپس متدهای نمونه، متدهای کلاسی، متدهای ایستا و متدهای ویژه __str__ و __repr__ را بررسی کردیم.

در بخش وراثت، کلاس والد و فرزند، بازنویسی متد، استفاده از super()، وراثت چندسطحی و چندگانه، MRO، چندریختی و تفاوت وراثت با ترکیب را یاد گرفتیم.

اکنون ابزارهای اصلی طراحی کلاس‌های واقعی را در اختیار دارید. در درس پایانی دوره متوسط، این مفاهیم را همراه فایل JSON، مدیریت خطا، توابع و ساختارهای داده در پروژه مدیریت مخاطبان به کار می‌بریم.

ادامه مسیر یادگیری پایتون

برای یادگیری منظم، آموزش‌های مرتبط زیر را به‌ترتیب دنبال کنید:

📌 همچنین بخوانید: بازار کار پایتون در ایران، دستورهای break، continue و pass در پایتون و ساخت ربات تلگرام با پایتون

می‌خواهی پیشرفتت ذخیره شود؟با ورود یا ثبت‌نام رایگان، درس‌های تمام‌شده را علامت بزن و درصد پیشرفتت را در حساب کاربری ببین.ورود / ثبت‌نام برای ذخیره پیشرفت
درس بعدی · دوره پایتون متوسط (18 از 18)پروژه مدیریت مخاطبان با پایتون؛ ساخت برنامه کامل با JSON و OOP ←
آموزش‌های تازه، کد آماده و رفع خطاهر هفته در کانال Dev Source؛ رایگان.
🧭 مسیر یادگیری پایتون از صفر
ماژول 9: برنامه‌نویسی شیءگرا — درس 40 از 44

برای ذخیره پیشرفت در همه دستگاه‌ها و دریافت گواهی پایان مسیر، رایگان ثبت‌نام کن یا وارد شو.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *