۱۳ مثال کپسولهسازی (Encapsulation) در پایتون؛ از ساده تا کاربردی

این مقاله با رویکردهای امروزی پایتون (از جمله اعتبارسنجی با Pydantic v2) بهروزرسانی شده است.
اگر فقط دنبال جواب سریع هستید: کپسولهسازی یعنی دادهها و متدهای مرتبط با آنها را داخل یک کلاس جمع کنید و دسترسی مستقیم به دادهها را محدود کنید تا فقط از طریق متدها یا propertyهای کنترلشده قابل تغییر باشند.
طبق نظرسنجی توسعهدهندگان Stack Overflow در سال ۲۰۲۵، استفاده از پایتون نسبت به سال قبل حدود هفت واحد درصد رشد کرده و اکنون به نزدیک ۵۸ درصد از توسعهدهندگان رسیده است؛ همچنین طبق شاخص TIOBE در نوامبر ۲۰۲۵، پایتون همچنان رتبه اول زبانهای برنامهنویسی دنیا را در اختیار دارد. با رشد پروژههای بزرگتر (بهخصوص در حوزه هوش مصنوعی و ابزارهای backend)، رعایت درست کپسولهسازی اهمیت بیشتری پیدا کرده، چون تیمها بیشتر از قبل روی کد یکدیگر (و کدهای تولیدشده توسط ابزارهای AI) کار میکنند.
در ادامه ۱۳ مثال واقعی را در سه سطح (مبتدی، متوسط، کاربردی) میبینید — بدون تکرار همان مثال کلیشهای «ماشین» که در اکثر مقالهها تکرار شده.
قوانین پایه قبل از شروع
| نشانهگذاری | معنی | قابل دسترسی از بیرون؟ |
|---|---|---|
| name | عمومی (Public) | بله، بدون محدودیت |
| _name | محافظتشده (Protected) — قرارداد است | بله، ولی توصیه نمیشود |
| __name | خصوصی (Private) — name mangling | فقط با _ClassName__name |
نکته مهم: پایتون هیچکدام از اینها را واقعاً «قفل» نمیکند. فلسفه پایتون این است: «ما همه بزرگسالِ رضایتمند هستیم» — یعنی بهجای اجبار زبان، به قرارداد و مستندسازی تکیه میکنیم.
یه نکته مهم دیگه: متدهای فایل در پایتون؛ خواندن و مسیر فایل
سطح مبتدی (۳ مثال)
۱- حساب بانکی با موجودی محافظتشده
class BankAccount:
def __init__(self, balance):
self.__balance = balance # خصوصی
def deposit(self, amount):
if amount <= 0:
raise ValueError(“مبلغ واریزی باید مثبت باشد”)
self.__balance += amount
def get_balance(self):
return self.__balance
account = BankAccount(1000)
account.deposit(500)
print(account.get_balance()) # 1500
موجودی مستقیماً قابل تغییر نیست؛ فقط از طریق deposit که خودش قانون دارد.
۲- کاربر با رمز عبور هششده
import hashlib
class User:
def __init__(self, username, password):
self.username = username
self.__password_hash = self.__hash(password)
def __hash(self, password):
return hashlib.sha256(password.encode()).hexdigest()
def check_password(self, password):
return self.__password_hash == self.__hash(password)
u = User(“ali”, “12345”)
print(u.check_password(“12345”)) # True
print(u.check_password(“wrong”)) # False
رمز عبور اصلی هرگز بهصورت خام ذخیره یا برگردانده نمیشود — این یک کاربرد بسیار واقعی از encapsulation است، نه فقط تئوری.

۳- کارمند با سقف افزایش حقوق
class Employee:
def __init__(self, name, salary):
self.name = name
self.__salary = salary
def give_raise(self, percent):
if not (0 < percent <= 30):
raise ValueError(“درصد افزایش باید بین ۰ تا ۳۰ باشد”)
self.__salary *= (1 + percent / 100)
@property
def salary(self):
return round(self.__salary, 2)
e = Employee(“سارا”, 20000000)
e.give_raise(10)
print(e.salary) # 22000000.0
این یکی هم به کارت میاد: متاکلاس و ارثبری چندگانه؛ ۲ مفهوم مهم پایتون
سطح متوسط (۴ مثال)
۴- اعتبارسنجی ایمیل با property
import re
class Contact:
def __init__(self, email):
self.email = email # از setter عبور میکند
@property
def email(self):
return self._email
@email.setter
def email(self, value):
if not re.match(r”^[\w\.-]+@[\w\.-]+\.\w+$”, value):
raise ValueError(“ایمیل نامعتبر است”)
self._email = value
c = Contact(“test@example.com”)
# c.email = “invalid” # ValueError
۵- موجودی انبار که هرگز منفی نمیشود
class Inventory:
def __init__(self, quantity=0):
self.__quantity = quantity
def remove(self, count):
if count > self.__quantity:
raise ValueError(“موجودی کافی نیست”)
self.__quantity -= count
def add(self, count):
self.__quantity += count
@property
def quantity(self):
return self.__quantity
اینجا کپسولهسازی از یک باگ رایج در سیستمهای فروشگاهی (موجودی منفی) جلوگیری میکند.

۶- تنظیمات فقط-خواندنی (Read-only Config)
class Config:
def __init__(self, api_key, timeout=30):
self.__api_key = api_key
self.__timeout = timeout
@property
def api_key(self):
return self.__api_key
@property
def timeout(self):
return self.__timeout
cfg = Config(“secret-key-123”)
# cfg.api_key = “new-key” # AttributeError: هیچ setter تعریف نشده
وقتی هیچ setterی تعریف نمیشود، property بهطور طبیعی فقط-خواندنی میشود — بدون نیاز به کد اضافه.
۷- تبدیل واحد دما با کنترل داخلی
class Temperature:
def __init__(self, celsius=0):
self.celsius = celsius
@property
def fahrenheit(self):
return (self.celsius * 9/5) + 32
@fahrenheit.setter
def fahrenheit(self, value):
self.celsius = (value – 32) * 5/9
t = Temperature(25)
print(t.fahrenheit) # 77.0
t.fahrenheit = 100
print(round(t.celsius, 1)) # 37.8
دو نمای مختلف از یک داده، بدون اینکه دو مقدار جدا نگهداری شود — همگامسازی بهطور خودکار از طریق کپسولهسازی انجام میشود.
سطح کاربردی (۶ مثال)
۸- Rate Limiter ساده
import time
class RateLimiter:
def __init__(self, max_calls, period_seconds):
self.__max_calls = max_calls
self.__period = period_seconds
self.__calls = []
def allow(self):
now = time.time()
self.__calls = [t for t in self.__calls if now – t < self.__period]
if len(self.__calls) < self.__max_calls:
self.__calls.append(now)
return True
return False
لیست __calls هرگز از بیرون قابل دستکاری نیست؛ فقط از طریق allow() که منطق زمانبندی را رعایت میکند.

۹- کش با انقضا (TTL Cache)
import time
class TTLCache:
def __init__(self, ttl_seconds=60):
self.__ttl = ttl_seconds
self.__store = {}
def set(self, key, value):
self.__store[key] = (value, time.time())
def get(self, key):
if key not in self.__store:
return None
value, ts = self.__store[key]
if time.time() – ts > self.__ttl:
del self.__store[key]
return None
return value
۱۰- اتصال دیتابیس با Context Manager
class DBConnection:
def __init__(self, dsn):
self.__dsn = dsn
self.__connection = None
def __enter__(self):
self.__connection = f”CONNECTED to {self.__dsn}” # شبیهسازی اتصال
return self.__connection
def __exit__(self, exc_type, exc_val, exc_tb):
self.__connection = None # همیشه بسته میشود
with DBConnection(“postgres://localhost”) as conn:
print(conn)
جزئیات باز و بسته کردن اتصال کاملاً از کاربر کلاس پنهان است — این دقیقاً همان چیزی است که در پروژههای واقعی با کتابخانههایی مثل psycopg2 یا sqlalchemy میبینید.
۱۱- Singleton با وضعیت کپسولهشده
class AppSettings:
__instance = None
def __new__(cls):
if cls.__instance is None:
cls.__instance = super().__new__(cls)
cls.__instance.__data = {}
return cls.__instance
def set(self, key, value):
self.__data[key] = value
def get(self, key):
return self.__data.get(key)
s1 = AppSettings()
s1.set(“theme”, “dark”)
s2 = AppSettings()
print(s2.get(“theme”)) # dark — چون هر دو یک نمونهاند
۱۲- Logger با سطح دسترسی داخلی
class Logger:
def __init__(self, name):
self.__name = name
self.__logs = []
def log(self, message):
entry = f”[{self.__name}] {message}”
self.__logs.append(entry)
print(entry)
def history(self):
return list(self.__logs) # کپی، نه رفرنس مستقیم
نکته ظریف: history() یک کپی از لیست برمیگرداند، نه خودِ __logs. اگر رفرنس مستقیم را برگردانید، کاربر میتواند لاگها را از بیرون تغییر دهد — این یک اشتباه بسیار رایج است که در ادامه بیشتر توضیح داده میشود.

۱۳- کلاینت API با منطق تلاش مجدد (Retry) پنهان
import random
class APIClient:
def __init__(self, base_url, max_retries=3):
self.__base_url = base_url
self.__max_retries = max_retries
def __request(self):
# شبیهسازی: ۷۰٪ شانس خطا
return random.random() > 0.7
def get(self, endpoint):
for attempt in range(1, self.__max_retries + 1):
if self.__request():
return f”داده از {self.__base_url}{endpoint}”
raise ConnectionError(“درخواست پس از چند تلاش ناموفق بود”)
منطق retry کاملاً داخل کلاس پنهان است؛ کاربر فقط get() را صدا میزند و از پیچیدگی پشت صحنه بیخبر میماند.
بخش جدید: کپسولهسازی اعلانی با Pydantic v2
در پروژههای واقعی امروز (بهخصوص با FastAPI)، دیگر کمتر کسی برای هر فیلد یک @property و setter دستی مینویسد. بهجایش از Pydantic v2 استفاده میشود که اعتبارسنجی را بهصورت اعلانی (Declarative) و در سطح تعریف کلاس انجام میدهد — همان هدف encapsulation، اما با کد بسیار کمتر:
from pydantic import BaseModel, field_validator
class Contact(BaseModel):
email: str
@field_validator(“email”)
@classmethod
def validate_email(cls, value):
if “@” not in value:
raise ValueError(“ایمیل نامعتبر است”)
return value
c = Contact(email=”test@example.com”)
# Contact(email=”invalid”) # pydantic.ValidationError
نکته کلیدی: اینجا هم دقیقاً همان اصل encapsulation رعایت شده (داده هرگز در وضعیت نامعتبر ساخته نمیشود)، فقط منطق اعتبارسنجی بهجای متد دستی، به یک دکوراتور اعلانی منتقل شده. برای پروژههای جدید که ورودی از بیرون (API، فرم، فایل) میآید، این روش امروز رایجتر از نوشتن property دستی است.
بخش جدید (۲۰۲۶) dataclass(slots=True) بهجای __slots__ دستی
نوشتن __slots__ بهصورت دستی رایج بود، اما از پایتون ۳٫۱۰ به بعد میتوان همان مزیت (کاهش مصرف حافظه و جلوگیری از افزودن attribute ناخواسته) را با یک پارامتر ساده در dataclass گرفت — بدون نوشتن __slots__ بهصورت دستی و بدون خطر فراموش کردن هماهنگی آن با فیلدها:
from dataclasses import dataclass
@dataclass(slots=True, frozen=True)
class Point:
x: float
y: float
p = Point(1.0, 2.0)
# p.x = 5.0 # dataclasses.FrozenInstanceError
# p.z = 10 # AttributeError: چون slots اجازه فیلد جدید نمیدهد
ترکیب slots=True با frozen=True یک الگوی رایج در ۲۰۲۶ برای مدلهای دادهی تغییرناپذیر (immutable) است: هم دسترسی به فیلدهای تعریفنشده مسدود میشود، هم مقداردهی مجدد بعد از ساخت شیء. این دقیقاً همان فلسفهی کپسولهسازی است، اما در سطح ساختار کلاس، نه در سطح متد.

مقایسه عملی: کدام روش را کِی استفاده کنیم؟
| روش | مناسب برای | نقطه ضعف |
|---|---|---|
|
Getter/Setter سنتی (get_x(), set_x()) |
پروژههایی که باید با زبانهای دیگر (جاوا و…) هماهنگ باشند | در پایتون غیر ایدیومار و پرحجم است |
|
@property |
اعتبارسنجی، محاسبه مقدار در لحظه (مثل مثال دما)، read-only fields | برای کلاسهای سادهی دادهمحور کمی اضافهکاری است |
|
dataclass ساده (بدون property) |
مدلهای داده که فقط باید نگهداری شوند، بدون منطق خاص | کنترل و اعتبارسنجی ندارد مگر با __post_init__ |
|
dataclass(slots=True) |
مدلهای داده حجیم یا تعداد نمونهی بالا که نیاز به کاهش مصرف حافظه دارند | امکان اضافه کردن attribute پویا را از بین میبرد |
|
Pydantic v2 (BaseModel) |
دادهای که از بیرون (API، فرم، فایل کانفیگ) میآید و باید اعتبارسنجی شود | وابستگی خارجی اضافه؛ برای اسکریپتهای ساده زیادهروی است |
قاعده عملی بهروز: اگر داده از درون برنامه ساخته میشود و فقط نیاز به یک قانون ساده دارد → property. اگر داده حجم بالایی از نمونهها را شامل میشود و نیازی به اعتبارسنجی پیچیده نیست → dataclass(slots=True). اگر داده از بیرون (کاربر، API، فایل) میآید → Pydantic.
اشتباه رایجی که کمتر کسی دربارهاش میگوید: Getter/Setter بیمعنی
خیلی از برنامهنویسها (بهخصوص وقتی از جاوا به پایتون میآیند) برای هر attribute یک get_x() و set_x() مینویسند، بدون هیچ منطق اعتبارسنجی داخل آنها:
# نمونه بد — این واقعاً encapsulation نیست
class Product:
def __init__(self, price):
self.__price = price
def get_price(self):
return self.__price
def set_price(self, price):
self.__price = price # هیچ کنترلی ندارد!
این کد دقیقاً همان کاری را میکند که self.price عمومی میکرد، فقط با کد بیشتر. اگر setter شما هیچ قانونی اعمال نمیکند، آن attribute را عمومی بگذارید. encapsulation زمانی معنا دارد که پشت getter/setter یک قانون واقعی باشد — مثل مثال شماره ۵ (موجودی منفی نمیشود) یا مثال شماره ۴ (اعتبارسنجی ایمیل).
نسخه درست همان مثال بالا:
class Product:
def __init__(self, price):
self.price = price # عمومی، چون منطقی برای محدود کردنش نداریم

بخش جدید (۲۰۲۶): ابزارهای تحلیل استاتیک، مکمل کپسولهسازی قراردادی پایتون
از آنجا که پایتون attributeهای _ و __ را واقعاً قفل نمیکند، در تیمهای امروزی این کار به ابزارهای تحلیل استاتیک مثل mypy و pyright سپرده میشود. این ابزارها میتوانند دسترسی به یک attribute با پیشوند _ را از خارج کلاس هشدار بدهند، حتی اگر خود پایتون در زمان اجرا جلوی آن را نگیرد:
class Account:
def __init__(self, balance: float) -> None:
self._balance = balance
acc = Account(100)
acc._balance = -50 # پایتون اجرا میکند، اما pyright/mypy با تنظیمات سختگیرانه هشدار میدهد
این نکته بهخصوص وقتی اهمیت پیدا میکند که بخشی از کد توسط ابزارهای AI (مثل دستیارهای کدنویسی) تولید یا ویرایش میشود؛ چون این ابزارها معمولاً قرارداد نامگذاری _ و __ را رعایت میکنند، اما تضمین رعایت آن در بازبینی کد نهایی هنوز بر عهدهی pipeline بررسی کیفیت کد (CI با mypy/pyright/ruff) است، نه فقط چشم انسان.
جمعبندی: کِی از encapsulation استفاده کنیم؟
- وقتی داده باید همیشه در یک وضعیت معتبر بماند (موجودی، حقوق، ایمیل).
- وقتی میخواهید جزئیات پیادهسازی را بعداً بدون شکستن کد بیرونی تغییر دهید (مثال دما، اتصال دیتابیس).
- وقتی میخواهید از تغییر ناخواسته state جلوگیری کنید (لاگر، کش، rate limiter).
- وقتی داده از بیرون برنامه میآید (API، فرم، فایل کانفیگ) → به سراغ ابزارهای اعتبارسنجی اعلانی مثل Pydantic بروید، نه property دستی.
- نه فقط برای اینکه «همهجا private میگذارند» — اگر منطقی پشتش نیست، ساده نگهش دارید.
منابع معتبر برای مطالعه بیشتر
- Real Python – Encapsulation in Python
- مستندات رسمی پایتون درباره Private Variables و Name Mangling: docs.python.org (بخش Classes)
- PEP 8 – Style Guide for Python Code (بخش Naming Conventions)
- مستندات رسمی Pydantic v2 – Validators
- مستندات رسمی پایتون درباره پارامتر slots در dataclasses (بخش Data Classes، از نسخه ۳٫۱۰ به بعد)
- نظرسنجی توسعهدهندگان Stack Overflow ۲۰۲۵ — آمار رشد استفاده از پایتون
- شاخص TIOBE — رتبهبندی محبوبیت زبانهای برنامهنویسی (نسخه نوامبر ۲۰۲۵)







