برنامه نویسی

۱۳ مثال کپسوله‌سازی (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 است، نه فقط تئوری.

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) است: هم دسترسی به فیلدهای تعریف‌نشده مسدود می‌شود، هم مقداردهی مجدد بعد از ساخت شیء. این دقیقاً همان فلسفه‌ی کپسوله‌سازی است، اما در سطح ساختار کلاس، نه در سطح متد.

بخش جدید (۲۰۲۶) dataclass(slots=True) به‌جای __slots__ دستی

مقایسه عملی: کدام روش را کِی استفاده کنیم؟

روش مناسب برای نقطه ضعف

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 می‌گذارند» — اگر منطقی پشتش نیست، ساده نگهش دارید.

منابع معتبر برای مطالعه بیشتر

کوانتوم سافت

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

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

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

نوشته های مشابه

دکمه بازگشت به بالا