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 — پست ۷۲
چند الگو در یک case
در match می‌توانیم چند مقدار یا الگوی مختلف را با | داخل یک case بررسی کنیم.
🔹 مثال
command = "start"

match command:
case "start" | "run":
print("Starting...")
case "stop" | "exit":
print("Stopping...")
case _:
print("Unknown command")

اگر مقدار command برابر "run" باشد، خروجی:
Starting...

چون "start" یا "run" هر کدام می‌توانند با این case مطابقت داشته باشند.
🔹 مثال با چند مقدار
day = 6

match day:
case 1 | 2 | 3 | 4 | 5:
print("Weekday")
case 6 | 7:
print("Weekend")
case _:
print("Invalid day")

خروجی:
Weekend

⚠️ نکته مهم
علامت | در اینجا یعنی:
این الگو OR آن الگو

مثلاً:
case "start" | "run":

یعنی مقدار باید با "start" یا "run" مطابقت داشته باشد.
🧠 خلاصه
case A | B:

یعنی اگر مقدار با A یا B مطابقت داشت، این case اجرا شود.
🎯 تمرین
کدی بنویسید که:
"jpg" یا "png" → Image
"mp3" یا "wav" → Audio
"mp4" یا "mkv" → Video
سایر موارد → Unknown

پست ۷۲ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۷۳
تطبیق ساختار داده‌ها با match
یکی از قابلیت‌های قدرتمند match این است که می‌توانیم ساختار داده‌ها را بررسی کنیم.
🔹 تطبیق لیست
data = [10, 20]

match data:
case [x, y]:
print("Two values:", x, y)
case _:
print("Different structure")

خروجی:
Two values: 10 20

Python تشخیص می‌دهد که data شامل دو مقدار است و آن‌ها را در x و y قرار می‌دهد.
🔹 تطبیق تعداد متفاوت
data = [10, 20, 30]

match data:
case [x]:
print("One value")
case [x, y]:
print("Two values")
case [x, y, z]:
print("Three values")

خروجی:
Three values

🔹 گرفتن بقیه مقدارها با *
data = [1, 2, 3, 4]

match data:
case [first, *rest]:
print(first)
print(rest)

خروجی:
1
[2, 3, 4]

*rest تمام مقدارهای باقی‌مانده را دریافت می‌کند.
⚠️ نکته مهم
در Pattern Matching، شکل داده هم می‌تواند بخشی از الگو باشد:
case [x, y]:

یعنی انتظار داریم داده ساختاری شامل دو عنصر داشته باشد.
🧠 خلاصه
[x, y]       → دو مقدار
[x, y, z] → سه مقدار
[first, *rest] → اولین مقدار + بقیه

🎯 تمرین
خروجی کد زیر را پیش‌بینی کنید:
data = [5, 10, 15, 20]

match data:
case [first, *rest]:
print(first)
print(rest)

پست ۷۳ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۷۴
Pattern Matching با dict
در match می‌توانیم ساختار یک دیکشنری (dict) را هم بررسی کنیم.
🔹 مثال ساده
user = {"name": "Ali", "age": 20}

match user:
case {"name": name, "age": age}:
print(name)
print(age)
case _:
print("Invalid data")

خروجی:
Ali
20

Python کلیدهای name و age را بررسی می‌کند و مقدارهای آن‌ها را در متغیرهای name و age قرار می‌دهد.
🔹 بررسی یک کلید خاص
لازم نیست تمام کلیدهای دیکشنری را در الگو بنویسیم:
user = {"name": "Ali", "age": 20, "admin": True}

match user:
case {"admin": True}:
print("Admin user")
case _:
print("Normal user")

خروجی:
Admin user

🔹 ترکیب با شرط
می‌توانیم بعد از Pattern Matching از if هم استفاده کنیم:
user = {"name": "Ali", "age": 20}

match user:
case {"name": name, "age": age} if age >= 18:
print(f"{name} is an adult")
case _:
print("Other user")

⚠️ نکته مهم
در Pattern Matching با dict، وجود کلیدهای اضافه معمولاً مانع تطبیق نمی‌شود.
مثلاً این الگو:
case {"name": name}:

می‌تواند با این دیکشنری هم مطابقت داشته باشد:
{"name": "Ali", "age": 20}

🧠 خلاصه
case {"name": name} → استخراج مقدار name
case {"admin": True} → بررسی مقدار یک کلید

🎯 تمرین
کدی بنویسید که دیکشنری زیر را بررسی کند:
user = {
"name": "Sara",
"age": 25,
"admin": True
}

اگر admin برابر True بود، چاپ کند:
Admin

در غیر این صورت:
Normal user

پست ۷۴ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۷۵
تطبیق اشیاء با match
در پست‌های قبل دیدیم که match می‌تواند با لیست و دیکشنری کار کند.
حالا می‌خواهیم ببینیم چطور می‌توانیم ساختار یک شیء را هم بررسی کنیم.
🔹 مثال
برای این بخش از یک کلاس ساده استفاده می‌کنیم:
class User:
__match_args__ = ("name", "age")

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


user = User("Ali", 20)

match user:
case User(name, age):
print(name)
print(age)
case _:
print("Unknown user")

خروجی:
Ali
20

🔹 شرط روی مقدار استخراج‌شده
می‌توانیم بعد از تطبیق، شرط هم قرار دهیم:
match user:
case User(name, age) if age >= 18:
print(f"{name} is an adult")
case User(name, age):
print(f"{name} is a minor")

خروجی:
Ali is an adult

⚠️ نکته مهم
__match_args__ مشخص می‌کند وقتی می‌نویسیم:
case User(name, age):

هر مقدار موقعیتی به کدام ویژگی کلاس مربوط شود.
در مثال ما:
name → user.name
age → user.age

🧠 خلاصه
match می‌تواند علاوه بر مقدارهای ساده و ساختارهایی مثل list و dict، با اشیاء کلاس‌ها نیز Pattern Matching انجام دهد.
🎯 تمرین
کلاس زیر را بسازید:
class Product:
__match_args__ = ("name", "price")

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

سپس با match بررسی کنید:
price >= 100 → Expensive
price < 100 → Cheap

پست ۷۵ از دوره جامع Python 🐍
👍1
🐍 آموزش جامع Python — پست ۷۶
محدوده متغیرها | Scope
تا اینجا بیشتر متغیرها را در سطح اصلی برنامه تعریف کردیم.
حالا باید با مفهوم Scope یا محدوده دسترسی متغیرها آشنا شویم.
🔹 متغیر در محدوده اصلی
name = "Ali"

print(name)

متغیر name در محدوده اصلی برنامه قرار دارد و از همان محدوده قابل دسترسی است.
🔹 متغیر داخل شرط
if True:
age = 20

print(age)

در Python، ایجاد شدن متغیر داخل if یک Scope جداگانه ایجاد نمی‌کند؛ بنابراین age در این مثال قابل دسترسی است.
🔹 متغیر داخل تابع
def show_name():
name = "Ali"
print(name)

show_name()

اینجا name داخل تابع تعریف شده است.
اگر خارج از تابع بنویسیم:
def show_name():
name = "Ali"

show_name()
print(name)

با خطا مواجه می‌شویم، چون name یک متغیر محلی (Local) است.
🔹 متغیر سراسری
name = "Ali"

def show_name():
print(name)

show_name()

تابع می‌تواند متغیر سراسری name را بخواند.
⚠️ نکته مهم
وجود if به‌تنهایی Scope جدید ایجاد نمی‌کند، اما تابع Scope محلی ایجاد می‌کند.
🧠 خلاصه
Global → محدوده اصلی برنامه
Local → محدوده داخل تابع
if → Scope جداگانه ایجاد نمی‌کند

در پست‌های بعدی Scope و نحوه کار متغیرها در توابع را دقیق‌تر بررسی می‌کنیم.
🎯 تمرین
خروجی کد زیر را پیش‌بینی کنید:
x = 10

if True:
y = 20

print(x)
print(y)

پست ۷۶ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۷۷
تغییر متغیر سراسری با global
در پست قبل با Global و Local Scope آشنا شدیم.
حالا می‌بینیم اگر بخواهیم داخل یک تابع، مقدار یک متغیر سراسری را تغییر دهیم، چه اتفاقی می‌افتد.
🔹 مشکل چیست؟
count = 10

def increase():
count = 20

increase()

print(count)

خروجی:
10

چون count = 20 داخل تابع، یک متغیر محلی جدید ایجاد می‌کند و متغیر سراسری را تغییر نمی‌دهد.
🔹 استفاده از global
با global به Python می‌گوییم منظورمان همان متغیر سراسری است:
count = 10

def increase():
global count
count = 20

increase()

print(count)

خروجی:
20

🔹 مثال کاربردی
score = 0

def add_score():
global score
score += 10

add_score()
add_score()

print(score)

خروجی:
20

هر بار که تابع اجرا می‌شود، همان متغیر سراسری score تغییر می‌کند.
⚠️ نکته مهم
استفاده زیاد از global معمولاً باعث می‌شود کد سخت‌تر قابل مدیریت و آزمایش باشد.
در برنامه‌های واقعی، اغلب بهتر است به‌جای تغییر مستقیم متغیرهای سراسری، از پارامترها و مقدار بازگشتی (return) استفاده کنیم که در فصل توابع به‌طور کامل یاد می‌گیریم.
🧠 خلاصه
متغیر داخل تابع → Local
متغیر خارج تابع → Global
global → اجازه تغییر متغیر Global داخل تابع

🎯 تمرین
خروجی کد زیر را پیش‌بینی کنید:
number = 5

def change():
global number
number += 10

change()
change()

print(number)

پست ۷۷ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۷۸
قانون LEGB در Scope
وقتی Python با یک نام متغیر مواجه می‌شود، باید پیدا کند که مقدار آن از کجا می‌آید.
برای این کار از ترتیب LEGB استفاده می‌کند.
🔹 L — Local
ابتدا داخل محدوده فعلی، معمولاً داخل تابع، جست‌وجو می‌شود:
name = "Global"

def show():
name = "Local"
print(name)

show()

خروجی:
Local

چون name محلی وجود دارد.
🔹 E — Enclosing
اگر تابعی داخل تابع دیگری باشد، Python در تابع بیرونی هم جست‌وجو می‌کند:
def outer():
name = "Outer"

def inner():
print(name)

inner()

outer()

خروجی:
Outer

🔹 G — Global
اگر در محدوده‌های محلی پیدا نشود، Python در محدوده اصلی برنامه جست‌وجو می‌کند:
name = "Global"

def show():
print(name)

show()

خروجی:
Global

🔹 B — Built-in
در نهایت Python در نام‌های داخلی خودش جست‌وجو می‌کند.
مثلاً:
print(len("Python"))

len یک نام Built-in است.
🧠 ترتیب LEGB
L → Local
E → Enclosing
G → Global
B → Built-in

Python این محدوده‌ها را به همین ترتیب بررسی می‌کند.
⚠️ نکته مهم
اگر یک نام در چند محدوده وجود داشته باشد، نزدیک‌ترین محدوده اولویت دارد.
name = "Global"

def show():
name = "Local"
print(name)

show()

خروجی Local است، نه Global.
🎯 تمرین
خروجی کد زیر را قبل از اجرا پیش‌بینی کنید:
x = "Global"

def outer():
x = "Outer"

def inner():
x = "Inner"
print(x)

inner()

outer()

پست ۷۸ از دوره جامع Python 🐍
1
🐍 آموزش جامع 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 🐍