کپسوله‌سازی (Encapsulation) در پایتون

فرض کنید کلاسی برای حساب بانکی نوشته‌اید. اگر هر کسی بتواند موجودی حساب را مستقیماً تغییر دهد، ممکن است مقدار موجودی به عددی منفی یا غیرواقعی تبدیل شود. این موضوع باعث ایجاد خطا و کاهش امنیت برنامه می‌شود. اینجاست که مفهوم کپسوله‌سازی (Encapsulation) وارد عمل می‌شود.

کپسوله‌سازی یکی از مهم‌ترین اصول برنامه‌نویسی شیءگرا است که داده‌ها و توابع مرتبط با آن‌ها را داخل یک کلاس قرار می‌دهد و از دسترسی یا تغییر مستقیم اطلاعات حساس جلوگیری می‌کند.

به بیان ساده، کپسوله‌سازی مانند یک جعبه محافظ عمل می‌کند؛ اطلاعات مهم داخل کلاس قرار می‌گیرند و تنها از طریق روش‌هایی که خود کلاس مشخص کرده است، قابل مشاهده یا تغییر هستند.

این ویژگی باعث می‌شود:

  • امنیت داده‌ها افزایش پیدا کند.
  • از تغییرات اشتباه جلوگیری شود.
  • نگهداری و توسعه برنامه آسان‌تر شود.
  • کدها منظم‌تر و قابل اعتمادتر باشند.

فهرست مطالب

کپسوله‌سازی در دنیای واقعی

چند نمونه از کپسوله‌سازی که هر روز با آن‌ها سروکار داریم:

  • خودپرداز بانک: شما فقط مبلغ برداشت را وارد می‌کنید و هیچ دسترسی مستقیمی به اطلاعات داخلی بانک ندارید.
  • تلویزیون: تنها با دکمه‌های کنترل صدا و کانال کار می‌کنید و مدارهای داخلی دستگاه از دسترس شما خارج هستند.
  • اپلیکیشن موبایل: اطلاعات کاربران در پایگاه داده ذخیره می‌شود و کاربران نمی‌توانند مستقیماً آن‌ها را تغییر دهند.

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

کپسوله‌سازی در پایتون

پایتون برخلاف برخی زبان‌ها مانند جاوا یا سی‌پلاس‌پلاس، سطح دسترسی سخت‌گیرانه ندارد؛ اما با استفاده از قراردادهای نام‌گذاری و چند قابلیت داخلی، می‌توان اطلاعات را از دسترسی مستقیم محافظت کرد. در این درس با موارد زیر آشنا خواهید شد:

  • متغیرهای Public
  • متغیرهای Protected
  • متغیرهای Private
  • استفاده از Getter و Setter
  • استفاده از @property

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

سطح دسترسی (Access Modifiers)

در بسیاری از زبان‌های برنامه‌نویسی مانند جاوا یا ++C، می‌توان مشخص کرد که هر ویژگی یا متد تا چه اندازه در دسترس سایر بخش‌های برنامه باشد. در پایتون نیز این مفهوم وجود دارد، اما با روشی ساده‌تر. پایتون بیشتر بر پایه قراردادهای نام‌گذاری (Naming Conventions) عمل می‌کند و برنامه‌نویس با استفاده از یک یا دو خط زیر (_) مشخص می‌کند که یک ویژگی تا چه اندازه باید در دسترس باشد.

به طور کلی، ویژگی‌ها و متدهای یک کلاس در سه دسته قرار می‌گیرند:

نوع دسترسی نحوه نام‌گذاری میزان دسترسی
Public name از همه جای برنامه قابل دسترسی است.
Protected _name بهتر است فقط داخل کلاس و کلاس‌های فرزند استفاده شود.
Private __name برای استفاده داخلی کلاس در نظر گرفته شده و دسترسی مستقیم به آن محدود می‌شود.

۱. متغیرهای Public (عمومی)

اگر نام یک ویژگی را به صورت معمولی بنویسیم، آن ویژگی Public محسوب می‌شود. این نوع متغیر از هر جای برنامه قابل مشاهده و تغییر است.

مثال

class Student:
    def __init__(self):
        self.name = "Ali"

student = Student()
print(student.name)
student.name = "Reza"
print(student.name)

خروجی

Ali
Reza

در این مثال، ویژگی name بدون هیچ محدودیتی قابل خواندن و تغییر است.

چه زمانی از Public استفاده کنیم؟

اگر تغییر مقدار یک ویژگی مشکلی برای برنامه ایجاد نمی‌کند، بهتر است آن را Public قرار دهیم.

برای مثال:

  • نام دانشجو
  • نام کتاب
  • رنگ خودرو
  • عنوان محصول

این اطلاعات معمولاً حساس نیستند و تغییر آن‌ها مشکلی ایجاد نمی‌کند.

مزایای Public

  • ساده و خوانا
  • دسترسی آسان
  • مناسب برای داده‌های غیرحساس

معایب Public

  • هر بخشی از برنامه می‌تواند مقدار آن را تغییر دهد.
  • ممکن است داده‌های نامعتبر وارد شوند.
  • امنیت اطلاعات کاهش پیدا می‌کند.

مثال یک مشکل واقعی

فرض کنید موجودی حساب بانکی را به صورت Public تعریف کنیم.

class BankAccount:
    def __init__(self):
        self.balance = 1000

account = BankAccount()
account.balance = -50000
print(account.balance)

خروجی

-50000

واضح است که چنین مقداری منطقی نیست، اما چون balance عمومی است، هر بخشی از برنامه می‌تواند آن را تغییر دهد. به همین دلیل، برای اطلاعات حساس از Protected یا Private استفاده می‌کنیم تا کنترل بیشتری روی نحوه دسترسی و تغییر داده‌ها داشته باشیم.

۲. متغیرهای Protected (محافظت‌شده)

در پایتون اگر نام یک ویژگی یا متد را با یک خط زیر (_) شروع کنیم، آن عضو Protected محسوب می‌شود.

_name

برخلاف برخی زبان‌های برنامه‌نویسی، پایتون دسترسی به اعضای Protected را ممنوع نمی‌کند. در واقع، استفاده از _ فقط یک قرارداد بین برنامه‌نویسان (Convention) است. یعنی با دیدن یک خط زیر در ابتدای نام متغیر، سایر برنامه‌نویسان متوجه می‌شوند که این عضو برای استفاده داخلی کلاس یا کلاس‌های فرزند طراحی شده است و بهتر است مستقیماً از بیرون به آن دسترسی نداشته باشند.

مثال

class Student:
    def __init__(self):
        self._score = 18

student = Student()
print(student._score)

خروجی

18

همان‌طور که مشاهده می‌کنید، برنامه بدون هیچ خطایی اجرا شد. بنابراین Protected در پایتون فقط یک توصیه است، نه یک محدودیت واقعی.

چرا از Protected استفاده می‌کنیم؟

ممکن است یک ویژگی فقط برای استفاده داخلی کلاس یا کلاس‌های فرزند باشد و نخواهیم سایر بخش‌های برنامه مستقیماً با آن کار کنند.

برای مثال:

  • نمره موقت دانش‌آموز
  • موجودی قابل ویرایش حساب
  • اطلاعات داخلی یک بازی
  • وضعیت پردازش یک سفارش

در چنین شرایطی، استفاده از _ باعث می‌شود سایر برنامه‌نویسان بدانند که بهتر است این متغیر را مستقیماً تغییر ندهند.

مثال کاربردی

class Employee:
    def __init__(self):
        self._salary = 50000

employee = Employee()
print(employee._salary)
employee._salary = 70000
print(employee._salary)

خروجی

50000
70000

اگرچه مقدار متغیر تغییر کرد، اما طبق اصول برنامه‌نویسی شیءگرا بهتر است چنین تغییری فقط از طریق متدهای کلاس انجام شود.

مزایا

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

محدودیت

Protected امنیت واقعی ایجاد نمی‌کند و همچنان می‌توان از خارج کلاس به آن دسترسی داشت. به همین دلیل، اگر بخواهیم از اطلاعات حساس بهتر محافظت کنیم، از Private استفاده می‌کنیم.

تفاوت Public و Protected

ویژگی Public Protected
نحوه نام‌گذاری name _name
دسترسی مستقیم از خارج کلاس امکان‌پذیر است. امکان‌پذیر است.
هدف استفاده استفاده عمومی در کل برنامه استفاده داخلی کلاس و کلاس‌های فرزند
محدودیت واقعی دسترسی ندارد. ندارد.
قرارداد بین برنامه‌نویسان خیر بله
مناسب برای اطلاعات حساس خیر تا حدی (به‌عنوان یک توصیه)

۳. متغیرهای Private (خصوصی)

اگر نام یک ویژگی یا متد را با دو خط زیر (__) شروع کنیم، آن عضو Private محسوب می‌شود.

__name

هدف از Private این است که از دسترسی مستقیم به اطلاعات مهم کلاس جلوگیری شود. برخلاف Protected که تنها یک قرارداد بین برنامه‌نویسان بود، پایتون برای اعضای Private مکانیزمی به نام Name Mangling را به کار می‌برد تا دسترسی مستقیم به آن‌ها دشوارتر شود. به همین دلیل، اگر از بیرون کلاس بخواهید مستقیماً به یک عضو Private دسترسی پیدا کنید، با خطا مواجه خواهید شد.

مثال

class Student:
    def __init__(self):
        self.__score = 20

student = Student()
print(student.__score)

خروجی

AttributeError: 'Student' object has no attribute '__score'

در این مثال، ویژگی __score از خارج کلاس قابل دسترسی نیست و برنامه خطا تولید می‌کند.

چرا از Private استفاده می‌کنیم؟

گاهی برخی اطلاعات نباید مستقیماً توسط سایر بخش‌های برنامه تغییر کنند. برای مثال:

  • موجودی حساب بانکی
  • رمز عبور
  • شماره کارت بانکی
  • اطلاعات پزشکی بیمار
  • وضعیت داخلی یک شیء

در چنین شرایطی بهتر است این اطلاعات را به صورت Private تعریف کنیم تا فقط خود کلاس بتواند آن‌ها را مدیریت کند.

مثال کاربردی

class BankAccount:
    def __init__(self):
        self.__balance = 1000

در این کلاس، موجودی حساب به صورت Private تعریف شده است؛ بنابراین سایر بخش‌های برنامه نمی‌توانند مستقیماً مقدار آن را تغییر دهند. به جای دسترسی مستقیم، کلاس می‌تواند متدهایی مانند deposit() و withdraw() را در اختیار کاربران قرار دهد تا افزایش یا کاهش موجودی به شکل کنترل‌شده انجام شود.

Name Mangling چیست؟

زمانی که یک ویژگی را با دو خط زیر تعریف می‌کنید، پایتون نام آن را در پشت صحنه تغییر می‌دهد تا از دسترسی تصادفی جلوگیری شود. برای مثال، اگر کلاس زیر را داشته باشیم:

class Student:
    def __init__(self):
        self.__score = 20

پایتون در پشت صحنه نام متغیر را تقریباً به این شکل تغییر می‌دهد:

_Student__score

به همین دلیل، عبارت زیر خطا می‌دهد:

student.__score

اما در واقع مقدار همچنان داخل شیء وجود دارد و فقط نام آن تغییر کرده است.

مزایا

  • محافظت بهتر از اطلاعات حساس
  • جلوگیری از دسترسی مستقیم
  • کاهش احتمال تغییرات اشتباه
  • افزایش خوانایی و قابلیت نگهداری کد

محدودیت

اعضای Private کاملاً مخفی نیستند و در صورت نیاز می‌توان از طریق نام تغییر‌یافته به آن‌ها دسترسی پیدا کرد. بنابراین Private در پایتون بیشتر برای محافظت منطقی از داده‌ها استفاده می‌شود، نه ایجاد امنیت غیرقابل نفوذ.

تفاوت Protected و Private

ویژگی Protected Private
نحوه نام‌گذاری _name __name
دسترسی مستقیم از خارج کلاس امکان‌پذیر است. امکان‌پذیر نیست.
هدف استفاده نشان دادن اینکه عضو برای استفاده داخلی کلاس یا کلاس‌های فرزند در نظر گرفته شده است. محافظت بیشتر از اطلاعات حساس و جلوگیری از دسترسی مستقیم.
محدودیت واقعی دسترسی ندارد. دسترسی مستقیم را محدود می‌کند.
تغییر نام توسط پایتون (Name Mangling) خیر بله
مناسب برای اطلاعات حساس تا حدی (به‌عنوان یک قرارداد) بله

Getter و Setter در پایتون

تا اینجا یاد گرفتیم چگونه با استفاده از __ متغیرهای Private ایجاد کنیم. اما یک سؤال مهم وجود دارد: اگر یک متغیر Private باشد، چگونه می‌توان مقدار آن را خواند یا تغییر داد؟ برای حل این مشکل از Getter و Setter استفاده می‌کنیم.

Getter چیست؟

Getter یک متد داخل کلاس است که برای خواندن مقدار یک ویژگی Private استفاده می‌شود. به عبارت ساده متغیر Private را مستقیم نمی‌خوانیم؛ بلکه یک متد مخصوص ایجاد می‌کنیم تا مقدار آن را برگرداند.

مثال بدون Getter

فرض کنید یک کلاس برای دانشجو داریم:

class Student:
    def __init__(self):
        self.__score = 18

student = Student()
print(student.__score)

خروجی:

AttributeError: 'Student' object has no attribute '__score'

چون score یک ویژگی Private است، نمی‌توانیم مستقیماً به آن دسترسی داشته باشیم.

استفاده از Getter

class Student:
    def __init__(self):
        self.__score = 18

    def get_score(self):
        return self.__score

student = Student()
print(student.get_score())

خروجی:

18

در این مثال، متد get_score() نقش Getter را دارد و مقدار متغیر Private را در اختیار ما قرار می‌دهد.

مزیت استفاده از Getter

شاید در نگاه اول به نظر برسد که Getter فقط یک مقدار را برمی‌گرداند و کار خاصی انجام نمی‌دهد؛ اما در پروژه‌های واقعی اهمیت زیادی دارد. با استفاده از Getter می‌توانیم:

  • قبل از نمایش اطلاعات، بررسی انجام دهیم.
  • فرمت خروجی را تغییر دهیم.
  • دسترسی مستقیم به داده را محدود کنیم.

برای مثال:

def get_score(self):
    return f"نمره دانشجو: {self.__score}"

خروجی:

نمره دانشجو: 18

Setter چیست؟

Setter یک متد داخل کلاس است که برای تغییر مقدار یک ویژگی Private استفاده می‌شود. به جای اینکه مقدار متغیر را مستقیماً تغییر دهیم، از یک متد استفاده می‌کنیم تا بتوانیم مقدار جدید را کنترل کنیم.

مشکل تغییر مستقیم اطلاعات

فرض کنید نمره دانشجو را مستقیماً تغییر دهیم:

student.__score = -10

این مقدار منطقی نیست، زیرا نمره نباید منفی باشد. پس بهتر است قبل از تغییر مقدار، آن را بررسی کنیم.

استفاده از Setter

class Student:
    def __init__(self):
        self.__score = 18

    def get_score(self):
        return self.__score

    def set_score(self, score):
        if 0 <= score <= 20:
            self.__score = score
        else:
            print("نمره نامعتبر است")

student = Student()
student.set_score(19)
print(student.get_score())

خروجی:

19

اگر مقدار نامعتبر وارد کنیم:

student.set_score(-5)

خروجی:

نمره نامعتبر است

در اینجا Setter اجازه نمی‌دهد مقدار اشتباه وارد ویژگی Private شود.

نقش Getter و Setter در کپسوله‌سازی

ترکیب Private + Getter + Setter باعث می‌شود:

  • داده‌ها مستقیم در دسترس نباشند.
  • تغییرات کنترل شوند.
  • قوانین کسب‌وکار داخل کلاس قرار بگیرند.
  • احتمال خطا کاهش پیدا کند.

در برنامه‌های واقعی مانند سیستم بانکی، فروشگاه اینترنتی و سامانه‌های آموزشی، این روش بسیار پرکاربرد است.

استفاده از @property برای Getter و Setter

در بخش قبل یاد گرفتیم که برای دسترسی کنترل‌شده به متغیرهای Private از متدهای Getter و Setter استفاده کنیم. روش قبلی کاملاً درست است، اما در پایتون یک روش حرفه‌ای‌تر و خواناتر نیز وجود دارد که با استفاده از دکوراتور @property انجام می‌شود.

با استفاده از @property می‌توانیم کاری کنیم که یک ویژگی Private مانند یک ویژگی معمولی خوانده یا تغییر داده شود، در حالی که همچنان کنترل‌های لازم روی آن وجود دارد.

مشکل روش معمول Getter و Setter

در روش قبلی برای خواندن مقدار باید یک متد را صدا می‌زدیم:

student.get_score()

و برای تغییر مقدار نیز از یک متد دیگر استفاده می‌کردیم:

student.set_score(19)

اما در پایتون می‌توانیم این کار را ساده‌تر کنیم:

student.score

و:

student.score = 19

در ظاهر مانند یک متغیر معمولی رفتار می‌کند، اما در پشت صحنه Getter و Setter اجرا می‌شوند.

ساخت Getter با @property

برای تبدیل یک متد به Getter کافی است قبل از آن از دکوراتور @property استفاده کنیم.

مثال:

class Student:
    def __init__(self):
        self.__score = 18

    @property
    def score(self):
        return self.__score

student = Student()
print(student.score)

خروجی:

18

دقت کنید که دیگر از پرانتز استفاده نکردیم:

student.score

نه:

student.score()

زیرا @property باعث می‌شود متد مانند یک ویژگی معمولی رفتار کند.

ساخت Setter با @score.setter

برای تغییر مقدار ویژگی، از یک Setter استفاده می‌کنیم.

Setter با استفاده از همان نام Getter و دکوراتور زیر ساخته می‌شود:

@score.setter

مثال کامل Getter و Setter با @property

class Student:
    def __init__(self):
        self.__score = 18

    @property
    def score(self):
        return self.__score

    @score.setter
    def score(self, value):
        if 0 <= value <= 20:
            self.__score = value
        else:
            print("نمره نامعتبر است")

student = Student()
print(student.score)
student.score = 20
print(student.score)

خروجی:

18
20

حالا اگر مقدار نامعتبر وارد کنیم:

student.score = 30

خروجی:

نمره نامعتبر است

Setter اجازه نمی‌دهد مقدار غیرمجاز وارد متغیر Private شود.

استفاده از @property چند مزیت مهم دارد:

  • کد خواناتر می‌شود.
  • کلاس حرفه‌ای‌تر طراحی می‌شود.
  • کاربر کلاس تفاوتی بین ویژگی معمولی و ویژگی کنترل‌شده احساس نمی‌کند.
  • قوانین اعتبارسنجی داخل خود کلاس باقی می‌مانند.
  

✏️ تمرین ۱

کلاسی به نام Student ایجاد کنید. ویژگی‌های زیر را برای کلاس در نظر بگیرید:
  • نام دانشجو به صورت Public
  • نمره دانشجو به صورت Private
سپس:
  • یک Getter برای مشاهده نمره ایجاد کنید.
  • یک Setter برای تغییر نمره ایجاد کنید.
  • در Setter بررسی کنید که نمره باید بین ۰ تا ۲۰ باشد.
در پایان یک شیء از کلاس ساخته و عملکرد آن را آزمایش کنید.

✏️ تمرین ۲

کلاسی به نام BankAccount طراحی کنید. ویژگی‌ها:
  • نام صاحب حساب به صورت Public
  • موجودی حساب به صورت Private
متدهای زیر را ایجاد کنید:
  • متدی برای واریز پول
  • متدی برای برداشت پول
  • Getter برای نمایش موجودی
قوانین زیر را در برنامه رعایت کنید:
  • مبلغ واریز باید مثبت باشد.
  • برداشت بیشتر از موجودی امکان‌پذیر نباشد.

✏️ تمرین ۳

کلاسی به نام User ایجاد کنید. ویژگی‌های کلاس:
  • نام کاربر به صورت Public
  • رمز عبور به صورت Private
امکانات زیر را پیاده‌سازی کنید:
  • متدی برای تغییر رمز عبور
  • بررسی کنید رمز جدید حداقل ۸ کاراکتر داشته باشد.
  • متدی برای بررسی ورود کاربر ایجاد کنید.

✏️ تمرین ۴

کلاسی به نام Product ایجاد کنید. ویژگی‌ها:
  • نام محصول به صورت Public
  • قیمت محصول به صورت Private
سپس:
  • Getter برای نمایش قیمت ایجاد کنید.
  • Setter برای تغییر قیمت ایجاد کنید.
  • اجازه ندهید قیمت کمتر یا مساوی صفر باشد.

کار با فایل، ماژول، پکیج و کتابخانه در پایتون

تا اینجا با مفاهیم اصلی برنامه‌نویسی شیءگرا مانند کلاس، وراثت و کپسوله‌سازی آشنا شدیم. اما در پروژه‌های واقعی، معمولاً تمام کدها در یک فایل نوشته نمی‌شوند و برای مدیریت بهتر برنامه‌ها نیاز داریم با فایل‌ها، ماژول‌ها و کتابخانه‌های مختلف کار کنیم. در بخش بعدی یاد می‌گیریم چگونه اطلاعات را در فایل‌ها ذخیره کنیم، کدهای خود را به ماژول‌های جداگانه تقسیم کنیم و از پکیج‌ها و کتابخانه‌های آماده پایتون استفاده کنیم.

گام بعدی : فایل و کتابخانه در پایتون

برای شرکت در آزمون آماده‌اید؟

با ورود به پنل کاربری می‌توانید جهت ثبت نام در آزمون‌ها و دسترسی به منابع آموزشی اقدام نمایید. کارشناسان ما در اسرع وقت با شما تماس می‌گیرند.

شروع کار با Django

شروع کار با Django در دوره آموزش جامع پایتون با مفاهیمی مانند متغیرها، شرط‌ها، حلقه‌ها، توابع، کلاس‌ها، فایل‌ها و کار

ادامه مطلب »

عملیات CRUD در SQLite

عملیات CRUD درSQLite در بخش قبل با مفهوم پایگاه داده، SQLite و نحوه اتصال پایتون به پایگاه داده آشنا شدیم.

ادامه مطلب »

توابع در پایتون

توابع در پایتون اگر هنگام برنامه‌نویسی مجبور شوید یک قطعه کد را بارها تکرار کنید، برنامه شما طولانی، شلوغ و

ادامه مطلب »

مجموعه در پایتون

مجموعه (Set) در پایتون به زبان ساده، Set مجموعه‌ای از عناصر منحصربه‌فرد (Unique) است که ترتیب مشخصی ندارد و اجازه

ادامه مطلب »

تاپل در پایتون

تاپل (Tuple) در پایتون بعد از آشنایی با لیست (List)، نوبت به یکی دیگر از ساختارهای داده در پایتون یعنی

ادامه مطلب »

لیست در پایتون

لیست (List) در پایتون پرکاربردترین ساختار داده​ تا اینجا یاد گرفتید که هر متغیر فقط یک مقدار را در خود

ادامه مطلب »

آموزش نصب پایتون

آموزش نصب پایتون و ابزارهای موردنیاز (راهنمای کامل برای ویندوز) اگر تصمیم گرفته‌اید برنامه‌نویسی را یاد بگیرید، احتمالاً نام زبان

ادامه مطلب »