Python Coding Academy
135 subscribers
7 photos
3 files
4 links
🐍 Learn Python. Build Skills. Create the Future.

📚 Python lessons from beginner to advanced
💻 Clear explanations & real-world examples
🚀 Learn to code. Learn to build.
Download Telegram
🐍 آموزش جامع Python — پست ۹۳
Decorator عمومی با *args و **kwargs
در پست ۹۰ یاد گرفتیم چگونه با *args و **kwargs یک Decorator را برای تابع‌های دارای آرگومان آماده کنیم.
حالا یک نکته مهم‌تر را می‌بینیم: یک Decorator عمومی می‌تواند روی تابع‌هایی با تعداد و نوع آرگومان‌های متفاوت کار کند.
🔹 یک Decorator برای چند تابع
from functools import wraps


def logger(function):

@wraps(function)
def wrapper(*args, **kwargs):
print("Function started")

result = function(*args, **kwargs)

print("Function finished")

return result

return wrapper

حالا همین Decorator را روی چند تابع متفاوت استفاده می‌کنیم:
@logger
def greet(name):
return f"Hello {name}"


@logger
def add(a, b):
return a + b


@logger
def welcome(name, language="English"):
return f"{name} - {language}"


print(greet("Ali"))
print(add(10, 5))
print(welcome("Sara", language="Persian"))

خروجی:
Function started
Function finished
Hello Ali

Function started
Function finished
15

Function started
Function finished
Sara - Persian

🔹 نقش *args و **kwargs
در این مثال:
def wrapper(*args, **kwargs):

wrapper می‌تواند آرگومان‌های مختلفی دریافت کند.
مثلاً:
greet("Ali")

یا:
add(10, 5)

یا:
welcome("Sara", language="Persian")

و سپس:
function(*args, **kwargs)

همان آرگومان‌ها را به تابع اصلی منتقل می‌کند.
🧠 چرا این موضوع مهم است؟
بدون *args و **kwargs ممکن بود مجبور شویم برای هر نوع تابع، Decorator متفاوتی بنویسیم.
اما با این الگو:
تابع با ۱ آرگومان ─┐
تابع با ۲ آرگومان ─┼→ یک Decorator
تابع با آرگومان نام‌دار ─┘

می‌توانیم یک Decorator عمومی داشته باشیم.
⚠️ نکته
این Decorator فقط به دلیل *args و **kwargs عمومی شده است؛ اما منطق داخل Decorator هنوز باید با هدف آن سازگار باشد.
🎯 تمرین
Decorator logger را روی این سه تابع امتحان کنید:
@logger
def square(x):
return x * x


@logger
def multiply(a, b):
return a * b


@logger
def greet(name, message="Hello"):
return f"{message}, {name}"

سپس هر سه تابع را با آرگومان‌های مختلف اجرا کنید.
پست ۹۳ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۹۴
ساخت Decorator با پارامتر
تا اینجا Decoratorهایی ساختیم که مستقیماً یک تابع را دریافت می‌کردند.
اما گاهی می‌خواهیم خود Decorator هم تنظیمات یا پارامتر دریافت کند.
مثلاً مشخص کنیم چند بار یک تابع اجرا شود.
🔹 مثال
می‌خواهیم Decoratorای بسازیم که تابع را به تعداد مشخص اجرا کند:
from functools import wraps


def repeat(times):

def decorator(function):

@wraps(function)
def wrapper(*args, **kwargs):
for _ in range(times):
function(*args, **kwargs)

return wrapper

return decorator

حالا:
@repeat(3)
def hello():
print("Hello")


hello()

خروجی:
Hello
Hello
Hello

🔹 اینجا چند لایه داریم
در:
@repeat(3)
def hello():
...

ابتدا:
repeat(3)
↓
decorator
↓
wrapper
↓
hello

repeat(3) تنظیمات Decorator را مشخص می‌کند.
سپس decorator تابع hello را دریافت می‌کند.
در نهایت wrapper هنگام اجرای hello() اجرا می‌شود.
🔹 یک مثال دیگر
می‌توانیم پیام دلخواه را به Decorator بدهیم:
def message(text):

def decorator(function):

@wraps(function)
def wrapper(*args, **kwargs):
print(text)
return function(*args, **kwargs)

return wrapper

return decorator


@message("Starting...")
def greet(name):
print(f"Hello {name}")


greet("Ali")

خروجی:
Starting...
Hello Ali

⚠️ نکته مهم
Decorator معمولی:
@logger

یک تابع را دریافت می‌کند.
اما Decorator دارای پارامتر:
@repeat(3)

ابتدا پارامتر را دریافت می‌کند و سپس Decorator ساخته می‌شود.
به همین دلیل یک لایه تابع بیشتر داریم.
🧠 خلاصه
@logger
→ Decorator معمولی

@repeat(3)
→ Decorator دارای پارامتر

ساختار کلی:
repeat(3)
↓
decorator(function)
↓
wrapper(*args, **kwargs)

🎯 تمرین
Decoratorای به نام repeat بسازید که تعداد اجرا را دریافت کند:
@repeat(5)
def hello():
print("Python")

خروجی باید "Python" را ۵ بار چاپ کند.
پست ۹۴ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۹۵
ترکیب Decorator معمولی و پارامتردار
حالا که هم Decorator معمولی و هم Decorator دارای پارامتر را می‌شناسیم، می‌توانیم آن‌ها را روی یک تابع با هم استفاده کنیم.
🔹 مثال
from functools import wraps


def log(function):

@wraps(function)
def wrapper(*args, **kwargs):
print("Function called")
return function(*args, **kwargs)

return wrapper


def repeat(times):

def decorator(function):

@wraps(function)
def wrapper(*args, **kwargs):
for _ in range(times):
function(*args, **kwargs)

return wrapper

return decorator

حالا:
@log
@repeat(2)
def greet(name):
print(f"Hello {name}")


greet("Ali")

خروجی:
Function called
Hello Ali
Hello Ali

🔹 ترتیب اعمال
این:
@log
@repeat(2)
def greet(name):
...

تقریباً معادل است با:
greet = log(repeat(2)(greet))

پس هنگام اعمال Decoratorها از پایین به بالا حرکت می‌کنیم:
greet
↓
repeat(2)
↓
log

اما هنگام اجرای تابع:
log
↓
repeat
↓
greet

🔹 اگر ترتیب را عوض کنیم
اگر بنویسیم:
@repeat(2)
@log
def greet(name):
print(f"Hello {name}")

ساختار تغییر می‌کند:
greet
↓
log
↓
repeat

در نتیجه خروجی:
Function called
Hello Ali
Function called
Hello Ali

چون این بار log داخل repeat قرار گرفته و همراه هر اجرای تکراری اجرا می‌شود.
⚠️ نکته مهم
دو Decorator ممکن است هر دو درست باشند، اما جابه‌جایی آن‌ها می‌تواند رفتار برنامه را تغییر دهد.
بنابراین همیشه به این دو سؤال توجه کنید:
۱. Decoratorها با چه ترتیبی اعمال می‌شوند؟
۲. هنگام اجرای تابع، کدام wrapper بیرونی‌تر است؟

🧠 خلاصه
@A
@B
def function():
...

تقریباً:
function = A(B(function))

بنابراین:
اعمال → B سپس A
اجرا → A سپس B سپس function

🎯 تمرین
با استفاده از log و repeat، دو حالت زیر را بسازید:
@log
@repeat(3)
def hello():
print("Hello")

و:
@repeat(3)
@log
def hello():
print("Hello")

قبل از اجرا، تفاوت تعداد دفعات "Function called" را حدس بزنید.
پست ۹۵ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۹۶
بررسی و تغییر مقدار بازگشتی با Decorator
تا اینجا دیدیم Decorator می‌تواند قبل یا بعد از اجرای تابع کاری انجام دهد.
اما می‌توانیم مقدار بازگشتی تابع (return) را هم دریافت و بررسی یا تغییر دهیم.
🔹 دریافت مقدار return
from functools import wraps

def check_result(function):
@wraps(function)
def wrapper(*args, **kwargs):
result = function(*args, **kwargs)

print("Result:", result)

return result

return wrapper

حالا:
@check_result
def add(a, b):
return a + b

x = add(10, 5)

print(x)

خروجی:
Result: 15
15

اینجا:
result = function(*args, **kwargs)

مقدار بازگشتی تابع اصلی را دریافت می‌کند.
🔹 تغییر مقدار بازگشتی
Decorator حتی می‌تواند مقدار return را تغییر دهد:
from functools import wraps

def double_result(function):
@wraps(function)
def wrapper(*args, **kwargs):
result = function(*args, **kwargs)

return result * 2

return wrapper

استفاده:
@double_result
def add(a, b):
return a + b

print(add(10, 5))

خروجی:
30

چون تابع اصلی 15 برمی‌گرداند و Decorator آن را دو برابر می‌کند.
🔹 چرا return result مهم است؟
اگر تابع اصلی مقداری برگرداند، اما Wrapper آن مقدار را return نکند، مقدار نهایی None خواهد بود.
مثلاً:
def decorator(function):
def wrapper():
result = function()
print(result)

return wrapper

اگر تابع اصلی این باشد:
@decorator
def get_number():
return 10

x = get_number()

print(x)

خروجی:
10
None

چون Wrapper فقط print() کرده و مقدار را برنگردانده است.
پس اگر می‌خواهیم مقدار بازگشتی حفظ شود:
return result

🧠 خلاصه
در یک Decorator می‌توانیم:
result = function(*args, **kwargs)

مقدار بازگشتی تابع را دریافت کنیم، سپس:
آن را بررسی کنیم.
تغییر دهیم.
یا بدون تغییر برگردانیم.
مثلاً:
result = function(*args, **kwargs)
return result

🎯 تمرین
Decorator زیر را کامل کن تا مقدار بازگشتی تابع را سه برابر کند:
def triple_result(function):
def wrapper(*args, **kwargs):
result = function(*args, **kwargs)

# کد اینجا

return wrapper

سپس:
@triple_result
def multiply(a, b):
return a * b

print(multiply(4, 5))

خروجی باید باشد:
60

پست ۹۶ از دوره جامع Python 🐍
1
🐍 آموزش جامع Python — پست ۹۷
ساخت Decorator با کلاس | __call__
تا اینجا Decoratorها را با تابع ساختیم.
در Python می‌توانیم یک Decorator را با کلاس هم بسازیم.
برای این کار، کلاس باید متد ویژه‌ی __call__ را داشته باشد.
🔹 مثال ساده
class MyDecorator:
def __init__(self, function):
self.function = function

def __call__(self):
print("Before")
self.function()
print("After")

حالا:
@MyDecorator
def hello():
print("Hello")

hello()

خروجی:
Before
Hello
After

🔹 چه اتفاقی افتاد؟
این:
@MyDecorator
def hello():
print("Hello")

تقریباً معادل است با:
hello = MyDecorator(hello)

بنابراین hello دیگر مستقیماً تابع اصلی نیست؛ بلکه یک شیء از کلاس MyDecorator است.
وقتی می‌نویسیم:
hello()

Python متد:
__call__()

را اجرا می‌کند.
🔹 Decorator کلاسی با return
می‌توانیم مقدار بازگشتی تابع را هم حفظ کنیم:
class MyDecorator:
def __init__(self, function):
self.function = function

def __call__(self, a, b):
print("Calculating...")
result = self.function(a, b)
return result

استفاده:
@MyDecorator
def add(a, b):
return a + b

print(add(10, 5))

خروجی:
Calculating...
15

⚠️ نکته مهم
در Decoratorهای کلاسی، __init__ معمولاً تابع اصلی را دریافت و ذخیره می‌کند:
self.function = function

و __call__ زمانی اجرا می‌شود که شیء Decorator را مانند تابع صدا بزنیم:
add(10, 5)

🧠 خلاصه
ساختار کلی:
class Decorator:
def __init__(self, function):
self.function = function

def __call__(self, *args, **kwargs):
return self.function(*args, **kwargs)

پس:
__init__ → تابع را دریافت می‌کند.
__call__ → هنگام اجرای تابع Decorated اجرا می‌شود.
self.function → به تابع اصلی اشاره می‌کند.
🎯 تمرین
یک Decorator کلاسی به نام UpperCase بساز که مقدار بازگشتی یک تابع را به حروف بزرگ تبدیل کند.
مثلاً:
@UpperCase
def message():
return "hello python"

خروجی:
HELLO PYTHON

پست ۹۷ از دوره جامع Python 🐍
1
🐍 آموزش جامع Python — پست ۹۸
استفاده از Decorator روی متدهای کلاس
حالا که Decoratorهای معمولی و کلاسی را می‌شناسیم، ببینیم چطور می‌توانیم یک Decorator را روی متدهای یک کلاس استفاده کنیم.
🔹 مثال ساده
from functools import wraps

def log_call(function):
@wraps(function)
def wrapper(*args, **kwargs):
print("Method called")
return function(*args, **kwargs)

return wrapper

حالا آن را روی یک متد قرار می‌دهیم:
class Calculator:

@log_call
def add(self, a, b):
return a + b

استفاده:
calc = Calculator()

print(calc.add(10, 5))

خروجی:
Method called
15

🔹 self چه اتفاقی می‌افتد؟
وقتی متد را این‌طور اجرا می‌کنیم:
calc.add(10, 5)

مقدار self به صورت خودکار به متد ارسال می‌شود.
به همین دلیل در Decorator بهتر است از:
*args, **kwargs

استفاده کنیم تا Decorator بتواند متدهای مختلف را بدون وابستگی به تعداد آرگومان‌ها مدیریت کند.
🔹 مثال کاربردی
فرض کنیم می‌خواهیم قبل از اجرای بعضی متدها پیام ثبت کنیم:
from functools import wraps

def log_call(function):
@wraps(function)
def wrapper(*args, **kwargs):
print(f"Calling {function.__name__}")
return function(*args, **kwargs)

return wrapper


class User:

@log_call
def login(self):
print("User logged in")

@log_call
def logout(self):
print("User logged out")

استفاده:
user = User()

user.login()
user.logout()

خروجی:
Calling login
User logged in
Calling logout
User logged out

🔹 مثال کامل show_result
حالا یک Decorator می‌سازیم که مقدار بازگشتی متد را دریافت و چاپ کند:
from functools import wraps

def show_result(function):
@wraps(function)
def wrapper(*args, **kwargs):
result = function(*args, **kwargs)

print(f"Result: {result}")

return result

return wrapper

استفاده در کلاس:
class Calculator:

@show_result
def multiply(self, a, b):
return a * b

حالا:
calc = Calculator()

result = calc.multiply(4, 6)

print("Returned value:", result)

خروجی:
Result: 24
Returned value: 24

اینجا:
result = function(*args, **kwargs)

مقدار 24 را از متد اصلی دریافت می‌کند.
سپس:
print(f"Result: {result}")

آن را نمایش می‌دهد.
و در نهایت:
return result

باعث می‌شود مقدار 24 همچنان به کد اصلی برگردد.
⚠️ نکته مهم
اگر این خط را حذف کنیم:
return result

متد Decorated دیگر مقدار 24 را به کد اصلی برنمی‌گرداند و نتیجه‌ی calc.multiply(4, 6) برابر None خواهد شد.
🧠 خلاصه
Decorator می‌تواند روی:
توابع معمولی
متدهای کلاس
متدهای دارای آرگومان
استفاده شود.
برای متدهای کلاس، استفاده از:
*args, **kwargs

باعث می‌شود Decorator انعطاف‌پذیرتر باشد.
🎯 تمرین
Decorator زیر را کامل کن تا نام متد و نتیجه‌ی آن را چاپ کند:
@show_result
def multiply(self, a, b):
return a * b

خروجی مورد انتظار:
multiply -> 24

پست ۹۸ از دوره جامع Python 🐍
1
🐍 آموزش جامع Python — پست ۹۹
متدهای Static در کلاس | @staticmethod
تا اینجا با متدهای معمولی کلاس آشنا شدیم؛ متدهایی که self دریافت می‌کنند.
اما گاهی متدی داریم که به هیچ شیء خاصی از کلاس نیاز ندارد.
در این حالت می‌توانیم از @staticmethod استفاده کنیم.
🔹 مثال ساده
class Calculator:

@staticmethod
def add(a, b):
return a + b

می‌توانیم بدون ساختن شیء از آن استفاده کنیم:
print(Calculator.add(10, 5))

خروجی:
15

🔹 تفاوت با متد معمولی
متد معمولی:
class Calculator:

def add(self, a, b):
return a + b

برای استفاده:
calc = Calculator()

print(calc.add(10, 5))

اما staticmethod نیازی به self ندارد:
class Calculator:

@staticmethod
def add(a, b):
return a + b

و می‌توانیم مستقیماً بنویسیم:
Calculator.add(10, 5)

🔹 آیا از طریق شیء هم می‌شود؟
بله:
calc = Calculator()

print(calc.add(10, 5))

خروجی:
15

اما نکته مهم این است که add() به اطلاعات داخل calc وابسته نیست.
🔹 مثال کاربردی
فرض کنیم کلاسی برای کار با کاربران داریم:
class User:

@staticmethod
def is_valid_age(age):
return age >= 18

استفاده:
print(User.is_valid_age(20))
print(User.is_valid_age(15))

خروجی:
True
False

این متد به self یا اطلاعات یک کاربر خاص نیاز ندارد؛ فقط یک مقدار دریافت می‌کند و نتیجه می‌دهد.
⚠️ نکته مهم
@staticmethod باعث می‌شود Python هنگام فراخوانی متد، self را به صورت خودکار ارسال نکند.
بنابراین:
@staticmethod
def add(a, b):
return a + b

با این ساختار:
def add(self, a, b):

متفاوت است.
🧠 خلاصه
اگر یک متد:
به self نیاز ندارد.
به اطلاعات نمونه‌ی کلاس وابسته نیست.
فقط یک عملیات مستقل مرتبط با کلاس انجام می‌دهد.
می‌توانیم از:
@staticmethod

استفاده کنیم.
مثلاً:
class Calculator:

@staticmethod
def add(a, b):
return a + b

فراخوانی:
Calculator.add(10, 5)

🎯 تمرین
کلاسی به نام Math بساز و یک staticmethod به نام square ایجاد کن که مربع یک عدد را برگرداند.
مثلاً:
print(Math.square(6))

خروجی:
36

پست ۹۹ از دوره جامع Python 🐍
1
🐍 آموزش جامع Python — پست ۱۰۰
متدهای Class در Python | @classmethod
در پست قبل با @staticmethod آشنا شدیم.
حالا نوع دیگری از متدها را یاد می‌گیریم: @classmethod.
این متد به جای self، یک پارامتر به نام cls دریافت می‌کند که به خود کلاس اشاره دارد.
🔹 مثال ساده
class Person:

count = 0

@classmethod
def show_count(cls):
return cls.count

استفاده:
print(Person.show_count())

خروجی:
0

اینجا cls به کلاس Person اشاره می‌کند.
🔹 تغییر مقدار Class Attribute
class Person:

count = 0

@classmethod
def increase_count(cls):
cls.count += 1

حالا:
Person.increase_count()
Person.increase_count()

print(Person.count)

خروجی:
2

🔹 تفاوت self و cls
در متد معمولی:
def method(self):

self به نمونه‌ی کلاس اشاره می‌کند.
اما در classmethod:
@classmethod
def method(cls):

cls به خود کلاس اشاره می‌کند.
🔹 یک کاربرد مهم: Alternative Constructor
یکی از کاربردهای رایج classmethod ساختن یک شیء با روش دیگری است.
class Person:

def __init__(self, name, age):
self.name = name
self.age = age

@classmethod
def from_string(cls, data):
name, age = data.split(",")
return cls(name, int(age))

حالا:
person = Person.from_string("Ali,25")

print(person.name)
print(person.age)

خروجی:
Ali
25

اینجا:
return cls(name, int(age))

یک نمونه‌ی جدید از کلاس ایجاد می‌کند.
⚠️ نکته مهم
سه حالت مهم را از هم جدا کن:
def method(self):

→ متد معمولی و مرتبط با نمونه
@classmethod
def method(cls):

→ متد مرتبط با کلاس
@staticmethod
def method():

→ متدی که به self و cls نیاز ندارد.
🧠 خلاصه
نوع متد
پارامتر اصلی
وابستگی
معمولی
self
نمونه
classmethod
cls
کلاس
staticmethod
ندارد
مستقل
🎯 تمرین
کلاس زیر را کامل کن:
class Product:

def __init__(self, name, price):
self.name = name
self.price = price

@classmethod
def from_string(cls, data):
# کد اینجا

طوری بنویس که:
product = Product.from_string("Laptop,1200")

print(product.name)
print(product.price)

خروجی:
Laptop
1200

✅ پاسخ کامل تمرین
class Product:

def __init__(self, name, price):
self.name = name
self.price = price

@classmethod
def from_string(cls, data):
name, price = data.split(",")
return cls(name, int(price))


product = Product.from_string("Laptop,1200")

print(product.name)
print(product.price)

خروجی:
Laptop
1200

در این مثال:
data.split(",")

رشته را به دو بخش تقسیم می‌کند:
Laptop
1200

سپس:
return cls(name, int(price))

با استفاده از cls یک شیء جدید از Product می‌سازد.
پست ۱۰۰ از دوره جامع Python 🐍
1
🐍 آموزش جامع Python — پست ۱۰۱
کنترل دسترسی به ویژگی‌ها با @property
در کلاس‌ها معمولاً ویژگی‌ها را مستقیماً از طریق شیء می‌خوانیم:
class Person:

def __init__(self, name):
self.name = name


person = Person("Ali")

print(person.name)

اما گاهی می‌خواهیم هنگام دسترسی به یک ویژگی، منطق خاصی اجرا شود.
برای این کار می‌توانیم از @property استفاده کنیم.
🔹 مثال ساده
class Person:

def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name

@property
def full_name(self):
return f"{self.first_name} {self.last_name}"

حالا:
person = Person("Ali", "Ahmadi")

print(person.full_name)

خروجی:
Ali Ahmadi

دقت کن که full_name را مثل ویژگی استفاده کردیم:
person.full_name

نه مثل تابع:
person.full_name()

🔹 چرا @property مفید است؟
بدون property ممکن بود مجبور شویم یک متد بنویسیم:
def get_full_name(self):
return f"{self.first_name} {self.last_name}"

و استفاده کنیم:
person.get_full_name()

اما با @property:
person.full_name

خواندن مقدار طبیعی‌تر و شبیه دسترسی به یک ویژگی معمولی است.
🔹 مثال کاربردی
class Rectangle:

def __init__(self, width, height):
self.width = width
self.height = height

@property
def area(self):
return self.width * self.height

استفاده:
rectangle = Rectangle(10, 5)

print(rectangle.area)

خروجی:
50

هر بار که rectangle.area را می‌خوانیم، مقدار مساحت محاسبه می‌شود.
⚠️ نکته مهم
متدی که با @property مشخص شده، هنگام استفاده بدون () فراخوانی می‌شود:
rectangle.area

نه:
rectangle.area()

🧠 خلاصه
@property به ما اجازه می‌دهد یک متد را مانند یک ویژگی استفاده کنیم.
@property
def area(self):
return self.width * self.height

سپس:
rectangle.area

🎯 تمرین
کلاس زیر را کامل کن:
class Circle:

def __init__(self, radius):
self.radius = radius

@property
def diameter(self):
# کد اینجا

طوری بنویس که:
circle = Circle(5)

print(circle.diameter)

خروجی:
10

✅ پاسخ کامل تمرین
class Circle:

def __init__(self, radius):
self.radius = radius

@property
def diameter(self):
return self.radius * 2


circle = Circle(5)

print(circle.diameter)

خروجی:
10

در اینجا:
return self.radius * 2

قطر دایره را محاسبه می‌کند.
چون radius برابر 5 است:
5 × 2 = 10

و به دلیل استفاده از @property، برای دسترسی به diameter نیازی به () نداریم:
circle.diameter

پست ۱۰۱ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۱۰۲
تغییر مقدار ویژگی با @property و Setter
در پست قبل یاد گرفتیم با @property می‌توانیم یک مقدار را مثل ویژگی بخوانیم.
اما اگر بخواهیم هنگام تغییر مقدار هم کنترل داشته باشیم، می‌توانیم از setter استفاده کنیم.
🔹 مثال ساده
class Person:

def __init__(self, age):
self._age = age

@property
def age(self):
return self._age

@age.setter
def age(self, value):
self._age = value

حالا:
person = Person(20)

print(person.age)

person.age = 25

print(person.age)

خروجی:
20
25

🔹 چرا Setter مفید است؟
می‌توانیم قبل از ذخیره مقدار، آن را اعتبارسنجی کنیم.
مثلاً سن نباید منفی باشد:
class Person:

def __init__(self, age):
self.age = age

@property
def age(self):
return self._age

@age.setter
def age(self, value):
if value < 0:
raise ValueError("Age cannot be negative")

self._age = value

استفاده:
person = Person(20)

person.age = 30

print(person.age)

خروجی:
30

اما:
person.age = -5

باعث خطای:
ValueError: Age cannot be negative

می‌شود.
🔹 چرا از _age استفاده کردیم؟
اگر داخل Setter بنویسیم:
self.age = value

Setter دوباره خودش را صدا می‌زند و وارد یک حلقه‌ی بازگشتی می‌شود.
به همین دلیل مقدار اصلی را در ویژگی دیگری مانند:
self._age

ذخیره می‌کنیم.
🧠 خلاصه
Getter:
@property
def age(self):
return self._age

برای خواندن مقدار است.
Setter:
@age.setter
def age(self, value):
self._age = value

برای تغییر مقدار است.
به این ترتیب می‌توانیم هنگام تغییر ویژگی، قوانین و اعتبارسنجی‌های موردنظرمان را اجرا کنیم.
🎯 تمرین
کلاس Product را طوری کامل کن که قیمت محصول نمی‌تواند منفی باشد:
class Product:

def __init__(self, price):
self.price = price

@property
def price(self):
# کد اینجا

@price.setter
def price(self, value):
# کد اینجا

باید این کد:
product = Product(100)

product.price = 150

print(product.price)

خروجی:
150

و این کد:
product.price = -20

خطای ValueError ایجاد کند.
✅ پاسخ کامل تمرین
class Product:

def __init__(self, price):
self.price = price

@property
def price(self):
return self._price

@price.setter
def price(self, value):
if value < 0:
raise ValueError("Price cannot be negative")

self._price = value


product = Product(100)

print(product.price)

product.price = 150

print(product.price)

خروجی:
100
150

اگر مقدار منفی وارد کنیم:
product.price = -20

خروجی خطا خواهد بود:
ValueError: Price cannot be negative

در این مثال:
self.price = price

در __init__ باعث می‌شود Setter نیز برای مقدار اولیه اجرا شود.
سپس Getter مقدار ذخیره‌شده در:
self._price

را برمی‌گرداند.
پست ۱۰۲ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۱۰۳
حذف ویژگی با @property و deleter
در پست قبل یاد گرفتیم با setter می‌توانیم تغییر مقدار یک ویژگی را کنترل کنیم.
حالا می‌توانیم عملیات حذف آن ویژگی را هم کنترل کنیم.
برای این کار از @property همراه با deleter استفاده می‌کنیم.
🔹 مثال ساده
class Person:

def __init__(self, name):
self._name = name

@property
def name(self):
return self._name

@name.deleter
def name(self):
del self._name

حالا:
person = Person("Ali")

print(person.name)

del person.name

قبل از حذف:
Ali

بعد از:
del person.name

ویژگی _name از شیء حذف می‌شود.
🔹 چرا Deleter مفید است؟
گاهی نمی‌خواهیم یک ویژگی بدون کنترل حذف شود.
مثلاً می‌توانیم هنگام حذف، یک عملیات اضافی انجام دهیم:
class User:

def __init__(self, username):
self._username = username

@property
def username(self):
return self._username

@username.deleter
def username(self):
print("Username deleted")
del self._username

استفاده:
user = User("Ali")

print(user.username)

del user.username

خروجی:
Ali
Username deleted

🔹 سه بخش یک Property
حالا می‌توانیم یک property کامل داشته باشیم:
class Product:

def __init__(self, price):
self._price = price

@property
def price(self):
return self._price

@price.setter
def price(self, value):
if value < 0:
raise ValueError("Price cannot be negative")

self._price = value

@price.deleter
def price(self):
del self._price

اینجا:
@property        → خواندن
@price.setter → تغییر
@price.deleter → حذف

🔹 استفاده
product = Product(100)

print(product.price)

product.price = 200

print(product.price)

del product.price

در اینجا هر سه عملیات توسط property کنترل می‌شوند.
⚠️ نکته مهم
بعد از:
del product.price

دیگر _price وجود ندارد.
بنابراین اگر دوباره بنویسیم:
print(product.price)

با خطای AttributeError مواجه می‌شویم.
🧠 خلاصه
یک property می‌تواند سه رفتار داشته باشد:
@property
def value(self):
...

برای خواندن
@value.setter
def value(self, new_value):
...

برای تغییر
@value.deleter
def value(self):
...

برای حذف
🎯 تمرین
کلاس Account را کامل کن تا ویژگی username قابلیت خواندن، تغییر و حذف داشته باشد:
class Account:

def __init__(self, username):
self._username = username

@property
def username(self):
# کد اینجا

@username.setter
def username(self, value):
# کد اینجا

@username.deleter
def username(self):
# کد اینجا

سپس این کد را اجرا کن:
account = Account("Ali")

print(account.username)

account.username = "Reza"
print(account.username)

del account.username

پست ۱۰۳ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۱۰۴
نمایش بهتر اشیاء با __str__ و __repr__
حالا که با کلاس‌ها و @property آشنا شدیم، می‌خواهیم نحوه نمایش Objectها را کنترل کنیم.
به طور پیش‌فرض اگر یک شیء را چاپ کنیم، Python اطلاعاتی شبیه آدرس آن شیء نمایش می‌دهد.
برای کنترل این رفتار از دو متد ویژه استفاده می‌کنیم:
__str__
__repr__

🔹 __str__
__str__ مشخص می‌کند وقتی شیء را با print() نمایش می‌دهیم، چه متنی نشان داده شود.
class Person:

def __init__(self, name, age):
self.name = name
self.age = age

def __str__(self):
return f"{self.name} - {self.age} years old"

حالا:
person = Person("Ali", 25)

print(person)

خروجی:
Ali - 25 years old

🔹 اگر __str__ نداشته باشیم
مثلاً:
class Person:

def __init__(self, name):
self.name = name


person = Person("Ali")

print(person)

خروجی شبیه این خواهد بود:
<__main__.Person object at 0x...>

این خروجی برای کاربر چندان مفید نیست.
با __str__ می‌توانیم نمایش خواناتری داشته باشیم.
🔹 __repr__
__repr__ برای ارائه یک نمایش دقیق‌تر و مناسب برای توسعه‌دهنده از شیء استفاده می‌شود.
class Person:

def __init__(self, name, age):
self.name = name
self.age = age

def __str__(self):
return f"{self.name} ({self.age})"

def __repr__(self):
return f"Person(name={self.name!r}, age={self.age!r})"

حالا:
person = Person("Ali", 25)

print(person)
print(repr(person))

خروجی:
Ali (25)
Person(name='Ali', age=25)

🔹 تفاوت str() و repr()
print(str(person))
print(repr(person))

به ترتیب:
Ali (25)
Person(name='Ali', age=25)

پس:
str  → نمایش خواناتر برای کاربر
repr → نمایش دقیق‌تر برای توسعه‌دهنده

⚠️ نکته مهم
اگر __str__ را تعریف نکرده باشیم، Python در بسیاری از موارد برای نمایش شیء می‌تواند از __repr__ استفاده کند.
اما __str__ و __repr__ هدف یکسانی ندارند:
__str__ → نمایش دوستانه و قابل‌خواندن
__repr__ → نمایش دقیق‌تر و مناسب توسعه و دیباگ
🧠 خلاصه
def __str__(self):
return "..."

برای:
print(object)

و:
def __repr__(self):
return "..."

برای:
repr(object)

🎯 تمرین
کلاس زیر را کامل کن:
class Product:

def __init__(self, name, price):
self.name = name
self.price = price

def __str__(self):
# کد اینجا

def __repr__(self):
# کد اینجا

طوری بنویس که:
product = Product("Laptop", 1200)

print(product)
print(repr(product))

خروجی شبیه این باشد:
Laptop - $1200
Product(name='Laptop', price=1200)

✅ پاسخ کامل تمرین
class Product:

def __init__(self, name, price):
self.name = name
self.price = price

def __str__(self):
return f"{self.name} - ${self.price}"

def __repr__(self):
return f"Product(name={self.name!r}, price={self.price!r})"


product = Product("Laptop", 1200)

print(product)
print(repr(product))

خروجی:
Laptop - $1200
Product(name='Laptop', price=1200)

در __str__:
return f"{self.name} - ${self.price}"

نمایش خوانا و مناسب برای کاربر ساخته می‌شود.
در __repr__:
return f"Product(name={self.name!r}, price={self.price!r})"

نمایش دقیق‌تری از نوع شیء و مقادیر آن ایجاد می‌کنیم.
پست ۱۰۴ از دوره جامع Python 🐍
1
🐍 آموزش جامع Python — پست ۱۰۵
تعیین طول شیء با __len__
در پست قبل با __str__ و __repr__ آشنا شدیم.
حالا یکی دیگر از متدهای جادویی (Magic Methods) را یاد می‌گیریم:
__len__

این متد مشخص می‌کند وقتی از len() روی یک شیء استفاده می‌کنیم، چه مقداری برگردانده شود.
🔹 استفاده از __len__
مثلاً می‌توانیم برای یک کلاس Playlist تعداد آهنگ‌ها را مشخص کنیم:
class Playlist:

def __init__(self, songs):
self.songs = songs

def __len__(self):
return len(self.songs)

حالا:
playlist = Playlist(["Song 1", "Song 2", "Song 3"])

print(len(playlist))

خروجی:
3

🔹 چرا __len__؟
Python نمی‌داند طول یک شیء ساخته‌شده از کلاس ما چقدر است.
با تعریف:
def __len__(self):

مشخص می‌کنیم len() چگونه مقدار طول را محاسبه کند.
مثلاً:
class Team:

def __init__(self, members):
self.members = members

def __len__(self):
return len(self.members)


team = Team(["Ali", "Sara", "Reza", "Mina"])

print(len(team))

خروجی:
4

⚠️ نکته مهم
__len__ باید یک عدد صحیح (int) غیرمنفی برگرداند.
مثلاً این اشتباه است:
def __len__(self):
return "5"

چون "5" یک رشته است، نه عدد.
🔹 ارتباط با bool
یک نکته جالب:
اگر کلاس __bool__ نداشته باشد، Python می‌تواند از __len__ برای تعیین True یا False بودن شیء استفاده کند.
class Team:

def __init__(self, members):
self.members = members

def __len__(self):
return len(self.members)


team = Team([])

print(bool(team))

خروجی:
False

چون طول شیء 0 است.
اگر عضو داشته باشیم:
team = Team(["Ali"])

print(bool(team))

خروجی:
True

🎯 تمرین
کلاس Cart را کامل کن تا len() تعداد محصولات سبد خرید را برگرداند:
class Cart:

def __init__(self, products):
self.products = products

def __len__(self):
# کد اینجا


cart = Cart(["Laptop", "Mouse", "Keyboard"])

print(len(cart))

خروجی مورد انتظار:
3

پست ۱۰۵ از دوره جامع Python 🐍
1
🐍 آموزش جامع Python — پست ۱۰۶
مقایسه اشیاء با __eq__
تا اینجا دیدیم چطور رفتار شیء را با متدهایی مثل __str__ و __len__ کنترل کنیم.
حالا می‌خواهیم مشخص کنیم دو شیء چه زمانی برابر محسوب می‌شوند.
برای این کار از متد جادویی زیر استفاده می‌کنیم:
__eq__

🔹 __eq__ چیست؟
وقتی از == استفاده می‌کنیم، Python می‌تواند از __eq__ برای مقایسه دو شیء استفاده کند.
مثلاً:
class Product:

def __init__(self, name, price):
self.name = name
self.price = price

def __eq__(self, other):
return self.name == other.name and self.price == other.price

حالا:
product1 = Product("Laptop", 1200)
product2 = Product("Laptop", 1200)

print(product1 == product2)

خروجی:
True

چون name و price هر دو برابر هستند.
🔹 اگر مقدارها متفاوت باشند
product1 = Product("Laptop", 1200)
product2 = Product("Laptop", 1500)

print(product1 == product2)

خروجی:
False

🔹 چرا این متد مهم است؟
بدون پیاده‌سازی __eq__، دو شیء مستقل معمولاً فقط به دلیل اینکه مقادیر مشابهی دارند، برابر در نظر گرفته نمی‌شوند.
class Product:

def __init__(self, name, price):
self.name = name
self.price = price


product1 = Product("Laptop", 1200)
product2 = Product("Laptop", 1200)

print(product1 == product2)

در این حالت نتیجه معمولاً:
False

است؛ چون product1 و product2 دو شیء جداگانه هستند.
🔹 پارامتر other
در این متد:
def __eq__(self, other):

self به شیء سمت چپ و other به شیء سمت راست مقایسه اشاره می‌کند.
مثلاً:
product1 == product2

تقریباً به این شکل بررسی می‌شود:
product1.__eq__(product2)

⚠️ نکته مهم
باید مراقب نوع other باشیم.
مثلاً این کد:
product == 100

ممکن است باعث خطا شود، چون 100 ویژگی‌های name و price را ندارد.
یک روش امن‌تر:
def __eq__(self, other):

if not isinstance(other, Product):
return NotImplemented

return self.name == other.name and self.price == other.price

NotImplemented به Python می‌گوید این مقایسه را با این نوع شیء نمی‌توانیم انجام دهیم.
🎯 تمرین
کلاس User را طوری کامل کن که دو کاربر وقتی username و age یکسان دارند، برابر محسوب شوند:
class User:

def __init__(self, username, age):
self.username = username
self.age = age

def __eq__(self, other):
# کد اینجا


user1 = User("ali", 25)
user2 = User("ali", 25)
user3 = User("sara", 25)

print(user1 == user2)
print(user1 == user3)

خروجی مورد انتظار:
True
False

✅ پاسخ کامل تمرین
class User:

def __init__(self, username, age):
self.username = username
self.age = age

def __eq__(self, other):

if not isinstance(other, User):
return NotImplemented

return (
self.username == other.username
and self.age == other.age
)


user1 = User("ali", 25)
user2 = User("ali", 25)
user3 = User("sara", 25)

print(user1 == user2)
print(user1 == user3)

خروجی:
True
False

در این قسمت:
self.username == other.username

نام کاربری دو شیء را مقایسه می‌کنیم.
و در این قسمت:
self.age == other.age

سن دو شیء را مقایسه می‌کنیم.
در نهایت با and هر دو شرط باید True باشند تا دو کاربر برابر محسوب شوند.
پست ۱۰۶ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۱۰۷
مقایسه اشیاء با __lt__
در پست قبل یاد گرفتیم با __eq__ مشخص کنیم دو شیء چه زمانی برابر هستند.
حالا می‌خواهیم رفتار عملگر < را برای اشیای کلاس خودمان تعریف کنیم.
برای این کار از:
__lt__

استفاده می‌کنیم.
🔹 __lt__ چیست؟
__lt__ مخفف Less Than است.
یعنی:
<

مثلاً می‌توانیم محصولات را بر اساس قیمت مقایسه کنیم:
class Product:

def __init__(self, name, price):
self.name = name
self.price = price

def __lt__(self, other):
return self.price < other.price

حالا:
product1 = Product("Mouse", 20)
product2 = Product("Keyboard", 50)

print(product1 < product2)

خروجی:
True

چون:
20 < 50

🔹 مقایسه برعکس
print(product2 < product1)

خروجی:
False

چون:
50 < 20

صحیح نیست.
🔹 __lt__ چگونه اجرا می‌شود؟
وقتی می‌نویسیم:
product1 < product2

Python تقریباً این متد را فراخوانی می‌کند:
product1.__lt__(product2)

بنابراین:
self

به product1 و:
other

به product2 اشاره می‌کند.
⚠️ نکته مهم
__lt__ فقط رفتار < را تعریف می‌کند.
یعنی با تعریف آن، لزوماً تمام عملگرهای مقایسه‌ای دیگر (>, <=, >=) را برای کلاس تعریف نکرده‌ایم.
هرکدام از این رفتارها متدهای ویژه مربوط به خودشان را دارند.
🔹 مثال کاربردی
می‌توانیم دانش‌آموزها را بر اساس نمره مقایسه کنیم:
class Student:

def __init__(self, name, score):
self.name = name
self.score = score

def __lt__(self, other):
return self.score < other.score


student1 = Student("Ali", 15)
student2 = Student("Sara", 18)

print(student1 < student2)

خروجی:
True

چون نمره Ali کمتر از نمره Sara است.
🎯 تمرین
کلاس Book را کامل کن تا دو کتاب بر اساس قیمت با < مقایسه شوند:
class Book:

def __init__(self, title, price):
self.title = title
self.price = price

def __lt__(self, other):
# کد اینجا


book1 = Book("Python", 30)
book2 = Book("Django", 45)

print(book1 < book2)
print(book2 < book1)

خروجی مورد انتظار:
True
False

پست ۱۰۷ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۱۰۸
مقایسه اشیاء با __gt__
در پست قبل با __lt__ یاد گرفتیم رفتار عملگر < را برای اشیای کلاس خودمان تعریف کنیم.
حالا نوبت عملگر بزرگ‌تر است:
>

برای تعریف این رفتار از متد جادویی:
__gt__

استفاده می‌کنیم.
🔹 __gt__ چیست؟
__gt__ مخفف Greater Than است.
مثلاً می‌توانیم محصولات را بر اساس قیمت مقایسه کنیم:
class Product:

def __init__(self, name, price):
self.name = name
self.price = price

def __gt__(self, other):
return self.price > other.price

حالا:
product1 = Product("Laptop", 1200)
product2 = Product("Mouse", 50)

print(product1 > product2)

خروجی:
True

چون:
1200 > 50

🔹 مقایسه برعکس
print(product2 > product1)

خروجی:
False

چون:
50 > 1200

صحیح نیست.
🔹 __gt__ چگونه اجرا می‌شود؟
وقتی می‌نویسیم:
product1 > product2

Python از متد مقایسه‌ای مربوط به این عملگر استفاده می‌کند؛ در حالت معمول:
product1.__gt__(product2)

بنابراین:
self

به شیء سمت چپ و:
other

به شیء سمت راست اشاره می‌کند.
🔹 مثال با دانش‌آموز
class Student:

def __init__(self, name, score):
self.name = name
self.score = score

def __gt__(self, other):
return self.score > other.score


student1 = Student("Ali", 18)
student2 = Student("Sara", 15)

print(student1 > student2)

خروجی:
True

چون نمره 18 از 15 بیشتر است.
⚠️ نکته مهم
__gt__ فقط رفتار عملگر:
>

را برای کلاس تعریف می‌کند.
برای عملگرهایی مانند:
<   <=   >=

متدهای مقایسه‌ای دیگری وجود دارند.
در ادامه با آن‌ها آشنا می‌شویم.
🎯 تمرین
کلاس Car را کامل کن تا خودروها بر اساس قیمت با > مقایسه شوند:
class Car:

def __init__(self, model, price):
self.model = model
self.price = price

def __gt__(self, other):
# کد اینجا


car1 = Car("BMW", 50000)
car2 = Car("Toyota", 30000)

print(car1 > car2)
print(car2 > car1)

خروجی مورد انتظار:
True
False

پست ۱۰۸ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۱۰۹
مقایسه اشیاء با __le__ و __ge__
در دو پست قبل با:
__lt__
__gt__

رفتار < و > را برای اشیای کلاس تعریف کردیم.
حالا دو عملگر دیگر را یاد می‌گیریم:
<=
>=

🔹 __le__
__le__ مخفف Less Than or Equal است.
یعنی:
کوچک‌تر یا مساوی

مثلاً:
class Product:

def __init__(self, name, price):
self.name = name
self.price = price

def __le__(self, other):
return self.price <= other.price

حالا:
product1 = Product("Mouse", 50)
product2 = Product("Keyboard", 50)

print(product1 <= product2)

خروجی:
True

چون:
50 <= 50

صحیح است.
🔹 __ge__
__ge__ مخفف Greater Than or Equal است.
یعنی:
بزرگ‌تر یا مساوی

مثلاً:
class Product:

def __init__(self, name, price):
self.name = name
self.price = price

def __ge__(self, other):
return self.price >= other.price

حالا:
product1 = Product("Laptop", 1200)
product2 = Product("Mouse", 50)

print(product1 >= product2)

خروجی:
True

چون:
1200 >= 50

صحیح است.
🔹 یک مثال کامل
می‌توانیم هر دو متد را در یک کلاس داشته باشیم:
class Student:

def __init__(self, name, score):
self.name = name
self.score = score

def __le__(self, other):
return self.score <= other.score

def __ge__(self, other):
return self.score >= other.score


student1 = Student("Ali", 18)
student2 = Student("Sara", 18)

print(student1 <= student2)
print(student1 >= student2)

خروجی:
True
True

چون نمره هر دو دانش‌آموز 18 است.
🧠 متدهای مقایسه‌ای مهم
تا اینجا با این متدها آشنا شدیم:
__eq__  → ==
__lt__ → <
__gt__ → >
__le__ → <=
__ge__ → >=

این متدها به Python می‌گویند عملگرهای مقایسه‌ای با اشیای کلاس ما چگونه رفتار کنند.
🎯 تمرین
کلاس Book را کامل کن تا دو کتاب بر اساس قیمت با <= و >= مقایسه شوند:
class Book:

def __init__(self, title, price):
self.title = title
self.price = price

def __le__(self, other):
# کد اینجا

def __ge__(self, other):
# کد اینجا


book1 = Book("Python", 30)
book2 = Book("Django", 45)

print(book1 <= book2)
print(book1 >= book2)

خروجی مورد انتظار:
True
False

✅ پاسخ کامل تمرین
class Book:

def __init__(self, title, price):
self.title = title
self.price = price

def __le__(self, other):
return self.price <= other.price

def __ge__(self, other):
return self.price >= other.price


book1 = Book("Python", 30)
book2 = Book("Django", 45)

print(book1 <= book2)
print(book1 >= book2)

خروجی:
True
False

در این قسمت:
return self.price <= other.price

قیمت کتاب سمت چپ با قیمت کتاب سمت راست مقایسه می‌شود.
و در:
return self.price >= other.price

برعکس، بررسی می‌کنیم که قیمت کتاب سمت چپ بزرگ‌تر یا مساوی قیمت کتاب سمت راست باشد.
پست ۱۰۹ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۱۱۰
بررسی نابرابری اشیاء با __ne__
در پست‌های قبل با متدهای مقایسه‌ای مختلف آشنا شدیم.
حالا می‌خواهیم رفتار عملگر:
!=

را برای اشیای کلاس خودمان تعریف کنیم.
برای این کار از متد جادویی:
__ne__

استفاده می‌کنیم.
🔹 __ne__ چیست؟
__ne__ مخفف Not Equal است.
یعنی بررسی می‌کند آیا دو شیء با هم متفاوت هستند یا نه.
مثلاً:
class Product:

def __init__(self, name, price):
self.name = name
self.price = price

def __ne__(self, other):
return (
self.name != other.name
or self.price != other.price
)

حالا:
product1 = Product("Laptop", 1200)
product2 = Product("Laptop", 1500)

print(product1 != product2)

خروجی:
True

چون قیمت دو محصول متفاوت است.
🔹 وقتی هر دو مقدار یکسان باشند
product1 = Product("Laptop", 1200)
product2 = Product("Laptop", 1200)

print(product1 != product2)

خروجی:
False

چون دو محصول از نظر name و price یکسان هستند.
🔹 __ne__ چگونه اجرا می‌شود؟
وقتی می‌نویسیم:
product1 != product2

Python می‌تواند متد زیر را فراخوانی کند:
product1.__ne__(product2)

بنابراین:
self  → شیء سمت چپ
other → شیء سمت راست

⚠️ نکته مهم
در مثال بالا از or استفاده کردیم:
self.name != other.name or self.price != other.price

چون اگر حتی یکی از این دو مقدار متفاوت باشد، دو محصول متفاوت محسوب می‌شوند.
مثلاً:
name یکسان + price متفاوت → True
name متفاوت + price یکسان → True
name متفاوت + price متفاوت → True
name یکسان + price یکسان → False

🧠 متدهای مقایسه‌ای تا اینجا
__eq__  → ==
__ne__ → !=
__lt__ → <
__gt__ → >
__le__ → <=
__ge__ → >=

با این متدها می‌توانیم رفتار اصلی عملگرهای مقایسه‌ای را برای کلاس خودمان تعریف کنیم.
🎯 تمرین
کلاس User را کامل کن تا دو کاربر وقتی username یا age متفاوت دارند، متفاوت محسوب شوند:
class User:

def __init__(self, username, age):
self.username = username
self.age = age

def __ne__(self, other):
# کد اینجا


user1 = User("ali", 25)
user2 = User("ali", 25)
user3 = User("sara", 25)

print(user1 != user2)
print(user1 != user3)

خروجی مورد انتظار:
False
True

✅ پاسخ کامل تمرین
class User:

def __init__(self, username, age):
self.username = username
self.age = age

def __ne__(self, other):
if not isinstance(other, User):
return NotImplemented

return (
self.username != other.username
or self.age != other.age
)


user1 = User("ali", 25)
user2 = User("ali", 25)
user3 = User("sara", 25)

print(user1 != user2)
print(user1 != user3)

خروجی:
False
True

در این قسمت:
self.username != other.username

نام کاربری دو شیء مقایسه می‌شود.
و:
self.age != other.age

سن دو شیء مقایسه می‌شود.
از or استفاده می‌کنیم، چون متفاوت بودن حتی یکی از ویژگی‌ها کافی است تا دو کاربر متفاوت محسوب شوند.
پست ۱۱۰ از دوره جامع Python 🐍
1
🐍 آموزش جامع Python — پست ۱۱۱
کنترل عملگر in با __contains__
تا اینجا چندین Magic Method برای کنترل رفتار اشیاء یاد گرفتیم.
حالا می‌خواهیم مشخص کنیم وقتی از عملگر:
in

برای یک شیء استفاده می‌کنیم، Python چگونه بررسی کند که یک مقدار داخل آن شیء وجود دارد یا نه.
برای این کار از:
__contains__

استفاده می‌کنیم.
🔹 __contains__ چیست؟
این متد رفتار عملگر in را برای شیء مشخص می‌کند.
مثلاً یک کلاس Playlist می‌سازیم:
class Playlist:

def __init__(self, songs):
self.songs = songs

def __contains__(self, song):
return song in self.songs

حالا:
playlist = Playlist([
"Song A",
"Song B",
"Song C"
])

print("Song B" in playlist)
print("Song X" in playlist)

خروجی:
True
False

🔹 not in
وقتی __contains__ را تعریف کنیم، می‌توانیم از not in هم استفاده کنیم:
print("Song X" not in playlist)

خروجی:
True

🔹 __contains__ چگونه اجرا می‌شود؟
وقتی می‌نویسیم:
"Song B" in playlist

Python از رفتار تعریف‌شده در:
playlist.__contains__("Song B")

استفاده می‌کند.
در این متد:
def __contains__(self, song):

پارامتر song همان مقداری است که می‌خواهیم وجود آن را بررسی کنیم.
🔹 مثال با UserGroup
می‌توانیم بررسی کنیم آیا یک نام کاربری در گروه وجود دارد یا نه:
class UserGroup:

def __init__(self, users):
self.users = users

def __contains__(self, username):
return username in self.users


group = UserGroup(["ali", "sara", "reza"])

print("ali" in group)
print("mina" in group)

خروجی:
True
False

⚠️ نکته مهم
__contains__ باید نتیجه‌ای مناسب برای بررسی وجود داشتن برگرداند؛ معمولاً:
True

یا:
False

در واقع می‌توانیم منطق بررسی را کاملاً مطابق نیاز کلاس خودمان تعریف کنیم.
🧠 چند Magic Method مهم تا اینجا
__str__       → print()
__repr__ → repr()
__len__ → len()
__eq__ → ==
__ne__ → !=
__lt__ → <
__gt__ → >
__le__ → <=
__ge__ → >=
__contains__ → in

این متدها به ما اجازه می‌دهند رفتار طبیعی Python را برای کلاس‌های خودمان شخصی‌سازی کنیم.
🎯 تمرین
کلاس Library را کامل کن تا بتوانیم با in بررسی کنیم آیا یک کتاب در کتابخانه وجود دارد یا نه:
class Library:

def __init__(self, books):
self.books = books

def __contains__(self, title):
# کد اینجا


library = Library([
"Python",
"Django",
"Flask"
])

print("Python" in library)
print("Java" in library)

خروجی مورد انتظار:
True
False

پست ۱۱۱ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۱۱۲
دسترسی به عناصر شیء با __getitem__
تا اینجا یاد گرفتیم با __contains__ رفتار in را برای یک شیء کنترل کنیم.
حالا می‌خواهیم کاری کنیم که بتوانیم با استفاده از [] به عناصر یک شیء دسترسی داشته باشیم.
برای این کار از __getitem__ استفاده می‌کنیم.
🔹 __getitem__
وقتی Python چنین کدی می‌بیند:
object[index]

در واقع می‌تواند متد زیر را صدا بزند:
object.__getitem__(index)

🔹 مثال ساده
class Playlist:
def __init__(self, songs):
self.songs = songs

def __getitem__(self, index):
return self.songs[index]


playlist = Playlist([
"Song A",
"Song B",
"Song C"
])

print(playlist[0])
print(playlist[1])

خروجی:
Song A
Song B

در اینجا:
playlist[0]

باعث می‌شود Python از __getitem__ استفاده کند.
🔹 پشتیبانی از Index منفی
چون در مثال بالا دسترسی نهایی به یک list انجام می‌شود، Index منفی هم کار می‌کند:
print(playlist[-1])

خروجی:
Song C

🔹 پشتیبانی از Slice
__getitem__ فقط برای یک Index نیست؛ می‌توانیم Slice هم دریافت کنیم:
print(playlist[0:2])

خروجی:
['Song A', 'Song B']

چون مقدار 0:2 به عنوان index به __getitem__ ارسال می‌شود.
⚠️ نکته مهم
__getitem__ خودش الزاماً داده‌ها را نگهداری نمی‌کند.
در مثال ما:
self.songs

داده‌ها را نگهداری می‌کند و __getitem__ فقط نحوه دسترسی به آن‌ها را مشخص می‌کند.
🧠 خلاصه
obj[index]
↓
__getitem__(index)

مثلاً:
playlist[1]

تقریباً معادل:
playlist.__getitem__(1)

است.
🎯 تمرین
کلاس Library را کامل کن تا بتوانیم با Index به کتاب‌ها دسترسی داشته باشیم:
class Library:
def __init__(self, books):
self.books = books

def __getitem__(self, index):
# code


library = Library([
"Python",
"Django",
"Flask"
])

print(library[0])
print(library[2])

خروجی مورد انتظار:
Python
Flask

پست ۱۱۲ از دوره جامع Python 🐍