عمليات الملفات الآمنة مع معالجة الأخطاء
التحقق من وجود الملفات والأذونات ومعالجة أخطاء IO بسلاسة
عمليات الملفات الآمنة مع معالجة الأخطاء درس مجاني في AI Agents على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في AI Agents، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة AI Agents 4 دروس في المجموع.
لماذا تُعدّ عمليات الملفات الآمنة مهمة
قد تفشل عمليات الملفات في الوكلاء بطرق عديدة: قد لا يكون الملف موجودًا، أو قد يفتقر الوكيل إلى الإذن، أو قد يكون المسار مجلدًا، أو قد تنفد مساحة القرص أثناء الكتابة. ويؤدي تعطل الوكيل بسبب خطأ في ملف إلى ترك إخراج جزئي وحالة تالفة. وتجعل البرمجة الدفاعية، مع إجراء عمليات التحقق المناسبة ومعالجة الأخطاء، الوكلاء أكثر قدرة على الصمود.
from pathlib import Path
# Unsafe: crashes with FileNotFoundError
# content = Path('missing.txt').read_text()
# Safe: check first
path = Path('config.json')
if path.exists():
content = path.read_text(encoding='utf-8')
print('Loaded config')
else:
print(f'Config not found at {path.resolve()}')
content = '{}' # use defaultPath.exists() و Path.is_file()
قبل قراءة ملف، تحقّق من أنه موجود و أنه ملف عادي فعلًا (وليس دليلًا أو رابطًا رمزيًا إلى دليل أو ملفًا خاصًا). تُرجع Path.exists() القيمة True لأي كائن في نظام الملفات، بينما تُرجع Path.is_file() القيمة True للملفات العادية فقط.
from pathlib import Path
path = Path('data/report.csv')
# Chain of checks
if not path.exists():
print(f'Not found: {path}')
elif not path.is_file():
print(f'Not a regular file: {path} (is_dir={path.is_dir()})')
elif path.stat().st_size == 0:
print(f'File is empty: {path}')
else:
# Safe to read
import csv
with open(path, 'r', encoding='utf-8', newline='') as f:
reader = csv.DictReader(f)
rows = list(reader)
print(f'Read {len(rows)} rows')os.access() — التحقق من الأذونات
تتحقق os.access(path, mode) مما إذا كانت العملية الحالية تملك الصلاحية المحددة على ملف. استخدم os.R_OK للقراءة، وos.W_OK للكتابة، وos.X_OK للتنفيذ. يفيد ذلك قبل محاولة إجراء عمليات تتطلب أذونات محددة.
import os
from pathlib import Path
def check_file_access(path):
p = Path(path)
checks = {
'exists': p.exists(),
'is_file': p.is_file(),
'readable': os.access(p, os.R_OK),
'writable': os.access(p, os.W_OK),
}
for check, result in checks.items():
status = 'OK' if result else 'FAIL'
print(f' {check}: {status}')
return all(checks.values())
if check_file_access('data/input.json'):
print('File is accessible')
else:
print('Access problem — check path and permissions')التقاط FileNotFoundError
يُرفع FileNotFoundError (وهو فئة فرعية من OSError) عند محاولة فتح ملف غير موجود. التقطه صراحةً وقدّم رسالة خطأ مفيدة تتضمن المسار المتوقع، حتى يعرف المستخدم أو المشغّل بالضبط ما الملف المفقود.
import json
from pathlib import Path
def load_agent_config(config_path='agent_config.json'):
path = Path(config_path)
try:
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
except FileNotFoundError:
print(f'Config file not found: {path.resolve()}')
print('Create agent_config.json with your settings')
print('Example: {"model": "gpt-4o", "max_retries": 3}')
return {} # return empty config as default
except json.JSONDecodeError as e:
print(f'Invalid JSON in {path}: {e}')
return {}
# --- demo ---
config = load_agent_config('does_not_exist_config.json')
print(f'Config used: {config}')
التقاط PermissionError
يُرفع PermissionError عندما تفتقر العملية إلى صلاحية قراءة ملف أو الكتابة فيه. قد يحدث ذلك مع ملفات النظام، أو الملفات التي يملكها مستخدم آخر، أو الملفات ذات الأذونات المقيّدة. احرص دائمًا على التقاطه بشكل منفصل عن FileNotFoundError، لأن كلًّا منهما يتطلب استجابة مختلفة.
from pathlib import Path
def read_file_safely(path):
try:
return Path(path).read_text(encoding='utf-8')
except FileNotFoundError:
print(f'File not found: {path}')
return None
except PermissionError:
import os
print(f'Permission denied: {path}')
print(f'File permissions: {oct(Path(path).stat().st_mode)}')
print(f'Current user: {os.getlogin()}')
print('Try: chmod +r ' + str(path))
return None
except IsADirectoryError:
print(f'Path is a directory, not a file: {path}')
return None
# --- demo ---
import os
print(read_file_safely('does_not_exist.txt'))
os.makedirs('a_directory', exist_ok=True)
print(read_file_safely('a_directory'))
التقاط IsADirectoryError
يُرفع IsADirectoryError عند محاولة فتح دليل كما لو كان ملفًا. قد يحدث ذلك عندما ينشئ الوكيل مسارًا بطريقة غير صحيحة، مثل إضافة اسم ملف موجود أصلًا كدليل. احرص دائمًا على التقاطه لإخراج رسالة خطأ مفهومة.
from pathlib import Path
def safe_write(output_path, content):
path = Path(output_path)
# Check the path is not an existing directory
if path.is_dir():
raise IsADirectoryError(
f'Cannot write file: {path} is a directory. '
f'Use a filename like {path}/output.txt instead.'
)
# Ensure parent directory exists
path.parent.mkdir(parents=True, exist_ok=True)
try:
path.write_text(content, encoding='utf-8')
print(f'Written: {path} ({len(content)} chars)')
except IsADirectoryError as e:
print(f'Path error: {e}')
except PermissionError:
print(f'Cannot write to {path} — permission denied')
# --- demo ---
import os
safe_write('demo_output/report.txt', 'Agent finished the task.')
os.makedirs('already_a_dir', exist_ok=True)
try:
safe_write('already_a_dir', 'this will fail')
except IsADirectoryError as e:
print(f'Rejected: {e}')
الكتابات الذرية باستخدام tempfile
تُعد الكتابة مباشرةً إلى ملف أمرًا خطيرًا، فإذا تعطل الوكيل أثناء الكتابة، يُترك الملف مكتوبًا جزئيًا وتالفًا. والحل هو الكتابة الذرية: اكتب أولًا إلى ملف مؤقت، ثم أعد تسميته إلى المسار النهائي. إعادة التسمية ذرية على أنظمة POSIX، أي إن الملف النهائي يكون إما الإصدار القديم أو الإصدار الجديد، ولا يكون مكتوبًا جزئيًا أبدًا.
import tempfile
import os
import json
from pathlib import Path
def atomic_write_json(file_path, data):
path = Path(file_path)
path.parent.mkdir(parents=True, exist_ok=True)
# Write to temp file in same directory
tmp_fd, tmp_path = tempfile.mkstemp(
dir=path.parent,
prefix='.tmp_',
suffix='.json'
)
try:
with os.fdopen(tmp_fd, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
# Atomic rename: replaces final file in one operation
os.replace(tmp_path, path)
print(f'Atomically wrote: {path}')
except Exception as e:
os.unlink(tmp_path) # clean up temp file on error
raise
# --- demo ---
atomic_write_json('demo_state/state.json', {'step': 3, 'status': 'running'})
print('File contents:', Path('demo_state/state.json').read_text(encoding='utf-8'))
قفل الملفات للوكلاء المتزامنين
عند تشغيل عدة مثيلات من الوكيل بالتوازي وكتابتها في الملف نفسه، قد تتسبب حالات التسابق في إتلاف البيانات. استخدم قفل الملفات مع الوحدة fcntl (على Linux/macOS)، أو مكتبة filelock متعددة المنصات لمنع عمليات الكتابة المتزامنة.
from filelock import FileLock, Timeout
import json
from pathlib import Path
COUNTER_FILE = Path('shared_counter.json')
LOCK_FILE = Path('shared_counter.json.lock')
def increment_counter():
lock = FileLock(str(LOCK_FILE), timeout=10)
try:
with lock:
# Only one process can be here at a time
if COUNTER_FILE.exists():
data = json.loads(COUNTER_FILE.read_text())
else:
data = {'count': 0}
data['count'] += 1
COUNTER_FILE.write_text(
json.dumps(data, indent=2)
)
return data['count']
except Timeout:
print('Could not acquire lock within 10 seconds')
return Noneالحذف الآمن مع التحقق من الوجود
يؤدي حذف ملف غير موجود إلى رفع FileNotFoundError. ويؤدي حذف دليل باستخدام Path.unlink() إلى رفع IsADirectoryError. استخدم Path.unlink(missing_ok=True) (في Python 3.8 والإصدارات الأحدث)، أو تحقّق من الوجود أولًا لإجراء حذف آمن.
from pathlib import Path
import shutil
# Safe file deletion (Python 3.8+)
Path('temp_output.json').unlink(missing_ok=True)
# Safe directory deletion
def safe_remove(path):
p = Path(path)
if not p.exists():
print(f'Already gone: {p}')
return
if p.is_file():
p.unlink()
print(f'Deleted file: {p}')
elif p.is_dir():
shutil.rmtree(p)
print(f'Deleted directory: {p}')
else:
print(f'Unknown file type: {p}')
# Clean up temporary workspace
safe_remove('/tmp/agent_workspace/run_001')التحقق من مساحة القرص قبل الكتابة
ينبغي للوكلاء الذين يكتبون ملفات كبيرة (مخرجات الذكاء الاصطناعي ومجموعات البيانات والسجلات) التحقق أولًا من مساحة القرص المتاحة. تُرجع shutil.disk_usage(path) عدد البايتات الإجمالي والمستخدم والمتاح. تحقّق من أن المساحة المتاحة تتجاوز حجم المخرجات المتوقع قبل بدء عملية كتابة طويلة.
import shutil
from pathlib import Path
def check_disk_space(output_dir, required_bytes):
path = Path(output_dir)
path.mkdir(parents=True, exist_ok=True)
usage = shutil.disk_usage(path)
free_gb = usage.free / (1024 ** 3)
required_gb = required_bytes / (1024 ** 3)
print(f'Disk free: {free_gb:.2f} GB')
print(f'Required: {required_gb:.2f} GB')
if usage.free < required_bytes * 1.1: # 10% safety margin
raise IOError(
f'Insufficient disk space: '
f'{free_gb:.2f} GB free, '
f'{required_gb:.2f} GB required'
)
return True
# Before writing a 500 MB dataset
check_disk_space('/tmp/output', 500 * 1024 * 1024)إنشاء نسخة احتياطية قبل الاستبدال
عندما يحدّث الوكيل ملفًا موجودًا، فمن الحكمة الاحتفاظ بنسخة احتياطية من الإصدار السابق. يتيح ذلك التراجع إذا كان المحتوى الجديد غير صحيح. استخدم اسم ملف للنسخة الاحتياطية يتضمن طابعًا زمنيًا، وحدّد عدد النسخ الاحتياطية لتجنب امتلاء مساحة القرص.
import shutil
import datetime
from pathlib import Path
def write_with_backup(file_path, content, max_backups=5):
path = Path(file_path)
backup_dir = path.parent / '.backups'
backup_dir.mkdir(exist_ok=True)
# Backup existing file
if path.exists():
timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
backup = backup_dir / f'{path.name}.{timestamp}'
shutil.copy2(path, backup)
print(f'Backed up to: {backup}')
# Write new content
path.write_text(content, encoding='utf-8')
# Prune old backups
backups = sorted(backup_dir.glob(f'{path.name}.*'))
for old in backups[:-max_backups]:
old.unlink()
print(f'Pruned old backup: {old.name}')
# --- demo ---
write_with_backup('demo_notes.txt', 'version 1')
write_with_backup('demo_notes.txt', 'version 2')
print('Backups:', [p.name for p in sorted(Path('.backups').glob('demo_notes.txt.*'))])
تحقق سريع: الكتابات الذرية
اختبر مدى فهمك لأنماط كتابة الملفات الآمنة.
مراجعة عمليات الملفات الآمنة
أصبح بإمكان وكلائك الآن التعامل مع الملفات بطريقة دفاعية:
- التحقق قبل الوصول:
Path.exists()+Path.is_file()+os.access(path, os.R_OK) - التقاط FileNotFoundError وPermissionError وIsADirectoryError مع رسائل واضحة
- استخدام الكتابات الذرية (tempfile + os.replace) لمنع الملفات المكتوبة جزئيًا أو التالفة عند التعطل
- استخدام قفل الملفات (مكتبة filelock) عندما يكتب عدة وكلاء في الملف نفسه
- استخدام missing_ok=True للحذف الآمن دون رفع FileNotFoundError
- التحقق من مساحة القرص قبل عمليات الكتابة الكبيرة، والاحتفاظ بنسخ احتياطية قبل استبدال الملفات المهمة
الأسئلة الشائعة
هل درس «عمليات الملفات الآمنة مع معالجة الأخطاء» مجاني؟
نعم — نص درس «عمليات الملفات الآمنة مع معالجة الأخطاء» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة AI Agents، انتقل إلى CoddyKit PRO. تتضمن دورة AI Agents 4 دروس في المجموع.
ماذا ستتعلم في «عمليات الملفات الآمنة مع معالجة الأخطاء»؟
التحقق من وجود الملفات والأذونات ومعالجة أخطاء IO بسلاسة تتمرن على AI Agents مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ AI Agents؟
لا تُشترط خبرة سابقة. AI Agents على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.
كم من الوقت يستغرق درس «عمليات الملفات الآمنة مع معالجة الأخطاء»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس AI Agents هذا؟
نعم. كل درس في AI Agents يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- قراءة الملفات وكتابتها في سياق الوكيل
- اجتياز المجلدات واكتشاف الملفات
- التعامل مع تنسيقات الملفات: CSV وJSON وTXT
- عمليات الملفات الآمنة مع معالجة الأخطاء