اذهب إلى المحتوى

تعلّمنا في مقالٍ سابق كيفية إنشاء ملفات جديدة والكتابة فيها باستخدام لغة بايثون Python، ويمكن لبرامجك أيضًا تنظيم الملفات الموجودة مسبقًا على القرص الصلب. لا بد أنك جرّبتَ تصفح مجلدٍ مليء بالعشرات أو المئات أو حتى الآلاف من الملفات ونسخها أو إعادة تسميتها أو نقلها أو ضغطها جميعًا يدويًا، أو جرّبتَ مهامًا أخرى مثل المهام التالية:

  • إنشاء نُسخ من جميع ملفات PDF الموجودة في كل مجلدٍ فرعي من مجلدٍ ما.
  • إزالة الأصفار البادئة في أسماء الملفات لكل ملفٍ في مجلد يضم مئات الملفات المسمّاة مثلًا spam001.txt و spam002.txt وإلخ.
  • ضغط محتويات عدة مجلدات في ملف مضغوط ZIP واحد، والذي يمكن أن يكون نظام نسخ احتياطي بسيط.

يمكنك تنفيذ كافة هذه المهام المملة آليًا في شيفرة بايثون البرمجية، فإذا برمجتَ حاسوبك لإجراء هذه المهام، فيمكنك تحويله إلى محرر ملفات سريع في العمل ولا يرتكب أخطاءً أبدًا.

من المفيد رؤية امتداد الملف (مثل ‎.txt و ‎.pdf و ‎.jpg وإلخ) بسرعة عند بدء العمل مع الملفات، إذ يُرجَّح أن يعرض متصفح ملفاتك الامتدادات تلقائيًا في نظامي ماك macOS ولينكس Linux، ولكن قد تكون امتدادات الملفات مخفية افتراضيًا في نظام ويندوز Windows، لذا يمكنك إظهار الامتدادات من خلال الانتقال إلى قائمة ابدأ Start، ثم لوحة التحكم Control Panel، ثم المظهر وإضفاء طابع شخصي Appearance and Personalization، ثم خيارات مستكشف الملفات Folder Options. ألغِ تحديد خانة الاختيار إخفاء ملحقات الملفات لأنواع الملفات المعروفة Hide extensions for known file types في تبويب عرض View ضمن الإعدادات المتقدمة Advanced Settings.

وحدة shutil

تحتوي وحدة shutil (التي هي اختصار لأدوات الصدفة المساعدة Shell Utilities) على دوالٍ تتيح لك نسخ الملفات ونقلها وإعادة تسميتها وحذفها في برامج بايثون الخاصة بك، ولكن يجب أولًا أن تستخدم التعليمة import shutil لاستخدام هذه الدوال.

نسخ الملفات والمجلدات

توفّر وحدة shutil دوالًا لنسخ الملفات والمجلدات الكاملة، حيث سيؤدي استدعاء الدالة shutil.copy(source, destination)‎ إلى نسخ الملف من مسار المصدر source إلى المجلد الموجود في مسار الوِجهة destination، إذ يمكن أن يكون كلٌّ من source و destination سلاسلًا نصية أو كائنات Path. إذا كان destination اسم ملف، فسنستخدمه بوصفه اسمًا جديدًا للملف المنسوخ. تعيد هذه الدالة سلسلة نصية أو كائن Path للملف المنسوخ.

أدخِل مثلًا ما يلي في الصدفة التفاعلية Interactive Shell لترى كيفية عمل الدالة shutil.copy()‎:

   >>> import shutil, os
   >>> from pathlib import Path
   >>> p = Path.home()
 >>> shutil.copy(p / 'spam.txt', p / 'some_folder')
   'C:\\Users\\Al\\some_folder\\spam.txt'
 >>> shutil.copy(p / 'eggs.txt', p / 'some_folder/eggs2.txt')
   WindowsPath('C:/Users/Al/some_folder/eggs2.txt')

ينسخ استدعاء الدالة shutil.copy()‎ الأول الملف الموجود في C:\Users\Al\spam.txt إلى المجلد C:\Users\Al\some_folder، وتكون القيمة المُعادة هي مسار الملف المنسوخ، ولاحظ استخدام اسم الملف spam.txt الأصلي لاسم الملف المنسوخ الجديد عند تحديد مجلدٍ بوصفه الوِجهة ➊. ينسخ استدعاء الدالة shutil.copy()‎ الثاني ➋ الملف الموجود في C:\Users\Al\eggs.txt إلى المجلد C:\Users\Al\some_folder، ولكنه يعطي الملف المنسوخ الاسم eggs2.txt.

تنسخ الدالة shutil.copy()‎ ملفًا واحدًا، وتنسخ الدالة shutil.copytree()‎ مجلدًا كاملًا مع جميع المجلدات والملفات الموجودة فيه، حيث يؤدي استدعاء الدالة shutil.copytree(source, destination)‎ إلى نسخ المجلد الموجود في مسار المصدر source مع جميع ملفاته ومجلداته الفرعية إلى المجلد الموجود في مسار الوِجهة destination، إذ تكون المعاملات source و destination سلاسلًا نصية. تعيد هذه الدالة سلسلة نصية تمثّل مسار المجلد المنسوخ.

لندخِل مثلًا ما يلي في الصدفة التفاعلية:

>>> import shutil, os
>>> from pathlib import Path
>>> p = Path.home()
>>> shutil.copytree(p / 'spam', p / 'spam_backup')
WindowsPath('C:/Users/Al/spam_backup')

ينشئ استدعاء الدالة shutil.copytree()‎ السابق مجلدًا جديدًا بالاسم spam_backup الذي يحتوي على محتوى المجلد spam الأصلي نفسه، وبالتالي سنحصل بأمان على نسخة احتياطية من المجلد spam.

نقل وإعادة تسمية الملفات والمجلدات

سيؤدي استدعاء الدالة shutil.move(source, destination)‎ إلى نقل الملف أو المجلد الموجود في مسار المصدر source إلى مسار الوجهة destination، وسيعيد سلسلة نصية للمسار المطلق الخاص بالموقع الجديد.

إذا أشار مسار الوجهة destination إلى مجلد، فسيُنقَل ملف المصدر source إلى الوجهة destination ويحتفظ باسم الملف الحالي. لندخِل مثلًا ما يلي في الصدفة التفاعلية:

>>> import shutil
>>> shutil.move('C:\\beef.txt', 'C:\\eggs')
'C:\\eggs\\beef.txt'

لنفترض أن المجلد الذي اسمه eggs موجودٌ مسبقًا في المجلد ‎C:\‎، فسيمثّل استدعاء الدالة ‎shutil.move()‎ نقلَ الملف C:\beef.txt إلى المجلد C:\eggs. إذا كان الملف beef.txt موجودًا مسبقًا في المجلد C:\eggs، فسيُكتَب فوقه، لذا يجب عليك توخي الحذر عند استخدام الدالة move()‎ لأنه من السهل الكتابة فوق الملفات عن طريق الخطأ باستخدام هذه الطريقة.

يمكن لمسار الوِجهة destination أيضًا تحديد اسم الملف، حيث سننقل ملف المصدر source ونعيد تسميته في المثال التالي:

>>> shutil.move('C:\\beef.txt', 'C:\\eggs\\new_beef.txt')
'C:\\eggs\\new_beef.txt'

يمثّل السطر السابق نقل الملف C:\beef.txt إلى المجلد C:\eggs وإعادة تسميته بالاسم new_beef.txt.

نفترض في المثالين السابقين وجود المجلد eggs في المجلد ‎C:\‎، ولكن إن لم يوجَد المجلد eggs، فستعيد الدالة move()‎ تسمية الملف beef.txt إلى ملفٍ اسمه eggs.

>>> shutil.move('C:\\beef.txt', 'C:\\eggs')
'C:\\eggs'

لم تتمكّن الدالة move()‎ من العثور على مجلدٍ بالاسم eggs في المجلد C:\‎، وبالتالي تفترض أن الوجهة destination يجب أن تحدد اسم ملفٍ وليس اسم مجلد، لذلك أُعيدت تسمية الملف النصي beef.txt إلى egg (ملف نصي بدون امتداد الملف ‎.txt)، وربما ليس هذا ما أردته. يمكن أن يكون ذلك خطأً يصعب اكتشافه في برامجك، لأن استدعاء الدالة move()‎ يمكن أن يفعل شيئًا قد يكون مختلفًا تمامًا عمّا تتوقعه، وهذا سبب آخر لتوخي الحذر عند استخدام الدالة move()‎.

أخيرًا، يجب أن تكون المجلدات التي تشكّل الوِجهة موجودة فعليًا، وإلّا فسترمي شيفرة بايثون استثناءً. لندخِل الآن ما يلي في الصدفة التفاعلية:

>>> shutil.move('spam.txt', 'c:\\does_not_exist\\eggs\\meat')
Traceback (most recent call last):
  --snip--
FileNotFoundError: [Errno 2] No such file or directory: 'c:\\does_not_exist\\
eggs\\meat'

تبحث شيفرة بايثون عن المجلدين eggs و meat ضمن المجلد does_not_exist، ولكنها لا تعثر على هذا المجلد، لذا لا يمكنها نقل الملف spam.txt إلى المسار الذي حدّدته.

حذف الملفات والمجلدات نهائيًا

يمكنك حذف ملف واحد أو مجلد واحد فارغ باستخدام دوال تابعة للوحدة os، ولكن يمكنك حذف مجلدٍ وجميع محتوياته باستخدام الوحدة shutil، وهذه الدوال هي:

  • سيؤدي استدعاء الدالة os.unlink(path)‎ إلى حذف الملف الموجود في المسار path.
  • سيؤدي استدعاء الدالة os.rmdir(path)‎ إلى حذف المجلد الموجود في المسار path، ولكن يجب أن يكون هذا المجلد خاليًا من أي ملفات أو مجلدات.
  • سيؤدي استدعاء الدالة shutil.rmtree(path)‎ إلى إزالة المجلد الموجود في المسار path، وستُحذَف جميع الملفات والمجلدات الموجودة ضمنه.

كن حذرًا عند استخدام هذه الدوال في برامجك، إذ من الجيد أن تشغّل برنامجك أولًا مع تعليق هذه الاستدعاءات وإضافة استدعاءات الدالة print()‎ لإظهار الملفات التي ستُحذَف. إليك فيما يلي برنامج بايثون الذي يهدف إلى حذف الملفات التي لها امتداد الملف ‎.txt، ولكن يوجد به خطأ مطبعي يؤدي إلى حذف ملفات ‎.rxt بدلًا من ذلك:

import os
from pathlib import Path
for filename in Path.home().glob('*.rxt'):
    os.unlink(filename)

إذا كان لديك أيّ ملفات مهمة تنتهي بالامتداد ‎.rxt فستُحذَف نهائيًا عن طريق الخطأ، لذا يجب أولًا أن تشغّل البرنامج كما يلي:

import os
from pathlib import Path
for filename in Path.home().glob('*.rxt'):
    #os.unlink(filename)
    print(filename)

لاحظ أننا علّقنا استدعاء الدالة os.unlink()‎، لذا تجاهلته شيفرة بايثون، وستطبع اسم الملف المحذوف فقط، حيث سيؤدي تشغيل هذه النسخة من البرنامج أولًا إلى إظهار أنك طلبت من البرنامج عن طريق الخطأ حذف ملفات ‎.rxt بدلًا من ملفات ‎.txt.

تأكّد من عمل البرنامج بالطريقة الصحيحة، ثم احذف سطر التعليمة print(filename)‎ وألغِ التعليق عند سطر التعليمة os.unlink(filename)‎، ثم شغّل البرنامج مرة أخرى لحذف الملفات فعليًا.

الحذف الآمن باستخدام وحدة send2trash

تحذف الدالة shutil.rmtree()‎ المُدمَجة مع لغة بايثون الملفات والمجلدات نهائيًا، ولكن قد يكون استخدامها خطيرًا، لذا توجد طريقة أفضل بكثير لحذف الملفات والمجلدات، وهي استخدام الوحدة send2trash الخارجية. يمكنك تثبيت هذه الوحدة من خلال تشغيل الأمر pip install --user send2trash من نافذة الطرفية Terminal.

يُعَد استخدام الوحدة send2trash أكثر أمانًا من دوال الحذف العادية الخاصة بلغة بايثون، لأنها سترسل المجلدات والملفات إلى سلة المهملات أو سلة المحذوفات الخاصة بحاسوبك بدلًا من حذفها نهائيًا. إذا أدّى خطأٌ ما في برنامجك إلى حذفٍ شيءٍ باستخدام الوحدة send2trash ولا تريد حذفه، فيمكنك استعادته من سلة المحذوفات لاحقًا.

أدخِل مثلًا ما يلي في الصدفة التفاعلية بعد تثبيت الوحدة send2trash:

>>> import send2trash
>>> beefFile = open('beef.txt', 'a')   # إنشاء الملف
>>> beefFile.write('Beef is not a vegetable.')
25
>>> beefFile.close()
>>> send2trash.send2trash('beef.txt')

يجب دائمًا أن تستخدم الدالة send2trash.send2trash()‎ لحذف الملفات والمجلدات، ولكن بالرغم من أن إرسال الملفات إلى سلة المحذوفات يتيح لك استعادتها لاحقًا، إلّا أنه لن يؤدي إلى تحرير مساحةٍ من القرص الصلب كما يفعل الحذف النهائي، لذا إذا أردتَ أن يحرّر برنامجك مساحةً من القرص الصلب، فاستخدم دوال الوحدتين os و shutil لحذف الملفات والمجلدات. لاحظ أن الدالة send2trash()‎ يمكنها إرسال الملفات إلى سلة المحذوفات فقط، ولا يمكنها سحب الملفات منها.

المرور على شجرة مجلدات

لنفترض أنك تريد إعادة تسمية كل ملف في مجلدٍ ما وكل ملفٍ في كل مجلدٍ فرعي من هذا المجلد، وهذا يعني أنك تريد المرور على شجرة المجلدات، والتفاعل مع جميع الملفات أثناء المرور عليها. قد تكون كتابة برنامجٍ لذلك أمرًا صعبًا، ولكن توفّر بايثون الدالة os.walk()‎ للتعامل مع هذه العملية نيابةً عنك.

أولًا، لنلقِ نظرة على المجلد C:\delicious ومحتوياته كما هو موضح في الشكل التالي:

01 000047

مثال لمجلد يحتوي على ثلاثة مجلدات وأربعة ملفات

إليك فيما يلي مثال لبرنامج يستخدم الدالة os.walk()‎ مع شجرة المجلدات من الشكل السابق:

import os

for folderName, subfolders, filenames in os.walk('C:\\delicious'):
    print('The current folder is ' + folderName)

    for subfolder in subfolders:
        print('SUBFOLDER OF ' + folderName + ': ' + subfolder)

    for filename in filenames:
        print('FILE INSIDE ' + folderName + ': '+ filename)

    print('')

نمرّر قيمة سلسلة نصية واحدة تمثّل مسار المجلد إلى الدالة os.walk()‎ التي يمكنك استخدامها في تعليمة حلقة for للمرور على شجرة المجلدات، حيث يشبه ذلك استخدام الدالة range()‎ للمرور على مجالٍ من الأعداد، ولكن ستعيد الدالة os.walk()‎ ثلاث قيم في كل تكرار من هذه الحلقة، وهذه القيم هي:

  • سلسلة نصية تمثّل اسم المجلد الحالي.
  • قائمة من السلاسل النصية التي تمثّل المجلدات الموجودة في المجلد الحالي.
  • قائمة من السلاسل النصية التي تمثّل الملفات الموجودة في المجلد الحالي.

ملاحظة: المجلد الحالي هو المجلد الخاص بالتكرار الحالي لحلقة for، ولم تغيّر الدالة os.walk()‎ مجلد العمل الحالي للبرنامج.

يمكنك اختيار اسم المتغير i في شيفرة for i in range(10):‎، ويمكنك أيضًا اختيار أسماء المتغيرات للقيم الثلاث المذكورة سابقًا، ولكننا سنستخدم أسماء المتغيرات foldername و subfolders و filenames في أغلب الأحيان.

إذا شغّلنا البرنامج، فسينتج ما يلي:

The current folder is C:\delicious
SUBFOLDER OF C:\delicious: cats
SUBFOLDER OF C:\delicious: walnut
FILE INSIDE C:\delicious: spam.txt

The current folder is C:\delicious\cats
FILE INSIDE C:\delicious\cats: catnames.txt
FILE INSIDE C:\delicious\cats: zophie.jpg

The current folder is C:\delicious\walnut
SUBFOLDER OF C:\delicious\walnut: waffles

The current folder is C:\delicious\walnut\waffles
FILE INSIDE C:\delicious\walnut\waffles: butter.txt.

تعيد الدالة os.walk()‎ قوائمًا من السلاسل النصية التي تمثّل المتغيرات subfolder و filename، لذا يمكنك استخدام هذه القوائم في حلقات for الخاصة بها. ضع شيفرتك البرمجية مكان استدعاءات الدالة print()‎، أو احذف حلقتي for إن لم تكن بحاجة إليهما.

ضغط الملفات باستخدام الوحدة zipfile

قد تكون على دراية بالملفات المضغوطة ZIP ذات امتداد الملف ‎.zip، والتي يمكنها الاحتفاظ بالمحتويات المضغوطة للعديد من الملفات الأخرى، حيث يؤدي ضغط الملف إلى تقليل حجمه، ويُعَد ذلك أمرًا مفيدًا عند نقله عبر الإنترنت. يمكن أن يحتوي ملف ZIP أيضًا على ملفات ومجلدات فرعية متعددة، لذا تُعَد طريقة سهلة لحَزم ملفات متعددة في ملف واحد، ويمكن بعد ذلك مثلًا إرفاق هذا الملف الذي يسمّى ملف الأرشفة Archive File مع رسالة بريد إلكتروني.

يمكن لبرامج بايثون الخاص بك إنشاء ملفات ZIP وفتحها (أو فك ضغطها Extract) باستخدام الدوال الموجودة في الوحدة zipfile. لنفترض أن لديك ملف ZIP بالاسم example.zip ويحتوي على المحتويات الموضّحة في الشكل التالي:

02 000008

محتويات الملف example.zip

يمكنك تنزيل هذا الملف من موقع nostarch أو المتابعة باستخدام ملف ZIP موجود مسبقًا على حاسوبك.

قراءة الملفات المضغوطة ZIP

يمكنك قراءة محتويات ملف مضغوط ZIP من خلال إنشاء كائن ZipFile أولًا (لاحظ الأحرف الكبيرة Z و F)، حيث تتشابه كائنات ZipFile مع كائنات File التي تعيدها الدالة open()‎، فهي قيم يتفاعل البرنامج من خلالها مع الملف. ننشئ كائن ZipFile من خلال استدعاء الدالة zipfile.ZipFile()‎ وتمرير سلسلة نصية تمثّل اسم ملف ‎.ZIP إليها. لاحظ أن zipfile هو اسم وحدة بايثون، وأن ZipFile()‎ هو اسم الدالة.

أدخِل مثلًا ما يلي في الصدفة التفاعلية:

   >>> import zipfile, os

   >>> from pathlib import Path
   >>> p = Path.home()
   >>> exampleZip = zipfile.ZipFile(p / 'example.zip')
   >>> exampleZip.namelist()
   ['spam.txt', 'cats/', 'cats/catnames.txt', 'cats/zophie.jpg']
   >>> spamInfo = exampleZip.getinfo('spam.txt')
   >>> spamInfo.file_size
   13908
   >>> spamInfo.compress_size
   3828
 >>> f'Compressed file is {round(spamInfo.file_size / spamInfo
   .compress_size, 2)}x smaller!'
   )
   'Compressed file is 3.63x smaller!'
   >>> exampleZip.close()

يحتوي كائن ZipFile على التابع namelist()‎ الذي يعيد قائمةً من السلاسل النصية التي تمثّل جميع الملفات والمجلدات الموجودة في ملف ZIP، حيث يمكن تمرير هذه السلاسل النصية إلى التابع getinfo()‎ لإعادة كائن ZipInfo لهذا الملف المحدّد. تمتلك كائنات ZipInfo سماتها Attributes الخاصة مثل السمات file_size و compress_size بالبايتات، والتي تحتوي على أعداد صحيحة لحجم الملف الأصلي وحجم الملف المضغوط على التوالي. يمثل كائن ZipInfo ملف أرشفة كامل، ولكن يحمل كائن ZipInfo أيضًا معلومات مفيدة حول ملف واحد في ملف الأرشفة.

يحسب الأمر الموجود في التعليمة ➊ مدى كفاءة ضغط الملف example.zip من خلال قسمة حجم الملف الأصلي على حجم الملف المضغوط ويطبع هذه المعلومات.

فك ضغط ملفات ZIP

يفك التابع extractall()‎ الخاص بكائنات ZipFile ضغط جميع الملفات والمجلدات من ملف مضغوط ZIP إلى مجلد العمل الحالي.

   >>> import zipfile, os
   >>> from pathlib import Path
   >>> p = Path.home()
   >>> exampleZip = zipfile.ZipFile(p / 'example.zip')
 >>> exampleZip.extractall()
   >>> exampleZip.close()

يؤدي تشغيل الشيفرة البرمجية السابقة إلى فَك ضغط محتويات الملف example.zip في المجلد ‎C:\‎. يمكنك اختياريًا تمرير اسم مجلد إلى التابع ‎extractall()‎ لفك ضغط الملفات في مجلد آخر مختلفٍ عن مجلد العمل الحالي، وإذا كان المجلد الذي مرّرناه إلى التابع extractall()‎ غير موجود، فسيُشَأ هذا المجلد، فمثلًا إذا وضعتَ الاستدعاء exampleZip.extractall('C:\\delicious')‎ مكان الاستدعاء ➊، فستفك الشيفرة البرمجية ضغط الملفات من الملف example.zip إلى المجلد C:\delicious الذي أنشأناه.

يفك التابع extract()‎ الخاص بكائنات ZipFile ضغط ملفٍ واحد من الملف المضغوط ZIP. تابع مثال الصدفة التفاعلية بما يلي:

>>> exampleZip.extract('spam.txt')
'C:\\spam.txt'
>>> exampleZip.extract('spam.txt', 'C:\\some\\new\\folders')
'C:\\some\\new\\folders\\spam.txt'
>>> exampleZip.close()

يجب أن تتطابق السلسلة النصية التي تمررها إلى التابع extract()‎ مع إحدى السلاسل النصية الموجودة في القائمة التي يعيدها التابع namelist()‎، ويمكنك اختياريًا تمرير وسيطٍ ثانٍ إلى التابع extract()‎ لفك ضغط الملف في مجلد آخر مختلف عن مجلد العمل الحالي، حيث إذا كان هذا الوسيط الثاني مجلدًا غير موجودٍ بعد، فستنشِئ شيفرة بايثون هذا المجلد. القيمة التي يعيدها التابع extract()‎ هي المسار المطلق الذي فكينا ضغط الملف فيه.

إنشاء ملفات ZIP والإضافة إليها

يمكنك إنشاء ملفات ZIP المضغوطة من خلال فتح كائن ZipFile في وضع الكتابة مع تمرير 'w' كوسيط ثانٍ، حيث يشبه ذلك فتح ملفٍ نصي في وضع الكتابة من خلال تمرير 'w' إلى الدالة open()‎.

إذا مرّرتَ مسارًا إلى التابع ‎write()‎ مع كائن ZipFile، ستضغط شيفرة بايثون الملف الموجود في هذا المسار وتضيفه إلى ملف ZIP. الوسيط الأول للتابع ‎write()‎ هو سلسلة نصية تمثّل اسم الملف المراد إضافته، والوسيط الثاني هو معامل يمثّل نوع عملية الضغط، إذ يخبر هذا النوع الحاسوبَ بالخوارزمية التي يجب أن يستخدمها لضغط الملفات، حيث يمكنك دائمًا ضبط هذه القيمة على zipfile.ZIP_DEFLATED التي تحدّد خوارزمية الضغط Deflate التي تعمل على جميع أنواع البيانات. أدخِل مثلًا ما يلي في الصدفة التفاعلية:

>>> import zipfile
>>> newZip = zipfile.ZipFile('new.zip', 'w')
>>> newZip.write('spam.txt', compress_type=zipfile.ZIP_DEFLATED)
>>> newZip.close()

ستؤدي الشيفرة البرمجية السابقة إلى إنشاء ملف ZIP جديد بالاسم new.zip، حيث يحتوي هذا الملف على محتويات مضغوطة للملف spam.txt.

ضع في بالك أن وضع الكتابة سيؤدي إلى مسح جميع المحتويات الموجودة مسبقًا في ملف ZIP كما هو الحال مع الكتابة في الملفات. إذا أردتَ ببساطة إضافة ملفات إلى ملف ZIP موجود مسبقًا، فمرّر 'a' كوسيطٍ ثانٍ إلى الدالة zipfile.ZipFile()‎ لفتح ملف ZIP في وضع الإلحاق Append Mode.

تطبيق عملي: إعادة تسمية الملفات ذات تواريخ النمط الأمريكي إلى تواريخ النمط الأوروبي

لنفترض أن مديرك في العمل يرسل إليك عبر البريد الإلكتروني آلاف الملفات ذات تواريخ النمط الأمريكي (MM-DD-YYYY) الموجودة في أسماء هذه الملفات ويريد إعادة تسميتها إلى تواريخ النمط الأوروبي (DD-MM-YYYY)، إذ قد يستغرق إنجاز هذه المهمة المملة يدويًا وقتًا طويلًا، إذًا لنكتب برنامجًا ينّفذ هذه المهمة نيابةً عنك.

إليك الخطوات التي يفعلها هذا البرنامج:

  1. البحث في جميع أسماء الملفات الموجودة في مجلد العمل الحالي عن التواريخ ذات النمط الأمريكي.
  2. إعادة تسمية الملف مع التبديل بين الشهر واليوم لجعله على النمط الأوروبي عند العثور على أحد هذه الملفات.

وبالتالي يجب أن تطبّق شيفرتك البرمجية الخطوات التالية:

  1. إنشاء تعبير نمطي Regex يمكنه تحديد نمط النص للتواريخ ذات النمط الأمريكي.
  2. استدعاء التابع ‎os.listdir()‎ للعثور على جميع الملفات الموجودة في مجلد العمل.
  3. المرور ضمن حلقة على جميع أسماء الملفات باستخدام التعبير النمطي للتحقق من احتوائه على تاريخ.
  4. إذا احتوى اسم الملف على تاريخ، فيجب إعادة تسمية الملف باستخدام الدالة shutil.move()‎.

افتح نافذةً جديدة في محرّرك لإنشاء ملف جديد للمشروع واحفظ شيفرتك البرمجية بالاسم renameDates.py.

الخطوة الأولى: إنشاء تعبير نمطي للتواريخ ذات النمط الأمريكي

سيحتاج الجزء الأول من البرنامج إلى استيراد الوحدات الضرورية وإنشاء تعبير نمطي يمكنه تحديد تواريخ النمط الأمريكي MM-DD-YYYY. ستذكرك تعليقات TODO في النهاية بما تبقى لتكتبه في هذا البرنامج، حيث كتبناها ليسهل عليك العثور عليها باستخدام ميزة البحث Ctrl-F في محرّر Mu. اجعل شيفرتك البرمجية تبدو كما يلي:

   #! python3
   # renameDates.py - ‫إعادة تسمية أسماء الملفات ذات تنسيق التاريخ الأمريكي MM-DD-YYYY إلى
   # ‫تنسيق التاريخ الأوروبي DD-MM-YYYY

 import shutil, os, re

   # إنشاء تعبير نمطي يطابق الملفات ذات تنسيق التاريخ الأمريكي
 datePattern = re.compile(r"""^(.*?) # كل النص قبل التاريخ
       ((0|1)?\d)-                     # رقم أو رقمين للشهر
       ((0|1|2|3)?\d)-                 # رقم أو رقمين لليوم
       ((19|20)\d\d)                   # أربعة أرقام للسنة
       (.*?)$                          # كل النص بعد التاريخ
       """, re.VERBOSE➌)

   # TODO: المرور ضمن حلقة على الملفات الموجودة في مجلد العمل

   # TODO: تخطي الملفات التي تكون بدون تاريخ

   # TODO: الحصول على الأجزاء المختلفة من اسم الملف

   # TODO: تشكيل اسم الملف على النمط الأوروبي

   # TODO: الحصول على مسارات الملفات الكاملة والمطلقة

   # TODO: إعادة تسمية الملفات

تعلّمنا في هذا المقال أنه يمكن استخدام الدالة shutil.move()‎ لإعادة تسمية الملفات، ووسطاؤها هي اسم الملف المراد إعادة تسميته واسم الملف الجديد، ويجب استيراد الوحدة shutil ➊ بسبب وجود هذه الدالة فيها.

يجب تحديد الملفات التي تريد إعادة تسميتها قبل إعادة تسميتها، إذ يجب إعادة تسمية أسماء الملفات التي لها تواريخ مثل spam4-4-1984.txt و 01‎-03-2014eggs.zip، بينما يمكن تجاهل أسماء الملفات التي لا تحتوي على تواريخ مثل littlebrother.epub.

يمكنك استخدام تعبير نمطي لتحديد هذا النمط، لذا استدعِ التابع re.compile()‎ لإنشاء كائن Regex ➋ بعد استيراد الوحدة re في البداية. سيسمح تمرير القيمة re.VERBOSE للوسيط الثاني ➌ بوجود المسافات البيضاء والتعليقات في السلسلة النصية للتعبير النمطي لجعلها أكثر قابلية للقراءة.

تبدأ السلسلة النصية للتعبير النمطي بالمحارف ‎^‎(‎.‎*‎?‎)‎ لمطابقة أيّ نصٍ موجود في بداية اسم الملف الذي قد يأتي قبل التاريخ. تطابق المجموعة ‎((0|1)?\d)‎ الشهر، حيث يمكن أن يكون الرقم الأول إما 0 أو 1، وبالتالي يطابق التعبير النمطي القيمةَ 12 للشهر 12 ويطابق القيمة 02 للشهر الثاني، حيث يكون هذا الرقم اختياريًا أيضًا بحيث يمكن أن يكون الشهر 04 أو 4 للشهر الرابع. مجموعة اليوم هي ‎((0|1|2|3)?\d)‎ وتتبع منطقًا مشابهًا لمجموعة الشهر، حيث تُعَد القيم 3 و 03 و 31 أرقامًا صالحة للأيام. لاحظ أن هذا التعبير النمطي سيقبل بعض التواريخ غير الصالحة مثل 4‎-31-2022 و 2‎-29-2023 و 0‎-15-2024، إذ تحتوي التواريخ على الكثير من الحالات الخاصة التي يمكن أن نخطئ بها بسهولة، ولكن يعمل التعبير النمطي في هذا البرنامج جيدًا بما فيه الكفاية للتبسيط.

تُعَد السنة 1885 سنةً صالحة، ولكن يمكنك فقط البحث عن السنوات في القرن العشرين أو الحادي والعشرين، مما سيؤدي إلى منع برنامجك من مطابقة أسماء الملفات التي لها تنسيق مشابه للتاريخ ولكنها لا تمثّل تواريخًا مثل 10‎-10-1000.txt عن طريق الخطأ.

أخيرًا، يتطابق الجزء $(?*.) من التعبير النمطي مع أيّ نص يأتي بعد التاريخ.

الخطوة الثانية: تحديد أجزاء التاريخ من أسماء الملفات

يجب بعد ذلك أن يمر البرنامج ضمن حلقة على قائمة السلاسل النصية لأسماء الملفات التي يعيدها التابع os.listdir()‎، وأن يطابقها مع التعبير النمطي. يجب تخطي أيّ ملفات لا تتضمن تاريخًا في اسمها، وسيُخزَّن النص المطابق في عدة متغيرات بالنسبة لأسماء الملفات التي تحتوي على تاريخ فيها. املأ المهام TODO الثلاثة الأولى في برنامجك بالشيفرة البرمجية التالية:

#! python3
# renameDates.py - ‫إعادة تسمية أسماء الملفات ذات تنسيق التاريخ الأمريكي MM-DD-YYYY إلى
# ‫تنسيق التاريخ الأوروبي DD-MM-YYYY

--snip--

# المرور ضمن حلقة على الملفات الموجودة في مجلد العمل
for amerFilename in os.listdir('.'):
    mo = datePattern.search(amerFilename)

    # تخطي الملفات التي تكون بدون تاريخ
   if mo == None:
       continue

   # الحصول على الأجزاء المختلفة من اسم الملف
    beforePart = mo.group(1)
    monthPart  = mo.group(2)
    dayPart    = mo.group(4)
    yearPart   = mo.group(6)
    afterPart  = mo.group(8)

--snip

إذا كانت قيمة كائن Match الذي يعيده التابع search()‎ هي None ➊، فلن يتطابق اسم الملف الموجود في المتغير amerFilename مع التعبير النمطي، وستتخطى تعليمة continue ➋ بقية الحلقة وتنتقل إلى اسم الملف التالي، وإلّا فستُخزَّن السلاسل النصية المختلفة المطابِقة مع مجموعات التعبير النمطي في متغيرات بالاسم beforePart و monthPart و dayPart و yearPart و afterPart ➌. ستُستخدَم السلاسل النصية الموجودة في هذه المتغيرات لتشكيل اسم الملف على النمط الأوروبي في الخطوة التالية.

أبقِ أرقام المجموعة سهلة الاستخدام من خلال محاولة قراءة التعبير النمطي من البداية وحساب كل مرة يظهر فيها قوس مفتوح. اكتب مخططًا تفصيليًا للتعبير النمطي دون التفكير في الشيفرة البرمجية، حيث يمكن أن يساعدك المثال التالي في تصوّر هذه المجموعات:

datePattern = re.compile(r"""^(1) # كل النص قبل التاريخ
    (2 (3) )-                     # رقم أو رقمين للشهر
    (4 (5) )-                     # رقم أو رقمين لليوم
    (6 (7) )                      # أربعة أرقام للسنة
    (8)$                          # كل النص بعد التاريخ
    """, re.VERBOSE)

تمثل الأرقام من 1 إلى 8 في المثال السابق المجموعات في التعبير النمطي الذي كتبته. يمكن أن يمنحكَ إنشاء مخطط تفصيلي للتعبير النمطي باستخدام الأقواس وأرقام المجموعات فقط فهمًا أوضح لتعبيرك النمطي قبل الانتقال إلى بقية البرنامج.

الخطوة الثالثة: تشكيل اسم الملف الجديد وإعادة تسمية الملفات

جرّب سَلسَلة السلاسل النصية الموجودة في المتغيرات من الخطوة السابقة مع التاريخ ذي النمط الأوروبي، حيث يأتي اليوم قبل الشهر. املأ المهام TODO الثلاثة المتبقية في برنامجك بالشيفرة البرمجية التالية:

#! python3
# renameDates.py - ‫إعادة تسمية أسماء الملفات ذات تنسيق التاريخ الأمريكي MM-DD-YYYY إلى
# ‫تنسيق التاريخ الأوروبي DD-MM-YYYY

--snip--

     # تشكيل اسم الملف على النمط الأوروبي
   euroFilename = beforePart + dayPart + '-' + monthPart + '-' + yearPart +
                    afterPart

     # الحصول على مسارات الملفات الكاملة والمطلقة
     absWorkingDir = os.path.abspath('.')
     amerFilename = os.path.join(absWorkingDir, amerFilename)
     euroFilename = os.path.join(absWorkingDir, euroFilename)

     # إعادة تسمية الملفات
   print(f'Renaming "{amerFilename}" to "{euroFilename}"...')
   #shutil.move(amerFilename, euroFilename)   # ألغِ التعليق بعد الاختبار

خزّن السلسلة النصية المتسلسلة في متغير بالاسم euroFilename ➊، ثم مرّر اسم الملف الأصلي الموجود في المتغير amerFilename والمتغير euroFilename الجديد إلى الدالة shutil.move()‎ لإعادة تسمية الملف ➌.

يتضمن هذا البرنامج تعليقًا على استدعاء الدالة ‎shutil.move()‎، حيث يطبع أسماء الملفات التي ستُعاد تسميتها ➋. يمكن أن يتيح لك تشغيل البرنامج بهذه الطريقة أولًا التحققَ من إعادة تسمية الملفات بطريقة صحيحة، ثم يمكنك إلغاء تعليق استدعاء الدالة ‎shutil.move()‎ وتشغيل البرنامج مرة أخرى لإعادة تسمية الملفات فعليًا.

أفكار لبرامج مماثلة

هناك العديد من الأسباب الأخرى التي قد تجعلك ترغب في إعادة تسمية عدد كبير من الملفات مثل:

  • إضافة بادئة إلى بداية اسم الملف مثل إضافة spam_‎ لإعادة تسمية الملف eggs.txt إلى الاسم spam_eggs.txt.
  • تغيير أسماء الملفات التي تحتوي على تواريخ ذات نمط أوروبي إلى تواريخ ذات نمط الأمريكي.
  • حذف الأصفار من أسماء الملفات مثل spam0042.txt.

تطبيق عملي: إنشاء نسخة احتياطية لمجلد في ملف مضغوط ZIP

لنفترض أنك تعمل على مشروع تحتفظ بملفاته في مجلد بالاسم C:\AlsPythonBook، ولا بد أنك قلق بشأن فقدان عملك، لذا سترغب في إنشاء "لقطات" من ملفات ZIP للمجلد بأكمله، إذ قد ترغب في الاحتفاظ بنسخ مختلفة، لذلك يجب أن يزيد اسم ملف ZIP في كل مرة تنشئ فيها نسخة مثل AlsPythonBook_1.zip و AlsPythonBook_2.zip و AlsPythonBook_3.zip وإلخ. يمكنك إنجاز ذلك يدويًا، ولكنه أمر مزعج إلى حدٍ ما، وقد تخطئ في ترقيم أسماء ملفات ZIP، فمن الأسهل تشغيل برنامج ينجز هذه المهمة المملة نيابةً عنك.

افتح نافذة جديدة في محرّرك لإنشاء ملف جديد لهذا المشروع واحفظه بالاسم backupToZip.py.

الخطوة الأولى: اكتشاف اسم الملف المضغوط ZIP

سنضع الشيفرة البرمجية الخاصة بهذا البرنامج في دالة اسمها backupToZip()‎، حيث سيؤدي ذلك إلى تسهيل نسخ الدالة ولصقها في برامج بايثون الأخرى التي تحتاج إليها. ستُستدعَى هذه الدالة لإجراء النسخ الاحتياطي في نهاية البرنامج، لذا اجعل برنامجك يبدو كما يلي:

   #! python3
   # backupToZip.py - ‫نسخ مجلد كامل ومحتوياته في ملف ZIP يزيد اسمه بمقدار واحد في كل مرة يُنسَخ فيها

 import zipfile, os

   def backupToZip(folder):
       # إنشاء نسخة احتياطية من محتويات المجلد بالكامل في ملف‫ ZIP

       folder = os.path.abspath(folder)   # التأكد من أن المجلد مسار مطلق

       # اكتشاف اسم الملف الذي يجب أن تستخدمه هذه الشيفرة البرمجية بناءً على الملفات الموجودة مسبقًا
     number = 1
     while True:
           zipFilename = os.path.basename(folder) + '_' + str(number) + '.zip'
           if not os.path.exists(zipFilename):
               break
           number = number + 1

     # TODO: إنشاء ملف مضغوط‫ ZIP

       # TODO: المرور على شجرة المجلدات بأكملها وضغط الملفات الموجودة في كل مجلد
       print('Done.')

backupToZip('C:\\delicious')

أضِف أولًا سطر Shebang (الذي يبدأ بالسلسلة النصية !#) مع وصف ما يفعله البرنامج، ثم استورد وحدات zipfile و os ➊. عرّف بعد ذلك دالة بالاسم backupToZip()‎، حيث تأخذ هذه الدالة معاملًا واحدًا فقط هو folder، والذي هو سلسلة نصية تمثّل مسارًا إلى المجلد الذي يجب نسخ محتوياته احتياطيًا. ستحدّد هذه الدالة اسم الملف المُستخدَم لملف ZIP الذي ستنشئه، ثم تنشئ هذه الدالة الملف، وتمر على المجلد folder، وتضيف كلًا من المجلدات الفرعية والملفات إلى ملف ZIP. اكتب تعليقات TODO لهذه الخطوات في الشيفرة البرمجية لتذكير نفسك بإنجازها لاحقًا ➍.

يستخدم الجزء الأول -الذي يمثّل تسمية الملف ZIP- الاسم الأساسي للمسار المطلق للمجلد folder. إذا كان المجلد الذي ننسخه احتياطيًا هو C:\delicious، فيجب أن يكون اسم الملف ZIP هو delicious_N.zip، حيث N = 1 هي المرة الأولى التي نشغّل فيها البرنامج و N = 2 هي المرة الثانية وإلخ.

يمكنك تحديد ما يجب أن تكون عليه قيمة N من خلال التحقق مما إذا كان الملف delicious1.zip موجودًا مسبقًا، ثم التحقق مما إذا كان الملف delicious2.zip موجودًا مسبقًا وإلخ. استخدم متغيرًا اسمه number لتمثيل N ➋، واستمر في زيادته ضمن الحلقة التي تستدعي التابع os.path.exists()‎ للتحقق من وجود الملف ➌. سيؤدي العثور على أول اسم ملف غير موجود إلى كسر الحلقة باستخدام التعليمة break، لأنها عثرت على اسم الملف المضغوط الجديد.

الخطوة الثانية: إنشاء ملف مضغوط ZIP جديد

لننشئ الآن ملف ZIP، لذا اجعل برنامجك يبدو كما يلي:

#! python3
# backupToZip.py - نسخ مجلد كامل ومحتوياته في ملف‫ ZIP يزيد اسمه بمقدار واحد في كل مرة يُنسَخ فيها

--snip--
    while True:
        zipFilename = os.path.basename(folder) + '_' + str(number) + '.zip'
        if not os.path.exists(zipFilename):
            break
        number = number + 1

    # إنشاء ملف مضغوط‫ ZIP
    print(f'Creating {zipFilename}...')
   backupZip = zipfile.ZipFile(zipFilename, 'w')

     # TODO: المرور على شجرة المجلدات بأكملها وضغط الملفات الموجودة في كل مجلد
     print('Done.')

backupToZip('C:\\delicious')

خزّنا اسم ملف ZIP الجديد في المتغير zipFilename، ويمكننا الآن استدعاء الدالة zipfile.ZipFile()‎ لإنشاء ملف ZIP فعليًا ➊. تأكد من تمرير 'w' كوسيطٍ ثانٍ لهذه الدالة لفتح الملف ZIP في وضع الكتابة.

الخطوة الثالثة: المرور على شجرة المجلدات والإضافة إلى الملف المضغوط ZIP

يجب الآن أن تستخدم الدالة os.walk()‎ لسرد كل ملف موجود في المجلد ومجلداته الفرعية، لذا اجعل برنامجك يبدو كما يلي:

#! python3
# backupToZip.py -  نسخ مجلد كامل ومحتوياته في ملف‫ ZIP يزيد اسمه بمقدار واحد في كل مرة يُنسَخ فيها

--snip--

     # المرور على شجرة المجلدات بأكملها وضغط الملفات الموجودة في كل مجلد
   for foldername, subfolders, filenames in os.walk(folder):
         print(f'Adding files in {foldername}...')
         # ‫إضافة المجلد الحالي إلى ملف ZIP
       backupZip.write(foldername)

         # إضافة كافة الملفات الموجودة في هذا المجلد إلى ملف‫ ZIP
       for filename in filenames:
            newBase = os.path.basename(folder) + '_'
            if filename.startswith(newBase) and filename.endswith('.zip'):
                continue   # ‫لا تنشئ نسخة احتياطية من ملفات ZIP الاحتياطية
             backupZip.write(os.path.join(foldername, filename))
     backupZip.close()
     print('Done.')

backupToZip('C:\\delicious')

يمكنك استخدام الدالة os.walk()‎ في حلقة for ➊، حيث ستعيد في كل تكرار اسم المجلد الحالي لهذا التكرار والمجلدات الفرعية الموجودة في هذا المجلد وأسماء الملفات الموجودة في هذا المجلد. يُضاف المجلد إلى ملف ZIP ➋ في حلقة for، ويمكن لحلقة for المتداخلة المرور على كل اسم ملف في القائمة filenames ➌، ويُضاف كل منها إلى ملف ZIP باستثناء ملفات ZIP الاحتياطية التي أنشأناها مسبقًا.

سينتج ما يلي عند تشغيل هذا البرنامج:

Creating delicious_1.zip...
Adding files in C:\delicious...
Adding files in C:\delicious\cats...
Adding files in C:\delicious\waffles...
Adding files in C:\delicious\walnut...
Adding files in C:\delicious\walnut\waffles...
Done.

سيضع هذا البرنامج جميع الملفات الموجودة في المجلد C:\delicious في ملف ZIP بالاسم delicious_2.zip في المرة الثانية لتشغيله وهكذا.

أفكار لبرامج مماثلة

يمكنك الاستفادة من فكرة المرور على شجرة المجلدات وإضافة الملفات إلى ملفات الأرشفة ZIP المضغوطة في العديد من البرامج الأخرى، فمثلًا يمكنك كتابة البرامج التي تنجز المهام التالية:

  • المرور على شجرة المجلدات وأرشفة الملفات التي لها امتدادات محددة فقط مثل ‎.txt أو ‎.py.
  • المرور على شجرة المجلدات وأرشفة جميع الملفات باستثناء ملفات ‎.txt و ‎.py.
  • البحث عن المجلد في شجرة المجلدات الذي يحتوي على أكبر عدد من الملفات أو المجلد الذي يستخدم أكبر مساحة على القرص الصلب.

مشاريع للتدريب

حاول كتابة البرامج التي تؤدي المهام التي سنوضّحها فيما يلي لكسب خبرة عملية أكبر.

برنامج لإنشاء نسخة انتقائية للملفات من شجرة المجلدات

اكتب برنامجًا يمر على شجرة المجلدات ويبحث عن الملفات التي لها امتداد ملف محدّد (مثل ‎.pdf‎ أو ‎.jpg‎)، وانسخ هذه الملفات من أيّ موقع توجد فيه في مجلد جديد.

برنامج لحذف الملفات غير الضرورية

يمكن أن تَشغَل بعض الملفات أو المجلدات غير الضرورية والضخمة الجزء الأكبر من المساحة على قرص حاسوبك الصلب، فإذا أدرتَ تحرير مساحةٍ على حاسوبك، فستحصل على أقصى استفادة من خلال حذف أكبر عدد ممكن من الملفات غير المرغوب فيها، ولكن يجب أولًا العثور عليها.

اكتب برنامجًا يمر على شجرة المجلدات ويبحث عن الملفات أو المجلدات الكبيرة استثنائيًا مثل الملفات أو المجلدات التي يزيد حجم ملفها عن 100 ميجابايت، حيث يمكنك استخدام الدالة os.path.getsize()‎ من وحدة os للحصول على حجم الملف. اطبع هذه الملفات مع مسارها المطلق على الشاشة.

ملء الفجوات في ترقيم أسماء الملفات

اكتب برنامجًا يبحث عن جميع الملفات ذات البادئة المُحدَّدة -مثل spam001.txt و spam002.txt وإلخ- في مجلدٍ واحد، ويحدّد هذا البرنامج موقع أيّ فجوات في الترقيم مثل وجود الملفين spam001.txt و spam003.txt دون وجود الملف spam002.txt، لذا اجعل البرنامج يعيد تسمية جميع الملفات اللاحقة لسد هذه الفجوة. اكتب أيضًا برنامجًا آخر يمكنه إدراج فجوات في الملفات المرقّمة بحيث يمكن إضافة ملف جديد.

الخلاصة

يُحتمَل أنك تتعامل مع الملفات يدويًا باستخدام الفأرة ولوحة المفاتيح حتى لو كنت من مستخدمي الحاسوب ذوي الخبرة. تسهّل مستكشفات الملفات الحديثة العمل مع عددٍ من الملفات، ولكن ستحتاج في بعض الأحيان إلى تنفيذ مهمة قد تستغرق ساعات باستخدام مستكشف الملفات الخاص بحاسوبك.

توفّر وحدتا os و shutil دوالًا لنسخ الملفات ونقلها وإعادة تسميتها وحذفها، ولكن قد ترغب في استخدام وحدة send2trash عند حذف الملفات لنقلها إلى سلة المحذوفات أو سلة المهملات بدلًا من حذفها نهائيًا. يُفضَّل تعليق الشيفرة البرمجية التي تنسخ أو تنقل أو تعيد التسمية أو تحذف الملفات فعليًا عند كتابة البرامج التي تتعامل مع الملفات، ويجب إضافة استدعاء الدالة print()‎ حتى تتمكّن من تشغيل البرنامج والتحقق مما سيفعله بالضبط.

يجب في أغلب الأحيان تنفيذ هذه العمليات على الملفات الموجودة في أحد المجلدات وعلى كل مجلد موجود في هذا المجلد وعلى كل مجلد موجود في تلك المجلدات وإلخ. تتولى الدالة os.walk()‎ هذه الرحلة عبر المجلدات نيابةً عنك حتى تتمكّن من التركيز على ما يحتاج برنامجك إلى فعله مع الملفات الموجودة في هذه المجلدات.

تمنحك وحدة zipfile طريقةً لضغط وفك ضغط الملفات في أرشيفات ‎.ZIP باستخدام لغة بايثون. تسهّل الوحدة zipfile -مع دوال معالجة الملفات الخاصة بوحدتي os و shutil- تجميعَ العديد من الملفات من أيّ مكان على قرص حاسوبك الصلب. يُعَد رفع هذه الملفات المضغوطة ZIP على مواقع الويب أو إرسالها بوصفها مرفقات في البريد الإلكتروني أسهلَ بكثير من العديد من الملفات المنفصلة.

وفرنا في هذه السلسلة من المقالات شيفرة برمجية يمكنك نسخها ولصقها في برنامجك، ولكن يُحتمَل ألّا تظهر بمظهرٍ مثالي في البداية. يركز المقال التالي على بعض وحدات بايثون التي ستساعدك على تحليل برامجك وتنقيح أخطائها لتتمكّن من تشغيلها بصورة صحيحة بسرعة.

ترجمة -وبتصرُّف- للمقال Organizing Files لصاحبه Al Sweigart.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...