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 — پست ۷۹
تغییر متغیر تابع بیرونی با nonlocal
در پست قبل با بخش Enclosing در قانون LEGB آشنا شدیم.
حالا می‌بینیم اگر بخواهیم داخل یک تابع داخلی، متغیر تابع بیرونی را تغییر دهیم، از nonlocal استفاده می‌کنیم.
🔹 مثال ساده
def outer():
count = 0

def inner():
nonlocal count
count += 1

inner()
print(count)

outer()

خروجی:
1

بدون nonlocal، دستور:
count += 1

باعث می‌شود Python آن را به‌عنوان استفاده از یک متغیر محلی در inner در نظر بگیرد و خطا ایجاد شود.
🔹 چند بار تغییر مقدار
def counter():
count = 0

def increase():
nonlocal count
count += 1
return count

print(increase())
print(increase())
print(increase())

counter()

خروجی:
1
2
3

هر بار inner اجرا می‌شود، همان count متعلق به outer تغییر می‌کند.
⚠️ تفاوت global و nonlocal
global   → متغیر در محدوده اصلی برنامه
nonlocal → متغیر در تابع بیرونی

مثلاً:
global count

به متغیر سراسری اشاره می‌کند، اما:
nonlocal count

به متغیر موجود در تابع بیرونی اشاره می‌کند.
🧠 خلاصه
Local    → داخل تابع فعلی
Enclosing → تابع بیرونی
global → محدوده اصلی برنامه
nonlocal → تغییر متغیر تابع بیرونی

🎯 تمرین
کد زیر را کامل کنید تا هر بار اجرای increase() مقدار count را یک واحد افزایش دهد:
def counter():
count = 0

def increase():
# کد را اینجا بنویسید

count += 1
print(count)

increase()
increase()
increase()

counter()

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

پست ۷۹ از دوره جامع Python 🐍
👍1
🐍 آموزش جامع Python — پست ۸۰
ذخیره تابع در متغیر
در Python، تابع‌ها خودشان یک مقدار (Object) هستند.
بنابراین می‌توانیم یک تابع را داخل یک متغیر قرار دهیم و بعد از آن متغیر برای اجرای تابع استفاده کنیم.
🔹 مثال ساده
def greet():
print("Hello!")

my_function = greet

my_function()

خروجی:
Hello!

اینجا:
my_function = greet

به این معنی است که my_function به همان تابع greet اشاره می‌کند.
🔹 نکته مهم درباره ()
تفاوت این دو را دقت کنید:
my_function = greet

یعنی خود تابع را در متغیر قرار بده.
اما:
my_function = greet()

یعنی تابع را همین الان اجرا کن و نتیجه اجرای آن را در متغیر قرار بده.
🔹 مثال با مقدار بازگشتی
def add(a, b):
return a + b

operation = add

result = operation(10, 5)

print(result)

خروجی:
15

🧠 خلاصه
greet       → خود تابع
greet() → اجرای تابع
my_function = greet

بعد از این:
my_function()

تابع greet اجرا می‌شود.
🎯 تمرین
تابعی به نام square بنویسید که یک عدد دریافت کند و مربع آن را برگرداند.
سپس:
operation = square

و با استفاده از operation تابع را اجرا کنید.
پست ۸۰ از دوره جامع Python 🐍
26👍1
🐍 آموزش جامع Python — پست ۸۱
ارسال تابع به تابع دیگر
در Python می‌توانیم یک تابع را به‌عنوان آرگومان به تابع دیگری ارسال کنیم.
این قابلیت یکی از ویژگی‌های مهم توابع در Python است.
🔹 مثال ساده
def greet():
return "Hello Python"

def execute(function):
print(function())

execute(greet)

خروجی:
Hello Python

اینجا:
execute(greet)

خود تابع greet را ارسال کرده‌ایم، نه نتیجه اجرای آن.
🔹 تابع با آرگومان
def square(x):
return x * x

def calculate(function, number):
return function(number)

result = calculate(square, 5)

print(result)

خروجی:
25

در اینجا calculate هر تابعی را که بتواند یک عدد دریافت کند، می‌تواند اجرا کند.
⚠️ نکته مهم
این دو با هم فرق دارند:
calculate(square, 5)

ارسال تابع square
اما:
calculate(square(5), 5)

ابتدا square(5) اجرا می‌شود و نتیجه آن ارسال می‌شود؛ بنابراین در این مثال نوع آرگومان با چیزی که calculate انتظار دارد سازگار نیست.
🧠 خلاصه
تابع می‌تواند:
• در متغیر ذخیره شود
• به تابع دیگری ارسال شود
• توسط تابع دیگری اجرا شود

🎯 تمرین
دو تابع بنویسید:
def double(x):
return x * 2

def apply(function, number):
# کامل کنید

سپس:
print(apply(double, 10))

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

پست ۸۱ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۸۲
تابع کوتاه با lambda
در Python می‌توانیم برای کارهای ساده، تابعی کوتاه را با lambda تعریف کنیم.
🔹 ساختار
lambda arguments: expression

مثلاً:
square = lambda x: x * x

print(square(5))

خروجی:
25

این کد تقریباً معادل این است:
def square(x):
return x * x

🔹 چند آرگومان
add = lambda a, b: a + b

print(add(10, 5))

خروجی:
15

⚠️ نکته مهم
lambda برای توابع ساده و کوتاه مناسب است.
اگر منطق تابع پیچیده باشد، معمولاً def خواناتر و مناسب‌تر است.
🔹 استفاده همراه با تابع دیگر
def calculate(function, number):
return function(number)

result = calculate(lambda x: x * 2, 10)

print(result)

خروجی:
20

اینجا تابع lambda مستقیماً به calculate ارسال شده است.
🧠 خلاصه
def    → تعریف معمولی تابع
lambda → تعریف کوتاه یک تابع ساده

🎯 تمرین
یک lambda بنویسید که عدد را به توان ۳ برساند:
cube = ...

print(cube(4))

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

پست ۸۲ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۸۳
اعمال یک تابع روی همه اعضا با map()
گاهی می‌خواهیم یک عملیات یکسان را روی تمام اعضای یک مجموعه انجام دهیم.
map() برای همین کار استفاده می‌شود.
🔹 مثال ساده
numbers = [1, 2, 3, 4]

result = map(lambda x: x * 2, numbers)

print(list(result))

خروجی:
[2, 4, 6, 8]

تابع lambda روی تک‌تک اعضای numbers اجرا شده است.
🔹 با تابع معمولی
def square(x):
return x * x

numbers = [2, 3, 4, 5]

result = map(square, numbers)

print(list(result))

خروجی:
[4, 9, 16, 25]

🔹 چرا list()؟
در Python 3، map() یک iterator برمی‌گرداند و نتیجه‌ها را به‌صورت مستقیم به شکل لیست نمایش نمی‌دهد.
برای تبدیل آن به لیست:
list(result)

استفاده می‌کنیم.
⚠️ نکته مهم
map() داده اصلی را تغییر نمی‌دهد:
numbers = [1, 2, 3]

result = list(map(lambda x: x * 10, numbers))

print(numbers)
print(result)

خروجی:
[1, 2, 3]
[10, 20, 30]

🧠 خلاصه
map(function, iterable)

یعنی تابع را روی اعضای iterable اعمال کن.
🎯 تمرین
numbers = [5, 10, 15, 20]

result = map(lambda x: x + 5, numbers)

print(list(result))

خروجی را قبل از اجرا حدس بزنید.
پست ۸۳ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۸۴
فیلتر کردن داده‌ها با filter()
در پست قبل با map() آشنا شدیم که برای تبدیل اعضای یک مجموعه استفاده می‌شود.
اما اگر بخواهیم فقط اعضایی را نگه داریم که یک شرط خاص را دارند، از filter() استفاده می‌کنیم.
🔹 مثال ساده
numbers = [1, 2, 3, 4, 5, 6]

result = filter(lambda x: x % 2 == 0, numbers)

print(list(result))

خروجی:
[2, 4, 6]

تابع lambda برای هر عدد بررسی می‌کند که زوج است یا نه.
اگر نتیجه True باشد، آن مقدار در نتیجه باقی می‌ماند.
🔹 با تابع معمولی
def is_positive(x):
return x > 0

numbers = [-2, 0, 3, -1, 5]

result = filter(is_positive, numbers)

print(list(result))

خروجی:
[3, 5]

🔹 تفاوت map() و filter()
map()    → مقدارها را تبدیل می‌کند
filter() → مقدارها را انتخاب یا حذف می‌کند

مثلاً:
numbers = [1, 2, 3, 4]

list(map(lambda x: x * 2, numbers))
# [2, 4, 6, 8]

list(filter(lambda x: x % 2 == 0, numbers))
# [2, 4]

⚠️ نکته
filter() هم مانند map() در Python 3 یک iterator برمی‌گرداند؛ بنابراین برای ساختن لیست می‌توانیم از list() استفاده کنیم.
🎯 تمرین
numbers = [10, 15, 20, 25, 30]

result = filter(lambda x: x >= 20, numbers)

print(list(result))

خروجی را قبل از اجرا حدس بزنید.
پست ۸۴ از دوره جامع Python 🐍
1
🐍 آموزش جامع Python — پست ۸۵
ترکیب map() و filter()
در دو پست قبل یاد گرفتیم:
map()    → تبدیل داده‌ها
filter() → فیلتر کردن داده‌ها

حالا می‌توانیم این دو را با هم ترکیب کنیم.
🔹 مثال
فرض کنید فقط اعداد زوج را می‌خواهیم و سپس آن‌ها را دو برابر کنیم:
numbers = [1, 2, 3, 4, 5, 6]

even_numbers = filter(lambda x: x % 2 == 0, numbers)

result = map(lambda x: x * 2, even_numbers)

print(list(result))

خروجی:
[4, 8, 12]

ابتدا:
[1, 2, 3, 4, 5, 6]
↓
filter()
↓
[2, 4, 6]
↓
map()
↓
[4, 8, 12]

🔹 چرا ترتیب مهم است؟
اگر ابتدا map() انجام دهیم:
numbers = [1, 2, 3, 4]

result = filter(
lambda x: x > 5,
map(lambda x: x * 2, numbers)
)

print(list(result))

ابتدا اعداد دو برابر می‌شوند:
[2, 4, 6, 8]

سپس فقط اعداد بزرگ‌تر از 5 باقی می‌مانند:
[6, 8]

پس ترتیب عملیات روی نتیجه تأثیر دارد.
🧠 خلاصه
filter() → انتخاب
map() → تبدیل

می‌توانیم خروجی یکی را به‌عنوان ورودی دیگری استفاده کنیم.
🎯 تمرین
کدی بنویسید که:
اعداد بزرگ‌تر از 10 را انتخاب کند.
سپس آن‌ها را 3 برابر کند.
numbers = [5, 10, 15, 20, 25]

خروجی مورد انتظار:
[45, 60, 75]

پست ۸۵ از دوره جامع Python 🐍
1
🐍 آموزش جامع Python — پست ۸۶
ترکیب همه داده‌ها با reduce()
تا اینجا:
map()    → تبدیل داده‌ها
filter() → فیلتر کردن داده‌ها

حالا reduce() را یاد می‌گیریم.
reduce() چند مقدار را به‌صورت مرحله‌ای با یکدیگر ترکیب می‌کند تا در نهایت به یک مقدار برسد.
برای استفاده از آن باید reduce را از functools وارد کنیم.
🔹 مثال ساده
from functools import reduce

numbers = [1, 2, 3, 4]

result = reduce(lambda a, b: a + b, numbers)

print(result)

خروجی:
10

نحوه محاسبه:
1 + 2 → 3
3 + 3 → 6
6 + 4 → 10

🔹 ضرب اعداد
from functools import reduce

numbers = [2, 3, 4]

result = reduce(lambda a, b: a * b, numbers)

print(result)

خروجی:
24

چون:
2 × 3 → 6
6 × 4 → 24

🔹 مقدار اولیه
می‌توانیم یک مقدار اولیه هم مشخص کنیم:
from functools import reduce

numbers = [1, 2, 3]

result = reduce(lambda a, b: a + b, numbers, 10)

print(result)

خروجی:
16

محاسبه:
10 + 1 → 11
11 + 2 → 13
13 + 3 → 16

⚠️ نکته مهم
اگر لیست خالی باشد و مقدار اولیه نداشته باشیم، reduce() نمی‌داند از چه مقداری شروع کند و خطا ایجاد می‌شود.
🧠 خلاصه
map()    → چند مقدار → چند مقدارِ تبدیل‌شده
filter() → چند مقدار → چند مقدارِ انتخاب‌شده
reduce() → چند مقدار → یک مقدار

🎯 تمرین
با reduce() مجموع اعداد زیر را محاسبه کنید:
from functools import reduce

numbers = [5, 10, 15, 20]

result = reduce(lambda a, b: a + b, numbers)

print(result)

خروجی را قبل از اجرا حدس بزنید.
پست ۸۶ از دوره جامع Python 🐍
1
🐍 آموزش جامع Python — پست ۸۷
تابع تو در تو | Nested Function
در Python می‌توانیم یک تابع را داخل تابع دیگری تعریف کنیم.
به این نوع تابع، Nested Function می‌گوییم.
🔹 مثال ساده
def outer():
print("Outer function")

def inner():
print("Inner function")

inner()

outer()

خروجی:
Outer function
Inner function

تابع inner داخل outer تعریف شده و در این مثال فقط از داخل outer قابل استفاده است.
🔹 چرا تابع تو در تو؟
یکی از کاربردها، محدود کردن یک تابع کمکی به همان تابعی است که به آن نیاز دارد.
def calculate_total(price, tax):

def calculate_tax():
return price * tax

return price + calculate_tax()

print(calculate_total(100, 0.1))

خروجی:
110.0

اینجا calculate_tax() فقط برای محاسبه داخل calculate_total() استفاده شده است.
🔹 تابع داخلی می‌تواند مقدار بیرونی را ببیند
def outer():
message = "Hello"

def inner():
print(message)

inner()

outer()

خروجی:
Hello

تابع inner می‌تواند متغیر موجود در تابع بیرونی را بخواند.
⚠️ نکته مهم
این موضوع با nonlocal که در پست ۷۹ دیدیم مرتبط است:
خواندن متغیر بیرونی → بدون nonlocal
تغییر متغیر بیرونی → معمولاً با nonlocal

🧠 خلاصه
Nested Function
↓
تابع داخل تابع
↓
برای منطق کمکی و محدود به یک محدوده مفید است

🎯 تمرین
تابعی به نام greet_user بنویسید که داخل آن تابع get_message قرار داشته باشد.
تابع داخلی باید "Hello Python" را برگرداند و تابع بیرونی آن را چاپ کند.
پست ۸۷ از دوره جامع Python 🐍
1
🐍 آموزش جامع Python — پست ۸۸
Closure چیست؟
در پست قبل با تابع‌های تو‌در‌تو آشنا شدیم.
حالا یک قابلیت مهم‌تر را بررسی می‌کنیم: Closure.
Closure زمانی ایجاد می‌شود که یک تابع داخلی، متغیرهای تابع بیرونی را به خاطر بسپارد؛ حتی بعد از تمام شدن اجرای تابع بیرونی.
🔹 مثال ساده
def create_greeting(name):

def greet():
return f"Hello {name}"

return greet


message = create_greeting("Ali")

print(message())

خروجی:
Hello Ali

اینجا create_greeting() تمام شده، اما تابع greet هنوز مقدار name را در دسترس دارد.
🔹 یک مثال کاربردی
def multiplier(factor):

def multiply(number):
return number * factor

return multiply


double = multiplier(2)
triple = multiplier(3)

print(double(5))
print(triple(5))

خروجی:
10
15

هر تابع مقدار factor مربوط به خودش را به خاطر سپرده است.
multiplier(2)
↓
double
↓
factor = 2

multiplier(3)
↓
triple
↓
factor = 3

⚠️ نکته مهم
در Closure، تابع داخلی فقط یک تابع ساده نیست؛ بلکه همراه با اطلاعاتی از محیطی که در آن ساخته شده باقی می‌ماند.
🧠 خلاصه
Nested Function
→ تابع داخل تابع

Closure
→ تابع داخلی + دسترسی حفظ‌شده به متغیرهای محیط بیرونی

🎯 تمرین
تابعی به نام power بسازید که یک توان دریافت کند و یک تابع برگرداند:
square = power(2)

print(square(5))

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

پست ۸۸ از دوره جامع Python 🐍
1
🐍 آموزش جامع Python — پست ۸۹
تغییر رفتار تابع با Decorator
در پست‌های قبل دیدیم که می‌توانیم تابع را به تابع دیگری ارسال کنیم.
حالا از همین قابلیت برای ساخت Decorator استفاده می‌کنیم.
Decorator به ما اجازه می‌دهد بدون تغییر مستقیم کد یک تابع، رفتار آن را گسترش یا تغییر دهیم.
🔹 مثال ساده
def logger(function):

def wrapper():
print("Function started")
function()
print("Function finished")

return wrapper

حالا می‌توانیم آن را روی یک تابع اعمال کنیم:
def greet():
print("Hello Python")


greet = logger(greet)

greet()

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

در واقع:
greet = logger(greet)

تابع اصلی را با تابع wrapper جایگزین می‌کند.
🔹 استفاده از @
Python برای Decorator یک syntax ساده‌تر دارد:
def logger(function):

def wrapper():
print("Function started")
function()
print("Function finished")

return wrapper


@logger
def greet():
print("Hello Python")


greet()

نتیجه همان است.
این:
@logger
def greet():
...

تقریباً معادل این است:
def greet():
...

greet = logger(greet)

⚠️ نکته مهم
Decorator خودش تابعی است که یک تابع را دریافت می‌کند و معمولاً تابع دیگری برمی‌گرداند.
function
↓
Decorator
↓
wrapper
↓
تابع با رفتار جدید

🎯 تمرین
یک Decorator به نام before بنویسید که قبل از اجرای تابع، عبارت زیر را چاپ کند:
Starting...

سپس آن را روی تابع زیر اعمال کنید:
@before
def hello():
print("Hello!")

خروجی مورد انتظار:
Starting...
Hello!

پست ۸۹ از دوره جامع Python 🐍
1
🐍 آموزش جامع Python — پست ۹۰
Decorator برای توابع دارای آرگومان
در پست قبل یک Decorator ساده ساختیم که روی تابعی بدون آرگومان کار می‌کرد.
اما در عمل، توابع معمولاً آرگومان دارند.
برای اینکه Decorator بتواند با انواع مختلف تابع‌ها کار کند، از *args و **kwargs استفاده می‌کنیم.
🔹 مثال
def logger(function):

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

result = function(*args, **kwargs)

print("Function finished")

return result

return wrapper

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


result = add(10, 5)

print(result)

خروجی:
Function started
Function finished
15

🔹 چرا *args؟
*args آرگومان‌های موقعیتی را دریافت می‌کند:
add(10, 5)

در اینجا:
args → (10, 5)

و:
function(*args)

آن‌ها را دوباره به تابع اصلی ارسال می‌کند.
🔹 چرا **kwargs؟
برای آرگومان‌های نام‌دار:
@logger
def greet(name, language="English"):
return f"{name} - {language}"


print(greet(name="Ali", language="Persian"))

kwargs آرگومان‌های نام‌دار را دریافت می‌کند و:
function(*args, **kwargs)

همه آن‌ها را به تابع اصلی منتقل می‌کند.
⚠️ نکته مهم
اگر مقدار بازگشتی تابع اصلی را برنگردانیم، نتیجه تابع ممکن است از بین برود.
پس معمولاً در Decorator می‌نویسیم:
result = function(*args, **kwargs)

return result

🧠 خلاصه
*args    → آرگومان‌های موقعیتی
**kwargs → آرگومان‌های نام‌دار

function(*args, **kwargs)
→ ارسال دوباره آرگومان‌ها به تابع اصلی

🎯 تمرین
Decorator زیر را کامل کنید:
def logger(function):

def wrapper(*args, **kwargs):
print("Starting...")

# تابع اصلی را اجرا کنید

# نتیجه را برگردانید

return wrapper

سپس آن را روی تابع زیر امتحان کنید:
@logger
def multiply(a, b):
return a * b

print(multiply(4, 5))

خروجی مورد انتظار:
Starting...
20

پست ۹۰ از دوره جامع Python 🐍
1
🐍 آموزش جامع Python — پست ۹۱
حفظ مشخصات تابع با functools.wraps
در پست‌های قبل یاد گرفتیم چگونه Decorator بسازیم.
اما یک مشکل مهم وجود دارد:
وقتی یک تابع را با Decorator می‌پیچیم، ممکن است اطلاعات اصلی تابع مثل نام و توضیحات آن با اطلاعات wrapper جایگزین شود.
🔹 مثال
def logger(function):

def wrapper(*args, **kwargs):
return function(*args, **kwargs)

return wrapper


@logger
def greet():
"""Say hello to the user."""
return "Hello"


print(greet.__name__)
print(greet.__doc__)

خروجی معمولاً:
wrapper
None

یعنی مشخصات تابع اصلی از دست رفته‌اند.
🔹 راه‌حل: wraps
از functools، ابزار wraps را وارد می‌کنیم:
from functools import wraps


def logger(function):

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

return wrapper

حالا:
@logger
def greet():
"""Say hello to the user."""
return "Hello"


print(greet.__name__)
print(greet.__doc__)

خروجی:
greet
Say hello to the user.

🔹 @wraps(function) چه می‌کند؟
این Decorator اطلاعات مهم تابع اصلی را روی wrapper حفظ می‌کند؛ از جمله اطلاعاتی مانند:
__name__
__doc__

بنابراین بهتر است هنگام ساخت Decoratorهای واقعی، معمولاً از @wraps(function) استفاده کنیم.
🧠 خلاصه
Decorator
↓
wrapper
↓
@wraps(function)
↓
حفظ اطلاعات تابع اصلی

🎯 تمرین
Decorator زیر را طوری کامل کنید که نام تابع اصلی حفظ شود:
from functools import wraps

def my_decorator(function):

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

return wrapper

سپس بررسی کنید:
@my_decorator
def hello():
"""Hello function"""
return "Hello"


print(hello.__name__)
print(hello.__doc__)

پست ۹۱ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۹۲
استفاده از چند Decorator برای یک تابع
در Python می‌توانیم بیش از یک Decorator را روی یک تابع قرار دهیم.
این کار زمانی مفید است که بخواهیم چند رفتار مستقل را به یک تابع اضافه کنیم.
🔹 مثال ساده
from functools import wraps


def first(function):
@wraps(function)
def wrapper():
print("First")
function()
return wrapper


def second(function):
@wraps(function)
def wrapper():
print("Second")
function()
return wrapper


@first
@second
def hello():
print("Hello")


hello()

خروجی:
First
Second
Hello

🔹 ترتیب Decoratorها مهم است
وقتی می‌نویسیم:
@first
@second
def hello():
...

Python آن را تقریباً مانند این در نظر می‌گیرد:
hello = first(second(hello))

یعنی Decoratorها از پایین به بالا اعمال می‌شوند:
hello اصلی
↓
second
↓
first

پس ابتدا second تابع hello را می‌گیرد و سپس نتیجه آن به first داده می‌شود.
🔹 اما هنگام اجرای تابع چه اتفاقی می‌افتد؟
وقتی می‌نویسیم:
hello()

اجرای Wrapperها از بالا به پایین وارد می‌شود:
first.wrapper
↓
second.wrapper
↓
hello اصلی

مثلاً:
def first(function):
def wrapper():
print("First START")
function()
print("First END")
return wrapper


def second(function):
def wrapper():
print("Second START")
function()
print("Second END")
return wrapper


@first
@second
def hello():
print("Hello")


hello()

خروجی:
First START
Second START
Hello
Second END
First END

چرا؟
چون ابتدا first.wrapper اجرا می‌شود:
First START

سپس داخل آن:
function()

اجرا می‌شود که در اینجا همان second.wrapper است:
Second START

بعد second.wrapper تابع اصلی را اجرا می‌کند:
Hello

و بعد از تمام شدن تابع اصلی:
Second END
First END

🧠 یک قانون مهم برای حفظ کردن
اگر داشته باشیم:
@A
@B
@C
def function():
...

ترتیب اعمال Decoratorها:
C → B → A

اما هنگام اجرای تابع، ورود به Wrapperها:
A → B → C → function

و هنگام خروج، ترتیب برعکس می‌شود:
function → C → B → A

به همین دلیل 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 check_access(function):
@wraps(function)
def wrapper(*args, **kwargs):
print("Access checked")
return function(*args, **kwargs)
return wrapper


@log
@check_access
def delete_file():
print("File deleted")


delete_file()

خروجی:
Function called
Access checked
File deleted

چون:
delete_file = log(check_access(delete_file))

⚠️ نکته مهم
جای Decoratorها را نمی‌توان همیشه بدون تغییر نتیجه جابه‌جا کرد.
مثلاً:
@first
@second

لزوماً با:
@second
@first

رفتار یکسانی ندارد.
پس ترتیب Decoratorها بخشی از منطق برنامه است.
🧠 خلاصه
@first
@second
def hello():
...

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

بنابراین:
اعمال Decorator → از پایین به بالا
ورود به Wrapperها → از بالا به پایین
خروج از Wrapperها → از پایین به بالا

🎯 تمرین
دو Decorator بسازید:
before → قبل از اجرای تابع "Before" چاپ کند.
after → بعد از اجرای تابع "After" چاپ کند.
سپس هر دو را روی این تابع قرار دهید:
def hello():
print("Hello")

ترتیب خروجی را قبل از اجرا حدس بزنید.
پست ۹۲ از دوره جامع Python 🐍
🐍 آموزش جامع 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