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 — پست ۱۲۹
Context Manager با مدیریت چند Resource
گاهی یک Context Manager خودش باید چند Resource را هم‌زمان مدیریت کند.
مثلاً برنامه‌ای که هم‌زمان به یک فایل و یک اتصال دیگر نیاز دارد.
🔹 مدیریت چند Resource
می‌توانیم داخل یک Context Manager از ExitStack استفاده کنیم:
from contextlib import contextmanager
from contextlib import ExitStack

@contextmanager
def resources():
with ExitStack() as stack:
file = stack.enter_context(open("data.txt", "w"))

print("Resources opened")

yield file

print("Resources closing")

حالا:
with resources() as file:
file.write("Hello Python")

در اینجا:
resources()
↓
ExitStack
↓
باز شدن Resourceها
↓
yield
↓
کد داخل with
↓
cleanup

🔹 چرا ExitStack داخل Context Manager؟
چون ممکن است تعداد Resourceها ثابت نباشد:
@contextmanager
def open_files(file_names):

with ExitStack() as stack:

files = []

for name in file_names:
file = stack.enter_context(open(name, "w"))
files.append(file)

yield files

حالا:
files = ["a.txt", "b.txt", "c.txt"]

with open_files(files) as opened_files:
for file in opened_files:
file.write("Hello")

هر تعداد فایل که در لیست باشد، ExitStack آن‌ها را مدیریت می‌کند.
⚠️ نکته مهم
@contextmanager ساختار Context Manager را ساده می‌کند.
ExitStack هم برای مدیریت چند Resource پویا بسیار مناسب است.
ترکیب این دو زمانی مفید است که یک Context Manager خودش نیاز به مدیریت چند Resource داشته باشد.
🧠 خلاصه
@contextmanager
↓
ساخت Context Manager ساده
+
ExitStack
↓
مدیریت چند Resource
↓
cleanup خودکار

🎯 تمرین
یک Context Manager به نام open_files بنویس که:
لیستی از نام فایل‌ها دریافت کند.
همه فایل‌ها را با ExitStack باز کند.
لیست فایل‌های بازشده را با yield برگرداند.
بعد از خروج از with، همه فایل‌ها خودکار بسته شوند.
پست ۱۲۹ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۱۳۰
Context Manager با __enter__ و __exit__ در کلاس‌ها
تا اینجا Context Manager را با @contextmanager و ExitStack دیدیم.
حالا یک نکته مهم دیگر: می‌توانیم یک کلاس Context Manager بسازیم که علاوه بر مدیریت ورود و خروج، اطلاعات یا وضعیت داخلی خودش را هم نگه دارد.
🔹 ساخت Context Manager کلاسی
class Database:
def __init__(self, name):
self.name = name

def __enter__(self):
print(f"Connecting to {self.name}")
return self

def __exit__(self, exc_type, exc_value, traceback):
print(f"Closing {self.name}")

استفاده:
with Database("MyDB") as db:
print(f"Using {db.name}")

خروجی:
Connecting to MyDB
Using MyDB
Closing MyDB

🔹 چرا return self؟
در:
with Database("MyDB") as db:

مقداری که __enter__() برمی‌گرداند، داخل db قرار می‌گیرد.
پس:
return self

باعث می‌شود خود شیء را داخل db داشته باشیم.
🔹 مدیریت خطا
__exit__() اطلاعات خطا را دریافت می‌کند:
def __exit__(self, exc_type, exc_value, traceback):
if exc_type is not None:
print("An error occurred")

print("Closing database")

اگر داخل with خطایی رخ دهد، exc_type و exc_value اطلاعات آن خطا را در اختیارمان قرار می‌دهند.
اگر خطایی رخ ندهد:
exc_type = None
exc_value = None
traceback = None

⚠️ نکته مهم
اگر __exit__() مقدار True برگرداند، Exception سرکوب می‌شود.
def __exit__(self, exc_type, exc_value, traceback):
print("Closing database")
return True

اما اگر False یا None برگرداند، خطا به بیرون منتقل می‌شود.
پس return True را فقط زمانی استفاده کن که عمداً می‌خواهی خطا را مدیریت و متوقف کنی.
🧠 خلاصه
__enter__() → ورود و آماده‌سازی
yield/as → در اختیار قرار دادن Resource
__exit__() → خروج و Cleanup

🎯 تمرین
یک کلاس FileManager بساز که:
نام فایل را در __init__ دریافت کند.
در __enter__() فایل را باز کند.
خود فایل را با return برگرداند.
در __exit__() فایل را ببندد.
استفاده:
with FileManager("data.txt") as file:
file.write("Hello Python")

پست ۱۳۰ از دوره جامع Python 🐍
1
🐍 آموزش جامع Python — پست ۱۳۱
Context Manager برای فایل‌ها با __enter__ و __exit__
در پست قبل یک Context Manager کلاسی ساختیم.
حالا آن را روی فایل‌ها پیاده می‌کنیم تا ببینیم چطور می‌توانیم باز کردن و بستن فایل را خودمان مدیریت کنیم.
🔹 ساخت FileManager
class FileManager:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None

def __enter__(self):
self.file = open(self.filename, self.mode)
return self.file

def __exit__(self, exc_type, exc_value, traceback):
self.file.close()

حالا می‌توانیم از آن با with استفاده کنیم:
with FileManager("data.txt", "w") as file:
file.write("Hello Python")

ترتیب اجرا:
__enter__()
↓
باز شدن فایل
↓
کد داخل with
↓
__exit__()
↓
بسته شدن فایل

🔹 اگر داخل with خطا رخ دهد؟
حتی اگر داخل with خطایی ایجاد شود، __exit__() اجرا می‌شود:
with FileManager("data.txt", "w") as file:
file.write("Hello")
raise ValueError("Something went wrong")

قبل از خروج، فایل بسته می‌شود.
بنابراین __exit__() محل مناسبی برای cleanup است.
⚠️ نکته مهم
در Python برای کار معمول با فایل‌ها نیازی نیست خودمان چنین کلاسی بنویسیم.
روش استاندارد همان است:
with open("data.txt", "w") as file:
file.write("Hello Python")

این مثال فقط برای درک بهتر نحوه کار Context Manager است.
🧠 خلاصه
__enter__() → باز کردن Resource
with → استفاده از Resource
__exit__() → cleanup و بستن Resource

🎯 تمرین
کلاس FileManager را طوری تغییر بده که اگر داخل with خطایی رخ داد، پیام زیر را چاپ کند:
An error occurred

اما فایل همچنان در __exit__() بسته شود.
پست ۱۳۱ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۱۳۲
Context Manager و مدیریت خطا در __exit__
در پست قبل دیدیم که __exit__() حتی هنگام رخ دادن خطا هم اجرا می‌شود.
حالا می‌خواهیم از اطلاعات خطا داخل __exit__() استفاده کنیم.
🔹 اطلاعات خطا در __exit__
متد __exit__() سه پارامتر مهم دریافت می‌کند:
def __exit__(self, exc_type, exc_value, traceback):

این پارامترها شامل:
exc_type   → نوع Exception
exc_value → پیام Exception
traceback → اطلاعات محل رخ دادن خطا

اگر خطایی رخ نداده باشد، هر سه مقدار None هستند.
🔹 بررسی وجود خطا
class MyContext:

def __enter__(self):
print("Start")

def __exit__(self, exc_type, exc_value, traceback):

if exc_type is not None:
print("Error:", exc_value)

print("Cleanup")

استفاده:
with MyContext():
print("Working...")
raise ValueError("Invalid value")

خروجی:
Start
Working...
Error: Invalid value
Cleanup

🔹 جلوگیری از انتشار خطا
اگر __exit__() مقدار True برگرداند، Exception دیگر به بیرون منتقل نمی‌شود:
def __exit__(self, exc_type, exc_value, traceback):

if exc_type is not None:
print("Error handled")
return True

در این حالت برنامه بعد از with ادامه پیدا می‌کند.
⚠️ نکته مهم
اگر فقط می‌خواهیم خطا را مشاهده کنیم و اجازه دهیم Exception به بیرون منتقل شود، نباید True برگردانیم.
def __exit__(self, exc_type, exc_value, traceback):
print("Cleanup")

اما:
return True

یعنی:
این Exception را مدیریت کردم؛ دیگر آن را به بیرون منتقل نکن.

🧠 خلاصه
exc_type   → نوع خطا
exc_value → اطلاعات خطا
traceback → محل رخ دادن خطا

return True → خطا سرکوب می‌شود
return None → خطا ادامه پیدا می‌کند

🎯 تمرین کامل
یک Context Manager به نام SafeContext بساز که رفتار زیر را داشته باشد:
در __enter__() پیام زیر را چاپ کند:
Context started

اگر هیچ خطایی رخ نداد، در __exit__() پیام زیر را چاپ کند:
Context finished

اگر ValueError رخ داد:
پیام خطا را چاپ کند.
خطا را با return True مدیریت کند.
اگر Exception دیگری رخ داد:
پیام Unknown error را چاپ کند.
خطا را به بیرون منتقل کند.
کد اولیه:
class SafeContext:

def __enter__(self):
# کد را کامل کن
pass

def __exit__(self, exc_type, exc_value, traceback):
# کد را کامل کن
pass

حالا این دو حالت را تست کن:
حالت اول
with SafeContext():
print("Working...")
raise ValueError("Invalid value")

خروجی مورد انتظار:
Context started
Working...
Error: Invalid value

حالت دوم
with SafeContext():
print("Working...")
raise TypeError("Wrong type")

در این حالت باید:
Context started
Working...
Unknown error

نمایش داده شود و TypeError همچنان به بیرون منتقل شود.
پست ۱۳۲ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۱۳۳
ساخت Context Manager با contextlib.contextmanager
در پست‌های قبل Context Manager را با کلاس و __enter__() و __exit__() ساختیم.
Python روش ساده‌تری هم دارد: دکوراتور @contextmanager.
🔹 ساخت Context Manager
from contextlib import contextmanager

@contextmanager
def my_context():
print("Start")

yield

print("End")

استفاده:
with my_context():
print("Working...")

خروجی:
Start
Working...
End

کد قبل از yield هنگام ورود اجرا می‌شود و کد بعد از yield هنگام خروج.
🔹 برگرداندن مقدار با yield
می‌توانیم یک مقدار را در اختیار with قرار دهیم:
from contextlib import contextmanager

@contextmanager
def connection():
print("Connecting...")

data = "Database"
yield data

print("Connection closed")

استفاده:
with connection() as db:
print("Using:", db)

خروجی:
Connecting...
Using: Database
Connection closed

مقدار yield داخل متغیر db قرار می‌گیرد.
🔹 مدیریت خطا
می‌توانیم yield را داخل try/except قرار دهیم:
from contextlib import contextmanager

@contextmanager
def safe_context():
print("Start")

try:
yield
except ValueError as error:
print("Error:", error)
finally:
print("Cleanup")

حالا:
with safe_context():
print("Working...")
raise ValueError("Invalid value")

خروجی:
Start
Working...
Error: Invalid value
Cleanup

⚠️ نکته مهم
ساختار کلی:
@contextmanager
def my_context():
# setup

try:
yield
except Exception:
# مدیریت خطا
pass
finally:
# cleanup
pass

yield نقطه‌ای است که اجرای Context Manager موقتاً متوقف می‌شود و کد داخل with اجرا می‌شود.
🧠 خلاصه
@contextmanager → ساخت ساده Context Manager
yield → اجرای بخش داخلی with
try/except → مدیریت خطا
finally → cleanup

🎯 تمرین
یک Context Manager به نام database بساز که:
قبل از yield چاپ کند:
Connecting...

خطاهای ValueError را با پیام زیر مدیریت کند:
Invalid value

در هر شرایطی در finally چاپ کند:
Connection closed

داخل with این کد اجرا شود:
with database():
print("Query running...")

✅ پاسخ کامل تمرین
from contextlib import contextmanager


@contextmanager
def database():
print("Connecting...")

try:
yield

except ValueError:
print("Invalid value")

finally:
print("Connection closed")


with database():
print("Query running...")

خروجی:
Connecting...
Query running...
Connection closed

اگر داخل with خطای ValueError ایجاد کنیم:
with database():
print("Query running...")
raise ValueError("Bad query")

خروجی:
Connecting...
Query running...
Invalid value
Connection closed

اینجا except خطا را مدیریت می‌کند و finally در هر حالت اجرا می‌شود.
پست ۱۳۳ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۱۳۴
نادیده گرفتن خطاهای مشخص با contextlib.suppress
گاهی می‌دانیم یک Exception خاص ممکن است رخ دهد و می‌خواهیم فقط همان خطا را نادیده بگیریم.
برای این کار می‌توانیم از suppress() در ماژول contextlib استفاده کنیم.
🔹 استفاده ساده
from contextlib import suppress

with suppress(FileNotFoundError):
open("missing.txt")

print("Program continues")

اگر فایل وجود نداشته باشد، FileNotFoundError نادیده گرفته می‌شود و برنامه ادامه پیدا می‌کند:
Program continues

🔹 فقط Exception مشخص نادیده گرفته می‌شود
مثلاً:
from contextlib import suppress

with suppress(ValueError):
number = int("abc")

print("Done")

خروجی:
Done

اما اگر Exception دیگری رخ دهد، suppress() آن را نادیده نمی‌گیرد.
مثلاً:
with suppress(ValueError):
result = 10 / 0

اینجا ZeroDivisionError رخ می‌دهد و همچنان منتشر می‌شود.
🔹 چند Exception
می‌توانیم چند نوع خطا را مشخص کنیم:
from contextlib import suppress

with suppress(ValueError, TypeError):
value = int("abc")

در این حالت فقط ValueError یا TypeError نادیده گرفته می‌شوند.
⚠️ نکته مهم
suppress() جایگزین try/except در همه شرایط نیست.
وقتی نیاز داریم خطا را بررسی، ثبت یا پردازش کنیم، try/except مناسب‌تر است.
اما وقتی فقط می‌خواهیم یک خطای مشخص را نادیده بگیریم، suppress() کد را ساده‌تر می‌کند.
🧠 خلاصه
suppress(Exception)
↓
اجرای کد
↓
Exception مشخص؟
↓
نادیده گرفتن خطا
↓
ادامه برنامه

🎯 تمرین
کدی بنویس که با suppress() خطای FileNotFoundError را نادیده بگیرد:
from contextlib import suppress

with suppress(FileNotFoundError):
# فایل data.txt را باز کن

print("Finished")

برنامه باید حتی اگر فایل وجود نداشت، به Finished برسد.
✅ پاسخ کامل تمرین
from contextlib import suppress

with suppress(FileNotFoundError):
file = open("data.txt", "r")
print(file.read())

print("Finished")

اگر فایل data.txt وجود نداشته باشد:
Finished

نمایش داده می‌شود و FileNotFoundError نادیده گرفته می‌شود.
اگر فایل وجود داشته باشد، محتوای آن چاپ می‌شود و سپس:
Finished

نمایش داده می‌شود.
پست ۱۳۴ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۱۳۵
Context Manager اختیاری با nullcontext()
گاهی می‌خواهیم یک بخش از برنامه در بعضی شرایط داخل with اجرا شود و در بعضی شرایط نه.
برای این حالت می‌توانیم از nullcontext() در ماژول contextlib استفاده کنیم.
🔹 nullcontext() چیست؟
nullcontext() یک Context Manager است که هیچ کار خاصی انجام نمی‌دهد؛ اما اجازه می‌دهد در هر دو حالت از ساختار with استفاده کنیم.
مثلاً:
from contextlib import nullcontext

use_context = True

context = nullcontext() if use_context else nullcontext()

with context:
print("Code is running")

خروجی:
Code is running

🔹 استفاده با مقدار
nullcontext() می‌تواند یک مقدار را هم در اختیار as قرار دهد:
from contextlib import nullcontext

with nullcontext("Python") as value:
print(value)

خروجی:
Python

یعنی مقدار داده‌شده مستقیماً در متغیر value قرار می‌گیرد.
🔥 مثال کاربردی
فرض کنیم گاهی می‌خواهیم فایل را باز کنیم و گاهی از یک فایل موجود استفاده کنیم.
from contextlib import nullcontext

file = open("data.txt", "r")

context = nullcontext(file)

with context as f:
print(f.read())

file.close()

اینجا nullcontext(file) باعث می‌شود with از فایل موجود استفاده کند، بدون اینکه خودش آن را مدیریت یا در پایان ببندد.
⚠️ بنابراین مسئولیت بستن فایل همچنان با برنامه‌نویس است.
🔹 چرا nullcontext() مفید است؟
فرض کنید یک تابع گاهی باید Context Manager داشته باشد و گاهی نه.
به‌جای اینکه دو مسیر کاملاً متفاوت بنویسیم، می‌توانیم یک Context Manager انتخاب کنیم:
from contextlib import nullcontext

enabled = False

context = open("data.txt") if enabled else nullcontext()

with context as resource:
print("Running...")

این الگو در کدهای قابل تنظیم و کتابخانه‌ها کاربرد دارد.
🧠 خلاصه
nullcontext()
↓
Context Manager بدون عملیات خاص
↓
مناسب برای حالت‌های اختیاری

و:
with nullcontext(value) as x:
...

مقدار value را در اختیار x قرار می‌دهد.
🎯 تمرین
کدی بنویس که اگر:
use_resource = True

بود، از nullcontext("Python") استفاده کند و مقدار را چاپ کند.
اگر مقدار use_resource برابر False بود، باز هم برنامه بدون خطا اجرا شود.
پست ۱۳۵ از دوره جامع Python 🐍
1
🐍 آموزش جامع Python — پست ۱۳۶
تغییر موقت خروجی print() با redirect_stdout()
گاهی می‌خواهیم خروجی‌هایی که با print() تولید می‌شوند، به‌جای نمایش در ترمینال، داخل یک فایل یا مقصد دیگری نوشته شوند.
برای این کار می‌توانیم از redirect_stdout() در ماژول contextlib استفاده کنیم.
🔹 استفاده ساده
from contextlib import redirect_stdout

with open("output.txt", "w") as file:
with redirect_stdout(file):
print("Hello Python")
print("This goes into the file")

در این حالت، خروجی print() داخل output.txt نوشته می‌شود.
محتوای فایل:
Hello Python
This goes into the file

🔹 چرا print() دیگر در ترمینال نمایش داده نمی‌شود؟
به‌صورت عادی:
print("Hello")

خروجی به stdout یا همان خروجی استاندارد ارسال می‌شود.
اما داخل:
with redirect_stdout(file):

خروجی استاندارد به فایل هدایت می‌شود.
بنابراین:
print("Hello")

به‌جای ترمینال، داخل فایل نوشته می‌شود.
🔹 بعد از with چه اتفاقی می‌افتد؟
تغییر مسیر فقط موقتی است:
from contextlib import redirect_stdout

with open("output.txt", "w") as file:
with redirect_stdout(file):
print("Inside")

print("Outside")

محتوای output.txt:
Inside

و در ترمینال:
Outside

یعنی بعد از خروج از with، stdout به حالت قبلی برمی‌گردد.
🔥 مثال کاربردی
فرض کنید یک تابع داریم که چندین print() انجام می‌دهد:
def show_report():
print("Report")
print("Total: 100")
print("Status: OK")

می‌توانیم بدون تغییر دادن خود تابع، خروجی آن را داخل فایل ذخیره کنیم:
from contextlib import redirect_stdout

def show_report():
print("Report")
print("Total: 100")
print("Status: OK")

with open("report.txt", "w") as file:
with redirect_stdout(file):
show_report()

حالا تمام خروجی تابع داخل report.txt قرار می‌گیرد.
⚠️ نکته مهم
redirect_stdout() فقط stdout را تغییر می‌دهد.
بنابراین برای پیام‌های خطا که معمولاً از stderr استفاده می‌کنند، ابزار دیگری مثل:
redirect_stderr()

وجود دارد.
این دو را در ادامه بررسی می‌کنیم.
🧠 خلاصه
stdout
↓
print()
↓
redirect_stdout()
↓
فایل یا مقصد دیگر

redirect_stdout() تغییر مسیر stdout را فقط در محدوده‌ی with انجام می‌دهد.
🎯 تمرین
کد زیر را کامل کن تا خروجی تابع داخل result.txt ذخیره شود:
from contextlib import redirect_stdout

def calculate():
print("Calculation started")
print("Result: 42")

with open("result.txt", "w") as file:
# خروجی calculate را به فایل هدایت کن
...

بعد فایل result.txt را باز کن و محتوای آن را بررسی کن.
پست ۱۳۶ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۱۳۷
تغییر مسیر خطاها با redirect_stderr()
در پست قبل یاد گرفتیم redirect_stdout() می‌تواند خروجی print() را موقتاً به یک فایل یا مقصد دیگر هدایت کند.
اما خطاها معمولاً از مسیر دیگری به نام stderr ارسال می‌شوند.
برای تغییر مسیر stderr می‌توانیم از redirect_stderr() استفاده کنیم.
🔹 استفاده ساده
from contextlib import redirect_stderr
import sys

with open("errors.txt", "w") as file:
with redirect_stderr(file):
print("This is an error message", file=sys.stderr)

حالا پیام داخل errors.txt نوشته می‌شود.
محتوای فایل:
This is an error message

🔹 تفاوت stdout و stderr
به‌صورت ساده:
stdout → خروجی عادی برنامه
stderr → خطاها و پیام‌های خطا

مثلاً:
import sys

print("Normal output")
print("Error message", file=sys.stderr)

این دو پیام از دو مسیر متفاوت ارسال می‌شوند.
🔹 تغییر مسیر موقت
مثل redirect_stdout()، تغییر مسیر stderr فقط داخل with انجام می‌شود:
import sys
from contextlib import redirect_stderr

with open("errors.txt", "w") as file:
with redirect_stderr(file):
print("Error 1", file=sys.stderr)

print("Normal output")
print("Error 2", file=sys.stderr)

فقط Error 1 داخل فایل قرار می‌گیرد.
بعد از خروج از with، stderr به حالت قبلی برمی‌گردد.
🔥 مثال کاربردی
فرض کنید تابعی داریم که پیام خطا تولید می‌کند:
import sys

def process():
print("Processing...")
print("Something went wrong", file=sys.stderr)

می‌توانیم فقط خطاها را داخل فایل ذخیره کنیم:
from contextlib import redirect_stderr

with open("errors.txt", "w") as file:
with redirect_stderr(file):
process()

در نتیجه:
ترمینال:
Processing...

و داخل errors.txt:
Something went wrong

⚠️ نکته مهم
redirect_stderr() خودِ Exceptionها را مدیریت یا حذف نمی‌کند.
مثلاً:
from contextlib import redirect_stderr

with open("errors.txt", "w") as file:
with redirect_stderr(file):
raise ValueError("Invalid value")

این ValueError همچنان یک Exception واقعی است و برنامه را متوقف می‌کند؛ redirect_stderr() فقط مسیر stderr را تغییر می‌دهد.
برای مدیریت Exception باید از try/except یا ابزارهای مناسب دیگر استفاده کنیم.
🧠 خلاصه
stdout → خروجی عادی
stderr → خروجی خطا

redirect_stdout() → تغییر مسیر stdout
redirect_stderr() → تغییر مسیر stderr

هر دو تغییر مسیر را می‌توان به‌صورت موقت و داخل with انجام داد.
🎯 تمرین
کد زیر را کامل کن تا پیام خطا داخل errors.txt ذخیره شود:
import sys
from contextlib import redirect_stderr

def check():
print("Checking...")
print("Invalid input", file=sys.stderr)

with open("errors.txt", "w") as file:
# stderr را به فایل هدایت کن
...

پست ۱۳۷ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۱۳۸
مدیریت هم‌زمان stdout و stderr
تا اینجا یاد گرفتیم:
stdout → خروجی عادی
stderr → پیام‌های خطا

و برای تغییر مسیر آن‌ها از:
redirect_stdout()
redirect_stderr()

استفاده کردیم.
حالا می‌توانیم هر دو را هم‌زمان به مقصدهای مختلف هدایت کنیم.
🔹 هدایت به دو فایل مختلف
import sys
from contextlib import redirect_stdout, redirect_stderr

with open("output.txt", "w") as output:
with open("errors.txt", "w") as errors:
with redirect_stdout(output), redirect_stderr(errors):
print("Program started")
print("Something went wrong", file=sys.stderr)

بعد از اجرای برنامه:
output.txt:
Program started

و errors.txt:
Something went wrong

🔹 چرا از چند Context Manager استفاده کردیم؟
در Python می‌توان چند Context Manager را در یک with قرار داد:
with context1, context2:
...

بنابراین:
with redirect_stdout(output), redirect_stderr(errors):

یعنی هر دو Context Manager در یک بلوک فعال هستند.
🔥 مثال کاربردی
فرض کنید یک برنامه گزارش اجرا تولید می‌کند:
import sys

def run():
print("Starting...")
print("Loading data...")
print("Invalid record", file=sys.stderr)
print("Finished")

می‌توانیم خروجی عادی و خطاها را جداگانه ذخیره کنیم:
from contextlib import redirect_stdout, redirect_stderr

with open("output.txt", "w") as output:
with open("errors.txt", "w") as errors:
with redirect_stdout(output), redirect_stderr(errors):
run()

نتیجه:
output.txt
----------------
Starting...
Loading data...
Finished

و:
errors.txt
----------------
Invalid record

⚠️ نکته مهم
ترتیب Context Managerها در with اهمیت دارد.
مثلاً:
with context1, context2:
...

ورود به Contextها به ترتیب نوشته‌شدن انجام می‌شود و هنگام خروج، برعکس خارج می‌شوند.
یعنی:
ورود:
context1
↓
context2

خروج:
context2
↓
context1

این همان رفتار LIFO است که در ExitStack هم دیدیم.
🧠 خلاصه
with context1, context2:
...

یعنی چند Context Manager را می‌توان هم‌زمان مدیریت کرد.
در این مثال:
with redirect_stdout(output), redirect_stderr(errors):

خروجی عادی و پیام‌های خطا در دو مسیر جدا قرار می‌گیرند.
🎯 تمرین
کد را کامل کن:
import sys
from contextlib import redirect_stdout, redirect_stderr

def program():
print("Start")
print("Warning", file=sys.stderr)
print("Done")

with open("output.txt", "w") as output:
with open("errors.txt", "w") as errors:
# هر دو مسیر را به فایل مناسب هدایت کن
...

هدف:
output.txt
Start
Done

و:
errors.txt
Warning

پست ۱۳۸ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۱۳۹
تغییر موقت ورودی استاندارد با sys.stdin
در پست قبل با stdout و stderr آشنا شدیم.
برای ورودی استاندارد نیز چیزی به نام redirect_stdin() در contextlib وجود ندارد.
اگر بخواهیم موقتاً ورودی input() را از یک فایل یا منبع دیگر دریافت کنیم، می‌توانیم sys.stdin را تغییر دهیم.
🔹 sys.stdin چیست؟
sys.stdin همان ورودی استاندارد برنامه است.
به‌صورت معمول، وقتی می‌نویسیم:
name = input("Name: ")

ورودی از sys.stdin خوانده می‌شود.
می‌توانیم sys.stdin را موقتاً به یک فایل وصل کنیم.
🔹 تغییر stdin
برای اینکه این تغییر فقط داخل with انجام شود، می‌توانیم یک Context Manager ساده بسازیم:
import sys
from contextlib import contextmanager

@contextmanager
def redirect_stdin(file):
old_stdin = sys.stdin
sys.stdin = file

try:
yield
finally:
sys.stdin = old_stdin

حالا می‌توانیم از آن استفاده کنیم:
with open("input.txt", "r") as file:
with redirect_stdin(file):
name = input()

print(name)

اگر input.txt شامل:
Ali

باشد، خروجی:
Ali

خواهد بود.
🔹 چند ورودی از فایل
فرض کنید فایل input.txt شامل:
Ali
25

باشد.
می‌توانیم بنویسیم:
with open("input.txt", "r") as file:
with redirect_stdin(file):
name = input()
age = input()

print(name)
print(age)

خروجی:
Ali
25

🔥 کاربرد مهم: تست برنامه
این روش برای برنامه‌هایی که با input() کار می‌کنند، در تست‌های ساده مفید است.
مثلاً:
def calculate():
a = int(input())
b = int(input())
print(a + b)

فایل:
10
20

و اجرای برنامه:
with open("input.txt", "r") as file:
with redirect_stdin(file):
calculate()

خروجی:
30

بدون اینکه کاربر 10 و 20 را دستی وارد کند.
⚠️ محدودیت مهم
این روش ورودی تعاملی واقعی را شبیه‌سازی نمی‌کند.
وقتی:
sys.stdin = file

باشد، input() از فایل می‌خواند، نه از کیبورد.
بنابراین برای برنامه‌هایی که نیاز به تعامل مداوم با کاربر دارند، مناسب نیست.
مثلاً اگر فایل فقط دو خط داشته باشد و برنامه سه بار input() اجرا کند:
a = input()
b = input()
c = input()

در بار سوم دیگر ورودی کافی وجود ندارد و ممکن است EOFError رخ دهد.
⚠️ یک نکته دیگر
تغییر stdin فقط باید موقت باشد.
به همین دلیل در Context Manager بالا، مقدار قبلی ذخیره می‌شود:
old_stdin = sys.stdin

و در finally دوباره برگردانده می‌شود:
sys.stdin = old_stdin

حتی اگر داخل with خطایی رخ دهد، stdin به حالت قبلی برمی‌گردد.
🧠 خلاصه
sys.stdin  → ورودی استاندارد
sys.stdout → خروجی استاندارد
sys.stderr → خروجی خطا

برای تغییر موقت stdin می‌توانیم:
old_stdin = sys.stdin
sys.stdin = file

و در پایان:
sys.stdin = old_stdin

را انجام دهیم.
نکته: برخلاف redirect_stdout() و redirect_stderr()، در contextlib تابع آماده‌ای به نام redirect_stdin() نداریم و باید چنین Context Managerای را خودمان بسازیم یا از ابزارهای مناسب تست استفاده کنیم.
🎯 تمرین
فایل input.txt را با این محتوا بساز:
10
20

سپس Context Manager را کامل کن تا برنامه زیر ورودی‌هایش را از فایل دریافت کند:
def calculate():
a = int(input())
b = int(input())
print(a + b)

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

پست ۱۳۹ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۱۴۰
تغییر موقت پوشه کاری با contextlib.chdir()
گاهی برنامه لازم دارد برای مدت کوتاهی در یک پوشه خاص کار کند و بعد از اتمام عملیات، به پوشه قبلی برگردد.
در Python می‌توانیم از chdir() در contextlib استفاده کنیم.
contextlib.chdir()
از Python 3.11 اضافه شده است.

🔹 استفاده ساده
from contextlib import chdir
import os

print(os.getcwd())

with chdir("data"):
print(os.getcwd())

print(os.getcwd())

اگر پوشه data وجود داشته باشد:
C:\project
C:\project\data
C:\project

یعنی:
قبل از with → پوشه اصلی
داخل with → پوشه data
بعد از with → پوشه اصلی

🔹 چرا chdir() مفید است؟
فرض کنید:
project/
├── main.py
└── data/
├── users.txt
└── products.txt

می‌توانیم موقتاً وارد data شویم:
from contextlib import chdir

with chdir("data"):
with open("users.txt", "r") as file:
print(file.read())

داخل with، مسیر نسبی users.txt نسبت به پوشه data بررسی می‌شود.
🔹 بازگشت خودکار
اگر خطایی هم رخ دهد، مسیر قبلی بازگردانده می‌شود:
from contextlib import chdir

try:
with chdir("data"):
print("Inside data")
raise ValueError("Something went wrong")
except ValueError:
print("Error handled")

بعد از خروج از with، CWD دوباره به مسیر قبلی برمی‌گردد.
⚠️ محدودیت مهم: تغییر مسیر سراسری
نکته مهم این است که CWD متعلق به کل Process است، نه یک تابع یا Thread خاص.
مثلاً:
from contextlib import chdir
import os

with chdir("data"):
print(os.getcwd())

تا زمانی که داخل with هستیم، هر کدی که از مسیر نسبی استفاده کند، تغییر مسیر را می‌بیند:
open("file.txt")

در این حالت Python فایل را نسبت به:
data/file.txt

جست‌وجو می‌کند.
🔥 مشکل در Threadها
فرض کنیم دو Thread داریم:
def worker_a():
os.chdir("folder_a")

def worker_b():
open("file.txt")

اگر worker_a مسیر کاری را تغییر دهد، worker_b هم ممکن است مسیر جدید را ببیند.
چون CWD متعلق به Thread اول نیست؛ وضعیت کل Process تغییر کرده است.
بنابراین در برنامه‌های چندنخی، تغییر CWD می‌تواند باعث رفتارهای غیرقابل‌پیش‌بینی شود.
🔹 حتی توابع دیگر هم تحت تأثیر قرار می‌گیرند
مثلاً:
def load_config():
return open("config.json")

حالا:
from contextlib import chdir

with chdir("data"):
load_config()

تابع load_config() هیچ اطلاعی از data ندارد، اما فایل را نسبت به پوشه جدید جست‌وجو می‌کند.
یعنی تغییر CWD می‌تواند روی کدهای دیگری که از مسیر نسبی استفاده می‌کنند نیز اثر بگذارد.
✅ راهکار بهتر در بسیاری از موارد
به‌جای تغییر CWD، می‌توانیم مسیر فایل را مستقیماً مشخص کنیم:
from pathlib import Path

base = Path("data")
file_path = base / "config.json"

with open(file_path) as file:
print(file.read())

در این روش، تابع به CWD سراسری وابسته نیست.
🔹 تفاوت با os.chdir()
می‌توانیم مستقیماً بنویسیم:
import os

os.chdir("data")

اما در این حالت باید خودمان مسیر قبلی را ذخیره و بعداً برگردانیم.
contextlib.chdir() این کار را به‌صورت Context Manager انجام می‌دهد:
from contextlib import chdir

with chdir("data"):
...

پس تغییر مسیر موقت و قابل‌بازگشت است؛ اما همچنان خاصیت سراسری CWD را دارد.
🧠 خلاصه
CWD
↓
متعلق به کل Process
↓
همه کدهای استفاده‌کننده از مسیر نسبی آن را می‌بینند

chdir(path)
↓
تغییر موقت CWD
↓
اجرای کد
↓
بازگشت خودکار به مسیر قبلی

⚠️ در کدهای چندنخی یا کدهایی که نباید وضعیت سراسری برنامه را تغییر دهند، بهتر است تا حد امکان از مسیرهای صریح با pathlib استفاده کنیم.
🎯 تمرین
ساختار زیر را در نظر بگیر:
project/
├── main.py
└── files/
└── data.txt

برنامه‌ای بنویس که:
مسیر فعلی را چاپ کند.
با chdir() وارد files شود.
data.txt را باز کند و محتوایش را چاپ کند.
بعد از خروج از with دوباره مسیر فعلی را چاپ کند.
باید مشاهده کنی که مسیر بعد از with به مسیر قبلی برگشته است.
پست ۱۴۰ از دوره جامع Python 🐍
1
🐍 آموزش جامع Python — پست ۱۴۱
کار با مسیرها با pathlib
تا اینجا برای کار با مسیرها از رشته‌هایی مثل:
"data/config.json"

و همچنین os استفاده کردیم.
اما در Python یک روش مدرن‌تر و خواناتر برای کار با مسیرها وجود دارد:
pathlib

🔹 ساخت یک مسیر
ابتدا Path را import می‌کنیم:
from pathlib import Path

path = Path("data")

print(path)

خروجی:
data

Path مسیر را به‌صورت یک شیء در اختیار ما قرار می‌دهد.
🔹 ساخت مسیرهای تو در تو
به‌جای چسباندن رشته‌ها با +، می‌توانیم از / برای ترکیب مسیرها استفاده کنیم:
from pathlib import Path

base = Path("project")

file_path = base / "data" / "users.txt"

print(file_path)

خروجی:
project/data/users.txt

این / در pathlib به معنی تقسیم نیست؛ برای ترکیب مسیرها استفاده می‌شود.
🔹 ساخت پوشه‌های تو در تو
فرض کنیم می‌خواهیم این ساختار را ایجاد کنیم:
project/
└── data/
└── users/

می‌توانیم بنویسیم:
from pathlib import Path

path = Path("project/data/users")

path.mkdir(parents=True, exist_ok=True)

اینجا:
parents=True

یعنی اگر پوشه‌های والد وجود نداشته باشند، آن‌ها را هم بساز.
بنابراین اگر هیچ‌کدام از این پوشه‌ها وجود نداشته باشند:
project
└── data
└── users

هر سه پوشه ساخته می‌شوند.
گزینه:
exist_ok=True

یعنی اگر پوشه‌ها از قبل وجود داشته باشند، Python خطا ندهد.
پس ترکیب:
mkdir(parents=True, exist_ok=True)

برای ساخت پوشه‌های تو‌در‌تو بسیار کاربردی است.
🔹 تفاوت parents=True و exist_ok=True
این دو گزینه کار متفاوتی انجام می‌دهند:
parents=True
→ پوشه‌های والد را هم ایجاد کن

exist_ok=True
→ اگر پوشه از قبل وجود داشت، خطا نده

مثلاً:
Path("project/data/users").mkdir(
parents=True,
exist_ok=True
)

حتی اگر project و data وجود نداشته باشند، Python مسیر را ایجاد می‌کند.
🔹 بررسی وجود مسیر
با exists() می‌توانیم بررسی کنیم مسیر وجود دارد یا نه:
from pathlib import Path

path = Path("data")

print(path.exists())

نتیجه:
True

یا:
False

🔹 فایل است یا پوشه؟
Path متدهای مختلفی برای بررسی نوع مسیر دارد:
from pathlib import Path

path = Path("data")

print(path.is_file())
print(path.is_dir())

اگر data یک پوشه باشد:
False
True

🔹 ساخت یک پوشه ساده
اگر فقط یک پوشه را بخواهیم ایجاد کنیم:
from pathlib import Path

path = Path("data")

path.mkdir(exist_ok=True)

در این حالت چون فقط خود data را می‌سازیم، نیازی به parents=True نداریم.
🔹 نام فایل و پسوند
فرض کنیم:
from pathlib import Path

path = Path("data/users.txt")

print(path.name)
print(path.stem)
print(path.suffix)

خروجی:
users.txt
users
.txt

یعنی:
name   → نام کامل فایل
stem → نام فایل بدون پسوند
suffix → پسوند فایل

⚠️ نکته مهم
pathlib فقط مخصوص فایل‌ها نیست.
می‌توانیم از آن برای:
فایل‌ها
پوشه‌ها
مسیرهای نسبی
مسیرهای مطلق
ترکیب مسیرها
ساخت پوشه‌های تو‌در‌تو
استفاده کنیم.
همچنین استفاده از Path باعث می‌شود کد مدیریت مسیرها خواناتر و قابل‌حمل‌تر باشد.
🧠 خلاصه
from pathlib import Path

path = Path("data")

path.exists() # وجود دارد؟
path.is_file() # فایل است؟
path.is_dir() # پوشه است؟
path.mkdir() # ساخت پوشه
path.name # نام کامل
path.stem # نام بدون پسوند
path.suffix # پسوند

برای ساخت پوشه‌های تو‌در‌تو:
path.mkdir(
parents=True,
exist_ok=True
)

🎯 تمرین
کدی بنویس که:
مسیر project/data/users را ایجاد کند.
حتی اگر project و data وجود نداشتند، آن‌ها را هم بسازد.
اگر پوشه‌ها از قبل وجود داشتند، خطا ندهد.
مسیر فایل users.json را داخل پوشه users بسازد.
نام فایل و پسوند آن را چاپ کند.
شروع:
from pathlib import Path

users_dir = Path("project/data/users")

پست ۱۴۱ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۱۴۲
خواندن و نوشتن فایل با pathlib
در پست قبل با Path و ساخت مسیرها آشنا شدیم.
حالا می‌توانیم با Path مستقیماً فایل متنی را بخوانیم و بنویسیم.
🔹 نوشتن در فایل با write_text()
from pathlib import Path

file = Path("data/message.txt")

file.write_text("Hello Python", encoding="utf-8")

اگر فایل وجود نداشته باشد، ساخته می‌شود.
⚠️ اما اگر پوشه data وجود نداشته باشد، write_text() خودش آن را ایجاد نمی‌کند و با خطا مواجه می‌شویم.
🔹 ساخت پوشه والد قبل از فایل
برای اطمینان، می‌توانیم ابتدا پوشه والد فایل را بسازیم:
from pathlib import Path

file = Path("data/message.txt")

file.parent.mkdir(parents=True, exist_ok=True)

file.write_text("Hello Python", encoding="utf-8")

اینجا:
file.parent

پوشه والد فایل را برمی‌گرداند:
data

و:
file.parent.mkdir(
parents=True,
exist_ok=True
)

پوشه data را ایجاد می‌کند.
اگر پوشه‌های والد بیشتری داشته باشیم، parents=True همه آن‌ها را ایجاد می‌کند.
مثلاً:
from pathlib import Path

file = Path("project/data/users/info.txt")

file.parent.mkdir(parents=True, exist_ok=True)

file.write_text("Ali", encoding="utf-8")

ساختار زیر ایجاد می‌شود:
project/
└── data/
└── users/
└── info.txt

🔹 خواندن فایل با read_text()
بعد از نوشتن می‌توانیم فایل را بخوانیم:
from pathlib import Path

file = Path("data/message.txt")

text = file.read_text(encoding="utf-8")

print(text)

خروجی:
Hello Python

🔹 نوشتن چند خط
می‌توانیم از \n برای ایجاد خط جدید استفاده کنیم:
from pathlib import Path

file = Path("data/users.txt")

file.parent.mkdir(parents=True, exist_ok=True)

file.write_text(
"Ali\nSara\nReza",
encoding="utf-8"
)

محتوای فایل:
Ali
Sara
Reza

🔹 خواندن همه محتوا
read_text() کل محتوای فایل را به‌صورت یک str برمی‌گرداند:
text = file.read_text(encoding="utf-8")

print(type(text))

خروجی:
<class 'str'>

⚠️ نکته مهم درباره encoding
برای فایل‌های متنی، مخصوصاً زمانی که فارسی داریم، بهتر است encoding را مشخص کنیم:
encoding="utf-8"

مثلاً:
from pathlib import Path

file = Path("data/message.txt")

file.parent.mkdir(parents=True, exist_ok=True)

file.write_text(
"سلام Python",
encoding="utf-8"
)

print(file.read_text(encoding="utf-8"))

خروجی:
سلام Python

🔹 تفاوت با open()
قبلاً برای کار با فایل‌ها می‌توانستیم از:
open()

استفاده کنیم.
با pathlib برای عملیات ساده متنی می‌توانیم مستقیماً از:
file.write_text(...)

و:
file.read_text(...)

استفاده کنیم.
⚠️ نکته مهم
write_text() محتوای قبلی فایل را جایگزین می‌کند:
file.write_text("First", encoding="utf-8")
file.write_text("Second", encoding="utf-8")

در پایان محتوای فایل:
Second

خواهد بود.
برای اضافه‌کردن محتوا به انتهای فایل، بعداً روش مناسب آن را بررسی می‌کنیم.
🧠 خلاصه
from pathlib import Path

file = Path("data/message.txt")

file.parent.mkdir(parents=True, exist_ok=True)

file.write_text("Hello", encoding="utf-8")

text = file.read_text(encoding="utf-8")

یعنی:
file.parent → پوشه والد فایل
mkdir() → ساخت پوشه
write_text → نوشتن متن
read_text → خواندن متن

🎯 تمرین
برنامه‌ای بنویس که:
مسیر project/data/users/info.txt را ایجاد کند.
تمام پوشه‌های والد را در صورت نیاز بسازد.
داخل فایل نام و سن یک کاربر را بنویسد.
فایل را دوباره بخواند.
محتوای آن را چاپ کند.
مثلاً:
Name: Ali
Age: 25

پست ۱۴۲ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۱۴۳
اضافه کردن محتوا به فایل با حالت append
در پست قبل یاد گرفتیم write_text() محتوای فایل را جایگزین می‌کند.
اما گاهی نمی‌خواهیم اطلاعات قبلی حذف شود؛ می‌خواهیم متن جدید را به انتهای فایل اضافه کنیم.
برای این کار از حالت:
"a"

استفاده می‌کنیم.
🔹 اضافه کردن متن به فایل
با Path.open() می‌توانیم فایل را در حالت append باز کنیم:
from pathlib import Path

file = Path("data/users.txt")

with file.open("a", encoding="utf-8") as f:
f.write("Ali\n")

اگر فایل قبلاً این محتوا را داشته باشد:
Sara
Reza

بعد از اجرای کد:
Sara
Reza
Ali

خواهد شد.
🔹 تفاوت w و a
حالت w برای نوشتن و جایگزین کردن محتوا است:
with file.open("w", encoding="utf-8") as f:
f.write("Ali\n")

محتوای قبلی حذف می‌شود.
اما حالت a برای اضافه کردن است:
with file.open("a", encoding="utf-8") as f:
f.write("Ali\n")

محتوای قبلی باقی می‌ماند و متن جدید به انتهای فایل اضافه می‌شود.
🔹 یک مثال کاربردی
فرض کنیم می‌خواهیم کاربران را یکی‌یکی به فایل اضافه کنیم:
from pathlib import Path

file = Path("data/users.txt")

with file.open("a", encoding="utf-8") as f:
f.write("Ali\n")

with file.open("a", encoding="utf-8") as f:
f.write("Sara\n")

with file.open("a", encoding="utf-8") as f:
f.write("Reza\n")

محتوای فایل:
Ali
Sara
Reza

هر بار که فایل با حالت a باز می‌شود، اطلاعات جدید به انتهای آن اضافه می‌شود.
🔹 چرا از with استفاده می‌کنیم؟
with file.open("a", encoding="utf-8") as f:
f.write("Ali\n")

with باعث می‌شود بعد از پایان کار، فایل به‌درستی بسته شود.
این روش برای مدیریت فایل‌ها امن‌تر و استانداردتر است.
⚠️ نکته مهم درباره \n
اگر می‌خواهیم هر مقدار در یک خط جدید قرار بگیرد، باید خط جدید ایجاد کنیم:
f.write("Ali\n")

اگر \n نگذاریم:
f.write("Ali")
f.write("Sara")

نتیجه:
AliSara

اما:
f.write("Ali\n")
f.write("Sara\n")

نتیجه:
Ali
Sara

🧠 خلاصه
"w" → نوشتن و جایگزین کردن محتوا
"a" → اضافه کردن محتوا به انتهای فایل
"r" → خواندن فایل

مثلاً برای اضافه کردن:
with file.open("a", encoding="utf-8") as f:
f.write("New data\n")

🎯 تمرین
برنامه‌ای بنویس که:
فایل data/log.txt را باز کند.
عبارت Program started را به انتهای فایل اضافه کند.
دوباره همین برنامه را اجرا کن و تفاوت را ببین.
در هر اجرا، متن در یک خط جدید اضافه شود.
کد را طوری بنویس که اطلاعات قبلی فایل حذف نشود.
پست ۱۴۳ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۱۴۴
خواندن فایل خط‌به‌خط با readlines() و readline()
در پست‌های قبل یاد گرفتیم کل محتوای فایل را با read_text() بخوانیم.
اما همیشه لازم نیست کل فایل را یکجا بخوانیم.
گاهی می‌خواهیم فایل را خط‌به‌خط پردازش کنیم.
🔹 readline()
متد readline() یک خط از فایل را می‌خواند:
from pathlib import Path

file = Path("data/users.txt")

with file.open("r", encoding="utf-8") as f:
print(f.readline())
print(f.readline())

اگر فایل این باشد:
Ali
Sara
Reza

خروجی:
Ali

Sara

هر بار که readline() را اجرا می‌کنیم، به خط بعدی می‌رویم.
🔹 readlines()
readlines() تمام خطوط فایل را می‌خواند و آن‌ها را داخل یک لیست قرار می‌دهد:
from pathlib import Path

file = Path("data/users.txt")

with file.open("r", encoding="utf-8") as f:
lines = f.readlines()

print(lines)

اگر فایل:
Ali
Sara
Reza

باشد، نتیجه چیزی شبیه این است:
['Ali\n', 'Sara\n', 'Reza\n']

دقت کن \n نیز ممکن است داخل رشته هر خط وجود داشته باشد.
🔹 پردازش خط‌به‌خط با for
در بسیاری از مواقع لازم نیست از readlines() استفاده کنیم.
خود فایل قابل پیمایش است:
from pathlib import Path

file = Path("data/users.txt")

with file.open("r", encoding="utf-8") as f:
for line in f:
print(line)

این روش برای خواندن فایل‌های بزرگ بسیار مناسب‌تر است، چون همه خطوط را یکجا داخل یک لیست قرار نمی‌دهد.
🔹 حذف \n با strip()
اگر نمی‌خواهیم خط جدید همراه متن باقی بماند:
from pathlib import Path

file = Path("data/users.txt")

with file.open("r", encoding="utf-8") as f:
for line in f:
print(line.strip())

اگر فایل:
Ali
Sara
Reza

باشد، هر خط به‌صورت تمیز پردازش می‌شود.
⚠️ تفاوت مهم
readline()
→ یک خط را می‌خواند

readlines()
→ همه خطوط را در یک لیست قرار می‌دهد

for line in file
→ فایل را خط‌به‌خط پیمایش می‌کند

برای فایل‌های کوچک، هر سه روش می‌توانند مناسب باشند.
برای فایل‌های بزرگ، پیمایش خط‌به‌خط معمولاً انتخاب مناسب‌تری است.
🧠 یک مثال کاربردی
فرض کنیم فایل شامل نمره‌ها باشد:
18
15
20
12

می‌توانیم آن‌ها را خط‌به‌خط بخوانیم:
from pathlib import Path

file = Path("data/scores.txt")

with file.open("r", encoding="utf-8") as f:
for line in f:
score = int(line.strip())
print(score)

خروجی:
18
15
20
12

اینجا:
line.strip()

متن خط را تمیز می‌کند و:
int(...)

آن را به عدد صحیح تبدیل می‌کند.
🧠 خلاصه
f.readline()

یک خط.
f.readlines()

تمام خطوط به شکل لیست.
for line in f:

پردازش خط‌به‌خط.
🎯 تمرین
فایلی به نام:
data/numbers.txt

بساز و داخل آن قرار بده:
10
20
30
40

سپس برنامه‌ای بنویس که:
فایل را باز کند.
اعداد را خط‌به‌خط بخواند.
هر خط را به int تبدیل کند.
مجموع اعداد را محاسبه کند.
مجموع را چاپ کند.
خروجی مورد انتظار:
100

پست ۱۴۴ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۱۴۵
کپی، انتقال و حذف فایل و پوشه با pathlib
حالا که با ساخت، خواندن و نوشتن فایل‌ها آشنا شدیم، وقت آن است که عملیات مدیریتی روی فایل‌ها و پوشه‌ها را یاد بگیریم.
برای این کار می‌توانیم از shutil در کنار pathlib استفاده کنیم.
🔹 کپی کردن فایل
برای کپی فایل از shutil.copy2() استفاده می‌کنیم:
from pathlib import Path
import shutil

source = Path("data/users.txt")
destination = Path("backup/users.txt")

destination.parent.mkdir(parents=True, exist_ok=True)

shutil.copy2(source, destination)

در اینجا:
source      → فایل اصلی
destination → محل کپی

محتوای فایل اصلی حفظ می‌شود و یک نسخه در backup ایجاد می‌شود.
🔹 انتقال یا تغییر نام فایل
برای انتقال فایل می‌توانیم از shutil.move() استفاده کنیم:
from pathlib import Path
import shutil

source = Path("data/users.txt")
destination = Path("archive/users.txt")

destination.parent.mkdir(parents=True, exist_ok=True)

shutil.move(source, destination)

بعد از اجرا:
data/users.txt

دیگر وجود ندارد و فایل به:
archive/users.txt

منتقل شده است.
move() برای تغییر نام هم کاربرد دارد:
shutil.move(
"data/old.txt",
"data/new.txt"
)

🔹 حذف یک فایل
برای حذف فایل می‌توانیم از Path.unlink() استفاده کنیم:
from pathlib import Path

file = Path("data/users.txt")

file.unlink()

بعد از اجرای این کد، فایل حذف می‌شود.
⚠️ unlink() را با دقت استفاده کن؛ حذف فایل عملیاتی برگشت‌پذیر مانند انتقال یا کپی نیست.
🔹 بررسی قبل از حذف
بهتر است قبل از حذف، وجود فایل را بررسی کنیم:
from pathlib import Path

file = Path("data/users.txt")

if file.exists():
file.unlink()

🔹 حذف پوشه خالی
برای حذف یک پوشه خالی:
from pathlib import Path

folder = Path("data/old")

folder.rmdir()

⚠️ rmdir() فقط پوشه‌ای را حذف می‌کند که خالی باشد.
اگر داخل پوشه فایل یا پوشه دیگری وجود داشته باشد، حذف انجام نمی‌شود.
🔹 حذف پوشه دارای محتوا
برای حذف یک پوشه همراه با تمام محتویات آن از shutil.rmtree() استفاده می‌شود:
from pathlib import Path
import shutil

folder = Path("data/old")

if folder.exists():
shutil.rmtree(folder)

⚠️ این عملیات می‌تواند تمام فایل‌ها و پوشه‌های داخل مسیر را حذف کند؛ بنابراین باید با دقت استفاده شود.
🧠 خلاصه
shutil.copy2() → کپی فایل
shutil.move() → انتقال / تغییر نام
Path.unlink() → حذف فایل
Path.rmdir() → حذف پوشه خالی
shutil.rmtree() → حذف پوشه و تمام محتویات

🎯 تمرین
ساختار زیر را در نظر بگیر:
project/
├── data/
│ └── users.txt
└── backup/

برنامه‌ای بنویس که:
فایل users.txt را داخل backup کپی کند.
فایل اصلی را به archive/users.txt منتقل کند.
وجود فایل منتقل‌شده را بررسی کند.
در صورت وجود، آن را حذف کند.
پست ۱۴۵ از دوره جامع Python 🐍
calculator.py
8.1 KB
🖩 ماشین‌حساب قدرتمند پایتون
یک ماشین‌حساب علمی با پایتون ساختم — سریع، امن و بدون نیاز به کتابخونه‌ی اضافه.
امکانات: عملیات پایه، توابع علمی (sin, cos, log, sqrt, fact...)، حافظه (M+/MR/MC)، تاریخچه، ثابت‌های pi و e، و محاسبه عبارات تودرتو.
راه‌اندازی
bash
python3 calculator.py

تست سریع
>>> (3+5)*sqrt(16) - sin(30)
= 31.5
>>> fact(5)
= 120
>>> help ← راهنمای کامل

🔐 برخلاف اکثر ماشین‌حساب‌های مشابه، از eval() استفاده نمی‌کنه — تجزیه با AST انجام میشه، پس ورودی کاربر نمی‌تونه کد دلخواه اجرا کنه.
🐍 آموزش جامع Python — پست ۱۴۶
پیدا کردن فایل‌ها و پوشه‌ها با Path.glob()
در پست قبل یاد گرفتیم چگونه فایل‌ها و پوشه‌ها را مدیریت کنیم.
حالا فرض کنید داخل یک پوشه تعداد زیادی فایل داریم و می‌خواهیم فقط فایل‌های موردنظرمان را پیدا کنیم.
برای این کار می‌توانیم از glob() در pathlib استفاده کنیم.
🔹 پیدا کردن فایل‌های خاص
فرض کنیم ساختار زیر را داریم:
project/
├── data/
│ ├── users.txt
│ ├── products.txt
│ ├── notes.txt
│ └── image.png

می‌توانیم تمام فایل‌های .txt را پیدا کنیم:
from pathlib import Path

data = Path("project/data")

for file in data.glob("*.txt"):
print(file)

خروجی:
project/data/users.txt
project/data/products.txt
project/data/notes.txt

فایل image.png پیدا نمی‌شود، چون پسوند آن .png است.
🔹 glob("*.txt") یعنی چه؟
در:
data.glob("*.txt")

علامت:
*

یعنی هر نامی.
پس:
*.txt

یعنی:
هر فایلی که پسوند آن

.txt
باشد.

مثلاً:
users.txt
notes.txt
data.txt

همه با این الگو مطابقت دارند.
🔹 پیدا کردن فایل‌های Python
برای پیدا کردن فایل‌های .py:
from pathlib import Path

project = Path("project")

for file in project.glob("*.py"):
print(file)

🔹 جستجو در پوشه‌های تو‌در‌تو با rglob()
glob() به‌صورت معمول در همان پوشه جستجو می‌کند.
اما اگر بخواهیم داخل تمام زیرپوشه‌ها نیز جستجو کنیم، می‌توانیم از rglob() استفاده کنیم:
from pathlib import Path

project = Path("project")

for file in project.rglob("*.txt"):
print(file)

مثلاً اگر ساختار این باشد:
project/
├── data/
│ ├── users.txt
│ └── products.txt
│
└── backup/
└── old/
└── users.txt

rglob("*.txt") هر سه فایل .txt را پیدا می‌کند.
🔹 فقط پوشه‌ها
می‌توانیم با glob() پوشه‌ها را هم پیدا کنیم:
from pathlib import Path

project = Path("project")

for item in project.glob("*"):
if item.is_dir():
print(item)

🔹 بررسی نوع نتیجه
هر چیزی که از glob() دریافت می‌کنیم یک شیء Path است:
from pathlib import Path

data = Path("project/data")

for item in data.glob("*"):
print(item)
print(type(item))

⚠️ نکته مهم
glob() خودش فایل‌ها را حذف یا تغییر نمی‌دهد.
فقط مسیرهای مطابق الگو را پیدا می‌کند.
مثلاً:
files = data.glob("*.txt")

فقط فایل‌های .txt را پیدا می‌کند.
بعد می‌توانیم روی آن‌ها عملیات دیگری انجام دهیم.
🔥 مثال کاربردی
فرض کنیم می‌خواهیم اندازه فایل‌های متنی را بررسی کنیم:
from pathlib import Path

data = Path("project/data")

for file in data.glob("*.txt"):
print(file.name, file.stat().st_size)

stat().st_size اندازه فایل را برحسب بایت برمی‌گرداند.
🧠 خلاصه
glob("*.txt")
→ پیدا کردن فایل‌های txt در همان پوشه

glob("*")
→ پیدا کردن موارد موجود در همان پوشه

rglob("*.txt")
→ پیدا کردن فایل‌های txt در پوشه و تمام زیرپوشه‌ها

🎯 تمرین
ساختار زیر را در نظر بگیر:
project/
├── data/
│ ├── users.txt
│ ├── products.txt
│ └── image.png
│
└── backup/
└── old/
└── users.txt

برنامه‌ای بنویس که:
تمام فایل‌های .txt داخل project و زیرپوشه‌های آن را پیدا کند.
نام هر فایل را چاپ کند.
مسیر کامل هر فایل را نیز چاپ کند.
پست ۱۴۶ از دوره جامع Python 🐍
🐍 آموزش جامع Python — پست ۱۴۷
اطلاعات فایل با stat() و ویژگی‌های مسیر
در پست قبل با glob() فایل‌ها را پیدا کردیم.
حالا می‌خواهیم اطلاعات بیشتری درباره یک فایل یا مسیر به دست بیاوریم؛ مثل اندازه فایل، زمان تغییر و نوع مسیر.
برای این کار می‌توانیم از stat() استفاده کنیم.
🔹 اندازه فایل با stat()
from pathlib import Path

file = Path("data/users.txt")

info = file.stat()

print(info.st_size)

st_size اندازه فایل را برحسب بایت برمی‌گرداند.
مثلاً:
125

یعنی اندازه فایل ۱۲۵ بایت است.
🔹 چند ویژگی مهم
شیء برگشتی از stat() اطلاعات مختلفی دارد:
from pathlib import Path

file = Path("data/users.txt")

info = file.stat()

print(info.st_size)
print(info.st_mtime)

یعنی:
st_size → اندازه فایل برحسب بایت
st_mtime → زمان آخرین تغییر

مقدار st_mtime معمولاً به شکل یک timestamp عددی نمایش داده می‌شود.
🔹 تبدیل زمان تغییر به تاریخ قابل خواندن
برای نمایش زمان به شکل قابل فهم‌تر:
from pathlib import Path
from datetime import datetime

file = Path("data/users.txt")

timestamp = file.stat().st_mtime

modified = datetime.fromtimestamp(timestamp)

print(modified)

خروجی مثلاً:
2026-09-23 18:42:10

زمان دقیق به سیستم شما بستگی دارد.
🔹 بررسی اندازه فایل
می‌توانیم از اندازه فایل برای یک بررسی ساده استفاده کنیم:
from pathlib import Path

file = Path("data/users.txt")

if file.stat().st_size == 0:
print("File is empty")
else:
print("File has content")

اگر فایل خالی باشد:
File is empty

🔹 exists()، is_file() و is_dir()
در پست‌های قبل با این متدها آشنا شدیم:
path.exists()
path.is_file()
path.is_dir()

می‌توانیم آن‌ها را برای تصمیم‌گیری درباره مسیر استفاده کنیم:
from pathlib import Path

path = Path("data/users.txt")

if not path.exists():
print("Path does not exist")
elif path.is_file():
print("This is a file")
elif path.is_dir():
print("This is a directory")

ترتیب این بررسی‌ها مهم است؛ ابتدا مطمئن می‌شویم مسیر واقعاً وجود دارد.
🔹 یک مثال کاربردی
فرض کنیم می‌خواهیم مشخصات فایل‌های متنی یک پوشه را نمایش دهیم:
from pathlib import Path

data = Path("data")

for file in data.glob("*.txt"):
info = file.stat()

print("Name:", file.name)
print("Size:", info.st_size, "bytes")
print("Modified:", info.st_mtime)
print()

در اینجا ابتدا فایل‌ها را با glob() پیدا می‌کنیم و سپس با stat() اطلاعات هر فایل را می‌گیریم.
⚠️ نکته مهم
stat() اطلاعات سیستم فایل را می‌خواند؛ بنابراین برای مسیری که وجود ندارد، معمولاً خطای FileNotFoundError دریافت می‌کنیم.
بهتر است قبل از استفاده، وجود مسیر را بررسی کنیم:
if file.exists():
info = file.stat()

🧠 خلاصه
path.stat()
→ دریافت اطلاعات فایل یا مسیر

st_size
→ اندازه فایل برحسب بایت

st_mtime
→ زمان آخرین تغییر

exists()
→ آیا مسیر وجود دارد؟

is_file()
→ آیا فایل است؟

is_dir()
→ آیا پوشه است؟

🎯 تمرین
برنامه‌ای بنویس که:
فایل‌های .txt داخل data را پیدا کند.
نام هر فایل را چاپ کند.
اندازه هر فایل را برحسب بایت چاپ کند.
زمان آخرین تغییر هر فایل را نمایش دهد.
پست ۱۴۷ از دوره جامع Python 🐍