<?xml version="1.0"?>
<rss version="2.0"><channel><title>&#x627;&#x644;&#x628;&#x631;&#x645;&#x62C;&#x629;: &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646;</title><link>https://academy.hsoub.com/programming/python/?d=2</link><description>&#x627;&#x644;&#x628;&#x631;&#x645;&#x62C;&#x629;: &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646;</description><language>ar</language><item><title>&#x628;&#x631;&#x645;&#x62C;&#x629; &#x62A;&#x637;&#x628;&#x64A;&#x642; &#x644;&#x641;&#x644;&#x62A;&#x631;&#x629; &#x627;&#x644;&#x635;&#x648;&#x631; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x644;&#x63A;&#x629; &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646;</title><link>https://academy.hsoub.com/programming/python/%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%84%D9%81%D9%84%D8%AA%D8%B1%D8%A9-%D8%A7%D9%84%D8%B5%D9%88%D8%B1-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2608/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2026_04/624090640_.png.52896abac30e7d044ca97a7851add158.png" /></p>
<p>
	هل تساءلتم مسبقًا عن كيفية عمل برامج تعديل الصور وكيف يتم تطبيق الفلاتر على الصورة لتظهر بمظهر مختلف من إزالة للخلفية وتعديل السطوع وتغيير ألوانها وإلى ما ذلك؟ سنتطرق في هذه المقالة إلى شرح هذه المفاهيم ونوضح لكم كيفية بناء برنامج لفلترة الصور باستخدام لغة البرمجة بايثون وبالاستعانة ببعض مكتبات معالجة الصور التي توفرها لنا.
</p>

<h2 id="-">
	كيف تعمل فلاتر الصور؟
</h2>

<p>
	دعونا نتعلّم كيفية عمل فلاتر الصور قبل أن نبدأ ببرمجة البرنامج، إذ سيساعدنا فهم عملها على فهم الشيفرة البرمجية التي نكتبها بشكل أكبر.
</p>

<p>
	كما نعرف فإن الصورة مكوّنة من عدد من البكسلات pixels التي تحتوي على ثلاث قيم من 0 إلى 255 لكل من القنوات اللونية الثلاث وهي الأحمر والأخضر والأزرق التي يرمز لهم بالمصطلح RGB. وبالتالي، يمكننا تمثيل الصورة بمصفوفة عددية كما يوضّح الشكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2026_04/001.png.808039b693c6690d5fab203d9fff1ff6.png" data-fileid="182150" data-fileext="png" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="182150" data-ratio="41.17" data-unique="t8wv3t1ji" style="width: 600px; height: auto;" width="900" alt="001 تمثيل الصورة كبكسلات.png" src="https://academy.hsoub.com/uploads/monthly_2026_04/001.thumb.png.2fbfbb01427c8335c543e9e0a09cf24f.png"></a>
</p>

<p>
	وضعنا هنا صورة صغيرة لأجل اختصار طول المصفوفة العددية، لكن لك أن تتخيل أن كل بيكسل في الصورة يقابله قيمة عددية. وقد مثلنا هذه الصورة باستخدام قناة واحدة وهي تدرجات الرمادي Grayscale، لكن الحالة تنطبق أيضًا على صورة RGB الملونة لكن الفارق في هذه الحالة أننا سنستخدم ثلاث قنوات، أي ثلاث مصفوفات ثنائية البعد كما هو موضح في الصورة التالية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2026_04/002.png.8074391d4045df27d8d594c670022016.png" data-fileid="182151" data-fileext="png" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="182151" data-ratio="95.77" data-unique="md48i2dif" style="width: 426px; height: auto;" width="626" alt="002 بكسلات صورة ملونة.png" src="https://academy.hsoub.com/uploads/monthly_2026_04/002.thumb.png.8fda17c69e529170a74eab74b47e71f8.png"></a>
</p>

<p>
	وبالتالي عندما نطبّق فلتر معين على صورة ما، فإننا نعدّل قيمة المصفوفة العددية هذه بضربها بمصفوفة أخرى نسميها مرشّحًا، أو نجري تغييرًا على كل بكسل فيها وفق معادلة معينة، إليكَ بعض الفلاتر:
</p>

<p style="text-align: center;">
	<img class="ipsImage ipsImage_thumbnailed" data-fileid="182152" data-ratio="70.84" data-unique="xhi6voqpb" width="734" alt="003 تطبيق فلاتر على الصورة.png" src="https://academy.hsoub.com/uploads/monthly_2026_04/003.png.85aa5af852cc371468d7cc9a023109e2.png">
</p>

<p>
	لحسن الحظ، ليس علينا القلق بخصوص العمليات الحسابية ومعالجة الصورة كل بيكسل على حدة، إذ ستساعدنا مكتبات بايثون على ذلك عن طريق توابع جاهزة توفرها لتسهل علينا تطبيق الفلاتر.
</p>

<p>
	بعد أن تعرفنا على آلية عمل الفلاتر على الصورة دعونا نبدأ خطوات تطوير تطبيقنا بايثون يمكننا من رفع الصورة المطلوبة وتعديلها بفلاتر مختلفة.
</p>

<h2 id="-">
	الخطوة الأولى: تجهيز بيئة العمل
</h2>

<p>
	لنبدأ أولًا بتجهيز بيئة العمل عن طريق تجهيز المكتبات التي سنستخدمها، بالإضافة لهيكل المشروع من ملفات ومجلدات. سننشئ مجلدًا نسميه <code>image-filters</code> وهو المجلد الذي سيمثل المجلد الجذر لمشروعنا، ومن ثم ننشئ بداخله مجلدين الأول باسم <code>images</code> الذي سنضع فيه الصور التي نريد فلترتها والثاني باسم <code>output</code> وهو المجلد الذي سنخزن فيه الصور الناتجة عن تطبيق الفلتر، وأخيرًا ننشئ ملف <code>image_filters.py</code> الذي سيحتوي الشيفرة البرمجية لبرنامجنا، ويمكننا إنشاء ملف يدعى <code>README.md</code> يشرح كيفية عمل البرنامج ومزاياه.
</p>

<p>
	يصبح لدينا هيكل المشروع في النهاية كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_9" style=""><span class="pln">image</span><span class="pun">-</span><span class="pln">filters</span><span class="pun">/</span><span class="pln">
</span><span class="pun">├──</span><span class="pln"> images</span><span class="pun">/</span><span class="pln">               </span><span class="com"># مجلد يحتوي الصور التي تريد فلترتها</span><span class="pln">
</span><span class="pun">│</span><span class="pln">   </span><span class="pun">├──</span><span class="pln"> sample</span><span class="pun">.</span><span class="pln">jpg
</span><span class="pun">├──</span><span class="pln"> output</span><span class="pun">/</span><span class="pln">               </span><span class="com"># مجلد نخزن فيه الصور الناتجة عن تطبيق الفلتر</span><span class="pln">
</span><span class="pun">├──</span><span class="pln"> image_filters</span><span class="pun">.</span><span class="pln">py      </span><span class="com"># الشيفرة البرمجية</span><span class="pln">
</span><span class="pun">├──</span><span class="pln"> README</span><span class="pun">.</span><span class="pln">md             </span><span class="com"># للتوثيق ‪(اختياري)‪‬‬</span></pre>

<p>
	بعدها، نذهب إلى طرفية سطر الأوامر command line وننفّذ التعليمة التالية:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_846_11" style=""><span class="pln">pip install opencv-python numpy pillow</span></pre>

<p>
	ستتكفل هذه التعليمة بتحميل المكتبات التي سنستعين بها لتطبيق الفلاتر على الصور، وهي مكتبة <code>opencv</code> ومكتبة <code>numpy</code> ومكتبة <code>pillow</code> لذا يجب التأكد من توفر اتصال الإنترنت قبل تنفيذ الأمر.
</p>

<p>
	يمكننا التأكد من أن عملية التحميل قد تمت بنجاح بتنفيذ السطر التالي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_846_13" style=""><span class="pln">py -c "import cv2; import numpy; import PIL; print('Packages installed successfully!')"</span></pre>

<p>
	إن تمت العملية بنجاح سنحصل على رسالة <code>Packages installed successfully!‎</code> في الطرفية.
</p>

<h2 id="-">
	الخطوة الثالثة: كتابة الهيكل الأساسي للبرنامج
</h2>

<p>
	نبدأ باستيراد import المكاتب التي سنستخدمها في بداية الشيفرة البرمجية داخل ملف <code>image_filters.py</code> سنستخدم أيضًا مكتبة <code>os</code> المضمنة في بايثون التي ستساعدنا على إنشاء المجلدات/المسارات:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_15" style=""><span class="kwd">import</span><span class="pln"> cv2
</span><span class="kwd">import</span><span class="pln"> numpy </span><span class="kwd">as</span><span class="pln"> np
</span><span class="kwd">import</span><span class="pln"> os</span></pre>

<p>
	نريد أن ننشئ واجهة مستخدم بسيطة لنرفع منها الصورة التي نريد فلترتها ومن ثم نختار الفلتر ونطبقه، لتحقيق ذلك نضمّن أيضًا مكتبة tkinter و PIL بالشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_17" style=""><span class="kwd">import</span><span class="pln"> tkinter </span><span class="kwd">as</span><span class="pln"> tk
</span><span class="kwd">from</span><span class="pln"> tkinter </span><span class="kwd">import</span><span class="pln"> filedialog</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Label</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Button</span><span class="pun">,</span><span class="pln"> ttk
</span><span class="kwd">from</span><span class="pln"> PIL </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">,</span><span class="pln"> </span><span class="typ">ImageTk</span></pre>

<p>
	نحفظ كل من مسار الصورة مع اسمها واسم الفلتر الذي نختاره وسنبني الفلاتر المتاحة في برنامجنا في الخطوات التالية، ومن ثم نخزّن الصورة في متغير بالاستعانة بمكتبة opencv:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_19" style=""><span class="pln">image </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">imread</span><span class="pun">(</span><span class="pln">image_path</span><span class="pun">)</span></pre>

<p>
	ننشئ <code>filters</code> نضمّن فيها جميع الفلاتر التي يدعمها برنامجنا بالشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_21" style=""><span class="pln">filters </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">"Grayscale"</span><span class="pun">:</span><span class="pln"> apply_grayscale</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"Blur"</span><span class="pun">:</span><span class="pln"> apply_blur</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"Edge Detection"</span><span class="pun">:</span><span class="pln"> apply_edges</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"Sharpen"</span><span class="pun">:</span><span class="pln"> apply_sharpen</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"Cartoon"</span><span class="pun">:</span><span class="pln"> apply_cartoon</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"Remove Background"</span><span class="pun">:</span><span class="pln"> remove_background
</span><span class="pun">}</span></pre>

<p>
	إن أردنا تجزئة منطق البرنامج المطلوب ليسهل علينا فهمه، يمكننا النظر إليه بكونه يقدّم ثلاث مزايا أساسية:
</p>

<ol>
	<li>
		تطبيق الفلتر على صورة
	</li>
	<li>
		عرض الصورة على الواجهة البرمجية
	</li>
	<li>
		حفظ الصورة التي طُبّق عليها الفلتر على الحاسب
	</li>
</ol>

<p>
	لنشرح الشيفرة البرمجية لكل ميزة على حدى بالتفصيل فيما يلي.
</p>

<h3 id="-">
	تطبيق الفلتر على الصورة
</h3>

<p>
	نطبّق الفلتر على الصورة من خلال الدالة التالية، ونضع بعين الاعتبار الحالات التي من الممكن أن تُفشل عمل البرنامج مثل عدم وجود الفلتر الذي تم اختياره في برنامجنا أو عدم استطاعة البرنامج على قراءة الصورة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_23" style=""><span class="kwd">def</span><span class="pln"> apply_filter</span><span class="pun">():</span><span class="pln">
</span><span class="com"># جعل نطاق المتغيرات نطاق عام يمكن الوصول إليهما في أي مكان من البرنامج</span><span class="pln">
    </span><span class="kwd">global</span><span class="pln"> img</span><span class="pun">,</span><span class="pln"> img_display

</span><span class="com"># بحال لم يختر المستخدم الصورة بعد أو لم يستطع البرنامج قراءة الصورة</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> img </span><span class="kwd">is</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln">

</span><span class="com"># تخزين الفلتر الذي اختاره المستخدم من الواجهة الرسومية</span><span class="pln">
    selected_filter </span><span class="pun">=</span><span class="pln"> filter_var</span><span class="pun">.</span><span class="pln">get</span><span class="pun">()</span><span class="pln">

</span><span class="com"># في حال لم يكن الفلتر مدعومًا من قبل البرنامج</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> selected_filter </span><span class="kwd">not</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> filters</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln">

</span><span class="com"># نسخ الصورة بعد قرائتها وتطبيق الفلتر على الصورة</span><span class="pln">
    image </span><span class="pun">=</span><span class="pln"> img</span><span class="pun">.</span><span class="pln">copy</span><span class="pun">()</span><span class="pln">
    image </span><span class="pun">=</span><span class="pln"> filters</span><span class="pun">[</span><span class="pln">selected_filter</span><span class="pun">](</span><span class="pln">image</span><span class="pun">)</span><span class="pln">

</span><span class="com"># تحويل الصورة إلى صورة بألوان RGB في حال كانت صورة بتدرجات رمادية</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">image</span><span class="pun">.</span><span class="pln">shape</span><span class="pun">)</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="lit">2</span><span class="pun">:</span><span class="pln">  </span><span class="com"># Convert grayscale to RGB for display</span><span class="pln">
        image </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">cvtColor</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">COLOR_GRAY2RGB</span><span class="pun">)</span><span class="pln">

</span><span class="com"># ‫تحويل ترميز الصورة من نظام BGR إلى RGB‬</span><span class="pln">
    image </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">cvtColor</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">COLOR_BGR2RGB</span><span class="pun">)</span><span class="pln">

</span><span class="com"># تحويل مصفوفة القيم إلى صورة</span><span class="pln">
    image </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">.</span><span class="pln">fromarray</span><span class="pun">(</span><span class="pln">image</span><span class="pun">)</span><span class="pln">

</span><span class="com"># تحويل الصورة إلى ترميز يدعمه‫ Tkinter لعرضها على الواجهة الرسومية‬</span><span class="pln">
    img_display </span><span class="pun">=</span><span class="pln"> </span><span class="typ">ImageTk</span><span class="pun">.</span><span class="typ">PhotoImage</span><span class="pun">(</span><span class="pln">image</span><span class="pun">)</span><span class="pln">

</span><span class="com"># عرض الصورة على الواجهة الرسومية</span><span class="pln">
    label_img</span><span class="pun">.</span><span class="pln">config</span><span class="pun">(</span><span class="pln">image</span><span class="pun">=</span><span class="pln">img_display</span><span class="pun">)</span></pre>

<p>
	ملاحظة: الخطوات الأخيرة هي بسبب اختلاف تعامل كل من مكتبة OpenCV وTkinter -وبالنتيجة مكتبة Pillow- بعرض وتمثيل الصور، إذ تستخدم كل مكتبة ترميزًا معينًا والتحويل السابق بين الترميز ضروري لعرض الصورة على الواجهة الرسومية.
</p>

<h3 id="-">
	عرض الصورة على الواجهة الرسومية
</h3>

<p>
	نريد أيضًا أن نعرض الصورة الأولية بعد أن نختارها من مستعرض الملفات، لذا ننشئ الدالة <code>load_image</code> لتحقيق ذلك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_25" style=""><span class="kwd">def</span><span class="pln"> load_image</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">global</span><span class="pln"> img</span><span class="pun">,</span><span class="pln"> img_display

</span><span class="com"># فتح نافذة مستعرض الملفات واختيار الصورة بالامتدادت المدعومة المضمنة</span><span class="pln">
    file_path </span><span class="pun">=</span><span class="pln"> filedialog</span><span class="pun">.</span><span class="pln">askopenfilename</span><span class="pun">(</span><span class="pln">filetypes</span><span class="pun">=[(</span><span class="str">"Image Files"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"*.jpg;*.png;*.jpeg"</span><span class="pun">)])</span><span class="pln">

</span><span class="com"># في حال تعذّر وجود الصورة</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> file_path</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln">

</span><span class="com"># قراء الصورة وضبط أبعادها وتحضيرها للعرض على الواجهة الرسومية</span><span class="pln">
    img </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">imread</span><span class="pun">(</span><span class="pln">file_path</span><span class="pun">)</span><span class="pln">
    img_resized </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">resize</span><span class="pun">(</span><span class="pln">img</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="lit">400</span><span class="pun">,</span><span class="pln"> </span><span class="lit">300</span><span class="pun">))</span><span class="pln">
    img_resized </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">cvtColor</span><span class="pun">(</span><span class="pln">img_resized</span><span class="pun">,</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">COLOR_BGR2RGB</span><span class="pun">)</span><span class="pln">
    img_resized </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">.</span><span class="pln">fromarray</span><span class="pun">(</span><span class="pln">img_resized</span><span class="pun">)</span><span class="pln">

</span><span class="com"># عرض الصورة على الواجهة</span><span class="pln">
    img_display </span><span class="pun">=</span><span class="pln"> </span><span class="typ">ImageTk</span><span class="pun">.</span><span class="typ">PhotoImage</span><span class="pun">(</span><span class="pln">img_resized</span><span class="pun">)</span><span class="pln">
    label_img</span><span class="pun">.</span><span class="pln">config</span><span class="pun">(</span><span class="pln">image</span><span class="pun">=</span><span class="pln">img_display</span><span class="pun">)</span></pre>

<h3 id="-">
	حفظ الصورة الناتجة على الحاسب
</h3>

<p>
	أخيرًا، نريد حفظ الصورة الناتجة على حاسبنا، لذا نكتب دالة نسميها <code>save_image</code> بالشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_27" style=""><span class="kwd">def</span><span class="pln"> save_image</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">global</span><span class="pln"> img

</span><span class="com"># في حال تعذر قراءة الصورة أو كونها فارغة</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> img </span><span class="kwd">is</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln">

</span><span class="com"># الحصول على اسم الفلتر الذي اخترناه لتضمين اسمه في الصورة المحفوظة</span><span class="pln">
    selected_filter </span><span class="pun">=</span><span class="pln"> filter_var</span><span class="pun">.</span><span class="pln">get</span><span class="pun">()</span><span class="pln">
    output_dir </span><span class="pun">=</span><span class="pln"> </span><span class="str">"output"</span><span class="pln">

</span><span class="com"># ‫ننشئ مجلد الصور الناتجة output في حال عدم وجوده‬</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">exists</span><span class="pun">(</span><span class="pln">output_dir</span><span class="pun">):</span><span class="pln">
        os</span><span class="pun">.</span><span class="pln">makedirs</span><span class="pun">(</span><span class="pln">output_dir</span><span class="pun">)</span><span class="pln">

    output_filename </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">output_dir</span><span class="pun">,</span><span class="pln"> f</span><span class="str">"filtered_{selected_filter}.jpg"</span><span class="pun">)</span><span class="pln">
    cv2</span><span class="pun">.</span><span class="pln">imwrite</span><span class="pun">(</span><span class="pln">output_filename</span><span class="pun">,</span><span class="pln"> img</span><span class="pun">)</span></pre>

<p>
	القيم الموجودة في <code>filters</code> هي جميع الفلاتر التي سندعمها في برنامجنا، وسنبرمج المنطق الخاص بكل واحد منها ولكن دعونا أولًا نبرمج الواجهة الرسومية!
</p>

<h2 id="-">
	الخطوة الخامسة: برمجة الواجهة الرسومية
</h2>

<p>
	نبني الواجهة الرسومية بشكل بسيط بحيث تحتوي على ثلاثة أزرار: زر اختيار الصورة، وزر تطبيق الفلتر وزر حفظ الصورة، بالإضافة إلى قائمة منسدلة تحتوي الفلاتر التي يدعمها البرنامج لكي يستطيع المستخدم الاختيار بينها بالإضافة لإطار يعرض الصورة التي اخترناها أو الصورة الناتجة بعد الفلترة بالشكل التالي:
</p>

<p style="text-align: center;">
	<img class="ipsImage ipsImage_thumbnailed" data-fileid="182153" data-ratio="115.94" data-unique="1yov98cni" width="502" alt="004 واجهة تطبيق بايثون.png" src="https://academy.hsoub.com/uploads/monthly_2026_04/004.png.79c72860eae1343a13f735648575128e.png">
</p>

<p>
	نكتب الشيفرة البرمجية التالية لتحقيق الشكل السابق:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_29" style=""><span class="com"># إنشاء عقدة جذر البرنامج</span><span class="pln">
root </span><span class="pun">=</span><span class="pln"> tk</span><span class="pun">.</span><span class="typ">Tk</span><span class="pun">()</span><span class="pln">

</span><span class="com"># ضبط عنوان نافذة البرنامج وأبعادها</span><span class="pln">
root</span><span class="pun">.</span><span class="pln">title</span><span class="pun">(</span><span class="str">"Image Filter App"</span><span class="pun">)</span><span class="pln">
root</span><span class="pun">.</span><span class="pln">geometry</span><span class="pun">(</span><span class="str">"500x550"</span><span class="pun">)</span><span class="pln">

</span><span class="com"># ‫إنشاء متغير img وإسناد قيمة أولية له‬</span><span class="pln">
img </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">

</span><span class="com"># إنشاء زر إضافة الصورة ونص العنوان</span><span class="pln">
</span><span class="typ">Label</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> text</span><span class="pun">=</span><span class="str">"Image Filter Application"</span><span class="pun">,</span><span class="pln"> font</span><span class="pun">=(</span><span class="str">"Arial"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">16</span><span class="pun">)).</span><span class="pln">pack</span><span class="pun">(</span><span class="pln">pady</span><span class="pun">=</span><span class="lit">10</span><span class="pun">)</span><span class="pln">

</span><span class="typ">Button</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> text</span><span class="pun">=</span><span class="str">"Load Image"</span><span class="pun">,</span><span class="pln"> command</span><span class="pun">=</span><span class="pln">load_image</span><span class="pun">).</span><span class="pln">pack</span><span class="pun">(</span><span class="pln">pady</span><span class="pun">=</span><span class="lit">5</span><span class="pun">)</span><span class="pln">

label_img </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Label</span><span class="pun">(</span><span class="pln">root</span><span class="pun">)</span><span class="pln">
label_img</span><span class="pun">.</span><span class="pln">pack</span><span class="pun">(</span><span class="pln">pady</span><span class="pun">=</span><span class="lit">5</span><span class="pun">)</span><span class="pln">

</span><span class="com"># القائمة المنسدلة التي تحتوي على الفلاتر</span><span class="pln">
filter_var </span><span class="pun">=</span><span class="pln"> tk</span><span class="pun">.</span><span class="typ">StringVar</span><span class="pun">()</span><span class="pln">
filter_var</span><span class="pun">.</span><span class="pln">set</span><span class="pun">(</span><span class="str">"Grayscale"</span><span class="pun">)</span><span class="pln"> </span><span class="com"># نضبط فلتر التدرج الرمادي كقيمة افتراضية</span><span class="pln">
filter_menu </span><span class="pun">=</span><span class="pln"> ttk</span><span class="pun">.</span><span class="typ">Combobox</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> textvariable</span><span class="pun">=</span><span class="pln">filter_var</span><span class="pun">,</span><span class="pln"> values</span><span class="pun">=</span><span class="pln">list</span><span class="pun">(</span><span class="pln">filters</span><span class="pun">.</span><span class="pln">keys</span><span class="pun">()),</span><span class="pln"> state</span><span class="pun">=</span><span class="str">"readonly"</span><span class="pun">)</span><span class="pln">
filter_menu</span><span class="pun">.</span><span class="pln">pack</span><span class="pun">(</span><span class="pln">pady</span><span class="pun">=</span><span class="lit">5</span><span class="pun">)</span><span class="pln">

</span><span class="com"># إضافة الأزرار</span><span class="pln">
</span><span class="typ">Button</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> text</span><span class="pun">=</span><span class="str">"Apply Filter"</span><span class="pun">,</span><span class="pln"> command</span><span class="pun">=</span><span class="pln">apply_filter</span><span class="pun">).</span><span class="pln">pack</span><span class="pun">(</span><span class="pln">pady</span><span class="pun">=</span><span class="lit">5</span><span class="pun">)</span><span class="pln">
</span><span class="typ">Button</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> text</span><span class="pun">=</span><span class="str">"Save Image"</span><span class="pun">,</span><span class="pln"> command</span><span class="pun">=</span><span class="pln">save_image</span><span class="pun">).</span><span class="pln">pack</span><span class="pun">(</span><span class="pln">pady</span><span class="pun">=</span><span class="lit">5</span><span class="pun">)</span><span class="pln">

root</span><span class="pun">.</span><span class="pln">mainloop</span><span class="pun">()</span></pre>

<h2 id="-">
	الخطوة الرابعة: كتابة الفلاتر المتاحة
</h2>

<p>
	سنضمّن المنطق لكل فلتر من الفلاتر الستة في دالة منفصلة، تسمح لنا هذه الطريقة بمعاملة كل فلتر على حدا بحيث لا يؤثر التعديل عليه أو حذفه أو إضافة فلتر جديد على عمل البرنامج ككل.
</p>

<h3 id="-grayscale">
	فلتر التدرج الرمادي Grayscale
</h3>

<p>
	نبدأ بأبسط الفلاتر عملًا وهو فلتر تغيير ألوان الصورة إلى تدرجات اللون الرمادي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_31" style=""><span class="kwd">def</span><span class="pln"> apply_grayscale</span><span class="pun">(</span><span class="pln">image</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">cvtColor</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">COLOR_BGR2GRAY</span><span class="pun">)</span></pre>

<p>
	تعمل الدالة <code>cvtColor</code> على تغيير قيم البكسلات الموجودة في الصورة التي تحتوي على القيم الثلاث الأحمر والأخضر والأزرق حتى تحتوي على قيمة واحدة تدلّ على شدة اللون مما يمنحنا اللون الرمادي بتدرجاته.
</p>

<p style="text-align: center;">
	<img class="ipsImage ipsImage_thumbnailed" data-fileid="182154" data-ratio="115.94" data-unique="8duzm825o" width="502" alt="005 فلتر تدرج الرمادي.png" src="https://academy.hsoub.com/uploads/monthly_2026_04/005.png.46ce091afb5404b30486bf36414c065f.png">
</p>

<h3 id="-blur">
	فلتر التغبيش Blur
</h3>

<p>
	ننتقل فيما بعد إلى فلتر التغبيش blur ولدى مكتبة opencv دالة جاهزة يمكننا استخدامها ألا وهي <code>GaussianBlur</code> ونمرر لها ثلاث قيم، الصورة ومقدار التشويش والانحراف المعياري للمرشح:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_33" style=""><span class="kwd">def</span><span class="pln"> apply_blur</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> ksize </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="lit">15</span><span class="pun">,</span><span class="lit">15</span><span class="pun">)):</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> cv2</span><span class="pun">.</span><span class="typ">GaussianBlur</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> ksize</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span></pre>

<p>
	يمكننا زيادة مقدار التغبيش بزيادة قيمة المعامل الثاني <code>ksize</code>.
</p>

<p style="text-align: center;">
	<img class="ipsImage ipsImage_thumbnailed" data-fileid="182155" data-ratio="115.94" data-unique="21vgd3e8t" width="502" alt="006 فلتر التغبيش.png" src="https://academy.hsoub.com/uploads/monthly_2026_04/006.png.c7c315de22d1036958b071bc59b4d416.png">
</p>

<h3 id="-edge-detection">
	فلتر الكشف عن الحواف Edge detection
</h3>

<p>
	الفلتر الثالث هو فلتر الكشف عن الحواف edge detection وهو فلتر يستخدم بكثرة في تطبيقات الرؤية الحاسوبية للكشف عن الأجسام والتعرف عليها، إذ يعتمد على التغيرات الكبيرة بين بكسل وبكسل آخر يجاوره. لدينا أيضًا تابع جاهز تقدمة مكتبة opencv وهو <code>Canny</code> نمرر له ثلاث قيم وهي الصورة والعتبة الدنيا والعتبة العليا، وهي قيمتين تتحكمان بمقدار الحساسية للكشف عن حافة ما.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_35" style=""><span class="kwd">def</span><span class="pln"> apply_edges</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> low_threshold</span><span class="pun">=</span><span class="lit">100</span><span class="pun">,</span><span class="pln"> high_threshold</span><span class="pun">=</span><span class="lit">200</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> cv2</span><span class="pun">.</span><span class="typ">Canny</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> low_threshold</span><span class="pun">,</span><span class="pln"> high_threshold</span><span class="pun">)</span></pre>

<p>
	يمكنك تغيير قيم العتبة الدنيا والعليا للتحكم بالحساسية، وسيولد لك البرنامج نتائجًا مختلفة.
</p>

<p style="text-align: center;">
	<img class="ipsImage ipsImage_thumbnailed" data-fileid="182156" data-ratio="115.94" data-unique="42v7qrq3i" width="502" alt="007 فلتر كشف الحواف.png" src="https://academy.hsoub.com/uploads/monthly_2026_04/007.png.0ba09c8eab312d94c74ce8a7a42a30c7.png">
</p>

<h3 id="-sharpen">
	فلتر الشحذ Sharpen
</h3>

<p>
	ننتقل إلى فلتر الشحذ Sharpen الذي يبرز حواف الصورة بشكل أكبر ويزيد من التباين في الصورة، نستخدم في هذا الفلتر مصفوفة معرّفة وهو أحد المرشحات Kernel التي ذكرناها في بداية المقال بالشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_37" style=""><span class="kwd">def</span><span class="pln"> apply_sharpen</span><span class="pun">(</span><span class="pln">image</span><span class="pun">):</span><span class="pln">
    kernel </span><span class="pun">=</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">array</span><span class="pun">([[</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">],</span><span class="pln">
                       </span><span class="pun">[-</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">],</span><span class="pln">
                       </span><span class="pun">[</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">]])</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">filter2D</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> kernel</span><span class="pun">)</span></pre>

<p>
	نستطيع الاستفادة من هذا الفلتر عن طريق توضيح الصور المغبّشة غير الواضحة.
</p>

<h3 id="-cartoon">
	فلتر الرسم الكرتوني Cartoon
</h3>

<p>
	ليس من الضرورة أن تكون الفلاتر بسيطة بتعليمة واحدة، بل يمكننا إنشاء فلتر عن طريق استخدام مجموعة من الفلاتر الواحدة تلو الأخرى، كما سنفعل في فلتر الكرتون. إذ نستخدم هذا الفلتر للحصول على نتيجة تشبه صور أفلام الكرتون.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_39" style=""><span class="kwd">def</span><span class="pln"> apply_cartoon</span><span class="pun">(</span><span class="pln">image</span><span class="pun">):</span><span class="pln">
    </span><span class="com"># تحويل الصورة إلى صورة ذات تدرج رمادي ‪(بشكل مشابه للفلتر الأول)‎‬</span><span class="pln">
    gray </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">cvtColor</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">COLOR_BGR2GRAY</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># تطبيق تغبيش على الصورة</span><span class="pln">
    blurred </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">medianBlur</span><span class="pun">(</span><span class="pln">gray</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># التعرف على الحواف وتنعيمها للحصول على نتيجة تشبه صورة مرسومة</span><span class="pln">
    edges </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">adaptiveThreshold</span><span class="pun">(</span><span class="pln">blurred</span><span class="pun">,</span><span class="pln"> </span><span class="lit">255</span><span class="pun">,</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">ADAPTIVE_THRESH_MEAN_C</span><span class="pun">,</span><span class="pln">
                                  cv2</span><span class="pun">.</span><span class="pln">THRESH_BINARY</span><span class="pun">,</span><span class="pln"> </span><span class="lit">9</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># تغبيش الصورة مع المحافظة على الحواف مشحوذة</span><span class="pln">
    color </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">bilateralFilter</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> </span><span class="lit">9</span><span class="pun">,</span><span class="pln"> </span><span class="lit">250</span><span class="pun">,</span><span class="pln"> </span><span class="lit">250</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># دمج صورة الحواف مع صورة الألوان</span><span class="pln">
    cartoon </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">bitwise_and</span><span class="pun">(</span><span class="pln">color</span><span class="pun">,</span><span class="pln"> color</span><span class="pun">,</span><span class="pln"> mask</span><span class="pun">=</span><span class="pln">edges</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> cartoon</span></pre>

<p>
	قد يصعب تخيّل نتيجة كل عملية فقط عن طريق قراءة الشيفرة البرمجية، لذا نشجعك هنا على فصل كل تعليمة لوحدها ورؤية نتيجتها على حدى. النتيجة النهائية ستبدو كالشكل التالي:
</p>

<p style="text-align: center;">
	<img class="ipsImage ipsImage_thumbnailed" data-fileid="182157" data-ratio="115.94" data-unique="w1s398ohb" width="502" alt="008 فلتر الرسم الكرتوني.png" src="https://academy.hsoub.com/uploads/monthly_2026_04/008.png.4111f5d84978e644d6d3b0dfd4938a48.png">
</p>

<h3 id="-remove-background">
	فلتر إزالة الخلفية Remove Background
</h3>

<p>
	أخيرًا، نكتب الفلتر الأخير في هذا البرنامج ألا وهو فلتر إزالة الخلفية. هذا الفلتر مفيد في الحالات التي تريد فيها عزل كائن ما عن البيئة المحيطة به مثل صورة شخصية أو في مثالنا هذا النمر.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_41" style=""><span class="kwd">def</span><span class="pln"> remove_background</span><span class="pun">(</span><span class="pln">image</span><span class="pun">):</span><span class="pln">
    </span><span class="com"># ننشئ نسخة بذات أبعاد الصورة لكن نملؤها بالأصفار</span><span class="pln">
    mask </span><span class="pun">=</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">zeros</span><span class="pun">(</span><span class="pln">image</span><span class="pun">.</span><span class="pln">shape</span><span class="pun">[:</span><span class="lit">2</span><span class="pun">],</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">uint8</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># نستخدم خوارزمية‫ GrabCut الموجودة في opencv لإنشاء نموذج للكائن في الصورة‬</span><span class="pln">
    bgd_model </span><span class="pun">=</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">zeros</span><span class="pun">((</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">65</span><span class="pun">),</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">float64</span><span class="pun">)</span><span class="pln">
    fgd_model </span><span class="pun">=</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">zeros</span><span class="pun">((</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">65</span><span class="pun">),</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">float64</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># نعرف مستطيلًا حول النموذج الذي أنشأناه</span><span class="pln">
    rect </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="lit">50</span><span class="pun">,</span><span class="pln"> </span><span class="lit">50</span><span class="pun">,</span><span class="pln"> image</span><span class="pun">.</span><span class="pln">shape</span><span class="pun">[</span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">50</span><span class="pun">,</span><span class="pln"> image</span><span class="pun">.</span><span class="pln">shape</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">50</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># ‫ننفذ خوارزمية GrabCut ‬</span><span class="pln">
    cv2</span><span class="pun">.</span><span class="pln">grabCut</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> mask</span><span class="pun">,</span><span class="pln"> rect</span><span class="pun">,</span><span class="pln"> bgd_model</span><span class="pun">,</span><span class="pln"> fgd_model</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">,</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">GC_INIT_WITH_RECT</span><span class="pun">)</span><span class="pln">
    mask </span><span class="pun">=</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">where</span><span class="pun">((</span><span class="pln">mask </span><span class="pun">==</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> </span><span class="pun">(</span><span class="pln">mask </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0</span><span class="pun">),</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">).</span><span class="pln">astype</span><span class="pun">(</span><span class="str">"uint8"</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># نطبق الصورة ذات القيم الصفرية على صورتنا لإزالة الخلفية</span><span class="pln">
    result </span><span class="pun">=</span><span class="pln"> image </span><span class="pun">*</span><span class="pln"> mask</span><span class="pun">[:,</span><span class="pln"> </span><span class="pun">:,</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">newaxis</span><span class="pun">]</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> result</span></pre>

<p>
	نحصل على النتيجة التالية:
</p>

<p style="text-align: center;">
	<img class="ipsImage ipsImage_thumbnailed" data-fileid="182158" data-ratio="115.94" data-unique="qm9d11m0d" width="502" alt="009 فلتر إزالة الخلفية.png" src="https://academy.hsoub.com/uploads/monthly_2026_04/009.png.cbf916a3f6254a233077052f0290c0b9.png">
</p>

<p>
	سنكتفي بهذه الفلاتر في تطبيقنا، ونترك لكم حرية التجربة وإضافة المزيد من الفلاتر على البرنامج، كل ما عليكم هو كتابة دالة جديدة تحتوي على المنطق البرمجي الذي يستخدمه هذا الفلتر وإضافتها إلى <code>filters</code> واستدعاؤها. ندعوكم لتجربة فلتر آخر وليكن عكس الألوان Invert colors وتجربة تضمينه في البرنامج لتحسينه.
</p>

<h2 id="-">
	الشيفرة البرمجية الكاملة للتطبيق
</h2>

<p>
	إليكم كامل الشيفرة الخاصة بالتطبيق لاختبارها وتجربتها لديكم:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_846_43" style=""><span class="kwd">import</span><span class="pln"> cv2
</span><span class="kwd">import</span><span class="pln"> numpy </span><span class="kwd">as</span><span class="pln"> np
</span><span class="kwd">import</span><span class="pln"> os
</span><span class="kwd">import</span><span class="pln"> tkinter </span><span class="kwd">as</span><span class="pln"> tk
</span><span class="kwd">from</span><span class="pln"> tkinter </span><span class="kwd">import</span><span class="pln"> filedialog</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Label</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Button</span><span class="pun">,</span><span class="pln"> ttk
</span><span class="kwd">from</span><span class="pln"> PIL </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">,</span><span class="pln"> </span><span class="typ">ImageTk</span><span class="pln">

</span><span class="com"># دالة فلتر التدرج الرمادي</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> apply_grayscale</span><span class="pun">(</span><span class="pln">image</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">cvtColor</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">COLOR_BGR2GRAY</span><span class="pun">)</span><span class="pln">

</span><span class="com"># دالة فلتر التغبيش</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> apply_blur</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> ksize</span><span class="pun">=(</span><span class="lit">25</span><span class="pun">,</span><span class="pln"> </span><span class="lit">25</span><span class="pun">)):</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> cv2</span><span class="pun">.</span><span class="typ">GaussianBlur</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> ksize</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln">

</span><span class="com"># دالة الكشف عن الحواف</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> apply_edges</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> low_threshold</span><span class="pun">=</span><span class="lit">100</span><span class="pun">,</span><span class="pln"> high_threshold</span><span class="pun">=</span><span class="lit">200</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> cv2</span><span class="pun">.</span><span class="typ">Canny</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> low_threshold</span><span class="pun">,</span><span class="pln"> high_threshold</span><span class="pun">)</span><span class="pln">

</span><span class="com"># دالة فلتر الشحذ</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> apply_sharpen</span><span class="pun">(</span><span class="pln">image</span><span class="pun">):</span><span class="pln">
    kernel </span><span class="pun">=</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">array</span><span class="pun">([[</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">],</span><span class="pln">
                       </span><span class="pun">[-</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">],</span><span class="pln">
                       </span><span class="pun">[</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">]])</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">filter2D</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> kernel</span><span class="pun">)</span><span class="pln">

</span><span class="com"># دالة فلتر الرسوم الكرتونية</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> apply_cartoon</span><span class="pun">(</span><span class="pln">image</span><span class="pun">):</span><span class="pln">
    gray </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">cvtColor</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">COLOR_BGR2GRAY</span><span class="pun">)</span><span class="pln">
    blurred </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">medianBlur</span><span class="pun">(</span><span class="pln">gray</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">)</span><span class="pln">
    edges </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">adaptiveThreshold</span><span class="pun">(</span><span class="pln">blurred</span><span class="pun">,</span><span class="pln"> </span><span class="lit">255</span><span class="pun">,</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">ADAPTIVE_THRESH_MEAN_C</span><span class="pun">,</span><span class="pln">
                                  cv2</span><span class="pun">.</span><span class="pln">THRESH_BINARY</span><span class="pun">,</span><span class="pln"> </span><span class="lit">9</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">)</span><span class="pln">
    color </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">bilateralFilter</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> </span><span class="lit">9</span><span class="pun">,</span><span class="pln"> </span><span class="lit">250</span><span class="pun">,</span><span class="pln"> </span><span class="lit">250</span><span class="pun">)</span><span class="pln">
    cartoon </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">bitwise_and</span><span class="pun">(</span><span class="pln">color</span><span class="pun">,</span><span class="pln"> color</span><span class="pun">,</span><span class="pln"> mask</span><span class="pun">=</span><span class="pln">edges</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> cartoon

</span><span class="com"># ‫دالة فلتر إزالة الخلفية باستخدام خوارزمية Grab cut‬</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> remove_background</span><span class="pun">(</span><span class="pln">image</span><span class="pun">):</span><span class="pln">
    mask </span><span class="pun">=</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">zeros</span><span class="pun">(</span><span class="pln">image</span><span class="pun">.</span><span class="pln">shape</span><span class="pun">[:</span><span class="lit">2</span><span class="pun">],</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">uint8</span><span class="pun">)</span><span class="pln">
    bgd_model </span><span class="pun">=</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">zeros</span><span class="pun">((</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">65</span><span class="pun">),</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">float64</span><span class="pun">)</span><span class="pln">
    fgd_model </span><span class="pun">=</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">zeros</span><span class="pun">((</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">65</span><span class="pun">),</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">float64</span><span class="pun">)</span><span class="pln">

    rect </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="lit">50</span><span class="pun">,</span><span class="pln"> </span><span class="lit">50</span><span class="pun">,</span><span class="pln"> image</span><span class="pun">.</span><span class="pln">shape</span><span class="pun">[</span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">50</span><span class="pun">,</span><span class="pln"> image</span><span class="pun">.</span><span class="pln">shape</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">50</span><span class="pun">)</span><span class="pln">
    cv2</span><span class="pun">.</span><span class="pln">grabCut</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> mask</span><span class="pun">,</span><span class="pln"> rect</span><span class="pun">,</span><span class="pln"> bgd_model</span><span class="pun">,</span><span class="pln"> fgd_model</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">,</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">GC_INIT_WITH_RECT</span><span class="pun">)</span><span class="pln">
    mask2 </span><span class="pun">=</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">where</span><span class="pun">((</span><span class="pln">mask </span><span class="pun">==</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> </span><span class="pun">(</span><span class="pln">mask </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0</span><span class="pun">),</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">).</span><span class="pln">astype</span><span class="pun">(</span><span class="str">"uint8"</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> image </span><span class="pun">*</span><span class="pln"> mask2</span><span class="pun">[:,</span><span class="pln"> </span><span class="pun">:,</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">newaxis</span><span class="pun">]</span><span class="pln">

</span><span class="com"># الفلاتر التي يدعمها البرنامج</span><span class="pln">
filters </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">"grayscale"</span><span class="pun">:</span><span class="pln"> apply_grayscale</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"blur"</span><span class="pun">:</span><span class="pln"> apply_blur</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"edges"</span><span class="pun">:</span><span class="pln"> apply_edges</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"sharpen"</span><span class="pun">:</span><span class="pln"> apply_sharpen</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"cartoon"</span><span class="pun">:</span><span class="pln"> apply_cartoon</span><span class="pun">,</span><span class="pln">
    </span><span class="str">"remove_bg"</span><span class="pun">:</span><span class="pln"> remove_background
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> apply_filter</span><span class="pun">():</span><span class="pln">
</span><span class="com"># جعل نطاق المتغيرات نطاق عام يمكن الوصول إليهما في أي مكان من البرنامج</span><span class="pln">
    </span><span class="kwd">global</span><span class="pln"> img</span><span class="pun">,</span><span class="pln"> img_display

</span><span class="com"># بحال لم يختر المستخدم الصورة بعد أو لم يستطع البرنامج قراءة الصورة</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> img </span><span class="kwd">is</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln">

</span><span class="com"># تخزين الفلتر الذي اختاره المستخدم من الواجهة الرسومية</span><span class="pln">
    selected_filter </span><span class="pun">=</span><span class="pln"> filter_var</span><span class="pun">.</span><span class="pln">get</span><span class="pun">()</span><span class="pln">

</span><span class="com"># في حال لم يكن الفلتر مدعومًا من قبل البرنامج</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> selected_filter </span><span class="kwd">not</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> filters</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln">

</span><span class="com"># نسخ الصورة بعد قرائتها وتطبيق الفلتر على الصورة</span><span class="pln">
    image </span><span class="pun">=</span><span class="pln"> img</span><span class="pun">.</span><span class="pln">copy</span><span class="pun">()</span><span class="pln">
    image </span><span class="pun">=</span><span class="pln"> filters</span><span class="pun">[</span><span class="pln">selected_filter</span><span class="pun">](</span><span class="pln">image</span><span class="pun">)</span><span class="pln">

</span><span class="com"># تحويل الصورة إلى صورة بألوان RGB في حال كانت صورة بتدرجات رمادية</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">image</span><span class="pun">.</span><span class="pln">shape</span><span class="pun">)</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="lit">2</span><span class="pun">:</span><span class="pln">  </span><span class="com"># Convert grayscale to RGB for display</span><span class="pln">
        image </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">cvtColor</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">COLOR_GRAY2RGB</span><span class="pun">)</span><span class="pln">

</span><span class="com"># ‫تحويل ترميز الصورة من نظام BGR إلى RGB‬</span><span class="pln">
    image </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">cvtColor</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">COLOR_BGR2RGB</span><span class="pun">)</span><span class="pln">

</span><span class="com"># تحويل مصفوفة القيم إلى صورة</span><span class="pln">
    image </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">.</span><span class="pln">fromarray</span><span class="pun">(</span><span class="pln">image</span><span class="pun">)</span><span class="pln">

</span><span class="com"># تحويل الصورة إلى ترميز يدعمه‫ Tkinter لعرضها على الواجهة الرسومية‬</span><span class="pln">
    img_display </span><span class="pun">=</span><span class="pln"> </span><span class="typ">ImageTk</span><span class="pun">.</span><span class="typ">PhotoImage</span><span class="pun">(</span><span class="pln">image</span><span class="pun">)</span><span class="pln">

</span><span class="com"># عرض الصورة على الواجهة الرسومية</span><span class="pln">
    label_img</span><span class="pun">.</span><span class="pln">config</span><span class="pun">(</span><span class="pln">image</span><span class="pun">=</span><span class="pln">img_display</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> load_image</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">global</span><span class="pln"> img</span><span class="pun">,</span><span class="pln"> img_display

</span><span class="com"># فتح نافذة مستعرض الملفات واختيار الصورة بالامتدادت المدعومة المضمنة</span><span class="pln">
    file_path </span><span class="pun">=</span><span class="pln"> filedialog</span><span class="pun">.</span><span class="pln">askopenfilename</span><span class="pun">(</span><span class="pln">filetypes</span><span class="pun">=[(</span><span class="str">"Image Files"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"*.jpg;*.png;*.jpeg"</span><span class="pun">)])</span><span class="pln">

</span><span class="com"># في حال تعذّر وجود الصورة</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> file_path</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln">

</span><span class="com"># قراء الصورة وضبط أبعادها وتحضيرها للعرض على الواجهة الرسومية</span><span class="pln">
    img </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">imread</span><span class="pun">(</span><span class="pln">file_path</span><span class="pun">)</span><span class="pln">
    img_resized </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">resize</span><span class="pun">(</span><span class="pln">img</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="lit">400</span><span class="pun">,</span><span class="pln"> </span><span class="lit">300</span><span class="pun">))</span><span class="pln">
    img_resized </span><span class="pun">=</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">cvtColor</span><span class="pun">(</span><span class="pln">img_resized</span><span class="pun">,</span><span class="pln"> cv2</span><span class="pun">.</span><span class="pln">COLOR_BGR2RGB</span><span class="pun">)</span><span class="pln">
    img_resized </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">.</span><span class="pln">fromarray</span><span class="pun">(</span><span class="pln">img_resized</span><span class="pun">)</span><span class="pln">

</span><span class="com"># عرض الصورة على الواجهة</span><span class="pln">
    img_display </span><span class="pun">=</span><span class="pln"> </span><span class="typ">ImageTk</span><span class="pun">.</span><span class="typ">PhotoImage</span><span class="pun">(</span><span class="pln">img_resized</span><span class="pun">)</span><span class="pln">
    label_img</span><span class="pun">.</span><span class="pln">config</span><span class="pun">(</span><span class="pln">image</span><span class="pun">=</span><span class="pln">img_display</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> save_image</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">global</span><span class="pln"> img

</span><span class="com"># في حال تعذر قراءة الصورة أو كونها فارغة</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> img </span><span class="kwd">is</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln">

</span><span class="com"># الحصول على اسم الفلتر الذي اخترناه لتضمين اسمه في الصورة المحفوظة</span><span class="pln">
    selected_filter </span><span class="pun">=</span><span class="pln"> filter_var</span><span class="pun">.</span><span class="pln">get</span><span class="pun">()</span><span class="pln">
    output_dir </span><span class="pun">=</span><span class="pln"> </span><span class="str">"output"</span><span class="pln">

</span><span class="com"># ‫ننشئ مجلد الصور الناتجة output في حال عدم وجوده‬</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">exists</span><span class="pun">(</span><span class="pln">output_dir</span><span class="pun">):</span><span class="pln">
        os</span><span class="pun">.</span><span class="pln">makedirs</span><span class="pun">(</span><span class="pln">output_dir</span><span class="pun">)</span><span class="pln">

    output_filename </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">output_dir</span><span class="pun">,</span><span class="pln"> f</span><span class="str">"filtered_{selected_filter}.jpg"</span><span class="pun">)</span><span class="pln">
    cv2</span><span class="pun">.</span><span class="pln">imwrite</span><span class="pun">(</span><span class="pln">output_filename</span><span class="pun">,</span><span class="pln"> img</span><span class="pun">)</span><span class="pln">

</span><span class="com"># إنشاء عقدة جذر البرنامج</span><span class="pln">
root </span><span class="pun">=</span><span class="pln"> tk</span><span class="pun">.</span><span class="typ">Tk</span><span class="pun">()</span><span class="pln">

</span><span class="com"># ضبط عنوان نافذة البرنامج وأبعادها</span><span class="pln">
root</span><span class="pun">.</span><span class="pln">title</span><span class="pun">(</span><span class="str">"Image Filter App"</span><span class="pun">)</span><span class="pln">
root</span><span class="pun">.</span><span class="pln">geometry</span><span class="pun">(</span><span class="str">"500x550"</span><span class="pun">)</span><span class="pln">

</span><span class="com"># ‫إنشاء متغير img وإسناد قيمة أولية له‬</span><span class="pln">
img </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">

</span><span class="com"># إنشاء زر إضافة الصورة ونص العنوان</span><span class="pln">
</span><span class="typ">Label</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> text</span><span class="pun">=</span><span class="str">"Image Filter Application"</span><span class="pun">,</span><span class="pln"> font</span><span class="pun">=(</span><span class="str">"Arial"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">16</span><span class="pun">)).</span><span class="pln">pack</span><span class="pun">(</span><span class="pln">pady</span><span class="pun">=</span><span class="lit">10</span><span class="pun">)</span><span class="pln">

</span><span class="typ">Button</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> text</span><span class="pun">=</span><span class="str">"Load Image"</span><span class="pun">,</span><span class="pln"> command</span><span class="pun">=</span><span class="pln">load_image</span><span class="pun">).</span><span class="pln">pack</span><span class="pun">(</span><span class="pln">pady</span><span class="pun">=</span><span class="lit">5</span><span class="pun">)</span><span class="pln">

label_img </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Label</span><span class="pun">(</span><span class="pln">root</span><span class="pun">)</span><span class="pln">
label_img</span><span class="pun">.</span><span class="pln">pack</span><span class="pun">(</span><span class="pln">pady</span><span class="pun">=</span><span class="lit">5</span><span class="pun">)</span><span class="pln">

</span><span class="com"># القائمة المنسدلة التي تحتوي على الفلاتر</span><span class="pln">
filter_var </span><span class="pun">=</span><span class="pln"> tk</span><span class="pun">.</span><span class="typ">StringVar</span><span class="pun">()</span><span class="pln">
filter_var</span><span class="pun">.</span><span class="pln">set</span><span class="pun">(</span><span class="str">"Grayscale"</span><span class="pun">)</span><span class="pln"> </span><span class="com"># نضبط فلتر التدرج الرمادي كقيمة افتراضية</span><span class="pln">
filter_menu </span><span class="pun">=</span><span class="pln"> ttk</span><span class="pun">.</span><span class="typ">Combobox</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> textvariable</span><span class="pun">=</span><span class="pln">filter_var</span><span class="pun">,</span><span class="pln"> values</span><span class="pun">=</span><span class="pln">list</span><span class="pun">(</span><span class="pln">filters</span><span class="pun">.</span><span class="pln">keys</span><span class="pun">()),</span><span class="pln"> state</span><span class="pun">=</span><span class="str">"readonly"</span><span class="pun">)</span><span class="pln">
filter_menu</span><span class="pun">.</span><span class="pln">pack</span><span class="pun">(</span><span class="pln">pady</span><span class="pun">=</span><span class="lit">5</span><span class="pun">)</span><span class="pln">

</span><span class="com"># إضافة الأزرار</span><span class="pln">
</span><span class="typ">Button</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> text</span><span class="pun">=</span><span class="str">"Apply Filter"</span><span class="pun">,</span><span class="pln"> command</span><span class="pun">=</span><span class="pln">apply_filter</span><span class="pun">).</span><span class="pln">pack</span><span class="pun">(</span><span class="pln">pady</span><span class="pun">=</span><span class="lit">5</span><span class="pun">)</span><span class="pln">
</span><span class="typ">Button</span><span class="pun">(</span><span class="pln">root</span><span class="pun">,</span><span class="pln"> text</span><span class="pun">=</span><span class="str">"Save Image"</span><span class="pun">,</span><span class="pln"> command</span><span class="pun">=</span><span class="pln">save_image</span><span class="pun">).</span><span class="pln">pack</span><span class="pun">(</span><span class="pln">pady</span><span class="pun">=</span><span class="lit">5</span><span class="pun">)</span><span class="pln">

root</span><span class="pun">.</span><span class="pln">mainloop</span><span class="pun">()</span></pre>

<h2 id="-">
	الخاتمة
</h2>

<p>
	استعرضنا في هذا المقال كيفية عمل فلاتر الصور، وشرحنا خطوات تطوير تطبيق بايثون البسيط يتضمن بعض الفلاتر بالاستعانة بكل من مكتبة OpenCV وPillow وTkinter، إذ نجد أن تطبيق فلتر إلى الصورة ما هو إلا عمليات حسابية تحدث على بيكسلات الصورة، ويتفاوت تعقيد الفلتر من فلتر بسيط يمكن برمجته بسطر واحد إلى فلتر معقد يحتاج إلى خوارزميات متقدمة لتنفيذه. نرجو أن يكون هذا المقال قد وفر لكم الفهم الأساسي لطريقة عمل برنامج لمعالجة الصور وتطبيق فلاتر منوعة عليها.
</p>
]]></description><guid isPermaLink="false">2608</guid><pubDate>Sun, 12 Apr 2026 12:31:45 +0000</pubDate></item><item><title>&#x643;&#x64A;&#x641; &#x62A;&#x633;&#x62A;&#x62E;&#x62F;&#x645; &#x62F;&#x648;&#x627;&#x644; &#x627;&#x644;&#x62A;&#x62C;&#x632;&#x626;&#x629; &#x644;&#x644;&#x639;&#x62B;&#x648;&#x631; &#x639;&#x644;&#x649; &#x627;&#x644;&#x645;&#x644;&#x641;&#x627;&#x62A; &#x627;&#x644;&#x645;&#x643;&#x631;&#x631;&#x629; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x644;&#x63A;&#x629; &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646;</title><link>https://academy.hsoub.com/programming/python/%D9%83%D9%8A%D9%81-%D8%AA%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D8%AF%D9%88%D8%A7%D9%84-%D8%A7%D9%84%D8%AA%D8%AC%D8%B2%D8%A6%D8%A9-%D9%84%D9%84%D8%B9%D8%AB%D9%88%D8%B1-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D9%83%D8%B1%D8%B1%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2604/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2026_03/ChatGPTImageMar17202610_11_22AM.png.b2b705e55fb83e9fcb727952a1fcef85.png" /></p>
<p>
	لنفترض أننا نريد العثور على ملفات مكررة على الحاسوب مثل نسخ إضافية من صور أو مجموعة بيانات، قد نجري عملية بحث عادية ونحصل على أسماء الملفات المتشابهة لكن قد يغير المستخدم اسم الملف وهذا يعني أن المحتويات ستظل نفسها، فنحن نحتاج الآن إلى مقارنة تلك المحتويات، لكن إن كان لدينا الكثير من الملفات فستكون هذه عملية بطيئة.
</p>

<p>
	يمكن تقدير مدى بطء هذه العملية بحساب بسيط حيث يمكن ترتيب عدد n من العناصر في أزواج بعدد من الطرق يساوي n(n-1)، وإذا حذفنا الأزواج المكررة -إذا اعتبرنا أن الزوج A-B هو نفسه B-A يكون لدينا عدد من الأزواج الفريدة يساوي n(n-1)/2 أو (n²-n)/2 للتبسيط، وكلما زادت قيمة n تناسبت تقريبيًا مع n2، وهنا قد يقال أن تعقيد الوقت Time complexity لخوارزميتنا هو O(n²). بعبارة أبسط، حين يتضاعف عدد الملفات يزيد زمن التشغيل أربع مرات تقريبًا، أي أن الوقت المستغرق لكل ملف يزيد مع زيادة العدد الإجمالي للملفات.
</p>

<p>
	لا يمكن تجنب هذا النوع من التباطؤ في الغالب، لكن لدينا حل أفضل في مثالنا هنا، حيث سنتمكن تجميع الملفات التي لديها نفس المعرف ثم نقارن بين الملفات داخل كل مجموعة، إذا ولّدنا معرفًا identifier قصيرًا لكل ملف يعتمد على البايتات التي يحتويها، ويُعد هذا الأسلوب أسرع لأننا ننفذ عملية المقارنة المكلفة -مقارنة كل بايت مقابل بايت على حدة- على الملفات التي يُحتمل تطابقها فقط، بل قد نتجنب عملية المقارنة بالكلية إذا أتقنا توليد هذه المعرفات.
</p>

<h2 id="-">
	الأسلوب التقليدي
</h2>

<p>
	سنبدأ بتنفيذ الأسلوب غير الفعال الذي يعتمد على تعقيد الوقت O(N²) لكي نقارنه بالتصميمات المتطورة التي سنكتبها لاحقًا.
</p>

<p>
	يأخذ البرنامج القصير أدناه -لنسميه <code>brute_force_1.py</code>- قائمة بأسماء الملفات من سطر الأوامر ثم يبحث عن الملفات المكررة، ويطبع النتائج المتطابقة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_7" style=""><span class="kwd">import</span><span class="pln"> sys


</span><span class="com"># [bytes]</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> same_bytes</span><span class="pun">(</span><span class="pln">left_name</span><span class="pun">,</span><span class="pln"> right_name</span><span class="pun">):</span><span class="pln">
 left_bytes </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="pln">left_name</span><span class="pun">,</span><span class="pln"> </span><span class="str">"rb"</span><span class="pun">).</span><span class="pln">read</span><span class="pun">()</span><span class="pln">
 right_bytes </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="pln">right_name</span><span class="pun">,</span><span class="pln"> </span><span class="str">"rb"</span><span class="pun">).</span><span class="pln">read</span><span class="pun">()</span><span class="pln">
 </span><span class="kwd">return</span><span class="pln"> left_bytes </span><span class="pun">==</span><span class="pln"> right_bytes
</span><span class="com"># [/bytes]</span><span class="pln">


</span><span class="com"># [main]</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> find_duplicates</span><span class="pun">(</span><span class="pln">filenames</span><span class="pun">):</span><span class="pln">
 matches </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">
 </span><span class="kwd">for</span><span class="pln"> left </span><span class="kwd">in</span><span class="pln"> filenames</span><span class="pun">:</span><span class="pln">
 </span><span class="kwd">for</span><span class="pln"> right </span><span class="kwd">in</span><span class="pln"> filenames</span><span class="pun">:</span><span class="pln">
 </span><span class="kwd">if</span><span class="pln"> same_bytes</span><span class="pun">(</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> right</span><span class="pun">):</span><span class="pln">
 matches</span><span class="pun">.</span><span class="pln">append</span><span class="pun">((</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> right</span><span class="pun">))</span><span class="pln">
 </span><span class="kwd">return</span><span class="pln"> matches


</span><span class="kwd">if</span><span class="pln"> __name__ </span><span class="pun">==</span><span class="pln"> </span><span class="str">"__main__"</span><span class="pun">:</span><span class="pln">
 duplicates </span><span class="pun">=</span><span class="pln"> find_duplicates</span><span class="pun">(</span><span class="pln">sys</span><span class="pun">.</span><span class="pln">argv</span><span class="pun">[</span><span class="lit">1</span><span class="pun">:])</span><span class="pln">
 </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> right</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> duplicates</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> right</span><span class="pun">)</span><span class="pln">
</span><span class="com"># [/main]</span></pre>

<p>
	يستخدم هذا البرنامج دالة <code>same_bytes</code> تقرأ ملفين وتقارن بينهما "بايت مقابل بايت":
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_9" style=""><span class="kwd">def</span><span class="pln"> same_bytes</span><span class="pun">(</span><span class="pln">left_name</span><span class="pun">,</span><span class="pln"> right_name</span><span class="pun">):</span><span class="pln">
    left_bytes </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="pln">left_name</span><span class="pun">,</span><span class="pln"> </span><span class="str">"rb"</span><span class="pun">).</span><span class="pln">read</span><span class="pun">()</span><span class="pln">
    right_bytes </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="pln">right_name</span><span class="pun">,</span><span class="pln"> </span><span class="str">"rb"</span><span class="pun">).</span><span class="pln">read</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> left_bytes </span><span class="pun">==</span><span class="pln"> right_bytes</span></pre>

<p>
	لاحظ في المثال أعلاه أننا نفتح الملفات في الوضع الثنائي binary mode باستخدام الوسم <code>rb</code> بدلًا من <code>r</code> المعتاد، وهذا يخبر لغة بايثون أن تقرأ البايتات تمامًا كما هي بدلًا من محاولة تحويلها إلى محارف نصية.
</p>

<p>
	ننشئ مجلدًا باسم <code>tests</code> من أجل اختبار هذا البرنامج والبرامج الأخرى، ويحتوي على ستة ملفات:
</p>

<table>
	<thead>
		<tr>
			<th>
				اسم الملف
			</th>
			<th>
				<code>a1.txt</code>
			</th>
			<th>
				<code>a2.txt</code>
			</th>
			<th>
				<code>a3.txt</code>
			</th>
			<th>
				<code>b1.txt</code>
			</th>
			<th>
				<code>b2.txt</code>
			</th>
			<th>
				<code>c1.txt</code>
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				المحتوى
			</td>
			<td style="text-align: center;">
				<code>aaa</code>
			</td>
			<td style="text-align: center;">
				<code>aaa</code>
			</td>
			<td style="text-align: center;">
				<code>aaa</code>
			</td>
			<td style="text-align: center;">
				<code>bb</code>
			</td>
			<td style="text-align: center;">
				<code>bb</code>
			</td>
			<td style="text-align: center;">
				<code>c</code>
			</td>
		</tr>
	</tbody>
</table>

<p>
	نتوقع أن يتم الإبلاغ عن ملفات <code>a</code> الثلاثية وملفات <code>b</code> الثنائية كملفات مكررة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_13" style=""><span class="pln">python brute_force_1</span><span class="pun">.</span><span class="pln">py tests</span><span class="pun">/*.</span><span class="pln">txt</span></pre>

<p>
	ينبغي أن نحصل على الخرج التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_15" style=""><span class="pln">tests</span><span class="pun">/</span><span class="pln">a1</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">a1</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">a1</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">a2</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">a1</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">a3</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">a2</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">a1</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">a2</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">a2</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">a2</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">a3</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">a3</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">a1</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">a3</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">a2</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">a3</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">a3</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">b1</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">b1</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">b1</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">b2</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">b2</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">b1</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">b2</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">b2</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">c1</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">c1</span><span class="pun">.</span><span class="pln">txt</span></pre>

<p>
	هذا الخرج غير مفيد وإن كان صحيحًا، إذ يبلغ عن كل ملف على أنه مطابق لنفسه، كما يُكرر الإبلاغ مرتين عن كل تطابق بين ملفين مختلفين، لنصلح هذه الحلقة المتداخلة nested loop في دالة <code>find_duplicates</code> كي نتأكد أننا نفحص أزواج الملفات التي يحتمل اختلافها مرة واحدة فقط:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_17" style=""><span class="kwd">import</span><span class="pln"> sys


</span><span class="kwd">def</span><span class="pln"> same_bytes</span><span class="pun">(</span><span class="pln">left_name</span><span class="pun">,</span><span class="pln"> right_name</span><span class="pun">):</span><span class="pln">
    left_bytes </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="pln">left_name</span><span class="pun">,</span><span class="pln"> </span><span class="str">"rb"</span><span class="pun">).</span><span class="pln">read</span><span class="pun">()</span><span class="pln">
    right_bytes </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="pln">right_name</span><span class="pun">,</span><span class="pln"> </span><span class="str">"rb"</span><span class="pun">).</span><span class="pln">read</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> left_bytes </span><span class="pun">==</span><span class="pln"> right_bytes


</span><span class="com"># [dup]</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> find_duplicates</span><span class="pun">(</span><span class="pln">filenames</span><span class="pun">):</span><span class="pln">
    matches </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> i_left </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="pln">len</span><span class="pun">(</span><span class="pln">filenames</span><span class="pun">)):</span><span class="pln">
        left </span><span class="pun">=</span><span class="pln"> filenames</span><span class="pun">[</span><span class="pln">i_left</span><span class="pun">]</span><span class="pln">
        </span><span class="kwd">for</span><span class="pln"> i_right </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="pln">i_left</span><span class="pun">):</span><span class="pln">
            right </span><span class="pun">=</span><span class="pln"> filenames</span><span class="pun">[</span><span class="pln">i_right</span><span class="pun">]</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> same_bytes</span><span class="pun">(</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> right</span><span class="pun">):</span><span class="pln">
                matches</span><span class="pun">.</span><span class="pln">append</span><span class="pun">((</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> right</span><span class="pun">))</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> matches
</span><span class="com"># [/dup]</span><span class="pln">


</span><span class="kwd">if</span><span class="pln"> __name__ </span><span class="pun">==</span><span class="pln"> </span><span class="str">"__main__"</span><span class="pun">:</span><span class="pln">
    duplicates </span><span class="pun">=</span><span class="pln"> find_duplicates</span><span class="pun">(</span><span class="pln">sys</span><span class="pun">.</span><span class="pln">argv</span><span class="pun">[</span><span class="lit">1</span><span class="pun">:])</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> right</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> duplicates</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> right</span><span class="pun">)</span></pre>

<p style="text-align: center;">
	<img alt="triangle.png" class="ipsImage ipsImage_thumbnailed" data-fileid="181742" data-ratio="100.00" data-unique="sokyug4mr" width="171" src="https://academy.hsoub.com/uploads/monthly_2026_03/triangle.png.45e700f5d91a0cfe478600476d156cb8.png">
</p>

<p style="text-align: center;">
	تحديد نطاق الحلقة الداخلية لإنتاج تركبيات فريدة.
</p>

<h2 id="-">
	تجزئة الملفات
</h2>

<p>
	نريد الآن أن نعالج كل ملف مرة واحدة فقط لإنتاج معرف قصير يعتمد على محتويات الملف ثم نقارن الملفات التي تحمل نفس المعرف، وهي الملفات التي يُحتمل أن تكون متطابقة.
</p>

<p>
	إذا قُسمت الملفات إلى عدد g من المجموعات فستحتوي كل مجموعة على عدد N/g من الملفات، ويصبح إجمالي العمل المطلوب O(g(N / g²)) - على سبيل المثال: عدد g مجموعة * عدد مقارنات يساوي (N/g²) داخل كل مجموعة-، أي أننا نحصل على (N²/g) مما يعني أن زمن التشغيل الكلي <strong>ينخفض</strong> بزيادة عدد المجموعات.
</p>

<p style="text-align: center;">
	<img alt="hash_group.png" class="ipsImage ipsImage_thumbnailed" data-fileid="181743" data-ratio="51.89" data-unique="7zaxhm195" width="291" src="https://academy.hsoub.com/uploads/monthly_2026_03/hash_group.png.a9b9f6129db7b23487892b8d76076c9b.png">
</p>

<p style="text-align: center;">
	تجميع الملفات عبر شيفرة التجزئة يقلص عدد المقارنات من 15 إلى 4 فقط.
</p>

<p>
	نستطيع إنشاء معرفات للملفات باستخدام دالة تجزئة hash function لإنتاج شيفرة تجزئة hash code، وبما أن البايتات في الأصل عبارة عن أرقام فنستطيع إنشاء دالة تجزئة بسيطة عبر جمع قيم البايتات الموجودة في الملف ثم أخذ باقي القسم على رقم معين:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_19" style=""><span class="com"># [hash]</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> naive_hash</span><span class="pun">(</span><span class="pln">data</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> sum</span><span class="pun">(</span><span class="pln">data</span><span class="pun">)</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> </span><span class="lit">13</span><span class="pln">
</span><span class="com"># [/hash]</span><span class="pln">

</span><span class="kwd">if</span><span class="pln"> __name__ </span><span class="pun">==</span><span class="pln"> </span><span class="str">"__main__"</span><span class="pun">:</span><span class="pln">
    </span><span class="com"># [example]</span><span class="pln">
    example </span><span class="pun">=</span><span class="pln"> bytes</span><span class="pun">(</span><span class="str">"hashing"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"utf-8"</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> i </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">example</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pun">):</span><span class="pln">
        substring </span><span class="pun">=</span><span class="pln"> example</span><span class="pun">[:</span><span class="pln">i</span><span class="pun">]</span><span class="pln">
        hash </span><span class="pun">=</span><span class="pln"> naive_hash</span><span class="pun">(</span><span class="pln">substring</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">f</span><span class="str">"{hash:2} {substring}"</span><span class="pun">)</span><span class="pln">
    </span><span class="com"># [/example]</span></pre>

<p>
	إليك اختبارًا سريعًا يحسب شيفرة التجزئة لأجزاء من كلمة hashing يتزايد طولها:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_21" style=""><span class="pln">example </span><span class="pun">=</span><span class="pln"> bytes</span><span class="pun">(</span><span class="str">"hashing"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"utf-8"</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> i </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">example</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pun">):</span><span class="pln">
    substring </span><span class="pun">=</span><span class="pln"> example</span><span class="pun">[:</span><span class="pln">i</span><span class="pun">]</span><span class="pln">
    hash </span><span class="pun">=</span><span class="pln"> naive_hash</span><span class="pun">(</span><span class="pln">substring</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">f</span><span class="str">"{hash:2} {substring}"</span><span class="pun">)</span></pre>

<p>
	يكون الخرج ما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_23" style=""><span class="lit">0</span><span class="pln"> b</span><span class="str">'h'</span><span class="pln">
 </span><span class="lit">6</span><span class="pln"> b</span><span class="str">'ha'</span><span class="pln">
 </span><span class="lit">4</span><span class="pln"> b</span><span class="str">'has'</span><span class="pln">
 </span><span class="lit">4</span><span class="pln"> b</span><span class="str">'hash'</span><span class="pln">
 </span><span class="lit">5</span><span class="pln"> b</span><span class="str">'hashi'</span><span class="pln">
</span><span class="lit">11</span><span class="pln"> b</span><span class="str">'hashin'</span><span class="pln">
</span><span class="lit">10</span><span class="pln"> b</span><span class="str">'hashing'</span></pre>

<p>
	يبدو الخرج عشوائيًا، نريد إجراء اختبار أكثر صرامة عبر تجربة تجزئة كل سطر نصي في <a href="https://www.gutenberg.org/" rel="external nofollow">رواية دراكولا -نسخة مشروع جوتنبرج-</a>، ثم نرسم مخططًا بيانيًا للتوزيع:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="181744" href="https://academy.hsoub.com/uploads/monthly_2026_03/naive_dracula.png.78af9cd6a1260e0f5440955c3d91df6a.png" rel=""><img alt="naive_dracula.png" class="ipsImage ipsImage_thumbnailed" data-fileid="181744" data-ratio="40.50" data-unique="aakrgk5xt" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_03/naive_dracula.thumb.png.b48437924849f17c75a161d37c5ce320.png"></a>
</p>

<p style="text-align: center;">
	توزيع شيفرات التجزئة لكل سطر في رواية دراكولا.
</p>

<p>
	تظهر أغلب الدلاء buckets - أي الأعمدة في الرسم البياني- متساوية في الطول، باستثناء ارتفاع ملحوظ عند القيمة صفر، لقد اعتمد تقديرنا لترميز Big O حول مدى كفاءة خوارزميتنا على توزيع الملفات بالتساوي بين المجموعات، فلن تعمل الشيفرة بالسرعة المطلوبة إذا لم يتحقق هذا الشرط.
</p>

<p>
	يتبين بالقليل من البحث أن الملف النصي الذي نستخدمه يستخدم سطرًا فارغًا للفصل بين الفقرات، تنتج هذه الأسطر قيمة تجزئة تساوي صفر، لذا يعكس ذلك الارتفاع توزيعًا غير عادل في البيانات، و ستصبح النتيجة أكثر توازنًا إذا رسمنا توزيع شيفرة التجزئة للأسطر الفريدة فقط:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="181745" href="https://academy.hsoub.com/uploads/monthly_2026_03/naive_dracula_unique.png.018165c116176db93b1d603b63bb26fd.png" rel=""><img alt="naive_dracula_unique.png" class="ipsImage ipsImage_thumbnailed" data-fileid="181745" data-ratio="40.50" data-unique="2v4zbkfbm" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_03/naive_dracula_unique.thumb.png.32fce3c0951103cad53eb4accc62d34c.png"></a>
</p>

<p style="text-align: center;">
	توزيع شيفرات التجزئة للأسطر الفريدة في رواية دراكولا.
</p>

<p>
	تُعد التجزئة أداة قوية للغاية، حيث تجزئ القواميس في لغة بايثون مثلًا مفاتيحها لتسريع عملية البحث، وبما أننا الآن نستطيع تجزئة الملفات فإننا نستطيع بناء قاموس يستخدم شيفرات التجزئة كمفاتيح ومجموعات أسماء الملفات كقيم:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_27" style=""><span class="kwd">import</span><span class="pln"> sys
</span><span class="kwd">from</span><span class="pln"> naive_hash </span><span class="kwd">import</span><span class="pln"> naive_hash


</span><span class="kwd">def</span><span class="pln"> same_bytes</span><span class="pun">(</span><span class="pln">left_name</span><span class="pun">,</span><span class="pln"> right_name</span><span class="pun">):</span><span class="pln">
    left_bytes </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="pln">left_name</span><span class="pun">,</span><span class="pln"> </span><span class="str">"rb"</span><span class="pun">).</span><span class="pln">read</span><span class="pun">()</span><span class="pln">
    right_bytes </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="pln">right_name</span><span class="pun">,</span><span class="pln"> </span><span class="str">"rb"</span><span class="pun">).</span><span class="pln">read</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> left_bytes </span><span class="pun">==</span><span class="pln"> right_bytes


</span><span class="kwd">def</span><span class="pln"> find_duplicates</span><span class="pun">(</span><span class="pln">filenames</span><span class="pun">):</span><span class="pln">
    matches </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> i_left </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="pln">len</span><span class="pun">(</span><span class="pln">filenames</span><span class="pun">)):</span><span class="pln">
        left </span><span class="pun">=</span><span class="pln"> filenames</span><span class="pun">[</span><span class="pln">i_left</span><span class="pun">]</span><span class="pln">
        </span><span class="kwd">for</span><span class="pln"> i_right </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="pln">i_left</span><span class="pun">):</span><span class="pln">
            right </span><span class="pun">=</span><span class="pln"> filenames</span><span class="pun">[</span><span class="pln">i_right</span><span class="pun">]</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> same_bytes</span><span class="pun">(</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> right</span><span class="pun">):</span><span class="pln">
                matches</span><span class="pun">.</span><span class="pln">append</span><span class="pun">((</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> right</span><span class="pun">))</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> matches


</span><span class="com"># [group]</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> find_groups</span><span class="pun">(</span><span class="pln">filenames</span><span class="pun">):</span><span class="pln">
    groups </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> fn </span><span class="kwd">in</span><span class="pln"> filenames</span><span class="pun">:</span><span class="pln">
        data </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="pln">fn</span><span class="pun">,</span><span class="pln"> </span><span class="str">"rb"</span><span class="pun">).</span><span class="pln">read</span><span class="pun">()</span><span class="pln">
        hash_code </span><span class="pun">=</span><span class="pln"> naive_hash</span><span class="pun">(</span><span class="pln">data</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> hash_code </span><span class="kwd">not</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> groups</span><span class="pun">:</span><span class="pln">
            groups</span><span class="pun">[</span><span class="pln">hash_code</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> set</span><span class="pun">()</span><span class="pln">
        groups</span><span class="pun">[</span><span class="pln">hash_code</span><span class="pun">].</span><span class="pln">add</span><span class="pun">(</span><span class="pln">fn</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> groups
</span><span class="com"># [/group]</span><span class="pln">


</span><span class="kwd">if</span><span class="pln"> __name__ </span><span class="pun">==</span><span class="pln"> </span><span class="str">"__main__"</span><span class="pun">:</span><span class="pln">
    </span><span class="com"># [main]</span><span class="pln">
    groups </span><span class="pun">=</span><span class="pln"> find_groups</span><span class="pun">(</span><span class="pln">sys</span><span class="pun">.</span><span class="pln">argv</span><span class="pun">[</span><span class="lit">1</span><span class="pun">:])</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> filenames </span><span class="kwd">in</span><span class="pln"> groups</span><span class="pun">.</span><span class="pln">values</span><span class="pun">():</span><span class="pln">
        duplicates </span><span class="pun">=</span><span class="pln"> find_duplicates</span><span class="pun">(</span><span class="pln">list</span><span class="pun">(</span><span class="pln">filenames</span><span class="pun">))</span><span class="pln">
        </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> right</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> duplicates</span><span class="pun">:</span><span class="pln">
            </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> right</span><span class="pun">)</span><span class="pln">
    </span><span class="com"># [/main]</span></pre>

<p>
	تتحقق الشيفرة أعلاه في كل مرة يحسب البرنامج فيها شيفرة تجزئة مما إذا كانت القيمة قد ظهرت من قبل، وينشئ إدخالًا جديدًا إذا لم تكن موجودة في قاموس المجموعات <code>groups</code>، بحيث تكون شيفرة التجزئة هي المفتاح، ويضع مجموعة فارغة كقيمة لها، وهكذا يضمن وجود مجموعة جاهزة لإضافة اسم الملف إليها. انظر المقطع التالي من الشيفرة الذي يوضح منطق تجميع الملفات بناءً على شيفرة التجزئة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_29" style=""><span class="kwd">def</span><span class="pln"> find_groups</span><span class="pun">(</span><span class="pln">filenames</span><span class="pun">):</span><span class="pln">
    groups </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> fn </span><span class="kwd">in</span><span class="pln"> filenames</span><span class="pun">:</span><span class="pln">
        data </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="pln">fn</span><span class="pun">,</span><span class="pln"> </span><span class="str">"rb"</span><span class="pun">).</span><span class="pln">read</span><span class="pun">()</span><span class="pln">
        hash_code </span><span class="pun">=</span><span class="pln"> naive_hash</span><span class="pun">(</span><span class="pln">data</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> hash_code </span><span class="kwd">not</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> groups</span><span class="pun">:</span><span class="pln">
            groups</span><span class="pun">[</span><span class="pln">hash_code</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> set</span><span class="pun">()</span><span class="pln">
        groups</span><span class="pun">[</span><span class="pln">hash_code</span><span class="pun">].</span><span class="pln">add</span><span class="pun">(</span><span class="pln">fn</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> groups</span></pre>

<p>
	نستطيع الآن إعادة استخدام معظم الشيفرة التي كتبناها سابقًا للبحث عن التكرارات داخل كل مجموعة على حدة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_31" style=""><span class="pln">groups </span><span class="pun">=</span><span class="pln"> find_groups</span><span class="pun">(</span><span class="pln">sys</span><span class="pun">.</span><span class="pln">argv</span><span class="pun">[</span><span class="lit">1</span><span class="pun">:])</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> filenames </span><span class="kwd">in</span><span class="pln"> groups</span><span class="pun">.</span><span class="pln">values</span><span class="pun">():</span><span class="pln">
    duplicates </span><span class="pun">=</span><span class="pln"> find_duplicates</span><span class="pun">(</span><span class="pln">list</span><span class="pun">(</span><span class="pln">filenames</span><span class="pun">))</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> right</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> duplicates</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> right</span><span class="pun">)</span></pre>

<p>
	فيكون الخرج كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_33" style=""><span class="pln">tests</span><span class="pun">/</span><span class="pln">a2</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">a1</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">a3</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">a1</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">a3</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">a2</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">b1</span><span class="pun">.</span><span class="pln">txt tests</span><span class="pun">/</span><span class="pln">b2</span><span class="pun">.</span><span class="pln">txt</span></pre>

<h2 id="-">
	تجزئة أفضل
</h2>

<p>
	بالعودة إلى صيغة O(n²/g) التي تخبرنا بمقدار الجهد المطلوب إذا قسمنا n من الملفات على g من المجموعات، فإذا كان لدينا عدد مجموعات يساوي عدد الملفات تمامًا -أي حين تكون n مساوية لـ g- فإن الجهد المطلوب لمعالجة n ملف سيكون O(n²/n) أو O(N) للتبسيط، وهذا يعني أن الجهد المطلوب سيتناسب طرديًا مع عدد الملفات، وبما أننا نحتاج إلى قراءة كل ملف مرة واحدة على الأقل على أي حال فلن نتمكن من تحقيق نتيجة أفضل من هذه، لكن كيف نضمن أن كل ملف فريد سيكون في مجموعته التي ينتمي لها؟
</p>

<p>
	يكمن الحل في استخدام دالة تجزئة تشفيرية Cryptographic hash function تتميز مخرجاتها أنها حتمية، فإذا أعطيتها نفس البايتات بنفس الترتيب ستنتج نفس الخرج في كل مرة، ومع هذا فإن الخرج يتوزع مثل متغير عشوائي منتظم بحيث يتساوى احتمال كل خرج منها مما يضمن توزيع الملفات بالعدل بين المجموعات.
</p>

<p>
	تصعب كتابة دوال التجزئة بالتشفير كما يصعب كذلك إثبات أن خوارزمية ما لديها الخصائص التي نريدها، لذا سنستخدم دالة من مكتبة التجزئة <code>hashlib</code> في لغة بايثون التي تنفذ خوارزمية التجزئة SHA-256.
</p>

<p>
	تنتج هذه الدالة تجزئة بحجم 256 بت عند إعطائها بعض البايتات كمدخلات، وتُكتب عادة كسلسلة نصية من 64 محرف بالنظام الست عشري حيث يستخدم هذا النظام الأحرف A حتى F لتمثيل الأرقام من 10 إلى 15، فتحسب القيمة 3D5 مثلًا كالتالي:
</p>

<p>
	3 × 16² + 13 × 16¹ + 5 × 16⁰ = 981 بالنظام العشري.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_35" style=""><span class="kwd">from</span><span class="pln"> hashlib </span><span class="kwd">import</span><span class="pln"> sha256

</span><span class="kwd">if</span><span class="pln"> __name__ </span><span class="pun">==</span><span class="pln"> </span><span class="str">"__main__"</span><span class="pun">:</span><span class="pln">
    </span><span class="com"># [example]</span><span class="pln">
    example </span><span class="pun">=</span><span class="pln"> bytes</span><span class="pun">(</span><span class="str">"hash"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"utf-8"</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> i </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">example</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pun">):</span><span class="pln">
        substring </span><span class="pun">=</span><span class="pln"> example</span><span class="pun">[:</span><span class="pln">i</span><span class="pun">]</span><span class="pln">
        hash </span><span class="pun">=</span><span class="pln"> sha256</span><span class="pun">(</span><span class="pln">substring</span><span class="pun">).</span><span class="pln">hexdigest</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">f</span><span class="str">"{substring}\n{hash}"</span><span class="pun">)</span><span class="pln">
    </span><span class="com"># [/example]</span></pre>

<p>
	ويكون خرجها كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_37" style=""><span class="pln">b</span><span class="str">'h'</span><span class="pln">
aaa9402664f1a41f40ebbc52c9993eb66aeb366602958fdfaa283b71e64db123
b</span><span class="str">'ha'</span><span class="pln">
</span><span class="lit">8693873cd8f8a2d9c7c596477180f851e525f4eaf55a4f637b445cb442a5e340</span><span class="pln">
b</span><span class="str">'has'</span><span class="pln">
</span><span class="lit">9150c74c5f92d51a92857f4b9678105ba5a676d308339a353b20bd38cd669ce7</span><span class="pln">
b</span><span class="str">'hash'</span><span class="pln">
d04b98f48e8f8bcc15c6ae5ac050801cd6dcfd428fb5f9e65c4e16e7807340fa</span></pre>

<h2 id="-">
	مسألة عيد الميلاد
</h2>

<p>
	يمثل احتمال تشارك شخصين في نفس عيد الميلاد 1/365 مع تجاهل يوم 29 فبراير، ومن ثم تكون احتمالية ألا يتشاركاه هو 364/365، وإذا أضفنا شخصًا ثالثًا يكون احتمال ألا يتشارك عيد الميلاد مع الشخصين السابقين هي 363/365، وهكذا يكون الاحتمال الإجمالي لعدم تشارك هؤلاء الأشخاص في نفس عيد الميلاد هي (364/365)*(363/365)، وإذا استمر الحال على هذا النمط سنجد أن هناك احتمالًا بنسبة 50% لوجود شخصين يتشاركان يوم الميلاد نفسه في مجموعة مكونة من 23 شخصًا فقط، تزيد هذه النسبة إلى 99.9% مع وجود 70 شخصًا.
</p>

<h3 id="-">
	تطبيق المنطق على الملفات
</h3>

<p>
	يمكن استخدام نفس المنطق الرياضي لنرى عدد الملفات التي نحتاج إلى تجزئتها قبل أن نصل إلى احتمال حدوث تصادم collision باستخدام تجزئة 256-بت -التصادم في التجزئة يعني أن قيمتين أو أكثر لهما نفس شيفرة التجزئة-، وتخبرنا ويكيبيديا أن الإجابة هي
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_3335_7" style=""><span class="pln">$4 \times 10^{38}$</span></pre>

<p>
	ملفًا تقريبًا، وبما أن الرقم كبير للغاية فنحن مستعدون للمخاطرة. يسهم استخدام دالة المكتبة هذه في جعل برنامج البحث عن الملفات المكررة أقصر بكثير:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_39" style=""><span class="kwd">import</span><span class="pln"> sys
</span><span class="kwd">from</span><span class="pln"> hashlib </span><span class="kwd">import</span><span class="pln"> sha256

</span><span class="kwd">def</span><span class="pln"> find_groups</span><span class="pun">(</span><span class="pln">filenames</span><span class="pun">):</span><span class="pln">
    groups </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> fn </span><span class="kwd">in</span><span class="pln"> filenames</span><span class="pun">:</span><span class="pln">
        data </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="pln">fn</span><span class="pun">,</span><span class="pln"> </span><span class="str">"rb"</span><span class="pun">).</span><span class="pln">read</span><span class="pun">()</span><span class="pln">
        hash_code </span><span class="pun">=</span><span class="pln"> sha256</span><span class="pun">(</span><span class="pln">data</span><span class="pun">).</span><span class="pln">hexdigest</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> hash_code </span><span class="kwd">not</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> groups</span><span class="pun">:</span><span class="pln">
            groups</span><span class="pun">[</span><span class="pln">hash_code</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> set</span><span class="pun">()</span><span class="pln">
        groups</span><span class="pun">[</span><span class="pln">hash_code</span><span class="pun">].</span><span class="pln">add</span><span class="pun">(</span><span class="pln">fn</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> groups


</span><span class="kwd">if</span><span class="pln"> __name__ </span><span class="pun">==</span><span class="pln"> </span><span class="str">"__main__"</span><span class="pun">:</span><span class="pln">
    groups </span><span class="pun">=</span><span class="pln"> find_groups</span><span class="pun">(</span><span class="pln">sys</span><span class="pun">.</span><span class="pln">argv</span><span class="pun">[</span><span class="lit">1</span><span class="pun">:])</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> filenames </span><span class="kwd">in</span><span class="pln"> groups</span><span class="pun">.</span><span class="pln">values</span><span class="pun">():</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="str">", "</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">sorted</span><span class="pun">(</span><span class="pln">filenames</span><span class="pun">)))</span></pre>

<p>
	وعند تشغيل البرنامج:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_41" style=""><span class="pln">python dup</span><span class="pun">.</span><span class="pln">py tests</span><span class="pun">/*.</span><span class="pln">txt</span></pre>

<p>
	نحصل على النتائج التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1792_43" style=""><span class="pln">tests</span><span class="pun">/</span><span class="pln">a1</span><span class="pun">.</span><span class="pln">txt</span><span class="pun">,</span><span class="pln"> tests</span><span class="pun">/</span><span class="pln">a2</span><span class="pun">.</span><span class="pln">txt</span><span class="pun">,</span><span class="pln"> tests</span><span class="pun">/</span><span class="pln">a3</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">b1</span><span class="pun">.</span><span class="pln">txt</span><span class="pun">,</span><span class="pln"> tests</span><span class="pun">/</span><span class="pln">b2</span><span class="pun">.</span><span class="pln">txt
tests</span><span class="pun">/</span><span class="pln">c1</span><span class="pun">.</span><span class="pln">txt</span></pre>

<p>
	المهم هنا أن هذا المنظور الجديد يستطيع التعامل مع مجموعات ملفات ضخمة جدًا، فنحن لا نحتاج إلا إلى فحص كل ملف مرة واحدة فقط مما يقلل زمن التشغيل إلى أدنى مستوى ممكن.
</p>

<h2 id="-">
	خاتمة
</h2>

<p>
	تلخص الصورة أدناه الأفكار الرئيسية الواردة في هذا الفصل، ولعل أهمها أن بعض الخوارزميات تتفوق على غيرها من حيث الكفاءة والأداء.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="181746" href="https://academy.hsoub.com/uploads/monthly_2026_03/concept_map.png.8db8be0c2dd32385cac2a72d8f4c68ee.png" rel=""><img alt="concept_map.png" class="ipsImage ipsImage_thumbnailed" data-fileid="181746" data-ratio="51.50" data-unique="a7rx9sg1f" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_03/concept_map.thumb.png.e62b86bd56976e46b7e46b8fe1118fdb.png"></a>
</p>

<p>
	خريطة تصور اكتشاف الملفات المكررة باستخدام التجزئة.
</p>

<ul>
	<li>
		توضح هذه الخريطة كيف ننتقل من المقارنة الثنائية البطيئة O(n²) إلى التوزيع الذكي باستخدام التجزئة.
	</li>
	<li>
		تحول دالة التجزئة محتوى الملف إلى بصمة رقمية فريدة.
	</li>
	<li>
		يؤدي استخدام خوارزميات مثل SHA-256 إلى تقليل احتمالية التصادمات إلى أدنى مستوياتها.
	</li>
	<li>
		يتحسن أداء البرنامج ليصبح زمن التشغيل خطيًا O(n) مما يسمح لنا بمعالجة كميات هائلة من البيانات في وقت قياسي.
	</li>
</ul>

<h2 id="-">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D9%85%D9%81%D8%B3%D8%B1-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-python-interpreter-r2601/" rel=""><strong>تطوير مفسر بايثون Python Interpreter</strong></a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D8%A8%D9%86%D8%A7%D8%A1-%D9%84%D8%B9%D8%A8%D8%A9-%D8%B1%D8%B3%D9%88%D9%85%D9%8I%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D9%88%D9%88%D8%AD%D8%AF%D8%A9-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-pygame-r1704/" rel=""><strong>بناء لعبة رسومية باستخدام بايثون ووحدة الألعاب Pygame</strong></a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D8%25%D8%A3%D9%87%D9%85-%D8%A7%D9%84%D8%A3%D8%B3%D8%A6%D9%84%D8%A9-%D8%A7%D9%84%D9%86%D8%B8%D8%B1%D9%258I%D8%A9-%D8%A7%D9%84%D8%AA%D9%258I-%D9%82%D8%AF-%D8%AA%D8%B7%D8%B1%D8%AD-%D9%81%D9%258I-%D8%A7%D9%84%D9%85%D9%82%D8%A7%D8%A8%D9%84%D8%A7%D8%AA-%D9%84%D8%AA%D9%88%D8%B8%D9%258I%D9%81-%D9%85%D8%B7%D9%88%D8%B1-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2595/" rel=""><strong>أهم الأسئلة النظرية التي قد تطرح في المقابلات لتوظيف مطور بايثون</strong></a>
	</li>
</ul>

<p>
	ترجمة -بتصرف- للفصل <a href="https://third-bit.com/sdxpy/dup/" rel="external nofollow"><strong>Chapter 3: Finding Duplicate Files</strong></a> من كتاب Software Design by Example.
</p>
]]></description><guid isPermaLink="false">2604</guid><pubDate>Tue, 17 Mar 2026 07:12:00 +0000</pubDate></item><item><title>&#x62A;&#x637;&#x648;&#x64A;&#x631; &#x645;&#x641;&#x633;&#x631; &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646; Python Interpreter</title><link>https://academy.hsoub.com/programming/python/%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D9%85%D9%81%D8%B3%D8%B1-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-python-interpreter-r2601/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2026_03/12.------.png.c1ee0e628df9e3f614cafa383d9c226b.png" /></p>
<p id="-python-interpreter">
	يهدف هذا المقال إلى مساعدة المبرمج في توسيع معرفته البرمجية من خلال الاطلاع على شيفرة برمجية مفتوحة المصدر توضح له آلية العمل وتزيد من خبرته البرمجية، لذا سنوضح الشيفرة البرمجية لمفسر بايثون بسيط هو المفسر Byterun المكتوب بلغة بايثون نفسها والمحدود بحوالي 500 سطر، مما يبسط كيفية عمل المفسر ودراسة شيفرته البرمجية مفتوحة المصدر، وبالتالي سيساعد في تحسين المهارات البرمجية وفهم عمل المفسر ويعلمنا كيفية بناء مفسر لغة برمجية خاص بنا.
</p>

<p>
	كتب Ned Batchelder و Allison Kaptur مفسر بايثون Byterun بناءً على عمل Paul Swartz، وبنيته مماثلة لمفسر بايثون الأساسي CPython، لذا سيساعدنا فهم مفسر Byterun على فهم المفسرات وخاصةً مفسر CPython شائع الاستخدام. يستطيع مفسر Byterun تشغيل معظم برامج بايثون البسيطة بالرغم من عدد سطور شيفرته القصير.
</p>

<h2 id="-">
	مفسر بايثون
</h2>

<p>
	لنحدد أولًا ما يعنيه مفسر بايثون، إذ يمكن استخدام مصطلح <strong>مفسر Interpreter</strong> بطرق مختلفة في سياق الحديث عن <a href="https://academy.hsoub.com/programming/python/" rel="">بايثون</a>، حيث يشير أحيانًا المفسر إلى Python REPL، وهو الموجِّه التفاعلي الذي نحصل عليه من خلال كتابة <code>python</code> في سطر الأوامر، وقد يُستخدَم مفسر بايثون للدلالة على التنفيذ الكامل لشيفرة بايثون البرمجية، ولكن سيدل مصطلح المفسر في هذا المقال على مجال أضيق، وهو الخطوة الأخيرة من عملية تنفيذ برنامج بايثون.
</p>

<p>
	يسبق تشغيل المفسر ثلاث خطوات في بايثون وهي: تحليل المفردات Lexing والتحليل Parsing و<a href="https://academy.hsoub.com/programming/c/%D8%A7%D9%84%D9%81%D8%B5%D9%84-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D9%85%D9%81%D9%87%D9%88%D9%85-%D8%A7%D9%84%D8%AA%D8%B5%D8%B1%D9%8A%D9%81-compilation-%D9%81%D9%8A-%D9%84%D8%BA%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-r976/" rel="">التصريف Compiling</a>، حيث تعمل هذه الخطوات مع بعضها البعض لتحويل شيفرة المبرمج المصدرية من أسطر نصية إلى كائنات شيفرة Code Objects منظمة تحتوي على تعليمات Instructions يمكن للمفسر فهمها، وتتمثل مهمة المفسر في أخذ هذه الكائنات واتباع التعليمات.
</p>

<p>
	يُعَد التصريف خطوة من خطوات تنفيذ شيفرة بايثون بالرغم من اعتبارها لغة مُفسَّرة مثل لغتي <a href="https://wiki.hsoub.com/Ruby" rel="external">Ruby</a> أو Perl، بينما تُعَد <a href="https://academy.hsoub.com/programming/c/" rel="">C</a> أو <a href="https://academy.hsoub.com/programming/rust/" rel="">Rust</a> لغات مُصرَّفة. تتضمن معظم اللغات المفسَّرة بما في ذلك لغة بايثون خطوة تصريف، وسبب وصف بايثون بأنها لغة مفسَّرة هو أن خطوة التصريف تنجر عملًا أقل نسبيًا من العمل الذي تنجزه في لغة مُصرَّفة. ينجز المفسر في لغة بايثون العمل الأكبر نسبيًا، إذ يحتوي مُصرِّف بايثون على معلومات أقل بكثير حول سلوك البرنامج مقارنةً بمصرِّف لغة سي C.
</p>

<h2 id="-">
	تطوير مفسر بايثون باستخدام بايثون
</h2>

<p>
	Byterun هو مفسر بايثون مكتوب بلغة بايثون نفسها كما هو الحال مع مصرِّف C المستخدَم على نطاق واسع gcc المكتوب بلغة C، ولكن يمكننا كتابة مفسر بايثون بأي لغة تقريبًا.
</p>

<p>
	توجد مزايا وعيوب لكتابة مفسر بايثون باستخدام بايثون، فالعيب الأكبر هو السرعة، إذ يُعَد تنفيذ الشيفرة البرمجية في Byterun أبطأ بكثير من تنفيذها في مفسر CPython المكتوب بلغة C، ولكن صُمِّم مفسر Byterun في الأصل كتطبيق تعليمي، لذا لم تكن السرعة ذات أهمية كبيرة.
</p>

<p>
	الميزة الأهم لاستخدام بايثون هي أنه يمكننا تنفيذ المفسّر فقط بسهولة دون تنفيذ باقي وقت تشغيل بايثون وخاصة نظام الكائنات، إذ يمكن لمفسر Byterun مثلًا الرجوع إلى شيفرة بايثون الحقيقية عندما يحتاج إلى إنشاء صنف. يتميز مفسر Byterun بأنه سهل الفهم، ويعود ذلك إلى كتابته بلغة عالية المستوى هي لغة بايثون التي يجدها الكثيرون سهلة القراءة، ولا حاجة للتحسينات في مفسر Byterun بسبب تفضيل الوضوح والبساطة على السرعة فيه.
</p>

<h2 id="-">
	بناء مفسر
</h2>

<p>
	لنتعرف أولًا على كيفية عمل مفسر بايثون قبل البدء بشيفرة Byterun البرمجية، حيث يُعَد مفسر بايثون آلة افتراضية Virtual Machine، أي أنه برنامج يحاكي حاسوبًا حقيقيًا، وتكون هذه الآلة الافتراضية عبارة عن آلة مكدس Stack Machine، أي أنها تعالج عدة مكدسات لأداء عملياتها على عكس آلة التسجيل Register Machine التي تكتب وتقرأ من مواقع ذاكرة معينة.
</p>

<p>
	مفسّر بايثون هو مفسر بايت كود Bytecode، فمدخلاته هي مجموعات من التعليمات تسمى بايت كود، حيث يولّد محلل المفردات والمحلل والمصرِّف كائنات شيفرة ليعمل عليها المفسّر عندما نكتب شيفرة بايثون الخاصة بنا، ويحتوي كل كائن من هذه الكائنات على مجموعة من التعليمات التي يجب تنفيذها والتي هي البايت كود بالإضافة إلى معلومات أخرى سيحتاجها المفسر. يُعَد البايت كود تمثيلًا وسيطًا لشيفرة بايثون البرمجية، لأنها تعبّر عن الشيفرة المصدرية المكتوبة بطريقة يمكن للمفسّر فهمها، وهي تشبه الطريقة التي تعمل بها لغة التجميع Assembly كتمثيل وسيط بين شيفرة C والعتاد.
</p>

<h3 id="-">
	بناء مفسر بسيط
</h3>

<p>
	لنبدأ ببناء مفسر بسيط يستطيع جمع أعداد فقط ولا يفهم إلّا ثلاث تعليمات، حيث تتكون الشيفرة البرمجية التي يمكنه تنفيذها من التعليمات الثلاث التالية ضمن مجموعات مختلفة:
</p>

<ul>
	<li>
		<code>LOAD_VALUE</code>
	</li>
	<li>
		<code>ADD_TWO_VALUES</code>
	</li>
	<li>
		<code>PRINT_ANSWER</code>
	</li>
</ul>

<p>
	لن نهتم في هذا المقال بمحلل المفردات والمحلل والمصرِّف، وبالتالي لن نهتم بكيفية توليد مجموعات التعليمات. لنفترض كتابة العملية ‎<code>7 + 5</code>‎ التالية، حيث يصدر المصرِّف مجموعة من التعليمات الثلاث السابقة، أو إذا كان لدينا المصرِّف الصحيح، فيمكننا الكتابة بصيغة لغة Lisp المُحوَّلة إلى مجموعة التعليمات نفسها. لا يهتم المفسر بذلك، فما يهمه هو أن يحصل على تنسيق مناسب من هذه التعليمات.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1449_7" style=""><span class="pln">7 + 5</span></pre>

<p>
	وستنتج العملية السابقة مجموعة التعليمات التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_15" style=""><span class="pln">what_to_execute </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">"instructions"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[(</span><span class="str">"LOAD_VALUE"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">),</span><span class="pln">  </span><span class="com"># العدد الأول</span><span class="pln">
                     </span><span class="pun">(</span><span class="str">"LOAD_VALUE"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">),</span><span class="pln">  </span><span class="com"># العدد الثاني</span><span class="pln">
                     </span><span class="pun">(</span><span class="str">"ADD_TWO_VALUES"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">),</span><span class="pln">
                     </span><span class="pun">(</span><span class="str">"PRINT_ANSWER"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">)],</span><span class="pln">
    </span><span class="str">"numbers"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="lit">7</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">]</span><span class="pln"> </span><span class="pun">}</span></pre>

<p>
	مُفسِّر بايثون هو آلة مكدس، لذا يجب أن يعالج المكدسات لجمع عددين كما هو موضح في الشكل الآتي، حيث سيبدأ المفسر بتنفيذ التعليمة الأولى <code>LOAD_VALUE</code> ودفع العدد الأول إلى المكدس، ثم يدفع العدد الثاني إلى المكدس، ثم تستخرج التعليمة الثالثة <code>ADD_TWO_VALUES</code> العددين وتجمعهما وتدفع النتيجة إلى المكدس، وأخيرًا يستخرج المفسر الإجابة مرة أخرى من المكدس ويطبعها.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="181521" href="https://academy.hsoub.com/uploads/monthly_2026_03/001__.png.0d2b00ef494abc605390e5b5abe6586f.png" rel=""><img alt="001_مكدس_المفسر.png" class="ipsImage ipsImage_thumbnailed" data-fileid="181521" data-ratio="20.00" data-unique="x54vrtv67" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_03/001__.thumb.png.e8ad70741ced84a6000b9ccb816c1ce3.png"></a><span id="cke_bm_901E" style="display: none;"> </span>
</p>

<p>
	تخبر التعليمة <code>LOAD_VALUE</code> المفسر بدفع عدد إلى المكدس، ولكن لا تحدد التعليمات وحدها هذا العدد، إذ تحتاج التعليمة إلى معلومات إضافية تخبر المفسر بمكان العثور على العدد الذي يجب تحميله، لذا تحتوي مجموعة التعليمات في مثالنا على معلومتين هما: التعليمات نفسها وقائمة الثوابت التي ستحتاجها التعليمات. التعليمات هي البايت كود في بايثون، والكائن الذي يجب تنفيذه <code>what_to_execute</code> هو كائن الشيفرة.
</p>

<p>
	ولكن لماذا لا نضع الأعداد في التعليمات مباشرةً؟ حسنًا، لنفترض أننا نريد جمع سلاسل نصية بدلًا من جمع أعداد، إذ لن نرغب في جعل التعليمات كبيرة الحجم عندما نضع هذه السلاسل النصية مع التعليمات، وتسمح هذه الطريقة بالحصول على نسخة واحدة فقط من كل كائن نحتاجه، وبالتالي يمكن أن تكون الأعداد <code>"numbers"</code> هي <code>[7]</code> فقط لإجراء العملية <code>7 + 7</code> مثلًا.
</p>

<p>
	قد لا يكون سبب وجود التعليمات الأخرى المغايرة عن التعليمة <code>ADD_TWO_VALUES</code> واضحًا في مثالنا البسيط، ولكن تشكّل هذه التعليمات الأساس لبناء برامج أكثر تعقيدًا، إذ يمكننا مثلًا جمع ثلاث قيم أو أيّ عدد من القيم مع المجموعة الصحيحة من هذه التعليمات المذكورة في مثالنا. يوفر هذا المكدس طريقة واضحة لتتبع حالة المفسر، ويدعم مزيدًا من التعقيد لاحقًا.
</p>

<p>
	لنبدأ الآن في كتابة المفسر، حيث يحتوي كائن المفسر على مكدس نمثله بقائمة، ويحتوي على تابع يحدد كيفية تنفيذ التعليمات، إذ سيدفع المفسر القيمة إلى المكدس مثلًا بالنسبة للتعليمة <code>LOAD_VALUE</code>.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_17" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Interpreter</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> __init__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># مكدّس لتخزين القيم أثناء التنفيذ</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">stack </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> LOAD_VALUE</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> number</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># تحميل/إدخال قيمة إلى المكدّس</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">stack</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">number</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> PRINT_ANSWER</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># إخراج آخر قيمة من المكدّس وطباعتها</span><span class="pln">
        answer </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">stack</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">answer</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> ADD_TWO_VALUES</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># سحب آخر قيمتين من المكدّس ثم جمعهما وإرجاع الناتج إلى المكدّس</span><span class="pln">
        first_num </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">stack</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">
        second_num </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">stack</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">
        total </span><span class="pun">=</span><span class="pln"> first_num </span><span class="pun">+</span><span class="pln"> second_num
        self</span><span class="pun">.</span><span class="pln">stack</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">total</span><span class="pun">)</span></pre>

<p>
	تنفّذ الدوال السابقة التعليمات الثلاث التي يفهمها المفسر الذي يحتاج إلى طريقة لربطها معًا وتنفيذها، إذ تتمثل هذه الطريقة في التابع <code>run_code</code> الذي يأخذ وسيطًا هو القاموس <code>what_to_execute</code>، ويمر ضمن حلقة على كل تعليمة لمعالجة الوسطاء الخاصة بها إن كانت موجودة، ثم يستدعي التابع المقابل مع كائن المفسر.<code> </code>
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9793_7" style=""><span class="kwd">def</span><span class="pln"> run_code</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> what_to_execute</span><span class="pun">):</span><span class="pln">
    </span><span class="com"># استخراج قائمة التعليمات وقائمة القيم العددية من المدخل</span><span class="pln">
    instructions </span><span class="pun">=</span><span class="pln"> what_to_execute</span><span class="pun">[</span><span class="str">"instructions"</span><span class="pun">]</span><span class="pln">
    numbers </span><span class="pun">=</span><span class="pln"> what_to_execute</span><span class="pun">[</span><span class="str">"numbers"</span><span class="pun">]</span><span class="pln">

    </span><span class="com"># تنفيذ التعليمات بالترتيب (كمفسّر بسيط)</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> each_step </span><span class="kwd">in</span><span class="pln"> instructions</span><span class="pun">:</span><span class="pln">
        </span><span class="com"># كل خطوة تحتوي اسم التعليمة ووسيطها إن وجد</span><span class="pln">
        instruction</span><span class="pun">,</span><span class="pln"> argument </span><span class="pun">=</span><span class="pln"> each_step

        </span><span class="kwd">if</span><span class="pln"> instruction </span><span class="pun">==</span><span class="pln"> </span><span class="str">"LOAD_VALUE"</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># جلب قيمة عددية حسب الفهرس ثم وضعها في المكدّس</span><span class="pln">
            number </span><span class="pun">=</span><span class="pln"> numbers</span><span class="pun">[</span><span class="pln">argument</span><span class="pun">]</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">LOAD_VALUE</span><span class="pun">(</span><span class="pln">number</span><span class="pun">)</span><span class="pln">

        </span><span class="kwd">elif</span><span class="pln"> instruction </span><span class="pun">==</span><span class="pln"> </span><span class="str">"ADD_TWO_VALUES"</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># جمع آخر قيمتين في المكدّس ثم وضع الناتج في المكدّس</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">ADD_TWO_VALUES</span><span class="pun">()</span><span class="pln">

        </span><span class="kwd">elif</span><span class="pln"> instruction </span><span class="pun">==</span><span class="pln"> </span><span class="str">"PRINT_ANSWER"</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># طباعة آخر قيمة في المكدّس (غالبًا النتيجة)</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">PRINT_ANSWER</span><span class="pun">()</span></pre>

<p>
	يمكننا اختبار ذلك من خلال إنشاء نسخة من الكائن ثم استدعاء التابع <code>run_code</code> مع مجموعة التعليمات الخاصة بجمع العددين ‎<code>7 + 5</code>‎، وستظهر الإجابة 12 بالتأكيد.<code><span class="hljs-attribute"> </span></code><code> </code>
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_33" style=""><span class="pln">  interpreter </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Interpreter</span><span class="pun">()</span><span class="pln">
    interpreter</span><span class="pun">.</span><span class="pln">run_code</span><span class="pun">(</span><span class="pln">what_to_execute</span><span class="pun">)</span></pre>

<p>
	يُعَد هذا المفسر محدودًا جدًا، ولكنه يمثل بالضبط الطريقة التي يجمع بها مفسر بايثون الحقيقي الأعداد، إذ تحتاج بعض التعليمات إلى وسطاء، حيث تحتوي كثير من التعليمات على وسطاء في البايت كود الحقيقي لبايثون، وتُضمَّن الوسطاء مع التعليمات كما في مثالنا، ولكن تختلف وسطاء التعليمات عن وسطاء التوابع المستدعاة. لا تتطلب التعليمة <code>ADD_TWO_VALUES</code> وسطاء، إذ أُخرِجت القيم المراد جمعها من مكدس المفسر، ويُعَد ذلك من السمات المميزة للمفسر القائم على المكدس.
</p>

<p>
	يمكننا جمع أكثر من عددين في وقت واحد باستخدام مجموعات تعليمات صالحة دون أي تعديلات على المفسر الخاص بنا، إذًا ليكن لدينا مجموعة التعليمات التالية، ولنحاول توقع ما يحدث وما هي الشيفرة البرمجية المناسبة لتوليد هذه المجموعة من التعليمات عند وجود مصرِّف مناسب:<code> </code>
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_35" style=""><span class="pln"> what_to_execute </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">"instructions"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[(</span><span class="str">"LOAD_VALUE"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">),</span><span class="pln">
                         </span><span class="pun">(</span><span class="str">"LOAD_VALUE"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">),</span><span class="pln">
                         </span><span class="pun">(</span><span class="str">"ADD_TWO_VALUES"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">),</span><span class="pln">
                         </span><span class="pun">(</span><span class="str">"LOAD_VALUE"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">),</span><span class="pln">
                         </span><span class="pun">(</span><span class="str">"ADD_TWO_VALUES"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">),</span><span class="pln">
                         </span><span class="pun">(</span><span class="str">"PRINT_ANSWER"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">)],</span><span class="pln">
        </span><span class="str">"numbers"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="lit">7</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">,</span><span class="pln"> </span><span class="lit">8</span><span class="pun">]</span><span class="pln"> </span><span class="pun">}</span></pre>

<p>
	يمكننا تصور كيفية توسيع هذه البنية من خلال إضافة توابع إلى كائن المفسر لإجراء العديد من العمليات الأخرى مع وجود مصرِّف يزودنا بمجموعات تعليمات ذات تنسيق مناسب.
</p>

<h4 id="-">
	المتغيرات
</h4>

<p>
	نحتاج الآن إلى إضافة متغيرات إلى المفسر، حيث تتطلب المتغيرات تعليمات لتخزين قيمة متغير <code>STORE_NAME</code> وتعليمات لاسترجاعها <code>LOAD_NAME</code> مع ربط أسماء المتغيرات بقيمها. سنتجاهل حاليًا مساحات الأسماء والمجالات لنتمكن من تخزين ربط المتغيرات مع كائن المفسر. يجب التأكد من أن المتغير <code>what_to_execute</code> لديه قائمة بأسماء المتغيرات بالإضافة إلى قائمة الثوابت الخاصة به.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9793_9" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">def</span><span class="pln"> s</span><span class="pun">():</span><span class="pln">
</span><span class="pun">...</span><span class="pln">     a </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
</span><span class="pun">...</span><span class="pln">     b </span><span class="pun">=</span><span class="pln"> </span><span class="lit">2</span><span class="pln">
</span><span class="pun">...</span><span class="pln">     </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">a </span><span class="pun">+</span><span class="pln"> b</span><span class="pun">)</span><span class="pln">

</span><span class="com"># هذا قاموس يصف بايت كود مبسّط للدالة s: ماذا ننفّذ وبأي ترتيب</span><span class="pln">

what_to_execute </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com"># قائمة التعليمات بالترتيب</span><span class="pln">
    </span><span class="com"># كل عنصر عبارة عن: (اسم التعليمة، وسيطها) والوسيط قد يكون فهرسًا أو لا يوجد</span><span class="pln">
    </span><span class="str">"instructions"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
        </span><span class="pun">(</span><span class="str">"LOAD_VALUE"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">),</span><span class="pln">         </span><span class="com"># وضع القيمة الأولى في المكدّس</span><span class="pln">
        </span><span class="pun">(</span><span class="str">"STORE_NAME"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">),</span><span class="pln">         </span><span class="com"># تخزين أعلى قيمة في المكدّس داخل المتغير الأول</span><span class="pln">
        </span><span class="pun">(</span><span class="str">"LOAD_VALUE"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">),</span><span class="pln">         </span><span class="com"># وضع القيمة الثانية في المكدّس</span><span class="pln">
        </span><span class="pun">(</span><span class="str">"STORE_NAME"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">),</span><span class="pln">         </span><span class="com"># تخزين أعلى قيمة في المكدّس داخل المتغير الثاني</span><span class="pln">
        </span><span class="pun">(</span><span class="str">"LOAD_NAME"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">),</span><span class="pln">          </span><span class="com"># تحميل قيمة المتغير الأول إلى المكدّس</span><span class="pln">
        </span><span class="pun">(</span><span class="str">"LOAD_NAME"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">),</span><span class="pln">          </span><span class="com"># تحميل قيمة المتغير الثاني إلى المكدّس</span><span class="pln">
        </span><span class="pun">(</span><span class="str">"ADD_TWO_VALUES"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">),</span><span class="pln">  </span><span class="com"># جمع آخر قيمتين في المكدّس ووضع الناتج في المكدّس</span><span class="pln">
        </span><span class="pun">(</span><span class="str">"PRINT_ANSWER"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">)</span><span class="pln">     </span><span class="com"># طباعة آخر قيمة في المكدّس</span><span class="pln">
    </span><span class="pun">],</span><span class="pln">

    </span><span class="com"># قائمة القيم العددية التي تشير إليها التعليمات عبر فهارس</span><span class="pln">
    </span><span class="str">"numbers"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">],</span><span class="pln">

    </span><span class="com"># قائمة أسماء المتغيرات التي تشير إليها التعليمات عبر فهارس</span><span class="pln">
    </span><span class="str">"names"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="str">"a"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"b"</span><span class="pun">]</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يمكننا تتبع الأسماء المرتبطة بالقيم من خلال إضافة القاموس <code>environment</code> إلى التابع ‎<code>__init__</code>‎ مع إضافة <code>STORE_NAME</code> و <code>LOAD_NAME</code>، حيث تبحث هذه التوابع أولًا عن اسم المتغير ثم تستخدم القاموس لتخزين قيمة المتغير أو استرجاعها.
</p>

<p>
	يمكن أن تكون الوسطاء الخاصة بالتعليمة إما فهرسًا في قائمة الأعداد <code>"numbers"</code>، أو يمكن أن تكون فهرسًا في قائمة الأسماء <code>"names"</code>، إذ يعرف المفسر ما يجب أن تكون عليه من خلال التحقق من التعليمة التي ينفذها. لنضع هذه الشيفرة البرمجية مع ربط التعليمات مع ما تمثله وسطاؤها في تابع منفصل كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_39" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Interpreter</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> __init__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># مكدّس لتخزين القيم مؤقتًا أثناء التنفيذ</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">stack </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">
        </span><span class="com"># بيئة لحفظ قيم المتغيرات (اسم المتغير ← قيمته)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">environment </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> STORE_NAME</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># أخذ آخر قيمة من المكدّس وتخزينها باسم متغير</span><span class="pln">
        val </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">stack</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">environment</span><span class="pun">[</span><span class="pln">name</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> val

    </span><span class="kwd">def</span><span class="pln"> LOAD_NAME</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># جلب قيمة متغير ووضعها في المكدّس</span><span class="pln">
        val </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">environment</span><span class="pun">[</span><span class="pln">name</span><span class="pun">]</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">stack</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">val</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> parse_argument</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> instruction</span><span class="pun">,</span><span class="pln"> argument</span><span class="pun">,</span><span class="pln"> what_to_execute</span><span class="pun">):</span><span class="pln">
        </span><span class="str">"""تفسير معنى الوسيط لكل تعليمة"""</span><span class="pln">
        </span><span class="com"># تعليمات تستخدم قائمة الأرقام</span><span class="pln">
        numbers </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">"LOAD_VALUE"</span><span class="pun">]</span><span class="pln">
        </span><span class="com"># تعليمات تستخدم قائمة أسماء المتغيرات</span><span class="pln">
        names </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">"LOAD_NAME"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"STORE_NAME"</span><span class="pun">]</span><span class="pln">

        </span><span class="kwd">if</span><span class="pln"> instruction </span><span class="kwd">in</span><span class="pln"> numbers</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># تحويل الفهرس إلى رقم فعلي</span><span class="pln">
            argument </span><span class="pun">=</span><span class="pln"> what_to_execute</span><span class="pun">[</span><span class="str">"numbers"</span><span class="pun">][</span><span class="pln">argument</span><span class="pun">]</span><span class="pln">
        </span><span class="kwd">elif</span><span class="pln"> instruction </span><span class="kwd">in</span><span class="pln"> names</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># تحويل الفهرس إلى اسم متغير فعلي</span><span class="pln">
            argument </span><span class="pun">=</span><span class="pln"> what_to_execute</span><span class="pun">[</span><span class="str">"names"</span><span class="pun">][</span><span class="pln">argument</span><span class="pun">]</span><span class="pln">

        </span><span class="kwd">return</span><span class="pln"> argument

    </span><span class="kwd">def</span><span class="pln"> run_code</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> what_to_execute</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># أخذ التعليمات وتنفيذها بالترتيب</span><span class="pln">
        instructions </span><span class="pun">=</span><span class="pln"> what_to_execute</span><span class="pun">[</span><span class="str">"instructions"</span><span class="pun">]</span><span class="pln">
        </span><span class="kwd">for</span><span class="pln"> each_step </span><span class="kwd">in</span><span class="pln"> instructions</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># كل خطوة: (اسم التعليمة، وسيطها)</span><span class="pln">
            instruction</span><span class="pun">,</span><span class="pln"> argument </span><span class="pun">=</span><span class="pln"> each_step
            </span><span class="com"># تحويل الوسيط من فهرس إلى قيمة مفهومة</span><span class="pln">
            argument </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">parse_argument</span><span class="pun">(</span><span class="pln">instruction</span><span class="pun">,</span><span class="pln"> argument</span><span class="pun">,</span><span class="pln"> what_to_execute</span><span class="pun">)</span><span class="pln">

            </span><span class="kwd">if</span><span class="pln"> instruction </span><span class="pun">==</span><span class="pln"> </span><span class="str">"LOAD_VALUE"</span><span class="pun">:</span><span class="pln">
                </span><span class="com"># إدخال رقم إلى المكدّس</span><span class="pln">
                self</span><span class="pun">.</span><span class="pln">LOAD_VALUE</span><span class="pun">(</span><span class="pln">argument</span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">elif</span><span class="pln"> instruction </span><span class="pun">==</span><span class="pln"> </span><span class="str">"ADD_TWO_VALUES"</span><span class="pun">:</span><span class="pln">
                </span><span class="com"># جمع آخر قيمتين في المكدّس</span><span class="pln">
                self</span><span class="pun">.</span><span class="pln">ADD_TWO_VALUES</span><span class="pun">()</span><span class="pln">
            </span><span class="kwd">elif</span><span class="pln"> instruction </span><span class="pun">==</span><span class="pln"> </span><span class="str">"PRINT_ANSWER"</span><span class="pun">:</span><span class="pln">
                </span><span class="com"># طباعة آخر قيمة في المكدّس</span><span class="pln">
                self</span><span class="pun">.</span><span class="pln">PRINT_ANSWER</span><span class="pun">()</span><span class="pln">
            </span><span class="kwd">elif</span><span class="pln"> instruction </span><span class="pun">==</span><span class="pln"> </span><span class="str">"STORE_NAME"</span><span class="pun">:</span><span class="pln">
                </span><span class="com"># تخزين قيمة في متغير</span><span class="pln">
                self</span><span class="pun">.</span><span class="pln">STORE_NAME</span><span class="pun">(</span><span class="pln">argument</span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">elif</span><span class="pln"> instruction </span><span class="pun">==</span><span class="pln"> </span><span class="str">"LOAD_NAME"</span><span class="pun">:</span><span class="pln">
                </span><span class="com"># تحميل قيمة متغير إلى المكدّس</span><span class="pln">
                self</span><span class="pun">.</span><span class="pln">LOAD_NAME</span><span class="pun">(</span><span class="pln">argument</span><span class="pun">)</span></pre>

<p>
	قد تصبح بنية التابع <code>run_code</code> معقدة بعض الشيء بالرغم من وجود خمس تعليمات فقط، إذ نحتاج فرعًا واحدًا من تعليمة <code>if</code> لكل تعليمة، لذا يمكن الاستفادة من البحث الديناميكي عن تابع في بايثون بحيث نعرّف تابعًا اسمه <code>FOO</code> ينفّذ التعليمة التي اسمها <code>FOO</code>، وبالتالي نستخدم الدالة <code>getattr</code> في بايثون للبحث عن التابع بسرعة بدلًا من استخدام تعليمة <code>if</code> السابقة المعقدة. سيبدو الآن التابع <code>run_code</code> على النحو التالي
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_41" style=""><span class="kwd">def</span><span class="pln"> execute</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> what_to_execute</span><span class="pun">):</span><span class="pln">
    </span><span class="com"># جلب قائمة التعليمات</span><span class="pln">
    instructions </span><span class="pun">=</span><span class="pln"> what_to_execute</span><span class="pun">[</span><span class="str">"instructions"</span><span class="pun">]</span><span class="pln">

    </span><span class="com"># تنفيذ التعليمات خطوة بخطوة</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> each_step </span><span class="kwd">in</span><span class="pln"> instructions</span><span class="pun">:</span><span class="pln">
        </span><span class="com"># استخراج اسم التعليمة ووسيطها</span><span class="pln">
        instruction</span><span class="pun">,</span><span class="pln"> argument </span><span class="pun">=</span><span class="pln"> each_step

        </span><span class="com"># تحويل الوسيط من فهرس إلى قيمة فعلية (رقم أو اسم متغير)</span><span class="pln">
        argument </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">parse_argument</span><span class="pun">(</span><span class="pln">instruction</span><span class="pun">,</span><span class="pln"> argument</span><span class="pun">,</span><span class="pln"> what_to_execute</span><span class="pun">)</span><span class="pln">

        </span><span class="com"># الحصول على الدالة المطابقة لاسم التعليمة داخل هذا الصنف</span><span class="pln">
        bytecode_method </span><span class="pun">=</span><span class="pln"> getattr</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> instruction</span><span class="pun">)</span><span class="pln">

        </span><span class="com"># تنفيذ التعليمة: إن لم يكن لها وسيط ننفذها مباشرة، وإلا نمرر الوسيط</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> argument </span><span class="kwd">is</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">:</span><span class="pln">
            bytecode_method</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
            bytecode_method</span><span class="pun">(</span><span class="pln">argument</span><span class="pun">)</span></pre>

<h2 id="-">
	بناء بايت كود حقيقي في بايثون
</h2>

<p>
	سننتقل الآن إلى البايت كود الحقيقي في بايثون الذي يشبه مجموعات التعليمات في المفسر السابق، ولكنه يستخدم بايتًا واحدًا بدلًا من اسم طويل لتحديد كل تعليمة. إذًا ليكن لدينا المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9793_12" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">def</span><span class="pln"> cond</span><span class="pun">():</span><span class="pln">
</span><span class="pun">...</span><span class="pln">     x </span><span class="pun">=</span><span class="pln"> </span><span class="lit">3</span><span class="pln">                 
</span><span class="pun">...</span><span class="pln">     </span><span class="kwd">if</span><span class="pln"> x </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">5</span><span class="pun">:</span><span class="pln">             
</span><span class="pun">...</span><span class="pln">         </span><span class="kwd">return</span><span class="pln"> </span><span class="str">'yes'</span><span class="pln">      
</span><span class="pun">...</span><span class="pln">     </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">                 
</span><span class="pun">...</span><span class="pln">         </span><span class="kwd">return</span><span class="pln"> </span><span class="str">'no'</span><span class="pln">       </span></pre>

<p>
	يكشف بايثون عن مجموعة كبيرة من مكوناته الداخلية في وقت التشغيل بحيث يمكن الوصول إليها مباشرةً من بيئة REPL. يكون ‎ <code>cond.__code__</code>‎‎ هو كائن الشيفرة المرتبط بكائن الدالة <code>cond</code>، ويكون <code>cond.__code__.co_code</code> هو البايت كود، حيث لا يوجد سبب وجيه لاستخدام هذه السمات مباشرةً عند كتابة شيفرة بايثون، ولكنها تسمح لنا بالتعامل مع جميع أنواع المشاكل والاطلاع على المكونات الداخلية لفهمها.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_49" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> cond</span><span class="pun">.</span><span class="pln">__code__</span><span class="pun">.</span><span class="pln">co_code  </span><span class="com"># الوصول إلى البايت كود الخام للدالة على شكل بايتات</span><span class="pln">
b</span><span class="str">'d\x01\x00}\x00\x00|\x00\x00d\x02\x00k\x00\x00r\x16\x00d\x03\x00Sd\x04\x00Sd\x00
   \x00S'</span><span class="pln">                  </span><span class="com"># هذه هي البايتات نفسها التي ينفّذها مفسّر بايثون</span><span class="pln">

</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> list</span><span class="pun">(</span><span class="pln">cond</span><span class="pun">.</span><span class="pln">__code__</span><span class="pun">.</span><span class="pln">co_code</span><span class="pun">)</span><span class="pln">  </span><span class="com"># تحويل نفس البايتات إلى قائمة أعداد لقراءتها بسهولة</span><span class="pln">
</span><span class="pun">[</span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">125</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">124</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">107</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">114</span><span class="pun">,</span><span class="pln"> </span><span class="lit">22</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">83</span><span class="pun">,</span><span class="pln"> 
 </span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="lit">4</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">83</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">83</span><span class="pun">]</span><span class="pln">  </span><span class="com"># كل رقم يمثّل جزءًا من تعليمة أو وسيطها داخل البايت كود</span></pre>

<p>
	يُعَد البايت كود غير مفهوم، إذ لا يمكننا تمييز إلّا سلسلة من البايتات، ولكن توجد أداة يمكننا استخدامها لفهمها، والتي هي وحدة <code>dis</code> في مكتبة بايثون المعيارية، حيث تمثل هذه الوحدة مفككًا للبايت كود الذي يأخذ شيفرة منخفضة المستوى ومكتوبة للأجهزة مثل شيفرة التجميع أو البايت كود، ويطبعها بطريقة يمكن أن يقرأها الإنسان، فمثلًا يؤدي تشغيل <code>dis.dis</code> إلى عرض شرحٍ للبايت كود الممرَّر إليه.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5407_9" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> dis</span><span class="pun">.</span><span class="pln">dis</span><span class="pun">(</span><span class="pln">cond</span><span class="pun">)</span><span class="pln">  </span><span class="com"># عرض البايت كود بصيغة مقروءة (تعليمات + عناوين)</span><span class="pln">

  </span><span class="lit">2</span><span class="pln">           </span><span class="lit">0</span><span class="pln"> LOAD_CONST               </span><span class="lit">1</span><span class="pln"> </span><span class="pun">(</span><span class="lit">3</span><span class="pun">)</span><span class="pln">      </span><span class="com"># تحميل الثابت رقم 1 (القيمة 3)</span><span class="pln">
              </span><span class="lit">3</span><span class="pln"> STORE_FAST               </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(</span><span class="pln">x</span><span class="pun">)</span><span class="pln">      </span><span class="com"># تخزين القيمة في المتغير المحلي رقم 0 (x)</span><span class="pln">

  </span><span class="lit">3</span><span class="pln">           </span><span class="lit">6</span><span class="pln"> LOAD_FAST                </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(</span><span class="pln">x</span><span class="pun">)</span><span class="pln">      </span><span class="com"># x تحميل قيمة المتغير المحلي </span><span class="pln">
              </span><span class="lit">9</span><span class="pln"> LOAD_CONST               </span><span class="lit">2</span><span class="pln"> </span><span class="pun">(</span><span class="lit">5</span><span class="pun">)</span><span class="pln">      </span><span class="com"># تحميل الثابت رقم 2 (القيمة 5)</span><span class="pln">
             </span><span class="lit">12</span><span class="pln"> COMPARE_OP               </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(&lt;)</span><span class="pln">      </span><span class="com"># (&lt;) مقارنة القيمتين باستخدام العامل</span><span class="pln">
             </span><span class="lit">15</span><span class="pln"> POP_JUMP_IF_FALSE       </span><span class="lit">22</span><span class="pln">          </span><span class="com"># إذا كانت نتيجة الشرط غير صحيحة اقفز إلى العنوان 22</span><span class="pln">

  </span><span class="lit">4</span><span class="pln">          </span><span class="lit">18</span><span class="pln"> LOAD_CONST               </span><span class="lit">3</span><span class="pln"> </span><span class="pun">(</span><span class="str">'yes'</span><span class="pun">)</span><span class="pln">  </span><span class="com"># 'yes' تحميل الثابت </span><span class="pln">
             </span><span class="lit">21</span><span class="pln"> RETURN_VALUE                        </span><span class="com"># إرجاع القيمة</span><span class="pln">

  </span><span class="lit">6</span><span class="pln">     </span><span class="pun">&gt;&gt;</span><span class="pln">   </span><span class="lit">22</span><span class="pln"> LOAD_CONST               </span><span class="lit">4</span><span class="pln"> </span><span class="pun">(</span><span class="str">'no'</span><span class="pun">)</span><span class="pln">   </span><span class="com"># 'no' تحميل الثابت</span><span class="pln">
             </span><span class="lit">25</span><span class="pln"> RETURN_VALUE                        </span><span class="com"># إرجاع القيمة</span><span class="pln">
             </span><span class="lit">26</span><span class="pln"> LOAD_CONST               </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">None</span><span class="pun">)</span><span class="pln">   </span><span class="com"># قيمة افتراضية عند عدم الإرجاع صراحة</span><span class="pln">
             </span><span class="lit">29</span><span class="pln"> RETURN_VALUE                        </span></pre>

<p>
	لنوضح التعليمة الأولى <code>LOAD_CONST</code> كمثال، حيث يمثل العدد في العمود الأول (2) رقم السطر في شيفرة بايثون المصدرية الخاصة بنا، ويمثل العمود الثاني فهرسًا إلى البايت كود يخبرنا أن التعليمة <code>LOAD_CONST</code> تظهر في الموضع صفر، ويمثل العمود الثالث التعليمة مع ربطها باسمها القابل للقراءة. يمثل العمود الرابع -إن وجد- وسيط هذه التعليمة، والعمود الخامس -إن وجد- هو تلميح حول ما يعنيه الوسيط.
</p>

<p>
	تمثل البايتات الستة الأولى من البايت كود‎ <code>[100, 1, 0, 125, 0, 0]</code> تعليمتين مع وسطائهما، ويمكننا استخدام <code>dis.opname</code> التي تربط البايتات مع سلاسل نصية مفهومة لمعرفة ما تعنيه التعليمتان 100 و 125 كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_53" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> dis</span><span class="pun">.</span><span class="pln">opname</span><span class="pun">[</span><span class="lit">100</span><span class="pun">]</span><span class="pln">        </span><span class="com"># إرجاع اسم التعليمة المقابل لرقم العملية 100</span><span class="pln">
</span><span class="str">'LOAD_CONST'</span><span class="pln">               </span><span class="com"># الرقم 100 يعني: تحميل ثابت</span><span class="pln">

</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> dis</span><span class="pun">.</span><span class="pln">opname</span><span class="pun">[</span><span class="lit">125</span><span class="pun">]</span><span class="pln">        </span><span class="com"># إرجاع اسم التعليمة المقابل لرقم العملية 125</span><span class="pln">
</span><span class="str">'STORE_FAST'</span><span class="pln">               </span><span class="com"># الرقم 125 يعني: تخزين قيمة في متغير محلي</span></pre>

<p>
	يمثل البايتان الثاني والثالث ‎1 و 0‎ وسطاء التعليمة <code>LOAD_CONST</code>، ويمثل البايتان الخامس والسادس ‎0 و 0‎ وسطاء التعليمة <code>STORE_FAST</code>، إذ تحتاج التعليمة <code>LOAD_CONST</code> إلى معرفة مكان العثور على الثابت لتحميله، وتحتاج التعليمة <code>STORE_FAST</code> إلى العثور على الاسم لتخزينه. تمثل هذه البايتات الستة السطر الأول من الشيفرة البرمجية <code>x = 3</code>، حيث تقابل التعليمة <code>LOAD_CONST</code> في بايثون التعليمة <code>LOAD_VALUE</code> في المفسر السابق، وتقابل التعليمة <code>LOAD_FAST</code> التعليمة <code>LOAD_NAME</code>.
</p>

<p>
	يُستخدَم بايتان لكل وسيط، لأنه إذا استخدم بايثون بايتًا واحدًا فقط لتحديد مكان الثوابت والأسماء بدلًا من استخدام بايتين، فلن نتمكن إلا من الحصول على 256 اسمٍ أو ثابت مرتبط بكائن شيفرة واحد، بينما يمكن الحصول على ما يصل إلى مربع العدد 256 أو 65536 عند استخدام بايتين.
</p>

<h3 id="-">
	التعليمات الشرطية والحلقات
</h3>

<p>
	نفّذَ المفسر حتى الآن الشيفرة البرمجية ببساطة من خلال تنفيذ التعليمات واحدة تلو الأخرى، ولكن قد يشكّل ذلك مشكلة، لأننا في أغلب الاحيان نريد تكرار تنفيذ تعليمات معينة، أو قد نرغب في تخطي بعض التعليمات في شروط محددة، لذا يجب أن يكون المفسر قادرًا على التنقل عبر مجموعة التعليمات للسماح بكتابة الحلقات والتعليمات الشرطية <code>if</code> في الشيفرة البرمجية، حيث يتعامل بايثون مع الحلقات والتعليمات الشرطية باستخدام تعليمات <code>GOTO</code> في البايت كود. لنطلع الآن على تفكيك الدالة <code>cond</code> مرة أخرى:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5407_11" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> dis</span><span class="pun">.</span><span class="pln">dis</span><span class="pun">(</span><span class="pln">cond</span><span class="pun">)</span><span class="pln">  </span><span class="com"># عرض تعليمات الدالة بصيغة مقروءة (العنوان + اسم التعليمة + الوسيط)</span><span class="pln">

  </span><span class="lit">2</span><span class="pln">           </span><span class="lit">0</span><span class="pln"> LOAD_CONST               </span><span class="lit">1</span><span class="pln"> </span><span class="pun">(</span><span class="lit">3</span><span class="pun">)</span><span class="pln">      </span><span class="com"># تحميل الثابت 3 إلى المكدّس</span><span class="pln">
              </span><span class="lit">3</span><span class="pln"> STORE_FAST               </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(</span><span class="pln">x</span><span class="pun">)</span><span class="pln">      </span><span class="com"># x تخزين القيمة في المتغير المحلي </span><span class="pln">

  </span><span class="lit">3</span><span class="pln">           </span><span class="lit">6</span><span class="pln"> LOAD_FAST                </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(</span><span class="pln">x</span><span class="pun">)</span><span class="pln">      </span><span class="com"># تحميل قيمة x إلى المكدّس</span><span class="pln">
              </span><span class="lit">9</span><span class="pln"> LOAD_CONST               </span><span class="lit">2</span><span class="pln"> </span><span class="pun">(</span><span class="lit">5</span><span class="pun">)</span><span class="pln">      </span><span class="com"># تحميل الثابت 5 إلى المكدّس</span><span class="pln">
             </span><span class="lit">12</span><span class="pln"> COMPARE_OP               </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(&lt;)</span><span class="pln">      </span><span class="com"># مقارنة: هل x أصغر من 5؟</span><span class="pln">
             </span><span class="lit">15</span><span class="pln"> POP_JUMP_IF_FALSE       </span><span class="lit">22</span><span class="pln">          </span><span class="com"># إذا كانت المقارنة خطأ انتقل إلى العنوان 22</span><span class="pln">

  </span><span class="lit">4</span><span class="pln">          </span><span class="lit">18</span><span class="pln"> LOAD_CONST               </span><span class="lit">3</span><span class="pln"> </span><span class="pun">(</span><span class="str">'yes'</span><span class="pun">)</span><span class="pln">  </span><span class="com"># تحميل 'yes' (فرع if)</span><span class="pln">
             </span><span class="lit">21</span><span class="pln"> RETURN_VALUE                        </span><span class="com"># إرجاع القيمة وإنهاء الدالة</span><span class="pln">

  </span><span class="lit">6</span><span class="pln">     </span><span class="pun">&gt;&gt;</span><span class="pln">   </span><span class="lit">22</span><span class="pln"> LOAD_CONST               </span><span class="lit">4</span><span class="pln"> </span><span class="pun">(</span><span class="str">'no'</span><span class="pun">)</span><span class="pln">   </span><span class="com"># تحميل 'no' (فرع else)</span><span class="pln">
             </span><span class="lit">25</span><span class="pln"> RETURN_VALUE                        </span><span class="com"># إرجاع القيمة وإنهاء الدالة</span><span class="pln">
             </span><span class="lit">26</span><span class="pln"> LOAD_CONST               </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">None</span><span class="pun">)</span><span class="pln">   </span><span class="com"># تحميل None كقيمة افتراضية</span><span class="pln">
             </span><span class="lit">29</span><span class="pln"> RETURN_VALUE                        </span><span class="com"># إرجاع None (مسار افتراضي/احتياطي)</span></pre>

<p>
	تُصرَّف التعليمة الشرطية ‎<code>if x &lt; 5</code>‎ في السطر 3 من الشيفرة البرمجية إلى أربع تعليمات هي: <code>LOAD_FAST</code> و <code>LOAD_CONST</code> و <code>COMPARE_OP</code> و <code>POP_JUMP_IF_FALSE</code>، حيث يولد الشرط ‎<code>x &lt; 5</code>‎ الشيفرة البرمجية لتحميل <code>x</code> وتحميل القيمة 5 ومقارنة القيمتين. تكون التعليمة <code>POP_JUMP_IF_FALSE</code> مسؤولة عن تنفيذ تعليمة <code>if</code>، إذ تؤدي هذه التعليمة إلى سحب القيمة العليا من مكدس المفسر، حيث إذا كانت القيمة صحيحة، فلن يحدث شيء، ويمكن أن تكون القيمة صحيحة دون أن تكون كائن <code>True</code> حرفيًا، بينما إذا كانت القيمة خاطئة، فسينتقل المفسر إلى تعليمة أخرى.
</p>

<p>
	تسمى التعليمة التي يجب الوصول إليها بهدف عملية القفز Jump Target، وتكون وسيطًا للتعليمة <code>POP_JUMP</code>، فهدف القفز هو 22 في مثالنا، والتعليمة ذات الفهرس 22 هي <code>LOAD_CONST</code> عند السطر 6، حيث تضع وحدة <code>dis</code> الرمز <code>&lt;&lt;</code> على أهداف القفز. إذا كانت نتيجة الشرط ‎<code>x &lt; 5</code>‎ خاطئة، فسيقفز المفسر إلى السطر 6 مباشرةً <code>return "no"</code>‎‎ مع تجاوز السطر 4 الذي هو ‎<code>return "yes"</code>، وبالتالي يستخدم المفسر تعليمات القفز للتخطي الانتقائي لأجزاء من مجموعة التعليمات.
</p>

<p>
	تعتمد حلقات Loops بايثون على القفز، فمثلًا يولّد السطر <code>while x &lt; 5</code> في البايت كود التالي بايت كود متطابقًا تقريبًا مع بايت كود التعليمة <code>if x &lt; 10</code>، حيث تُطبَّق المقارنة في كلتا الحالتين ثم تتحكم التعليمة <code>POP_JUMP_IF_FALSE</code> بالتعليمة التالية التي ستُنفَّذ. ترسل التعليمة <code>JUMP_ABSOLUTE</code> في نهاية السطر 4 الذي يمثل نهاية جسم الحلقة دائمًا المفسر مرة أخرى إلى التعليمة 9 في أعلى الحلقة، وتؤدي التعليمة <code>POP_JUMP_IF_FALSE</code> إلى قفز المفسر إلى ما بعد نهاية الحلقة إلى التعليمة 34 عندما يصبح الشرط <code>x &lt; 5</code>‎ خاطئًا.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5407_13" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">def</span><span class="pln"> loop</span><span class="pun">():</span><span class="pln">
</span><span class="pun">...</span><span class="pln">      x </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">                  
</span><span class="pun">...</span><span class="pln">      </span><span class="kwd">while</span><span class="pln"> x </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">5</span><span class="pun">:</span><span class="pln">          
</span><span class="pun">...</span><span class="pln">          x </span><span class="pun">=</span><span class="pln"> x </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pln">             
</span><span class="pun">...</span><span class="pln">      </span><span class="kwd">return</span><span class="pln"> x            
</span><span class="pun">...</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> dis</span><span class="pun">.</span><span class="pln">dis</span><span class="pun">(</span><span class="pln">loop</span><span class="pun">)</span><span class="pln">  </span><span class="com"># عرض تعليمات الدالة بصيغة مقروءة</span><span class="pln">

  </span><span class="lit">2</span><span class="pln">           </span><span class="lit">0</span><span class="pln"> LOAD_CONST               </span><span class="lit">1</span><span class="pln"> </span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">      </span><span class="com"># تحميل الثابت 1 إلى المكدّس</span><span class="pln">
              </span><span class="lit">3</span><span class="pln"> STORE_FAST               </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(</span><span class="pln">x</span><span class="pun">)</span><span class="pln">      </span><span class="com"># x تخزينه في المتغير المحلي </span><span class="pln">

  </span><span class="lit">3</span><span class="pln">           </span><span class="lit">6</span><span class="pln"> SETUP_LOOP              </span><span class="lit">26</span><span class="pln"> </span><span class="pun">(</span><span class="pln">to </span><span class="lit">35</span><span class="pun">)</span><span class="pln">  </span><span class="com"># تجهيز إطار الحلقة (بداية/نهاية ومسار الخروج)</span><span class="pln">
        </span><span class="pun">&gt;&gt;</span><span class="pln">    </span><span class="lit">9</span><span class="pln"> LOAD_FAST                </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(</span><span class="pln">x</span><span class="pun">)</span><span class="pln">      </span><span class="com"># تحميل x لفحص شرط الحلقة</span><span class="pln">
             </span><span class="lit">12</span><span class="pln"> LOAD_CONST               </span><span class="lit">2</span><span class="pln"> </span><span class="pun">(</span><span class="lit">5</span><span class="pun">)</span><span class="pln">      </span><span class="com"># تحميل الثابت 5</span><span class="pln">
             </span><span class="lit">15</span><span class="pln"> COMPARE_OP               </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(&lt;)</span><span class="pln">      </span><span class="com"># مقارنة: هل x أصغر من 5؟</span><span class="pln">
             </span><span class="lit">18</span><span class="pln"> POP_JUMP_IF_FALSE       </span><span class="lit">34</span><span class="pln">          </span><span class="com"># إذا كان الشرط غير صحيح اقفز إلى نهاية الحلقة (العنوان 34)</span><span class="pln">

  </span><span class="lit">4</span><span class="pln">          </span><span class="lit">21</span><span class="pln"> LOAD_FAST                </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(</span><span class="pln">x</span><span class="pun">)</span><span class="pln">      </span><span class="com"># تحميل x لتنفيذ جسم الحلقة</span><span class="pln">
             </span><span class="lit">24</span><span class="pln"> LOAD_CONST               </span><span class="lit">1</span><span class="pln"> </span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">      </span><span class="com"># تحميل الثابت 1</span><span class="pln">
             </span><span class="lit">27</span><span class="pln"> BINARY_ADD                          </span><span class="com"># (x + 1) جمع القيمتين </span><span class="pln">
             </span><span class="lit">28</span><span class="pln"> STORE_FAST               </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(</span><span class="pln">x</span><span class="pun">)</span><span class="pln">      </span><span class="com"># x تخزين الناتج في </span><span class="pln">
             </span><span class="lit">31</span><span class="pln"> JUMP_ABSOLUTE            </span><span class="lit">9</span><span class="pln">          </span><span class="com"># الرجوع لبداية فحص الشرط (العنوان 9)</span><span class="pln">
        </span><span class="pun">&gt;&gt;</span><span class="pln">   </span><span class="lit">34</span><span class="pln"> POP_BLOCK                          </span><span class="com"># إنهاء إطار الحلقة بعد الخروج منها</span><span class="pln">

  </span><span class="lit">5</span><span class="pln">     </span><span class="pun">&gt;&gt;</span><span class="pln">   </span><span class="lit">35</span><span class="pln"> LOAD_FAST                </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(</span><span class="pln">x</span><span class="pun">)</span><span class="pln">      </span><span class="com"># تحميل x بعد انتهاء الحلقة</span><span class="pln">
             </span><span class="lit">38</span><span class="pln"> RETURN_VALUE                        </span><span class="com"># x إرجاع </span></pre>

<h3 id="-">
	استكشاف البايت كود
</h3>

<p>
	يمكننا تجربة تشغيل <code>dis.dis</code> مع الدوال التي نكتبها للإجابة عن الأسئلة التالية:
</p>

<ul>
	<li>
		ما الفرق بين حلقة for وحلقة while بالنسبة لمفسر بايثون؟
	</li>
	<li>
		كيف يمكننا كتابة دوال مختلفة تولد بايت كود متطابق؟
	</li>
	<li>
		كيف تعمل تعليمة <code>elif</code>، وما هو استيعاب القوائم List Comprehensions؟
	</li>
</ul>

<h2 id="-frames">
	الإطارات Frames
</h2>

<p>
	تعلمنا حتى الآن أن آلة بايثون الافتراضية هي آلة مكدس، لأنها تتنقل وتقفز عبر التعليمات مع دفع وسحب القيم من المكدس وإليه، ولكن لا تزال هناك بعض الأمور الغامضة مثل التعليمة الأخيرة <code>RETURN_VALUE</code> التي تقابل تعليمة <code>return</code> في الشيفرة البرمجية، إذ لا نعرف المكان الذي تعود إليه هذه التعليمة.
</p>

<p>
	نحتاج إلى إضافة طبقةٍ من التعقيد تتمثل في الإطار Frame لمعرفة المكان الذي تعود إليه التعليمة <code>RETURN_VALUE</code>، فالإطار هو مجموعة من المعلومات والسياق لجزء من الشيفرة البرمجية، وينشأ ويُدمَّر أثناء تنفيذ شيفرة بايثون الخاصة بنا. يوجد إطار واحد يقابل كل استدعاء لدالة، وبالتالي يمكن لكائن الشيفرة أن يكون له العديد من الإطارات بينما يكون لكل إطار كائن شيفرة واحد. إذا كان لدينا دالة تستدعي نفسها تعاوديًا عشر مرات، فسيكون لدينا أحد عشر إطارًا، إطار لكل مستوى من التعاود وإطار للوحدة التي بدأنا منها. يوجد إطار لكل مجال في برنامج بايثون، إذ يوجد مثلًا إطار لكل وحدة وإطار لكل استدعاء دالة وإطار لكل تعريف صنف.
</p>

<p>
	توجد الإطارات في مكدس الاستدعاءات Call Stack الذي هو مكدس مختلف تمامًا عن المكدس الذي تحدثنا عنه سابقًا، فمكدس الاستدعاءات هو مكدسٌ رأيناه مسبقًا في عمليات التتبع العكسي Tracebacks للاستثناءات، إذ يقابل كل سطر في عملية التتبع العكسي يبدأ بعبارة ‎<code>"File 'program.py', line 10"</code>‎‎ إطارًا واحدًا في مكدس الاستدعاءات. نسمي المكدس الذي يتعامل معه المفسر أثناء تنفيذ البايت كود بمكدس البيانات Data Stack. يوجد مكدس ثالث اسمه مكدس الكتل Block Stack، حيث تُستخدَم الكتل لأنواع معينة من تدفق التحكم مثل التكرار ضمن حلقة ومعالجة الاستثناءات، ويكون لكل إطارٍ في مكدس الاستدعاءات مكدس بيانات ومكدس كتل خاص به.
</p>

<p>
	لنفترض مثلًا أن مفسر بايثون ينفّذ حاليًا السطر المحدد بالرقم 3، حيث يكون المفسر عند استدعاء الدالة <code>foo</code> التي تستدعي الدالة <code>bar</code> بدورها. يوضح الرسم البياني الآتي مخطط مكدس الاستدعاءات للإطارات ومكدسات الكتل ومكدسات البيانات، وتعَد هذه الشيفرة البرمجية مكتوبة بطريقة مشابهة لجلسة REPL، لذلك جرى تعريف الدوال المطلوبة أولًا. ينفذ المفسر حاليًا الدالة <code>foo()‎</code>‎ التي تصل بعد ذلك إلى جسم الدالة <code>foo</code> ثم إلى <code>bar</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5407_15" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">def</span><span class="pln"> bar</span><span class="pun">(</span><span class="pln">y</span><span class="pun">):</span><span class="pln">
</span><span class="pun">...</span><span class="pln">     z </span><span class="pun">=</span><span class="pln"> y </span><span class="pun">+</span><span class="pln"> </span><span class="lit">3</span><span class="pln">             </span><span class="com"># (3) داخل الدالة bar: حساب z بجمع y مع 3</span><span class="pln">
</span><span class="pun">...</span><span class="pln">     </span><span class="kwd">return</span><span class="pln"> z              
</span><span class="pun">...</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">def</span><span class="pln"> foo</span><span class="pun">():</span><span class="pln">
</span><span class="pun">...</span><span class="pln">     a </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">                 
</span><span class="pun">...</span><span class="pln">     b </span><span class="pun">=</span><span class="pln"> </span><span class="lit">2</span><span class="pln">                 
</span><span class="pun">...</span><span class="pln">     </span><span class="kwd">return</span><span class="pln"> a </span><span class="pun">+</span><span class="pln"> bar</span><span class="pun">(</span><span class="pln">b</span><span class="pun">)</span><span class="pln">     </span><span class="com"># (2) </span><span class="pln">
</span><span class="pun">...</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> foo</span><span class="pun">()</span><span class="pln">                     </span><span class="com"># (1) استدعاء الدالة foo لتنفيذ ما بداخلها</span><span class="pln">
</span><span class="lit">3</span><span class="pln">                             </span><span class="com"># foo الناتج النهائي بعد تنفيذ</span></pre>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="181522" href="https://academy.hsoub.com/uploads/monthly_2026_03/002___.png.74b33ee166344ef5e169cb02ac1713f8.png" rel=""><img alt="002_مكدس_استدعاءات_المفسر.png" class="ipsImage ipsImage_thumbnailed" data-fileid="181522" data-ratio="40.67" data-unique="0wwmaf6fa" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2026_03/002___.thumb.png.e3afab3eb62b33f85a3880f9d962f62a.png"></a><span id="cke_bm_3420E" style="display: none;"> </span>
</p>

<p>
	يكون المفسر حاليًا عند استدعاء الدالة <code>bar</code>، وتوجد ثلاثة إطارات في مكدس الاستدعاءات هي: إطار على مستوى الوحدة، وإطار للدالة <code>foo</code>، وإطار للدالة <code>bar</code> كما هو موضح في الشكل السابق، حيث تعود الدالة <code>bar</code> ثم يُخرَج ويُهمَل الإطار المرتبط بها من مكدس الاستدعاءات. تخبر تعليمة البايت كود <code>RETURN_VALUE</code> المفسرَ بتمرير قيمةٍ بين الإطارات، حيث يخرِج أولًا القيمة العليا من مكدس البيانات الخاص بالإطار العلوي من مكدس الاستدعاءات، ثم يخرِج الإطار بالكامل من مكدس الاستدعاءات ويتخلص منه، وتُدفَع أخيرًا القيمة إلى مكدس البيانات في الإطار التالي.
</p>

<p>
	واجه مطورو مفسر Byterun لفترة طويلة خطأً كبيرًا في التنفيذ، إذ كان يوجد مكدس بيانات واحد فقط على الآلة الافتراضية بالكامل بدلًا من وجود مكدس بيانات لكل إطار، حيث أُجريت كثير من الاختبارات المكوَّنة من مقاطع شيفرة بايثون والمنفَّذة باستخدام Byterun ومفسر بايثون الحقيقي للتأكد من حدوث الشيء نفسه عليهما. نجحت جميع هذه الاختبارات تقريبًا باستثناء دوال التوليد Generators، حيث اُكتشِف الخطأ أخيرًا عند قراءة شيفرة CPython بعناية، ولكن أدى استخدام مكدس بيانات لكل إطار إلى حل المشكلة.
</p>

<p>
	لا تعتمد بايثون على أن يكون لكل إطار مكدس بيانات، إذ تنظّف جميعُ العمليات تقريبًا في مفسّر بايثون مكدسَ البيانات بعناية، لذا لم يكن مهمًا أن تشترك الإطارات في المكدس نفسه، حيث سيكون مكدس البيانات الخاص بالدالة <code>bar</code> فارغًا في المثال السابق عند انتهاء تنفيذها. ستكون القيم أقل حتى إذا تشاركت <code>foo</code> في المكدس نفسه، ولكن تكمن الميزة الرئيسية لدوال التوليد في القدرة على إيقاف إطار مؤقتًا والعودة إلى إطار آخر ثم العودة إلى إطار الدالة المولدة لاحقًا بحيث يكون في الحالة نفسها تمامًا التي تركناه عليها.
</p>

<h2 id="-byterun">
	مفسر Byterun
</h2>

<p>
	تعرّفنا على بعض المفاهيم المتعلقة بمفسر بايثون ولنبدأ الآن بمفسر Byterun، حيث توجد أربعة أنواع من الكائنات في Byterun وهي:
</p>

<ul>
	<li>
		الصنف <code>VirtualMachine</code>: يدير البنية ذات المستوى الأعلى وخاصة مكدس استدعاءات الإطارات، ويحتوي على ربط بين التعليمات والعمليات، حيث يُعَد نسخة أكثر تعقيدًا من الكائن <code>Intepreter</code>.
	</li>
	<li>
		الصنف <code>Frame</code>: تحتوي كل نسخة من هذا الصنف على كائن شيفرة واحد، وتدير عددًا من بتات الحالة الضرورية الأخرى وخاصة مساحات الأسماء المحلية والعامة ومرجعًا للإطار المستدعِي وتعليمة البايت كود الأخيرة المُنفَّذة.
	</li>
	<li>
		الصنف <code>Function</code>: يُستخدَم بدلًا من دوال بايثون الحقيقية، حيث ينشئ استدعاء دالة إطارًا جديدًا في المفسر، ونُفِّذ هذا الصنف للتحكم في إنشاء إطارات جديدة.
	</li>
	<li>
		الصنف <code>Block</code>: يغلِّف سمات Attributes الكتل، ولا تُعَد تفاصيل الكتل أساسية بالنسبة لمفسر بايثون، لذا لن نخوض بتفاصيلها، ولكنها مُدرجَة في هذا المقال حتى يتمكّن مفسر Byterun من تشغيل شيفرة بايثون الحقيقية.
	</li>
</ul>

<h3 id="-virtualmachine">
	الصنف VirtualMachine
</h3>

<p>
	تنشأ نسخة واحدة فقط من الصنف <code>VirtualMachine</code> عند كل تشغيل للبرنامج بسبب وجود مفسر بايثون واحد فقط، ويخزّن هذا الصنف مكدس الاستدعاءات وحالة الاستثناءات والقيم المُعادة أثناء تمريرها بين الإطارات. يبدأ تنفيذ الشيفرة البرمجية من التابع <code>run_code</code> الذي يأخذ كائن شيفرة مُصرَّف كوسيط، ويبدأ من خلال إعداد إطار وتشغيله، وقد ينشئ هذا الإطار إطارات أخرى، إذ سيكبر مكدس الاستدعاءات ويصغر عند تنفيذ البرنامج، وينتهي التنفيذ عند إعادة الإطار الأول.<code> </code>
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_69" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">VirtualMachine</span><span class="pun">(</span><span class="pln">object</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> __init__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">frames </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">   </span><span class="com"># مكدس استدعاءات الإطارات</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">frame </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">  </span><span class="com"># الإطار الحالي</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">return_value </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">last_exception </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> run_code</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> code</span><span class="pun">,</span><span class="pln"> global_names</span><span class="pun">=</span><span class="kwd">None</span><span class="pun">,</span><span class="pln"> local_names</span><span class="pun">=</span><span class="kwd">None</span><span class="pun">):</span><span class="pln">
        </span><span class="str">""" An entry point to execute code using the virtual machine."""</span><span class="pln">
        frame </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">make_frame</span><span class="pun">(</span><span class="pln">code</span><span class="pun">,</span><span class="pln"> global_names</span><span class="pun">=</span><span class="pln">global_names</span><span class="pun">,</span><span class="pln"> 
                                local_names</span><span class="pun">=</span><span class="pln">local_names</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">run_frame</span><span class="pun">(</span><span class="pln">frame</span><span class="pun">)</span></pre>

<h3 id="-frame">
	الصنف Frame
</h3>

<p>
	الإطار هو مجموعة من السمات بدون توابع، وتتضمن هذه السمات كائن الشيفرة الذي أنشأه المصرف ومساحات الأسماء المحلية والعامة والمضمَّنة ومرجعًا إلى الإطار السابق ومكدس بيانات ومكدس كتل والتعليمة الأخيرة المنفَّذة. يحتاج الوصول إلى مساحة الأسماء المضمنة إلى عمل إضافي لأن بايثون يعامل هذه المساحة بطريقة مختلفة لكل وحدة، ولكن لا تُعَد هذه التفاصيل مهمة للآلة الافتراضية.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_71" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Frame</span><span class="pun">(</span><span class="pln">object</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> __init__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> code_obj</span><span class="pun">,</span><span class="pln"> global_names</span><span class="pun">,</span><span class="pln"> local_names</span><span class="pun">,</span><span class="pln"> prev_frame</span><span class="pun">):</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">code_obj </span><span class="pun">=</span><span class="pln"> code_obj              </span><span class="com"># كائن الكود الذي سيتم تنفيذه</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">global_names </span><span class="pun">=</span><span class="pln"> global_names      </span><span class="com"># مساحة الأسماء العامة</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">local_names </span><span class="pun">=</span><span class="pln"> local_names        </span><span class="com"># مساحة الأسماء المحلية</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">prev_frame </span><span class="pun">=</span><span class="pln"> prev_frame          </span><span class="com"># الإطار السابق (للاستدعاءات المتداخلة)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">stack </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">                       </span><span class="com"># مكدّس القيم أثناء التنفيذ</span><span class="pln">

        </span><span class="kwd">if</span><span class="pln"> prev_frame</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># وراثة مساحة الأسماء المضمنة من الإطار السابق</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">builtin_names </span><span class="pun">=</span><span class="pln"> prev_frame</span><span class="pun">.</span><span class="pln">builtin_names
        </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># في أول إطار: أخذ مساحة الأسماء المضمنة من __builtins__</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">builtin_names </span><span class="pun">=</span><span class="pln"> local_names</span><span class="pun">[</span><span class="str">'__builtins__'</span><span class="pun">]</span><span class="pln">
            </span><span class="com"># إذا كانت كائنًا يحتوي قاموس أسماء، نستخدم قاموسه مباشرة</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> hasattr</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">builtin_names</span><span class="pun">,</span><span class="pln"> </span><span class="str">'__dict__'</span><span class="pun">):</span><span class="pln">
                self</span><span class="pun">.</span><span class="pln">builtin_names </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">builtin_names</span><span class="pun">.</span><span class="pln">__dict__

        self</span><span class="pun">.</span><span class="pln">last_instruction </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pln">             </span><span class="com"># مؤشر آخر تعليمة تم تنفيذها</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">block_stack </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">                 </span><span class="com"># مكدّس الكتل (مثل الحلقات/الاستثناءات) لإدارة القفزات والخروج</span></pre>

<p>
	نحتاج بعد ذلك إلى إضافة معالجةٍ للإطارات إلى الآلة الافتراضية، حيث توجد ثلاث دوال مساعدة للإطارات هي: دالة إنشاء إطارات جديدة تفرز مساحات الأسماء للإطار الجديد، ودالة لدفع الإطارات، ودالة لسحبها من مكدس الإطارات. تنجز الدالة الرابعة <code>run_frame</code> العمل الأساسي المتمثل في تنفيذ الإطار، والذي سنشرحه بمزيدٍ من التفصيل لاحقًا.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_73" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">VirtualMachine</span><span class="pun">(</span><span class="pln">object</span><span class="pun">):</span><span class="pln">
    </span><span class="pun">[...</span><span class="pln"> snip </span><span class="pun">...]</span><span class="pln">

    </span><span class="com"># إنشاء إطار جديد للتنفيذ</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> make_frame</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> code</span><span class="pun">,</span><span class="pln"> callargs</span><span class="pun">={},</span><span class="pln"> global_names</span><span class="pun">=</span><span class="kwd">None</span><span class="pun">,</span><span class="pln"> local_names</span><span class="pun">=</span><span class="kwd">None</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># تحديد مساحات الأسماء التي سيعمل عليها هذا الإطار</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> global_names </span><span class="kwd">is</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> </span><span class="kwd">None</span><span class="pln"> </span><span class="kwd">and</span><span class="pln"> local_names </span><span class="kwd">is</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># عند تمريرهما معًا: اجعل المحلية نفس العامة (مساحة واحدة)</span><span class="pln">
            local_names </span><span class="pun">=</span><span class="pln"> global_names
        </span><span class="kwd">elif</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frames</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># إذا كان هناك إطار حالي: ورّث العامة منه وابدأ محلية جديدة</span><span class="pln">
            global_names </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">global_names
            local_names </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">
        </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># إذا كان هذا أول إطار: أنشئ مساحة أسماء ابتدائية</span><span class="pln">
            global_names </span><span class="pun">=</span><span class="pln"> local_names </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                </span><span class="str">'__builtins__'</span><span class="pun">:</span><span class="pln"> __builtins__</span><span class="pun">,</span><span class="pln">
                </span><span class="str">'__name__'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'__main__'</span><span class="pun">,</span><span class="pln">
                </span><span class="str">'__doc__'</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">,</span><span class="pln">
                </span><span class="str">'__package__'</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">,</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">

        </span><span class="com"># إدخال معاملات الاستدعاء داخل المحلية</span><span class="pln">
        local_names</span><span class="pun">.</span><span class="pln">update</span><span class="pun">(</span><span class="pln">callargs</span><span class="pun">)</span><span class="pln">

        </span><span class="com"># بناء الإطار وربطه بالإطار الحالي كإطار سابق</span><span class="pln">
        frame </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Frame</span><span class="pun">(</span><span class="pln">code</span><span class="pun">,</span><span class="pln"> global_names</span><span class="pun">,</span><span class="pln"> local_names</span><span class="pun">,</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> frame

    </span><span class="kwd">def</span><span class="pln"> push_frame</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> frame</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># دفع الإطار الجديد ليصبح الإطار الحالي</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">frames</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">frame</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">frame </span><span class="pun">=</span><span class="pln"> frame

    </span><span class="kwd">def</span><span class="pln"> pop_frame</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># إزالة الإطار الحالي والعودة للإطار السابق إن وُجد</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">frames</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frames</span><span class="pun">:</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">frame </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frames</span><span class="pun">[-</span><span class="lit">1</span><span class="pun">]</span><span class="pln">
        </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">frame </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> run_frame</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">pass</span><span class="pln">
        </span><span class="com"># تنفيذ الإطار (سنضيف منطق التنفيذ لاحقًا)</span></pre>

<h3 id="-function">
	الصنف Function
</h3>

<p>
	يُعَد تنفيذ الكائن <code>Function</code> معقدًا بعض الشيء، ولكن ليست معظم هذه التفاصيل ذات أهمية كبيرة لفهم المفسر، فالأمر المهم الذي يجب ملاحظته هو أن استدعاء دالة أو استدعاء التابع <code>__call__</code> يؤدي إلى إنشاء كائن <code>Frame</code> جديد وبدء تشغيله.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_75" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Function</span><span class="pun">(</span><span class="pln">object</span><span class="pun">):</span><span class="pln">
    </span><span class="str">"""
    إنشاء كائن دالة قريب من دوال بايثون الحقيقية، بالشكل الذي يتوقعه المفسّر.
    """</span><span class="pln">
    __slots__ </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
        </span><span class="str">'func_code'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'func_name'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'func_defaults'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'func_globals'</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'func_locals'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'func_dict'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'func_closure'</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'__name__'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'__dict__'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'__doc__'</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'_vm'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'_func'</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">]</span><span class="pln">  </span><span class="com"># تحديد الحقول المسموح بها لتقليل الذاكرة ومنع إضافة حقول عشوائية</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __init__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">,</span><span class="pln"> code</span><span class="pun">,</span><span class="pln"> globs</span><span class="pun">,</span><span class="pln"> defaults</span><span class="pun">,</span><span class="pln"> closure</span><span class="pun">,</span><span class="pln"> vm</span><span class="pun">):</span><span class="pln">
        </span><span class="str">"""لا تحتاج لفهم كل التفاصيل هنا لفهم المفسّر."""</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">_vm </span><span class="pun">=</span><span class="pln"> vm                          </span><span class="com"># مرجع للآلة الافتراضية التي ستنفّذ هذه الدالة</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">func_code </span><span class="pun">=</span><span class="pln"> code                  </span><span class="com"># كائن الكود الخاص بالدالة</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">func_name </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">__name__ </span><span class="pun">=</span><span class="pln"> name </span><span class="kwd">or</span><span class="pln"> code</span><span class="pun">.</span><span class="pln">co_name  </span><span class="com"># اسم الدالة (المعطى أو من كائن الكود)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">func_defaults </span><span class="pun">=</span><span class="pln"> tuple</span><span class="pun">(</span><span class="pln">defaults</span><span class="pun">)</span><span class="pln">   </span><span class="com"># القيم الافتراضية للوسطاء</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">func_globals </span><span class="pun">=</span><span class="pln"> globs              </span><span class="com"># مساحة الأسماء العامة</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">func_locals </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">_vm</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">f_locals  </span><span class="com"># مساحة الأسماء المحلية من الإطار الحالي</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">__dict__ </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">                     </span><span class="com"># قاموس خصائص إضافية للدالة</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">func_closure </span><span class="pun">=</span><span class="pln"> closure            </span><span class="com"># معلومات الإغلاق (إن وُجدت)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">__doc__ </span><span class="pun">=</span><span class="pln"> code</span><span class="pun">.</span><span class="pln">co_consts</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> code</span><span class="pun">.</span><span class="pln">co_consts </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">  </span><span class="com"># نص التوثيق إن وُجد</span><span class="pln">

        </span><span class="com"># إنشاء دالة بايثون "حقيقية" لنستفيد منها في ربط الوسطاء والتحقق من التواقيع</span><span class="pln">
        kw </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="str">'argdefs'</span><span class="pun">:</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">func_defaults</span><span class="pun">,</span><span class="pln">     </span><span class="com"># تمرير القيم الافتراضية</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> closure</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># تجهيز خلايا للإغلاق بشكل صوري عند الحاجة</span><span class="pln">
            kw</span><span class="pun">[</span><span class="str">'closure'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> tuple</span><span class="pun">(</span><span class="pln">make_cell</span><span class="pun">(</span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> _ </span><span class="kwd">in</span><span class="pln"> closure</span><span class="pun">)</span><span class="pln">

        self</span><span class="pun">.</span><span class="pln">_func </span><span class="pun">=</span><span class="pln"> types</span><span class="pun">.</span><span class="typ">FunctionType</span><span class="pun">(</span><span class="pln">code</span><span class="pun">,</span><span class="pln"> globs</span><span class="pun">,</span><span class="pln"> </span><span class="pun">**</span><span class="pln">kw</span><span class="pun">)</span><span class="pln">  </span><span class="com"># بناء كائن دالة بايثون من كائن الكود</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> __call__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> </span><span class="pun">*</span><span class="pln">args</span><span class="pun">,</span><span class="pln"> </span><span class="pun">**</span><span class="pln">kwargs</span><span class="pun">):</span><span class="pln">
        </span><span class="str">"""عند استدعاء الدالة: ننشئ إطارًا جديدًا وننفّذه."""</span><span class="pln">
        callargs </span><span class="pun">=</span><span class="pln"> inspect</span><span class="pun">.</span><span class="pln">getcallargs</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">_func</span><span class="pun">,</span><span class="pln"> </span><span class="pun">*</span><span class="pln">args</span><span class="pun">,</span><span class="pln"> </span><span class="pun">**</span><span class="pln">kwargs</span><span class="pun">)</span><span class="pln">  </span><span class="com"># ربط القيم بأسماء الوسطاء حسب التوقيع</span><span class="pln">
        </span><span class="com"># استخدام هذا الربط لتمرير الوسطاء إلى الإطار الجديد</span><span class="pln">
        frame </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">_vm</span><span class="pun">.</span><span class="pln">make_frame</span><span class="pun">(</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">func_code</span><span class="pun">,</span><span class="pln"> callargs</span><span class="pun">,</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">func_globals</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">
        </span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">_vm</span><span class="pun">.</span><span class="pln">run_frame</span><span class="pun">(</span><span class="pln">frame</span><span class="pun">)</span><span class="pln">       </span><span class="com"># تنفيذ الإطار وإرجاع الناتج</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> make_cell</span><span class="pun">(</span><span class="pln">value</span><span class="pun">):</span><span class="pln">
    </span><span class="str">"""إنشاء خلية إغلاق حقيقية (لاستخراج مرجع خلية من إغلاق بايثون)."""</span><span class="pln">
    </span><span class="com"># إنشاء دالة داخل دالة لإجبار بايثون على صنع خلية إغلاق</span><span class="pln">
    fn </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">lambda</span><span class="pln"> x</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">lambda</span><span class="pun">:</span><span class="pln"> x</span><span class="pun">)(</span><span class="pln">value</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> fn</span><span class="pun">.</span><span class="pln">__closure__</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln">                  </span><span class="com"># استخراج الخلية نفسها</span></pre>

<p>
	نعود الآن إلى كائن <code>VirtualMachine</code>، ونضيف بعض التوابع المساعدة لمعالجة مكدس البيانات، حيث يشغّل البايت كود الذي يعالج المكدس دائمًا مكدسَ بيانات الإطار الحالي، مما يؤدي إلى جعل تنفيذ تعليمة <code>POP_TOP</code> و <code>LOAD_FAST</code> وجميع التعليمات الأخرى التي تتعامل مع المكدس أكثر قابلية للقراءة.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5407_17" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">VirtualMachine</span><span class="pun">(</span><span class="pln">object</span><span class="pun">):</span><span class="pln">
    </span><span class="pun">[...</span><span class="pln"> snip </span><span class="pun">...]</span><span class="pln">

    </span><span class="com"># عمليات على مكدّس القيم</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> top</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># إرجاع آخر قيمة في المكدّس دون إزالتها</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">stack</span><span class="pun">[-</span><span class="lit">1</span><span class="pun">]</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> pop</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># إزالة آخر قيمة من المكدّس وإرجاعها</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">stack</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> push</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> </span><span class="pun">*</span><span class="pln">vals</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># إضافة قيمة أو أكثر إلى المكدّس</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">stack</span><span class="pun">.</span><span class="pln">extend</span><span class="pun">(</span><span class="pln">vals</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> popn</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> n</span><span class="pun">):</span><span class="pln">
        </span><span class="str">"""إزالة عدد من القيم من المكدّس وإرجاعها كقائمة."""</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> n</span><span class="pun">:</span><span class="pln">
            ret </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">stack</span><span class="pun">[-</span><span class="pln">n</span><span class="pun">:]</span><span class="pln">    </span><span class="com"># أخذ آخر n قيم</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">stack</span><span class="pun">[-</span><span class="pln">n</span><span class="pun">:]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">     </span><span class="com"># حذفها من المكدّس</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> ret
        </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># إذا كان صفرًا، نرجع قائمة فارغة</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">[]</span></pre>

<p>
	نحتاج إلى تابعين آخرين قبل تشغيل الإطار وهما: التابع الأول <code>parse_byte_and_args</code> الذي يأخذ بايت كود، ويتحقق من احتوائه على وسطاء، ويحلل هذه الوسطاء عند وجودها، ويحدّث هذا التابع سمة الإطار <code>last_instruction</code> التي هي مرجع إلى آخر تعليمة مُنفَّذة. يبلغ طول التعليمة بايتًا واحدًا عند عدم وجود وسيط، وثلاثة بايتات إذا كان لها وسيط بحيث يمثل البايتان الأخيران الوسيط. يعتمد معنى الوسيط لكل تعليمة على نوعها، فمثلًا يمثل وسيط التعليمة <code>POP_JUMP_IF_FALSE</code> هدف القفز، ويمثل وسيط التعليمة <code>BUILD_LIST</code> عدد العناصر في القائمة، ويمثل وسيط التعليمة <code>LOAD_CONST</code> فهرسًا في قائمة الثوابت.
</p>

<p>
	تستخدم بعض التعليمات أعدادًا بسيطة كوسطاء لها، بينما يجب على الآلة الافتراضية إنجاز عمل إضافي لاكتشاف معنى الوسطاء بالنسبة لتعليمات أخرى. تعطي وحدة <code>dis</code> في المكتبة المعيارية شرحًا يوضح معنى الوسطاء، مما يحسن من شيفرتنا البرمجية، فمثلًا تخبرنا القائمة <code>dis.hasname</code> أن الوسطاء الخاصة بالتعليمات <code>LOAD_NAME</code> و <code>IMPORT_NAME</code> و <code>LOAD_GLOBAL</code> وتسعة تعليمات أخرى لها المعنى نفسه، حيث يمثل وسيط هذه التعليمات فهرسًا في قائمة الأسماء الموجودة في كائن الشيفرة.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_79" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">VirtualMachine</span><span class="pun">(</span><span class="pln">object</span><span class="pun">):</span><span class="pln">
    </span><span class="pun">[...</span><span class="pln"> snip </span><span class="pun">...]</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> parse_byte_and_args</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        f </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frame
        opoffset </span><span class="pun">=</span><span class="pln"> f</span><span class="pun">.</span><span class="pln">last_instruction
        byteCode </span><span class="pun">=</span><span class="pln"> f</span><span class="pun">.</span><span class="pln">code_obj</span><span class="pun">.</span><span class="pln">co_code</span><span class="pun">[</span><span class="pln">opoffset</span><span class="pun">]</span><span class="pln">
        f</span><span class="pun">.</span><span class="pln">last_instruction </span><span class="pun">+=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
        byte_name </span><span class="pun">=</span><span class="pln"> dis</span><span class="pun">.</span><span class="pln">opname</span><span class="pun">[</span><span class="pln">byteCode</span><span class="pun">]</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> byteCode </span><span class="pun">&gt;=</span><span class="pln"> dis</span><span class="pun">.</span><span class="pln">HAVE_ARGUMENT</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># فهرس إلى البايت كود</span><span class="pln">
            arg </span><span class="pun">=</span><span class="pln"> f</span><span class="pun">.</span><span class="pln">code_obj</span><span class="pun">.</span><span class="pln">co_code</span><span class="pun">[</span><span class="pln">f</span><span class="pun">.</span><span class="pln">last_instruction</span><span class="pun">:</span><span class="pln">f</span><span class="pun">.</span><span class="pln">last_instruction</span><span class="pun">+</span><span class="lit">2</span><span class="pun">]</span><span class="pln">  
            f</span><span class="pun">.</span><span class="pln">last_instruction </span><span class="pun">+=</span><span class="pln"> </span><span class="lit">2</span><span class="pln">   </span><span class="com"># تقدّم مؤشر التعليمة</span><span class="pln">
            arg_val </span><span class="pun">=</span><span class="pln"> arg</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="pun">(</span><span class="pln">arg</span><span class="pun">[</span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="lit">256</span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> byteCode </span><span class="kwd">in</span><span class="pln"> dis</span><span class="pun">.</span><span class="pln">hasconst</span><span class="pun">:</span><span class="pln">   </span><span class="com"># البحث عن ثابت</span><span class="pln">
                arg </span><span class="pun">=</span><span class="pln"> f</span><span class="pun">.</span><span class="pln">code_obj</span><span class="pun">.</span><span class="pln">co_consts</span><span class="pun">[</span><span class="pln">arg_val</span><span class="pun">]</span><span class="pln">
            </span><span class="kwd">elif</span><span class="pln"> byteCode </span><span class="kwd">in</span><span class="pln"> dis</span><span class="pun">.</span><span class="pln">hasname</span><span class="pun">:</span><span class="pln">  </span><span class="com"># البحث عن اسم</span><span class="pln">
                arg </span><span class="pun">=</span><span class="pln"> f</span><span class="pun">.</span><span class="pln">code_obj</span><span class="pun">.</span><span class="pln">co_names</span><span class="pun">[</span><span class="pln">arg_val</span><span class="pun">]</span><span class="pln">
            </span><span class="kwd">elif</span><span class="pln"> byteCode </span><span class="kwd">in</span><span class="pln"> dis</span><span class="pun">.</span><span class="pln">haslocal</span><span class="pun">:</span><span class="pln"> </span><span class="com"># البحث عن اسم محلي</span><span class="pln">
                arg </span><span class="pun">=</span><span class="pln"> f</span><span class="pun">.</span><span class="pln">code_obj</span><span class="pun">.</span><span class="pln">co_varnames</span><span class="pun">[</span><span class="pln">arg_val</span><span class="pun">]</span><span class="pln">
            </span><span class="kwd">elif</span><span class="pln"> byteCode </span><span class="kwd">in</span><span class="pln"> dis</span><span class="pun">.</span><span class="pln">hasjrel</span><span class="pun">:</span><span class="pln">  </span><span class="com"># حساب القفزة النسبية</span><span class="pln">
                arg </span><span class="pun">=</span><span class="pln"> f</span><span class="pun">.</span><span class="pln">last_instruction </span><span class="pun">+</span><span class="pln"> arg_val
            </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
                arg </span><span class="pun">=</span><span class="pln"> arg_val
            argument </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">arg</span><span class="pun">]</span><span class="pln">
        </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
            argument </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">

        </span><span class="kwd">return</span><span class="pln"> byte_name</span><span class="pun">,</span><span class="pln"> argument</span></pre>

<p>
	التابع الثاني هو <code>dispatch</code> الذي يبحث عن العمليات لتعليمة معينة وينفّذها. يُنفَّذ هذا التابع للإرسال في مفسّر CPython باستخدام تعليمة <code>switch</code> ضخمة بحجم 1500 سطر، ولكن ستكون الأمور أفضل بكثير عند كتابة الشيفرة البرمجية باستخدام بايثون، حيث سنعرّف تابعًا لكل اسم تعليمة ثم نستخدم الدالة <code>getattr</code> للبحث عنه، فمثلًا إذا كان اسم التعليمة <code>FOO_BAR</code> كما في مثالنا للمفسر البسيط السابق، فسيكون اسم التابع المقابل هو <code>byte_FOO_BAR</code>. يعيد كل تابع بايت كود القيمةَ <code>None</code> أو سلسلةً نصية اسمها <code>why</code> تمثل جزءًا إضافيًا من الحالة التي قد يحتاجها المفسّر في بعض الحالات. تُستخدَم هذه القيم المعادة لتوابع التعليمات كمؤشرات داخلية فقط لحالة المفسّر، لذا يجب عدم الخلط بينها وبين القيم المعادة من إطارات التنفيذ.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_81" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">VirtualMachine</span><span class="pun">(</span><span class="pln">object</span><span class="pun">):</span><span class="pln">
    </span><span class="pun">[...</span><span class="pln"> snip </span><span class="pun">...]</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> dispatch</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> byte_name</span><span class="pun">,</span><span class="pln"> argument</span><span class="pun">):</span><span class="pln">
        </span><span class="str">""" Dispatch by bytename to the corresponding methods.
        Exceptions are caught and set on the virtual machine."""</span><span class="pln">

        </span><span class="com"># نحتاج إلى تتبع سبب فك مكدس الكتل لاحقًا</span><span class="pln">
        why </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">
        </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
            bytecode_fn </span><span class="pun">=</span><span class="pln"> getattr</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> </span><span class="str">'byte_%s'</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> byte_name</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> bytecode_fn </span><span class="kwd">is</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">:</span><span class="pln">
                </span><span class="kwd">if</span><span class="pln"> byte_name</span><span class="pun">.</span><span class="pln">startswith</span><span class="pun">(</span><span class="str">'UNARY_'</span><span class="pun">):</span><span class="pln">
                    self</span><span class="pun">.</span><span class="pln">unaryOperator</span><span class="pun">(</span><span class="pln">byte_name</span><span class="pun">[</span><span class="lit">6</span><span class="pun">:])</span><span class="pln">
                </span><span class="kwd">elif</span><span class="pln"> byte_name</span><span class="pun">.</span><span class="pln">startswith</span><span class="pun">(</span><span class="str">'BINARY_'</span><span class="pun">):</span><span class="pln">
                    self</span><span class="pun">.</span><span class="pln">binaryOperator</span><span class="pun">(</span><span class="pln">byte_name</span><span class="pun">[</span><span class="lit">7</span><span class="pun">:])</span><span class="pln">
                </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
                    </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">VirtualMachineError</span><span class="pun">(</span><span class="pln">
                        </span><span class="str">"unsupported bytecode type: %s"</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> byte_name
                    </span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
                why </span><span class="pun">=</span><span class="pln"> bytecode_fn</span><span class="pun">(*</span><span class="pln">argument</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">except</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># التعامل مع الاستثناءات التي واجهتنا أثناء تنفيذ العملية</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">last_exception </span><span class="pun">=</span><span class="pln"> sys</span><span class="pun">.</span><span class="pln">exc_info</span><span class="pun">()[:</span><span class="lit">2</span><span class="pun">]</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">None</span><span class="pun">,)</span><span class="pln">
            why </span><span class="pun">=</span><span class="pln"> </span><span class="str">'exception'</span><span class="pln">

        </span><span class="kwd">return</span><span class="pln"> why

    </span><span class="kwd">def</span><span class="pln"> run_frame</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> frame</span><span class="pun">):</span><span class="pln">
        </span><span class="str">"""Run a frame until it returns (somehow).
        Exceptions are raised, the return value is returned.
        """</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">push_frame</span><span class="pun">(</span><span class="pln">frame</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">while</span><span class="pln"> </span><span class="kwd">True</span><span class="pun">:</span><span class="pln">
            byte_name</span><span class="pun">,</span><span class="pln"> arguments </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">parse_byte_and_args</span><span class="pun">()</span><span class="pln">

            why </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">dispatch</span><span class="pun">(</span><span class="pln">byte_name</span><span class="pun">,</span><span class="pln"> arguments</span><span class="pun">)</span><span class="pln">

            </span><span class="com"># التعامل مع إدارة الكتل التي نحتاجها</span><span class="pln">
            </span><span class="kwd">while</span><span class="pln"> why </span><span class="kwd">and</span><span class="pln"> frame</span><span class="pun">.</span><span class="pln">block_stack</span><span class="pun">:</span><span class="pln">
                why </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">manage_block_stack</span><span class="pun">(</span><span class="pln">why</span><span class="pun">)</span><span class="pln">

            </span><span class="kwd">if</span><span class="pln"> why</span><span class="pun">:</span><span class="pln">
                </span><span class="kwd">break</span><span class="pln">

        self</span><span class="pun">.</span><span class="pln">pop_frame</span><span class="pun">()</span><span class="pln">

        </span><span class="kwd">if</span><span class="pln"> why </span><span class="pun">==</span><span class="pln"> </span><span class="str">'exception'</span><span class="pun">:</span><span class="pln">
            exc</span><span class="pun">,</span><span class="pln"> val</span><span class="pun">,</span><span class="pln"> tb </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">last_exception
            e </span><span class="pun">=</span><span class="pln"> exc</span><span class="pun">(</span><span class="pln">val</span><span class="pun">)</span><span class="pln">
            e</span><span class="pun">.</span><span class="pln">__traceback__ </span><span class="pun">=</span><span class="pln"> tb
            </span><span class="kwd">raise</span><span class="pln"> e

        </span><span class="kwd">return</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">return_value</span></pre>

<h3 id="-block">
	الصنف Block
</h3>

<p>
	سنوضح الآن الكتل بإيجاز قبل تنفيذ التوابع لكل تعليمة بايت كود، حيث تُستخدَم الكتلة لأنواع معينة من التحكم في التدفق وخاصة معالجة الاستثناءات والتكرار ضمن حلقة، إذ تُعَد الكتلة مسؤولة عن التأكد من أن مكدس البيانات في الحالة المناسبة عند انتهاء العملية، فمثلًا يبقى كائن تكرار خاص في المكدس أثناء تشغيل الحلقة، ولكنه يخرج عند انتهائها، لذا يجب على المفسر تتبع ما إذا كانت الحلقة مستمرة أم أنها انتهت.
</p>

<p>
	يتتبع المفسر هذه المعلومة الإضافية من خلال وضع رايةٍ Flag للإشارة إلى حالتها، حيث تكون هذه الراية متغيرًا بالاسم <code>why</code>، والذي يمكن أن تكون قيمته <code>None</code> أو إحدى السلاسل النصية <code>"continue"</code> أو <code>"break"</code> أو <code>"exception"</code> أو <code>"return"</code>، إذ تشير هذه القيم إلى كيفية التعامل مع مكدس الكتل ومكدس البيانات، حيث إذا كان الجزء العلوي من مكدس الكتل في مثالنا هو كتلة <code>loop</code> وكانت قيمة <code>why</code> هي <code>continue</code>، فيجب أن يبقى كائن التكرار في مكدس البيانات، ولكن إذا كانت قيمة <code>why</code> هي <code>break</code>، فيجب إخراجه من هذا المكدس.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5498_9" style=""><span class="typ">Block</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> collections</span><span class="pun">.</span><span class="pln">namedtuple</span><span class="pun">(</span><span class="str">"Block"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"type, handler, stack_height"</span><span class="pun">)</span><span class="pln">  </span><span class="com"># بنية خفيفة لتمثيل كتلة تنفيذ (نوعها + وجهة القفز + ارتفاع المكدّس)</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">VirtualMachine</span><span class="pun">(</span><span class="pln">object</span><span class="pun">):</span><span class="pln">
    </span><span class="pun">[...</span><span class="pln"> snip </span><span class="pun">...]</span><span class="pln">

    </span><span class="com"># معالجة مكدّس الكتل</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> push_block</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> b_type</span><span class="pun">,</span><span class="pln"> handler</span><span class="pun">=</span><span class="kwd">None</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># حفظ ارتفاع مكدّس القيم عند دخول الكتلة لنستطيع تنظيفه عند الخروج</span><span class="pln">
        stack_height </span><span class="pun">=</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">stack</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">block_stack</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="typ">Block</span><span class="pun">(</span><span class="pln">b_type</span><span class="pun">,</span><span class="pln"> handler</span><span class="pun">,</span><span class="pln"> stack_height</span><span class="pun">))</span><span class="pln">  </span><span class="com"># دفع كتلة جديدة إلى مكدّس الكتل</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> pop_block</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># إزالة أحدث كتلة من مكدّس الكتل</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">block_stack</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> unwind_block</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> block</span><span class="pun">):</span><span class="pln">
        </span><span class="str">"""تنظيف القيم من مكدّس البيانات بما يناسب الكتلة."""</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> block</span><span class="pun">.</span><span class="pln">type </span><span class="pun">==</span><span class="pln"> </span><span class="str">'except-handler'</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># في معالجة الاستثناء: تُحجز ثلاث قيم عادة (نوع/قيمة/تتبّع)</span><span class="pln">
            offset </span><span class="pun">=</span><span class="pln"> </span><span class="lit">3</span><span class="pln">
        </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
            offset </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pln">

        </span><span class="com"># حذف أي قيم زائدة في مكدّس القيم للوصول لارتفاع الكتلة وقت الدخول</span><span class="pln">
        </span><span class="kwd">while</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">stack</span><span class="pun">)</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> block</span><span class="pun">.</span><span class="pln">level </span><span class="pun">+</span><span class="pln"> offset</span><span class="pun">:</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">

        </span><span class="kwd">if</span><span class="pln"> block</span><span class="pun">.</span><span class="pln">type </span><span class="pun">==</span><span class="pln"> </span><span class="str">'except-handler'</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># استرجاع معلومات الاستثناء لتخزينها كآخر استثناء</span><span class="pln">
            traceback</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">,</span><span class="pln"> exctype </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">popn</span><span class="pun">(</span><span class="lit">3</span><span class="pun">)</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">last_exception </span><span class="pun">=</span><span class="pln"> exctype</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">,</span><span class="pln"> traceback

    </span><span class="kwd">def</span><span class="pln"> manage_block_stack</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> why</span><span class="pun">):</span><span class="pln">
        </span><span class="str">"""تقرير ما يجب فعله عند الخروج من كتلة بسبب سبب معيّن (كسر/متابعة/استثناء/إرجاع)."""</span><span class="pln">
        frame </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frame
        block </span><span class="pun">=</span><span class="pln"> frame</span><span class="pun">.</span><span class="pln">block_stack</span><span class="pun">[-</span><span class="lit">1</span><span class="pun">]</span><span class="pln">  </span><span class="com"># النظر إلى أحدث كتلة دون حذفها</span><span class="pln">

        </span><span class="kwd">if</span><span class="pln"> block</span><span class="pun">.</span><span class="pln">type </span><span class="pun">==</span><span class="pln"> </span><span class="str">'loop'</span><span class="pln"> </span><span class="kwd">and</span><span class="pln"> why </span><span class="pun">==</span><span class="pln"> </span><span class="str">'continue'</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># متابعة الحلقة: قفز إلى بداية الحلقة (العنوان مخزّن في return_value هنا)</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">jump</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">return_value</span><span class="pun">)</span><span class="pln">
            why </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> why

        </span><span class="com"># الخروج من الكتلة: حذفها ثم تنظيف مكدّس القيم المرتبط بها</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">pop_block</span><span class="pun">()</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">unwind_block</span><span class="pun">(</span><span class="pln">block</span><span class="pun">)</span><span class="pln">

        </span><span class="kwd">if</span><span class="pln"> block</span><span class="pun">.</span><span class="pln">type </span><span class="pun">==</span><span class="pln"> </span><span class="str">'loop'</span><span class="pln"> </span><span class="kwd">and</span><span class="pln"> why </span><span class="pun">==</span><span class="pln"> </span><span class="str">'break'</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># كسر الحلقة: القفز إلى نهاية الحلقة (المعالج هو وجهة الخروج)</span><span class="pln">
            why </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">jump</span><span class="pun">(</span><span class="pln">block</span><span class="pun">.</span><span class="pln">handler</span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> why

        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">block</span><span class="pun">.</span><span class="pln">type </span><span class="kwd">in</span><span class="pln"> </span><span class="pun">[</span><span class="str">'setup-except'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'finally'</span><span class="pun">]</span><span class="pln"> </span><span class="kwd">and</span><span class="pln"> why </span><span class="pun">==</span><span class="pln"> </span><span class="str">'exception'</span><span class="pun">):</span><span class="pln">
            </span><span class="com"># عند وقوع استثناء: تجهيز كتلة معالجة الاستثناء ودفع معلوماته للمكدّس ثم القفز لمعالج الاستثناء</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">push_block</span><span class="pun">(</span><span class="str">'except-handler'</span><span class="pun">)</span><span class="pln">
            exctype</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">,</span><span class="pln"> tb </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">last_exception
            self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">tb</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">,</span><span class="pln"> exctype</span><span class="pun">)</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">tb</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">,</span><span class="pln"> exctype</span><span class="pun">)</span><span class="pln">  </span><span class="com"># تُدفع مرتين لتوافق تهيئة بايثون في هذا التبسيط</span><span class="pln">
            why </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">jump</span><span class="pun">(</span><span class="pln">block</span><span class="pun">.</span><span class="pln">handler</span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> why

        </span><span class="kwd">elif</span><span class="pln"> block</span><span class="pun">.</span><span class="pln">type </span><span class="pun">==</span><span class="pln"> </span><span class="str">'finally'</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># عند finally: نمرّر سبب الخروج، وأحيانًا قيمة الإرجاع/المتابعة</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> why </span><span class="kwd">in</span><span class="pln"> </span><span class="pun">(</span><span class="str">'return'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'continue'</span><span class="pun">):</span><span class="pln">
                self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">return_value</span><span class="pun">)</span><span class="pln">

            self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">why</span><span class="pun">)</span><span class="pln">  </span><span class="com"># حفظ سبب الخروج ليُستخدم داخل finally</span><span class="pln">

            why </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">jump</span><span class="pun">(</span><span class="pln">block</span><span class="pun">.</span><span class="pln">handler</span><span class="pun">)</span><span class="pln">  </span><span class="com"># الانتقال إلى بداية finally</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> why

        </span><span class="com"># إن لم تُعالج الحالة هنا، نُعيد السبب كما هو</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> why</span></pre>

<h2 id="-">
	التعليمات
</h2>

<p>
	نحتاج الآن إلى تنفيذ التوابع الخاصة بالتعليمات، والذي يُعَد جزءًا مملًا من بناء المفسر، لذا إليك بعضًا منها، والتي تتضمن تعليمات كافية لتنفيذ الشيفرة البرمجية الموضحة سابقًا في هذا المقال، ولكن التنفيذ الكامل متاح على <a href="https://github.com/nedbat/byterun" rel="external nofollow">GitHub</a>.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5498_7" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">VirtualMachine</span><span class="pun">(</span><span class="pln">object</span><span class="pun">):</span><span class="pln">
    </span><span class="pun">[...</span><span class="pln"> snip </span><span class="pun">...]</span><span class="pln">

    </span><span class="com">## معالجة المكدس</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_LOAD_CONST</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> const</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># وضع ثابت (قيمة جاهزة) في المكدّس</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">const</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_POP_TOP</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># حذف أعلى قيمة من المكدّس دون استخدامها</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">

    </span><span class="com">## الأسماء</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> byte_LOAD_NAME</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># تحميل اسم: نبحث عنه محليًا ثم عالميًا ثم ضمن الأسماء المضمنة</span><span class="pln">
        frame </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frame
        </span><span class="kwd">if</span><span class="pln"> name </span><span class="kwd">in</span><span class="pln"> frame</span><span class="pun">.</span><span class="pln">f_locals</span><span class="pun">:</span><span class="pln">
            val </span><span class="pun">=</span><span class="pln"> frame</span><span class="pun">.</span><span class="pln">f_locals</span><span class="pun">[</span><span class="pln">name</span><span class="pun">]</span><span class="pln">
        </span><span class="kwd">elif</span><span class="pln"> name </span><span class="kwd">in</span><span class="pln"> frame</span><span class="pun">.</span><span class="pln">f_globals</span><span class="pun">:</span><span class="pln">
            val </span><span class="pun">=</span><span class="pln"> frame</span><span class="pun">.</span><span class="pln">f_globals</span><span class="pun">[</span><span class="pln">name</span><span class="pun">]</span><span class="pln">
        </span><span class="kwd">elif</span><span class="pln"> name </span><span class="kwd">in</span><span class="pln"> frame</span><span class="pun">.</span><span class="pln">f_builtins</span><span class="pun">:</span><span class="pln">
            val </span><span class="pun">=</span><span class="pln"> frame</span><span class="pun">.</span><span class="pln">f_builtins</span><span class="pun">[</span><span class="pln">name</span><span class="pun">]</span><span class="pln">
        </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># إذا لم يُعثر عليه في أي مكان نرمي خطأ اسم غير معرّف</span><span class="pln">
            </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">NameError</span><span class="pun">(</span><span class="str">"name '%s' is not defined"</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> name</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">val</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_STORE_NAME</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># تخزين قيمة في اسم محلي</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">f_locals</span><span class="pun">[</span><span class="pln">name</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_LOAD_FAST</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># تحميل متغير محلي "سريع" (يجب أن يكون قد أُسنِد مسبقًا)</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> name </span><span class="kwd">in</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">f_locals</span><span class="pun">:</span><span class="pln">
            val </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">f_locals</span><span class="pun">[</span><span class="pln">name</span><span class="pun">]</span><span class="pln">
        </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># استخدام متغير محلي قبل إسناده</span><span class="pln">
            </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">UnboundLocalError</span><span class="pun">(</span><span class="pln">
                </span><span class="str">"local variable '%s' referenced before assignment"</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> name
            </span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">val</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_STORE_FAST</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># تخزين قيمة في متغير محلي "سريع"</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">f_locals</span><span class="pun">[</span><span class="pln">name</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_LOAD_GLOBAL</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># تحميل اسم من المجال العام أو من الأسماء المضمنة</span><span class="pln">
        f </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frame
        </span><span class="kwd">if</span><span class="pln"> name </span><span class="kwd">in</span><span class="pln"> f</span><span class="pun">.</span><span class="pln">f_globals</span><span class="pun">:</span><span class="pln">
            val </span><span class="pun">=</span><span class="pln"> f</span><span class="pun">.</span><span class="pln">f_globals</span><span class="pun">[</span><span class="pln">name</span><span class="pun">]</span><span class="pln">
        </span><span class="kwd">elif</span><span class="pln"> name </span><span class="kwd">in</span><span class="pln"> f</span><span class="pun">.</span><span class="pln">f_builtins</span><span class="pun">:</span><span class="pln">
            val </span><span class="pun">=</span><span class="pln"> f</span><span class="pun">.</span><span class="pln">f_builtins</span><span class="pun">[</span><span class="pln">name</span><span class="pun">]</span><span class="pln">
        </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
            </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">NameError</span><span class="pun">(</span><span class="str">"global name '%s' is not defined"</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> name</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">val</span><span class="pun">)</span><span class="pln">

    </span><span class="com">## المعاملات</span><span class="pln">

    BINARY_OPERATORS </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">'POWER'</span><span class="pun">:</span><span class="pln">    pow</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'MULTIPLY'</span><span class="pun">:</span><span class="pln"> operator</span><span class="pun">.</span><span class="pln">mul</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'FLOOR_DIVIDE'</span><span class="pun">:</span><span class="pln"> operator</span><span class="pun">.</span><span class="pln">floordiv</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'TRUE_DIVIDE'</span><span class="pun">:</span><span class="pln">  operator</span><span class="pun">.</span><span class="pln">truediv</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'MODULO'</span><span class="pun">:</span><span class="pln">   operator</span><span class="pun">.</span><span class="pln">mod</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'ADD'</span><span class="pun">:</span><span class="pln">      operator</span><span class="pun">.</span><span class="pln">add</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'SUBTRACT'</span><span class="pun">:</span><span class="pln"> operator</span><span class="pun">.</span><span class="pln">sub</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'SUBSCR'</span><span class="pun">:</span><span class="pln">   operator</span><span class="pun">.</span><span class="pln">getitem</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'LSHIFT'</span><span class="pun">:</span><span class="pln">   operator</span><span class="pun">.</span><span class="pln">lshift</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'RSHIFT'</span><span class="pun">:</span><span class="pln">   operator</span><span class="pun">.</span><span class="pln">rshift</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'AND'</span><span class="pun">:</span><span class="pln">      operator</span><span class="pun">.</span><span class="pln">and_</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'XOR'</span><span class="pun">:</span><span class="pln">      operator</span><span class="pun">.</span><span class="pln">xor</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'OR'</span><span class="pun">:</span><span class="pln">       operator</span><span class="pun">.</span><span class="pln">or_</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">  </span><span class="com"># جدول يربط اسم العملية بدالة تنفيذها</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> binaryOperator</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> op</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># أخذ قيمتين من المكدّس وتنفيذ العملية الثنائية ثم إعادة الناتج للمكدّس</span><span class="pln">
        x</span><span class="pun">,</span><span class="pln"> y </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">popn</span><span class="pun">(</span><span class="lit">2</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">BINARY_OPERATORS</span><span class="pun">[</span><span class="pln">op</span><span class="pun">](</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">))</span><span class="pln">

    COMPARE_OPERATORS </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
        operator</span><span class="pun">.</span><span class="pln">lt</span><span class="pun">,</span><span class="pln">
        operator</span><span class="pun">.</span><span class="pln">le</span><span class="pun">,</span><span class="pln">
        operator</span><span class="pun">.</span><span class="pln">eq</span><span class="pun">,</span><span class="pln">
        operator</span><span class="pun">.</span><span class="pln">ne</span><span class="pun">,</span><span class="pln">
        operator</span><span class="pun">.</span><span class="pln">gt</span><span class="pun">,</span><span class="pln">
        operator</span><span class="pun">.</span><span class="pln">ge</span><span class="pun">,</span><span class="pln">
        </span><span class="kwd">lambda</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">:</span><span class="pln"> x </span><span class="kwd">in</span><span class="pln"> y</span><span class="pun">,</span><span class="pln">
        </span><span class="kwd">lambda</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">:</span><span class="pln"> x </span><span class="kwd">not</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> y</span><span class="pun">,</span><span class="pln">
        </span><span class="kwd">lambda</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">:</span><span class="pln"> x </span><span class="kwd">is</span><span class="pln"> y</span><span class="pun">,</span><span class="pln">
        </span><span class="kwd">lambda</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">:</span><span class="pln"> x </span><span class="kwd">is</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> y</span><span class="pun">,</span><span class="pln">
        </span><span class="kwd">lambda</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">:</span><span class="pln"> issubclass</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Exception</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">and</span><span class="pln"> issubclass</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">),</span><span class="pln">
    </span><span class="pun">]</span><span class="pln">  </span><span class="com"># جدول عمليات المقارنة حسب رقم العملية</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_COMPARE_OP</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> opnum</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># مقارنة قيمتين من المكدّس حسب رقم عملية المقارنة</span><span class="pln">
        x</span><span class="pun">,</span><span class="pln"> y </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">popn</span><span class="pun">(</span><span class="lit">2</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">COMPARE_OPERATORS</span><span class="pun">[</span><span class="pln">opnum</span><span class="pun">](</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">))</span><span class="pln">

    </span><span class="com">## السمات والفهرسة</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_LOAD_ATTR</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> attr</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># قراءة سمة من كائن (مثل obj.attr)</span><span class="pln">
        obj </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">
        val </span><span class="pun">=</span><span class="pln"> getattr</span><span class="pun">(</span><span class="pln">obj</span><span class="pun">,</span><span class="pln"> attr</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">val</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_STORE_ATTR</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># تعيين سمة لكائن (مثل obj.attr = val)</span><span class="pln">
        val</span><span class="pun">,</span><span class="pln"> obj </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">popn</span><span class="pun">(</span><span class="lit">2</span><span class="pun">)</span><span class="pln">
        setattr</span><span class="pun">(</span><span class="pln">obj</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">,</span><span class="pln"> val</span><span class="pun">)</span><span class="pln">

    </span><span class="com">## البناء</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_BUILD_LIST</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> count</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># بناء قائمة من عدد عناصر مأخوذة من المكدّس</span><span class="pln">
        elts </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">popn</span><span class="pun">(</span><span class="pln">count</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">elts</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_BUILD_MAP</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> size</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># إنشاء قاموس فارغ (الحجم هنا غير مستخدم في هذا التبسيط)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">({})</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_STORE_MAP</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># إضافة (مفتاح، قيمة) إلى قاموس موجود ثم إرجاعه للمكدّس</span><span class="pln">
        the_map</span><span class="pun">,</span><span class="pln"> val</span><span class="pun">,</span><span class="pln"> key </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">popn</span><span class="pun">(</span><span class="lit">3</span><span class="pun">)</span><span class="pln">
        the_map</span><span class="pun">[</span><span class="pln">key</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> val
        self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">the_map</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_LIST_APPEND</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> count</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># إضافة عنصر إلى قائمة موجودة أعمق في المكدّس</span><span class="pln">
        val </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">
        the_list </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">stack</span><span class="pun">[-</span><span class="pln">count</span><span class="pun">]</span><span class="pln">  </span><span class="com"># الوصول للقائمة دون سحبها</span><span class="pln">
        the_list</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">val</span><span class="pun">)</span><span class="pln">

    </span><span class="com">## القفزات</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_JUMP_FORWARD</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> jump</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># الانتقال إلى تعليمة أخرى للأمام</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">jump</span><span class="pun">(</span><span class="pln">jump</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_JUMP_ABSOLUTE</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> jump</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># الانتقال إلى تعليمة برقم محدد</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">jump</span><span class="pun">(</span><span class="pln">jump</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_POP_JUMP_IF_TRUE</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> jump</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># سحب قيمة شرطية: إذا كانت صحيحة نقفز</span><span class="pln">
        val </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> val</span><span class="pun">:</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">jump</span><span class="pun">(</span><span class="pln">jump</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_POP_JUMP_IF_FALSE</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> jump</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># سحب قيمة شرطية: إذا كانت خاطئة نقفز</span><span class="pln">
        val </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> val</span><span class="pun">:</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">jump</span><span class="pun">(</span><span class="pln">jump</span><span class="pun">)</span><span class="pln">

    </span><span class="com">## الكتل</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_SETUP_LOOP</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> dest</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># تسجيل كتلة حلقة ومعرفة مكان الخروج منها</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">push_block</span><span class="pun">(</span><span class="str">'loop'</span><span class="pun">,</span><span class="pln"> dest</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_GET_ITER</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># تحويل قيمة إلى مُكرّر ووضعه في المكدّس</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">iter</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()))</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_FOR_ITER</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> jump</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># جلب العنصر التالي من المُكرّر، أو القفز عند انتهاء التكرار</span><span class="pln">
        iterobj </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">top</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
            v </span><span class="pun">=</span><span class="pln"> next</span><span class="pun">(</span><span class="pln">iterobj</span><span class="pun">)</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">v</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">except</span><span class="pln"> </span><span class="typ">StopIteration</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># عند انتهاء التكرار: إزالة المُكرّر والقفز للخروج</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">jump</span><span class="pun">(</span><span class="pln">jump</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_BREAK_LOOP</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># إشارة إلى رغبة في كسر الحلقة</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="str">'break'</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_POP_BLOCK</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># إنهاء أحدث كتلة مسجّلة</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">pop_block</span><span class="pun">()</span><span class="pln">

    </span><span class="com">## الدوال</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_MAKE_FUNCTION</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> argc</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># إنشاء دالة من كائن الكود + الاسم + القيم الافتراضية</span><span class="pln">
        name </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">
        code </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">
        defaults </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">popn</span><span class="pun">(</span><span class="pln">argc</span><span class="pun">)</span><span class="pln">
        globs </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">frame</span><span class="pun">.</span><span class="pln">f_globals
        fn </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Function</span><span class="pun">(</span><span class="pln">name</span><span class="pun">,</span><span class="pln"> code</span><span class="pun">,</span><span class="pln"> globs</span><span class="pun">,</span><span class="pln"> defaults</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">,</span><span class="pln"> self</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">fn</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_CALL_FUNCTION</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> arg</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># استدعاء دالة بعد تجهيز معاملات الموضع فقط (دون معاملات مسماة هنا)</span><span class="pln">
        lenKw</span><span class="pun">,</span><span class="pln"> lenPos </span><span class="pun">=</span><span class="pln"> divmod</span><span class="pun">(</span><span class="pln">arg</span><span class="pun">,</span><span class="pln"> </span><span class="lit">256</span><span class="pun">)</span><span class="pln">  </span><span class="com"># المعاملات المسماة غير مدعومة هنا</span><span class="pln">
        posargs </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">popn</span><span class="pun">(</span><span class="pln">lenPos</span><span class="pun">)</span><span class="pln">

        func </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">
        retval </span><span class="pun">=</span><span class="pln"> func</span><span class="pun">(*</span><span class="pln">posargs</span><span class="pun">)</span><span class="pln">  </span><span class="com"># تنفيذ الدالة فعليًا</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">retval</span><span class="pun">)</span><span class="pln">        </span><span class="com"># وضع الناتج في المكدّس</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> byte_RETURN_VALUE</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># إنهاء الدالة الحالية وإرجاع قيمة</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">return_value </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">pop</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="str">"return"</span></pre>

<h2 id="-dynamic-typing">
	تحديد الأنواع الديناميكي Dynamic Typing
</h2>

<p>
	تُعَد لغة بايثون لغةً ديناميكية، إذ يمكن تحديد الأنواع ديناميكيًا، فأحد المعاني التي تعنيها كلمة ديناميكية في هذا السياق هو إنجاز الكثير من العمل في وقت التشغيل. رأينا سابقًا أن مصرِّف بايثون لا يمتلك كثيرًا من المعلومات حول ما تفعله الشيفرة البرمجية، فمثلًا إذا كان لدينا الدالة <code>mod</code> الآتية، حيث تأخذ هذه الدالة وسيطين وتعيد باقي قسمة قيمة الوسيط الأول على قيمة الوسيط الثاني، حيث نرى في البايت كود تحميل المتغيرين <code>a</code> و <code>b</code>، ثم يجري البايت كود <code>BINARY_MODULO</code> عملية إيجاد باقي القسمة.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_87" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">def</span><span class="pln"> mod</span><span class="pun">(</span><span class="pln">a</span><span class="pun">,</span><span class="pln"> b</span><span class="pun">):</span><span class="pln">
</span><span class="pun">...</span><span class="pln">    </span><span class="kwd">return</span><span class="pln"> a </span><span class="pun">%</span><span class="pln"> b
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> dis</span><span class="pun">.</span><span class="pln">dis</span><span class="pun">(</span><span class="pln">mod</span><span class="pun">)</span><span class="pln">
  </span><span class="lit">2</span><span class="pln">           </span><span class="lit">0</span><span class="pln"> LOAD_FAST                </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(</span><span class="pln">a</span><span class="pun">)</span><span class="pln">
              </span><span class="lit">3</span><span class="pln"> LOAD_FAST                </span><span class="lit">1</span><span class="pln"> </span><span class="pun">(</span><span class="pln">b</span><span class="pun">)</span><span class="pln">
              </span><span class="lit">6</span><span class="pln"> BINARY_MODULO
              </span><span class="lit">7</span><span class="pln"> RETURN_VALUE
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> mod</span><span class="pun">(</span><span class="lit">19</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">)</span><span class="pln">
</span><span class="lit">4</span></pre>

<p>
	تعطي عملية حساب ‎<code>19 % 5</code>‎ النتيجة 4، ولكن قد نتساءل عمّا يحدث عند استدعائها مع وسطاء مختلفة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_89" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> mod</span><span class="pun">(</span><span class="str">"by%sde"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"teco"</span><span class="pun">)</span><span class="pln">
</span><span class="str">'bytecode'</span></pre>

<p>
	هذا شيء غريب، ولكنه يشبه ما يحدث في سياق مختلف كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_91" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="str">"by%sde"</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> </span><span class="str">"teco"</span><span class="pun">)</span><span class="pln">
bytecode</span></pre>

<p>
	يؤدي استخدام الرمز <code>%</code> في تنسيق سلسلة نصية لطباعتها إلى استدعاء التعليمة <code>BINARY_MODULO</code> التي تعطي ناتج القسمة لأعلى قيمتين في المكدس عند تنفيذ التعليمة بغض النظر عمّا إذا كانت سلاسل نصية أو أعدادًا صحيحة أو نسخًا من صنف عرّفناه بأنفسنا، حيث يولّد البايت كود عند تصريف الدالة أو عند تعريفها ويُستخدَم البايت كود نفسه مع أنواع مختلفة من الوسطاء.
</p>

<p>
	يجهل مصرّف بايثون التأثير الذي يحدثه البايت كود، لذا فالأمر متروك للمفسر لتحديد نوع الكائن الذي تعمل عليه التعليمة <code>BINARY_MODULO</code> وإجراء الأمر الصحيح المناسب لهذا النوع، وهذا هو سبب وصف بايثون بأنها تحدد الأنواع ديناميكيًا، إذ لن تعرف أنواع وسطاء الدالة حتى تشغيلها، بينما يخبر المبرمج المُصرِّف مسبقًا بنوع الوسطاء أو يكتشفها بنفسه في اللغات ذات الأنواع الثابتة.
</p>

<p>
	يُعَد جهل المصرف من التحديات التي تواجه تحسين أو تحليل بايثون بطريقة ثابتة، إذ لا نعرف ما الذي ستفعله التعليمات عند النظر إلى البايت كود فقط دون تشغيل الشيفرة البرمجية فعليًا، ولكن يمكننا تعريف صنف ينفذ التابع <code>__mod__</code>، وسيستدعي بايثون هذا التابع إذا استخدمنا <code>%</code> مع الكائنات الخاصة بنا، وبالتالي يمكن للتعليمة <code>BINARY_MODULO</code> تشغيل أيّ شيفرة برمجية نريدها.
</p>

<p>
	سنعرف أن العملية التالية <code>a % b</code> بدون فائدة بنظرة واحدة إليها:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1449_93" style=""><span class="kwd">def</span><span class="pln"> mod</span><span class="pun">(</span><span class="pln">a</span><span class="pun">,</span><span class="pln">b</span><span class="pun">):</span><span class="pln">
    a </span><span class="pun">%</span><span class="pln"> b
    </span><span class="kwd">return</span><span class="pln"> a </span><span class="pun">%</span><span class="pln">b</span></pre>

<p>
	لا يمكن لتحليل هذه الشيفرة البرمجية الثابت -الذي يمكن تطبيقه دون تشغيلها- التأكد من أن التعليمة <code>a % b</code> لا تفعل شيئًا، ولكن قد يؤدي استدعاء التابع <code>__mod__</code> مع <code>%</code> إلى الكتابة في ملف أو التفاعل مع جزء آخر من برنامجنا أو فعل أي شيء آخر ممكن في بايثون، إذ من الصعب تحسين دالة عندما لا نعرف ما تفعله. وضع كل من Russell Power و Alex Rubinsteyn في ورقتهما العلمية التي تدرس مقدار السرعة التي يمكن الوصول إليها لتفسير بايثون ملاحظةً مفادها بأنه: يجب التعامل مع كل تعليمة على أنها <code>INVOKE_ARBITRARY_METHOD</code> مع عدم وجود معلومات عن النوع.
</p>

<h2 id="-">
	الخلاصة
</h2>

<p>
	Byterun هو مفسر بايثون أبسط وأسهل في الفهم من CPython، حيث يكرر هذا المفسر تفاصيل CPython البنيوية الرئيسية، فهو مفسر قائم على المكدس ويعمل على مجموعات من التعليمات تسمى البايت كود، حيث يتنقل أو يقفز عبر هذه التعليمات، ويدفعها إلى مكدس من البيانات ويسحبها منه، وينشئ المفسر الإطارات ويدمرها ويقفز بينها عند استدعاء الدوال والمولدات والعودة منها. يتشارك Byterun في قيود المفسر الحقيقي، إذ يجب أن يعمل المفسر في وقت التشغيل لتحديد السلوك الصحيح للبرنامج لأن بايثون يستخدم تحديد الأنواع الديناميكي.
</p>

<p>
	يُنصَح بتفكيك برامجنا الخاصة وتشغيلها باستخدام Byterun، ولكن سنعثر على تعليمات لا تنفذها هذه النسخة القصيرة من Byterun، لذا يمكن العثور على التنفيذ الكامل على <a href="https://github.com/nedbat/byterun" rel="external nofollow">Github</a> أو من خلال القراءة الدقيقة لملف <code>ceval.c</code> الخاص بمفسر CPython الحقيقي.
</p>

<p>
	<strong>ملاحظة</strong>: يعتمد هذا المقال على البايت كود الناتج من إصدار Python 3.5 أو الإصدارات الأقدم، حيث كانت هناك بعض التغييرات على مواصفات البايت كود في الإصدار Python 3.6.
</p>

<p>
	ترجمة -وبتصرّف- للمقال <a href="https://aosabook.org/en/500L/a-python-interpreter-written-in-python.html" rel="external nofollow">A Python Interpreter Written in Python</a> لصاحبته Allison Kaptur.
</p>
]]></description><guid isPermaLink="false">2601</guid><pubDate>Sun, 01 Mar 2026 09:19:00 +0000</pubDate></item><item><title>&#x623;&#x647;&#x645; &#x627;&#x644;&#x62A;&#x645;&#x627;&#x631;&#x64A;&#x646; &#x627;&#x644;&#x639;&#x645;&#x644;&#x64A;&#x629; &#x627;&#x644;&#x62A;&#x64A; &#x642;&#x62F; &#x62A;&#x637;&#x644;&#x628; &#x641;&#x64A; &#x627;&#x644;&#x645;&#x642;&#x627;&#x628;&#x644;&#x627;&#x62A; &#x644;&#x62A;&#x648;&#x638;&#x64A;&#x641; &#x645;&#x637;&#x648;&#x631; &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646;</title><link>https://academy.hsoub.com/programming/python/%D8%A3%D9%87%D9%85-%D8%A7%D9%84%D8%AA%D9%85%D8%A7%D8%B1%D9%8A%D9%86-%D8%A7%D9%84%D8%B9%D9%85%D9%84%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D9%8A-%D9%82%D8%AF-%D8%AA%D8%B7%D9%84%D8%A8-%D9%81%D9%8A-%D8%A7%D9%84%D9%85%D9%82%D8%A7%D8%A8%D9%84%D8%A7%D8%AA-%D9%84%D8%AA%D9%88%D8%B8%D9%8A%D9%81-%D9%85%D8%B7%D9%88%D8%B1-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2596/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2025_05/-----.png.74bd27198dc93753c1aadd109be8286d.png" /></p>
<p>
	شرحنا في المقال السابق أهم الأسئلة النظرية التي قد تُطرح على المتقدمين لوظيفة مطور بايثون خلال مقابلة التوظيف، وسنوضح في هذا المقال أهم الأسئلة والتمارين العملية التي قد تُطلب منك فوريًا في مقابلات التوظيف بوظيفة مطور بايثون.
</p>

<p>
	قد تُطرح في المقابلات التقنية بعض الأسئلة التي تتطلب تحسين أو تصحيح شيفرة برمجية، سنذكر أهمها في ما يلي.
</p>

<h2 id="">
	التمرين الأول: تحسين الشيفرة
</h2>

<p>
	قد يحتوي التمرين على أسئلة من نوع:
</p>

<ol>
	<li>
		اشرح وظيفة الدالة <code>process_numbers</code> في المثال التالي وما هو الخرج؟
	</li>
	<li>
		ما هي العملية التي يمكن التخلي عنها دون أن يؤثر ذلك في الخرج؟
	</li>
</ol>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6765_6" style=""><span class="kwd">def</span><span class="pln"> process_numbers</span><span class="pun">(</span><span class="pln">numbers</span><span class="pun">):</span><span class="pln">
    unique_numbers </span><span class="pun">=</span><span class="pln"> list</span><span class="pun">(</span><span class="pln">set</span><span class="pun">(</span><span class="pln">numbers</span><span class="pun">))</span><span class="pln">
    unique_numbers</span><span class="pun">.</span><span class="pln">sort</span><span class="pun">()</span><span class="pln">
    unique_tuple </span><span class="pun">=</span><span class="pln"> tuple</span><span class="pun">(</span><span class="pln">unique_numbers</span><span class="pun">)</span><span class="pln">
    total </span><span class="pun">=</span><span class="pln"> sum</span><span class="pun">(</span><span class="pln">unique_tuple</span><span class="pun">)</span><span class="pln">
    average </span><span class="pun">=</span><span class="pln"> total </span><span class="pun">/</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">unique_tuple</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> unique_tuple </span><span class="kwd">else</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">[</span><span class="pln">num </span><span class="kwd">for</span><span class="pln"> num </span><span class="kwd">in</span><span class="pln"> unique_tuple </span><span class="kwd">if</span><span class="pln"> num </span><span class="pun">&gt;</span><span class="pln"> average</span><span class="pun">]</span><span class="pln">

</span><span class="kwd">print</span><span class="pun">(</span><span class="pln">process_numbers</span><span class="pun">([</span><span class="lit">7</span><span class="pun">,</span><span class="lit">1</span><span class="pun">,</span><span class="lit">3</span><span class="pun">,</span><span class="lit">4</span><span class="pun">,</span><span class="lit">2</span><span class="pun">,</span><span class="lit">3</span><span class="pun">,</span><span class="lit">5</span><span class="pun">,</span><span class="lit">6</span><span class="pun">,</span><span class="lit">4</span><span class="pun">]))</span></pre>

<p>
	وتكون الإجابة على النحو الآتي:
</p>

<ul>
	<li>
		<strong>الجواب 1</strong>: تقبل الدالة السابقة وسيطًا على شكل قائمة list مكونة من أعداد ثم تختار العناصر الفريدة منها وتوجِد متوسطها الحسابي، ثم تعيد قائمةً تضم الأعداد الفريدة الأكبر تمامًا من المتوسط. وسيكون الخرج <code>[7, 6, 5]</code>
	</li>
	<li>
		<strong>الجواب 2</strong>: عملية تحويل القائمة List إلى قائمة Tuple في السطر الرابع
	</li>
</ul>

<h2 id="-1">
	التمرين الثاني: استخدام مكتبات بايثون مقارنة بالتنفيذ اليدوي لخوارزمية
</h2>

<p>
	قد يحتوي التمرين على أسئلة من نوع:
</p>

<ol>
	<li>
		ما وظيفة الشيفرة في هذا التمرين؟
	</li>
	<li>
		هل يمكنك تنفيذ نفس الوظيفة باستخدام مكتبات جاهزة؟
	</li>
	<li>
		اكتب شيفرةً تقارن بين زمني تنفيذ الطريقتين السابقتين، ما الذي تلاحظه مع الازدياد الكبير لحجم قائمة البيانات؟
	</li>
</ol>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6765_8" style=""><span class="kwd">import</span><span class="pln"> random

random_data </span><span class="pun">=</span><span class="pln"> set</span><span class="pun">(</span><span class="pln">random</span><span class="pun">.</span><span class="pln">sample</span><span class="pun">(</span><span class="pln">range</span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10000</span><span class="pun">),</span><span class="pln"> </span><span class="lit">1000</span><span class="pun">))</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> calculate_median</span><span class="pun">(</span><span class="pln">data</span><span class="pun">):</span><span class="pln">
   data </span><span class="pun">=</span><span class="pln"> list</span><span class="pun">(</span><span class="pln">data</span><span class="pun">)</span><span class="pln">
    data</span><span class="pun">.</span><span class="pln">sort</span><span class="pun">()</span><span class="pln">
    n </span><span class="pun">=</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">data</span><span class="pun">)</span><span class="pln">
    mid </span><span class="pun">=</span><span class="pln"> n </span><span class="pun">//</span><span class="pln"> </span><span class="lit">2</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> n </span><span class="pun">%</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="lit">1</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> data</span><span class="pun">[</span><span class="pln">mid</span><span class="pun">]</span><span class="pln">
    </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">(</span><span class="pln">data</span><span class="pun">[</span><span class="pln">mid </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> data</span><span class="pun">[</span><span class="pln">mid</span><span class="pun">])</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> </span><span class="lit">2</span></pre>

<p>
	وتكون الإجابة كالآتي:
</p>

<ul>
	<li>
		<strong>الجواب الأول:</strong> تعطي الشيفرة السابقة طريقة حساب وسيط Median لعينة من الأرقام بطريقة يدوية
	</li>
	<li>
		<strong>الجواب الثاني</strong>: نعم، باستخدام المكتبة <code>statistics</code> أو <code>scipy</code>
	</li>
	<li>
		<strong>الجواب الثالث</strong>: باستخدام المكتبة <code>time</code>
	</li>
</ul>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6765_10" style=""><span class="kwd">import</span><span class="pln"> random
</span><span class="kwd">import</span><span class="pln"> time
</span><span class="kwd">import</span><span class="pln"> statistics

random_data </span><span class="pun">=</span><span class="pln"> set</span><span class="pun">(</span><span class="pln">random</span><span class="pun">.</span><span class="pln">sample</span><span class="pun">(</span><span class="pln">range</span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10000</span><span class="pun">),</span><span class="pln"> </span><span class="lit">1000</span><span class="pun">))</span><span class="pln">

</span><span class="com"># تنفيذ يدوي للخوارزمية</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> calculate_median</span><span class="pun">(</span><span class="pln">data</span><span class="pun">):</span><span class="pln">
   data </span><span class="pun">=</span><span class="pln"> list</span><span class="pun">(</span><span class="pln">data</span><span class="pun">)</span><span class="pln">
    data</span><span class="pun">.</span><span class="pln">sort</span><span class="pun">()</span><span class="pln">
    n </span><span class="pun">=</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">data</span><span class="pun">)</span><span class="pln">
    mid </span><span class="pun">=</span><span class="pln"> n </span><span class="pun">//</span><span class="pln"> </span><span class="lit">2</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> n </span><span class="pun">%</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="lit">1</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> data</span><span class="pun">[</span><span class="pln">mid</span><span class="pun">]</span><span class="pln">
    </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">(</span><span class="pln">data</span><span class="pun">[</span><span class="pln">mid </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> data</span><span class="pun">[</span><span class="pln">mid</span><span class="pun">])</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> </span><span class="lit">2</span><span class="pln">

</span><span class="com"># Statistics استخدام المكتبة</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> median_using_statistics</span><span class="pun">(</span><span class="pln">data</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> statistics</span><span class="pun">.</span><span class="pln">median</span><span class="pun">(</span><span class="pln">data</span><span class="pun">)</span><span class="pln">

</span><span class="com"># تقدير وقت التنفيذ</span><span class="pln">
start_manual </span><span class="pun">=</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">time</span><span class="pun">()</span><span class="pln">
calculate_median</span><span class="pun">(</span><span class="pln">random_data</span><span class="pun">)</span><span class="pln">
end_manual </span><span class="pun">=</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">time</span><span class="pun">()</span><span class="pln">

start_Statistics </span><span class="pun">=</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">time</span><span class="pun">()</span><span class="pln">
median_using_statistics</span><span class="pun">(</span><span class="pln">random_data</span><span class="pun">)</span><span class="pln">
end_Statistics </span><span class="pun">=</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">time</span><span class="pun">()</span><span class="pln">

</span><span class="com"># طباعة النتائج</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="str">"Manual Sort-Based Time:"</span><span class="pun">,</span><span class="pln"> end_manual </span><span class="pun">-</span><span class="pln"> start_manual</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="str">"Statistics-Based Time:"</span><span class="pun">,</span><span class="pln"> end_Statistics </span><span class="pun">-</span><span class="pln"> start_Statistics</span><span class="pun">)</span></pre>

<p>
	يمكن ملاحظة أنه مع ازدياد حجم البيانات، يصبح وقت التنفيذ أكبر بكثير في حال الطريقة اليدوية، لأن المكتبات تكون محققة باستخدام خوارزميات أسرع وأمثل وتعمل بطريقة أكثر كفاءة، مما يجعل وقت تنفيذها أقل أو أكثر استقرارًا مقارنةً بالأسلوب اليدوي.
</p>

<h2 id="numpy">
	التمرين الثالث: استخدام المكتبة <code>numpy</code>
</h2>

<p>
	قد يحتوي التمرين على أسئلة من نوع:
</p>

<ol>
	<li>
		أوجد الخطأ في الشيفرة التالية -لا بد من امتلاك خلفية باستخدام المكتبة <code>numpy</code>
	</li>
	<li>
		فسّر الخطأ
	</li>
	<li>
		صحح الخطأ، واكتب الشيفرة الصحيحة
	</li>
</ol>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6765_12" style=""><span class="kwd">import</span><span class="pln"> numpy </span><span class="kwd">as</span><span class="pln"> np
</span><span class="kwd">def</span><span class="pln"> transform_array</span><span class="pun">(</span><span class="pln">arr</span><span class="pun">):</span><span class="pln">
    arr</span><span class="pun">[</span><span class="pln">arr </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">nan  </span><span class="com"># NaN استبدل القيم السالبة بقيمة من النوع </span><span class="pln">
    mean_value </span><span class="pun">=</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">nanmean</span><span class="pun">(</span><span class="pln">arr</span><span class="pun">)</span><span class="pln">  </span><span class="com"># NaN احسب المتوسط مهملًا القيم </span><span class="pln">
    arr </span><span class="pun">=</span><span class="pln"> arr </span><span class="pun">/</span><span class="pln"> mean_value  </span><span class="com"># سوّي عناصرالمصفوفة بالقسمة على المتوسط</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> arr
data </span><span class="pun">=</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">array</span><span class="pun">([</span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">7</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">5</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">])</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="pln">transform_array</span><span class="pun">(</span><span class="pln">data</span><span class="pun">))</span></pre>

<p>
	يكون الجواب على النحو الآتي:
</p>

<ul>
	<li>
		<strong>الجواب الأول</strong>: الخطأ في السطر الثالث
	</li>
	<li>
		<strong>الجواب الثاني</strong>: لا تدعم <code>numpy</code> تخزين القيم <code>NaN</code> ضمن المصفوفات الصحيحة
	</li>
	<li>
		<strong>الجواب الثالث</strong>: علينا تحويل عناصر المصفوفة صراحة إلى النوع <a href="https://wiki.hsoub.com/Python/float" rel="external">float</a> قبل استبدال الأعداد السالبة
	</li>
</ul>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6765_14" style=""><span class="kwd">import</span><span class="pln"> numpy </span><span class="kwd">as</span><span class="pln"> np
</span><span class="kwd">def</span><span class="pln"> transform_array</span><span class="pun">(</span><span class="pln">arr</span><span class="pun">):</span><span class="pln">
    arr </span><span class="pun">=</span><span class="pln"> arr</span><span class="pun">.</span><span class="pln">astype</span><span class="pun">(</span><span class="pln">float</span><span class="pun">)</span><span class="pln">  </span><span class="com"># float حوّل عناصر المصفوفة إلى النوع </span><span class="pln">
    arr</span><span class="pun">[</span><span class="pln">arr </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">nan  
    </span><span class="pun">.</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">
data </span><span class="pun">=</span><span class="pln"> np</span><span class="pun">.</span><span class="pln">array</span><span class="pun">([</span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">7</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">5</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">])</span><span class="pln"> 
</span><span class="kwd">print</span><span class="pun">(</span><span class="pln">transform_array</span><span class="pun">(</span><span class="pln">data</span><span class="pun">))</span></pre>

<h2 id="panda">
	التمرين الرابع: استخدام المكتبة <code>pandas</code>
</h2>

<p>
	قد يحتوي التمرين على أسئلة من نوع:
</p>

<ol>
	<li>
		أوجد الخطأ في الشيفرة التالية التي تستخدم المكتبة <code>pandas</code>
	</li>
	<li>
		فسّر الخطأ
	</li>
	<li>
		صحح الخطأ الموجود بالشيفرة، واكتب الشيفرة الصحيحة
	</li>
</ol>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6765_16" style=""><span class="kwd">import</span><span class="pln"> pandas </span><span class="kwd">as</span><span class="pln"> pd
data </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="str">'A'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="lit">4</span><span class="pun">],</span><span class="pln"> </span><span class="str">'B'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="lit">5</span><span class="pun">,</span><span class="pln"> </span><span class="lit">6</span><span class="pun">,</span><span class="pln"> </span><span class="lit">7</span><span class="pun">,</span><span class="pln"> </span><span class="lit">8</span><span class="pun">]}</span><span class="pln">
df </span><span class="pun">=</span><span class="pln"> pd</span><span class="pun">.</span><span class="typ">DataFrame</span><span class="pun">(</span><span class="pln">data</span><span class="pun">)</span><span class="pln">
df</span><span class="pun">[</span><span class="str">'C'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> df</span><span class="pun">.</span><span class="pln">A </span><span class="pun">*</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> </span><span class="com"># C أنشئ عمود جديد </span><span class="pln">
filtered_df </span><span class="pun">=</span><span class="pln"> df</span><span class="pun">[</span><span class="pln">df</span><span class="pun">.</span><span class="pln">C </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">5</span><span class="pun">]</span><span class="pln"> </span><span class="com"># أكبر من 5 C احذف الصف عندما تكون القيمة في </span><span class="pln">
filtered_df</span><span class="pun">.</span><span class="pln">reset_index</span><span class="pun">()</span><span class="pln"> </span><span class="com"># أعد ضبط فهرس إطار البيانات</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="pln">filtered_df</span><span class="pun">)</span></pre>

<p>
	ويكون الجواب على النحو الآتي:
</p>

<ul>
	<li>
		<strong>الجواب الأول</strong>: الخطأ في السطر السادس
	</li>
	<li>
		<strong>الجواب الثاني</strong>: لن تتغير الفهرسة بعد حذف الصفوف ولن تُطبق على <code>filtered_df</code>
	</li>
	<li>
		<strong>الجواب الثالث</strong>: علينا استدعاء التابع <code>()reset_index</code> باستخدام <code>inplace=true</code> على النحو:
	</li>
</ul>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6765_18" style=""><span class="pln">filtered_df</span><span class="pun">.</span><span class="pln">reset_index</span><span class="pun">(</span><span class="pln">drop</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">,</span><span class="pln"> inplace</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span></pre>

<h4 id="-2">
	التمرين الخامس: الاستيثاق والثغرات الأمنية
</h4>

<p>
	تحاول الشيفرة التالية الاستيثاق باستخدام JWT، لكنها تعاني من ثغرة أمنية خطيرة والمطلوب:
</p>

<ol>
	<li>
		حدد الثغرة الأمنية في هذه الشيفرة
	</li>
	<li>
		اقترح حلًا لها
	</li>
</ol>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6765_20" style=""><span class="kwd">import</span><span class="pln"> jwt
</span><span class="kwd">import</span><span class="pln"> datetime

SECRET_KEY </span><span class="pun">=</span><span class="pln"> </span><span class="str">"supersecretkey"</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> generate_token</span><span class="pun">(</span><span class="pln">username</span><span class="pun">):</span><span class="pln">
    payload </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">"user"</span><span class="pun">:</span><span class="pln"> username</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"exp"</span><span class="pun">:</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">.</span><span class="pln">utcnow</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">timedelta</span><span class="pun">(</span><span class="pln">minutes</span><span class="pun">=</span><span class="lit">30</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> jwt</span><span class="pun">.</span><span class="pln">encode</span><span class="pun">(</span><span class="pln">payload</span><span class="pun">,</span><span class="pln"> SECRET_KEY</span><span class="pun">,</span><span class="pln"> algorithm</span><span class="pun">=</span><span class="str">"HS256"</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> verify_token</span><span class="pun">(</span><span class="pln">token</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
        decoded </span><span class="pun">=</span><span class="pln"> jwt</span><span class="pun">.</span><span class="pln">decode</span><span class="pun">(</span><span class="pln">token</span><span class="pun">,</span><span class="pln"> SECRET_KEY</span><span class="pun">,</span><span class="pln"> algorithms</span><span class="pun">=</span><span class="str">"HS256"</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> decoded</span><span class="pun">[</span><span class="str">"user"</span><span class="pun">]</span><span class="pln">
    </span><span class="kwd">except</span><span class="pln"> </span><span class="typ">Exception</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> e</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">None</span></pre>

<p>
	وللإجابة عن هذه التساؤلات، سيكون <strong>الجواب الأول</strong> أن هذه الشيفرة معرضة لهجوم خلط الخوارزميات Algorithm Confusion Attack، نظرًا لإمكانية تعديل المخترق على ترويسة الطلب ببساطة ويلغي استخدام خوارزمية <code>HS256</code> إلى <code>none</code> وبالتالي يتسلل دون استيثاق
</p>

<p>
	أما الحل السريع وهو <strong>جواب السؤال الثاني</strong>، فيتمثل في إجبار استخدام خوارزمية ضمن الدالة <code>verify_token</code> والتقاط الأخطاء الناتجة. فيما يلي الشيفرة المعدلة لهذه الدالة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_6765_22" style=""><span class="kwd">def</span><span class="pln"> verify_token</span><span class="pun">(</span><span class="pln">token</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
        </span><span class="com"># صرّح عن خوارزمية التشفير المتوقعة من المفتاح</span><span class="pln">
        decoded </span><span class="pun">=</span><span class="pln"> jwt</span><span class="pun">.</span><span class="pln">decode</span><span class="pun">(</span><span class="pln">token</span><span class="pun">,</span><span class="pln"> SECRET_KEY</span><span class="pun">,</span><span class="pln"> algorithms</span><span class="pun">=[</span><span class="str">"HS256"</span><span class="pun">])</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> decoded</span><span class="pun">[</span><span class="str">"user"</span><span class="pun">]</span><span class="pln">
    </span><span class="kwd">except</span><span class="pln"> jwt</span><span class="pun">.</span><span class="typ">ExpiredSignatureError</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="str">"Token expired!"</span><span class="pln">
    </span><span class="kwd">except</span><span class="pln"> jwt</span><span class="pun">.</span><span class="typ">InvalidTokenError</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="str">"Invalid token!"</span></pre>

<h2 id="-3">
	نصائح للاستعداد لمقابلة العمل
</h2>

<p>
	من أجل الاستعداد الجيد لمقابلة العمل كمطور بايثون، لا بد من التأكد من اعتماد النصائح الآتية:
</p>

<h3 id="-4">
	فهم ما نتعلمه
</h3>

<p>
	من المهم استيعاب الغاية من كل قاعدة ومبدأ وتقنية وتدوين الملاحظات حول الاستخدام الأمثل الذي وجدناه لها. يحتاج أي مطور بايثون إلى سؤال نفسه: لماذا تقدم بايثون مثلًا عدة بنىً مختلفة لتجميع البيانات مثل <code>list</code> و <code>tuple</code> و <code>range</code>؟ وما الغاية من كل منها؟ وأين يكمن الاستخدام الأمثل لها؟ هذه التساؤلات، ستساعد كل متقدم للوظيفة على تهيئة نفسه للإجابة بدقة واختصار على الأسئلة المطروحة عليه.
</p>

<h3 id="-5">
	التحليل والمقارنة
</h3>

<p>
	لا بد من التحليل والمقارنة سواء باستخدام تعليمات مختلفة أو خوارزمية مختلفة، خاصة عند موااجهة مقاربات مختلفة لحل المشكلة نفسها. علينا أن نفهم كل نهج بدقة وندوّن ملاحظاتنا عليه، ثم نضيف رأينا الخاص ونشرح لأنفسنا ما يجري وما هو الاستخدام الأمثل الذي وجدناه.
</p>

<h3 id="-6">
	تطبيق أفضل الممارسات
</h3>

<p>
	كل منا حر في صياغة الكود البرمجي الخاص به بالطريقة التي يجدها مناسبة، لكن لنتخيل أن يفعل ذلك كل عضو من أعضاء فريق العمل؟ ستكون الامور فوضوية ومربكة لذا علينا أن نعود نفسنا على تطبيق أفضل الممارسات التي يُنصح بها أثناء بناء المشاريع، إذ سيسهّل هذا الأمر الاندماج لاحقًا ضمن أي فريق عمل خاصة في الشركات التي تأخذ تطبيق أفضل الممارسات في كتابة وتنسيق وصيانة الشيفرة على محمل الجد دائمًا. يمكن قراءة مقال <a href="https://academy.hsoub.com/programming/python/%D9%83%D9%8A%D9%81-%D8%AA%D9%83%D8%AA%D8%A8-%D9%83%D9%88%D8%AF-%D8%A3%D9%86%D9%8A%D9%82-%D9%88%D8%B3%D9%87%D9%84-%D8%A7%D9%84%D9%82%D8%B1%D8%A7%D8%A1%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-coding-style-r2513/" rel="">كيف تكتب كود أنيق وسهل القراءة باستخدام لغة بايثون</a> للمساعدة بذلك.
</p>

<h3 id="-7">
	التركيز على المهارات المطلوبة في العمل
</h3>

<p>
	لا أحد منا كامل، فقد نمتلك بعضًا من المهارات المطلوبة وينقصنا بعضها، لكن من المهم أن لا نشعر بالإحباط، وأن نحاول تعزيز معارفنا وخبراتنا بالاطلاع على مراجع مفيدة والمراجع كثيرة ومتنوعة، ويمكن هنا الاستفادة من <a href="https://academy.hsoub.com/programming/python/" rel="">مقالات أكاديمية حسوب</a> حول بايثون، إلى جانب <a href="https://academy.hsoub.com/files/c5-programming/" rel="">الكتب البرمجية</a> التي تساعد على التعلم بطريقة منهجية منظمة.
</p>

<h2 id="-8">
	ختامًا
</h2>

<p>
	بهذا نكون قد قدمنا في هذا المقال ما ينبغي على المتقدم لوظيفة مطور بايثون أن يحضّره ويتذكر تفاصيله قبل الوصول إلى مقابلة العمل، ثم عرضنا بعضًا من الأسئلة التي قد تُطرح عليه أثناء المقابلة.
</p>

<h2 id="-9">
	المراجع
</h2>

<ul>
	<li>
		<a href="https://wiki.hsoub.com/Python" rel="external">توثيق بايثون باللغة العربية</a> على موقع موسوعة حسوب 
	</li>
	<li>
		<a href="https://academy.hsoub.com/files/15-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86/" rel="">كتاب البرمجة بلغة بايثون من إعداد أكاديمية حسوب</a>
	</li>
	<li>
		<a href="https://www.javatpoint.com/advance-concepts-of-python-for-python-developer" rel="external nofollow">Advance Concepts of Python for Python Developer</a> 
	</li>
	<li>
		<a href="https://pwskills.com/blog/advanced-python-tutorials/" rel="external nofollow">Advanced Python Tutorials: Dive into Complex Concepts</a>
	</li>
	<li>
		<a href="https://www.testgorilla.com/blog/advanced-python-interview-questions/" rel="external nofollow">( advanced Python interview questions (and answers</a>
	</li>
	<li>
		<a href="https://github.com/Tanu-N-Prabhu/Python/blob/master/Python%20Coding%20Interview%20Prep/Python%20Coding%20Interview%20Questions%20(Beginner%20to%20Advanced).md" rel="external nofollow">Python Coding Interview Questions (Beginner to Advanced)</a>
	</li>
</ul>

<h2 id="-10">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/%D8%A3%D9%87%D9%85-%D8%A7%D9%84%D8%A3%D8%B3%D8%A6%D9%84%D8%A9-%D8%A7%D9%84%D9%86%D8%B8%D8%B1%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D9%8A-%D9%82%D8%AF-%D8%AA%D8%B7%D8%B1%D8%AD-%D9%81%D9%8A-%D8%A7%D9%84%D9%85%D9%82%D8%A7%D8%A8%D9%84%D8%A7%D8%AA-%D9%84%D8%AA%D9%88%D8%B8%D9%8A%D9%81-%D9%85%D8%B7%D9%88%D8%B1-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2595/" rel="">أهم الأسئلة النظرية التي قد تطرح في المقابلات لتوظيف مطور بايثون</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/general/%D8%A3%D9%87%D9%85-%D8%A3%D8%B3%D8%A6%D9%84%D8%A9-%D8%A7%D9%84%D9%85%D9%82%D8%A7%D8%A8%D9%84%D8%A7%D8%AA-%D9%84%D8%AA%D9%88%D8%B8%D9%8A%D9%81-%D9%85%D8%B7%D9%88%D8%B1-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A3%D9%85%D8%A7%D9%85%D9%8A%D8%A9-r2569/" rel="">أهم أسئلة المقابلات لتوظيف مطور واجهة أمامية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/entrepreneurship/tips/%D9%83%D9%8A%D9%81-%D8%AA%D8%AD%D8%B6%D8%B1-%D9%84%D9%85%D9%82%D8%A7%D8%A8%D9%84%D8%A9-%D8%B9%D9%85%D9%84-%D9%86%D8%A7%D8%AC%D8%AD%D8%A9%D8%9F-r1036/" rel="">كيف تحضر لمقابلة عمل ناجحة؟</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/advanced/%D9%83%D9%8A%D9%81-%D8%AA%D8%AD%D8%B6%D8%B1-%D9%84%D8%A3%D8%B3%D8%A6%D9%84%D8%A9-%D9%85%D9%82%D8%A7%D8%A8%D9%84%D8%A9-%D8%B9%D9%85%D9%84-%D9%85%D9%87%D9%86%D8%AF%D8%B3-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A7%D8%AA-r751/" rel="">كيف تحضر لأسئلة مقابلة عمل مهندس البرمجيات</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2596</guid><pubDate>Wed, 13 Aug 2025 16:02:01 +0000</pubDate></item><item><title>&#x623;&#x647;&#x645; &#x627;&#x644;&#x623;&#x633;&#x626;&#x644;&#x629; &#x627;&#x644;&#x646;&#x638;&#x631;&#x64A;&#x629; &#x627;&#x644;&#x62A;&#x64A; &#x642;&#x62F; &#x62A;&#x637;&#x631;&#x62D; &#x641;&#x64A; &#x627;&#x644;&#x645;&#x642;&#x627;&#x628;&#x644;&#x627;&#x62A; &#x644;&#x62A;&#x648;&#x638;&#x64A;&#x641; &#x645;&#x637;&#x648;&#x631; &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646;</title><link>https://academy.hsoub.com/programming/python/%D8%A3%D9%87%D9%85-%D8%A7%D9%84%D8%A3%D8%B3%D8%A6%D9%84%D8%A9-%D8%A7%D9%84%D9%86%D8%B8%D8%B1%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D9%8A-%D9%82%D8%AF-%D8%AA%D8%B7%D8%B1%D8%AD-%D9%81%D9%8A-%D8%A7%D9%84%D9%85%D9%82%D8%A7%D8%A8%D9%84%D8%A7%D8%AA-%D9%84%D8%AA%D9%88%D8%B8%D9%8A%D9%81-%D9%85%D8%B7%D9%88%D8%B1-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2595/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2025_05/-----.png.c44adb6a7305d89ee1e9a2df378c78bf.png" /></p>
<p>
	تُعَد لغة بايثون من أكثر لغات البرمجة قوةً وانتشارًا، فهي تدخل في معظم البنى الرقمية العصرية. ونظرًا لتنوع المسارات الوظيفية التي قد يوفرها احتراف البرمجة بلغة بايثون، سيركز المطوّر غالبًا على إتقان أساسيات مسار أو مسارين ثم يبحث عن فرصة عمل كمتدرب. ويَعُدّ أغلب المطوروين هذه الخطوة على أنها الخطوة الأصعب في المسيرة المهنية، ويعاني الكثيرون في اجتيازها.
</p>

<p>
	لهذا السبب، نوجه مقالنا إلى مطوري بايثون الذين يستعدون للدخول إلى سوق العمل. ونناقش فيه أهم النقاط التي قد يُسألون عنها في مقابلات العمل، سواءً لناحية استيعابهم مفاهيم اللغة الأساسية أو لتحديد إمكانياتهم التقنية وقدرتهم على إيجاد الحلول.
</p>

<p>
	سنتحدث فيما يلي عن بعض الأسئلة التي تُطرح على مطوري بايثون في مقابلات العمل، وقد قدمناها وفق أقسام تغطي أساسيات اللغة، ثم مكتبات بايثون، وأطر العمل التي تعتمد على بايثون.
</p>

<h2>
	أسئلة عامة
</h2>

<p>
	فيما يلي أبرز الأسئلة التي قد تطرح على المتقدمين لوظيفة مطوري بايثون في مقابلات التوظيف.
</p>

<h3 id="-1">
	لماذا اخترت العمل مع بايثون؟ ما مميزاتها
</h3>

<p>
	للإجابة على هذا السؤال، لا بد من توضيح أن <a href="https://academy.hsoub.com/programming/python/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1815/" rel="">بايثون</a> هي لغة واسعة الانتشار تعتمد عليها كبرى الشركات العالمية، ومطلوبة بشدة في سوق العمل، نظرًا لإمكانياتها الكبيرة. ويعزز ذلك الكمّ الهائل من المكتبات التي تغطي تقريبًا جميع الاحتياجات في العالم الرقمي مثل تحليل البيانات وتعلم الآلة والذكاء الصنعي وتطبيقات الويب.
</p>

<p>
	أما ما <a href="https://academy.hsoub.com/programming/python/%D9%85%D9%85%D9%8A%D8%B2%D8%A7%D8%AA-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86/" rel="">يميز بايثون</a> فبدايةً هي سهولة الصياغة اللغوية، فهي قريبة من الإنكليزية، إلى جانب وجود فكرة الإزاحة لتميز الكتل عن بعضها، مما يجعلها ذلك أسهل من ناحية الفهم والقراءة؛ إلى جانب أن قدرة بايثون على تحديد نوع المتغير دون التصريح عنه يُعَد فعالًا جدًا ومريحًا؛ أما الشيء الأهم، فهو وجود كم هائل من المكتبات التي تغطي معظم احتياجاتي كمطور دون إعادة اختراع العجلة من جديد، إضافةً إلى إمكانية استخدام اللغة لتطوير أي شيء تقريبًا دون الحاجة إلى الاستعانة بلغات أخرى.
</p>

<h3 id="dynamicallytyped">
	ما المقصود بأن لغة بايثون ديناميكية النمط dynamically typed
</h3>

<p>
	بايثون لغة ديناميكية في تحديد نوع المتغير، فهي تحدد تلقائيًا نوع المتغير بمجرد إسناد قيمة له. فإن أسندنا له قيمةً صحيحة، فسيكون المتحول صحيحًا، ونصيًا إن أسندنا إليه نصًا، بالتالي لا حاجة للتصريح عن نوع المتغير.
</p>

<h3 id="-2">
	كيف نحدد نوع متغير ما إن احتجنا إلى ذلك
</h3>

<p>
	لا يمكن تحديد نوع المتغير عند التصريح عنه؛ إذ يرمي عندها المفسّر Interpreter خطأً. ولحل الأمر نستخدم التابع <code>()type</code> لتحديد نوع المتغير والتعليمة <code>is</code> لمقارنة نوعه مع الأنواع التي تدعمها لغة بايثون وتكون نتيجة العملية نتيجة منطقيةً بوليانية (صحيح أو خاطئ)، وعندها يُنفذ الشرط المتعلق بالنوع أو لا يُنفّذ.
</p>

<h3 id="listtuple">
	قارن بين <code>list</code> و <code>tuple</code> في بايثون، ووضّح ذلك من خلال مثال
</h3>

<p>
	يقدم كلا النوعين أسلوبًا لترتيب البيانات وفق تسلسل محدد لتخزينها، مثل عرض قائمة بالمقاسات المتاحة لسلعة ما؛ أما وجه الاختلاف بينهما، فهو قابلية التعديل Immutability؛ فالقوائم من النوع <code><a href="https://wiki.hsoub.com/Python/list" rel="external">list</a></code> ديناميكية، ويمكن تعديل عناصرها وإضافة أو حذف عناصر بعد إنشائها وفي أي وقت؛ في حين لا يمكن تعديل أي شيء بالقوائم من النوع <code><a href="https://wiki.hsoub.com/Python/tuple" rel="external">tuple</a></code> بعد إنشائها.
</p>

<p>
	مع ذلك لكل نوع استخدامه المناسب، حيث نستخدم القائمة <code>list</code> مثلًا لتخزين الاسم المستعار لمستخدم وعمره ومكان إقامته وبريده الإلكتروني، فقد يرغب المستخدم بتغيير أي منها؛ في حين نستخدم القائمة <code>tuple</code> لتخزين أيام الأسبوع مثلًا فهي لن تتغير.
</p>

<h3 id="positional">
	كيف نمرر إلى دالة أربعة وسطاء موضعيين Positional دون أن تصرح عن أربع معاملات
</h3>

<p>
	نصرح في هذه الحالة عن الدالة بالطريقة التقليدية ثم نسمي معاملًا واحدًا فقط يبدأ بالرمز <code>*</code> كالتالي:
</p>

<pre class="ipsCode">def func(*args):
</pre>

<p>
	بهذا الشكل يمكن أن نمرر للدالة أي عدد من الوسطاء، وsنصل إلى أي وسيط من خلال دليل المعامل <code>args</code> مثل <code>args[2]</code> لاستخدام قيمة الوسيط الثالث.
</p>

<h3 id="-3">
	متى يحافظ وسيط ممر إلى دالة على قيمته بعد أن تحدّثها الدالة
</h3>

<p>
	تمرر بايثون الوسطاء إلى الدوال على شكل مرجع إلى كائن، لهذا نحن أمام سلوكين في هذه الحالة:
</p>

<ul>
	<li>
		الوسيط القابل للتعديل Mutable مثل القوائم <code>list</code> سيحتفظ المتغير عندها بالتعديلات التي أجرتها الدالة
	</li>
	<li>
		الوسيط غير القابل للتعديل مثل المتغيرات الصحيحة <a href="https://wiki.hsoub.com/Python#%D8%A7%D9%84%D8%A3%D8%B9%D8%AF%D8%A7%D8%AF_%D8%A7%D9%84%D8%B5%D8%AD%D9%8A%D8%AD%D8%A9_(int)" rel="external">int</a> والنصوص <a href="https://wiki.hsoub.com/Python#%D8%A7%D9%84%D8%B3%D9%84%D8%A7%D8%B3%D9%84_%D8%A7%D9%84%D9%86%D8%B5%D9%8A%D8%A9_(str)" rel="external">str</a> لن تتغير عندها قيمة المتغير خارج الدالة
	</li>
</ul>

<h3 id="decorators">
	ما هي المزخرفات Decorators في بايثون؟
</h3>

<p>
	<a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%85%D8%B2%D8%AE%D8%B1%D9%81%D8%A7%D8%AA-decorators-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r303/" rel="">المزخرفات Decorators</a> تقنيًا هي دوال تقبل دالة أخرى كوسيط لها وتعمل على زيادة وظائف هذه الدالة أو تغيير وظيفتها وإعادة هذه الدالة بشكلها المحسّن دون الحاجة إلى تغيير الشيفرة الأصلية لها.
</p>

<h3 id="classobjectinstance">
	ما الفرق بين الصنف Class والكائن Object والنسخة instance في بايثون؟
</h3>

<p>
	تُعَد هذه المفاهيم أساسيةً في <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%83%D8%A7%D8%A6%D9%86%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D9%88%D8%AC%D9%87-object-oriented-programming-%D9%88%D8%A7%D9%84%D8%A3%D8%B5%D9%86%D8%A7%D9%81-classes-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2154/" rel="">البرمجة كائنية التوجه <abbr title="Object-Oriented Programming | البرمجة كائنية التوجه"><abbr title="Object-Oriented Programming | البرمجة كائنية التوجه">OOP</abbr></abbr></a>، فالصنف في بايثون هو مخطط أو هيكل للكائن، تُعرَّف فيه المتغيرات والدوال التي تصف خصائص الكائن وطريقة عمله أو الوظائف التي يقوم بها؛ أما الكائن، فهو كيان فعلي حقيقي تأخذ حيزًا من الذاكرة بٌنيت على أساس مخطط الصنف. والنسخة هي مصطلح يُستخدم لوصف الكائن بعد إنشائه، خاصةً عند وجود أكثر من كائن ومن نفس الصنف.
</p>

<h3 id="__init____new____call__">
	ما عمل الدوال الخاصة  <code>__init__</code> و <code>__new__</code> و <code>__call__</code> في بايثون؟
</h3>

<p>
	تسمى هذه الدوال الخاصة باسم الدوال السحرية Magic Methods، حيث تتحكم بايثون من خلالها بسلوك الكائنات Objects بطرق خاصة، وتستخدم الدوال المعنية على النحو الآتي:
</p>

<ul>
	<li>
		تُستخدم الدالة <code>__new__</code> للتحكم في عملية إنشاء كائن من الصنف، وتُستدعى قبل <code>__init__</code> تُفيد في حالات خاصة مثل حالة نمط التصميم <a href="https://wiki.hsoub.com/Design_Patterns/singleton" rel="external">singleton</a> أو Metaclass
	</li>
	<li>
		تُستخدم الدالة <code>__init__</code> لتهيئة خصائص الكائن Object عند إنشائه، وتُستدعى مباشرة بعد <code>__new__</code>
	</li>
	<li>
		تُستخدم الدالة <code>__call__</code> لجعل الكائن قابلًا للاستدعاء كأنه دالة، بحيث يمكن تنفيذ كود عند استدعاء هذا الكائن مباشرةً
	</li>
</ul>

<h3 id="metaclass">
	وضح مفهوم الصنف Metaclass في بايثون
</h3>

<p>
	Metaclass هو صنف خاص في بايثون مهمته تعريف سلوك الأصناف، حيث يتحكم بإنشاء وتعديل وتحديد سلوك هذه الأصناف قبل بناء كائنات منها. ينتُج عن Metaclass في العادة أصناف أخرى، فبينما تُعطي الأصناف Classes كائنات Objects، يعطينا الصنف Metacalss أصنافًا مخصصةً أو يغير خصائص وتوابع صنف ديناميكيًا.
</p>

<h3 id="singleton">
	ما هو نمط المفردة Singleton في بايثون وأين يُستخدم
</h3>

<p>
	نمط المفردة <a href="https://wiki.hsoub.com/Design_Patterns/singleton" rel="external">singleton</a> هو نموذج في تصميم الأصناف يفرض على الصنف عدم إنتاج أكثر من نسخة منه، وهكذا سيعيد الصنف نفس الكائن عند استدعائه مرةً أخرى، أو عند إنشاء نسخة أخرى عنه. ولهذا النموذج أهميته في حالات استخدام معينة مثل بناء كائنات تسجيل الدخول إلى تطبيق مثلًا أو الحاجة لوجود كائن واحد فقط يؤمن اتصالًا مع قاعدة بيانات.
</p>

<h3 id="overriding">
	اشرح مفهوم التجاوز Overriding في بايثون
</h3>

<p>
	يرتبط هذا المفهوم بالبرمجة كائنية التوجه في بايثون وبالوراثة تحديدًا، فلو كان لدينا صنف ابن Class يرث من صنف آخر أب وعرّفنا تابع Method في الصنف الابن بنفس البصمة Signature للدالة الموجودة في الصنف الأب، فإن دالة الابن ستتجاوز وتلغي وتستبدل دالة الأب تلقائيًا. تدعم بايثون التجاوز، وتقدم التابع <code>super</code> إن أردنا استدعاء تابع العنصر الأب الأصلي ضمن الابن.
</p>

<h3 id="iterators">
	ماهي المكررات Iterators في بايثون
</h3>

<p>
	المكررات <a href="https://wiki.hsoub.com/Python/iterators" rel="external">Iterators</a> في بايثون هي آلية تسمح لنا بالتنقل عبر مجموعة من العناصر مثل القوائم <a href="https://wiki.hsoub.com/Python/list" rel="external"><code>list</code></a> أو <a href="https://wiki.hsoub.com/Python/tuples" rel="external">الصفوف</a> أو حتى <a href="https://wiki.hsoub.com/Python/str" rel="external"> السلاسل النصية </a> تدريجيًا. وبدون الحاجة إلى حفظ جميع العناصر في الذاكرة مرةً واحدة.
</p>

<p>
	يمكننا تحويل أي مجموعة قابلة للتكرار إلى مكرر باستخدام التابع <code>()iter</code>الذي يعيد لنا مكررًا يمكن استخدامه للتنقل عبر العناصر، فلكي نتقل من عنصر إلى عنصر آخر في المكرر، سنستخدم الدالة <code>()next</code>، وفي حال انتهت العناصر سنحصل على استنثاء من نوع <code>StopIteration</code> لنعرف أننا وصلنا لنهاية عناصر المكرر. فيما يلي مثال لمكرر سلسلة نصية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3304_22" style=""><span class="pln">text </span><span class="pun">=</span><span class="pln"> </span><span class="str">"Ali"</span><span class="pln">

</span><span class="com"># تحويل النص إلى مكرر</span><span class="pln">
text_iterator </span><span class="pun">=</span><span class="pln"> iter</span><span class="pun">(</span><span class="pln">text</span><span class="pun">)</span><span class="pln">

</span><span class="com"># التنقل عبر الأحرف باستخدام </span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="pln">next</span><span class="pun">(</span><span class="pln">text_iterator</span><span class="pun">))</span><span class="pln">  </span><span class="com"># Output: A</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="pln">next</span><span class="pun">(</span><span class="pln">text_iterator</span><span class="pun">))</span><span class="pln">  </span><span class="com"># Output: l</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="pln">next</span><span class="pun">(</span><span class="pln">text_iterator</span><span class="pun">))</span><span class="pln">  </span><span class="com"># Output: i</span><span class="pln">

</span><span class="com"># محاولة الوصول إلى حرف غير موجود</span><span class="pln">
</span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">next</span><span class="pun">(</span><span class="pln">text_iterator</span><span class="pun">))</span><span class="pln">
</span><span class="kwd">except</span><span class="pln"> </span><span class="typ">StopIteration</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">"وصلنا إلى نهاية المكرر"</span><span class="pun">)</span></pre>

<h3 id="with">
	حدد استخدامات الكلمة المفتاحية <code>with</code> في بايثون مع ذكر مثال حول ذلك
</h3>

<p>
	تُستخدم <code>with</code> غالبًا لإدارة الموارد التي يعمل عليها التطبيق كالملفات وقواعد البيانات والاتصالات الشبكية، فمن خلالها سنضمن حيازة المورد واستخدامه بأمان، وإغلاقه تلقائيًا بعد الانتهاء منه حتى في حال وقوع أخطاء؛ فعندما نريد فتح ملف مثلًا، سنستخدم التعليمة <code>open</code> لفتح الملف، ولا بد من إغلاق الملف يدويًا عند الانتهاء باستخدام <code>close</code>، لكن عند استخدام <code>with</code> قبل <code>open</code>، فلا حاجة عندها لإغلاق الملف وسيُغلق تلقائيًا.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3304_24" style=""><span class="kwd">with</span><span class="pln"> open</span><span class="pun">(</span><span class="str">"file.txt"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"r"</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> file</span><span class="pun">:</span><span class="pln">
    content </span><span class="pun">=</span><span class="pln"> file</span><span class="pun">.</span><span class="pln">read</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">content</span><span class="pun">)</span></pre>

<h3 id="threadprocess">
	ما الفرق بين الخيط Thread والعملية Process
</h3>

<p>
	الخيط هو آلية لتنفيذ مجموعة تعليمات متزامنة مع مجموعة تعليمات أخرى لكن باستخدام نفس الموارد، كمساحة الذاكرة والملفات مثلًا.
</p>

<p>
	توحي الخيوط بمعالجة التعليمات على التوازي، لكن ما يجري في الواقع هو الانتقال السريع بين خيوط المعالجة، كما تتبادل الخيوط بيانات فيما بينها لتنسيق ماركة الموارد؛ أما العملية Process فهي آلية مشابهة لكن لكل عملية معزولة ولها مواردها الخاصة ولا تتشارك العمليات الموارد والبيانات فيما بينها إلا إن طُلب منها صراحة فعل ذلك بآليات تواصل خاصة.
</p>

<h3 id="gil">
	ما هي فكرة GIL في بايثون، وما تأثيراتها على تنفيذ الشيفرة
</h3>

<p>
	تعدّ GIL أو مايعرف بالقفل العام للمفسر Global Interpreter Lock آليةً مهمة في بايثون تمنع أكثر من خيط معالجة Thread من تنفيذ شيفرة بايثون في نفس اللحظة ضمن نفس العملية. بمعنى آخر، حتى لو كانت لدينا عدة خيوط تعمل معًا، فإن GIL يسمح فقط لخيط واحد بتنفيذ تعليمات بشيفرة البايت Bytecode في كل وقت.
</p>

<p>
	يؤثر وجود GIL بوضوح على أداء البرامج التي تعتمد على المعالجة المكثفة للمعالج CPU-bound، مثل معالجة الصور وتدريب نماذج تعلم الآلة والعمليات الحسابية الثقيلة، وذلك لأنه يمنع الاستفادة الكاملة من جميع أنوية المعالج المتاحة، مما يؤدي إلى أداء أقل من المتوقع.
</p>

<p>
	مع ذلك، يمكن التغلب على هذه المشكلة باستخدام تقنية المعالجة المتعددة Multi-Processing بدلًا من الخيوط، بحيث تعمل كل عملية مستقلة بدون مشاركة GIL؛ كما يكمننا استخدام توزيعات بايثون لا تحتوي على GIL مثل Jython أو IronPython. ومن الحلول المتاحة أيضًا الاعتماد على مكتبات مكتوبة بلغة C أو لغات أخرى نتفادى قيد GIL، مثل NumPy وتنسرفلو <a href="https://academy.hsoub.com/programming/artificial-intelligence/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%85%D9%86%D8%B5%D8%A9-%D8%AA%D9%86%D8%B3%D8%B1%D9%81%D9%84%D9%88-tensorflow-%D9%84%D9%84%D8%B0%D9%83%D8%A7%D8%A1-%D8%A7%D9%84%D8%A7%D8%B5%D8%B7%D9%86%D8%A7%D8%B9%D9%8A-r2491/" rel="">TensorFlo</a>.
</p>

<h3 id="pythoncpythonjython">
	هل هناك فرق بين Python و CPython و Jython
</h3>

<p>
	بالطبع، فبايثون Python هي مجموعة من المواصفات التي تحدد صياغة اللغة، بينما CPython و Jython هما طريقتان لتطبيق وتنفيذ هذه المواصفات؛ حيث أن CPython هي تنفيذ بايثون الرسمي بلغة C، أي أنها كُتبت باستخدام لغة C وتُصرّف إلى شيفرة البايت bytecode قبل أن ينفذها مفسّر مبني بلغة C؛ بينما Jython هي تنفيذ لبايثون بلغة جافا، فقد كُتبت باستخدام جافا وتُنفّذ باستخدام <a href="https://academy.hsoub.com/programming/java/%D8%A2%D9%84%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%A7%D9%84%D8%A7%D9%81%D8%AA%D8%B1%D8%A7%D8%B6%D9%8A%D8%A9-java-virtual-machine-r964/" rel="">آلة جافا الافتراضية JVM</a> وتدعم كل مكتبات جافا.
</p>

<h2>
	أسئلة حول المكتبات Libraries
</h2>

<p>
	فيما يلي أبرز الأسئلة التي قد تطرح على المتقدمين لوظيفة مطوري بايثون في مقابلات التوظيف حول مكتبات بايثون.
</p>

<h3 id="ososenviron">
	ما هي وظيفة المكتبة المضمّنة <code>OS</code>؟ وما عمل <code>os.environ</code>
</h3>

<p>
	تقدم هذه المكتبة وظائف للتفاعل مع نظام التشغيل، بما في ذلك إدارة الملفات ومعالجة العمليات وإدارة استدعاء وظائف نظام التشغيل؛ أما <code>os.environ</code>، فهو كائن شبيه <a href="https://academy.hsoub.com/programming/python/%D9%81%D9%87%D9%85-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%85%D9%8A%D8%B3-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-r743/" rel="">بالقواميس</a> يُستخدم للعمل مع متغيرات البيئة في حالات عديدة مثل ضبط الإعدادات والتعامل مع البيانات الحساسة بأمان.
</p>

<h3 id="asyncio">
	ماهي وظيفة المكتبة <code>asyncio</code> ومتى تُستخدم
</h3>

<p>
	تُستخدم مكتبة <code>asyncio</code> في بايثون لتنفيذ عدة مهام في نفس الوقت، لاسيما في حال كانت هذه المهام تنتظر بيانات معينةم مثل انتظار رد من قاعدة بيانات أو من الإنترنت؛ فبدلًا من إيقاف البرنامج حتى ترد القاعدة أو السيرفر وحدوق حجب أو تعليق لعملية التنفيذ، تسمح <code>asyncio</code> للبرنامج بمواصلة عمله على مهام أخرى في نفس الوقت دون الحاجة لإنشاء خيوط Threads جديدة أو عمليات متعددة Multiprocessing.
</p>

<p>
	يتم استخدام هذه الوظيفة عندما تكون لدينا عمليات مقيدة بالمدخلات والمخرجات I/O bound، أي عمليات تتوقف على انتظار بيانات، مثل إرسال طلبات لشبكة، أو قراءة ملفات، أو عند الحاجة لتنفيذ عدة أشياء بنفس الوقت على التزامن بدون استهلاك موارد كثيرة، وبكفاءة أعلى من الخيوط Threads، فهي تعد أفضل من الخيوط في بعض الحالات لأن بايثون يعتمد تقنية GIL التي تمنع تشغيل خيوط كثيرة بفعالية عالية، وبهذا تجنبنا <code>asyncio</code> هذه المشكلة، فتكون أسرع وأخف على الذاكرة.
</p>

<h3 id="multithreadingmultiprocessing">
	ما هي المكتبات التي تدعم تقنيتي Multi-Threading و Multi-Processing في بايثون
</h3>

<p>
	تقدم بايثون المكتبة <code>threading</code> لدعم إنشاء خيوط المعالجة والتحكم بها. وتستخدم في العمليات المقيدة بأجهزة الدخل والخرج كتلك التي تنتظر الكتابة إلى وسيط أو القراءة منه؛ بينما تستخدم المكتبة <code>multiprocessing</code> لدعم العمليات المستقلة التي تقيدها الحمولة العالية للمعالج وعلى التوازي، مثل العمليات الحسابية المعقدة.
</p>

<p id="copydeepcopycopy">
	ما الفرق بين التابعين<code>()copy</code> و <code>()deepcopy</code> في المكتبة <code>copy</code>
</p>

<p>
	توفر المكتبة copy في بايثون طريقتين لنسخ الكائنات، هما:
</p>

<ol>
	<li>
		النسخ السطحي Shallow copy باستخدام الدالة <code>()copy</code>
	</li>
	<li>
		النسخ العميق Deep copy باستخدام الدالة <code>()deepcopy</code>
	</li>
</ol>

<p>
	في حالة النسخ السطحي ننسخ الكائن الخارجي فقط، لكن تبقى العناصر الداخلية للكائن مشتركةً بين النسخة الأصلية والنسخة الجديدة؛ بمعنى أن التغييرات التي نجريها على العناصر الداخلية ستؤثر على الأصل أيضًا؛ أما في حالة النسخ العميقة، فإننا ننسخ الكائن وكل عناصره الداخلية بالكامل، بحيث تكون النسخة مستقلةً تمامًا، وأي تعديل على النسخة لا يؤثر أبدًا على الأصل.
</p>

<p>
	على سبيل المثال لو عرفنا كائن قائمة <code>original</code> يحتوي على قائمتين داخلية، ونسخنا منه نسختين سطحية shallow_copy وعميقة deep_copy كما في المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3304_26" style=""><span class="kwd">import</span><span class="pln"> copy

</span><span class="com"># قائمة بداخلها قوائم </span><span class="pln">
original </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[[</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">3</span><span class="pun">],</span><span class="pln"> </span><span class="pun">[</span><span class="lit">4</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">,</span><span class="pln"> </span><span class="lit">6</span><span class="pun">]]</span><span class="pln">

</span><span class="com"># نسخ سطحي</span><span class="pln">
shallow_copy </span><span class="pun">=</span><span class="pln"> copy</span><span class="pun">.</span><span class="pln">copy</span><span class="pun">(</span><span class="pln">original</span><span class="pun">)</span><span class="pln">

</span><span class="com"># نسخ عميق</span><span class="pln">
deep_copy </span><span class="pun">=</span><span class="pln"> copy</span><span class="pun">.</span><span class="pln">deepcopy</span><span class="pun">(</span><span class="pln">original</span><span class="pun">)</span><span class="pln">

</span><span class="com"># نعدل على أحد العناصر الداخلية في القائمة الأصلية</span><span class="pln">
original</span><span class="pun">[</span><span class="lit">0</span><span class="pun">][</span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'X'</span><span class="pln">

</span><span class="com"># نطبع النتائج</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="str">"القائمة الأصلية:"</span><span class="pun">,</span><span class="pln"> original</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="str">"النسخة السطحية:"</span><span class="pun">,</span><span class="pln"> shallow_copy</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="str">"النسخة العميقة:"</span><span class="pun">,</span><span class="pln"> deep_copy</span><span class="pun">)</span></pre>

<p>
	فستتعدل النسخة السطحية مع هذا التغيير وتبقى النسخة العميقة على حالها كما يوضح الخرج التالي:
</p>

<pre class="ipsCode">القائمة الأصلية: [['X', 2, 3], [4, 5, 6]]
النسخة السطحية: [['X', 2, 3], [4, 5, 6]]
النسخة العميقة: [[1, 2, 3], [4, 5, 6]]
</pre>

<h3 id="-4">
	ما أشهر المكتبات المستخدمة للعمل مع المصفوفات والحسابات الرياضية في بايثون
</h3>

<p>
	توفر بايثون العديد من المكتبات القوية التي تساعدنا للعمل مع المصفوفات وإجراء الحسابات الرياضية المتقدمة وأشهرها:
</p>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D9%85%D9%81%D8%A7%D9%87%D9%8A%D9%85-%D9%85%D8%AA%D9%82%D8%AF%D9%85%D8%A9-%D8%AD%D9%88%D9%84-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-numpy-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1809/" rel="">Numpy</a>: تدعم المصفوفات متعددة الأبعاد، وتقدم مجموعةً كبيرة من الدوال الرياضية للعمل معها
	</li>
	<li>
		<code>SciPy</code>: مبنية على أساس <code>Numpy</code> وتقدم وظائف علمية وتقنية أكبر مثل التكامل والاستيفاء
	</li>
	<li>
		<code>Pandas</code>: مكتبة قوية لمعالجة وتحليل البيانات تقدم هياكل بيانات مثل إطار البيانات DataFrame لتسهيل التعامل مع الجداول والبيانات الضخمة
	</li>
	<li>
		<code>SymPy</code>: مكتبة للرياضيات الرمزية تتيح إجراء عمليات جبرية مثل حل المعادلات والتفاضل والتكامل
	</li>
</ul>

<h3 id="-5">
	ما أشهر مكتبات التعامل مع الرسوميات في بايثون
</h3>

<p>
	يمكن ذكر المكتبات الآتية:
</p>

<ul>
	<li>
		<code>Matplotlib</code>: لرسم صور ثنائية البعد ساكنة أو تفاعلية، وتقديم تمثيل رسومي متحرك
	</li>
	<li>
		<code>Pillow</code>: مكتبة لمعالجة الصور، تسمح بفتحها وتعديلها وحفظها
	</li>
	<li>
		<code>Plotly</code>: مكتبة قوية لإنشاء رسوميات تفاعلية ثلاثية الأبعاد وواجهات عرض ديناميكية تصلح للويب والتطبيقات التفاعلية
	</li>
</ul>

<h3 id="-7">
	ما المكتبات التي يشيع استخدامها في تطبيقات الرؤية الحاسوبية
</h3>

<p>
	أبرز هذه المكتبات هي:
</p>

<ul>
	<li>
		<code>OpenCV</code>: مكتبة بايثون قوية لإنجاز مهام الرؤية الحاسوبية، مثل معالجة الصور وتطبيق فلاتر مختلفة عليها وتحويلها بين أنظمة لونية مختلفة، كما تفيد في تحليل الفيديو واكتشاف الأشياء Object Detection
	</li>
	<li>
		<code>SimpleCV</code>: هي مكتبة سهلة الاستخدام للمبتدئين في الرؤية الحاسوبية، تتيح الوصول لأدوات معالجة الصور والكاميرا بسرعة وسهولة
	</li>
</ul>

<h3 id="-8">
	ما أشهر مكتبات بايثون لبرمجة الأنظمة المدمجة
</h3>

<p>
	يمكن الإشارة إلى مكتبتي:
</p>

<ul>
	<li>
		<code>MicroPython</code>: تتبع أسلوب خاص في بناء Python3 لبرمجة المتحكمات الصغرية Microcontroller وتجهيزات الأنظمة المدمجة مثل الحساسات
	</li>
	<li>
		<code>PySerial</code>: تقدم وظائف للتخاطب مع واجهات التخاطب التسلسلية مثل UART باستخدام بايثون
	</li>
</ul>

<h2>
	أسئلة حول اطر العمل Frameworks
</h2>

<p>
	فيما يلي أبرز الأسئلة التي قد تطرح على المتقدمين لوظيفة مطوري بايثون في مقابلات التوظيف حول اُطر العمل Frameworks
</p>

<h3 id="django">
	ما هو إطار العمل Django وما هي أهم ميزاته
</h3>

<p>
	يُعَد إطار جانغو <a href="https://academy.hsoub.com/programming/python/django/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-r2041/" rel="">Django</a> أحد أشهر أطر العمل الشاملة لتطوير تطبيقات الويب يوفر الكثير من الأدوات الجاهزة المضمّنة لتنفيذ الكثير من المهام، مثل إدارة المستخدمين، والتحقق من الصلاحيات، ونظام إدارة المحتوى، والتعامل مع قواعد البيانات؛ ويستخدم أساسًا لبناء التطبيقات المتوسطة إلى كبيرة.
</p>

<h3 id="flask">
	ماهو إطار العمل Flask وما هي أهم ميزاته؟
</h3>

<p>
	يُعَد فلاسك <a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r2201/" rel="">Flask</a> إطار خفيف ومرن يسمح بتطوير تطبيقات ويب بمرونة عالية، لكنه لا يتضمن الكثير من الأدوات الجاهزة مثل جانغو Django، ويتطلب خطوات إعداد وتهيئة أكثر منه، وهو يصلح لبناء تطبيقات ويب صغيرة إلى متوسطة، كما يستخدم في تقديم خدمات ويب مصغّرة Microservices بالإضافة إلى بناء واجهات برمجية.
</p>

<h3 id="fastapi">
	ماهو إطار العمل FastAPI وما هي أهم ميزاته؟
</h3>

<p>
	يُعَد FastAPI إطار عمل حديثًا وخفيف الوزن لبناء واجهات برمجة التطبيقات APIs سريعة وقوية وعالية الأداء باستخدام لغة بايثون.؛ هو يعتمد كثيرًا على ميزتين قويتين في بايثون هما:
</p>

<ul>
	<li>
		تحديد نوع البيانات المتوقعة للمتغيرات Type Hints
	</li>
	<li>
		البرمجة غير المتزامنة Asynchronous Programming
	</li>
</ul>

<h3 id="-6">
	ما هي اُطر العمل التي يشيع استخدامها في بناء الشبكات العصبونية وتعلم الآلة
</h3>

<p>
	يمكن ذكر الآتي:
</p>

<ul>
	<li>
		<code>TensorFlow</code>: إطار عمل يُستخدم لبناء وتدريب الشبكات العصبونية
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/artificial-intelligence/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%A8%D8%A7%D9%8A-%D8%AA%D9%88%D8%B1%D8%B4-pytorch-%D9%88%D8%A3%D9%87%D9%85%D9%8A%D8%AA%D9%87-%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D8%B0%D9%83%D8%A7%D8%A1-%D8%A7%D9%84%D8%A7%D8%B5%D8%B7%D9%86%D8%A7%D8%B9%D9%8A-r2311/" rel="">PyTorch</a>: إطار عمل مفتوح المصدر يقدم أدوات لبناء وتدريب الشبكات العصبونية
	</li>
</ul>

<h2 id="-9">
	ختامًا
</h2>

<p>
	بهذا نكون قد تعرفنا على أهم الأسئلة النظرية التي قد تطرح على المتقدم إلى وظيفة مطور بلغة بايثون، وسنواصل ففي مقالنا التالي توضيح أهم الأسئلة والتمارين العملية التي قد تطلب في المقابلات لتوظيف مطور بايثون، حتى تكون مستعدًا من كل النواحي لمقابلة العمل.
</p>

<h2 id="-10">
	المراجع
</h2>

<ul>
	<li>
		<a href="https://wiki.hsoub.com/Python" rel="external">توثيق بايثون باللغة العربية</a> على موقع موسوعة حسوب
	</li>
	<li>
		<a href="https://academy.hsoub.com/files/15-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86/" rel="">كتاب البرمجة بلغة بايثون من إعداد أكاديمية حسوب</a>
	</li>
	<li>
		<a href="https://www.javatpoint.com/advance-concepts-of-python-for-python-developer" rel="external nofollow">Advance Concepts of Python for Python Developer</a> 
	</li>
	<li>
		<a href="https://pwskills.com/blog/advanced-python-tutorials/" rel="external nofollow">Advanced Python Tutorials: Dive into Complex Concepts</a> <a href="https://www.testgorilla.com/blog/advanced-python-interview-questions/" rel="external nofollow">( advanced Python interview questions (and answers</a>
	</li>
	<li>
		<a href="https://github.com/Tanu-N-Prabhu/Python/blob/master/Python%20Coding%20Interview%20Prep/Python%20Coding%20Interview%20Questions%20(Beginner%20to%20Advanced).md" rel="external nofollow">Python Coding Interview Questions (Beginner to Advanced)</a>
	</li>
</ul>

<h2 id="-11">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/general/%D8%A3%D9%87%D9%85-%D8%A3%D8%B3%D8%A6%D9%84%D8%A9-%D8%A7%D9%84%D9%85%D9%82%D8%A7%D8%A8%D9%84%D8%A7%D8%AA-%D9%84%D8%AA%D9%88%D8%B8%D9%8A%D9%81-%D9%85%D8%B7%D9%88%D8%B1-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A3%D9%85%D8%A7%D9%85%D9%8A%D8%A9-r2569/" rel="">أهم أسئلة المقابلات لتوظيف مطور واجهة أمامية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/entrepreneurship/tips/%D9%83%D9%8A%D9%81-%D8%AA%D8%AD%D8%B6%D8%B1-%D9%84%D9%85%D9%82%D8%A7%D8%A8%D9%84%D8%A9-%D8%B9%D9%85%D9%84-%D9%86%D8%A7%D8%AC%D8%AD%D8%A9%D8%9F-r1036/" rel="">كيف تحضر لمقابلة عمل ناجحة؟</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/advanced/%D9%83%D9%8A%D9%81-%D8%AA%D8%AD%D8%B6%D8%B1-%D9%84%D8%A3%D8%B3%D8%A6%D9%84%D8%A9-%D9%85%D9%82%D8%A7%D8%A8%D9%84%D8%A9-%D8%B9%D9%85%D9%84-%D9%85%D9%87%D9%86%D8%AF%D8%B3-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A7%D8%AA-r751/" rel="">كيف تحضر لأسئلة مقابلة عمل مهندس البرمجيات</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2595</guid><pubDate>Wed, 06 Aug 2025 16:08:02 +0000</pubDate></item><item><title>&#x62A;&#x637;&#x648;&#x64A;&#x631; &#x62E;&#x627;&#x62F;&#x645; &#x648;&#x64A;&#x628; &#x628;&#x633;&#x64A;&#x637; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646;</title><link>https://academy.hsoub.com/programming/python/%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%AE%D8%A7%D8%AF%D9%85-%D9%88%D9%8A%D8%A8-%D8%A8%D8%B3%D9%8A%D8%B7-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2555/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2025_04/11.------.png.943b2baae5278adc5035729d1ad9bca8.png" /></p>
<p>
	لقد غير الويب وجه العالم، ولكن جوهره لم يتغير كثيرًا، إذ لا تزال معظم الأنظمة تتبع القواعد نفسها التي وضعها تيم بيرنرز لي Tim Berners-Lee، ولا تزال معظم خوادم الويب تتعامل مع أنواع الرسائل نفسها وبذات الطريقة، لذا سنوضح في هذا المقال الشيفرة البرمجية لخادم ويب بسيط محدود لا يتجازو كوده البرمجي 500 سطر، لنساعدكم على فهم كيفية عمل خادم الويب بشكل أفضل من خلال الاطلاع على شيفرته البرمجية مفتوحة المصدر.
</p>

<h2 id="-1">
	معلومات أساسية عن خوادم الويب
</h2>

<p>
	تستخدم جميع البرامج على الويب تقريبًا مجموعة من معايير الاتصال تسمى بروتوكولات الإنترنت Internet Protocols أو IPs اختصارًا، وما يهمنا من هذه البروتوكولات حاليًا هو بروتوكول التحكم في الإرسال <a href="https://academy.hsoub.com/devops/servers/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A8%D8%B1%D9%88%D8%AA%D9%88%D9%83%D9%88%D9%84-tcpip-%D9%88%D8%A8%D8%B9%D8%B6-%D9%85%D9%86-%D8%AE%D8%AF%D9%85%D8%A7%D8%AA%D9%87-r169/" rel="">Transmission Control Protocol</a> -أو TCP/IP اختصارًا- والذي يجعل الاتصال بين الحواسيب مشابهًا لقراءة وكتابة الملفات فنحن نفتح قناة اتصال، ثم نقرأ أو نكتب البيانات، ثم نغلق القناة.
</p>

<p>
	تتواصل البرامج التي تستخدم بروتوكول الإنترنت من خلال المقابس Sockets، حيث يمثل كل مقبس أحد طرفي قناة اتصال من نقطة إلى نقطة مثل الهاتف الذي يكون على أحد طرفي مكالمة هاتفية. يتكون المقبس من عنوان IP يحدد جهازًا معينًا ورقم منفذ على هذا الجهاز، حيث يتألف عنوان IP من أربعة أرقام مكونة من 8 بتات مثل ‎<code>174.136.14.108</code>‎، ويطابق<a href="https://academy.hsoub.com/devops/servers/%D9%85%D9%82%D8%AF%D9%91%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D9%85%D9%8F%D8%B5%D8%B7%D9%8E%D9%84%D8%AD%D8%A7%D8%AA-%D9%88%D8%B9%D9%86%D8%A7%D8%B5%D8%B1-%D9%88%D9%85%D9%81%D8%A7%D9%87%D9%8A%D9%85-%D9%86%D8%B8%D8%A7%D9%85-%D8%A3%D8%B3%D9%85%D8%A7%D8%A1-%D8%A7%D9%84%D9%86%D8%B7%D8%A7%D9%82%D8%A7%D8%AA-r5/" rel=""> نظام أسماء النطاقات Domain Name System أو DNS</a> هذه الأرقام مع أسماء مثل ‎<code>aosabook.org</code>‎، مما يسهل علينا تذكرها. رقم المنفذ هو رقم ضمن المجال من 0 إلى 65535، والذي يحدد المقبس الفريد على الجهاز المضيف، فإذا كان عنوان IP يشبه رقم هاتف شركة، فيمكن القول بأن رقم المنفذ هو امتداد لهذا الرقم. وتكون المنافذ ذات الأرقام من 0 إلى 1023 محجوزة ليستخدمها نظام التشغيل، ويمكن لأي شخص آخر استخدام المنافذ المتبقية.
</p>

<p>
	يمثل بروتوكول نقل النص التشعبي Hypertext Transfer Protocol أو HTTP إحدى الطرق التي تتبادل البرامج البيانات من خلالها عبر بروتوكول IP، ويُعدّ بروتوكول HTTP بسيطًا إذ يرسل العميل طلبًا يحدد من خلاله ما يريده عبر اتصال المقبس، ثم يرسل الخادم بعض البيانات في الاستجابة كما في الشكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="170967" href="https://academy.hsoub.com/uploads/monthly_2025_04/001HTTP.png.bc4e23b4904caec6b5eee8e9c43155ee.png" rel=""><img alt="001 دورة HTTP" class="ipsImage ipsImage_thumbnailed" data-fileid="170967" data-ratio="31.00" data-unique="at4s8tis7" style="width: 750px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2025_04/001HTTP.thumb.png.d92e9760e1007b037d5f6032e17efa33.png"></a>
</p>

<p style="text-align: center;">
	دورة HTTP
</p>

<p>
	عندما يرسل العميل أو متصفح الإنترنت طلبًا إلى الخادم، فإن الخادم يرد عليه بإرسال البيانات المطلوبة. ويمكن لهذه البيانات أن تأتي إما من ملف مخزَّن مسبقًا على القرص الصلب مثل صفحة HTML محفوظة سابقًا، أو يتم توليدها وإنشاؤها لحظيًا عند طلبها، أو الاثنين فقد تكون صفحة HTML ثابتة ويُضاف لها محتوى مولّد ديناميكيًا. فالخادم عند استجابته لا يكون محدودًا بمصدر واحد للبيانات، بل يمكنه أن يختار الأنسب أو يجمع بين عدة مصادر.
</p>

<p>
	تكمن أهمية طلب HTTP الذي يرسله المتصفح إلى السيرفر في أنه مجرد نص، إذ يمكن لأي برنامج إنشاء طلب أو تحليله. ولكن يجب أن يحتوي هذا النص على الأجزاء الموضحة في الشكل التالي لفهمه:
</p>

<p style="text-align: center;">
	<img alt="002 طلب HTTP" class="ipsImage ipsImage_thumbnailed" data-fileid="170968" data-ratio="59.40" data-unique="iji55p3nz" style="width: 500px; height: auto;" width="816" src="https://academy.hsoub.com/uploads/monthly_2025_04/002HTTP.png.9dc40eb2875f7c3f81333096eda2dc1a.png">
</p>

<p style="text-align: center;">
	طلب HTTP
</p>

<p>
	تكون طريقةHTTP إما GET لجلب المعلومات أو POST لإرسال بيانات النموذج أو رفع الملفات. ويحدد عنوان URL ما يريده العميل، حيث يكون في أغلب الأحيان مسارًا إلى ملف على القرص الصلب مثل ‎<code>/research/experiments.html</code>‎، ولكن يقرر الخادم ما يجب فعله به. ويكون إصدار HTTP إما HTTP/1.0 أو HTTP/1.1، ولسنا بصدد معرفة الاختلافات بينهما في نطاق المقال الحالي.
</p>

<p>
	ترويسات HTTP هي أزواج مفتاح وقيمة كما في الأمثلة الثلاث التالية:
</p>

<pre class="ipsCode">Accept: text/html
Accept-Language: en, fr
If-Modified-Since: 16-May-2024
</pre>

<p>
	قد تظهر المفاتيح عدة مرات في ترويسات HTTP على عكس جداول التعمية Hash التي تسمح بمفتاح واحد فقط لكل قيمة، وهذا يسمح للطلب بتحديد استعداده لقبول عدة أنواع من المحتوى. يتكون جسم الطلب من أي بيانات إضافية مرتبطة بالطلب، ويُستخدَم عند إرسال البيانات عبر نماذج الويب أو عند رفع الملفات وغير ذلك. يجب أن يكون هناك سطر فارغ بين آخر ترويسة وبداية جسم الطلب للإشارة إلى نهاية الترويسات، وتخبر إحدى الترويسات التي اسمها ‎<code>Content-Length</code>‎ الخادمَ بعدد البايتات المتوقع قراءتها في جسم الطلب.
</p>

<p>
	سيكون تنسيق الاستجابة HTTP Response مشابهًا لتنسيق الطلب HTTP Request كما في الشكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="170969" href="https://academy.hsoub.com/uploads/monthly_2025_04/003HTTP.png.2759860799bc35354762d4f3761d946f.png" rel=""><img alt="003 استجابة HTTP" class="ipsImage ipsImage_thumbnailed" data-fileid="170969" data-ratio="59.20" data-unique="vc9aue49a" style="width: 600px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2025_04/003HTTP.thumb.png.b943e3ddeefdfe7f8bef1bfe568cbdf1.png"></a>
</p>

<p style="text-align: center;">
	استجابة HTTP
</p>

<p>
	إذًا سيكون للإصدار والترويسات وجسم الطلب التنسيق والمعنى نفسه في كل من الطلب والاستجابة. لكن سيتضمن السطر الأول من الاستجابة رمز حالة Status Code الذي يشير إلى ما حدث عند معالجة الطلب، حيث يدل الرمز 200 على النجاح، ويدل الرمز 404 على أن المورد المطلوب غير موجود، ويكون للرموز الأخرى معانٍ أخرى، بينما تكرر عبارة الحالة هذه المعلومات في صيغة مفهومة بشريًا مثل ‎OK‎ أوnot found‎.
</p>

<p>
	هناك شيئان آخران إضافيان نحتاج إلى معرفتهما حول بروتوكول HTTP وهما أن بروتوكول HTTP عديم حالة Stateless، بمعنى أنه يعالج طلب بمفرده، ولا يتذكر الخادم أي شيء بين طلب وآخر ولا من أنت وما فعلته من قبل، حتى لو كنت قبل دقيقة على نفس الموقع. فإذا أراد تطبيقٌ ما تتبع شيء معين مثل هوية المستخدم، فسيفعل ذلك باستخدام ملف تعريف الارتباط Cookie، التي تتضمن سلسلة نصية من أحرف قصيرة يرسلها الخادم إلى العميل، ثم يعود العميل إلى الخادم لاحقًا. فإذا أراد المستخدم أداء بعض الوظائف التي تتطلب حفظ الحالة عبر عدة طلبات، فسينشئ الخادم ملف تعريف ارتباط جديد ويخزنه في قاعدة بيانات ويرسله إلى متصفحه، وبالتالي يستخدم الخادم ملف تعريف الارتباط للبحث عن معلومات حول ما يفعله المستخدم عندما يعيد المتصفح إرسال ملف تعريف الارتباط.
</p>

<p>
	الشيء الثاني الذي نحتاج إلى معرفته حول HTTP هو أنه يمكن إلحاق عنوان URL بمعاملات لتوفير مزيد من المعلومات، فمثلًا إذا استخدمنا محرك بحث، فيجب تحديد مصطلحات البحث الخاصة بنا بإضافتها إلى المسار في عنوان URL، ولكن يجب إضافة معاملات إلى عنوان URL من خلال إضافة الرمز <code>?</code> إلى عنوان URL متبوعًا بأزواج ‎<code>key=value</code>‎ نفصل بينها بالرمز <code>&amp;</code>. يطلب عنوان ‎<code><a href="http://www.google.ca?q=Python" ipsnoembed="true" rel="external nofollow">http://www.google.ca?q=Python</a></code>‎ من جوجل مثلًا البحث عن صفحات متعلقة ببايثون، فالمفتاح هو الحرف ‎<code>q</code>‎ والقيمة هي ‎<code>Python</code>‎، بينما يخبر الاستعلام ‎<code><a href="http://www.google.ca/search?q=Python&amp;client=Firefox" ipsnoembed="true" rel="external nofollow">http://www.google.ca/search?q=Python&amp;client=Firefox</a></code>‎ جوجل أننا نستخدم متصفح فايرفوكس. يمكننا تمرير أي معاملات نريدها هنا، ولكن يتحكم التطبيق الذي يعمل على موقع الويب في تحديد المعاملات التي يجب الانتباه إليها وكيفية تفسيرها.
</p>

<p>
	إذا كانت الرموز <code>?</code> و <code>&amp;</code> من المحارف الخاصة، فيحب الهروب منها، ويجب إيجاد طريقة لوضع علامة اقتباس مزدوجة ضمن سلسلة محارف مُحدَّدة بهذه العلامات، لذا يمثل معيار ترميز URL المحارف الخاصة باستخدام الرمز <code>%</code> متبوعًا برمز مكون من رقمين مع استبدال المسافات بالمحرف <code>+</code>، وبالتالي يمكنك البحث في جوجل عن ‎<code>grade = A+</code>‎ مع المسافات من خلال استخدام العنوان ‎<code><a href="http://www.google.ca/search?q=grade+%3D+A%2B" ipsnoembed="true" rel="external nofollow">http://www.google.ca/search?q=grade+%3D+A%2B</a></code>‎
</p>

<h2>
	مكتبات بايثون للاتصال بالخادم
</h2>

<p>
	للسهولة يستخدم معظم المطورين مكتبات خاصة لفتح المقابس وإنشاء طلبات HTTP وتحليل الاستجابات مثل مكتبة ‎<code>urllib2</code>‎ في بايثون، وهي بديل لمكتبة سابقة اسمها ‎<code>urllib</code>‎، كما تُعَد مكتبة ‎<code>Requests</code>‎ بديلًا أسهل في الاستخدام من مكتبة ‎<code>urllib2</code>‎. يستخدم المثال التالي مكتبة ‎<code>Requests</code>‎ لتنزيل صفحة من موقع للكتب:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7160_16" style=""><span class="kwd">import</span><span class="pln"> requests
response </span><span class="pun">=</span><span class="pln"> requests</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="str">'http://aosabook.org/en/500L/web-server/testpage.html'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">print</span><span class="pln"> </span><span class="str">'status code:'</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">status_code
</span><span class="kwd">print</span><span class="pln"> </span><span class="str">'content length:'</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">headers</span><span class="pun">[</span><span class="str">'content-length'</span><span class="pun">]</span><span class="pln">
</span><span class="kwd">print</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">text</span></pre>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_7160_18" style=""><span class="pln">status code: 200
content length: 61
</span><span class="tag">&lt;html&gt;</span><span class="pln">
  </span><span class="tag">&lt;body&gt;</span><span class="pln">
    </span><span class="tag">&lt;p&gt;</span><span class="pln">Test page.</span><span class="tag">&lt;/p&gt;</span><span class="pln">
  </span><span class="tag">&lt;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	يرسل التابع ‎<code>request.get</code>‎ طلب GET إلى الخادم ويعيد كائن يحتوي على الاستجابة، ويكون العضو ‎<code>status_code</code>‎ الخاص بهذا الكائن هو رمز حالة الاستجابة، والعضو ‎<code>content_length</code>‎ هو عدد البايتات في بيانات الاستجابة، و ‎<code>text</code>‎ هو البيانات الفعلية وهو في حالتنا صفحة HTML.
</p>

<h2 id="-2">
	تطوير خادم ويب بسيط
</h2>

<p>
	بعد أن تعرفنا على أساسيات عمل بروتوكلات الإنترنت، أصبحنا الآن مستعدين لكتابة خادم ويب بسيط، حيث تتألف فكرته الأساسية من الخطوات البسيطة التالية:
</p>

<ol>
	<li>
		الانتظار حتى يتصل شخص ما بهذا الخادم ويرسل طلب HTTP
	</li>
	<li>
		تحليل هذا الطلب
	</li>
	<li>
		اكتشاف ما يطلبه
	</li>
	<li>
		جلب هذه البيانات أو توليدها ديناميكيًا
	</li>
	<li>
		تنسيق البيانات بتنسيق HTML
	</li>
	<li>
		إرسال هذه البيانات
	</li>
</ol>

<p>
	تكون الخطوات 1 و 2 و 6 هي نفسها في جميع التطبيقات، لذا تحتوي مكتبة بايثون المعيارية على الوحدة ‎<code>BaseHTTPServer</code>‎ التي تنجز هذه الخطوات نيابة عنا، لذا نهتم فقط بالخطوات من 3 إلى 5 كما هو موضح فيما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7160_20" style=""><span class="kwd">import</span><span class="pln"> </span><span class="typ">BaseHTTPServer</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">RequestHandler</span><span class="pun">(</span><span class="typ">BaseHTTPServer</span><span class="pun">.</span><span class="typ">BaseHTTPRequestHandler</span><span class="pun">):</span><span class="pln">
    </span><span class="str">'''Handle HTTP requests by returning a fixed 'page'.'''</span><span class="pln">

    </span><span class="com"># الصفحة المراد إرسالها</span><span class="pln">
    </span><span class="typ">Page</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'''\
&lt;html&gt;
&lt;body&gt;
&lt;p&gt;Hello, web!&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;
'''</span><span class="pln">

    </span><span class="com"># معالجة طلب‫ GET‬</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> do_GET</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">send_response</span><span class="pun">(</span><span class="lit">200</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">send_header</span><span class="pun">(</span><span class="str">"Content-Type"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"text/html"</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">send_header</span><span class="pun">(</span><span class="str">"Content-Length"</span><span class="pun">,</span><span class="pln"> str</span><span class="pun">(</span><span class="pln">len</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="typ">Page</span><span class="pun">)))</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">end_headers</span><span class="pun">()</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">wfile</span><span class="pun">.</span><span class="pln">write</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="typ">Page</span><span class="pun">)</span><span class="pln">

</span><span class="com">#----------------------------------------------------------------------</span><span class="pln">

</span><span class="kwd">if</span><span class="pln"> __name__ </span><span class="pun">==</span><span class="pln"> </span><span class="str">'__main__'</span><span class="pun">:</span><span class="pln">
    serverAddress </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="str">''</span><span class="pun">,</span><span class="pln"> </span><span class="lit">8080</span><span class="pun">)</span><span class="pln">
    server </span><span class="pun">=</span><span class="pln"> </span><span class="typ">BaseHTTPServer</span><span class="pun">.</span><span class="typ">HTTPServer</span><span class="pun">(</span><span class="pln">serverAddress</span><span class="pun">,</span><span class="pln"> </span><span class="typ">RequestHandler</span><span class="pun">)</span><span class="pln">
    server</span><span class="pun">.</span><span class="pln">serve_forever</span><span class="pun">()</span></pre>

<p>
	يحلل الصنف ‎<code>BaseHTTPRequestHandler</code>‎ طلب HTTP الوارد ويحدد التابع التي يحتوي عليه، حيث إذا كان التابع هو GET، فسيستدعي هذا الصنف التابع ‎<code>do_GET</code>‎. يعدل الصنف ‎<code>RequestHandler</code>‎ هذا التابع لتوليد صفحة بسيطة ديناميكيًا، حيث يُخزَّن النص في متغير على مستوى الصنف ‎<code>Page</code>‎، والذي نرسله إلى العميل بعد إرسال رمز استجابة 200، وتخبر الترويسة ‎<code>Content-Type</code>‎ العميل بتفسير البيانات على أنها بتنسيق HTML، وتخبره بطول الصفحة. يؤدي استدعاء التابع ‎<code>end_headers</code>‎ إلى إدراج السطر الفارغ الذي يفصل الترويسات عن الصفحة نفسها.
</p>

<p>
	نحتاج إلى الأسطر الثلاثة الأخيرة لبدء تشغيل الخادم، حيث يحدد السطر الأول من هذه الأسطر عنوان الخادم كمجموعة مؤلفة من سلسلة نصية فارغة تعني التشغيل على الجهاز الحالي والقيمة 8080 التي تمثل المنفذ. ننشئ بعد ذلك نسخة من ‎<code>BaseHTTPServer.HTTPServer</code>‎ مع هذا العنوان واسم صنف معالج الطلب كمعاملات، ثم نطلب منه التشغيل إلى الأبد حتى إنهائه باستخدام الاختصار ‎<code>Control-C</code>‎.
</p>

<p>
	لن يؤدي تشغيل هذا البرنامج من سطر الأوامر إلى عرض أي شيء:
</p>

<pre class="ipsCode">$ python server.py
</pre>

<p>
	ننتقل بعد ذلك إلى العنوان ‎<code><a href="http://localhost:8080" ipsnoembed="true" rel="external nofollow">http://localhost:8080</a></code>‎ باستخدام المتصفح، وسنحصل على ما يلي في المتصفح:
</p>

<pre class="ipsCode">Hello, web!
</pre>

<p>
	وسنحصل على ما يلي في الصدفة Shell:
</p>

<pre class="ipsCode">127.0.0.1 - - [24/Feb/2014 10:26:28] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [24/Feb/2014 10:26:28] "GET /favicon.ico HTTP/1.1" 200 -
</pre>

<p>
	لم نطلب هنا ملفًا معينًا، لذا طلب المتصفح المجلد الجذري لأي شيء يقدمه الخادم <code>/</code> في السطر الأول، ويظهر السطر الثاني لأن المتصفح يرسل تلقائيًا طلبًا ثانيًا لملف صورة بالاسم ‎<code>/favicon.ico</code>‎، والذي سيعرضه كأيقونة في شريط العناوين عند وجوده.
</p>

<h2 id="-3">
	عرض القيم
</h2>

<p>
	لنعدل خادم الويب الخاص بنا لعرض بعض القيم المضمَّنة في طلب HTTP بهدف التدريب، حيث سنفصل إنشاء الصفحة عن إرسالها للحفاظ على ترتيب شيفرتنا البرمجية كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7160_22" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">RequestHandler</span><span class="pun">(</span><span class="typ">BaseHTTPServer</span><span class="pun">.</span><span class="typ">BaseHTTPRequestHandler</span><span class="pun">):</span><span class="pln">

    </span><span class="com"># قالب الصفحة</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> do_GET</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        page </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">create_page</span><span class="pun">()</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">send_page</span><span class="pun">(</span><span class="pln">page</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> create_page</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># نضع شيئًا هنا</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> send_page</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> page</span><span class="pun">):</span><span class="pln">
        </span><span class="com"># نضع شيئًا هنا</span></pre>

<p>
	ويكون التابع ‎<code>send_page</code>‎ كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7160_24" style=""><span class="pln">   </span><span class="kwd">def</span><span class="pln"> send_page</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> page</span><span class="pun">):</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">send_response</span><span class="pun">(</span><span class="lit">200</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">send_header</span><span class="pun">(</span><span class="str">"Content-type"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"text/html"</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">send_header</span><span class="pun">(</span><span class="str">"Content-Length"</span><span class="pun">,</span><span class="pln"> str</span><span class="pun">(</span><span class="pln">len</span><span class="pun">(</span><span class="pln">page</span><span class="pun">)))</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">end_headers</span><span class="pun">()</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">wfile</span><span class="pun">.</span><span class="pln">write</span><span class="pun">(</span><span class="pln">page</span><span class="pun">)</span></pre>

<p>
	يُعَد القالب الخاص بالصفحة التي نريد عرضها مجرد سلسلة نصية تحتوي على جدول HTML مع بعض العناصر النائبة Placeholders للتنسيق كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_7160_26" style=""><span class="pln">   Page = '''\
</span><span class="tag">&lt;html&gt;</span><span class="pln">
</span><span class="tag">&lt;body&gt;</span><span class="pln">
</span><span class="tag">&lt;table&gt;</span><span class="pln">
</span><span class="tag">&lt;tr&gt;</span><span class="pln">  </span><span class="tag">&lt;td&gt;</span><span class="pln">Header</span><span class="tag">&lt;/td&gt;</span><span class="pln">         </span><span class="tag">&lt;td&gt;</span><span class="pln">Value</span><span class="tag">&lt;/td&gt;</span><span class="pln">          </span><span class="tag">&lt;/tr&gt;</span><span class="pln">
</span><span class="tag">&lt;tr&gt;</span><span class="pln">  </span><span class="tag">&lt;td&gt;</span><span class="pln">Date and time</span><span class="tag">&lt;/td&gt;</span><span class="pln">  </span><span class="tag">&lt;td&gt;</span><span class="pln">{date_time}</span><span class="tag">&lt;/td&gt;</span><span class="pln">    </span><span class="tag">&lt;/tr&gt;</span><span class="pln">
</span><span class="tag">&lt;tr&gt;</span><span class="pln">  </span><span class="tag">&lt;td&gt;</span><span class="pln">Client host</span><span class="tag">&lt;/td&gt;</span><span class="pln">    </span><span class="tag">&lt;td&gt;</span><span class="pln">{client_host}</span><span class="tag">&lt;/td&gt;</span><span class="pln">  </span><span class="tag">&lt;/tr&gt;</span><span class="pln">
</span><span class="tag">&lt;tr&gt;</span><span class="pln">  </span><span class="tag">&lt;td&gt;</span><span class="pln">Client port</span><span class="tag">&lt;/td&gt;</span><span class="pln">    </span><span class="tag">&lt;td&gt;</span><span class="pln">{client_port}s</span><span class="tag">&lt;/td&gt;</span><span class="pln"> </span><span class="tag">&lt;/tr&gt;</span><span class="pln">
</span><span class="tag">&lt;tr&gt;</span><span class="pln">  </span><span class="tag">&lt;td&gt;</span><span class="pln">Command</span><span class="tag">&lt;/td&gt;</span><span class="pln">        </span><span class="tag">&lt;td&gt;</span><span class="pln">{command}</span><span class="tag">&lt;/td&gt;</span><span class="pln">      </span><span class="tag">&lt;/tr&gt;</span><span class="pln">
</span><span class="tag">&lt;tr&gt;</span><span class="pln">  </span><span class="tag">&lt;td&gt;</span><span class="pln">Path</span><span class="tag">&lt;/td&gt;</span><span class="pln">           </span><span class="tag">&lt;td&gt;</span><span class="pln">{path}</span><span class="tag">&lt;/td&gt;</span><span class="pln">         </span><span class="tag">&lt;/tr&gt;</span><span class="pln">
</span><span class="tag">&lt;/table&gt;</span><span class="pln">
</span><span class="tag">&lt;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span><span class="pln">
'''</span></pre>

<p>
	ويكون التابع الذي يملأ هذه الصفحة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7160_28" style=""><span class="pln">   </span><span class="kwd">def</span><span class="pln"> create_page</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        values </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="str">'date_time'</span><span class="pln">   </span><span class="pun">:</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">date_time_string</span><span class="pun">(),</span><span class="pln">
            </span><span class="str">'client_host'</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client_address</span><span class="pun">[</span><span class="lit">0</span><span class="pun">],</span><span class="pln">
            </span><span class="str">'client_port'</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">client_address</span><span class="pun">[</span><span class="lit">1</span><span class="pun">],</span><span class="pln">
            </span><span class="str">'command'</span><span class="pln">     </span><span class="pun">:</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">command</span><span class="pun">,</span><span class="pln">
            </span><span class="str">'path'</span><span class="pln">        </span><span class="pun">:</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">path
        </span><span class="pun">}</span><span class="pln">
        page </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="typ">Page</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(**</span><span class="pln">values</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> page</span></pre>

<p>
	لم تتغير البنية الرئيسية للبرنامج، فهو ينشئ نسخة من الصنف ‎<code>HTTPServer</code>‎ مع عنوان ومعالج الطلب كمعاملات، ثم يخدم الطلبات إلى الأبد، حيث إذا شغلنا البرنامج وأرسلنا طلب من متصفح على العنوان ‎<code><a href="http://localhost:8080/something.html" ipsnoembed="true" rel="external nofollow">http://localhost:8080/something.html</a></code>‎، فسنحصل على النتيجة التالية:
</p>

<pre class="ipsCode"> Date and time  Mon, 24 Feb 2014 17:17:12 GMT
  Client host    127.0.0.1
  Client port    54548
  Command        GET
  Path           /something.html
</pre>

<p>
	لم نحصل على خطأ 404 بالرغم من أن الصفحة ‎<code>something.html</code>‎ غير موجودة كملف على القرص الصلب، وسبب ذلك هو أن خادم الويب هو مجرد برنامج، ويمكنه أن يتخذ أي إجراء بناءً على البرمجة التي أعد بها،  ويمكنه بدلاً من إرجاع صفحة 404 Not Found -إذا كان الملف غير موجود- أن يرسل صفحة افتراضية مع رسالة تخبر المستخدم أن الصفحة غير موجودة، أو يرسل صفحة عشوائية مثلاً صفحة من ويكيبيديا أو صفحة أخرى تفاعلية، أو يُعيد توجيهنا إلى صفحة أخرى
</p>

<h2 id="-4">
	إرسال الصفحات الثابتة
</h2>

<p>
	أبسط شكل من أشكال خوادم الويب هو الذي يرسل ملفات HTML أو أي موارد أخرى كما هي إلى المتصفح، لذا سنعمل في هذه الخطوة على توفير صفحات الويب من القرص الصلب بدلًا من توليدها تلقائيًا، حيث سنبدأ بإعادة كتابة التابع ‎<code>do_GET</code>‎ كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7160_30" style=""><span class="pln">   </span><span class="kwd">def</span><span class="pln"> do_GET</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">

            </span><span class="com"># اكتشاف المطلوب بالضبط</span><span class="pln">
            full_path </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">getcwd</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">path

            </span><span class="com"># المطلوب غير موجود</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">exists</span><span class="pun">(</span><span class="pln">full_path</span><span class="pun">):</span><span class="pln">
                </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">ServerException</span><span class="pun">(</span><span class="str">"'{0}' not found"</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">path</span><span class="pun">))</span><span class="pln">

            </span><span class="com"># المطلوب هو ملف</span><span class="pln">
            </span><span class="kwd">elif</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">isfile</span><span class="pun">(</span><span class="pln">full_path</span><span class="pun">):</span><span class="pln">
                self</span><span class="pun">.</span><span class="pln">handle_file</span><span class="pun">(</span><span class="pln">full_path</span><span class="pun">)</span><span class="pln">

            </span><span class="com"># المطلوب هو شيء لا نستطيع معالجته</span><span class="pln">
            </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
                </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">ServerException</span><span class="pun">(</span><span class="str">"Unknown object '{0}'"</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">path</span><span class="pun">))</span><span class="pln">

        </span><span class="com"># معالجة الأخطاء</span><span class="pln">
        </span><span class="kwd">except</span><span class="pln"> </span><span class="typ">Exception</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> msg</span><span class="pun">:</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">handle_error</span><span class="pun">(</span><span class="pln">msg</span><span class="pun">)</span></pre>

<p>
	يفترض هذا التابع أنه يمكن توفير أي ملفات موجودة ضمن المجلد الذي يعمل فيه خادم الويب، والذي نحصل عليه باستخدام التابع ‎<code>os.getcwd</code>‎، ويدمجه مع المسار المقدم في عنوان URL الذي تضعه المكتبة في ‎<code>self.path</code>‎ تلقائيًا، ويبدأ هذا المسار دائمًا بالرمز <code>/</code> للحصول على مسار الملف الذي يريده المستخدم.
</p>

<p>
	إن لم يكن هذا المسار موجودًا أو إن لم يكن ملفًا، فسيبلغ التابع عن خطأ من خلال رفع استثناء والتقاطه، بينما إذا كان المسار يتطابق مع ملف، فسيستدعي تابعًا مساعدًا بالاسم ‎<code>handle_file</code>‎ لقراءة المحتويات وإعادتها. يقرأ التابع التالي مثلًا الملف فقط ويستخدم التابع ‎<code>send_content</code>‎ الموجود مسبقًا لإرساله إلى العميل:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7160_32" style=""><span class="pln">   </span><span class="kwd">def</span><span class="pln"> handle_file</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> full_path</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
            </span><span class="kwd">with</span><span class="pln"> open</span><span class="pun">(</span><span class="pln">full_path</span><span class="pun">,</span><span class="pln"> </span><span class="str">'rb'</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> reader</span><span class="pun">:</span><span class="pln">
                content </span><span class="pun">=</span><span class="pln"> reader</span><span class="pun">.</span><span class="pln">read</span><span class="pun">()</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">send_content</span><span class="pun">(</span><span class="pln">content</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">except</span><span class="pln"> </span><span class="typ">IOError</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> msg</span><span class="pun">:</span><span class="pln">
            msg </span><span class="pun">=</span><span class="pln"> </span><span class="str">"'{0}' cannot be read: {1}"</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">path</span><span class="pun">,</span><span class="pln"> msg</span><span class="pun">)</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">handle_error</span><span class="pun">(</span><span class="pln">msg</span><span class="pun">)</span></pre>

<p>
	نلاحظ هنا أننا نفتح الملف في الوضع الثنائي الممثل بالحرف ‎<code>b</code>‎ في ‎<code>rb</code>‎، هذا يعني أن بايثون تقرأ الملف كما هو دون محاولة تعديل أو تغيير تنسيق النصوص مثل نهاية السطور. وتُعَد قراءة الملف بالكامل في الذاكرة عند تقديمه فكرة سيئة في الحياة الواقعية، فقد يحتوي الملف على عدة جيجابايتات من بيانات الفيديو، ولن نتطرق إلى هذه الحالة في مقالنا.
</p>

<p>
	نحتاج إلى كتابة تابع لمعالجة الأخطاء وقالب لصفحة الإبلاغ عن الأخطاء كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7160_34" style=""><span class="pln">   </span><span class="typ">Error_Page</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">"""\
        &lt;html&gt;
        &lt;body&gt;
        &lt;h1&gt;Error accessing {path}&lt;/h1&gt;
        &lt;p&gt;{msg}&lt;/p&gt;
        &lt;/body&gt;
        &lt;/html&gt;
        """</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> handle_error</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> msg</span><span class="pun">):</span><span class="pln">
        content </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="typ">Error_Page</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">path</span><span class="pun">=</span><span class="pln">self</span><span class="pun">.</span><span class="pln">path</span><span class="pun">,</span><span class="pln"> msg</span><span class="pun">=</span><span class="pln">msg</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">send_content</span><span class="pun">(</span><span class="pln">content</span><span class="pun">)</span></pre>

<p>
	يعمل هذا البرنامج بنجاح، ولكن توجد مشكلة فيه تتمثل في أنه يعيد دائمًا رمز الحالة 200 حتى عندما لا تكون الصفحة المطلوبة موجودة. تحتوي الصفحة المرسلة في هذه الحالة على رسالة خطأ، ولكن لن يعرف المتصفح بفشل الطلب لأنه لا يستطيع قراءة اللغة الإنجليزية، لذا نحتاج إلى تعديل التابعين ‎<code>handle_error</code>‎ و ‎<code>send_content</code>‎ كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7160_36" style=""><span class="pln">   </span><span class="com"># معالجة الكائنات غير المعروفة</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> handle_error</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> msg</span><span class="pun">):</span><span class="pln">
        content </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="typ">Error_Page</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">path</span><span class="pun">=</span><span class="pln">self</span><span class="pun">.</span><span class="pln">path</span><span class="pun">,</span><span class="pln"> msg</span><span class="pun">=</span><span class="pln">msg</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">send_content</span><span class="pun">(</span><span class="pln">content</span><span class="pun">,</span><span class="pln"> </span><span class="lit">404</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># إرسال المحتوى الفعلي</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> send_content</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> content</span><span class="pun">,</span><span class="pln"> status</span><span class="pun">=</span><span class="lit">200</span><span class="pun">):</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">send_response</span><span class="pun">(</span><span class="pln">status</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">send_header</span><span class="pun">(</span><span class="str">"Content-type"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"text/html"</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">send_header</span><span class="pun">(</span><span class="str">"Content-Length"</span><span class="pun">,</span><span class="pln"> str</span><span class="pun">(</span><span class="pln">len</span><span class="pun">(</span><span class="pln">content</span><span class="pun">)))</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">end_headers</span><span class="pun">()</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">wfile</span><span class="pun">.</span><span class="pln">write</span><span class="pun">(</span><span class="pln">content</span><span class="pun">)</span></pre>

<p>
	لا نرفع الاستثناء ‎<code>ServerException</code>‎ عند عدم العثور على ملف، بل نولد صفحة خطأ بدلًا من ذلك، فالغرض من هذا الاستثناء هو الإشارة إلى خطأ في شيفرة الخادم، بينما تظهر صفحة الخطأ التي أنشأها التابع ‎<code>handle_error</code>‎ عندما يخطئ المستخدم في شيء ما مثل إرسال عنوان URL لملف غير موجود.
</p>

<p>
	<strong>ملاحظة</strong>: سنستخدم التابع ‎<code>handle_error</code>‎ عدة مرات في هذا المقال، بما في ذلك الحالات لا يكون فيها رمز الحالة <code>404</code> مناسبًا، لذا لنحاول التفكير في كيفية توسيع هذا البرنامج بحيث يمكن توفير رمز استجابة الحالة بسهولة.
</p>

<h2 id="-5">
	سرد محتويات المجلدات
</h2>

<p>
	يمكننا تعليم خادم الويب لعرض قائمة بمحتويات مجلد عندما يكون المسار في عنوان URL يمثل مجلدًا وليس ملفًا، كما يمكن جعله يبحث في هذا المجلد عن الملف ‎<code>index.html</code>‎ لعرضه وعرض قائمة بمحتويات المجلد فقط إن لم يكن هذا الملف موجودًا.
</p>

<p>
	سيؤدي بناء ذلك في التابع ‎<code>do_GET</code>‎ خطأ، إذ سيحتوي التابع الناتج على تداخل طويل من تعليمات ‎<code>if</code>‎ التي تتحكم في سلوكيات الخادم الخاصة، فالحل الصحيح هو العودة إلى الخطوة السابقة وحل المشكلة العامة من خلال معرفة ما يجب فعله بعنوان URL، لذا سنعيد كتابة التابع ‎<code>do_GET</code>‎ كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7160_38" style=""><span class="pln">   </span><span class="kwd">def</span><span class="pln"> do_GET</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">

            </span><span class="com"># اكتشاف المطلوب بالضبط</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">full_path </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">getcwd</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">path

            </span><span class="com"># اكتشاف كيفية معالجة المطلوب</span><span class="pln">
            </span><span class="kwd">for</span><span class="pln"> case </span><span class="kwd">in</span><span class="pln"> self</span><span class="pun">.</span><span class="typ">Cases</span><span class="pun">:</span><span class="pln">
                handler </span><span class="pun">=</span><span class="pln"> case</span><span class="pun">()</span><span class="pln">
                </span><span class="kwd">if</span><span class="pln"> handler</span><span class="pun">.</span><span class="pln">test</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
                    handler</span><span class="pun">.</span><span class="pln">act</span><span class="pun">(</span><span class="pln">self</span><span class="pun">)</span><span class="pln">
                    </span><span class="kwd">break</span><span class="pln">

        </span><span class="com"># معالجة الأخطاء</span><span class="pln">
        </span><span class="kwd">except</span><span class="pln"> </span><span class="typ">Exception</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> msg</span><span class="pun">:</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">handle_error</span><span class="pun">(</span><span class="pln">msg</span><span class="pun">)</span></pre>

<p>
	تبقى الخطوة الأولى نفسها، والتي تتمثل في اكتشاف المسار الكامل للشيء المطلوب، ثم تتكرر هذه النسخة من الشيفرة البرمجية ضمن حلقة على مجموعة من الحالات المخزنة في قائمة بدلًا من استخدام مجموعة من الاختبارات المضمنة، ويكون لكل حالة كائن مع تابعين هما: ‎<code>test</code>‎ الذي يخبرنا ما إذا كان الخادم قادرًا على معالجة الطلب، و ‎<code>act</code>‎ الذي يتخذ بعض الإجراءات، حيث يُعالَج الطلب ونخرج من الحلقة بمجرد العثور على الحالة الصحيحة.
</p>

<p>
	تغير أصناف الحالات الثلاثة التالية سلوك الخادم السابق كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7160_40" style=""><span class="kwd">class</span><span class="pln"> case_no_file</span><span class="pun">(</span><span class="pln">object</span><span class="pun">):</span><span class="pln">
    </span><span class="str">'''File or directory does not exist.'''</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> handler</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">exists</span><span class="pun">(</span><span class="pln">handler</span><span class="pun">.</span><span class="pln">full_path</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> act</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> handler</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">ServerException</span><span class="pun">(</span><span class="str">"'{0}' not found"</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">handler</span><span class="pun">.</span><span class="pln">path</span><span class="pun">))</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> case_existing_file</span><span class="pun">(</span><span class="pln">object</span><span class="pun">):</span><span class="pln">
    </span><span class="str">'''File exists.'''</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> handler</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">isfile</span><span class="pun">(</span><span class="pln">handler</span><span class="pun">.</span><span class="pln">full_path</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> act</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> handler</span><span class="pun">):</span><span class="pln">
        handler</span><span class="pun">.</span><span class="pln">handle_file</span><span class="pun">(</span><span class="pln">handler</span><span class="pun">.</span><span class="pln">full_path</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> case_always_fail</span><span class="pun">(</span><span class="pln">object</span><span class="pun">):</span><span class="pln">
    </span><span class="str">'''Base case if nothing else worked.'''</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> handler</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">True</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> act</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> handler</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">ServerException</span><span class="pun">(</span><span class="str">"Unknown object '{0}'"</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">handler</span><span class="pun">.</span><span class="pln">path</span><span class="pun">))</span></pre>

<p>
	نبني قائمة معالجات الحالات في بداية الصنف ‎<code>RequestHandler</code>‎ كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7160_42" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">RequestHandler</span><span class="pun">(</span><span class="typ">BaseHTTPServer</span><span class="pun">.</span><span class="typ">BaseHTTPRequestHandler</span><span class="pun">):</span><span class="pln">
    </span><span class="str">'''
    If the requested path maps to a file, that file is served.
    If anything goes wrong, an error page is constructed.
    '''</span><span class="pln">

    </span><span class="typ">Cases</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">case_no_file</span><span class="pun">(),</span><span class="pln">
             case_existing_file</span><span class="pun">(),</span><span class="pln">
             case_always_fail</span><span class="pun">()]</span><span class="pln">

    </span><span class="pun">...</span><span class="pln">everything </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> before</span><span class="pun">...</span></pre>

<p>
	أدى هذا التعديل على شيفرتنا البرمجية إلى جعل الخادم أكثر تعقيدًا، إذ زاد حجم الملف من 74 إلى 99 سطرًا دون إنجاز وظيفة جديدة، ولكن سنرى الفائدة من هذا التعديل عندما نحاول تعليم الخادم عرض صفحة ‎<code>index.html</code>‎ لمجلد ما عند وجود هذه الصفحة، وعرض قائمة بمحتويات المجلد عند عدم وجودها، حيث يكون المعالج للحالة الأولى كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7160_44" style=""><span class="kwd">class</span><span class="pln"> case_directory_index_file</span><span class="pun">(</span><span class="pln">object</span><span class="pun">):</span><span class="pln">
    </span><span class="str">'''Serve index.html page for a directory.'''</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> index_path</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> handler</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">handler</span><span class="pun">.</span><span class="pln">full_path</span><span class="pun">,</span><span class="pln"> </span><span class="str">'index.html'</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> handler</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">isdir</span><span class="pun">(</span><span class="pln">handler</span><span class="pun">.</span><span class="pln">full_path</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">and</span><span class="pln"> \
               os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">isfile</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">index_path</span><span class="pun">(</span><span class="pln">handler</span><span class="pun">))</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> act</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> handler</span><span class="pun">):</span><span class="pln">
        handler</span><span class="pun">.</span><span class="pln">handle_file</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">index_path</span><span class="pun">(</span><span class="pln">handler</span><span class="pun">))</span></pre>

<p>
	يبني التابع المساعد ‎<code>index_path</code>‎ مسار الوصول إلى الملف ‎<code>index.html</code>‎، حيث يمنع وضع هذا التابع في معالج الحالات من تعقيد الصنف ‎<code>RequestHandler</code>‎ الرئيسي. ويتحقق التابع ‎<code>test</code>‎ مما إذا كان المسار يمثل مجلدًا يحتوي على الصفحة ‎<code>index.html</code>‎، ويطلب التابع ‎<code>act</code>‎ من معالج الطلب الرئيسي تقديم هذه الصفحة.
</p>

<p>
	التغيير الوحيد المطلوب في الصنف ‎<code>RequestHandler</code>‎ هو إضافة كائن ‎<code>case_directory_index_file</code>‎ إلى قائمة الحالات ‎<code>Cases</code>‎ كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7160_46" style=""><span class="pln">   </span><span class="typ">Cases</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">case_no_file</span><span class="pun">(),</span><span class="pln">
             case_existing_file</span><span class="pun">(),</span><span class="pln">
             case_directory_index_file</span><span class="pun">(),</span><span class="pln">
             case_always_fail</span><span class="pun">()]</span></pre>

<p>
	إذا كان المجلد لا يحتوي على صفحة ‎<code>index.html</code>‎، فسيبقى تابع الاختبار نفسه بحيث يتحقق مما إذا كان المسار يمثل مجلدًا يحتوي على الصفحة ‎<code>index.html</code>‎ دون إدراجها باستخدام ‎<code>not</code>‎.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7160_48" style=""><span class="kwd">class</span><span class="pln"> case_directory_no_index_file</span><span class="pun">(</span><span class="pln">object</span><span class="pun">):</span><span class="pln">
    </span><span class="str">'''Serve listing for a directory without an index.html page.'''</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> index_path</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> handler</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">handler</span><span class="pun">.</span><span class="pln">full_path</span><span class="pun">,</span><span class="pln"> </span><span class="str">'index.html'</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> handler</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">isdir</span><span class="pun">(</span><span class="pln">handler</span><span class="pun">.</span><span class="pln">full_path</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">and</span><span class="pln"> \
               </span><span class="kwd">not</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">isfile</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">index_path</span><span class="pun">(</span><span class="pln">handler</span><span class="pun">))</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> act</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> handler</span><span class="pun">):</span><span class="pln">
        </span><span class="pun">???</span></pre>

<p>
	يجب أن ينشئ التابع ‎<code>act</code>‎ قائمة بمحتويات المجلد ويعيدها، ولكن لا تسمح الشيفرة البرمجية الحالية بذلك، إذ يستدعي ‎<code>RequestHandler.do_GET</code>‎ التابع ‎<code>act</code>‎، لكنه لا يتوقع أو يتعامل مع قيمة معادة منه. لنضف الآن تابعًا إلى الصنف <code>‎RequestHandler</code>‎ لتوليد قائمة بمحتويات المجلد، ونستدعيه من التابع ‎<code>act</code>‎ الخاص بمعالج الحالة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7160_50" style=""><span class="kwd">class</span><span class="pln"> case_directory_no_index_file</span><span class="pun">(</span><span class="pln">object</span><span class="pun">):</span><span class="pln">
    </span><span class="str">'''Serve listing for a directory without an index.html page.'''</span><span class="pln">

    </span><span class="com"># يبقى‫ index_path و test كما في السابق‬</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> act</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> handler</span><span class="pun">):</span><span class="pln">
        handler</span><span class="pun">.</span><span class="pln">list_dir</span><span class="pun">(</span><span class="pln">handler</span><span class="pun">.</span><span class="pln">full_path</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">RequestHandler</span><span class="pun">(</span><span class="typ">BaseHTTPServer</span><span class="pun">.</span><span class="typ">BaseHTTPRequestHandler</span><span class="pun">):</span><span class="pln">

    </span><span class="com"># ما تبقى من الشيفرة البرمجية </span><span class="pln">

    </span><span class="com"># كيفية عرض قائمة المجلد</span><span class="pln">
    </span><span class="typ">Listing_Page</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'''\
        &lt;html&gt;
        &lt;body&gt;
        &lt;ul&gt;
        {0}
        &lt;/ul&gt;
        &lt;/body&gt;
        &lt;/html&gt;
        '''</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> list_dir</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> full_path</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
            entries </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">listdir</span><span class="pun">(</span><span class="pln">full_path</span><span class="pun">)</span><span class="pln">
            bullets </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">'&lt;li&gt;{0}&lt;/li&gt;'</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">e</span><span class="pun">)</span><span class="pln"> 
                </span><span class="kwd">for</span><span class="pln"> e </span><span class="kwd">in</span><span class="pln"> entries </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> e</span><span class="pun">.</span><span class="pln">startswith</span><span class="pun">(</span><span class="str">'.'</span><span class="pun">)]</span><span class="pln">
            page </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="typ">Listing_Page</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="str">'\n'</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">bullets</span><span class="pun">))</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">send_content</span><span class="pun">(</span><span class="pln">page</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">except</span><span class="pln"> </span><span class="typ">OSError</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> msg</span><span class="pun">:</span><span class="pln">
            msg </span><span class="pun">=</span><span class="pln"> </span><span class="str">"'{0}' cannot be listed: {1}"</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">path</span><span class="pun">,</span><span class="pln"> msg</span><span class="pun">)</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">handle_error</span><span class="pun">(</span><span class="pln">msg</span><span class="pun">)</span></pre>

<h2 id="cgi">
	بروتوكول CGI
</h2>

<p>
	لن يرغب معظم الأشخاص في تعديل شيفرة خادم الويب الخاص بهم لإضافة وظائف جديدة، لذا تدعم الخوادم آلية تسمى واجهة البوابة المشتركة Common Gateway Interface -أو CGI اختصارًا- والتي توفر طريقة معيارية لخادم الويب لتشغيل برنامج خارجي لتلبية الطلبات.
</p>

<p>
	لنفترض مثلًا أننا نريد تمكين الخادم من عرض الوقت المحلي في صفحة HTML في برنامج مستقل كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_7160_52" style=""><span class="pln">from datetime import datetime
print '''\
</span><span class="tag">&lt;html&gt;</span><span class="pln">
</span><span class="tag">&lt;body&gt;</span><span class="pln">
</span><span class="tag">&lt;p&gt;</span><span class="pln">Generated {0}</span><span class="tag">&lt;/p&gt;</span><span class="pln">
</span><span class="tag">&lt;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span><span class="pln">'''.format(datetime.now())</span></pre>

<p>
	نضيف معالج الحالة التالي ليشغّل خادم الويب هذا البرنامج:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7160_54" style=""><span class="kwd">class</span><span class="pln"> case_cgi_file</span><span class="pun">(</span><span class="pln">object</span><span class="pun">):</span><span class="pln">
    </span><span class="str">'''Something runnable.'''</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> handler</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">isfile</span><span class="pun">(</span><span class="pln">handler</span><span class="pun">.</span><span class="pln">full_path</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">and</span><span class="pln"> \
               handler</span><span class="pun">.</span><span class="pln">full_path</span><span class="pun">.</span><span class="pln">endswith</span><span class="pun">(</span><span class="str">'.py'</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> act</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> handler</span><span class="pun">):</span><span class="pln">
        handler</span><span class="pun">.</span><span class="pln">run_cgi</span><span class="pun">(</span><span class="pln">handler</span><span class="pun">.</span><span class="pln">full_path</span><span class="pun">)</span></pre>

<p>
	يكون الاختبار بسيطًا، حيث إذا انتهى مسار الملف باللاحقة ‎<code>.py</code>‎، فسيشغل الصنف ‎<code>RequestHandler</code>‎ هذا البرنامج.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7160_56" style=""><span class="pln">   </span><span class="kwd">def</span><span class="pln"> run_cgi</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> full_path</span><span class="pun">):</span><span class="pln">
        cmd </span><span class="pun">=</span><span class="pln"> </span><span class="str">"python "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> full_path
        child_stdin</span><span class="pun">,</span><span class="pln"> child_stdout </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">popen2</span><span class="pun">(</span><span class="pln">cmd</span><span class="pun">)</span><span class="pln">
        child_stdin</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
        data </span><span class="pun">=</span><span class="pln"> child_stdout</span><span class="pun">.</span><span class="pln">read</span><span class="pun">()</span><span class="pln">
        child_stdout</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">send_content</span><span class="pun">(</span><span class="pln">data</span><span class="pun">)</span></pre>

<p>
	ولكن يُعَد هذا الاختبار غير آمن، فإذا عرف شخصٌ ما مسار الوصول إلى ملف بايثون على الخادم، فسيسمح له بتشغيله دون القلق بشأن البيانات التي يمكنه الوصول إليها أو من احتوائه على حلقة لا نهائية أو أي شيء آخر. تستخدم شيفرتنا البرمجية دالة المكتبة ‎<code>popen2</code>‎ التي أُوقِفت مع استخدام وحدة <code>subprocess</code>، ولكن ‎<code>popen2</code>‎ هي الأداة الأنسب للاستخدام في مثالنا.
</p>

<p>
	تُعَد الفكرة الأساسية لبروتوكول CGI بسيطة بغض النظر عن هذه المشكلة الأمنية، حيث تتألف من الخطوات التالية:
</p>

<ol>
	<li>
		تشغيل البرنامج في عملية فرعية
	</li>
	<li>
		التقاط كل ما ترسله هذه العملية الفرعية إلى الخرج
	</li>
	<li>
		إرسال الخرج الناتج إلى العميل الذي قدم الطلب
	</li>
</ol>

<p>
	يكون بروتوكول CGI الكامل أكبر من ذلك، فمثلًا يسمح بالمعاملات في عنوان URL التي يمررها الخادم إلى البرنامج المُشغَّل، ولكن لا تؤثر هذه التفاصيل على البنية العامة للنظام التي أصبحت متداخلة، إذ كان لدى الصنف ‎<code>RequestHandler</code>‎ تابع واحد في البداية هو ‎<code>handle_file</code>‎ للتعامل مع المحتوى، وأضفنا حالتين خاصتين في التابعين ‎<code>list_dir</code>‎ و ‎<code>run_cgi</code>‎. ليست هذه التوابع الثلاثة في مكانها الصحيح لأن التوابع الأخرى تستخدمها، ويتمثل الحل في إنشاء صنف أب لجميع معالجات الحالات، ثم ننقل التوابع الأخرى إلى هذا الصنف إذا كانت مشتركة فقط بين معالجين أو أكثر. سيكون الصنف ‎<code>RequestHandler</code>‎ في النهاية كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7160_58" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">RequestHandler</span><span class="pun">(</span><span class="typ">BaseHTTPServer</span><span class="pun">.</span><span class="typ">BaseHTTPRequestHandler</span><span class="pun">):</span><span class="pln">

    </span><span class="typ">Cases</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">case_no_file</span><span class="pun">(),</span><span class="pln">
             case_cgi_file</span><span class="pun">(),</span><span class="pln">
             case_existing_file</span><span class="pun">(),</span><span class="pln">
             case_directory_index_file</span><span class="pun">(),</span><span class="pln">
             case_directory_no_index_file</span><span class="pun">(),</span><span class="pln">
             case_always_fail</span><span class="pun">()]</span><span class="pln">

    </span><span class="com"># كيفية عرض الخطأ</span><span class="pln">
    </span><span class="typ">Error_Page</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">"""\
        &lt;html&gt;
        &lt;body&gt;
        &lt;h1&gt;Error accessing {path}&lt;/h1&gt;
        &lt;p&gt;{msg}&lt;/p&gt;
        &lt;/body&gt;
        &lt;/html&gt;
        """</span><span class="pln">

    </span><span class="com"># تصنيف الطلب ومعالجته</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> do_GET</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">

            </span><span class="com"># اكتشاف المطلوب بالضبط</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">full_path </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">getcwd</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">path

            </span><span class="com"># اكتشاف كيفية معالجة المطلوب</span><span class="pln">
            </span><span class="kwd">for</span><span class="pln"> case </span><span class="kwd">in</span><span class="pln"> self</span><span class="pun">.</span><span class="typ">Cases</span><span class="pun">:</span><span class="pln">
                </span><span class="kwd">if</span><span class="pln"> case</span><span class="pun">.</span><span class="pln">test</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
                    case</span><span class="pun">.</span><span class="pln">act</span><span class="pun">(</span><span class="pln">self</span><span class="pun">)</span><span class="pln">
                    </span><span class="kwd">break</span><span class="pln">

        </span><span class="com"># معالجة الأخطاء</span><span class="pln">
        </span><span class="kwd">except</span><span class="pln"> </span><span class="typ">Exception</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> msg</span><span class="pun">:</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">handle_error</span><span class="pun">(</span><span class="pln">msg</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># معالجة الكائنات غير المعروفة</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> handle_error</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> msg</span><span class="pun">):</span><span class="pln">
        content </span><span class="pun">=</span><span class="pln"> self</span><span class="pun">.</span><span class="typ">Error_Page</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">path</span><span class="pun">=</span><span class="pln">self</span><span class="pun">.</span><span class="pln">path</span><span class="pun">,</span><span class="pln"> msg</span><span class="pun">=</span><span class="pln">msg</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">send_content</span><span class="pun">(</span><span class="pln">content</span><span class="pun">,</span><span class="pln"> </span><span class="lit">404</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># إرسال المحتوى الفعلي</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> send_content</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> content</span><span class="pun">,</span><span class="pln"> status</span><span class="pun">=</span><span class="lit">200</span><span class="pun">):</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">send_response</span><span class="pun">(</span><span class="pln">status</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">send_header</span><span class="pun">(</span><span class="str">"Content-type"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"text/html"</span><span class="pun">)</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">send_header</span><span class="pun">(</span><span class="str">"Content-Length"</span><span class="pun">,</span><span class="pln"> str</span><span class="pun">(</span><span class="pln">len</span><span class="pun">(</span><span class="pln">content</span><span class="pun">)))</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">end_headers</span><span class="pun">()</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">wfile</span><span class="pun">.</span><span class="pln">write</span><span class="pun">(</span><span class="pln">content</span><span class="pun">)</span></pre>

<p>
	ويكون الصنف الأب لمعالجات الحالة الخاصة بنا كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7160_60" style=""><span class="kwd">class</span><span class="pln"> base_case</span><span class="pun">(</span><span class="pln">object</span><span class="pun">):</span><span class="pln">
    </span><span class="str">'''Parent for case handlers.'''</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> handle_file</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> handler</span><span class="pun">,</span><span class="pln"> full_path</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
            </span><span class="kwd">with</span><span class="pln"> open</span><span class="pun">(</span><span class="pln">full_path</span><span class="pun">,</span><span class="pln"> </span><span class="str">'rb'</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> reader</span><span class="pun">:</span><span class="pln">
                content </span><span class="pun">=</span><span class="pln"> reader</span><span class="pun">.</span><span class="pln">read</span><span class="pun">()</span><span class="pln">
            handler</span><span class="pun">.</span><span class="pln">send_content</span><span class="pun">(</span><span class="pln">content</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">except</span><span class="pln"> </span><span class="typ">IOError</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> msg</span><span class="pun">:</span><span class="pln">
            msg </span><span class="pun">=</span><span class="pln"> </span><span class="str">"'{0}' cannot be read: {1}"</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">full_path</span><span class="pun">,</span><span class="pln"> msg</span><span class="pun">)</span><span class="pln">
            handler</span><span class="pun">.</span><span class="pln">handle_error</span><span class="pun">(</span><span class="pln">msg</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> index_path</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> handler</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">handler</span><span class="pun">.</span><span class="pln">full_path</span><span class="pun">,</span><span class="pln"> </span><span class="str">'index.html'</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> handler</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">assert</span><span class="pln"> </span><span class="kwd">False</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Not implemented.'</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> act</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> handler</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">assert</span><span class="pln"> </span><span class="kwd">False</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Not implemented.'</span></pre>

<p>
	ويكون المعالج لملف موجود مسبقًا كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7160_62" style=""><span class="kwd">class</span><span class="pln"> case_existing_file</span><span class="pun">(</span><span class="pln">base_case</span><span class="pun">):</span><span class="pln">
    </span><span class="str">'''File exists.'''</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> test</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> handler</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">isfile</span><span class="pun">(</span><span class="pln">handler</span><span class="pun">.</span><span class="pln">full_path</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> act</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> handler</span><span class="pun">):</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">handle_file</span><span class="pun">(</span><span class="pln">handler</span><span class="pun">,</span><span class="pln"> handler</span><span class="pun">.</span><span class="pln">full_path</span><span class="pun">)</span></pre>

<h2 id="-6">
	الخاتمة
</h2>

<p>
	من خلال فهم وتطبيق الأفكار الواردة في هذه المقال، سنتمكن من بناء خادم ويب مرن وسهل التوسع باستخدام بايثون، وسيسهل علينا إضافة مميزات جديدة أو تعديل السلوك دون التأثير على بقية النظام، جرب تعديل الشيفرة  وبناء الخادم الخاص بك إذ يمكن على سبيل المثال إضافة معالجات لحالات جديدة فإذا أردنا دعم حالة معينة مثل عرض صفحة خاصة للمستخدمين المسجلين فيمكن إضافة صنف جديد مع معالج لهذه الحالة ببساطة عن طريق تعديل الكود في RequestHandler دون التأثير على بقية الخادم.
</p>

<p>
	كما يمكن إضافة وظائف جديدة عبر كتابة برنامج CGI خارجي يمكنه معالجة نوع معين من الطلبات، مثل تحليل البيانات الواردة من نماذج الويب أو إجراء عمليات معقدة على الخادم، دون الحاجة لتعديل الشيفرة الأساسية للخادم.
</p>

<p>
	أو يمكن لخادم الويب أن يقرأ ملف إعدادات لتحميل الأصناف المخصصة لمعالجة الطلبات. فهذا يسمح لك بتغيير سلوك الخادم أو إضافة معالجات جديدة دون تعديل الشيفرة الأساسية، فقط بتعديل ملف الإعدادات، كما يمكن توسيع الخادم ليدعم أنواع محتوى إضافية على سبيل المثال، يمكنك إضافة دعم ملفات الفيديو أو الصور الضخمة عن طريق كتابة صنف يعالج هذه الأنواع ويعرض المحتوى بشكل فعال ودون التأثير على طلبات أخرى.
</p>

<p>
	ترجمة -وبتصرّف- للمقال <a href="https://aosabook.org/en/500L/a-simple-web-server.html" rel="external nofollow">A Simple Web Server</a> لصاحبه Greg Wilson
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/devops/networking/%D8%B4%D8%A8%D9%83%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A8%D8%B1%D9%88%D8%AA%D9%88%D9%83%D9%88%D9%84-ip-r497/" rel="">شبكة الإنترنت باستخدام بروتوكول IP</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r1524/" rel="">كيفية كتابة تطبيقات الويب</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/servers/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1-%D9%85%D8%AD%D9%84%D9%8A-r609/" rel="">إعداد خادم اختبار محلي</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D9%85%D8%B4%D8%A7%D8%B1%D9%8A%D8%B9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D8%B9%D9%85%D9%84%D9%8A%D8%A9-%D8%AA%D9%86%D8%A7%D8%B3%D8%A8-%D8%A7%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-r2185/" rel="">مشاريع بايثون عملية تناسب المبتدئين</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/networking/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%B9%D9%86%D8%A7%D9%88%D9%8A%D9%86-%D8%A8%D8%B1%D9%88%D8%AA%D9%88%D9%83%D9%88%D9%84-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA-%D9%88%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A7%D8%AA-%D8%A7%D9%84%D9%81%D8%B1%D8%B9%D9%8A%D8%A9-%D9%88%D8%A7%D9%84%D8%AA%D9%88%D8%AC%D9%8A%D9%87-%D8%BA%D9%8A%D8%B1-%D8%A7%D9%84%D8%B5%D9%86%D9%81%D9%8A-%D8%A8%D9%8A%D9%86-%D8%A7%D9%84%D9%86%D8%B7%D8%A7%D9%82%D8%A7%D8%AA-r593/" rel="">تعرف على عناوين بروتوكول الإنترنت والشبكات الفرعية والتوجيه غير الصنفي بين النطاقات</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2555</guid><pubDate>Tue, 15 Apr 2025 13:00:00 +0000</pubDate></item><item><title>&#x645;&#x642;&#x627;&#x631;&#x646;&#x629; &#x628;&#x64A;&#x646; &#x625;&#x637;&#x627;&#x631;&#x64A; &#x627;&#x644;&#x639;&#x645;&#x644; &#x62C;&#x627;&#x646;&#x63A;&#x648; Django &#x648;&#x641;&#x644;&#x627;&#x633;&#x643; Flask</title><link>https://academy.hsoub.com/programming/python/%D9%85%D9%82%D8%A7%D8%B1%D9%86%D8%A9-%D8%A8%D9%8A%D9%86-%D8%A5%D8%B7%D8%A7%D8%B1%D9%8A-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-%D9%88%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r2514/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2025_02/3.FlaskDjango.png.6b1d60ce7312c457ec89a76d34462d33.png" /></p>
<p>
	قد يكون البدء ببناء تطبيقات الويب بعد تعلم لغة البرمجة الشهيرة بايثون محمسًا جدًا، لكن بمجرد البدء بالاطلاع على خيارات أطر العمل Frameworks الرائجة فيها والأكثر نجاحًا، نجد الكثير من الخيارات المهمة التي سنحتاج بطبيعة الحال لفهم مميزاتها والاختلافات الأساسية بينها قبل اتخاذنا لقرار تعلمها واعتمادها بالعمل.
</p>

<p>
	في هذا المقال سوف نقارن بين أشهر إطاري عمل في <a href="https://academy.hsoub.com/programming/python/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-python-r2325/" rel="">لغة بايثون</a>، وهما إطار العمل جانغو Django وإطار العمل فلاسك Flask، من أجل تسهيل المفاضلة بينهما واختيار ما نحتاجه فعليًا لعملنا وإمكانياتنا.
</p>

<h2 id="django">
	إطار العمل جانغو Django
</h2>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="171169" href="https://academy.hsoub.com/uploads/monthly_2025_04/Django-Logo.png.17bffd1332ad9ab8b2a1af3acfffd5df.png" rel=""><img alt="Django-Logo.png" class="ipsImage ipsImage_thumbnailed" data-fileid="171169" data-ratio="43.80" data-unique="alx7mucql" style="width: 500px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2025_04/Django-Logo.thumb.png.6235b50fd2cf2cc5026fe9bcbcfa3bc2.png"></a>
</p>

<p>
	<a href="https://academy.hsoub.com/programming/python/django/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-r2041/" rel="">جانغو Django</a> هو إطار عمل مجّاني <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%A7-%D8%A7%D9%84%D9%85%D9%82%D8%B5%D9%88%D8%AF-%D8%A8%D9%85%D8%B5%D8%B7%D9%84%D8%AD-%D9%85%D9%81%D8%AA%D9%88%D8%AD-%D8%A7%D9%84%D9%85%D8%B5%D8%AF%D8%B1-open-source%D8%9F-r885/" rel="">مفتوح المصدر</a> تم إنشاؤه سنة 2005، وهو مخصص لبناء مواقع وتطبيقات الويب، من أهم ميزات جانغو هو مفهوم قابلية إعادة الاستخدام Reusability للمكونات، فهو يعتمد على مفهوم Multi apps ضمن نفس المشروع؛ كما يوفر يوفر أجزاء جاهزة للمشاريع، مثل قسم التحقق Authentication الذي يجب أن يتوفر في كل مشروع ويب.
</p>

<p>
	يُعَد إطار العمل جانغو واحدًا من أطر العمل الضخمة، إذ لا يحتاج في عملياته الرئيسية على المكتبات الخارجية، بل يمكن الاعتماد عليه لوحده لإنجاز تلك العمليات.
</p>

<h3>
	ميزات إطار العمل جانغو Django
</h3>

<p>
	يتميز إطار العمل جانغو Django بكونه:
</p>

<ul>
	<li>
		إطار عمل احترافي
	</li>
	<li>
		مناسب جدًا للمشاريع الكبيرة
	</li>
	<li>
		يحوي الكثير من الخصائص المضمنة افتراضيًا
	</li>
	<li>
		يدعم تعدد التطبيقات إفتراضيًا، مما يجعل من السهل على المطور كتابة كود يمكن إعادة إستخدامه في العديد من المشاريع
	</li>
	<li>
		متوافق جدًا مع المبرمجين ذوي الخبرة العالية
	</li>
	<li>
		يحتوي على دعم مجتمعي قوي
	</li>
</ul>

<h3 id="-2">
	أشهر المشاريع المبنية باستخدام جانغو Django
</h3>

<p>
	سنذكر في الآتي أبرز المشاريع العالمية الضخمة التي تم بناؤها بإطار العمل Django:
</p>

<ul>
	<li>
		Instagram
	</li>
	<li>
		Pinterest
	</li>
	<li>
		Udemy
	</li>
	<li>
		Coursera
	</li>
</ul>

<h2 id="flask">
	ما هو إطار العمل فلاسك Flask؟
</h2>

<p style="text-align: center;">
	<img alt="flask-logo1.png" class="ipsImage ipsImage_thumbnailed" data-fileid="171170" data-ratio="32.40" data-unique="3we0jkj4x" style="width: 500px; height: auto;" width="618" src="https://academy.hsoub.com/uploads/monthly_2025_04/flask-logo1.png.d51e42e3ebd2d7ae60a54cc7c3cdee02.png">
</p>

<p>
	<a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r2201/https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r2201/" rel="">فلاسك Flask</a> هو إطار عمل صغير Micro Framework تم إنشاؤه سنة 2010، مع ذلك فهو يقدم الميزات الأساسية لتطبيق الويب.
</p>

<p>
	يعتمد فلاسك كثيرًا على المكتبات الخارجية، ولايحتوي على معمارية محددة مثل MVC، مما يمنح مرونة في اختيار واستخدام الطريقة التي نريدها؛ كما لا يحتوي على ORM أساسي، ولكن يكفي استخدام مكتبات خارجية للقيام بوظيفة الـ ORM.
</p>

<p>
	يُعَد إطار العمل فلاسك Flask مناسبًا جدًا للمبتدئين الراغبين في تعلم استخدام أول إطار عمل، نظرًا لبساطته وسهولة تعلمه مقارنةً مع باقي أطر العمل الشهيرة الأخرى.
</p>

<h3>
	ميزات إطار العمل فلاسك Flask
</h3>

<p>
	يتميز إطار العمل فلاسك Flask بكونه:
</p>

<ul>
	<li>
		إطار عمل سهل
	</li>
	<li>
		مناسب للمبتدئين
	</li>
	<li>
		مناسب للمشاريع الصغيرة
	</li>
	<li>
		مرن وقابل للتخصيص
	</li>
	<li>
		متوافق مع المبرمجين ذوي الخبرة البسيطة والعادية
	</li>
	<li>
		يحتوي على مجتمع نشط
	</li>
</ul>

<h3>
	أشهر المشاريع المبنية باستخدام فلاسك Flask
</h3>

<p>
	من أشهر المشاريع المبنية بفلاسك Flask، نجد المشاريع العالمية الضخمة الآتية:
</p>

<ul>
	<li>
		Netflix
	</li>
	<li>
		Lyft
	</li>
	<li>
		Reddit
	</li>
	<li>
		Zillow
	</li>
	<li>
		MailGui
	</li>
</ul>

<h2 id="">
	الإختلافات الرئيسية بين جانغو وفلاسك
</h2>

<p>
	رغم أن كل من جانغو وفلاسك إطارين مميزين جدًا، إلا أن هناك بعض المعايير التي يحتاج المبرمج لمعرفتها من أجل تحديد أيهما أنسب له للعمل أو التعلم.
</p>

<p>
	سنقارن فيما يلي بين كل من جانغو وفلاسك من عدة جوانب كما يلي.
</p>

<h3>
	1. حسب طبيعة الاستخدام
</h3>

<p>
	يُعَد إطار العمل Django مناسبًا لبناء تطبيقات الويب الكبيرة والمعقدة وفي مشاريع التطوير الشاملة Full Stack التي تتطلب الكثير من الميزات المضمنة، مثل أنظمة إدارة المحتوى CMS، ومنصات التجارة الإلكترونية؛ إذ يتيح جانغو خاصية عمل متكاملة مع مكوناته المضمنة للتعامل مع المهام المختلفة، مثل المصادقة وإدارة قاعدة البيانات وتوجيه عنوان URL، وغيرها.
</p>

<p>
	على الجانب الآخر، يتم استخدام إطار العمل فلاسك لبناء واجهات برمجة تطبيقات RESTful والخدمات الصغيرة والنماذج الأولية. وبالنظر لطبيعة فلاسك وخفة وزنه وتصميمه البسيط ومرونته، فهو يمنح المطورين تحكمًا مناسبًا أكثر في اختيار المكتبات والمكونات التي يرغبون بها بما يتماشى مع حاجة الاستخدام.
</p>

<h3>
	2. حسب سهولة التعلم
</h3>

<p>
	لدى إطار العمل Flask متطلبات تعليم منخفضة نسبيًا تجعله في متناول المبتدئين والمطورين ذوي المعرفة الأساسية بلغة بايثون، وهو مناسب للراغبين في البدء بتعلم البرمجة بإطار عمل خفيف الوزن واستكشاف المزيد من المفاهيم المتقدمة تدريجيًا.
</p>

<p>
	على عكس ذلك، لدى إطار العمل Django متطلبات عالية وأكثر تعقيدًا للتمكن من التعامل مع كافة الميزات التي يوفرها، ويتطلب العمل به توفر خبرة متوسطة إلى متقدمة في لغة بايثون.
</p>

<p>
	لذا يمكن القول أنه رغم أن الأصل هو المفاضلة بين الإطارين حسب حاجة مشاريعنا، إلا أنه في حال أردنا تعلم أحدهما من أجل أغراض مختلفة، أو لغرض البدء بتعلم التعامل مع أطر العمل، فلن يكون من الجيد اختيار أحدهما على حساب الثاني بعشوائية في صالحنا. إذ لو كنا مبتدئين مثلًا ونريد تعلم كيفية التعامل مع اطر العمل، فسيكون إطار العمل Flask هو الخيار الأمثل لنا، عكس Django الذي سيكون الخيار السيء بهذه الحالة.
</p>

<p>
	أما في حال كنا مبرمجين متمكنين ونرغب بتعلم إطار عمل جديد مع معرفة وخبرة تتناسب مع متطلبات العمل بجانغو، فسيكون جانغو خيارًا أنسب لنا، نظرًا للخيارات الكثيرة التي يمنحنا إياها وتمكيننا من تطوير المشاريع الكبيرة.
</p>

<h3>
	3. حسب سهولةالاستخدام
</h3>

<p>
	كما ذكرنا سابقًا، فـ Flask بطبيعته أنسب للمشاريع الصغيرة وأكثر بساطة من Django، وهذا يمنح المطورين خاصةً المبتدئين منهم أريحيةً أكبر في العمل من خلاله، إذ يتسم بسهولة الاستخدام ويتطلب أيضًا عددًا أقل من أسطر التعليمات البرمجية مقارنة مع جانغو الذي يتطلب وجود خبرة عالية ومهارات برمجية متقدمة.
</p>

<h3>
	4. حسب التكلفة
</h3>

<p>
	رغم أن كلًا من Flask وDjango هما من أطر عمل Python المجانية ومفتوحة المصدر، إلا أنه نظرًا لكون إطار العمل Flask أقل احتواءًا على الميزات المدمجة ويحتاج لصيانة أكبر مقارنة مع إطار العمل جانغو، فهذا قد يجعله أكثر تكلفةً من جانغو في بعض الحالات.
</p>

<p>
	مع ذلك، تعتمد حاجتنا للميزات الإضافية على متطلبات عملنا، لذا قد لا تكون هناك تكاليف إضافية حتى عند استخدام فلاسك إن كان مشروعنا لا يتطلب ذلك. في حال كنا بحاجة إلى وظائف إضافية مدمجة أو نفضل إطار عمل أكثر شمولًا بالعمل، فقد يكون Django خيارًا أفضل هنا.
</p>

<h3>
	5. حسب الأداء والسرعة
</h3>

<p>
	يتميز إطار العمل فلاسك Flask بكونه أفضل من ناحية الأداء والسرعة وقابلية التوسع بالمشاريع الصغيرة، إذ يتميز بخفة وزن تسمح للمطورين ببناء تطبيقاتهم الصغيرة في وقت قصير جدًا وباستهلاك أقل للموارد مقارنةً مع إطار العمل Django.
</p>

<p>
	أما بالمشاريع الكبيرة، فإطار العمل جانغو Django بطبيعته أنسب لهذا النوع من المشاريع، إذ يحوي العديد من الخصائص المدمجة التي تسهل عملية التطوير، مما يتيح عملية تطوير أسرع وأسلس مقارنة مع إطار العمل فلاسك.
</p>

<h3>
	6. حسب قابلية التوسع
</h3>

<p>
	كلا الإطارين قابلين للتوسع، لكن كل حسب مجال استخدامه الأمثل. فـ Flask مثلًا يوفر قابلية جيدة للتوسعة بالمشاريع الصغيرة ومتوسطة الحجم، في حين أن Django ممتاز بقابليته للتوسع بالمشاريع الصغيرة والكبيرة رغم أنه أمثل للمشاريع الكبيرة بفضل الخصائص المدمجة المتاحة به.
</p>

<h3>
	7. حسب المرونة
</h3>

<p>
	يتميز فلاسك بمرونة عالية جدًا مقارنةً مع جانغو، إذ أن عدم احتواء Flask على الكثير من الخصائص المدمجة، يتيح للمطور قدرة عالية في التحكم بهيكل المشروع وتصميمه، ويمنح له حرية عالية في الخصخصة وانتقاء الأدوات والمكتبات الأنسب له، عكس Django الذي يكون مرتبطًا بأغلبه بالخصائص المدمجة به.
</p>

<h3>
	8. حسب التعامل مع قاعدة البيانات
</h3>

<p>
	يأتي جانغو مع نظام ORM يمكّن المبرمجين من التعامل بسهولة مع مجموعة متنوعة من قواعد البيانات العلائقية مثل PostgreSQL وSQLite و Prophet و MySQL وغيره، حيث يقدم ORM الدعم لإنشاء وإدارة عمليات ترحيل قاعدة البيانات.
</p>

<p>
	أما Flask فنظرًا لاعتماده على القابلية الكبيرة في التخصيص، فهو لا يضع قيودًا على كيفية تخزين البيانات، ويمكن للمطور اختيار ما يناسبه لأجل هذا الغرض. يعتمد فلاسك هنا على الإضافات الخارجية لترتيب تنفيذ عمليات قاعدة البيانات المشتركة مثل إضافة SQLAlchemy
</p>

<h3>
	9. حسب دعم المجتمع البرمجي
</h3>

<p>
	بالنسبة للمطورين، لا بد أن يكون لدى الإطار المختار مجتمع مستخدمين قوي، إذ توفر هذه المجتمعات قدرًا كبيرًا من المساعدة من خلال الرد على أية استفسارات قد نحتاج للرد بشأنها، كما يمكنها أيضًا تشجيع تطوير التطبيقات الجديدة المتعلقة بالإطار.
</p>

<p>
	بالنظر إلى موقع Github نجد عدد نجوم التقييمات متقارب بين الإطارين؛ أما حسب موقع Stackoverflow فنجد إنه يوجد أكثر من 300 ألف سؤال عن جانغو، بينما يوجد حوالي 55 ألف سؤال فقط عن فلاسك؛ أما بالنظر الى موقع Google Trends فنجد أن التقنيتين متقاربتين جدًا في البحث.
</p>

<p>
	ومن خلال الإحصائيات المشار إليها، نجد أن كلا التقنيتين تحظى بدعم كبير جدًا من المجتمع لكن دعم جانغو أكبر من فلاسك بعض الشيء، مع ذلك ورغم صغر حجم الدعم المجتمعي لفلاسك مقارنةً مع جانغو، إلا أن مجتمع فلاسك نشط جدًا ويمكّن المبرمجين من الحصول على ما يحتاجون إليه.
</p>

<h3>
	10. حسب الاحتواء على واجهة تحكم إدارية Admin Interface
</h3>

<p>
	يحتوي إطار العمل جانغو Django على واجهة إدارية مدمجة سهلة الاستخدام تسمح للمطورين بإجراء عدة عمليات بسرعة على النموذج خلال إنشائهم تطبيق، وذلك دون الحاجة إلى أي تعليمات برمجية إضافية أو إضافات خارجية.
</p>

<p>
	عكس فلاسك Flask الذي يعتمد على المطور نفسه في العملية، فلا يحتوي على واجهة إدارية مدمجة؛ مع ذلك يمكن تثبيت إضافة خارجية Extention اسمها Flask-Admin، والتي توفر ما يحتاجه المبرمج بهذا الخصوص
</p>

<h3>
	11. الأمان والحماية
</h3>

<p>
	نظرًا لكون Django أكثر توجهًا واستخدامًا مع المشاريع الكبيرة، فهو يركز على خصائص الأمان والحماية بقوة افتراضيًا، ويحتوي على متطلبات تشفير عالية تجعله أقل عرضةً للتهديدات السيبرانية، عكس Flask الذي رغم توفره على بعض ميزات الأمان المضمنة مثل حماية CSRF، إلا أن الاهتمام بأمان وحماية التطبيقات المنشأة يقع بنسبة أكبر على عاتق المطورين.
</p>

<p>
	يمكننا اختصار الفروقات المذكورة أعلاه في الجدول التالي:
</p>

<table>
	<thead>
		<tr>
			<th>
				الخاصية
			</th>
			<th>
				جانغو Django
			</th>
			<th>
				فلاسك Flask
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				طبيعة الاستخدام
			</td>
			<td>
				مناسب للمشاريع الكبيرة والمعقدة
			</td>
			<td>
				مناسب للمشاريع الصغيرة والبسيطة
			</td>
		</tr>
		<tr>
			<td>
				سهولة التعلم
			</td>
			<td>
				مناسب للمحترفين
			</td>
			<td>
				أنسب للمبتدئين
			</td>
		</tr>
		<tr>
			<td>
				سهولة الاستخدام
			</td>
			<td>
				صعب الاستخدام ويتطلب خبرة أعلى
			</td>
			<td>
				سهل الاستخدام
			</td>
		</tr>
		<tr>
			<td>
				التكلفة
			</td>
			<td>
				مجاني، ولا يتطلب إضافات خارجية لإنجاز المشاريع
			</td>
			<td>
				مجاني، لكن يتطلب إضافات خارجية قد تكون مدفوعة لإنجاز المشاريع
			</td>
		</tr>
		<tr>
			<td>
				الأداء والسرعة
			</td>
			<td>
				أداء وسرعة أفضل في المشاريع الكبيرة
			</td>
			<td>
				أداء وسرعة أفضل في المشاريع الصغيرة
			</td>
		</tr>
		<tr>
			<td>
				قابلية التوسع
			</td>
			<td>
				قابلية توسع جيدة بكل أنواع المشاريع، بالأخص المشاريع الكبيرة
			</td>
			<td>
				قابلية توسع جيدة بالمشاريع الصغيرة ومتوسطة الحجم
			</td>
		</tr>
		<tr>
			<td>
				المرونة
			</td>
			<td>
				مرونة قليلة لارتباطه بأدواته المدمجة
			</td>
			<td>
				مرونة عالية جدًا لعدم ارتباطه بأدوات مدمجة كثيرة
			</td>
		</tr>
		<tr>
			<td>
				التعامل مع قاعدة البيانات
			</td>
			<td>
				يعتمد على نظام ORM مدمج للتعامل مع قواعد البيانات من نوع SQL
			</td>
			<td>
				لا يفرض قيود بالتعامل مع قواعد البيانات ويعتمد على إجراءات المطور
			</td>
		</tr>
		<tr>
			<td>
				الدعم المجتمعي
			</td>
			<td>
				مجتمع كبير ونشط
			</td>
			<td>
				مجتمع أقل حجمًا لكن نشط
			</td>
		</tr>
		<tr>
			<td>
				الاحتواء على واجهة تحكم إدارية Admin Interface
			</td>
			<td>
				يحوي واجهة إدارية مدمجة
			</td>
			<td>
				لا يحوي واجهة إدارية مدمجة، بل يحتاج لتثبيت إضافات خارجية مثل Flask-Admin
			</td>
		</tr>
		<tr>
			<td>
				الأمان والحماية
			</td>
			<td>
				يوفر خصائص حماية مدمجة قوية
			</td>
			<td>
				يوفر خصائص حماية مدمجة أقل ويعتمد على تأمين المطور لمشروعه بنفسه
			</td>
		</tr>
	</tbody>
</table>

<h2 id="-3">
	كيف نختار إطار العمل المناسب؟
</h2>

<p>
	يُعَد إطار العمل جانغو Django كما وضحنا سابقًا أصعب بالتعلم مقارنةً مع فلاسك، لذا في حال لم تكن لدينا خبرة مسبقة بالتعامل مع اُطر العمل عمومًا، فسيكون من الأنسب لنا اختيار إطار العمل فلاسك Flask، ثم الانتقال في حال الرغبة أو الحاجة إلى إطار العمل جانغو Django وعندها سيكون تعلمنا على جانغو أسهلك أما في حال توفرت لدينا خبرة مُسبقة بأحد أطر العمل، فيمكن تعلم أي إطار عمل بناءً على أهداف ورغبات المتعلم.
</p>

<h2 id="-4">
	خاتمة
</h2>

<p>
	بهذا نكون قد تعرفنا على مفهوم إطاري العمل جانغو وفلاسك ووضحنا ميزات كل واحد منهما، مع توضيح الفروقات الأساسية التي تسمح بالمفاضلة بينهما وانتقاء ما يتوافق مع إمكانيات وخبرات المتعلم بها. نأمل أن نكون قد جعلنا عملية الاختيار أسهل عليك.
</p>

<h2>
	المصادر
</h2>

<ul>
	<li>
		<a href="https://www.simplilearn.com/flask-vs-django-article" rel="external nofollow">Django Vs. Flask: Understanding The Major Differences</a>
	</li>
	<li>
		<a href="https://blog.jetbrains.com/pycharm/2025/02/django-flask-fastapi/" rel="external nofollow">?Which Is the Best Python Web Framework: Django, Flask, or FastAPI</a>
	</li>
	<li>
		<a href="https://www.geeksforgeeks.org/differences-between-django-vs-flask/" rel="external nofollow">Differences between Django vs Flask</a>
	</li>
	<li>
		<a href="https://kinsta.com/blog/flask-vs-django/" rel="external nofollow">Flask vs Django: Let’s Choose Your Next Python Framework</a>
	</li>
	<li>
		<a href="https://www.forestadmin.com/blog/flask-tastic-admin-panel-a-step-by-step-guide-to-building-your-own-2/" rel="external nofollow">Flask-tastic Admin Panel: A Step-by-Step Guide to Building Your Own</a>
	</li>
	<li>
		<a href="https://flask.palletsprojects.com/en/stable/patterns/sqlalchemy/" rel="external nofollow">SQLAlchemy in Flask</a>
	</li>
</ul>

<h2 id="-5">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-django-r2041/" rel="">مدخل إلى إطار عمل الويب جانغو Django</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r2201/" rel="">تعرف على إطار العمل فلاسك Flask</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/general/%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-framework/" rel="">تعرف على مفهوم إطار العمل Framework وأهميته في البرمجة</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2514</guid><pubDate>Wed, 19 Feb 2025 16:01:00 +0000</pubDate></item><item><title>&#x643;&#x64A;&#x641; &#x62A;&#x643;&#x62A;&#x628; &#x643;&#x648;&#x62F; &#x623;&#x646;&#x64A;&#x642; &#x648;&#x633;&#x647;&#x644; &#x627;&#x644;&#x642;&#x631;&#x627;&#x621;&#x629; &#x628;&#x644;&#x63A;&#x629; &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646; Coding Style</title><link>https://academy.hsoub.com/programming/python/%D9%83%D9%8A%D9%81-%D8%AA%D9%83%D8%AA%D8%A8-%D9%83%D9%88%D8%AF-%D8%A3%D9%86%D9%8A%D9%82-%D9%88%D8%B3%D9%87%D9%84-%D8%A7%D9%84%D9%82%D8%B1%D8%A7%D8%A1%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-coding-style-r2513/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2025_02/2.CodingStyle.png.4250246c9307144e3e0646559f39db60.png" /></p>
<p>
	إذا سألنا مبرمجي بايثون عن أكثر ما يعجبهم في هذه اللغة، فغالبًا ما سيشيرون إلى سهولة قراءتها. أحد أسباب هذه السهولة هو وجود مجموعة من الإرشادات الخاصة بأسلوب كتابة الكود Code Style وما يُعرف <strong>بالبايثونية Pythonic</strong>، أو العبارات البرمجية المثالية.
</p>

<p>
	مع ذلك هناك بعض الحالات التي لا يكون فيها اتفاق حول أفضل طريقة للتعبير عن غرض معين في كود بايثون، ولكن هذه الحالات نادرة؛ مع ذلك سنشرح في هذا المقال كيفية إضافة كود <a href="https://academy.hsoub.com/programming/python/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-python-r2325/" rel="">بايثون</a> جيد، وسهل القراءة وأنيق في نفس الوقت.
</p>

<h2 id="-1">
	كتابة كود واضح وصريح
</h2>

<p>
	رغم أنه يمكن تنفيذ العديد من الحيل المعقدة باستخدام <a href="https://wiki.hsoub.com/Python" rel="external">بايثون</a>، إلا أنه يُفضل دائمًا أن يكون الكود واضحًا ومباشرًا.
</p>

<p>
	في الآتي مثال عن كود غير واضح وغير صريح:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7453_53" style=""><span class="com"># مثال سيء</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> make_complex</span><span class="pun">(*</span><span class="pln">args</span><span class="pun">):</span><span class="pln">
    x</span><span class="pun">,</span><span class="pln"> y </span><span class="pun">=</span><span class="pln"> args
    </span><span class="kwd">return</span><span class="pln"> dict</span><span class="pun">(**</span><span class="pln">locals</span><span class="pun">())</span></pre>

<p>
	أما لكتابة كود جيد واضح وصريح، فسنكتبه على النحو الآتي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7453_9" style=""><span class="com"># مثال جيد</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> make_complex</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="str">'x'</span><span class="pun">:</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> </span><span class="str">'y'</span><span class="pun">:</span><span class="pln"> y</span><span class="pun">}</span></pre>

<p>
	وكما هو واضح، تم بهذا المثال استقبال القيم <code>x</code> و <code>y</code> مباشرةً من الشخص الذي يستدعي الدالة، وإرجاع قاموس صريح. وهنا، يمكن للمطور الذي يستخدم هذه الدالة أن يفهم تمامًا ما يجب فعله بمجرد قراءة السطر الأول والأخير؛ على عكس الكود الأول السيء الذي يكون أقل وضوحًا.
</p>

<h2 id="-2">
	عبارة واحدة في كل سطر
</h2>

<p>
	بينما يُسمح ببعض العبارات المركبة مثل قوائم الاستيعاب List Comprehensions التي تُقدّر لاختصارها وقدرتها التعبيرية، إلا أن وضع عبارتين غير مرتبطتين في نفس السطر من الكود يُعَد واحدًا من <a href="https://academy.hsoub.com/programming/python/%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%B4%D9%8A%D9%81%D8%B1%D8%A7%D8%AA-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D8%B5%D9%8A%D8%BA-%D8%B4%D8%A7%D8%A6%D8%B9%D8%A9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B9%D9%84%D9%89-%D9%86%D8%AD%D9%88-%D8%AE%D8%A7%D8%B7%D8%A6-r1971/" rel="">الممارسات السيئة في كتابة كود بلغة بايثون</a> كما يوضح المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7453_12" style=""><span class="com"># مثال سيء</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="str">'one'</span><span class="pun">);</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'two'</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">if</span><span class="pln"> x </span><span class="pun">==</span><span class="pln"> </span><span class="lit">1</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'one'</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">if</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">complex comparison</span><span class="pun">&gt;</span><span class="pln"> </span><span class="kwd">and</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">other complex comparison</span><span class="pun">&gt;:</span><span class="pln">
    </span><span class="com"># do something</span><span class="pln">
</span></pre>

<ul>
</ul>

<p>
	لذا يفضل كتابة الكود على النحو الآتي بتقسيم الكود ووضع كل جزء منه بسطر خاص به:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7453_14" style=""><span class="com"># مثال جيد    </span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="str">'one'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="str">'two'</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">if</span><span class="pln"> x </span><span class="pun">==</span><span class="pln"> </span><span class="lit">1</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'one'</span><span class="pun">)</span><span class="pln">

cond1 </span><span class="pun">=</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">complex comparison</span><span class="pun">&gt;</span><span class="pln">
cond2 </span><span class="pun">=</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">other complex comparison</span><span class="pun">&gt;</span><span class="pln">
</span><span class="kwd">if</span><span class="pln"> cond1 </span><span class="kwd">and</span><span class="pln"> cond2</span><span class="pun">:</span><span class="pln">
    </span><span class="com"># do something</span></pre>

<h2 id="functionarguments">
	تمرير وسائط الدوال Function Arguments
</h2>

<p>
	يمكن تمرير الوسائط إلى <a href="https://wiki.hsoub.com/Python#%D8%A7%D9%84%D8%AF%D9%88%D8%A7%D9%84" rel="external">الدوال</a> عبر أربع طرق مختلفة، يمكن توضيحها في ما يلي:
</p>

<h3>
	1. تمرير الوسائط الموضعية Positional Arguments
</h3>

<p>
	تُعَد هذه الوسائط الموضعية من أبسط أنواع الوسائط، وهي إلزامية وليس لها قيم افتراضية. تُستخدم هذه الوسائط في العادة عندما تكون الوسائط جزءًا أساسيًا من معنى الدالة ويكون ترتيبها طبيعيًا، أو تحتوي على وسيطين فقط يسهل تذكرهما؛ فعلى سبيل المثال، لن تكون هناك صعوبة في تذكر أو تحديد ترتيب الدالة <code>point(x, y)</code>، لذا يمكن استخدام أسماء وسائطها مباشرة عند استدعائها دون الحاجة للتأكيد على مكان ترتيبها. ورغم أنه بإمكاننا كتابتها على هذا النحو مثلًا: <code>point(y=2, x=1)</code>، إلا أن هذا يقلل من قابلية القراءة ويجعل الكود أطول دون داعٍ مقارنةً مع الاستدعاء المباشر على النحو <code>point(x, y)</code>.
</p>

<h3>
	2. تمرير الوسائط المعرفة بالأسماء Keyword Arguments
</h3>

<p>
	هذه الوسائط ليست إلزامية ولها قيم افتراضية، وتُستخدم عادةً للمدخلات الاختيارية التي تُمرر إلى الدالة. 
</p>

<p>
	عندما تحتوي الدالة على أكثر من الوسائط الموضوعية، يصبح تذكر ترتيبها أكثر صعوبة، وهنا سيكون من المفيد استخدام الوسائط المعرفة بالأسماء ذات القيم الافتراضية. على سبيل المثال، يمكن تعريف دالة send بطريقة مكتملة، أو بذكر صريح، أو دونه، مع ذلك لا يكون من المناسب اتباع أي منما سبق لكتابة كود أنيق، بل ويُفضل تجنبها حتى في حال عدم الحاجة إليها، مع ضرورة الالتزام بالتركيبة الأقرب إلى تعريف الدالة. وفي الآتي توضيح لخيارات التمرير المذكورة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7453_55" style=""><span class="com"># خيارات غير مناسبة</span><span class="pln">
</span><span class="com"># تعريف الدالة بطريقة مكتملة</span><span class="pln">
send</span><span class="pun">(</span><span class="pln">message</span><span class="pun">,</span><span class="pln"> to</span><span class="pun">,</span><span class="pln"> cc</span><span class="pun">=</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">,</span><span class="pln"> bcc</span><span class="pun">=</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">)</span><span class="pln">

</span><span class="com"># تعريف الدالة دون الذكر الصريح لأسماء الوسائط</span><span class="pln">
send</span><span class="pun">(</span><span class="str">'Hello'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'World'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Family'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Dad'</span><span class="pun">)</span><span class="pln">

</span><span class="com"># تغيير ترتيب الوسائط باستخدام أسمائها</span><span class="pln">
send</span><span class="pun">(</span><span class="str">'Hello again'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'World'</span><span class="pun">,</span><span class="pln"> bcc</span><span class="pun">=</span><span class="str">'Dad'</span><span class="pun">,</span><span class="pln"> cc</span><span class="pun">=</span><span class="str">'Family'</span><span class="pun">)</span><span class="pln">
</span></pre>

<p>
	في حين أن طريقة التمرير المثلى تكون على النحو الآتي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7453_18" style=""><span class="com"># الخيار الأفضل من كل ما سبق</span><span class="pln">
</span><span class="com"># الالتزام بالتركيبة الأقرب للدالة</span><span class="pln">
send</span><span class="pun">(</span><span class="str">'Hello'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'World'</span><span class="pun">,</span><span class="pln"> cc</span><span class="pun">=</span><span class="str">'Family'</span><span class="pun">,</span><span class="pln"> bcc</span><span class="pun">=</span><span class="str">'Dad'</span><span class="pun">)</span></pre>

<h3>
	3. تمرير قائمة الوسائط العشوائية Arbitrary Argument List
</h3>

<p>
	إذا كان من الأفضل التعبير عن الدالة بطريقة تحتوي على وسائط موضعية Positional Arguments بعدد قابل للزيادة والتوسعة، فيمكن تعريفها باستخدام <code>args*</code>، وستكون <code>args</code> هنا عبارة عن قائمة من كل الوسائط الموضعية المتبقية. يمكن مثلًا استدعاء <code>send(message, *args)</code> مع كل مدخل كوسيطة على النحو الآتي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_302_13" style=""><span class="com"># استدعاء الدالة مع كل مدخل كوسيطة</span><span class="pln">
send</span><span class="pun">(</span><span class="str">'Hello'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Dad'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Mom'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Family'</span><span class="pun">)</span></pre>

<p>
	وفي جسم الدالة ستكون <code>args</code> تساوي <code>('Dad', 'Mom', 'Family')</code>، وهذا يتيح إمكانية تمرير عدد غير محدود من الوسائط الموضعية إلى الدالة، مما يجعلها مرنةً للغاية في التعامل مع حالات الاستخدام المختلفة.
</p>

<p>
	مع ذلك، تجدر الإشارة هنا إلى أنه يجب استخدام هذا النوع من التمرير بحذر، فإذا تلقت الدالة قائمة من الوسائط ذات نفس الطبيعة مثلًا، فيجب تعريفها كدالة لوسيطة واحدة، بحيث تكون تلك الوسيطة عبارة عن قائمة أو سلسلة. وهنا، إذا كان send يحتوي على عدة مستلمين، فمن الأفضل تعريفه بشكل صريح واستدعاء الدالة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7453_57" style=""><span class="com"># التعريف الصريح لـ send</span><span class="pln">
send</span><span class="pun">(</span><span class="pln">message</span><span class="pun">,</span><span class="pln"> recipients</span><span class="pun">)</span><span class="pln">

</span><span class="com"># استدعاء الدالة</span><span class="pln">
send</span><span class="pun">(</span><span class="str">'Hello'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">[‘</span><span class="typ">Dad</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Mom'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">‘</span><span class="typ">Family</span><span class="pun">])</span></pre>

<h3>
	4. تمرير قاموس الوسائط العشوائية المعرفة بأسماء Arbitrary Keyword Argument Dictionary
</h3>

<p>
	هذه هي الطريقة الأخيرة لتمرير الوسائط إلى الدوال. إذا كانت الدالة تتطلب سلسلة غير محددة من الوسائط المسماة، فيمكن استخدام <code>kwargs**</code>، وداخل الدالة ستكون <code>kwargs</code> عبارة عن قاموس يحتوي على جميع الوسائط المسماة التي لم يتم التقاطها عبر باقي الوسائط المعرفة بأسماء في تعريف الدالة.
</p>

<p>
	يمكن التعرف بالتفصيل على كيفية استخدام الوسيط <code>kwargs</code> عبر مقال <a href="https://academy.hsoub.com/programming/python/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-args-%D9%88-kwargs-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-r753/" rel="">كيفية استخدام args* و kwargs** في بايثون</a>.
</p>

<p>
	وكما هو الحال مع تمرير قائمة الوسائط العشوائية، لابد من الحذر عند استخدام هذا النوع من التمرير أيضًا؛ إذ يجب استعماله عند الحاجة فقط، ولا ينبغي استخدامه إذا كان البناء الأبسط والأكثر وضوحًا كافيًا للتعبير عن غرض الوظيفة.
</p>

<h2 id="-3">
	تجنب استخدام العصا السحرية
</h2>

<p>
	تُعَد بايثون لغة قويةً، فهي تأتي مع مجموعة غنية من الأدوات والوظائف التي تسمح بتنفيذ العديد من الحيل المعقدة، مثل تغيير طريقة إنشاء وتكوين الكائنات، وتغيير طريقة استيراد المفسر للوحدات النمطية Modules، وتضمين روتينات <a href="https://academy.hsoub.com/programming/general/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%84%D8%BA%D8%A9-c-%D9%88%D9%85%D9%85%D9%8A%D8%B2%D8%A7%D8%AA%D9%87%D8%A7-%D9%88%D8%AE%D8%B7%D9%88%D8%A7%D8%AA-%D8%AA%D8%B9%D9%84%D9%85%D9%87%D8%A7-r2268/" rel="">لغة C</a> داخل بايثون؛ ومع ذلك، فإن كل هذه الخيارات لها العديد من العيوب، ومن الأفضل دائمًا استخدام الطريقة المباشرة أكثر، لأن قابلية القراءة تتأثر كثيرًا عند استخدام هذه التركيبات؛ كما أن العديد من أدوات تحليل الكود، مثل <code>pylint</code> أو <code>pyflakes</code>، لن تكون قادرةً على فهم هذا الكود السحري.
</p>

<h2 id="-4">
	جميعنا مستخدمون مسؤولون
</h2>

<p>
	في لغة بايثون، أي كود عميل Client Code بإمكانه تجاوز خصائص ووظائف الكائنات Objects، إذ لا توجد ببايثون كلمة خاص Private؛ وهذه الفلسفة تختلف تمامًا عن اللغات الدفاعية للغاية لمنع أي إساءة في الاستخدام. مع ذلك، تعبّر بايثون عن هذه الفلسفة بالمقولة الشهيرة:
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p data-gramm="false">
		"نحن جميعًا مستخدمون مسؤولون"
	</p>
</blockquote>

<p>
	وهذا لا يعني أنه لا توجد خصائص من نوع خاص Private، أو أنه لا يمكن تحقيق التغليف Encapsulation فيها كما يجب، بل أنه بدلًا من الاعتماد على جدران يقيمها المطورون بين كودهم وكود الآخرين، فضّل مجتمع بايثون الاعتماد على مجموعة من الاتفاقيات التي تشير إلى أن هذه العناصر لا يجب الوصول إليها مباشرة.
</p>

<p>
	الاتفاقية الرئيسية هنا هي بإضافة شرطة سفلية <code>_</code> كبادئة لجميع العناصر الداخلية؛ بحيث إذا قام كود العميل بكسر هذه القاعدة ووصل إلى هذه العناصر المحددة، فإن أي سلوك خاطئ أو مشاكل تحدث عند تعديل الكود تكون من مسؤولية كود العميل.
</p>

<h2 id="returningvalues">
	إرجاع القيم Returning Values
</h2>

<p>
	عندما تتعقد الدالة يشاع استخدام عدة عبارات <code>return</code> داخل جسمها، وهناك حالتان رئيسيتان لإرجاع القيم في الدالة:
</p>

<ol>
	<li>
		<strong>إرجاع النتيجة الطبيعية</strong>: عندما تتم معالجة الدالة طبيعيًا
	</li>
	<li>
		<strong>حالات الخطأ</strong>: عند الحالات التي تشير إلى وجود معاملات إدخال خاطئة أو أي سبب آخر يمنع الدالة من إكمال عملية الحساب أو المهمة المطلوبة
	</li>
</ol>

<p>
	إذا كنا لا نرغب في استخدام الاستثناءات Exceptions للحالة الثانية، فقد نحتاج إلى إرجاع قيمة مثل <code>None</code> أو <code>False</code> للإشارة إلى أن الدالة لم تتمكن من العمل كما يجب. وفي هذه الحالة، من الأفضل إرجاع القيمة في أقرب وقت يُكتشف فيه السياق غير الصحيح، فهذا يساعد في تبسيط هيكل الدالة أكثر.
</p>

<p>
	مع ذلك، عندما تحتوي الدالة على عدة نقاط خروج رئيسية للمسار الطبيعي، يصبح تصحيح النتيجة المرجعة أكثر صعوبة، ولذلك قد يكون من الأفضل الحفاظ على نقطة خروج واحدة Single exit point، فهذا سيساعد في تحسين مسارات الكود Code Paths.
</p>

<h2 id="idioms">
	التعبيرات البرمجية Idioms
</h2>

<p>
	غالبًا ما يُشار إلى الكود البايثوني الذي يتبع التعبيرات البرمجية الشائعة Idioms على أنه بايثوني Pythonic. وعلى الرغم من وجود طريقة واحدة واضحة في بايثون، إلا أن طريقة كتابة الكود البايثوني كما يجب قد لا تكون واضحة للمبتدئين، ولذلك يجب تعلم التعبيرات البرمجية الجيدة من البداية. وفيما يلي بعض التعبيرات البرمجية الشائعة في بايثون.
</p>

<h3 id="unpacking">
	تفريغ القيم Unpacking
</h3>

<p>
	يمكننا تعيين أسماء عناصر طول قائمة List أو صف Tuple باستخدام عملية التفريغ Unpacking. على سبيل المثال، بما أن الدالة <code>()enumerate</code> توفر صف Tuple من عنصرين لكل عنصر في القائمة، فيمكننا تفريع القيم المحتواة بأكثر من طريقة، سواءً الطريقة العادية، أو طريقة تبديل قيم المتغيرات، أو حتى عن طريق ما يُعرف بالتفريغ المتداخل Nested Unpacking؛ لكن الطريقة الأحدث والأفضل، هي طريقة التفريغ الموسع Extended Unpacking، وجميعها موضحة في ما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7453_25" style=""><span class="com"># 1.تفريغ القيم المحتواة على قائمة</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> index</span><span class="pun">,</span><span class="pln"> item </span><span class="kwd">in</span><span class="pln"> enumerate</span><span class="pun">(</span><span class="pln">some_list</span><span class="pun">):</span><span class="pln">
    </span><span class="com"># do something with index and item</span><span class="pln">

</span><span class="com"># 2. التفريغ لتبديل المتغيرات</span><span class="pln">
a</span><span class="pun">,</span><span class="pln"> b </span><span class="pun">=</span><span class="pln"> b</span><span class="pun">,</span><span class="pln"> a

</span><span class="com"># 3. التفريغ المتداخل</span><span class="pln">
a</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">b</span><span class="pun">,</span><span class="pln"> c</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">3</span><span class="pun">)</span><span class="pln">

</span><span class="com"># 4. طريقة التفريغ الموسع</span><span class="pln">

a</span><span class="pun">,</span><span class="pln"> </span><span class="pun">*</span><span class="pln">rest </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">3</span><span class="pun">]</span><span class="pln">
</span><span class="com"># a = 1, rest = [2, 3]</span><span class="pln">

a</span><span class="pun">,</span><span class="pln"> </span><span class="pun">*</span><span class="pln">middle</span><span class="pun">,</span><span class="pln"> c </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="lit">4</span><span class="pun">]</span><span class="pln">
</span><span class="com"># a = 1, middle = [2, 3], c = 4</span></pre>

<h3 id="ignoredvariable">
	إنشاء متغير غير مستخدم Ignored Variable
</h3>

<p>
	إذا كنا بحاجة إلى تعيين قيمة أثناء التفريغ Unpacking مثلًا ولكننا لن تحتاج إلى استخدام هذا المتغير، فيمكننا استخدام الشرطة السفلية المزدوجة <code>__</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_837_64" style=""><span class="com"># استخدام الشرطة السفلية المزدوجة</span><span class="pln">
filename </span><span class="pun">=</span><span class="pln"> </span><span class="str">'foobar.txt'</span><span class="pln">
basename</span><span class="pun">,</span><span class="pln"> __</span><span class="pun">,</span><span class="pln"> ext </span><span class="pun">=</span><span class="pln"> filename</span><span class="pun">.</span><span class="pln">rpartition</span><span class="pun">(</span><span class="str">'.'</span><span class="pun">)</span></pre>

<p>
	<strong>ملاحظة</strong>: توصي العديد من أدلة أسلوب كتابة كود بايثون باستخدام شرطة سفلية واحدة <code>_</code> للمتغيرات غير المستخدمة Throwaway Variables بدلًا من الشرطة السفلية المزدوجة <code>__</code> التي تم التوصية بها هنا، لكن المشكلة هي أن <code>_</code> يشاع استخدامها كاسم مختصر لدالة <code>()gettext</code>، كما تُستخدم في الوضع التفاعلي Interactive Prompt لتخزين قيمة آخر عملية تم تنفيذها، لذا يُعَد استخدام الشرطة السفلية المزدوجة <code>__</code> أكثر وضوحًا، وتقليلًا لخطر التداخل العرضي مع أي من هذه الاستخدامات الأخرى.
</p>

<h3 id="n">
	إنشاء قائمة بطول N تحتوي على نفس العنصر
</h3>

<p>
	من المهم هنا استخدام العامل <code>*</code> الخاص بقوائم بايثون:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1307_6" style=""><span class="com"># استخدام العامل * الخاص بقوائم بايثون</span><span class="pln">
four_nones </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="kwd">None</span><span class="pun">]</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="lit">4</span></pre>

<h3 id="n-1">
	إنشاء قائمة بطول N تحتوي على قوائم متعددة
</h3>

<p>
	نظرًا لأن القوائم قابلة للتغيير Mutable، فإن العامل <code>*</code> كما في المثال السابق، سيُنشئ قائمةً تحتوي على <code>N</code> مرجعًا لنفس القائمة، وهو على الأرجح ليس ما نريده؛ لذا بدلًا من ذلك، يفضل استخدام قوائم الاستيعاب كالتالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1307_8" style=""><span class="com"># إنشاء قائمة بطول N تحتوي على قوائم متعددة</span><span class="pln">
four_lists </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[[]</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> __ </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">4</span><span class="pun">)]</span></pre>

<h3 id="string">
	إنشاء سلسلة نصية String من قائمة
</h3>

<p>
	من التعبيرات الشائعة لإنشاء السلاسل النصية استخدام الدالة <code>()str.join</code> على سلسلة نصية فارغة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1307_13" style=""><span class="com"># إنشاء سلسلة نصية String من قائمة </span><span class="pln">
letters </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">'s'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'p'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'a'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'m'</span><span class="pun">]</span><span class="pln">
word </span><span class="pun">=</span><span class="pln"> </span><span class="str">''</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">letters</span><span class="pun">)</span></pre>

<p>
	سيؤدي هذا إلى تعيين قيمة المتغير <code>word</code> إلى <code>spam</code>. يمكن تطبيق هذا التعبير على القوائم والصفوف Tuples.
</p>

<h3 id="-5">
	البحث عن عنصر في مجموعة Set
</h3>

<p>
	قد نحتاج في بعض الأحيان إلى البحث في مجموعة من العناصر. على سبيل المثال:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1307_15" style=""><span class="com"># البحث عن عنصر في مجموعة</span><span class="pln">
s </span><span class="pun">=</span><span class="pln"> set</span><span class="pun">([</span><span class="str">'s'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'p'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'a'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'m'</span><span class="pun">])</span><span class="pln">
l </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">'s'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'p'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'a'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'m'</span><span class="pun">]</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> lookup_set</span><span class="pun">(</span><span class="pln">s</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="str">'s'</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> s

</span><span class="kwd">def</span><span class="pln"> lookup_list</span><span class="pun">(</span><span class="pln">l</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="str">'s'</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> l</span></pre>

<p>
	على الرغم من أن الدالتين تبدوان متطابقتين، إلا أن دالة <code>lookup_set</code> تستفيد من حقيقة أن المجموعات Sets في بايثون هي جداول تجزئة Hashtables، مما يجعل أداء البحث بين الاثنين مختلفًا تمامًا.
</p>

<p>
	لتحديد ما إذا كان عنصر ما موجودًا في قائمة، ستحتاج بايثون إلى المرور عبر كل عنصر حتى يعثر على العنصر المطابق، وهذا يستغرق وقتًا طويلًا، خاصةً للقوائم الطويلة. ومن ناحية أخرى، ستخبر التجزئة Hash بايثون بمكان البحث عن العنصر المطابق داخل المجموعة، ونتيجةً لذلك، يمكن إجراء البحث بسرعة، حتى إذا كانت المجموعة كبيرة.
</p>

<p>
	بسبب هذه الاختلافات في الأداء، غالبًا ما يكون من الجيد استخدام المجموعات Sets، أو القواميس Dictionaries بدلًا من القوائم في الحالات التالية:
</p>

<ul>
	<li>
		عندما تحتوي المجموعة على عدد كبير من العناصر
	</li>
	<li>
		عندما نبحث باستمرار عن العناصر في المجموعة
	</li>
	<li>
		عندما لا تكون لدينا عناصر مكررة
	</li>
</ul>

<p>
	وبالنسبة للمجموعات الصغيرة أو المجموعات التي لن نكرر البحث فيها، فإن الوقت والذاكرة الإضافية المطلوبة لإعداد جدول التجزئة Hashtable غالبًا ما تكون أكبر من الوقت الذي يتم توفيره، وذلك بسبب سرعة البحث المحسنة.
</p>

<h2 id="thezenofpython">
	فلسفة بايثون The Zen of Python
</h2>

<p>
	تُعرف أيضًا بـ PEP 20، وهي المبادئ التوجيهية لتصميم لغة بايثون. تتمثل فلسفة بايثون، بقلم تيم بيترز Tim Peters في ما يلي:
</p>

<ul>
	<li>
		الجميل أفضل من القبيح
	</li>
	<li>
		الواضح أفضل من الضمني
	</li>
	<li>
		البسيط أفضل من المعقد
	</li>
	<li>
		المعقد أفضل من الملتوي
	</li>
	<li>
		المسطح أفضل من المتداخل
	</li>
	<li>
		المتناثر أفضل من المكدس
	</li>
	<li>
		قابلية القراءة مهمة
	</li>
	<li>
		الحالات الخاصة ليست خاصة بما يكفي لكسر القواعد
	</li>
	<li>
		الأخطاء لا يجب أن تمر بصمت إلا إذا تم إسكاتها صراحةً
	</li>
	<li>
		في مواجهة الغموض، قاوم إغراء التخمين
	</li>
	<li>
		يجب أن تكون هناك طريقة واحدة واضحة للقيام بالأمر، ويفضل أن تكون واحدة فقط
	</li>
	<li>
		قد لا تكون الطريقة الوحيدة الواضحة واضحة من البداية
	</li>
	<li>
		الآن أفضل من أبدًا
	</li>
	<li>
		إذا كان تنفيذ الفكرة صعب الشرح، فهي فكرة سيئة
	</li>
	<li>
		إذا كان تنفيذ الفكرة سهل الشرح، فقد تكون فكرة جيدة
	</li>
	<li>
		مساحات الأسماء Namespaces فكرة رائعة جدًا وعلينا استخدامها أكثر
	</li>
</ul>

<h2 id="pep8">
	دليل PEP 8
</h2>

<p>
	<a href="https://pep8.org/" rel="external nofollow">PEP 8</a> هو دليل أسلوب كتابة الكود الفعلي de facto للغة بايثون. يوصى جدًا بقراءة هذا الدليل، لأنه يساعد في جعل الكود أكثر تناسقًا عند العمل على مشاريع مع مطورين آخرين.
</p>

<p>
	تتوفر عدة أدوات للتحقق مما إذا كان الكود متوافق مع أسلوب PEP 8، نذكرها في الآتي.
</p>

<h3 id="autopep8">
	المنسق الآلي autopep8
</h3>

<p>
	يمكن استخدام برنامج <a href="https://pypi.org/project/autopep8/" rel="external nofollow">autopep8</a> لإعادة تنسيق الكود تلقائيًا وفقًا لأسلوب PEP 8. ويمكننا تثبيت البرنامج باستخدام:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_302_16" style=""><span class="com"># تثبيت منسق autopep8</span><span class="pln">
$ pip install autopep8</span></pre>

<p>
	نستخدمه لتنسيق الملف مباشرةً، وذلك باستخدام:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_302_20" style=""><span class="com"># التنسيق المباشر للملف باستخدام autopep8</span><span class="pln">
$ autopep8 </span><span class="pun">--</span><span class="kwd">in</span><span class="pun">-</span><span class="pln">place optparse</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	حيث سيؤدي استبعاد العلامة <code>in-place--</code> إلى عرض الكود المعدل مباشرةً في وحدة التحكم للمراجعة، وستُجري العلامة <code>aggressive--</code> تغييرات أكثر جوهرية ويمكن تطبيقها عدة مرات لتحقيق تأثير أكبر.
</p>

<h3 id="yapf">
	المنسق الآلي yapf
</h3>

<p>
	بينما يركز autopep8 على حل انتهاكات PEP 8، يحاول <a href="https://github.com/google/yapf" rel="external nofollow">yapf</a> تحسين تنسيق الكود بجانب الالتزام بـ PEP 8. يهدف هذا المُنسق إلى تقديم كود يبدو جيدًا كما لو كان مكتوبًا بواسطة مبرمج يلتزم بـ PEP 8، ويُثبت باستخدام الأمر الآتي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_302_22" style=""><span class="com"># تثبيت yapf</span><span class="pln">
$ pip install yapf</span></pre>

<p>
	يمكننا تشغيل التنسيق الآلي لملف باستخدام الأمر:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_302_24" style=""><span class="com"># التشغيل التلقائي للملف عبر منسق yapf</span><span class="pln">
$ yapf </span><span class="pun">--</span><span class="kwd">in</span><span class="pun">-</span><span class="pln">place optparse</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	سيؤدي تشغيل الأمر بدون العلامة <code>in-place--</code> إلى عرض الاختلافات diff للمراجعة قبل تطبيق التغييرات.
</p>

<h3 id="black">
	المنسق الآلي black
</h3>

<p>
	يقدم المُنسق التلقائي black تنسيقًا حاسمًا ومحددًا لقاعدة الكود، ويركز على توفير أسلوب موحد للكود دون الحاجة إلى التكوين من قبل المستخدمين، وبالتالي يمكن لمستخدمي black نسيان التنسيق تمامًا. بالإضافة إلى ذلك، يضمن منسق black حدوث تغييرات بسيطة وذات صلة فقط في git diff؛ ويمكن تثبيت المنسق عبر الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1307_23" style=""><span class="com"># تثبيت المنسق black</span><span class="pln">
$ pip install black</span></pre>

<p>
	ويمكن تنسيق ملف بايثون باستخدام الأمر:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1307_27" style=""><span class="com"># تنسيق ملف بايثون عبر أمر black</span><span class="pln">
$ black optparse</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	توفر إضافة العلامة <code>diff--</code> تعديلات الكود للمراجعة دون التطبيق المباشر.
</p>

<h2 id="conventions">
	الاتفاقيات Conventions
</h2>

<p>
	فيما يلي بعض الاتفاقيات التي يجب اتباعها لجعل الكود أسهل في القراءة.
</p>

<h3 id="-6">
	التحقق من تساوي متغير مع ثابت
</h3>

<p>
	لا داعي لمقارنة <code>True</code> أو <code>None</code> أو <code>0</code> بشكل صريح، إذ يمكننا إضافتها إلى عبارة <code>if</code> مباشرةً. في ما يلي مثال توضيحي عن الأمر:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7453_59" style=""><span class="com"># مثال سيء</span><span class="pln">
</span><span class="kwd">if</span><span class="pln"> attr </span><span class="pun">==</span><span class="pln"> </span><span class="kwd">True</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'True!'</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">if</span><span class="pln"> attr </span><span class="pun">==</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'attr is None!'</span><span class="pun">)</span></pre>

<p>
	وكما هو ظاهر، يُعد المثال أعلاه غير جيد للاستخدام، إذ يمكن كتابة الكود بشكل أفضل على النحو الآتي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7453_65" style=""><span class="com"># مثال جيد</span><span class="pln">
</span><span class="com"># تحقق من القيمة فقط</span><span class="pln">
</span><span class="kwd">if</span><span class="pln"> attr</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'attr is truthy!'</span><span class="pun">)</span><span class="pln">

</span><span class="com"># أو تحقق من العكس </span><span class="pln">
</span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> attr</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'attr is falsey!'</span><span class="pun">)</span><span class="pln">

</span><span class="com"># أو، بما أن None يعتبر False، تحقق منه بشكل صريح</span><span class="pln">
</span><span class="kwd">if</span><span class="pln"> attr </span><span class="kwd">is</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'attr is None!'</span><span class="pun">)</span></pre>

<h3 id="-7">
	الوصول إلى عنصر في القاموس
</h3>

<p>
	يفترض أن لا نستخدم الدالة <code>()dict.has_key</code>. بل بدلًا من ذلك، يمكننا استخدام الصيغة <code>x in d</code>، أو تمرير قيمة افتراضية إلى <code>()dict.get</code>.
</p>

<p>
	يوضح المثال التالي الاستخدام السيء للوصول إلى عناصر القواميس:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7453_67" style=""><span class="com"># مثال سيء</span><span class="pln">
d </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="str">'hello'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'world'</span><span class="pun">}</span><span class="pln">
</span><span class="kwd">if</span><span class="pln"> d</span><span class="pun">.</span><span class="pln">has_key</span><span class="pun">(</span><span class="str">'hello'</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">d</span><span class="pun">[</span><span class="str">'hello'</span><span class="pun">])</span><span class="pln">    </span><span class="com"># prints 'world'</span><span class="pln">
</span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'default_value'</span><span class="pun">)</span></pre>

<p>
	في حين تكون الطريقة الأفضل للوصول إليها بكتابة كود من هذا النوع:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7453_71" style=""><span class="com"># مثال جيد</span><span class="pln">
d </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="str">'hello'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'world'</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">print</span><span class="pun">(</span><span class="pln">d</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="str">'hello'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'default_value'</span><span class="pun">))</span><span class="pln"> </span><span class="com"># تكتب 'world'</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="pln">d</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="str">'thingy'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'default_value'</span><span class="pun">))</span><span class="pln"> </span><span class="com"># تكتب 'default_value'</span><span class="pln">

 </span><span class="com"># :أو</span><span class="pln">
</span><span class="kwd">if</span><span class="pln"> </span><span class="str">'hello'</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> d</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">d</span><span class="pun">[</span><span class="str">'hello'</span><span class="pun">])</span></pre>

<h3 id="-8">
	طرق مختصرة للتعامل مع القوائم
</h3>

<p>
	توفر قوائم الاستيعاب List Comprehensions طريقةً قويةً وموجزةً للعمل مع القوائم. تتبع تعابير المولدات Generator Expressions نفس صيغة قوائم الاستيعاب تقريبًا، ولكنها تُرجع مولدًا Generator بدلًا من قائمة.
</p>

<p>
	يتطلب إنشاء قائمة جديدة المزيد من العمل ويستخدم ذاكرة أكثر، لذا إذا كنا سنكتفي بالتكرار فقط خلال القائمة الجديدة، فمن الأفضل استخدام مُكرر Iterator بدلًا من ذلك.
</p>

<p>
	يوضح المثال التالي طريقة غير جيدة للتعامل مع القوائم:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7453_31" style=""><span class="com"># مثال سيء</span><span class="pln">
</span><span class="com"># يخصص قائمة بجميع القيم (gpa, name) في الذاكرة دون داعٍ</span><span class="pln">
valedictorian </span><span class="pun">=</span><span class="pln"> max</span><span class="pun">([(</span><span class="pln">student</span><span class="pun">.</span><span class="pln">gpa</span><span class="pun">,</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">name</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> student </span><span class="kwd">in</span><span class="pln"> graduates</span><span class="pun">])</span><span class="pln">
</span></pre>

<p>
	أما المثال التالي فيوضح الطريقة الأفضل للتعامل معها:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7453_33" style=""><span class="com"># مثال جيد</span><span class="pln">
valedictorian </span><span class="pun">=</span><span class="pln"> max</span><span class="pun">((</span><span class="pln">student</span><span class="pun">.</span><span class="pln">gpa</span><span class="pun">,</span><span class="pln"> student</span><span class="pun">.</span><span class="pln">name</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> student </span><span class="kwd">in</span><span class="pln"> graduates</span><span class="pun">)</span></pre>

<p>
	يمكننا استخدام قوائم الاستيعاب عندما نحتاج إلى إنشاء قائمة ثانية. على سبيل المثال، إذا كنا بحاجة إلى استخدام النتيجة عدة مرات؛ فإذا كان منطقنا معقدًا جدًا بالنسبة لقائمة استيعاب قصيرة أو تعبير مولد، فلا بد من التفكير في استخدام دالة المولد Generator Function بدلًا من إرجاع قائمة.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1307_32" style=""><span class="com"># استخدام دالة المولد Generator Function</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> make_batches</span><span class="pun">(</span><span class="pln">items</span><span class="pun">,</span><span class="pln"> batch_size</span><span class="pun">):</span><span class="pln">
    </span><span class="str">"""
    &gt;&gt;&gt; list(make_batches([1, 2, 3, 4, 5], batch_size=3))
    [[1, 2, 3], [4, 5]]
    """</span><span class="pln">
    current_batch </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> item </span><span class="kwd">in</span><span class="pln"> items</span><span class="pun">:</span><span class="pln">
        current_batch</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">item</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">current_batch</span><span class="pun">)</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> batch_size</span><span class="pun">:</span><span class="pln">
            </span><span class="kwd">yield</span><span class="pln"> current_batch
            current_batch </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">
    </span><span class="kwd">yield</span><span class="pln"> current_batch</span></pre>

<p>
	<strong>ملاحظة</strong>: لا نستخدم دائمًا قوائم الاستيعاب بسبب تأثيراتها الجانبية
</p>

<h3 id="filteringalist">
	تصفية قائمة Filtering a List
</h3>

<p>
	من أجل تصفية قائمة يُفضل استخدام قوائم الاستيعاب List Comprehensions أو تعابير المولدات Generator Expressions، بدل تصفيتها بطريقة مكررة وغير دقيقة.
</p>

<p>
	يوضح المثال التالي الطريقة السيئة لتصفيتها:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7453_37" style=""><span class="com"># مثال سيء</span><span class="pln">
</span><span class="com"># إزالة عناصر من القائمة عند وجود التكرار</span><span class="pln">
</span><span class="com"># تصفية العناصر الأكبر من 4</span><span class="pln">
a </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="lit">4</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">]</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> i </span><span class="kwd">in</span><span class="pln"> a</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> i </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">4</span><span class="pun">:</span><span class="pln">
        a</span><span class="pun">.</span><span class="pln">remove</span><span class="pun">(</span><span class="pln">i</span><span class="pun">)</span><span class="pln">
        
</span><span class="com"># عمل عدة تمريرات على القائمة</span><span class="pln">
</span><span class="kwd">while</span><span class="pln"> i </span><span class="kwd">in</span><span class="pln"> a</span><span class="pun">:</span><span class="pln">
    a</span><span class="pun">.</span><span class="pln">remove</span><span class="pun">(</span><span class="pln">i</span><span class="pun">)</span></pre>

<p>
	في حين يعبر المثال التالي عن كيفية تصفيتها بطريقة صحيحة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7453_39" style=""><span class="com"># مثال جيد</span><span class="pln">
</span><span class="com"># قوائم الاستيعاب تنشئ كائن قائمة جديد</span><span class="pln">
filtered_values </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">value </span><span class="kwd">for</span><span class="pln"> value </span><span class="kwd">in</span><span class="pln"> sequence </span><span class="kwd">if</span><span class="pln"> value </span><span class="pun">!=</span><span class="pln"> x</span><span class="pun">]</span><span class="pln">

</span><span class="com"># تعابير المولدات لا تنشئ قائمة أخرى</span><span class="pln">
filtered_values </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">value </span><span class="kwd">for</span><span class="pln"> value </span><span class="kwd">in</span><span class="pln"> sequence </span><span class="kwd">if</span><span class="pln"> value </span><span class="pun">!=</span><span class="pln"> x</span><span class="pun">)</span></pre>

<h3 id="-9">
	الآثار الجانبية المحتملة لتعديل القائمة الأصلية
</h3>

<p>
	يمكن أن يكون تعديل القائمة الأصلية محفوفًا بالمخاطر إذا كانت هناك متغيرات أخرى تشير إليها. ولكن يمكننا استخدام تعيين الشرائح Slice Assignment إذا كنا نرغب في ذلك.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4799_105" style=""><span class="com"># استبدال محتويات القائمة الأصلية</span><span class="pln">
sequence</span><span class="pun">[::]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">value </span><span class="kwd">for</span><span class="pln"> value </span><span class="kwd">in</span><span class="pln"> sequence </span><span class="kwd">if</span><span class="pln"> value </span><span class="pun">!=</span><span class="pln"> x</span><span class="pun">]</span></pre>

<h3 id="-10">
	تعديل القيم في قائمة
</h3>

<p>
	من الممارسات قد تجعل كود بايثون الذي نكتبه أقل احترافية أن نحاول تذكر أن عملية التعيين لا تنشئ كائنًا جديدًا بطريقة تغير كل المتغيرات في حال كان هناك متغيران أو أكثر يشيران إلى نفس القائمة. وبدلًا عن ذلك ومن الأكثر أمانًا، يفضل إنشاء كائن قائمة جديد وترك القائمة الأصلية دون تغيير.
</p>

<p>
	يمثل المثال التالي الطريقة السيئة في تعديل القيم بالقائمة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7453_73" style=""><span class="com"># مثال سيء: إذا كان هناك متغيران أو أكثر يشيران إلى نفس القائمة، فإن تغيير أحدها سيؤثر على جميعها </span><span class="pln">
</span><span class="com"># إضافة 3 إلى جميع عناصر القائمة.</span><span class="pln">
a </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="lit">4</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">]</span><span class="pln">
b </span><span class="pun">=</span><span class="pln"> a                    
</span><span class="com"># a و b يشيران إلى نفس كائن القائمة</span><span class="pln">

</span><span class="kwd">for</span><span class="pln"> i </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="pln">len</span><span class="pun">(</span><span class="pln">a</span><span class="pun">)):</span><span class="pln">
    a</span><span class="pun">[</span><span class="pln">i</span><span class="pun">]</span><span class="pln"> </span><span class="pun">+=</span><span class="pln"> </span><span class="lit">3</span><span class="pln">            
    </span><span class="com"># b[i] تتغير أيضًا</span></pre>

<p>
	في حين يوضح المثال التالي الطريقة الصحيحة لفعل ذلك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7453_43" style=""><span class="com"># مثال جيد: الأكثر أمانًا هو إنشاء كائن قائمة جديد وترك القائمة الأصلية دون تغيي</span><span class="pln">
a </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="lit">4</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">]</span><span class="pln">
b </span><span class="pun">=</span><span class="pln"> a

</span><span class="com"># تعيين المتغير "a" إلى قائمة جديدة دون تغيير "b"</span><span class="pln">
a </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">i </span><span class="pun">+</span><span class="pln"> </span><span class="lit">3</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> i </span><span class="kwd">in</span><span class="pln"> a</span><span class="pun">]</span></pre>

<p>
	من المهم استخدام الدالة <code>()enumerate</code> لتتبع موقعنا في القائمة.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4799_111" style=""><span class="pln">a </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="lit">4</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">]</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> i</span><span class="pun">,</span><span class="pln"> item </span><span class="kwd">in</span><span class="pln"> enumerate</span><span class="pun">(</span><span class="pln">a</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">i</span><span class="pun">,</span><span class="pln"> item</span><span class="pun">)</span><span class="pln">
</span><span class="com"># الناتج:</span><span class="pln">
</span><span class="com"># 0 3</span><span class="pln">
</span><span class="com"># 1 4</span><span class="pln">
</span><span class="com"># 2 5</span></pre>

<p>
	تتميز الدالة <code>()enumerate</code> بسهولة قراءة أفضل من التعامل مع عداد يدويًا. بالإضافة إلى ذلك، فهي مُحسنة أكثر للتعامل مع المُكررات iterators.
</p>

<h3 id="-11">
	القراءة من ملف
</h3>

<p>
	يمكننا استخدام بناء الجملة <code>with open</code> للقراءة من الملفات، وسيؤدي هذا إلى إغلاق الملف تلقائيًا.
</p>

<p>
	يوضح المثال التالي شكل الكود بدون استخدام الجملة <code>with open</code> والذي يصنف كمثال سيء للاستخدام:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7453_45" style=""><span class="com"># مثال سيء</span><span class="pln">
f </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="str">'file.txt'</span><span class="pun">)</span><span class="pln">
a </span><span class="pun">=</span><span class="pln"> f</span><span class="pun">.</span><span class="pln">read</span><span class="pun">()</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="pln">a</span><span class="pun">)</span><span class="pln">
f</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span></pre>

<p>
	في حين يوضح المثال التالي الطريقة الصحيحة للقراءة من الملف باستخدام <code>with open</code><span>:</span>
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7453_47" style=""><span class="com"># مثال جيد</span><span class="pln">
</span><span class="kwd">with</span><span class="pln"> open</span><span class="pun">(</span><span class="str">'file.txt'</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> f</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> line </span><span class="kwd">in</span><span class="pln"> f</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">line</span><span class="pun">)</span></pre>

<p>
	تُعَد العبارة <code>with</code> أفضل لأنها تضمن إغلاق الملف دائمًا، حتى إذا تم رفع استثناء exception داخل الكتلة الخاصة بـ <code>with</code>.
</p>

<h3 id="-12">
	طول الأسطر
</h3>

<p>
	عندما يكون سطر الكود أطول من الحد المقبول، يجب تقسيمه إلى عدة أسطر. سيدمج مفسر بايثون الأسطر المتتالية إذا كان الحرف الأخير من السطر هو شرطة مائلة للخلف <code>\</code>. 
</p>

<p>
	يُعَد هذا مفيدًا في بعض الحالات، ولكن يجب تجنبه أحيانًا؛ إذ قد تكسر عملية إضافة مسافة بيضاء في نهاية السطر بعد الشرطة المائلة للخلف <code>\</code> الكود، وقد يؤدي هذا إلى نتائج غير متوقعة، والحل الأفضل هنا هو استخدام الأقواس حول العناصر.
</p>

<p>
	عند ترك قوس غير مغلق في نهاية السطر، سوف يدمج مفسر بايثون السطر الموالي إلى غاية إغلاق الأقواس. وينطبق نفس السلوك على الأقواس المعقوفة <code>{}</code> والأقواس المربعة <code>[]</code>.
</p>

<p>
	يوضح المثال التالي الطريقة السيئة للتعامل مع طول الأسطر:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7453_49" style=""><span class="com"># مثال سيء</span><span class="pln">
my_very_big_string </span><span class="pun">=</span><span class="pln"> </span><span class="str">"""For a long time I used to go to bed early. Sometimes, \
    when I had put out my candle, my eyes would close so quickly that I had not even \
    time to say “I’m going to sleep.”"""</span><span class="pln">

</span><span class="kwd">from</span><span class="pln"> some</span><span class="pun">.</span><span class="pln">deep</span><span class="pun">.</span><span class="pln">module</span><span class="pun">.</span><span class="pln">inside</span><span class="pun">.</span><span class="pln">a</span><span class="pun">.</span><span class="pln">module </span><span class="kwd">import</span><span class="pln"> a_nice_function</span><span class="pun">,</span><span class="pln"> another_nice_function</span><span class="pun">,</span><span class="pln"> \
    yet_another_nice_function</span></pre>

<p>
	في حين يكون الاستخدام صحيح على النحو الآتي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_7453_51" style=""><span class="com"># مثال جيد</span><span class="pln">
my_very_big_string </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"For a long time I used to go to bed early. Sometimes, "</span><span class="pln">
    </span><span class="str">"when I had put out my candle, my eyes would close so quickly "</span><span class="pln">
    </span><span class="str">"that I had not even time to say “I’m going to sleep.”"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">from</span><span class="pln"> some</span><span class="pun">.</span><span class="pln">deep</span><span class="pun">.</span><span class="pln">module</span><span class="pun">.</span><span class="pln">inside</span><span class="pun">.</span><span class="pln">a</span><span class="pun">.</span><span class="pln">module </span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    a_nice_function</span><span class="pun">,</span><span class="pln"> another_nice_function</span><span class="pun">,</span><span class="pln"> yet_another_nice_function</span><span class="pun">)</span></pre>

<p>
	ومع ذلك، في كثير من الأحيان، تكون الحاجة إلى تقسيم سطر منطقي طويل علامة على محاولة القيام بعدة الأشياء في نفس الوقت، مما قد يعيق قابلية القراءة.
</p>

<p>
	ترجمة -وبتصرّف- لمقال <a href="https://docs.python-guide.org/writing/style/" rel="external nofollow">Code Style</a>.
</p>

<h2 id="-13">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/general/%D9%83%D9%8A%D9%81-%D8%AA%D9%83%D8%AA%D8%A8-%D9%83%D9%88%D8%AF-%D8%A8%D8%B1%D9%85%D8%AC%D9%8A-%D9%85%D8%AB%D9%84-%D9%85%D9%87%D9%86%D8%AF%D8%B3%D9%8A-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A7%D8%AA-r2506/" rel="">كيف تكتب كود برمجي مثل مهندسي البرمجيات</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D8%AA%D8%B9%D9%84%D9%85-%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%A3%D9%83%D9%88%D8%A7%D8%AF-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D9%85%D9%86-%D8%AE%D9%84%D8%A7%D9%84-%D8%A7%D9%84%D8%A3%D9%85%D8%AB%D9%84%D8%A9-%D8%A7%D9%84%D8%B9%D9%85%D9%84%D9%8A%D8%A9-r2048/" rel="">تعلم كتابة أكواد بايثون من خلال الأمثلة العملية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D9%85%D8%B4%D8%A7%D8%B1%D9%8A%D8%B9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D8%B9%D9%85%D9%84%D9%8A%D8%A9-%D8%AA%D9%86%D8%A7%D8%B3%D8%A8-%D8%A7%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-r2185/" rel="">مشاريع بايثون عملية تناسب المبتدئين</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2513</guid><pubDate>Mon, 17 Feb 2025 16:06:00 +0000</pubDate></item><item><title>&#x62A;&#x637;&#x648;&#x64A;&#x631; &#x645;&#x633;&#x627;&#x639;&#x62F; &#x630;&#x643;&#x64A; &#x644;&#x644;&#x63A;&#x629; &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x646;&#x645;&#x627;&#x630;&#x62C; OpenAI</title><link>https://academy.hsoub.com/programming/python/%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D9%85%D8%B3%D8%A7%D8%B9%D8%AF-%D8%B0%D9%83%D9%8A-%D9%84%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%86%D9%85%D8%A7%D8%B0%D8%AC-openai-r2467/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_12/OpenAI_.png.d89782ff5e485be7c0eec410872e48a5.png" /></p>
<p>
	استحوذ الذكاء الاصطناعي في السنوات الأخيرة على الساحة التقنية وأثر على كل الصناعات والقطاعات، بداية من المجالات الإبداعية وصولًا إلى القطاعات المالية، مدفوعًا بالتطور متسارع الخطى للنماذج اللغوية الضخمة Large Language models أو اختصارًا LLMs مثل GPT من شركة OpenAI وجيميناي Gemini من شركة جوجل، فصارت هذه النماذج جزءًا أساسيًا من الأدوات التي يستخدمها مهندسو البرمجيات.
</p>

<p>
	لا يستطيع الجيل الحالي من النماذج اللغوية الضخمة استبدال مهندسي البرمجيات بالطبع، ولكنه يمكنه مساعدتهم في مهام عديدة مثل تحليل الأكواد البرمجية، واكتشاف الأخطاء وتتبعها، وإجراء المهام البسيطة الروتينية. نحاول في هذه المقالة تبسيط تعقيدات استخدام النماذج اللغوية الضخمة واستخدامها في تطبيق عملي لتوليد كود بايثون قادر على التفاعل مع تطبيق خارجي.
</p>

<h1 id="largelanguagemodels">
	ما هي النماذج اللغوية الضخمة Large Language Models
</h1>

<p>
	النماذج اللغوية الضخمة هي نماذج تعلم آلي دُربت على كميات ضخمة من البيانات النصية بهدف فهم وتوليد اللغة البشرية، يبنى النموذج اللغوي الضخم عادة باستخدام <a href="https://academy.hsoub.com/programming/artificial-intelligence/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%A7%D9%84%D9%85%D8%AD%D9%88%D9%91%D9%84%D8%A7%D8%AA-transformers-%D9%85%D9%86-%D9%85%D9%86%D8%B5%D8%A9-hugging-face-r2340/" rel="">المحولات transformers</a> والمحولات هي نوع من الشبكات العصبية الاصطناعية تُستخدم بشكل أساسي في معالجة اللغات الطبيعية مثل الترجمة أو الإجابة على الأسئلة وتستخدم أيضًا في تطبيقات مثل معالجة الصور. المميز في المحولات أنها تعتمد على آلية الانتباه الذاتي، مما يجعلها قادرة على التركيز على أجزاء معينة من المدخلات، وهي تقوم على آلية تسمى الانتباه الذاتي self-attention mechanism والتي تعني أن سلسلة المدخلات تعالج بشكل متزامن بدلًا من معالجتها كلمة كلمة، وهذا يتيح للنموذج تحليل جمل كاملة، ويحسن قدرتها على فهم السياق والمعاني الضمنية للكلمات، وفعالة في توليد نصوص قريبة جدًا من الأسلوب البشري.
</p>

<p>
	وكلما زاد عمق الشبكة العصبية الاصطناعية كلما أمكنها تعلم معاني أدق في اللغة، ويتطلب النموذج اللغوي الضخم الحديث كمية هائلة من بيانات التدريب وقد يحتوي مليارات المعاملات parameters وهي عناصر تتغير بالتدريب على البيانات ليتأقلم النموذج على المهمة المراد تعلمها، فزيادة عمق الشبكة يؤدي إلى تحسن في المهام مثل الربط بالأسباب reasoning، على سبيل المثال من أجل تدريب<strong> </strong>GPT-3 جُمعَت بيانات من المحتوى المنشور في الكتب والإنترنت بلغ حجمها 45 تيرابايت من النصوص المضغوطة، ويحتوي النموذج ما يقارب 175 مليار معامل كي يتمكن من تحقيق هذا المستوى من المعرفة.
</p>

<p>
	وقد ظهرت عدة نماذج لغوية ضخمة حققت تقدمًا ملحوظًا بالإضافة إلى GPT-3 و GPT-4، نذكر منها على سبيل المثال نموذج  PaLM 2 من جوجل ونموذج LLaMa 2 من ميتا. ونظرًا لاحتواء بيانات تدريب هذه النماذج على أكواد برمجية بلغات مختلفة فقد أصبحت هذه النماذج اللغوية الضخمة قادرة على توليد الأكواد البرمجية لا المحتوى النصي فحسب، إذ تستطيع النماذج اللغوية الحديثة تحويل الأوامر أو الموجهات prompts المكتوبة باللغة الطبيعية إلى كود بمختلف اللغات والتقنيات، وعلى الرغم من ذلك لن تحقق الاستفادة المرجوة من هذه الميزات القوية إذا لم تمتلك الخبرة التقنية الكافية.
</p>

<h2 id="">
	فوائد ومحدويات الأكواد المولدة بنماذج اللغات الضخمة
</h2>

<p>
	يتطلب حل المشكلات والمهام المعقدة في الغالب تدخل المطورين البشريين، لكن يمكن للنماذج اللغوية الضخمة أن تعمل كمساعدات ذكية للمطورين وتكتب الأكواد لمهام أقل تعقيدًا، وتسهل التعامل مع المهام التكرارية مما يعزز الإنتاجية ويقلل وقت التطوير في مراحل <a href="https://academy.hsoub.com/programming/general/%D9%87%D9%86%D8%AF%D8%B3%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A7%D8%AA/" rel="">هندسة البرمجيات</a> خصوصًا في المراحل الأولى من تطوير النماذج الأولية للبرمجيات، كما يمكن أن توفر النماذج اللغوية الضخمة مساعدة كبيرة في تصحيح الكود البرمجي وتشرحه وتساعدنا على إيجاد الأخطاء التي قد يصعب علينا ملاحظتها كبشر.
</p>

<p>
	يجدر التوضيح أن أي أكواد مولدة باستخدام النماذج اللغوية الضخمة تعتبر نقطة للبداية وليس منتجًا نهائيًا وينبغي دائمًا مراجعة الأكواد المولدة واختبارها. والوعي بمحدوديات نماذج اللغات الضخمة، فهي تفتقد لقدرات حل المشكلات والتفكير المنطقي والإبداعي الذي يميزنا كبشر، إضافة إلى احتمال عدم تعرض النماذج للتدريب الكافي الذي يؤهلها للتعامل مع مهام متخصصة مناسبة لنوع معين من المستخدمين أو <a href="https://academy.hsoub.com/programming/general/%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-framework/" rel="">أطر العمل</a> غير مفتوحة المصدر، وبالتالي يمكن لهذه النماذج اللغوية الضخمة أن تساعد بشكل كبير ولكن سيبقى دور المطور البشري أساسيًا في عملية التطوير بالطبع.
</p>

<h2 id="-1">
	توليد كود برمجي باستخدام نموذج لغوي ضخم: استخدام الواجهة البرمجية للطقس
</h2>

<p>
	من أهم مميزات التطبيقات الحديثة قدرتها على التعامل مع مصادر خارجية، إذ يمكن للمطورين إرسال طلبات لهذه المصادر الخارجية من خلال واجهة برمجة التطبيقات <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> وهي مجموعة من التعليمات أو البروتوكولات التي توفر طريقة موحدة لتواصل التطبيقات فيما بينها.
</p>

<p>
	وتتطلب كتابة الكود البرمجي الذي يستخدم <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-api-r1314/" rel="">واجهة برمجة التطبيقات</a> قراءة متعمقة لتوثيقات هذه الواجهة، حيث تشرح التوثيقات كافة المتطلبات والتسميات المصطلح عليها، والوظائف والعمليات التي تدعمها، وتنسيق البيانات المرسلة والمستلمة والمتطلبات الأمنية. ويمكن للنموذج اللغوي الضخم تسريع هذه العملية في حال توفرت لديه المعلومات المطلوبة.
</p>

<p>
	حيث يمكن للنموذج اللغوي المساعدة بتوليد الكود البرمجي اللازم لإرسال طلب لواجهة برمجة التطبيقات. وفيما يلي نوضح كيف يمكننا توجيه ChatGPT ليولد كود بلغة البرمجة بايثون لطلب بيانات الطقس من خلال الواجهة البرمجية لتطبيق للطقس OpenWeather <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>، وسنوضح كيف يمكننا تفادي أخطاء استخدام دوال قديمة deprecated لم تعد مدعومة، وكيف يمكننا إرسال الطلب من خلال الواجهة البرمجية OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> ونختبر تأثير المعاملات على سلوك وأداء النموذج.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="164090" href="https://academy.hsoub.com/uploads/monthly_2024_12/api_request_with_llm.png.acb528a511639fa793733e22b4522964.png" rel=""><img alt="api_request_with_llm.png" class="ipsImage ipsImage_thumbnailed" data-fileid="164090" data-ratio="41.86" data-unique="rhx354jmh" style="width: 700px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2024_12/api_request_with_llm.thumb.png.6e2fbb75178496ca1bb07570e61cfb53.png"></a>
</p>

<p>
	<strong>ملاحظة</strong>: انتبه إلى تكلفة استخدام الواجهة البرمجية للطقس OpenWeather <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> فهي مجانية لأول 1000 طلب فقط في اليوم الواحد.
</p>

<h2 id="promptschatgptapi">
	تحويل موجهات تشات جي بي تي لطلبات <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>
</h2>

<p>
	يتطلب المثال الذي سنطبقه عرض درجة الحرارة الحالية ووصف حالة الطقس. دعنا نوضح كيف يمكن أن يساعدنا شات جي بي تي ChatGPT في كتابة الكود، سنكتب موجهًا prompt على النحو التالي:
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p data-gramm="false">
		اكتب كود بايثون باستخدام مكتبة requests لإرسال طلب GET إلى واجهة OpenWeather <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> للحصول على بيانات الطقس، وذلك عبر نقطة الوصول التالية <code><a href="http://api.openweathermap.org/data/2.5/weather" ipsnoembed="true" rel="external nofollow">http://api.openweathermap.org/data/2.5/weather</a></code>. يجب أن يتضمن الطلب مفتاح <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> الخاص بك ضمن الرابط باستخدام المعامل<code> appid</code>. يجب أن تُعيد الواجهة البيانات بتنسيق JSON، ويجب على الكود استخراج درجة الحرارة من البيانات وإعادتها بوحدة الفهرنهايت
	</p>
</blockquote>

<p>
	سيولد شات جي بي تي ChatGPT هذا الكود ردًا على الموجه السابق:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4543_7" style=""><span class="kwd">import</span><span class="pln"> requests

</span><span class="com"># استبدل "YOUR_API_KEY" بمفتاح OpenWeather API الخاص بك.</span><span class="pln">
api_key </span><span class="pun">=</span><span class="pln"> </span><span class="str">"YOUR_API_KEY"</span><span class="pln">
api_url </span><span class="pun">=</span><span class="pln"> </span><span class="str">"http://api.openweathermap.org/data/2.5/weather"</span><span class="pln">

</span><span class="com"># اسم المدينة التي ترغب في الحصول على معلومات الطقس لها.</span><span class="pln">
city_name </span><span class="pun">=</span><span class="pln"> </span><span class="str">"London"</span><span class="pln">

</span><span class="com"># المعلمات المطلوبة لإرسال الطلب إلى واجهة API.</span><span class="pln">
params </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">'q'</span><span class="pun">:</span><span class="pln"> city_name</span><span class="pun">,</span><span class="pln">  </span><span class="com"># اسم المدينة</span><span class="pln">
    </span><span class="str">'appid'</span><span class="pun">:</span><span class="pln"> api_key</span><span class="pun">,</span><span class="pln">  </span><span class="com"># مفتاح API</span><span class="pln">
    </span><span class="str">'units'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'imperial'</span><span class="pln">  </span><span class="com"># استخدم metric للدرجة المئوية أو imperial للفهرنهايت</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
    </span><span class="com"># إرسال طلب GET إلى واجهة API.</span><span class="pln">
    response </span><span class="pun">=</span><span class="pln"> requests</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">api_url</span><span class="pun">,</span><span class="pln"> params</span><span class="pun">=</span><span class="pln">params</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># التحقق مما إذا كان الطلب ناجحًا</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">status_code </span><span class="pun">==</span><span class="pln"> </span><span class="lit">200</span><span class="pun">:</span><span class="pln">
        </span><span class="com"># قراءة وتحليل البيانات المستلمة بصيغة JSON.</span><span class="pln">
        weather_data </span><span class="pun">=</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="str">"بيانات الطقس لمدينة:"</span><span class="pun">,</span><span class="pln"> city_name</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="str">"درجة الحرارة:"</span><span class="pun">,</span><span class="pln"> weather_data</span><span class="pun">[</span><span class="str">'main'</span><span class="pun">][</span><span class="str">'temp'</span><span class="pun">],</span><span class="pln"> </span><span class="str">"°F"</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="str">"الوصف:"</span><span class="pun">,</span><span class="pln"> weather_data</span><span class="pun">[</span><span class="str">'weather'</span><span class="pun">][</span><span class="lit">0</span><span class="pun">][</span><span class="str">"description"</span><span class="pun">])</span><span class="pln">
    </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
        </span><span class="com"># طباعة رسالة خطأ إذا لم يكن الطلب ناجحًا.</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="str">"خطأ:"</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">status_code</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">text</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">except</span><span class="pln"> </span><span class="typ">Exception</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> e</span><span class="pun">:</span><span class="pln">
    </span><span class="com"># التعامل مع أي أخطاء أثناء تنفيذ الكود.</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">"حدث خطأ:"</span><span class="pun">,</span><span class="pln"> e</span><span class="pun">)</span></pre>

<p>
	يمكن أن تلاحظ أنه على الرغم من عدم تحديد الموقع المطلوب للواجهة البرمجية الخاصة بالطقي، فإن الكود المولد يتضمن متغير يسمى <code>city_name</code> وهو مضبوط على قيمة افتراضية هي لندن ويمكن تعديها حسب الطلب، ولا تنسى بالطبع استبدال <code>YOUR_API_KEY</code> بالمفتاح الخاص بك لواجهة برمجة التطبيق للطقس OpenWeather <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>.
</p>

<p>
	كي نتمكن من اختبار الكود المولد، نحتاج لأن يكون <a href="https://academy.hsoub.com/programming/python/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%AA%D9%87-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D9%88%D9%8A%D9%86%D8%AF%D9%88%D8%B2-10-r710/" rel="">مفسر interpreter لغة بايثون</a> مثبت على جهازنا، بعد تثبيته يمكن تشغيل الكود محرر أكواد مثل <a href="https://code.visualstudio.com/" rel="external nofollow">فيجوال ستوديو كود Visual Studio Code</a> أو باستخدام <a href="https://academy.hsoub.com/programming/workflow/%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B3%D8%B7%D8%B1-%D8%A7%D9%84%D8%A3%D9%88%D8%A7%D9%85%D8%B1-%D9%81%D9%8A-%D8%B9%D9%85%D9%84%D9%8A%D8%A9-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%85%D9%86-%D8%B7%D8%B1%D9%81-%D8%A7%D9%84%D8%B9%D9%85%D9%8A%D9%84-r1471/" rel="">سطر الأوامر Terminal</a>. إليك بيانات الطقس لمدينة لندن المستلمة من الواجهة البرمجية:
</p>

<pre class="ipsCode" id="ips_uid_4543_16">بيانات الطقس لمدينة: London
°F درجة الحرارة: 50.4 
الوصف: overcast clouds
</pre>

<p>
	سنحتاج غالبًا لتخزين البيانات التي حصلنا عليها من الواجهة البرمجية، وبالتالي سنحتاج لتحويل البيانات إلى تنسيق محدد مثل XML أو CSV أو استخدام أوامر <a href="https://wiki.hsoub.com/SQL" rel="external">لغة الاستعلام البنيوية SQL</a> لحفظ البيانات في جداول قاعدة البيانات، لذا سنطلب من النموذج اللغوي مساعدتنا في هذه المهمة عن طريق توليد الكود المناسب لكل تنسيق، فمثلا يمكننا إضافة الجملة التالية للموجه السابق:
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p data-gramm="false">
		ينبغي أن تكون المخرجات منسقة على هيئة أوامر للغة الاستعلام المهيكل SQL وتخزن البيانات في جدول يسمى <code>current_weather</code> والذي يحتوي الأعمدة التالية <code>city</code> و <code>temperature</code> و <code>weather_description</code> وأخيرًا <code>update_date</code>
	</p>
</blockquote>

<p>
	سيعيد ChatGPT نفس الكود السابق، ولكن مع إضافة بعض جمل الاستيراد وتحديث تنسيق المخرجات:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_369_7" style=""><span class="com"># استيراد المكتبات اللازمة</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> requests
</span><span class="kwd">import</span><span class="pln"> json
</span><span class="kwd">from</span><span class="pln"> datetime </span><span class="kwd">import</span><span class="pln"> datetime

</span><span class="com"># رابط الواجهة البرمجية</span><span class="pln">
api_url </span><span class="pun">=</span><span class="pln"> </span><span class="str">"http://api.openweathermap.org/data/2.5/weather"</span><span class="pln">

</span><span class="com"># إعداد المعاملات المطلوبة للطلب </span><span class="pln">
params </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">'q'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'الرياض'</span><span class="pun">,</span><span class="pln">  </span><span class="com"># اسم المدينة</span><span class="pln">
    </span><span class="str">'appid'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'مفتاح_API_الخاص_بك'</span><span class="pun">,</span><span class="pln">  </span><span class="com"># استبدل بمفتاح API الخاص بك</span><span class="pln">
    </span><span class="str">'units'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'imperial'</span><span class="pln">  </span><span class="com"># تحديد وحدة القياس (الفهرنهايت)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
    </span><span class="com"># إرسال طلب GET إلى الواجهة البرمجية</span><span class="pln">
    response </span><span class="pun">=</span><span class="pln"> requests</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">api_url</span><span class="pun">,</span><span class="pln"> params</span><span class="pun">=</span><span class="pln">params</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># التحقق من نجاح الطلب</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">status_code </span><span class="pun">==</span><span class="pln"> </span><span class="lit">200</span><span class="pun">:</span><span class="pln">
        </span><span class="com"># تحويل الاستجابة إلى صيغة JSON</span><span class="pln">
        weather_data </span><span class="pun">=</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">()</span><span class="pln">

        </span><span class="com"># استخراج البيانات المهمة</span><span class="pln">
        city </span><span class="pun">=</span><span class="pln"> params</span><span class="pun">[</span><span class="str">'q'</span><span class="pun">]</span><span class="pln">  </span><span class="com"># اسم المدينة</span><span class="pln">
        temperature </span><span class="pun">=</span><span class="pln"> weather_data</span><span class="pun">[</span><span class="str">'main'</span><span class="pun">][</span><span class="str">'temp'</span><span class="pun">]</span><span class="pln">  </span><span class="com"># درجة الحرارة</span><span class="pln">
        weather_description </span><span class="pun">=</span><span class="pln"> weather_data</span><span class="pun">[</span><span class="str">'weather'</span><span class="pun">][</span><span class="lit">0</span><span class="pun">][</span><span class="str">'description'</span><span class="pun">]</span><span class="pln">  </span><span class="com"># وصف الطقس</span><span class="pln">
        update_date </span><span class="pun">=</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">utcnow</span><span class="pun">().</span><span class="pln">strftime</span><span class="pun">(</span><span class="str">'%Y-%m-%d %H:%M:%S'</span><span class="pun">)</span><span class="pln">  </span><span class="com"># وقت التحديث</span><span class="pln">

        </span><span class="com"># إنشاء أمر SQL لإدخال البيانات في قاعدة البيانات</span><span class="pln">
        sql_command </span><span class="pun">=</span><span class="pln"> f</span><span class="str">"INSERT INTO current_weather (city, temperature, weather_description, update_date) VALUES ('{city}', {temperature}, '{weather_description}', '{update_date}')"</span><span class="pln">

        </span><span class="com"># طباعة أمر SQL</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="str">"أمر SQL للإدخال في قاعدة البيانات:"</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">sql_command</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
        </span><span class="com"># طباعة رسالة خطأ في حال فشل الطلب</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="str">"حدث خطأ في الطلب:"</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">status_code</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">text</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">except</span><span class="pln"> </span><span class="typ">Exception</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> e</span><span class="pun">:</span><span class="pln">
    </span><span class="com"># طباعة رسالة خطأ في حال حدوث استثناء</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">"حدث خطأ أثناء تنفيذ البرنامج:"</span><span class="pun">,</span><span class="pln"> e</span><span class="pun">)</span></pre>

<p>
	وسيؤدي تنفيذ الكود السابق لتوليد الأمر التالي بلغة SQL:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_8125_13" style=""><span class="pln">INSERT INTO current_weather </span><span class="pun">(</span><span class="pln">city</span><span class="pun">,</span><span class="pln"> temperature</span><span class="pun">,</span><span class="pln"> weather_description</span><span class="pun">,</span><span class="pln"> update_date</span><span class="pun">)</span><span class="pln"> VALUES </span><span class="pun">(</span><span class="str">'London'</span><span class="pun">,</span><span class="pln"> </span><span class="lit">53.37</span><span class="pun">,</span><span class="pln"> </span><span class="str">'broken clouds'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'2024-02-06 04:43:35'</span><span class="pun">)</span></pre>

<p>
	ولكن سنرى تحذيرًا ينبهنا إلى عدم استخدام الدالة <code>()utcnow</code> حيث أنها مهملة deprecated ولن تكون متوفرة في إصدارات قادمة وهذا قد يتسبب في تعطل الكود الخاص بنا في التحديثات المستقبلية:
</p>

<pre class="ipsCode">DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
</pre>

<p>
	وكي نمنع  ChatGPT من استخدام دوال مهملة deprecated، سنضيف التوجيه التالي:
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p data-gramm="false">
		لا تستخدم أي دوال قديمة deprecated من المحتمل إزالتها في التحديثات القادمة.
	</p>
</blockquote>

<p>
	بعد إضافة هذا السطر، سيبدل ChatGPT الدالة <code>()utcnow</code> بدالة أخرى كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_369_12" style=""><span class="com"># استخدم  الكائن timezone-aware لتحديث  التاريخ</span><span class="pln">
update_date </span><span class="pun">=</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">now</span><span class="pun">(</span><span class="pln">timezone</span><span class="pun">.</span><span class="pln">utc</span><span class="pun">).</span><span class="pln">strftime</span><span class="pun">(</span><span class="str">'%Y-%m-%d %H:%M:%S'</span><span class="pun">)</span></pre>

<p>
	يعيد لنا هذا الكود مجددًا أمرًا بلغة SQL ويمكن اختباره في أي محرر استعلامات Query editors لنظام إدارة قواعد البيانات DBMS. وفي حال كنت تولد هذا الكود ضمن تطبيق ويب تقليدي فمن المتوقع تشغيل أمر SQL مباشرة بعد توليده من خلال الواجهة البرمجية وتحديث بيانات قاعدة البيانات في الزمن الحقيقي.
</p>

<h2 id="openaiapichatgpt">
	استخدام الواجهة البرمجية OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> بدلًا من شات ChatGPT
</h2>

<p>
	تملك العديد من نماذج اللغات الضخمة واجهة برمجة تطبيقات <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> تمكّن المطور من التفاعل مع النماذج اللغوية الضخمة برمجيًا ودمجها بسلاسة داخل تطبيقاته، سنستفيد من هذه الواجهات البرمجية لإنشاء مساعد ذكي بشكل افتراضي، يوفر لنا هذا المساعد ميزات عديدة مثل إكمال الكود أو توليده من الصفر أو إعادة صياغته وتحسينه.
</p>

<p>
	ويمكن تحسين واجهات المستخدم وتخصيصها من خلال قوالب جاهزة من الموجهات Prompts templates، كما يتيح لنا دمج النماذج اللغوية الضخمة برمجيًا جدولة المهام أو تفعيلها عند تحقق شروط معينة ويسهل أتمتة المساعد الذكي الذي نعمل عليه.
</p>

<p>
	سننفذ في هذا المثال نفس المهمة السابقة وهي طلب معلومات الطقس باستخدام بايثون للتفاعل برمجيًا مع الواجهة البرمجية OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> وتوليد الكود من خلال واجهة المستخدم بدلًا من كتابة موجّه في ChatGPT مباشرة. بداية علينا تثبيت الواجهة البرمجية OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> باستخدام الأمر التالي:
</p>

<pre class="ipsCode">pip install openai
</pre>

<p>
	الآن لنتمكن من استخدام الواجهة البرمجية OpenAI <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> نحتاج لمفتاح الواجهة <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> key وهو غير مجاني ويتطلب دفع رسوم.
</p>

<p>
	بعدها، يمكننا استيراد المكتبة openai وإرسال نفس الموجه المستخدم من قبل مع إزالة الجزء الخاص بتوليد أوامر SQL، كما نحدد النموذج المستخدم GPT-3.5 من خلال الكود التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_369_18" style=""><span class="kwd">import</span><span class="pln"> openai

openai</span><span class="pun">.</span><span class="pln">api_key </span><span class="pun">=</span><span class="pln"> </span><span class="str">"YOUR_API_KEY"</span><span class="pln">  </span><span class="com"># استبدله بمفتاح الواجهة البرمجية الخاص بك</span><span class="pln">

response </span><span class="pun">=</span><span class="pln"> openai</span><span class="pun">.</span><span class="pln">chat</span><span class="pun">.</span><span class="pln">completions</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="pln">
  model</span><span class="pun">=</span><span class="str">'gpt-3.5-turbo'</span><span class="pun">,</span><span class="pln">
  max_tokens</span><span class="pun">=</span><span class="lit">1024</span><span class="pun">,</span><span class="pln"> temperature</span><span class="pun">=</span><span class="lit">0.3</span><span class="pun">,</span><span class="pln"> top_p</span><span class="pun">=</span><span class="lit">0.9</span><span class="pun">,</span><span class="pln">
  messages </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    </span><span class="pun">{</span><span class="str">'role'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'system'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'content'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'أنت مساعد ذكي قائم على الذكاء الاصطناعي.'</span><span class="pun">},</span><span class="pln">
    </span><span class="pun">{</span><span class="str">'role'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'user'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'content'</span><span class="pun">:</span><span class="pln"> </span><span class="str">"اكتب كود بلغة بايثون باستخدام مكتبة requests لإرسال طلب GET إلى واجهة OpenWeather API للحصول على بيانات الطقس، وذلك عبر نقطة الوصول التالية http://api.openweathermap.org/data/2.5/weather. يجب أن يتضمن الطلب مفتاح API الخاص بك ضمن الرابط باستخدام المعامل appid. يُتوقع أن تُعيد الواجهة البيانات بتنسيق JSON، ويجب على الكود استخراج درجة الحرارة من البيانات وإعادتها بوحدة الفهرنهايت ولا يستخدم أي دوال قديمة غير مستعملة"</span><span class="pun">},</span><span class="pln">
  </span><span class="pun">]</span><span class="pln">
response_message </span><span class="pun">=</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">choices</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">message</span><span class="pun">.</span><span class="pln">content
</span><span class="kwd">print</span><span class="pun">(</span><span class="pln">response_message </span><span class="pun">)</span></pre>

<p>
	سيولد النموذج اللغوي الضخم كود بايثون التالي، والذي يجلب درجات الحرارة لمدينة لندن:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_369_20" style=""><span class="kwd">import</span><span class="pln"> requests

</span><span class="com"># مفتاح الواجهة البرمجية ونقطة الوصول</span><span class="pln">
url </span><span class="pun">=</span><span class="pln"> </span><span class="str">"http://api.openweathermap.org/data/2.5/weather"</span><span class="pln">
api_key </span><span class="pun">=</span><span class="pln"> </span><span class="str">"YOUR_API_KEY"</span><span class="pln">

</span><span class="com"># معاملات الاستعلام</span><span class="pln">
params </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">'q'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'London,uk'</span><span class="pun">,</span><span class="pln">  </span><span class="com"># مثال على المدينة ورمز الدولة.</span><span class="pln">
    </span><span class="str">'appid'</span><span class="pun">:</span><span class="pln"> api_key
</span><span class="pun">}</span><span class="pln">

</span><span class="com"># إرسال طلب GET.</span><span class="pln">
response </span><span class="pun">=</span><span class="pln"> requests</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">url</span><span class="pun">,</span><span class="pln"> params</span><span class="pun">=</span><span class="pln">params</span><span class="pun">)</span><span class="pln">

</span><span class="com"># التحقق مما إذا كان الطلب ناجحاً.</span><span class="pln">
</span><span class="kwd">if</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">status_code </span><span class="pun">==</span><span class="pln"> </span><span class="lit">200</span><span class="pun">:</span><span class="pln">
    </span><span class="com"># تحليل استجابة JSON.</span><span class="pln">
    data </span><span class="pun">=</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">()</span><span class="pln">

    </span><span class="com"># طباعة درجة الحرارة بالدرجة المئوية.</span><span class="pln">
    temperature </span><span class="pun">=</span><span class="pln"> data</span><span class="pun">[</span><span class="str">'main'</span><span class="pun">][</span><span class="str">'temp'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">273.15</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">f</span><span class="str">"درجة الحرارة في لندن هي {temperature:.2f}°C."</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">f</span><span class="str">"خطأ: {response.status_code}"</span><span class="pun">)</span></pre>

<p>
	لاحظ أننا أزلنا التعليمات الخاصة بتحديد درجات الحرارة بالفهرنهايت لذا لم يحدد النموذج اللغوي الضخم وحدة القياس عند طلب الواجهة البرمجية للطقس، ولكنه اختار حسابها رياضيًا بتحويل الوحدة من <a href="https://ar.wikipedia.org/wiki/%D9%83%D9%84%D9%81%D9%86" rel="external nofollow">الكلفن</a> إلى <a href="https://ar.wikipedia.org/wiki/%D8%AF%D8%B1%D8%AC%D8%A9_%D8%AD%D8%B1%D8%A7%D8%B1%D8%A9_%D9%85%D8%A6%D9%88%D9%8A%D8%A9" rel="external nofollow">سلسيوس</a> عند عرض النتائج.
</p>

<h2 id="-2">
	الاستفادة من معاملات النموذج اللغوي الضخم لتحقيق أقصى استفادة
</h2>

<p>
	يمكننا ضبط بعض المعاملات عند استخدام الواجهة البرمجية لنماذج الذكاء الاصطناعي والنماذج اللغوية الضخمة من أجل تعديل الردود المولدة منها، فهناك معاملات يمكنها تحديد مستوى العشوائية والإبداع في الردود المولدة كالمعامل <code>Temperature</code>، ومعاملات يمكنها التحكم بتكرار النصوص المولدة مثل<code> Repetition Penalty</code>، بينما تؤثر معاملات أخرى على النص اللغوي المولد مما يساعد على التحكم في جودة الكود الناتج.
</p>

<p>
	في الكود السابق، يمكننا ضبط قيم معاملات GPT في السطر السابع على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8125_25" style=""><span class="pln">max_tokens</span><span class="pun">=</span><span class="lit">1024</span><span class="pun">,</span><span class="pln"> temperature</span><span class="pun">=</span><span class="lit">0.3</span><span class="pun">,</span><span class="pln"> top_p</span><span class="pun">=</span><span class="lit">0.9</span><span class="pun">,</span></pre>

<table>
	<thead>
		<tr>
			<th>
				المعامل
			</th>
			<th>
				الوصف
			</th>
			<th>
				التأثير على الكود المولد
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				 <code>Temperature</code>
			</td>
			<td>
				يعني درجة الحرارة إذ يتحكم هذا المعامل في عشوائية الردود المولدة، أو درجة الإبداعية في الردود، فالقيمة العالية لهذا المعامل تزيد عشوائية الردود المولدة، بينما القيمة المنخفضة تولد ردودًا واقعية، القيم الممكنة لهذا المعامل بين 0 و 2 والقيمة الافتراضية 0.7 أو 1 بحسب النموذج المستخدم.
			</td>
			<td>
				ستولد القيمة المنخفضة لهذا المعامل كودًا آمنًا يتبع الأنماط والهياكل المكتسبة خلال عملية التدريب، بينما ستولد القيمة العالية ردودًا أكثر تميزًا وغير اعتيادية مما يرفع من احتمالية الأخطاء والتناقضات البرمجية.
			</td>
		</tr>
		<tr>
			<td>
				<code>Max token</code>
			</td>
			<td>
				يحدد هذا المعامل الحد الأقصى لعدد الوحدات النصية tokens المولدة في الاستجابة. إذا ضبطته بقيمة صغيرة، فقد يتسبب ذلك في أن تكون الاستجابة قصيرة جدًا، بينما إذا تم ضبطته بقيمة كبيرة جدًا، قد يؤدي ذلك لاستهلاك عدد كبير من الوحدات النصية المتاحة، مما يزيد من تكلفة الاستخدام
			</td>
			<td>
				يجب أن يضبط المعامل على قيمة عالية بما يكفي لتغطية جميع الأكواد التي تحتاج إلى توليدها. ويمكنك تقليله إذا كنت ترغب في توليد الكود فقط دون توفير شرح الكود المضاف من النموذج
			</td>
		</tr>
		<tr>
			<td>
				 <code>top_p</code>
			</td>
			<td>
				<p>
					يتحكم هذا المعامل في اختيار الكلمات التالية بتقليص نطاق الخيارات المتاحة. إذا منحناه القيمة 0.1 سيقتصر على أفضل 10% من الكلمات الأكثر احتمالاً، مما يزيد الدقة. وإذا منحناه القيمة 0.5 سيوسع الاختيار ليشمل أفضل 50%، مما يعزز التنوع. ويساعد في موازنة الدقة والإبداع في الردوع.
				</p>
			</td>
			<td>
				عند ضبط المعامل بقيمة منخفضة، يصبح الكود المولد أكثر توقعًا وارتباطًا بالسياق، ويجري اختيار الاحتمالات الأكثر ترجيحًا بناءً على السياق الحالي. أما عند زيادة قيمته يزداد التنوع في المخرجات، مما قد يؤدي إلى توليد كود أقل ارتباطًا بالسياق وقد يتسبب في أخطاء أو توليد كود متناقض.
			</td>
		</tr>
		<tr>
			<td>
				<code>frequency_penalty</code>
			</td>
			<td>
				<p>
					يعني عقوبة التكرار إذ يحدّ هذا المعامل من تكرار الكلمات والعبارات في ردود النموذج اللغوي الكبير. عند ضبطه على قيمة عالية، يقلل من تكرار الكلمات أو العبارات التي استخدمها النموذج سابقًا. أما عند ضبطه على قيمة منخفضة، فيسمح للنموذج بتكرار الكلمات والعبارات. حيث تتراوح قيم هذا المعامل من 0 إلى 2.
				</p>
			</td>
			<td>
				تضمن القيمة العالية لهذا المعامل تقليل التكرار في الكود المولد، مما يؤدي لتوليد أكواد أكثر تنوعًا وإبداعًا. ومع ذلك، قد يؤدي ذلك إلى اختيار عناصر أقل كفاءة أو غير صحيحة. من ناحية أخرى، عند خفض قيمته، قد لا يستكشف طرقًا متنوعة للحل.
			</td>
		</tr>
		<tr>
			<td>
				 <code>presence_penalty</code>
			</td>
			<td>
				يعني عقوبة التواجد ويرتبط هذا المعامل بالمعامل السابق فكلاهما يشجعان على التنوع والتفرد في الكلمات المستخدمة، حيث يعاقب المعامل السابق الوحدات النصية التي استخدمت عدة مرات في النص في حين يعاقب المعامل الحالي الوحدات النصية التي ظهرت من قبل بغض النظر عن مرات التكرار، يتلخص تأثير  معامل عقوبة التكرار في أنه يقلل من تكرار الكلمات بينما يركز معامل عقوبة التواجد على استخدام كلمات جديدة بالكليّة، والقيمة الصغرى  لهذا المعامل هي 0 أما القيمة العظمى فهي 2.
			</td>
			<td>
				له تأثير مشابه لعقوبة التكرار، فالقيمة العالية تشجع النموذج على استكشاف حلول بديلة، ولكن قد يصبح الكود المولد أقل كفاءة ومليء بالأخطاء، بينما تسمح القيمة المنخفضة بتكرار الكود مما يؤدي إلى كود أكثر كفاءة واتساقًا، خاصةً عند وجود أنماط متكررة.
			</td>
		</tr>
		<tr>
			<td>
				 <code>stop</code>
			</td>
			<td>
				<p>
					دور هذا المعامل هو تحديد نقطة التوقف في عملية توليد النصوص. عند ضبطه مع سلسلة معينة مثل "n/"، سيتوقف النموذج عن توليد النصوص بمجرد أن يصادف هذه السلسلة مما يساعد في التحكم بدقة في نهاية الرد المولد وتحديد متى يجب أن يتوقف النموذج عن إضافة المزيد من الكلمات.
				</p>
			</td>
			<td>
				<p>
					يمنع هذا المعامل النموذج من توليد أجزاء من الكود لا تتناسب مع السياق أو الوظيفة المطلوبة. يجب أن تكون سلسلة التوقف نقطة منطقية وواضحة لنهاية الكود، مثل نهاية دالة أو نهاية حلقة تكرارية، لضمان توقف النموذج عن توليد المزيد من الكود بشكل مناسب.
				</p>
			</td>
		</tr>
	</tbody>
</table>

<p>
	لنختبر كيفية تأثير هذه المعاملات على توليد الكود، سنجرب تعديل معاملات عقوبة التكرار <code>frequency_penalty</code> وعقوبة التواجد <code>presence_penalty</code> .
</p>

<p>
	عند تهيئة عقوبة التكرار <code>frequency_penalty</code> بقيمة مرتفعة وهي 1.5،سينتج النموذج اللغوي الضخم كودًا إضافيًا ليلاقى التوقعات محاولًا تفادي تكرار المخرجات السابقة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_369_24" style=""><span class="com"># ...</span><span class="pln">

</span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
    </span><span class="com"># إرسال طلب GET مع المعلمات والحصول على البيانات في تنسيق JSON.</span><span class="pln">
    response </span><span class="pun">=</span><span class="pln"> requests</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">url</span><span class="pun">,</span><span class="pln"> params</span><span class="pun">=</span><span class="pln">params</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">status_code </span><span class="pun">==</span><span class="pln"> </span><span class="lit">200</span><span class="pun">:</span><span class="pln">
        weather_data </span><span class="pun">=</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">()</span><span class="pln">

        </span><span class="com"># طباعة بعض المعلومات المهمة من بيانات الطقس.</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="str">"المدينة:"</span><span class="pun">,</span><span class="pln"> weather_data</span><span class="pun">[</span><span class="str">'name'</span><span class="pun">])</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="str">"درجة الحرارة:"</span><span class="pun">,</span><span class="pln"> weather_data</span><span class="pun">[</span><span class="str">'main'</span><span class="pun">][</span><span class="str">'temp'</span><span class="pun">],</span><span class="pln"> </span><span class="str">"K"</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">except</span><span class="pln"> requests</span><span class="pun">.</span><span class="pln">exceptions</span><span class="pun">.</span><span class="typ">RequestException</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> e</span><span class="pun">:</span><span class="pln">
    </span><span class="com"># التعامل مع أي خطأ حدث أثناء الطلب</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">"خطأ:"</span><span class="pun">,</span><span class="pln"> e</span><span class="pun">)</span></pre>

<p>
	عند العمل مع الكود الذي يتعامل مع واجهة برمجية <abbr title="Application Programming Interface | واجهة برمجية"><a href="https://academy.hsoub.com/programming/general/%D9%85%D8%A7-%D9%87%D9%8A-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D9%84%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-api%D8%9F-r1512/" rel=""><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></a></abbr> من المهم أن نلاحظ أن النتائج قد تكون غير متوقعة. ففي الكود السابق، على الرغم من أن إضافة معالجة الأخطاء مفيدة، إلا أننا لم نطلب من النموذج توليد كود يتعامل مع جميع الأخطاء المحتملة بشكل دقيق. لهذا السبب، عند إرسال نفس الطلب مرة أخرى مع نفس المعاملات، قد نحصل على نتائج مختلفة تمامًا. ولضمان استقرار الكود ومعالجته للأخطاء بشكل صحيح، يجب إضافة موجه أولي initial prompt يوجّه النموذج لتوليد الكود مع معالجة الأخطاء بشكل مناسب. بهذا الشكل، يمكن ضمان أن الكود سيكون متسقًا ويعمل كما هو متوقع في جميع الحالات.
</p>

<p>
	يمكننا ضبط المعامل <code>presence_penalty</code> بقيمة عالية ولتكن 2.0 للحصول على تأثير مشابه، حيث يتفادى النموذج تكرار المخرجات السابقة ويرسل طلب جديد للواجهة البرمجية للطقس  OpenWeather <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> من خلال دالة ممرًا لها مفتاح الواجهة البرمجية <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> key كمعامل كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_369_26" style=""><span class="kwd">import</span><span class="pln"> requests

</span><span class="kwd">def</span><span class="pln"> get_weather</span><span class="pun">(</span><span class="pln">api_key</span><span class="pun">):</span><span class="pln">
    url </span><span class="pun">=</span><span class="pln"> </span><span class="str">"http://api.openweathermap.org/data/2.5/weather"</span><span class="pln">
    params </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">'q'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'London,uk'</span><span class="pun">,</span><span class="pln">  </span><span class="com"># مثال على المدينة ورمز الدولة.</span><span class="pln">
        </span><span class="str">'appid'</span><span class="pun">:</span><span class="pln"> api_key
    </span><span class="pun">}</span><span class="pln">

    response </span><span class="pun">=</span><span class="pln"> requests</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">url</span><span class="pun">,</span><span class="pln"> params</span><span class="pun">=</span><span class="pln">params</span><span class="pun">)</span><span class="pln">  </span><span class="com"># إضافة سطر إرسال الطلب</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">status_code </span><span class="pun">==</span><span class="pln"> </span><span class="lit">200</span><span class="pun">:</span><span class="pln">
        data </span><span class="pun">=</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> data
    </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="str">"خطأ:"</span><span class="pun">,</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">status_code</span><span class="pun">)</span><span class="pln">

</span><span class="com"># استبدل "YOUR_API_KEY" بمفتاح الواجهة البرمجية الفعلي من OpenWeather.</span><span class="pln">
api_key </span><span class="pun">=</span><span class="pln"> </span><span class="str">"YOUR_API_KEY"</span><span class="pln">

weather_data </span><span class="pun">=</span><span class="pln"> get_weather</span><span class="pun">(</span><span class="pln">api_key</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="pln">weather_data</span><span class="pun">)</span></pre>

<p>
	إن إنشاء دالة برمجية مخصصة لطلب الواجهة البرمجية لتطبيق الطقس إضافة مفيدة للكود، ولكن تمرير مفتاح الواجهة من خلال الدالة أمر غريب، فمن المعتاد في أغلب حالات الاستخدام تمرير المدينة <code>city</code> كمعامل للدالة، وهنا يمكننا معالجة هذه المشكلة بتعديل الموجه الأولي.
</p>

<p>
	يتطلب ضبط قيم معاملات نماذج اللغة الضخمة الكثير من التجربة، فبعض النتائج تكون غير متوقعة وبعض التعديلات قد تبدو عديمة التأثير، فتعديل المعاملات لا يمتلك تأثيرًا موحدًا يضمن اتباع أفضل الممارسات في توليد الكود.
</p>

<h2 id="-3">
	دمج النماذج اللغوية الضخمة في ملفات الكود المصدري
</h2>

<p>
	بمجرد أن يصبح الكود المولد باستخدام النموذج اللغوي الضخم محسنًا وجاهزًا للعمل، ستكون الخطوة التالية هي دمج الكود الناتج مع الأكواد البرمجية السابقة. لكن قد يؤدي نسخ الكود بشكل مباشر للكود الموجود سابقًا لمشكلات في تنظيم الكود وتنسيقها مما يصعب صيانة وتعديل الكود لاحقًا.
</p>

<p>
	يمكننا تبسيط هذه العملية بتحويل الكود إلى أجزاء قابلة لإعادة الاستخدام مثل دوال برمجية أو كائنات وأصناف برمجية، ويمكننا تحقيق هذا أيضًا باستخدام النموذج اللغوي الضخم ما دامت التعليمات المفصلة للقيام بهذا مرفقةً في الموجه Prompt، ويجب أيضاً تحديد مدخلات هذه الدوال والمخرجات المتوقعة منها.
</p>

<p>
	دعنا نجعل النموذج اللغوي الضخم ينشئ الكائن البرمجي <code>weather</code> الذي يحوي دالة <code>get_weather</code> ، من خلال إضافة الأوامر التالية للموجه:
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p data-gramm="false">
		ينبغي أن يوضع طلب الواجهة البرمجية OpenWeather <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> في كائن برمجي يسمى <code>weather</code> والذي يحوي دالة تسمى <code>get_weather</code> وينبغي تمرير المدينة <code>city</code> كمدخلات للدالة.
	</p>
</blockquote>

<p>
	عندها سنحصل على كود بايثون التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_369_28" style=""><span class="kwd">import</span><span class="pln"> requests

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Weather</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> __init__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> api_key</span><span class="pun">):</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">api_key </span><span class="pun">=</span><span class="pln"> api_key
        self</span><span class="pun">.</span><span class="pln">temperature </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> get_weather</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> city</span><span class="pun">):</span><span class="pln">
        url </span><span class="pun">=</span><span class="pln"> </span><span class="str">'http://api.openweathermap.org/data/2.5/weather'</span><span class="pln">
        params </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="str">'q'</span><span class="pun">:</span><span class="pln"> city</span><span class="pun">,</span><span class="pln">
            </span><span class="str">'appid'</span><span class="pun">:</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">api_key</span><span class="pun">,</span><span class="pln">
            </span><span class="str">'units'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'imperial'</span><span class="pln">  </span><span class="com"># طلب درجة الحرارة بالفهرنهايت.</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        response </span><span class="pun">=</span><span class="pln"> requests</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">url</span><span class="pun">,</span><span class="pln"> params</span><span class="pun">=</span><span class="pln">params</span><span class="pun">)</span><span class="pln">
        data </span><span class="pun">=</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">()</span><span class="pln">

        </span><span class="kwd">if</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">status_code </span><span class="pun">==</span><span class="pln"> </span><span class="lit">200</span><span class="pun">:</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">temperature </span><span class="pun">=</span><span class="pln"> data</span><span class="pun">[</span><span class="str">'main'</span><span class="pun">][</span><span class="str">'temp'</span><span class="pun">]</span><span class="pln">
            </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">f</span><span class="str">"درجة الحرارة في {city} هي {self.temperature}°F."</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
            </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">f</span><span class="str">"خطأ: {data['message']}"</span><span class="pun">)</span><span class="pln">

</span><span class="com"># استبدل "YOUR_API_KEY" بمفتاح الواجهة البرمجية الفعلي من OpenWeather.</span><span class="pln">
api_key </span><span class="pun">=</span><span class="pln"> </span><span class="str">"YOUR_API_KEY"</span><span class="pln">

weather </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Weather</span><span class="pun">(</span><span class="pln">api_key</span><span class="pun">)</span><span class="pln">
weather</span><span class="pun">.</span><span class="pln">get_weather</span><span class="pun">(</span><span class="str">'London'</span><span class="pun">)</span></pre>

<p>
	يولّد النموذج دالة قابلة للتشغيل بواسطة الكائن <code>weather</code>، ويمكننا دمج هذا الكائن بتطبيق آخر بكل سهولة، ولكن علينا معالجة الأخطاء التي قد تقع لضمان عدم فشل عمل التطبيق وعدم عرضه لبيانات حساسة أو سرية.
</p>

<h2 id="-4">
	اختبار التطبيق ومعالجة الأخطاء
</h2>

<p>
	إن فشل طلبات واجهة البرمجة للتطبيق <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> أمر شائع في التطبيقات الحقيقية، فيمكن أن تفشل الطلبات لعدة أسباب، لذا لضمان الاعتمادية ينبغي أن يتضمن الكود حالات لمعالجة الأخطاء التي يمكن أن تقع لمنع فشل التطبيق الكامل، في أغلب الأمثلة السابقة كان الكود المولد بواسطة النموذج يحوي شرطًا يختبر رمز الحالة <a href="https://ar.wikipedia.org/wiki/%D8%A8%D8%B1%D9%88%D8%AA%D9%88%D9%83%D9%88%D9%84_%D9%86%D9%82%D9%84_%D8%A7%D9%84%D9%86%D8%B5_%D8%A7%D9%84%D9%81%D8%A7%D8%A6%D9%82" rel="external nofollow">لبروتوكول نقل النص الفائق HTTP </a>، فالرمز 200 يعبر عن نجاح الطلب بينما أي رمز آخر يعبر عن فشل الطلب أو وجود خطأ ينبغي التعامل معه فيقوم الكود المولد بطباعته، ولكن هذا الشرط لا يأخذ في الاعتبار حالات أخطاء شائعة أخرى مثل انتهاء وقت الطلب timeout أو أخطاء <a href="https://ar.wikipedia.org/wiki/%D9%86%D8%B8%D8%A7%D9%85_%D8%A3%D8%B3%D9%85%D8%A7%D8%A1_%D8%A7%D9%84%D9%86%D8%B7%D8%A7%D9%82%D8%A7%D8%AA" rel="external nofollow">نظام أسماء النطاقات DNS</a>.
</p>

<p>
	لضمان الاعتمادية ينبغي أن يحوي الكود المولد آلية لمعالجة الأخطاء، مثل استخدام <code>try-catch</code> عند إجراء طلب من خلال واجهة برمجة التطبيق <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>. لذا أضفنا النص التالي للموجه  السابق:
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p data-gramm="false">
		لمعالجة الأخطاء، استخدم <code>try-catch</code> عند إرسال طلبات للواجهة البرمجية <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>، وعليك التأكد أن رمز الحالة لبروتوكول HTTP يساوي 200 فكل شيء على ما يرام أما إن كان رمز مختلف عليك طباعة الخطأ الذي حدث
	</p>
</blockquote>

<p>
	ولّد هذا التوجيه كود بايثون يحتوي كلتلة <code>try-catch</code> تعالج الأخطاء المحتملة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_369_30" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Weather</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> __init__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> api_key</span><span class="pun">):</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">api_key </span><span class="pun">=</span><span class="pln"> api_key
        self</span><span class="pun">.</span><span class="pln">temperature </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">

    </span><span class="kwd">def</span><span class="pln"> get_weather</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> city</span><span class="pun">):</span><span class="pln">
        </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># بناء عنوان URL للواجهة البرمجية باستخدام المدينة ومفتاح API المقدم.</span><span class="pln">
            url </span><span class="pun">=</span><span class="pln"> </span><span class="str">"http://api.openweathermap.org/data/2.5/weather"</span><span class="pln">
            params </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="str">'q'</span><span class="pun">:</span><span class="pln"> city</span><span class="pun">,</span><span class="pln"> 
                      </span><span class="str">'appid'</span><span class="pun">:</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">api_key</span><span class="pun">,</span><span class="pln">
                      </span><span class="str">'units'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'imperial'</span><span class="pln">  </span><span class="com"># الحصول على درجة الحرارة بالفهرنهايت.</span><span class="pln">
                      </span><span class="pun">}</span><span class="pln">

            </span><span class="com"># إرسال طلب GET إلى الواجهة البرمجية.</span><span class="pln">
            response </span><span class="pun">=</span><span class="pln"> requests</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">url</span><span class="pun">,</span><span class="pln"> params</span><span class="pun">=</span><span class="pln">params</span><span class="pun">)</span><span class="pln">

            </span><span class="com"># التحقق من حالة الاستجابة  </span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">status_code </span><span class="pun">==</span><span class="pln"> </span><span class="lit">200</span><span class="pun">:</span><span class="pln">
                </span><span class="com"># تحليل الاستجابة بتنسيق JSON</span><span class="pln">
                data </span><span class="pun">=</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">json</span><span class="pun">()</span><span class="pln">

                </span><span class="com"># استخراج درجة الحرارة بالفهرنهايت من الاستجابة.</span><span class="pln">
                self</span><span class="pun">.</span><span class="pln">temperature </span><span class="pun">=</span><span class="pln"> data</span><span class="pun">[</span><span class="str">'main'</span><span class="pun">][</span><span class="str">'temp'</span><span class="pun">]</span><span class="pln">
            </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
                </span><span class="com"># عرض وصف الخطأ إذا كانت حالة الاستجابة ليست 200.</span><span class="pln">
                </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">f</span><span class="str">'خطأ: {response.status_code} - {response.json()["message"]}'</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">except</span><span class="pln"> requests</span><span class="pun">.</span><span class="pln">exceptions</span><span class="pun">.</span><span class="typ">RequestException</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> e</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># التعامل مع أي استثناءات تحدث أثناء الطلب.</span><span class="pln">
            </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">f</span><span class="str">'خطأ: {e}'</span><span class="pun">)</span></pre>

<p>
	يعد الكود المولد بواسطة النموذج اللغوي الضخم نقطة بداية جيدة، لكنك ستحتاج في الغالب لتعديلات إضافية لتحصيصه وزيادة الوظائف التي يقوم به، على سبيل المثال من الممكن تسجيل الأخطاء التي تحدث وتنبيه المستخدم من أخطاء الإعدادات التي قد تشمل أخطاء في استيثاق الواجهة البرمجية <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> authentication error ويمكن للمطورين إضافة واجهة برمجية احتياطية في حالة تعطل الواجهة الأساسية.
</p>

<p>
	بمجرد أن يصبح الكود قادرًا على تأدية وظيفته كما ينبغي، فالخطوة التالية مهمة للتأكد من صموده عند التشغيل الفعلي، وينبغي اختبار كل الوظائف والحالات الممكنة للخطأ، ولضمان اعتمادية قوية يمكننا <a href="https://academy.hsoub.com/programming/advanced/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D8%AF%D8%A7%D8%A9-testgrid-%D9%88%D8%A3%D9%87%D9%85%D9%8A%D8%AA%D9%87%D8%A7-%D9%81%D9%8A-%D8%A3%D8%AA%D9%85%D8%AA%D8%A9-%D8%A7%D9%84%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r2390/" rel="">أتمتة عملية الاختبارات</a> وتقييم الأداء من خلال قياس مؤشرات الأداء المختلفة مثل وقت التشغيل واستخدام <a href="https://academy.hsoub.com/apps/operating-systems/%D8%A7%D9%84%D8%B0%D8%A7%D9%83%D8%B1%D8%A9-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9%D9%87%D8%A7-r880/" rel="">ذاكرة الوصول العشوائي</a> واستخدام الموارد الحاسوبية، ستساعدنا هذه المؤشرات على تحديد نقاط الخلل في النظام وتحسين الموجهات prompts وصقل fine-tune معاملات النموذج اللغوي الضخم.
</p>

<h2 id="-5">
	تطور نماذج اللغات الضخمة
</h2>

<p>
	تطورت النماذج اللغوية الضخمة لكنها لن تستبدل الخبرة البشرية حاليًا، بل سيقتصر دورها على توليد الكود البرمجي مما يسرّع ويسهّل عملية التطوير كما أنها قادرة على توليد نسخ متنوعة من الكود مما يساعد المطور على اختيار أفضل خيار من بينها، كما يمكنها تولي إنجاز المهام البسيطة والتكرارية لزيادة إنتاجية المطورين، مما يتيح للمطورين التركيز على المهام المعقدة التي تتطلب معرفة متخصصة وتفكير بشري إبداعي.
</p>

<p>
	ترجمة-وبتصرٌّف-للمقال <a href="https://www.toptal.com/openai/create-your-own-ai-assistant" rel="external nofollow">Using an LLM <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> As an Intelligent Virtual Assistant for Python Development</a>
</p>

<h2 id="-6">
	اقرأ أيضاً
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/apps/web/%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-chatgpt-api-%D9%84%D8%AA%D8%AD%D8%B3%D9%8A%D9%86-%D8%AE%D8%AF%D9%85%D8%A7%D8%AA%D9%83-%D8%B9%D8%A8%D8%B1-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA-r909/" rel="">دليل استخدام ChatGPT <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> لتحسين خدماتك عبر الإنترنت</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/artificial-intelligence/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%A7%D9%84%D9%85%D8%AD%D9%88%D9%91%D9%84%D8%A7%D8%AA-transformers-%D9%85%D9%86-%D9%85%D9%86%D8%B5%D8%A9-hugging-face-r2340/" rel="">تعرف على مكتبة المحوّلات Transformers من منصة Hugging Face</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/artificial-intelligence/%D8%AA%D9%84%D8%AE%D9%8A%D8%B5-%D8%A7%D9%84%D9%86%D8%B5%D9%88%D8%B5-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%B0%D9%83%D8%A7%D8%A1-%D8%A7%D9%84%D8%A7%D8%B5%D8%B7%D9%86%D8%A7%D8%B9%D9%8A-r2315/" rel="">تلخيص النصوص باستخدام الذكاء الاصطناعي</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/artificial-intelligence/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%86%D9%85%D8%A7%D8%B0%D8%AC-%D8%A7%D9%84%D8%AA%D9%88%D9%84%D9%8A%D8%AF%D9%8A%D9%91%D8%A9-generative-models-%D9%84%D8%AA%D9%88%D9%84%D9%8A%D8%AF-%D8%A7%D9%84%D8%B5%D9%88%D8%B1-r2240/" rel="">استخدام النماذج التوليديّة Generative models لتوليد الصور</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2467</guid><pubDate>Tue, 10 Dec 2024 15:00:00 +0000</pubDate></item><item><title>&#x625;&#x636;&#x627;&#x641;&#x629; &#x627;&#x644;&#x645;&#x624;&#x62B;&#x631;&#x627;&#x62A; &#x627;&#x644;&#x635;&#x648;&#x62A;&#x64A;&#x629; &#x644;&#x644;&#x639;&#x628;&#x629; &#x627;&#x644;&#x645;&#x637;&#x648;&#x631;&#x629; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646; &#x648;&#x645;&#x643;&#x62A;&#x628;&#x629; Pygame</title><link>https://academy.hsoub.com/programming/python/%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-%D8%A7%D9%84%D9%85%D8%A4%D8%AB%D8%B1%D8%A7%D8%AA-%D8%A7%D9%84%D8%B5%D9%88%D8%AA%D9%8A%D8%A9-%D9%84%D9%84%D8%B9%D8%A8%D8%A9-%D8%A7%D9%84%D9%85%D8%B7%D9%88%D8%B1%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D9%88%D9%85%D9%83%D8%AA%D8%A8%D8%A9-pygame-r2439/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_10/Pygame.png.ea85e75e8cc7e3b6390154cf9071baa9.png" /></p>
<p>
	تعلمنا في المقالات السابقة من سلسلة <a href="https://academy.hsoub.com/tags/pygame/" rel="">Pygame</a> التي تشرح طريقة بناء لعبة من الصفر بلغة بايثون3 ووحدة الألعاب Pygame، وكيف نضيف إليها الشخصيات سواء شخصيات الأبطال أو أعداء، ونحركهم بالقفز والركض ورمي المقذوفات مثل الكرات النارية وغيرها، وسنعرض في هذا المقال المتمم للسلسلة طريقة إضافة مؤثرات صوتية تناسب أحداث اللعبة تُشَغَّل في أثناء القتال أو القفز أو جمع الجوائز أو غير ذلك، لكن دعنا في البداية نذكرك بمقالات السلسلة بالترتيب:
</p>

<ol>
	<li>
		 <a href="https://academy.hsoub.com/programming/python/%D8%A8%D9%86%D8%A7%D8%A1-%D9%84%D8%B9%D8%A8%D8%A9-%D9%86%D8%B1%D8%AF-%D8%A8%D8%B3%D9%8A%D8%B7%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1703/" rel="">بناء لعبة نرد بسيطة بلغة بايثون</a>.
	</li>
	<li>
		 <a href="https://academy.hsoub.com/programming/python/%D8%A8%D9%86%D8%A7%D8%A1-%D9%84%D8%B9%D8%A8%D8%A9-%D8%B1%D8%B3%D9%88%D9%85%D9%8A%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D9%88%D9%88%D8%AD%D8%AF%D8%A9-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8-pygame-r1704/" rel="">بناء لعبة رسومية باستخدام بايثون ووحدة الألعاب PyGame</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-%D9%84%D8%A7%D8%B9%D8%A8-%D8%A5%D9%84%D9%89-%D9%84%D8%B9%D8%A8%D8%A9-%D9%85%D8%B7%D9%88%D8%B1%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D9%88%D9%85%D9%83%D8%AA%D8%A8%D8%A9-pygame-r1705/" rel="">إضافة لاعب إلى اللعبة المطورة باستخدام بايثون و Pygame</a>.
	</li>
	<li>
		 <a href="https://academy.hsoub.com/programming/python/%D8%AA%D8%AD%D8%B1%D9%8A%D9%83-%D8%B4%D8%AE%D8%B5%D9%8A%D8%A9-%D9%81%D9%8A-%D9%84%D8%B9%D8%A8%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-pygame-r1706/" rel="">تحريك شخصية اللعبة باستخدام PyGame</a>.
	</li>
	<li>
		 <a href="https://academy.hsoub.com/programming/python/%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-%D8%B4%D8%AE%D8%B5%D9%8A%D8%A9-%D8%A7%D9%84%D8%B9%D8%AF%D9%88-%D9%84%D9%84%D8%B9%D8%A8%D8%A9-%D8%B9%D8%A8%D8%B1-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-pygame-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1707/" rel="">إضافة شخصية العدو للعبة</a>.
	</li>
	<li>
		 <a href="https://academy.hsoub.com/programming/python/%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-%D8%A7%D9%84%D9%85%D9%86%D8%B5%D8%A7%D8%AA-%D8%A5%D9%84%D9%89-%D9%84%D8%B9%D8%A8%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%88%D8%AD%D8%AF%D8%A9-pygame-r1730/" rel="">إضافة المنصات إلى لعبة بايثون باستخدام الوحدة Pygame</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D9%85%D8%AD%D8%A7%D9%83%D8%A7%D8%A9-%D8%A3%D8%AB%D8%B1-%D8%A7%D9%84%D8%AC%D8%A7%D8%B0%D8%A8%D9%8A%D8%A9-%D9%81%D9%8A-%D9%84%D8%B9%D8%A8%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1731/" rel="">محاكاة أثر الجاذبية في لعبة بايثون</a>.
	</li>
	<li>
		 <a href="https://academy.hsoub.com/programming/python/%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-%D8%AE%D8%A7%D8%B5%D9%8A%D8%A9-%D8%A7%D9%84%D9%82%D9%81%D8%B2-%D9%88%D8%A7%D9%84%D8%B1%D9%83%D8%B6-%D8%A5%D9%84%D9%89-%D9%84%D8%B9%D8%A8%D8%A9-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1732/" rel="">إضافة خاصية القفز والركض إلى لعبة بايثون</a>.
	</li>
	<li>
		 <a href="https://academy.hsoub.com/programming/python/%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-%D8%A7%D9%84%D8%AC%D9%88%D8%A7%D8%A6%D8%B2-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%84%D8%B9%D8%A8%D8%A9-%D8%A7%D9%84%D9%85%D8%B7%D9%88%D8%B1%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1733/" rel="">إضافة الجوائز إلى اللعبة المطورة بلغة بايثون</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D8%AA%D8%B3%D8%AC%D9%8A%D9%84-%D9%86%D8%AA%D8%A7%D8%A6%D8%AC-%D8%A7%D9%84%D9%84%D8%B9%D8%A8%D8%A9-%D8%A7%D9%84%D9%85%D8%B7%D9%88%D8%B1%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D9%88%D8%B9%D8%B1%D8%B6%D9%87%D8%A7-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%B4%D8%A7%D8%B4%D8%A9-r1734/" rel="">تسجيل نتائج اللعبة المطورة بلغة بايثون وعرضها على الشاشة</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-%D8%A2%D9%84%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D9%82%D8%B0%D9%81-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%84%D8%B9%D8%A8%D8%A9-%D8%A7%D9%84%D9%85%D8%B7%D9%88%D8%B1%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1735/" rel="">إضافة آليات القذف إلى اللعبة المطورة بلغة بايثون</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-%D8%A2%D9%84%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D9%82%D8%B0%D9%81-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%84%D8%B9%D8%A8%D8%A9-%D8%A7%D9%84%D9%85%D8%B7%D9%88%D8%B1%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1735/" rel="">إضافة آليات القذف إلى اللعبة المطورة بلغة بايثون</a>.
	</li>
	<li>
		إضافة المؤثرات الصوتية إلى اللعبة المطورة بلغة بايثون ومكتبة Pygame.
	</li>
</ol>

<p>
	توفر المكتبة Pygame طريقةً سهلة لإضافة المؤثرات الصوتية إلى ألعاب الفيديو المطورة بلغة بايثون، وذلك اعتمادًا على وحدة خاصة تسمى <strong><a href="https://www.pygame.org/docs/ref/mixer.html" rel="external nofollow">mixer module</a></strong> تتيح لك تشغيل صوت واحد أو أكثر حسب طلبك، فيمكنك مثلًا تشغيل موسيقى خلفية background music بالتزامن مع صوت بطل اللعبة وهو يقاتل أو يقفز أو يجمع الجوائز.
</p>

<p>
	لن يتضمن هذا المقال تعديلات مباشرةً على الشيفرة البرمجية للعبة التي عملنا عليها خلال السلسلة (كما في فعلنا المقالات السابقة) لكننا سنقدم لك أمثلة للتعليمات المتعلقة بالوحدة mixer وسنشرح لك بالتفصيل كيفية الاستفادة منها ودمجها في لعبتك بخطوات متسلسلة.
</p>

<h2 id="mixer">
	تشغيل الوحدة mixer
</h2>

<p>
	اكتب في البداية التعليمة الخاصة بتشغيل الوحدة <code>mixer</code> في قسم الإعدادات <code>setup</code> ضمن شيفرة اللعبة، طبعًا يمكنك جمعها مع في كتلة واحدة مع التعليمات المشابهة لها مثل تعليمة تشغيل <code>pygame</code> وتشغيل <code>pygame.font</code> لتصبح كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8227_8" style=""><span class="pln">pygame</span><span class="pun">.</span><span class="pln">init</span><span class="pun">()</span><span class="pln">
pygame</span><span class="pun">.</span><span class="pln">font</span><span class="pun">.</span><span class="pln">init</span><span class="pun">()</span><span class="pln">
pygame</span><span class="pun">.</span><span class="pln">mixer</span><span class="pun">.</span><span class="pln">init</span><span class="pun">()</span><span class="pln"> </span><span class="com"># أضف هذا السطر</span></pre>

<p>
	<strong>ملاحظة</strong>: يمكنك الحصول على شيفرة اللعبة بشكلها النهائي من المقال الثاني عشر من السلسلة <a href="https://academy.hsoub.com/programming/python/%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-%D8%A2%D9%84%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D9%82%D8%B0%D9%81-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%84%D8%B9%D8%A8%D8%A9-%D8%A7%D9%84%D9%85%D8%B7%D9%88%D8%B1%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1735/" rel="">إضافة آليات القذف إلى اللعبة المطورة بلغة بايثون</a>.
</p>

<h2 id="">
	الحصول على الملفات الصوتية اللعبة
</h2>

<p>
	خطوتك التالية هي تحديد الأصوات التي تود استخدامها في اللعبة وتوفيرها محليًّا على حاسوبك، فاستخدام الأصوات في اللعبة المطورة <a href="https://wiki.hsoub.com/Python" rel="external">بلغة بايثون</a> يتطلب وجودها كملفات على الحاسوب المحلي تمامًا مثل الخطوط والرسوم.
</p>

<p>
	إذًا بعد تأمين ملفات الصوت عليك وضعها في حزمة واحدة مع ملفات اللعبة حتى يحصل عليها كل من يلعب بلعبتك.
</p>

<p>
	لنبدأ بإنشاء مجلد خاص لحفظ ملفات الصوت ضمن المجلد الرئيسي للعبة إلى جانب مجلدي الصور والخطوط، ولنسميه <code>sound</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8227_10" style=""><span class="pln">s </span><span class="pun">=</span><span class="pln"> </span><span class="str">'sound'</span></pre>

<p>
	يتوفر العديد من الملفات الصوتية على الإنترنت لكن قد لا يسمح لك باستخدامها جميعًا بسبب حقوق الملكية، لذا ابحث عن الملفات الصوتية <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%A7-%D8%A7%D9%84%D9%85%D9%82%D8%B5%D9%88%D8%AF-%D8%A8%D9%85%D8%B5%D8%B7%D9%84%D8%AD-%D9%85%D9%81%D8%AA%D9%88%D8%AD-%D8%A7%D9%84%D9%85%D8%B5%D8%AF%D8%B1-open-source%D8%9F-r885/" rel="">مفتوحة المصدر</a> أو المنشورة تحت رخصة المشاع الإبداعي Creative Commons واستخدمها في لعبتك، وهذه بعض المصادر التي تتيح لك تحميل ملفات الصوت مجانًا وبطريقة قانونية:
</p>

<ul>
	<li>
		يحتوي <a href="https://freesound.org/" rel="external nofollow">Freesound</a> على ملفات لمختلف أنواع المؤثرات الصوتية.
	</li>
	<li>
		يستضيف موقع <a href="https://incompetech.filmmusic.io/" rel="external nofollow">Incompetech</a> مجموعة واسعة من الموسيقى المناسبة لتكون موسيقى خلفية للألعاب.
	</li>
	<li>
		يوفر <a href="https://opengameart.org/" rel="external nofollow">Open Game Art</a> ملفاتٍ متنوعة من المؤثرات الصوتية والموسيقى.
	</li>
</ul>

<p>
	لكن احرص دائمًا على قراءة شروط الاستخدام قبل تحميل أي ملف صوتي مجاني واعتماده في لعبتك، إذ يشترط بعض المؤلفون الموسيقيون ومصممو الصوت أن تُنسب الملفات الصوتية إليهم ويذكر أنهم أصحاب الفضل في إنشائها عندما يستخدمها الآخرون مجانًا، وبكل الأحوال يُعدّ ذكر اسم صاحب الملف الصوتي تصرفًا جيدًا وأخلاقيًا لمطوري الألعاب، فهم في نهاية الأمر قد تعبوا على ملفاتهم الصوتية بالطريقة نفسها التي تعبت فيها لتطوير لعبة أو تطبيقك.
</p>

<p>
	فأين يُذكر اسم صاحب الملف الصوتي إذًا؟ يُنشأ عادةً ملفٌ نصيٌّ خاص في مجلد اللعبة الرئيسي يسمى <code>CREDIT</code> وتُكتب فيه الملفات الصوتية المستخدمة في اللعبة مع مصادرها.
</p>

<p>
	قد يرغب البعض بتأليف مؤثراتهم الصوتية الخاصة، فإذا كنت منهم يمكنك استخدام أدوات Linux Multimedia Studio، أو <a href="https://opensource.com/life/16/2/linux-multimedia-studio" rel="external nofollow">LMMS</a> ، فهو برنامج مجاني ومفتوح المصدر يساعد على لإنتاج وتحرير الموسيقى وتوليد التأثيرات الصوتية، كما أنه سهل الاستخدام ومتوافق مع معظم المنصات الأساسية، ويوفر لك العديد من الأصوات لتبدأ منها، فضلًا عن أنه يسمح لك بتصدير الملفات الصوتية بتنسيق Ogg Vorbis مفتوح المصدر الذي يسمى اختصارًا <a href="https://ar.wikipedia.org/wiki/%D8%A3%D9%88%D8%BA" rel="external nofollow">OGG</a>.
</p>

<p>
	يمكنك معرفة المزيد عن المشاريع والبرمجيات مفتوحة المصدر بمشاهدة هذا الفيديو:
</p>

<p>
	<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="" frameborder="0" height="480" id="ips_uid_5914_6" referrerpolicy="strict-origin-when-cross-origin" src="https://academy.hsoub.com/applications/core/interface/index.html" title="ما هي البرمجيات مفتوحة المصدر open source؟" width="930" data-embed-src="https://www.youtube.com/embed/LN1x4IcW7zU"></iframe>
</p>

<h2 id="pygame-1">
	إضافة الملفات الصوتية إلى Pygame
</h2>

<p>
	الآن بعد أن وجدت المؤثرات الصوتية المناسبة للعبتك ستُحَمِّلها غالبًا بصيغة ملفات مضغوطة <code>tar</code> أو <code>zip</code> لذا أول ما سنفعله هو فك ضغطها، ونقل الملفات الصوتية الناتجة إلى المجلد <code>sound</code> الموجود ضمن مجلد اللعبة الرئيسي.
</p>

<p>
	انظر بعدها إلى أسماء الملفات الصوتية فإذا وجدتها معقدة أو تتضمن العديد من المحارف الخاصة، أَعِدّْ تسميتها واختر لها أسماءً بسيطة يمكنك استخدامها بسهولة ضمن الشيفرة البرمجية.
</p>

<p>
	تعتمد معظم ألعاب الفيديو ملفاتٍ صوتية بصيغة <code>OGG</code> لأنها تجمع بين الجودة العالية وصِغَر حجم الملف، فإذا كانت الملفات الصوتية التي اخترتها للعبتك بصيغة <code>MP3</code> أو <code>WAVE</code> أو <code>FLAC</code> أو غيرها، احرص على تحويلها إلى صيغة <code>OGG</code> باستعمال أدوات مثل <a href="https://www.freac.org/index.php/en/downloads-mainmenu-330" rel="external nofollow">fre:ac</a> و <a href="http://getmiro.com/" rel="external nofollow">Miro</a> لتضمن توافقيةً أعلى وحجمًا أصغر عند تحميل اللعبة.
</p>

<p>
	لنفترض على سبيل المثال أن الملف الصوتي الذي حَمَّلته يدعى <code>ouch.ogg</code>.
</p>

<p>
	سننشئ متغيرًا خاصًا لتمثيله في قسم الإعدادات <code>setup</code> ضمن شيفرة اللعبة، ليكن مثلًا المتغير <code>ouch</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8227_12" style=""><span class="pln">ouch </span><span class="pun">=</span><span class="pln"> pygame</span><span class="pun">.</span><span class="pln">mixer</span><span class="pun">.</span><span class="typ">Sound</span><span class="pun">(</span><span class="pln">os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">s</span><span class="pun">,</span><span class="pln"> </span><span class="str">'ouch.ogg'</span><span class="pun">))</span></pre>

<h2 id="-1">
	تشغيل الأصوات ضمن اللعبة
</h2>

<p>
	الآن كل ما عليك فعله لتشغيل الصوت ضمن اللعبة هو استدعاء المتغير السابق عندما تحتاجه، فعلى سبيل المثال إذا رغبت بتشغيل الصوت <code>OUCH</code> عندما يصطدم بطلك بأحد الأعداء فستكتب الحلقة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8227_14" style=""><span class="kwd">for</span><span class="pln"> enemy </span><span class="kwd">in</span><span class="pln"> enemy_hit_list</span><span class="pun">:</span><span class="pln">
    pygame</span><span class="pun">.</span><span class="pln">mixer</span><span class="pun">.</span><span class="typ">Sound</span><span class="pun">.</span><span class="pln">play</span><span class="pun">(</span><span class="pln">ouch</span><span class="pun">)</span><span class="pln">
    score </span><span class="pun">-=</span><span class="pln"> </span><span class="lit">1</span></pre>

<p>
	وبالطريقة نفسها يمكنك إنشاء مؤثرات صوتية لمختلف أنواع الأحداث في اللعبة، مثل: القفز، وجمع الجوائز، ورمي المقذوفات، والاصطدام بالأشياء… إلخ.
</p>

<h2 id="-2">
	إضافة موسيقى خلفية للعبة
</h2>

<p>
	تساعدك الدالة <code>music</code> (إحدى دوال الوحدة <code>mixer</code> في <code>Pygame</code>) على تشغيل موسيقى أو مؤثرات جوية مثل صوت هواء أو غيره في خلفية background اللعبة، وذلك بخطوتين: أولًا تحميل الملف الصوتي بكتابة الأمر التالي في قسم الإعدادات <code>setup</code> من شيفرة اللعبة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8227_16" style=""><span class="pln">music </span><span class="pun">=</span><span class="pln"> pygame</span><span class="pun">.</span><span class="pln">mixer</span><span class="pun">.</span><span class="pln">music</span><span class="pun">.</span><span class="pln">load</span><span class="pun">(</span><span class="pln">os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">s</span><span class="pun">,</span><span class="pln"> </span><span class="str">'music.ogg'</span><span class="pun">))</span></pre>

<p>
	ثم تشغيل الدالة <code>music</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8227_18" style=""><span class="pln">pygame</span><span class="pun">.</span><span class="pln">mixer</span><span class="pun">.</span><span class="pln">music</span><span class="pun">.</span><span class="pln">play</span><span class="pun">(-</span><span class="lit">1</span><span class="pun">)</span></pre>

<p>
	تعني القيمة <code>1-</code> أن الدالة ستعمل إلى ما لا نهاية من دون توقف وهذه سمة الموسيقى الخلفية، لكن يمكنك استخدام أي عدد آخر بدءًا من <code>0</code> وما فوق لتحديد عدد المرات التي ستعمل فيها الدالة <code>music</code> قبل أن تتوقف.
</p>

<h2 id="-3">
	طوّر اللعبة ولا تتوقف هنا
</h2>

<p>
	لا تتوقف عند ما تعلمناه في هذه <a href="https://academy.hsoub.com/tags/pygame/" rel="">السلسلة عن Pygame</a> جرب إضافة المزيد من الأصوات والمراحل والمؤثرات الحركية إلى لعبتك، تعلَّم المزيد عنها فهي تضفي النكهة على لعبتك وتساهم في جعلها مفضلة لدى المستخدمين.
</p>

<p>
	ترجمة -وبتصرف- لمقال <a href="https://opensource.com/article/20/9/add-sound-python-game" rel="external nofollow">Add sound to your Python game</a> لصاحبه Seth Kenlon.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق:  <a href="https://academy.hsoub.com/programming/python/%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-%D8%A2%D9%84%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D9%82%D8%B0%D9%81-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%84%D8%B9%D8%A8%D8%A9-%D8%A7%D9%84%D9%85%D8%B7%D9%88%D8%B1%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1735/" rel="">إضافة آليات القذف إلى اللعبة المطورة بلغة بايثون</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D9%85%D8%B7%D9%88%D8%B1-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8/" rel="">مطور الألعاب: من هو وما هي مهامه</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D9%84%D8%BA%D8%A7%D8%AA-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8/" rel="">تعرف على أشهر لغات برمجة الألعاب</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D8%A3%D8%AF%D9%88%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D8%A9-%D9%81%D9%8A-%D8%A8%D9%86%D8%A7%D8%A1-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%B1%D8%B3%D9%88%D9%85%D9%8A%D8%A9-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1771/" rel="">الأدوات المستخدمة في بناء الواجهات الرسومية في بايثون</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/os-embedded-systems/%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%B1%D8%A7%D8%B3%D8%A8%D9%8A%D8%B1%D9%8A-%D8%A8%D8%A7%D9%8A-r1418/" rel="">البرمجة باستخدام لغة بايثون في تطبيقات راسبيري باي</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86/" rel="">تعرف على مجالات وتطبيقات لغة بايثون</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2439</guid><pubDate>Sun, 03 Nov 2024 15:01:07 +0000</pubDate></item><item><title>&#x627;&#x644;&#x623;&#x62E;&#x637;&#x627;&#x621; &#x627;&#x644;&#x639;&#x634;&#x631;&#x629; &#x627;&#x644;&#x623;&#x643;&#x62B;&#x631; &#x634;&#x64A;&#x648;&#x639;&#x64B;&#x627; &#x641;&#x64A; &#x634;&#x64A;&#x641;&#x631;&#x629; &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646; Python &#x627;&#x644;&#x628;&#x631;&#x645;&#x62C;&#x64A;&#x629;</title><link>https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D8%A3%D8%AE%D8%B7%D8%A7%D8%A1-%D8%A7%D9%84%D8%B9%D8%B4%D8%B1%D8%A9-%D8%A7%D9%84%D8%A3%D9%83%D8%AB%D8%B1-%D8%B4%D9%8A%D9%88%D8%B9%D9%8B%D8%A7-%D9%81%D9%8A-%D8%B4%D9%8A%D9%81%D8%B1%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-python-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r2429/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_10/959048728_.png.36c4b917ecd4dea237ba67606cffc8dd.png" /></p>
<p>
	يمكن أن تخدع صيغة بايثون البسيطة وسهلة التعلم مطوري لغة بايثون Python وخاصة الجدد منهم، مما يؤدي إلى تفويت بعض التفاصيل الدقيقة والتقليل من قوة اللغة، لذا سنقدّم في هذا المقال قائمة بأكثر 10 أخطاء شيوعًا، والتي تكون دقيقة ويصعب اكتشافها ويمكن أن تخدع حتى مطور بايثون الأكثر تقدمًا.
</p>

<h2 id="">
	مقدمة إلى بايثون
</h2>

<p>
	تُعَد <a href="https://academy.hsoub.com/python/" rel="">بايثون</a> لغة برمجة مُفسَّرة Interpreted وكائنية التوجّه Object-oriented و<a href="https://academy.hsoub.com/programming/general/%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D9%84%D8%BA%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9/" rel="">عالية المستوى</a> ولها دلالات Semantics ديناميكية، وتجعل هياكلُ البيانات المُضمَّنة عالية المستوى، والتحقق الديناميكي من الأنواع، والربط الديناميكي من لغة بايثون جذابة للغاية لتطوير التطبيقات بسرعة، بالإضافة إلى استخدامها بوصفها لغة برمجة لكتابة السكربتات أو لغة لاصقة Glue Language لوصل المكونات أو الخدمات الموجودة مسبقًا مع بعضها البعض. كما تدعم لغة بايثون <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%88%D8%AD%D8%AF%D8%A7%D8%AA-modules-%D9%88%D8%A7%D9%84%D8%AD%D8%B2%D9%85-packages-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r329/" rel="">الوحدات والحزم</a>، وبالتالي تشجع التقسيم إلى وحدات Modularity وإعادة استخدام الشيفرة البرمجية.
</p>

<p>
	<strong>ملاحظة</strong>: هذا المقال مخصَّص <a href="https://academy.hsoub.com/programming/general/%D8%A7%D9%84%D9%85%D8%A8%D8%B1%D9%85%D8%AC-%D8%A7%D9%84%D9%85%D8%AD%D8%AA%D8%B1%D9%81/" rel="">للمبرمجين المحترفين</a> في بايثون، وليس موجَّهًا للمطورين الجدد الذين قد يكونون أقل دراية بأخطاء بايثون الشائعة.
</p>

<h2 id="1">
	الخطأ 1: استخدام التعابير بوصفها قيمًا افتراضية لوسطاء الدوال بطريقة خاطئة
</h2>

<p>
	تسمح لغة بايثون بتحديد وسيط الدالة بأنه اختياري من خلال توفير قيمة افتراضية له، ولكن قد تؤدي هذه الميزة إلى بعض الارتباك عندما تكون القيمة الافتراضية متغيرة بالرغم من أن هذه ميزة رائعة لهذه اللغة. إليك <a href="https://academy.hsoub.com/programming/python/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%AF%D9%88%D8%A7%D9%84-functions-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r292/" rel="">تعريف دالة</a> بايثون التالي مثلًا:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3193_10" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">def</span><span class="pln"> foo</span><span class="pun">(</span><span class="pln">bar</span><span class="pun">=[]):</span><span class="pln">        </span><span class="com"># يُعد الوسيط‫ bar اختياريًا وقيمته الافتراضية هي [] عند عدم تحديدها</span><span class="pln">
</span><span class="pun">...</span><span class="pln">   bar</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="str">"baz"</span><span class="pun">)</span><span class="pln">    </span><span class="com"># ولكن يمكن أن يسبّب هذا السطر مشكلة كما سنرى لاحقًا‫...</span><span class="pln">
</span><span class="pun">...</span><span class="pln">   </span><span class="kwd">return</span><span class="pln"> bar</span></pre>

<p>
	من الأخطاء الشائعة أن نعتقد أن الوسيط الاختياري مضبوط على التعبير الافتراضي المحدَّد في كل مرة تُستدعَى فيها الدالة دون توفير قيمة لهذا الوسيط الاختياري، فمثلًا قد نتوقع في الشيفرة البرمجية السابقة أن استدعاء الدالة <code>foo()‎</code> بصورة متكررة (أي بدون تحديد الوسيط <code>bar</code>) سيؤدي دائمًا إلى إعادة القيمة <code>'baz'</code>، بما أننا اعتقدنا أن الوسيط <code>bar</code> مضبوط على القيمة <code>[]</code> (أي قائمة فارغة جديدة) في كل مرة نستدعي فيها الدالة <code>foo()‎</code> (بدون تحديد الوسيط <code>bar</code>)، ولكن لنلقِ نظرة على ما يحدث فعليًا:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3193_17" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> foo</span><span class="pun">()</span><span class="pln">
</span><span class="pun">[</span><span class="str">"baz"</span><span class="pun">]</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> foo</span><span class="pun">()</span><span class="pln">
</span><span class="pun">[</span><span class="str">"baz"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"baz"</span><span class="pun">]</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> foo</span><span class="pun">()</span><span class="pln">
</span><span class="pun">[</span><span class="str">"baz"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"baz"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"baz"</span><span class="pun">]</span></pre>

<p>
	لاحظ استمرار إلحاق القيمة الافتراضية <code>"baz"</code> إلى القائمة الموجودة مسبقًا في كل مرة نستدعي فيها الدالة <code>foo()‎</code> بدلًا من إنشاء قائمة جديدة في كل مرة، إذ تُقيَّم القيمة الافتراضية لوسيط الدالة مرة واحدة فقط في وقت تعريف الدالة، وبالتالي يُهيَّأ الوسيط <code>bar</code> على قيمته الافتراضية (أي قائمة فارغة) عند تعريف الدالة <code>foo()‎</code> لأول مرة فقط، ولكن ستستمر بعد ذلك استدعاءات الدالة <code>foo()‎</code> (بدون تحديد الوسيط <code>bar</code>) في استخدام القائمة نفسها التي هيّأنا بها الوسيط <code>bar</code> في الأصل.
</p>

<p>
	الحل الشائع لهذه المشكلة هو ما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3193_15" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">def</span><span class="pln"> foo</span><span class="pun">(</span><span class="pln">bar</span><span class="pun">=</span><span class="kwd">None</span><span class="pun">):</span><span class="pln">
</span><span class="pun">...</span><span class="pln">   </span><span class="kwd">if</span><span class="pln"> bar </span><span class="kwd">is</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">:</span><span class="pln">        </span><span class="com"># ‫أو if not bar:‎‫</span><span class="pln">
</span><span class="pun">...</span><span class="pln">       bar </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">
</span><span class="pun">...</span><span class="pln">   bar</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="str">"baz"</span><span class="pun">)</span><span class="pln">
</span><span class="pun">...</span><span class="pln">   </span><span class="kwd">return</span><span class="pln"> bar
</span><span class="pun">...</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> foo</span><span class="pun">()</span><span class="pln">
</span><span class="pun">[</span><span class="str">"baz"</span><span class="pun">]</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> foo</span><span class="pun">()</span><span class="pln">
</span><span class="pun">[</span><span class="str">"baz"</span><span class="pun">]</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> foo</span><span class="pun">()</span><span class="pln">
</span><span class="pun">[</span><span class="str">"baz"</span><span class="pun">]</span></pre>

<h2 id="2class">
	الخطأ 2: استخدام متغيرات الصنف Class استخدامًا خاطئًا
</h2>

<p>
	ليكن لدينا المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3193_19" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> A</span><span class="pun">(</span><span class="pln">object</span><span class="pun">):</span><span class="pln">
</span><span class="pun">...</span><span class="pln">    x </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
</span><span class="pun">...</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> B</span><span class="pun">(</span><span class="pln">A</span><span class="pun">):</span><span class="pln">
</span><span class="pun">...</span><span class="pln">    </span><span class="kwd">pass</span><span class="pln">
</span><span class="pun">...</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> C</span><span class="pun">(</span><span class="pln">A</span><span class="pun">):</span><span class="pln">
</span><span class="pun">...</span><span class="pln">    </span><span class="kwd">pass</span><span class="pln">
</span><span class="pun">...</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pln"> A</span><span class="pun">.</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> B</span><span class="pun">.</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> C</span><span class="pun">.</span><span class="pln">x
</span><span class="lit">1</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="lit">1</span></pre>

<p>
	وبالتالي سيكون لدينا أيضًا ما يلي كما هو متوقع:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3193_21" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> B</span><span class="pun">.</span><span class="pln">x </span><span class="pun">=</span><span class="pln"> </span><span class="lit">2</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pln"> A</span><span class="pun">.</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> B</span><span class="pun">.</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> C</span><span class="pun">.</span><span class="pln">x
</span><span class="lit">1</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> </span><span class="lit">1</span></pre>

<p>
	ولكن سيكون لدينا ما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3193_23" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> A</span><span class="pun">.</span><span class="pln">x </span><span class="pun">=</span><span class="pln"> </span><span class="lit">3</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pln"> A</span><span class="pun">.</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> B</span><span class="pun">.</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> C</span><span class="pun">.</span><span class="pln">x
</span><span class="lit">3</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> </span><span class="lit">3</span></pre>

<p>
	لاحظ تغيير قيمة <code>C.x</code> بالرغم من أننا غيرنا قيمة <code>A.x</code> فقط، حيث تُعامَل متغيرات <a href="https://academy.hsoub.com/programming/python/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%A7%D9%84%D8%A3%D8%B5%D9%86%D8%A7%D9%81-%D9%88%D8%AA%D8%B9%D8%B1%D9%8A%D9%81-%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-r754/" rel="">الصنف</a> داخليًا على أنها <a href="https://academy.hsoub.com/programming/python/%D9%81%D9%87%D9%85-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%85%D9%8A%D8%B3-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-r743/" rel="">قواميس</a> في لغة بايثون وتتبع ما يشار إليه غالبًا باسم ترتيب تحليل التوابع أو ترتيب استبيان التوابع Method Resolution Order -أو MRO اختصارًا وهو الآلية التي تستخدمها لغات البرمجة ومن ضمنها بايثون لتحديد ترتيب البحث عن التوابع في التسلسل الهرمي hierarchy الخاص بالكائنات في حالة استخدام <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A8%D8%B1%D8%A7%D9%85%D8%AC-%D9%83%D8%A7%D8%A6%D9%86%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D9%88%D8%AC%D9%87-%D9%88%D8%A7%D9%84%D9%88%D8%B1%D8%A7%D8%AB%D8%A9-%D8%A7%D9%84%D9%85%D8%AA%D8%B9%D8%AF%D8%AF%D8%A9-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2167/" rel="">الوراثة المتعددة</a>، أي أنه يحدد المسار الذي سيتبعه البرنامج عند محاولة استدعاء دالة معينة موجودة في أكثر من صنف أو الوراثة من عدة أصناف.
</p>

<p>
	لذلك سنبحث عن السمة Attribute التي هي <code>x</code> في أصنافها الأساسية (أي الصنف <code>A</code> فقط في المثال السابق بالرغم من أن لغة بايثون تدعم الوراثة المتعددة) بما أننا لم نعثر على هذه السمة في الصنف <code>C</code>. يمكن القول أيضًا أن الصنف <code>C</code> ليس لديه الخاصية <code>x</code> الخاصة به والمستقلة عن الصنف <code>A</code>، وبالتالي لا يُعَد المرجع إلى <code>C.x</code> هو المرجع نفسه إلى <code>A.x</code>، ويؤدي ذلك إلى حدوث مشكلة في بايثون إن لم نتعامل معها بطريقة صحيحة.
</p>

<h2 id="3exception">
	الخطأ 3: تحديد المعاملات لكتلة الاستثناء Exception بطريقة خاطئة
</h2>

<p>
	لنفترض أن لدينا الشيفرة البرمجية التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3193_27" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
</span><span class="pun">...</span><span class="pln">    l </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">"a"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"b"</span><span class="pun">]</span><span class="pln">
</span><span class="pun">...</span><span class="pln">    int</span><span class="pun">(</span><span class="pln">l</span><span class="pun">[</span><span class="lit">2</span><span class="pun">])</span><span class="pln">
</span><span class="pun">...</span><span class="pln"> </span><span class="kwd">except</span><span class="pln"> </span><span class="typ">ValueError</span><span class="pun">,</span><span class="pln"> </span><span class="typ">IndexError</span><span class="pun">:</span><span class="pln">  </span><span class="com"># لالتقاط الاستثناءَين</span><span class="pln">
</span><span class="pun">...</span><span class="pln">    </span><span class="kwd">pass</span><span class="pln">
</span><span class="pun">...</span><span class="pln">
</span><span class="typ">Traceback</span><span class="pln"> </span><span class="pun">(</span><span class="pln">most recent call last</span><span class="pun">):</span><span class="pln">
  </span><span class="typ">File</span><span class="pln"> </span><span class="str">"&lt;stdin&gt;"</span><span class="pun">,</span><span class="pln"> line </span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">module</span><span class="pun">&gt;</span><span class="pln">
</span><span class="typ">IndexError</span><span class="pun">:</span><span class="pln"> list index out of range</span></pre>

<p>
	المشكلة في المثال السابق هي أن تعليمة <code>except</code> لا تأخذ قائمة الاستثناءات المُحدَّدة بهذه الطريقة، حيث تستخدم بايثون الصيغة <code>except Exception, e</code> لربط الاستثناء بالمعامل الثاني الاختياري المُحدَّد (هو <code>e</code> في هذه الحالة)، وبالتالي يمكن إتاحته لمزيد من الفحص. لم تلتقط التعليمة <code>except</code> الاستثناء <code>IndexError</code>، بل يُربَط الاستثناء بمعاملٍ اسمه <code>IndexError</code>، وتُعَد مثل هذه الأخطاء شائعة في شيفرة بايثون البرمجية.
</p>

<p>
	الطريقة الصحيحة لالتقاط الاستثناءات المتعددة في التعليمة <code>except</code> هي تحديد المعامل الأول بوصفه مجموعة Tuple تحتوي على جميع الاستثناءات المُلتقَطة. يمكن تحقيق أقصى قدر من قابلية النقل من خلال استخدام الكلمة المفتاحية <code>as</code> لأن هذه الصيغة تدعمها Python 2 و Python 3:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3193_29" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
</span><span class="pun">...</span><span class="pln">    l </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">"a"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"b"</span><span class="pun">]</span><span class="pln">
</span><span class="pun">...</span><span class="pln">    int</span><span class="pun">(</span><span class="pln">l</span><span class="pun">[</span><span class="lit">2</span><span class="pun">])</span><span class="pln">
</span><span class="pun">...</span><span class="pln"> </span><span class="kwd">except</span><span class="pln"> </span><span class="pun">(</span><span class="typ">ValueError</span><span class="pun">,</span><span class="pln"> </span><span class="typ">IndexError</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> e</span><span class="pun">:</span><span class="pln">  
</span><span class="pun">...</span><span class="pln">    </span><span class="kwd">pass</span><span class="pln">
</span><span class="pun">...</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span></pre>

<h2 id="4scope">
	الخطأ 4: سوء فهم قواعد نطاق Scope بايثون
</h2>

<p>
	يعتمد تحليل Resolution نطاق بايثون على قاعدة LEGB، وهي اختصار للكلمات محلي ‎<strong>L</strong>ocal وشامل ‎<strong>E</strong>nclosing وعام ‎<strong>G</strong>lobal ومُضمَّن ‎<strong>B</strong>uilt-in. توجد بعض التفاصيل الدقيقة للطريقة التي تعمل بها هذه القاعدة في بايثون، مما يقودنا إلى مشكلة برمجة بايثون الشائعة الأكثر تقدمًا التالية، فليكن لدينا ما يلي مثلًا:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3193_31" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> x </span><span class="pun">=</span><span class="pln"> </span><span class="lit">10</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">def</span><span class="pln"> foo</span><span class="pun">():</span><span class="pln">
</span><span class="pun">...</span><span class="pln">    x </span><span class="pun">+=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
</span><span class="pun">...</span><span class="pln">    </span><span class="kwd">print</span><span class="pln"> x
</span><span class="pun">...</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> foo</span><span class="pun">()</span><span class="pln">
</span><span class="typ">Traceback</span><span class="pln"> </span><span class="pun">(</span><span class="pln">most recent call last</span><span class="pun">):</span><span class="pln">
  </span><span class="typ">File</span><span class="pln"> </span><span class="str">"&lt;stdin&gt;"</span><span class="pun">,</span><span class="pln"> line </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">module</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="typ">File</span><span class="pln"> </span><span class="str">"&lt;stdin&gt;"</span><span class="pun">,</span><span class="pln"> line </span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> foo
</span><span class="typ">UnboundLocalError</span><span class="pun">:</span><span class="pln"> local variable </span><span class="str">'x'</span><span class="pln"> referenced before assignment</span></pre>

<p>
	يحدث الخطأ السابق لأنه إذا أسندتَ قيمة إلى متغير في نطاقٍ ما، فستَعُد لغة بايثون هذا المتغير متغيرًا محليًا لذلك النطاق تلقائيًا وتظلّل أيّ متغير يحمل الاسم نفسه في أيّ نطاق خارجي.
</p>

<p>
	يتفاجأ الكثير من المبرمجين بالحصول على الخطأ <code>UnboundLocalError</code> في الشيفرة البرمجية التي عملت بنجاح سابقًا عند تعديلها من خلال إضافة تعليمة إسناد في مكانٍ ما من جسم الدالة، فمن الشائع أن يؤدي ذلك إلى أن يخطئ المطورون عند استخدام القوائم خاصةً كما في المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3193_33" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> lst </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">3</span><span class="pun">]</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">def</span><span class="pln"> foo1</span><span class="pun">():</span><span class="pln">
</span><span class="pun">...</span><span class="pln">    lst</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="lit">5</span><span class="pun">)</span><span class="pln">   </span><span class="com"># تعمل هذه التعليمة بنجاح</span><span class="pln">
</span><span class="pun">...</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> foo1</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> lst
</span><span class="pun">[</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">]</span><span class="pln">

</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> lst </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">3</span><span class="pun">]</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">def</span><span class="pln"> foo2</span><span class="pun">():</span><span class="pln">
</span><span class="pun">...</span><span class="pln">    lst </span><span class="pun">+=</span><span class="pln"> </span><span class="pun">[</span><span class="lit">5</span><span class="pun">]</span><span class="pln">      </span><span class="com"># ولكن تعطي هذه التعليمة خطأً</span><span class="pln">
</span><span class="pun">...</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> foo2</span><span class="pun">()</span><span class="pln">
</span><span class="typ">Traceback</span><span class="pln"> </span><span class="pun">(</span><span class="pln">most recent call last</span><span class="pun">):</span><span class="pln">
  </span><span class="typ">File</span><span class="pln"> </span><span class="str">"&lt;stdin&gt;"</span><span class="pun">,</span><span class="pln"> line </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">module</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="typ">File</span><span class="pln"> </span><span class="str">"&lt;stdin&gt;"</span><span class="pun">,</span><span class="pln"> line </span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> foo
</span><span class="typ">UnboundLocalError</span><span class="pun">:</span><span class="pln"> local variable </span><span class="str">'lst'</span><span class="pln"> referenced before assignment</span></pre>

<p>
	لاحظ أن الدالة <code>foo1</code> تعمل بنجاح بينما تعطي الدالة <code>foo2</code> خطأ، والسبب في ذلك هو مماثل لمشكلة المثال السابق ولكنه أكثر دقة، حيث لا تسند الدالة <code>foo1</code> قيمة إلى المتغير <code>lst</code> على عكس الدالة <code>foo2</code>، فالتعليمة <code>lst += [5]‎</code> هي مجرد اختصار للتعليمة<br>
	<code>lst = lst + [5]‎</code> التي تمثّل محاولة إسناد قيمة إلى المتغير <code>lst</code>، وبالتالي تفترض لغة بايثون أن هذا المتغير موجود في النطاق المحلي، ولكن تعتمد القيمة التي نريد إسنادها إلى المتغير <code>lst</code> على المتغير <code>lst</code> نفسه الذي يُفترَض وجوده في النطاق المحلي ولم نعرّفه بعد.
</p>

<h2 id="5">
	الخطأ 5: تعديل القائمة أثناء المرور عليها
</h2>

<p>
	يجب أن تكون مشكلة الشيفرة البرمجية التالية واضحة إلى حدٍ ما:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3193_35" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> odd </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">lambda</span><span class="pln"> x </span><span class="pun">:</span><span class="pln"> bool</span><span class="pun">(</span><span class="pln">x </span><span class="pun">%</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> numbers </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">n </span><span class="kwd">for</span><span class="pln"> n </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">10</span><span class="pun">)]</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> i </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="pln">len</span><span class="pun">(</span><span class="pln">numbers</span><span class="pun">)):</span><span class="pln">
</span><span class="pun">...</span><span class="pln">    </span><span class="kwd">if</span><span class="pln"> odd</span><span class="pun">(</span><span class="pln">numbers</span><span class="pun">[</span><span class="pln">i</span><span class="pun">]):</span><span class="pln">
</span><span class="pun">...</span><span class="pln">        </span><span class="kwd">del</span><span class="pln"> numbers</span><span class="pun">[</span><span class="pln">i</span><span class="pun">]</span><span class="pln">  </span><span class="com"># تصرف سيء: حذف عنصر من القائمة أثناء المرور عليها</span><span class="pln">
</span><span class="pun">...</span><span class="pln">
</span><span class="typ">Traceback</span><span class="pln"> </span><span class="pun">(</span><span class="pln">most recent call last</span><span class="pun">):</span><span class="pln">
        </span><span class="typ">File</span><span class="pln"> </span><span class="str">"&lt;stdin&gt;"</span><span class="pun">,</span><span class="pln"> line </span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">module</span><span class="pun">&gt;</span><span class="pln">
</span><span class="typ">IndexError</span><span class="pun">:</span><span class="pln"> list index out of range</span></pre>

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

<p>
	لحسن الحظ، تتضمن لغة بايثون عددًا من نماذج البرمجة الأنيقة التي يمكن أن تؤدي إلى شيفرة برمجية مبسطة ومنظَّمة بصورة كبيرة عند استخدامها استخدامًا صحيحًا، ممّا يقلل من احتمالية وجود خطأ الحذف غير المقصود لعنصر القائمة أثناء المرور عليها في هذه الشيفرة البرمجية الأبسط. أحد هذه النماذج هو نموذج استيعاب القوائم List Comprehensions الذي يُعَد مفيدًا خاصةً لتجنب هذه المشكلة كما هو موضّح في التطبيق البديل التالي للشيفرة البرمجية السابقة والذي يعمل بطريقة مثالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3193_37" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> odd </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">lambda</span><span class="pln"> x </span><span class="pun">:</span><span class="pln"> bool</span><span class="pun">(</span><span class="pln">x </span><span class="pun">%</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> numbers </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">n </span><span class="kwd">for</span><span class="pln"> n </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">10</span><span class="pun">)]</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> numbers</span><span class="pun">[:]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">n </span><span class="kwd">for</span><span class="pln"> n </span><span class="kwd">in</span><span class="pln"> numbers </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> odd</span><span class="pun">(</span><span class="pln">n</span><span class="pun">)]</span><span class="pln">  </span><span class="com"># الحل هنا</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> numbers
</span><span class="pun">[</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">4</span><span class="pun">,</span><span class="pln"> </span><span class="lit">6</span><span class="pun">,</span><span class="pln"> </span><span class="lit">8</span><span class="pun">]</span></pre>

<h2 id="6bindclosures">
	الخطأ 6: عدم وضوح كيفية ربط Bind بايثون للمتغيرات في المنغلقات Closures
</h2>

<p>
	ليكن لدينا المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3193_39" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">def</span><span class="pln"> create_multipliers</span><span class="pun">():</span><span class="pln">
</span><span class="pun">...</span><span class="pln">    </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">[</span><span class="kwd">lambda</span><span class="pln"> x </span><span class="pun">:</span><span class="pln"> i </span><span class="pun">*</span><span class="pln"> x </span><span class="kwd">for</span><span class="pln"> i </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">5</span><span class="pun">)]</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> multiplier </span><span class="kwd">in</span><span class="pln"> create_multipliers</span><span class="pun">():</span><span class="pln">
</span><span class="pun">...</span><span class="pln">    </span><span class="kwd">print</span><span class="pln"> multiplier</span><span class="pun">(</span><span class="lit">2</span><span class="pun">)</span><span class="pln">
</span><span class="pun">...</span></pre>

<p>
	قد نتوقع الخرج التالي للشيفرة البرمجية السابقة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3193_42" style=""><span class="lit">0</span><span class="pln">
</span><span class="lit">2</span><span class="pln">
</span><span class="lit">4</span><span class="pln">
</span><span class="lit">6</span><span class="pln">
</span><span class="lit">8</span></pre>

<p>
	لكننا سنحصل على ما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3193_44" style=""><span class="lit">8</span><span class="pln">
</span><span class="lit">8</span><span class="pln">
</span><span class="lit">8</span><span class="pln">
</span><span class="lit">8</span><span class="pln">
</span><span class="lit">8</span></pre>

<p>
	يحدث ذلك بسبب سلوك الربط المتأخر Late Binding لبايثون الذي يبحث عن قيم المتغيرات المُستخدَمة في المنغلقات Closures في وقت استدعاء الدالة الداخلية، لذلك إذا استدعينا أيًا من الدوال المُعادة في المثال السابق، فسيُجرَى البحث عن قيمة المتغير <code>i</code> في النطاق المحيط في وقت استدعائها، حيث ستكون الحلقة قد اكتملت عندها، لذلك أُسنِدت القيمة 4 إلى المتغير <code>i</code> فعليًا.
</p>

<p>
	ويكون حل هذه المشكلة الشائعة في بايثون كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3193_46" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">def</span><span class="pln"> create_multipliers</span><span class="pun">():</span><span class="pln">
</span><span class="pun">...</span><span class="pln">    </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">[</span><span class="kwd">lambda</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> i</span><span class="pun">=</span><span class="pln">i </span><span class="pun">:</span><span class="pln"> i </span><span class="pun">*</span><span class="pln"> x </span><span class="kwd">for</span><span class="pln"> i </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">5</span><span class="pun">)]</span><span class="pln">
</span><span class="pun">...</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> multiplier </span><span class="kwd">in</span><span class="pln"> create_multipliers</span><span class="pun">():</span><span class="pln">
</span><span class="pun">...</span><span class="pln">    </span><span class="kwd">print</span><span class="pln"> multiplier</span><span class="pun">(</span><span class="lit">2</span><span class="pun">)</span><span class="pln">
</span><span class="pun">...</span><span class="pln">
</span><span class="lit">0</span><span class="pln">
</span><span class="lit">2</span><span class="pln">
</span><span class="lit">4</span><span class="pln">
</span><span class="lit">6</span><span class="pln">
</span><span class="lit">8</span></pre>

<p>
	استفدنا من الوسطاء الافتراضية لإنشاء دوال مجهولة لتحقيق السلوك المطلوب. قد يَعُد البعض هذه الطريقة مناسبة، وقد يَعدها البعض رائعة، وقد يكرهها البعض الآخر، ولكن من المهم أن تفهمها إذا كنت مطور بايثون.
</p>

<h2 id="7dependencies">
	الخطأ 7: إنشاء اعتماديات Dependencies الوحدات الدائرية
</h2>

<p>
	لنفترض أن لدينا الملفان <code>a.py</code> و <code>b.py</code>، حيث يستورد كلّ منهما الآخر كما يلي:
</p>

<p>
	في الملف <code>a.py</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3193_48" style=""><span class="kwd">import</span><span class="pln"> b

</span><span class="kwd">def</span><span class="pln"> f</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> b</span><span class="pun">.</span><span class="pln">x

</span><span class="kwd">print</span><span class="pln"> f</span><span class="pun">()</span></pre>

<p>
	وفي الملف <code>b.py</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3193_50" style=""><span class="kwd">import</span><span class="pln"> a

x </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> g</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">print</span><span class="pln"> a</span><span class="pun">.</span><span class="pln">f</span><span class="pun">()</span></pre>

<p>
	أولًا، لنحاول استيراد الوحدة <code>a.py</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3193_52" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> a
</span><span class="lit">1</span></pre>

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

<p>
	لم يكن هناك مشكلة في استيراد الوحدة <code>b.py</code> لأنها لا تتطلب تعريف أيّ شيء من الوحدة <code>a.py</code> في وقت استيرادها عندما استوردنا الوحدة <code>a.py</code> في المثال السابق، فالإشارة الوحيدة إلى الوحدة <code>a</code> في الملف <code>b.py</code> هو استدعاء الدالة <code>a.f()‎</code>، ولكن هذا الاستدعاء موجود في الدالة <code>g()‎</code> ولا يوجد شيء في الملفين <code>a.py</code> أو <code>b.py</code> يستدعي الدالة <code>g()‎</code>، لذا لا يوجد شيء يدعو للقلق.
</p>

<p>
	ولكن إذا حاولنا استيراد الوحدة <code>b.py</code> دون استيراد الوحدة <code>a.py</code> مسبقًا كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3193_54" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> b
</span><span class="typ">Traceback</span><span class="pln"> </span><span class="pun">(</span><span class="pln">most recent call last</span><span class="pun">):</span><span class="pln">
        </span><span class="typ">File</span><span class="pln"> </span><span class="str">"&lt;stdin&gt;"</span><span class="pun">,</span><span class="pln"> line </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">module</span><span class="pun">&gt;</span><span class="pln">
        </span><span class="typ">File</span><span class="pln"> </span><span class="str">"b.py"</span><span class="pun">,</span><span class="pln"> line </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">module</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="kwd">import</span><span class="pln"> a
        </span><span class="typ">File</span><span class="pln"> </span><span class="str">"a.py"</span><span class="pun">,</span><span class="pln"> line </span><span class="lit">6</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">module</span><span class="pun">&gt;</span><span class="pln">
    </span><span class="kwd">print</span><span class="pln"> f</span><span class="pun">()</span><span class="pln">
        </span><span class="typ">File</span><span class="pln"> </span><span class="str">"a.py"</span><span class="pun">,</span><span class="pln"> line </span><span class="lit">4</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> f
    </span><span class="kwd">return</span><span class="pln"> b</span><span class="pun">.</span><span class="pln">x
</span><span class="typ">AttributeError</span><span class="pun">:</span><span class="pln"> </span><span class="str">'module'</span><span class="pln"> object has no attribute </span><span class="str">'x'</span></pre>

<p>
	فستظهر مشكلة تتمثّل في أن الوحدة <code>b.py</code> تحاول استيراد الوحدة <code>a.py</code> عند عملية استيراد الوحدة <code>b.py</code>، وتستدعي الوحدة <code>a.py</code> بدورها الدالة <code>f()‎</code> التي تحاول الوصول إلى المتغير <code>b.x</code> الذي لم نعرّفه بعد، وبالتالي سيظهر الاستثناء <code>AttributeError</code>.
</p>

<p>
	توجد حلول مختلفة لهذا الخطأ، وسيكون أحد هذه الحلول على الأقل بسيطًا، فمثلًا عدّل الوحدة <code>b.py</code> لتستورد الوحدة <code>a.py</code> ضمن الدالة <code>g()‎</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3193_56" style=""><span class="pln">x </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> g</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">import</span><span class="pln"> a    </span><span class="com"># ستُقيَّم هذه التعليمة عند استدعاء الدالة‫ g()‎ فقط</span><span class="pln">
    </span><span class="kwd">print</span><span class="pln"> a</span><span class="pun">.</span><span class="pln">f</span><span class="pun">()</span></pre>

<p>
	وإذا استوردناه هذه الوحدة، فسيكون كل شيء على ما يرام كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3193_58" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> b
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> b</span><span class="pun">.</span><span class="pln">g</span><span class="pun">()</span><span class="pln">
</span><span class="lit">1</span><span class="pln">    </span><span class="com"># يُطبَع لأول مرة بسبب استدعاء الوحدة‫ 'a' للتعليمة 'print f()‎' في النهاية</span><span class="pln">
</span><span class="lit">1</span><span class="pln">    </span><span class="com"># يُطبَع مرة ثانية، حيث يمثّل استدعاء الدالة‫ 'g'</span></pre>

<h2 id="8">
	الخطأ 8: تعارض الأسماء مع وحدات مكتبة بايثون المعيارية
</h2>

<p>
	تتميز لغة بايثون بوفرة وحدات المكتبات التي تأتي معها، ولكن قد يؤدي ذلك إلى الوقوع في تعارض في الأسماء بين اسم إحدى الوحدات الخاصة بك ووحدة أخرى تحمل الاسم نفسه في المكتبة المعيارية التي تأتي مع لغة بايثون إن لم تكن حذرًا، فمثلًا قد يكون لديك وحدة بالاسم <code>email.py</code> في شيفرتك البرمجية، والتي قد تتعارض مع وحدة المكتبة المعيارية التي تحمل الاسم نفسه.
</p>

<p>
	يمكن أن يؤدي ذلك إلى مشكلات خطيرة مثل استيراد مكتبة أخرى، والتي تحاول بدورها استيراد إصدارٍ من وحدة خاصة بمكتبة بايثون المعيارية، ولكن إذا كان لديك وحدة تحمل الاسم نفسه، فستستورد الحزمة الأخرى الإصدار الخاص بك عن طريق الخطأ بدلًا من الإصدار الموجود في مكتبة بايثون المعيارية، مما يؤدي إلى حدوث أخطاء، لذا يجب توخي الحذر لتجنب استخدام الأسماء نفسها الخاصة بوحدات مكتبة بايثون المعيارية. من الأسهل بالنسبة لك تغيير اسم الوحدة ضمن الحزمة الخاصة بك بدلًا من تقديم اقتراح تحسين بايثون <a href="https://legacy.python.org/dev/peps/" rel="external nofollow">Python Enhancement Proposal</a> -أو PEP اختصارًا- لطلب تغيير الاسم ومحاولة الحصول على الموافقة على ذلك.
</p>

<h2 id="9python2python3">
	الخطأ 9: الفشل في معالجة الاختلافات بين الإصدارين Python 2 و Python 3
</h2>

<p>
	ليكن لدينا الملف <code>foo.py</code> التالي مثلًا:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3193_60" style=""><span class="kwd">import</span><span class="pln"> sys

</span><span class="kwd">def</span><span class="pln"> bar</span><span class="pun">(</span><span class="pln">i</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> i </span><span class="pun">==</span><span class="pln"> </span><span class="lit">1</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">KeyError</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> i </span><span class="pun">==</span><span class="pln"> </span><span class="lit">2</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">ValueError</span><span class="pun">(</span><span class="lit">2</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> bad</span><span class="pun">():</span><span class="pln">
    e </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">
    </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
        bar</span><span class="pun">(</span><span class="pln">int</span><span class="pun">(</span><span class="pln">sys</span><span class="pun">.</span><span class="pln">argv</span><span class="pun">[</span><span class="lit">1</span><span class="pun">]))</span><span class="pln">
    </span><span class="kwd">except</span><span class="pln"> </span><span class="typ">KeyError</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> e</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'key error'</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">except</span><span class="pln"> </span><span class="typ">ValueError</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> e</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'value error'</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">e</span><span class="pun">)</span><span class="pln">

bad</span><span class="pun">()</span></pre>

<p>
	يعمل ما يلي بنجاح في Python 2:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3193_62" style=""><span class="pln">$ python foo</span><span class="pun">.</span><span class="pln">py </span><span class="lit">1</span><span class="pln">
key error
</span><span class="lit">1</span><span class="pln">
$ python foo</span><span class="pun">.</span><span class="pln">py </span><span class="lit">2</span><span class="pln">
value error
</span><span class="lit">2</span></pre>

<p>
	ولكنه يعطي خطأً في Python 3 كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3193_64" style=""><span class="pln">$ python3 foo</span><span class="pun">.</span><span class="pln">py </span><span class="lit">1</span><span class="pln">
key error
</span><span class="typ">Traceback</span><span class="pln"> </span><span class="pun">(</span><span class="pln">most recent call last</span><span class="pun">):</span><span class="pln">
  </span><span class="typ">File</span><span class="pln"> </span><span class="str">"foo.py"</span><span class="pun">,</span><span class="pln"> line </span><span class="lit">19</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">module</span><span class="pun">&gt;</span><span class="pln">
    bad</span><span class="pun">()</span><span class="pln">
  </span><span class="typ">File</span><span class="pln"> </span><span class="str">"foo.py"</span><span class="pun">,</span><span class="pln"> line </span><span class="lit">17</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> bad
    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">e</span><span class="pun">)</span><span class="pln">
</span><span class="typ">UnboundLocalError</span><span class="pun">:</span><span class="pln"> local variable </span><span class="str">'e'</span><span class="pln"> referenced before assignment</span></pre>

<p>
	المشكلة هي أنه لا يمكن الوصول إلى كائن الاستثناء خارج نطاق كتلة التعليمة <code>except</code> في Python 3، وإلّا فيجب الاحتفاظ بدورة مرجعية مع إطار المكدس في الذاكرة حتى تشغيل كانس المهملات Garbage Collector وإزالة المراجع من الذاكرة.
</p>

<p>
	إحدى الطرق لتجنب هذه المشكلة هي الاحتفاظ بمرجع إلى كائن الاستثناء خارج نطاق كتلة التعليمة <code>except</code> بحيث يبقى قابلًا للوصول. إليك فيما يلي نسخة من المثال السابق الذي يستخدم هذه التقنية، وبالتالي ستنتج شيفرة برمجية متوافقة مع Python 2 و Python 3:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3193_66" style=""><span class="kwd">import</span><span class="pln"> sys

</span><span class="kwd">def</span><span class="pln"> bar</span><span class="pun">(</span><span class="pln">i</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> i </span><span class="pun">==</span><span class="pln"> </span><span class="lit">1</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">KeyError</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> i </span><span class="pun">==</span><span class="pln"> </span><span class="lit">2</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">raise</span><span class="pln"> </span><span class="typ">ValueError</span><span class="pun">(</span><span class="lit">2</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">def</span><span class="pln"> good</span><span class="pun">():</span><span class="pln">
    exception </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">
    </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
        bar</span><span class="pun">(</span><span class="pln">int</span><span class="pun">(</span><span class="pln">sys</span><span class="pun">.</span><span class="pln">argv</span><span class="pun">[</span><span class="lit">1</span><span class="pun">]))</span><span class="pln">
    </span><span class="kwd">except</span><span class="pln"> </span><span class="typ">KeyError</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> e</span><span class="pun">:</span><span class="pln">
        exception </span><span class="pun">=</span><span class="pln"> e
        </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'key error'</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">except</span><span class="pln"> </span><span class="typ">ValueError</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> e</span><span class="pun">:</span><span class="pln">
        exception </span><span class="pun">=</span><span class="pln"> e
        </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'value error'</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">exception</span><span class="pun">)</span><span class="pln">

good</span><span class="pun">()</span></pre>

<p>
	لنشغّل هذه الشيفرة البرمجية على الإصدار Py3k:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3193_68" style=""><span class="pln">$ python3 foo</span><span class="pun">.</span><span class="pln">py </span><span class="lit">1</span><span class="pln">
key error
</span><span class="lit">1</span><span class="pln">
$ python3 foo</span><span class="pun">.</span><span class="pln">py </span><span class="lit">2</span><span class="pln">
value error
</span><span class="lit">2</span></pre>

<p>
	اطّلع على مقال <a href="https://academy.hsoub.com/programming/python/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%B1%D8%AD%D9%8A%D9%84-%D8%B4%D9%8A%D9%81%D8%B1%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-2-%D8%A5%D9%84%D9%89-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-r759/" rel="">كيفية ترحيل شيفرة بايثون 2 إلى بايثون 3</a> لمزيد من المعلومات.
</p>

<h2 id="10__del__">
	الخطأ 10: استخدام التابع del بطريقة خاطئة
</h2>

<p>
	لنفترض أن لدينا ما يلي في ملفٍ اسمه <code>mod.py</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3193_70" style=""><span class="kwd">import</span><span class="pln"> foo

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Bar</span><span class="pun">(</span><span class="pln">object</span><span class="pun">):</span><span class="pln">
           </span><span class="pun">...</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> __del__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        foo</span><span class="pun">.</span><span class="pln">cleanup</span><span class="pun">(</span><span class="pln">self</span><span class="pun">.</span><span class="pln">myhandle</span><span class="pun">)</span></pre>

<p>
	ثم حاولنا استيراده من الملف <code>another_mod.py</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3193_72" style=""><span class="kwd">import</span><span class="pln"> mod
mybar </span><span class="pun">=</span><span class="pln"> mod</span><span class="pun">.</span><span class="typ">Bar</span><span class="pun">()</span></pre>

<p>
	فسنحصل على الاستثناء <code>AttributeError</code>، والسبب هو ضبط جميع متغيرات الوحدة العامة على القيمة <code>None</code> عند إيقاف تشغيل المفسّر Interpreter، لذلك ضُبِط الاسم <code>foo</code> على القيمة <code>None</code> عند استدعاء التابع <code>__del__</code> في المثال السابق. الحل لهذه المشكلة هو استخدام الدالة <code>atexit.register()‎</code> بدلًا من ذلك، وبالتالي ستُشغَّل معالجاتك المسجَّلة قبل إيقاف تشغيل المفسِّر عندما ينتهي برنامجك من التنفيذ (أي عند الخروج منه بطريقة طبيعية).
</p>

<p>
	إذًا لنصلِح شيفرة <code>mod.py</code> البرمجية السابقة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3193_74" style=""><span class="kwd">import</span><span class="pln"> foo
</span><span class="kwd">import</span><span class="pln"> atexit

</span><span class="kwd">def</span><span class="pln"> cleanup</span><span class="pun">(</span><span class="pln">handle</span><span class="pun">):</span><span class="pln">
    foo</span><span class="pun">.</span><span class="pln">cleanup</span><span class="pun">(</span><span class="pln">handle</span><span class="pun">)</span><span class="pln">


</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Bar</span><span class="pun">(</span><span class="pln">object</span><span class="pun">):</span><span class="pln">
    </span><span class="kwd">def</span><span class="pln"> __init__</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        </span><span class="pun">...</span><span class="pln">
        atexit</span><span class="pun">.</span><span class="pln">register</span><span class="pun">(</span><span class="pln">cleanup</span><span class="pun">,</span><span class="pln"> self</span><span class="pun">.</span><span class="pln">myhandle</span><span class="pun">)</span></pre>

<p>
	يوفّر هذا المثال طريقة نظيفة وموثوقة لاستدعاء أيّ دالة تنظيف مطلوبة عند إنهاء البرنامج العادي، ومن الواضح أن الأمر متروك للدالة <code>foo.cleanup</code> لتحديد ما يجب فعله بالكائن المرتبط بالاسم <code>self.myhandle</code>.
</p>

<h2 id="-1">
	مخاطر بايثون يمكن تجنبها من خلال معرفة الفروق الأساسية
</h2>

<p>
	تُعَد بايثون لغة قوية ومرنة وتحتوي على العديد من الآليات والنماذج التي يمكن أن تحسّن الإنتاجية بصورة كبيرة، ولكن يمكن أن يكون الفهم أو التقدير المحدود لقدراتها في بعض الأحيان عائقًا أكثر من كونه فائدة كما هو الحال مع أيّ أداة أو لغة برمجية، حيث يعتقد الشخص في أن يعلم ما يكفي، ولكنه يشكّل خطرًا. سيساعد التعرف على الفروق الأساسية في لغة بايثون -مثل مشاكل البرمجة المتقدمة التي ذكرناها في هذا المقال- على تحسين استخدام اللغة مع تجنب بعض <a href="http://%20%D8%A8%D8%B9%D8%AF%20%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA%20%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86%20%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84%20%D9%85%D8%B9%20%D8%B1%D8%B3%D8%A7%D8%A6%D9%84%20%D8%A7%D9%84%D8%A3%D8%AE%D8%B7%D8%A7%D8%A1%20%D9%81%D9%8A%20%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86%20" rel="external nofollow">الأخطاء في بايثون</a>.
</p>

<p>
	ترجمة -وبتصرُّف- للمقال <a href="https://www.toptal.com/python/top-10-mistakes-that-python-programmers-make" rel="external nofollow">The 10 Most Common Python Code Mistakes</a> لصاحبه Martin Chikilian.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D9%85%D8%B5%D8%B7%D9%84%D8%AD%D8%A7%D8%AA-%D8%B4%D8%A7%D8%A6%D8%B9%D8%A9-%D9%85%D8%AB%D9%8A%D8%B1%D8%A9-%D9%84%D9%84%D8%A7%D9%84%D8%AA%D8%A8%D8%A7%D8%B3-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1985/" rel="">مصطلحات شائعة مثيرة للالتباس في بايثون</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D8%AA%D9%86%D9%82%D9%8A%D8%AD-%D8%A3%D8%AE%D8%B7%D8%A7%D8%A1-debugging-%D8%B4%D9%8A%D9%81%D8%B1%D8%AA%D9%83-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2359/" rel="">تنقيح أخطاء Debugging شيفرتك البرمجية باستخدام لغة بايثون</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D9%83%D9%8A%D9%81-%D8%AA%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D9%85%D9%86%D9%82%D8%AD-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r542/" rel="">كيف تستخدم منقح بايثون</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D9%81%D9%87%D9%85-%D8%A7%D9%84%D8%B9%D9%85%D9%84%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D9%86%D8%B7%D9%82%D9%8A%D8%A9-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-r732/" rel="">فهم العمليات المنطقية في بايثون 3</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D8%A3%D8%B4%D9%87%D8%B1-10-%D9%85%D8%B4%D9%83%D9%84%D8%A7%D8%AA-%D8%AA%D9%88%D8%A7%D8%AC%D9%87-%D9%85%D8%A8%D8%B1%D9%85%D8%AC%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-javascript-r2328/" rel="">أشهر 10 مشكلات تواجه مبرمجي لغة جافا سكريبت JavaScript</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2429</guid><pubDate>Sat, 12 Oct 2024 15:02:03 +0000</pubDate></item><item><title>&#x627;&#x644;&#x62A;&#x62D;&#x643;&#x645; &#x641;&#x64A; &#x644;&#x648;&#x62D;&#x629; &#x627;&#x644;&#x645;&#x641;&#x627;&#x62A;&#x64A;&#x62D; &#x648;&#x627;&#x644;&#x641;&#x623;&#x631;&#x629; &#x639;&#x628;&#x631; &#x623;&#x62A;&#x645;&#x62A;&#x629; &#x648;&#x627;&#x62C;&#x647;&#x629; &#x627;&#x644;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x627;&#x644;&#x631;&#x633;&#x648;&#x645;&#x64A;&#x629; GUI &#x641;&#x64A; &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646;</title><link>https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D8%AA%D8%AD%D9%83%D9%85-%D9%81%D9%8A-%D9%84%D9%88%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D9%81%D8%A7%D8%AA%D9%8A%D8%AD-%D9%88%D8%A7%D9%84%D9%81%D8%A3%D8%B1%D8%A9-%D8%B9%D8%A8%D8%B1-%D8%A3%D8%AA%D9%85%D8%AA%D8%A9-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%B1%D8%B3%D9%88%D9%85%D9%8A%D8%A9-gui-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2409/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_09/------------.png.cf3e787f659683276747650ee9153577.png" /></p>
<p>
	من المفيد معرفة وحدات بايثون Python المختلفة لتحرير جداول البيانات وتنزيل الملفات وتشغيل البرامج، ولكن لا توجد في بعض الأحيان أيّ وحداتٍ للتطبيقات التي تحتاج إلى العمل معها، فالأدوات الأساسية لأتمتة المهام على حاسوبك هي البرامج التي تكتبها وتتحكم في لوحة المفاتيح والفأرة مباشرةً، حيث يمكن لهذه البرامج التحكم في التطبيقات الأخرى من خلال إرسال ضغطات مفاتيح افتراضية ونقرات افتراضية بالفأرة إليها كما لو أنك جالس أمام حاسوبك وتتفاعل مع التطبيقات بنفسك.
</p>

<p>
	تُعرَف هذه التقنية باسم أتمتة واجهة المستخدم الرسومية Graphical User Interface Automation أو GUI automation اختصارًا، حيث يمكن لبرامجك باستخدام هذه التقنية فعل أيّ شيء يمكن أن يفعله المستخدم الجالس أمام الحاسوب باستثناء سكب القهوة على لوحة المفاتيح طبعًا. يمكن عَدّ أتمتة واجهة المستخدم الرسومية كبرمجة ذراع آلية، حيث يمكنك برمجة الذراع الآلية للكتابة باستخدام لوحة المفاتيح وتحريك الفأرة نيابةً عنك، وتُعَد هذه التقنية مفيدة خاصةً للمهام التي تتضمن الكثير من النقر أو ملء الاستمارات.
</p>

<p>
	تبيع بعض الشركات حلولَ الأتمتة المبتكرة والمكلفة، والتي تُسوَّق عادةً بأنها أتمتة العمليات الآلية Robotic Process Automation أو RPA اختصارًا، حيث لا تختلف هذه المنتجات فعليًا عن سكربتات بايثون التي يمكنك إنشاؤها بنفسك باستخدام الوحدة <code>pyautogui</code> التي تحتوي على دوال لمحاكاة حركات الفأرة ونقرات الأزرار وتمرير عجلة الفأرة. سنوضّح في هذا المقال مجموعة فرعية فقط من ميزات الوحدة PyAutoGUI، حيث يمكنك العثور على التوثيق الكامل على <a href="https://pyautogui.readthedocs.io/en/latest/" rel="external nofollow">موقعها الرسمي</a>.
</p>

<h2 id="pyautogui">
	تثبيت الوحدة pyautogui
</h2>

<p>
	يمكن لوحدة <code>pyautogui</code> إرسال ضغطات المفاتيح ونقرات الفأرة الافتراضية إلى أنظمة تشغيل ويندوز Windows وماك macOS <a href="https://academy.hsoub.com/devops/linux/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D9%84%D9%8A%D9%86%D9%83%D8%B3-r799/" rel="">ولينكس Linux</a>، حيث يمكن لمستخدمي ويندوز وماك macOS ببساطة استخدام أداة pip لتثبيت الوحدة PyAutoGUI، ولكن يجب على مستخدمي نظام لينكس أولًا تثبيت بعض البرامج التي تعتمد عليها وحدة PyAutoGUI، لذا افتح نافذة طرفية Terminal وأدخِل الأوامر التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_6" style=""><span class="pln">sudo apt install scrot python3</span><span class="pun">-</span><span class="pln">tk python3</span><span class="pun">-</span><span class="pln">dev</span></pre>

<p>
	يمكنك تثبيت الوحدة PyAutoGUI من خلال تشغيل الأمر <code>pip install --user pyautogui</code>، ولكن لا تستخدم الأمر <code>sudo</code> مع الأداة <code>pip</code>، إذ يمكن أن تثبِّتَ وحداتٍ مع تثبيت بايثون الذي يستخدمه <a href="https://academy.hsoub.com/apps/operating-systems/%D9%86%D8%B8%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84/" rel="">نظام التشغيل</a>، مما يتسبب في حدوث تعارضات مع أيّ سكربتات تعتمد على ضبطها الأصلي، ولكن يجب استخدام الأمر <code>sudo</code> عند تثبيت التطبيقات باستخدام <code>apt</code>.
</p>

<p>
	يمكنك اختبار صحة تثبيت الوحدة PyAutoGUI من خلال تشغيل الأمر <code>import pyautogui</code> في الصدفة التفاعلية Interactive Shell والتحقق من وجود رسائل خطأ.
</p>

<p>
	<strong>ملاحظة</strong>: لا تحفظ برنامجك بالاسم pyautogui.py، إذ ستستورد <a href="https://wiki.hsoub.com/Python" rel="external">لغة بايثون</a> برنامجك بدلًا من الوحدة PyAutoGUI وستتلقّى رسائل خطأ مثل الرسالة <code>AttributeError: module 'pyautogui' has no attribute 'click'‎</code> عند تشغيل الأمر <code>import pyautogui</code>.
</p>

<h2 id="accessibilitymacos">
	إعداد تطبيقات إمكانية الوصول Accessibility على نظام ماك macOS
</h2>

<p>
	لا يسمح نظام ماك للبرامج بالتحكم في الفأرة أو لوحة المفاتيح كإجراءٍ أمني، لذا يجب ضبط البرنامج الذي يشغّل سكربت بايثون ليكون تطبيقًا لإمكانية الوصول لكي تعمل وحدة PyAutoGUI على نظام تشغيل ماك، إذ لن يكون لاستدعاءات دوال PyAutoGUI أيّ تأثير بدون إجراء هذه الخطوة.
</p>

<p>
	اجعل تطبيقك مفتوحًا سواء شغّلته من محرّر Mu أو بيئة IDLE أو الطرفية Terminal، ثم افتح "تفضيلات النظام System Preferences" وانتقل إلى التبويب "إمكانية الوصول Accessibility". ستظهر التطبيقات المفتوحة حاليًا تحت العنوان "السماح للتطبيقات التالية بالتحكم في حاسوبك Allow the apps below to control your computer". تحقّق من تطبيق Mu أو IDLE أو الطرفية Terminal أو أيّ تطبيق تستخدمه لتشغيل سكربتات بايثون الخاصة بك، وسيُطلَب منك إدخال كلمة مرورك لتأكيد هذه التغييرات.
</p>

<h2 id="">
	البقاء على المسار الصحيح
</h2>

<p>
	يجب أن تعرف كيفية التهرب من المشكلات التي قد تواجهك قبل الانتقال إلى أتمتة واجهة المستخدم الرسومية، فمثلًا يمكن لسكربت بايثون تحريك الفأرة والكتابة من خلال ضغطات المفاتيح بسرعة مذهلة، وقد يكون الأمر سريعًا جدًا بحيث لا تتمكّن البرامج الأخرى من مجاراة هذه السرعة، وإذا حدث خطأٌ ما مع استمرار برنامجك في تحريك الفأرة، فسيكون من الصعب معرفة ما يفعله البرنامج بالضبط أو كيفية حل هذه المشكلة. كما يمكن أن يخرج برنامجك عن السيطرة بالرغم من أنه يتبع تعليماتك بطريقة مثالية مثل المكانس المسحورة من فيلم The Sorcerer’s Apprentice من إنتاج شركة ديزني، والتي ظلت تملأ حوض ميكي بالماء ثم تملأه أكثر من اللازم، وقد يكون إيقاف البرنامج أمرًا صعبًا إذا كانت الفأرة تتحرك من تلقاء نفسها، مما يمنعك من النقر على نافذة محرّر Mu لإغلاقه. توجد لحسن الحظ عدة طرق لمنع مشاكل أتمتة واجهة المستخدم الرسومية أو حلها، والتي سنوضّحها فيما يلي.
</p>

<h3 id="-1">
	التوقف المؤقت والفشل الآمن
</h3>

<p>
	إذا ظهر خطأ في برنامجك ولم تتمكّن من استخدام لوحة المفاتيح والفأرة لإغلاقه، فيمكنك استخدام ميزة الفشل الآمن في وحدة PyAutoGUI. حرّك الفأرة بسرعة إلى إحدى زوايا الشاشة الأربعة مثلًا، حيث يكون لكل استدعاء للدالة الخاصة بوحدة PyAutoGUI تأخير قدره 10 جزء من الثانية بعد تنفيذ الإجراء الخاص بها ليمنحك وقتًا كافيًا لتحريك الفأرة إلى الزاوية. إذا وجدَت وحدة PyAutoGUI بعد ذلك أن مؤشر الفأرة في الزاوية، فستطلق الاستثناء <code>pyautogui.FailSafeException</code>. لن يكون للتعليمات التي ليست تابعة لوحدة PyAutoGUI هذا التأخير الذي مقداره 10 جزء من الثانية.
</p>

<p>
	إذا وجدت نفسك في موقف تحتاج فيه إلى إيقاف برنامج PyAutoGUI، فما عليك سوى تحريك الفأرة بسرعة باتجاه الزاوية لإيقافه.
</p>

<h3 id="-2">
	إغلاق كل شيء من خلال تسجيل الخروج
</h3>

<p>
	قد تكون أبسط طريقة لإيقاف برنامج أتمتة واجهة المستخدم الرسومية الخارج عن السيطرة هي تسجيل الخروج، مما يؤدي إلى إيقاف تشغيل جميع البرامج التي تكون قيد التشغيل. مفتاح اختصار تسجيل الخروج هو CTRL-ALT-DEL في نظامي ويندوز ولينكس، وهو ‎<img alt="01_000064.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="157699" data-ratio="100.00" data-unique="xz6911cdv" width="15" src="https://academy.hsoub.com/uploads/monthly_2024_09/01_000064.jpg.a5cf06c5fc868ce86d099e5e65a17d4c.jpg">‎-SHIFT-OPTION-Q على نظام ماك. ستفقد أيّ عمل غير محفوظ عند تسجيل الخروج، ولكنك لن تضطر إلى الانتظار حتى تنتهي عملية إعادة التشغيل الكاملة للحاسوب.
</p>

<h2 id="-3">
	التحكم في حركة الفأرة
</h2>

<p>
	ستتعلّم في هذا القسم كيفية تحريك الفأرة وتعقّب موضعها على الشاشة باستخدام الوحدة PyAutoGUI، ولكن يجب أولًا أن تفهم كيفية عمل هذه الوحدة مع الإحداثيات.
</p>

<p>
	تستخدم دوال الفأرة الخاصة بوحدة PyAutoGUI إحداثيات x و y، حيث يبين الشكل التالي نظام إحداثيات شاشة الحاسوب، وهو مشابه لنظام الإحداثيات المستخدَم مع الصور الذي ناقشناه في المقال السابق، إذ توجد نقطة الأصل Origin حيث تكون قيمة x و y صفر في الزاوية العلوية اليسرى من الشاشة، وتزداد إحداثيات x باتجاه اليمين، وتزداد إحداثيات y باتجاه الأسفل. تكون جميع الإحداثيات أعدادًا صحيحة موجبة، إذ لا توجد إحداثيات سالبة.
</p>

<p style="text-align: center;">
	<img alt="02_000125-معرب.png" class="ipsImage ipsImage_thumbnailed" data-fileid="157701" data-ratio="100.51" data-unique="8utrlg0ja" width="394" src="https://academy.hsoub.com/uploads/monthly_2024_09/02_000125-.png.30fb5f333ddef0e1099cd0c8130e36c7.png">
</p>

<p style="text-align: center;">
	إحداثيات شاشة الحاسوب بدقة مقدارها 1920‎×1080
</p>

<p>
	تمثّل الدقة Resolution عدد البكسلات لعرض وطول الشاشة، حيث إذا كانت دقة شاشتك مضبوطة على القيمة ‎1920×1080، فستكون إحداثيات الزاوية العلوية اليسرى هو ‎(0, 0)‎، وستكون إحداثيات الزاوية السفلية اليمنى هو ‎(1919, 1079)‎.
</p>

<p>
	تعيد الدالة <code>pyautogui.size()‎</code> مجموعةً Tuple مكوّنة من عددين صحيحين لعرض الشاشة وارتفاعها بالبكسل. لندخِل مثلًا ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_8" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> pyautogui
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> wh </span><span class="pun">=</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">size</span><span class="pun">()</span><span class="pln"> </span><span class="com"># الحصول على دقة الشاشة</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> wh
</span><span class="typ">Size</span><span class="pun">(</span><span class="pln">width</span><span class="pun">=</span><span class="lit">1920</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">=</span><span class="lit">1080</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> wh</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln">
</span><span class="lit">1920</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> wh</span><span class="pun">.</span><span class="pln">width
</span><span class="lit">1920</span></pre>

<p>
	تعيد الدالة <code>pyautogui.size()‎</code> المجموعة ‎<code>(1920, 1080)‎</code>‎ على حاسوب دقته 1920‎×1080، إذ قد تختلف القيمة المُعادة اعتمادًا على دقة شاشتك. يُعَد الكائن <code>Size</code> الذي تعيده الدالة <code>size()‎</code> مجموعة مُسمَّاة Named Tuples، حيث يكون للمجموعات المُسماة فهارس رقمية مثل المجموعات العادية وأسماء سمات Attribute مثل الكائنات، إذ يُقيَّم كل من <code>wh[0]‎</code> و <code>wh.width</code> بأنه عرض الشاشة. لن نشرح المجموعات المُسمَّاة في هذا المقال، ولكن تذكّر فقط أنه يمكنك استخدامها باستخدام الطريقة نفسها التي تستخدم بها المجموعات العادية.
</p>

<h3 id="-4">
	تحريك الفأرة
</h3>

<p>
	تعرّفنا على مفهوم إحداثيات الشاشة، ويمكننا الآن تحريك الفأرة، حيث تحرّك الدالة <code>pyautogui.moveTo()‎</code> مؤشر الفأرة مباشرةً إلى موضعٍ محدّد على الشاشة. تشكّل القيم الصحيحة لإحداثيات x و y الوسيطين الأول والثاني لهذه الدالة على التوالي، ويحدّد وسيط الكلمات المفتاحية Keyword Argument الاختياري <code>duration</code> -الذي هو عدد صحيح أو عشري- عدد الثواني التي يجب أن يستغرقها تحريك الفأرة للوصول إلى وِجهتها، وإذا تركتَ هذا الوسيط دون تحديد، فإن القيمة الافتراضية هي 0 للحركة الفورية، وتكون جميع وسطاء الكلمات المفتاحية <code>duration</code> في دوال PyAutoGUI اختيارية. أدخِل مثلًا ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_10" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> pyautogui
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> i </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">10</span><span class="pun">):</span><span class="pln"> </span><span class="com"># تحريك الفأرة في مربع</span><span class="pln">
</span><span class="pun">...</span><span class="pln">       pyautogui</span><span class="pun">.</span><span class="pln">moveTo</span><span class="pun">(</span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">,</span><span class="pln"> duration</span><span class="pun">=</span><span class="lit">0.25</span><span class="pun">)</span><span class="pln">
</span><span class="pun">...</span><span class="pln">       pyautogui</span><span class="pun">.</span><span class="pln">moveTo</span><span class="pun">(</span><span class="lit">200</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">,</span><span class="pln"> duration</span><span class="pun">=</span><span class="lit">0.25</span><span class="pun">)</span><span class="pln">
</span><span class="pun">...</span><span class="pln">       pyautogui</span><span class="pun">.</span><span class="pln">moveTo</span><span class="pun">(</span><span class="lit">200</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">,</span><span class="pln"> duration</span><span class="pun">=</span><span class="lit">0.25</span><span class="pun">)</span><span class="pln">
</span><span class="pun">...</span><span class="pln">       pyautogui</span><span class="pun">.</span><span class="pln">moveTo</span><span class="pun">(</span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">,</span><span class="pln"> duration</span><span class="pun">=</span><span class="lit">0.25</span><span class="pun">)</span></pre>

<p>
	يحرّك المثال السابق مؤشر الفأرة باتجاه عقارب الساعة وفق نمطٍ مربع بين الإحداثيات الأربعة المُعطات 10 مرات، حيث تستغرق كل حركة ربع ثانية كما يحدّده وسيط الكلمات المفتاحية <code>duration=0.25</code>، وإن لم تمرّر الوسيط الثالث إلى أيٍّ من استدعاءات الدالة <code>pyautogui.moveTo()‎</code>، فسينتقل مؤشر الفأرة من نقطة إلى أخرى مباشرةً.
</p>

<p>
	تحرّك الدالة <code>pyautogui.move()‎</code> مؤشر الفأرة نسبةً إلى موضعه الحالي، حيث يحرّك المثال التالي الفأرة وفق نمط المربع نفسه، ولكنه يبدأ المربع من أيّ مكان توجد فيه الفأرة على الشاشة عند بدء تشغيل الشيفرة البرمجية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_12" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> pyautogui
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> i </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">10</span><span class="pun">):</span><span class="pln">
</span><span class="pun">...</span><span class="pln">       pyautogui</span><span class="pun">.</span><span class="pln">move</span><span class="pun">(</span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> duration</span><span class="pun">=</span><span class="lit">0.25</span><span class="pun">)</span><span class="pln">   </span><span class="com"># إلى اليمين</span><span class="pln">
</span><span class="pun">...</span><span class="pln">       pyautogui</span><span class="pun">.</span><span class="pln">move</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">,</span><span class="pln"> duration</span><span class="pun">=</span><span class="lit">0.25</span><span class="pun">)</span><span class="pln">   </span><span class="com"># للأسفل</span><span class="pln">
</span><span class="pun">...</span><span class="pln">       pyautogui</span><span class="pun">.</span><span class="pln">move</span><span class="pun">(-</span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> duration</span><span class="pun">=</span><span class="lit">0.25</span><span class="pun">)</span><span class="pln">  </span><span class="com"># إلى اليسار</span><span class="pln">
</span><span class="pun">...</span><span class="pln">       pyautogui</span><span class="pun">.</span><span class="pln">move</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">100</span><span class="pun">,</span><span class="pln"> duration</span><span class="pun">=</span><span class="lit">0.25</span><span class="pun">)</span><span class="pln">  </span><span class="com"># للأعلى</span></pre>

<p>
	تأخذ الدالة <code>pyautogui.move()‎</code> أيضًا ثلاثة وسطاء هي: عدد البكسلات التي يجب تحريكها أفقيًا إلى اليمين، وعدد البكسلات التي يجب تحريكها عموديًا للأسفل، والمدة التي يجب أن يستغرقها إكمال الحركة (اختياريًا). سيؤدي استخدام العدد الصحيح السالب مع الوسيط الأول أو الثاني إلى تحريك الفأرة إلى اليسار أو للأعلى على التوالي.
</p>

<h3 id="-5">
	الحصول على موضع الفأرة
</h3>

<p>
	يمكنك تحديد موضع الفأرة الحالي من خلال استدعاء الدالة <code>pyautogui.position()‎</code> التي ستعيد المجموعة المُسمَّاة <code>Point</code> لموضعي x و y الخاصين بمؤشر الفأرة عند استدعاء الدالة. أدخِل مثلًا ما يلي في الصدفة التفاعلية مع تحريك الفأرة بعد كل استدعاء:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_14" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">position</span><span class="pun">()</span><span class="pln"> </span><span class="com"># الحصول على موضع الفأرة الحالي</span><span class="pln">
</span><span class="typ">Point</span><span class="pun">(</span><span class="pln">x</span><span class="pun">=</span><span class="lit">311</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">=</span><span class="lit">622</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">position</span><span class="pun">()</span><span class="pln"> </span><span class="com"># الحصول على موضع الفأرة الحالي مرة أخرى</span><span class="pln">
</span><span class="typ">Point</span><span class="pun">(</span><span class="pln">x</span><span class="pun">=</span><span class="lit">377</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">=</span><span class="lit">481</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> p </span><span class="pun">=</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">position</span><span class="pun">()</span><span class="pln"> </span><span class="com"># الحصول على موضع الفأرة الحالي مرة أخرى</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> p
</span><span class="typ">Point</span><span class="pun">(</span><span class="pln">x</span><span class="pun">=</span><span class="lit">1536</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">=</span><span class="lit">637</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> p</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="com"># ‫يقع الإحداثي x عند الفهرس 0</span><span class="pln">
</span><span class="lit">1536</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> p</span><span class="pun">.</span><span class="pln">x </span><span class="com"># يوجد الإحداثي‫ x أيضًا في السمة x</span><span class="pln">
</span><span class="lit">1536</span></pre>

<p>
	ستختلف قيمك المُعادة اعتمادًا على مكان مؤشر الفأرة.
</p>

<h2 id="-6">
	التحكم في تفاعل الفأرة
</h2>

<p>
	تعرّفتَ كيفية تحريك الفأرة ومعرفة مكانها على الشاشة، وأصبحتَ الآن جاهزًا لبدء النقر والسحب والتمرير.
</p>

<h3 id="-7">
	النقر باستخدام الفأرة
</h3>

<p>
	يمكنك إرسال نقرة افتراضية باستخدام الفأرة إلى حاسوبك من خلال استدعاء التابع <code>pyautogui.click()‎</code>، حيث تستخدم هذه النقرة زر الفأرة الأيسر افتراضيًا وتُطبَّق في أيّ مكان يوجد فيه مؤشر الفأرة حاليًا. يمكنك تمرير إحداثيات x و y لهذه النقرة كوسيط أول وثانٍ اختياريين إلى التابع إذا أدرتَ أن تُطبَّق في مكانٍ آخر غير موضع الفأرة الحالي.
</p>

<p>
	إذا أدرتَ تحديد زر الفأرة الذي يجب استخدامه، فضمّن وسيط الكلمات المفتاحية <code>button</code> مع قيم <code>'left'</code> أو <code>'middle'</code> أو <code>'right'</code>، فمثلًا سيؤدي الاستدعاء <code>pyautogui.click(100, 150, button='left')‎</code> إلى النقر على زر الفأرة الأيسر عند الإحداثيات ‎(100, 150)‎، بينما سيؤدي الاستدعاء <code>pyautogui.click(200, 250, button='right')‎</code> إلى النقر بزر الفأرة الأيمن عند الإحداثيات ‎(200, 250)‎.
</p>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_16" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> pyautogui
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">click</span><span class="pun">(</span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">)</span><span class="pln"> </span><span class="com"># ‫تحريك الفأرة إلى الإحداثيات ‎(10, 5)‎ ثم النقر</span></pre>

<p>
	يُفترَض أن ترى مؤشر الفأرة يتحرك بالقرب من الزاوية العلوية اليسرى من الشاشة ثم يحدث النقر مرة واحدة. يمكن تعريف "النقرة" الكاملة على أنها الضغط على زر الفأرة للأسفل ثم تحريره للأعلى دون تحريك المؤشر، ويمكنك أيضًا إجراء نقرة من خلال استدعاء الدالة <code>pyautogui.mouseDown()‎</code> التي تضغط زر الفأرة للأسفل فقط، ثم استدعاء الدالة <code>pyautogui.mouseUp()‎</code> التي تحرّر الزر. تمتلك هاتان الدالتان وسطاء الدالة <code>click()‎</code> نفسها، ولكن تُعَد الدالة <code>click()‎</code> مجرد دالة مغلِّفة ملائمة لهاتين الدالتين.
</p>

<p>
	تنقر الدالة <code>pyautogui.doubleClick()‎</code> نقرتين باستخدام زر الفأرة الأيسر، بينما تنقر الدالتان <code>pyautogui.rightClick()‎</code> و <code>pyautogui.middleClick()‎</code> نقرة واحدة باستخدام زري الفأرة الأيمن والأوسط على التوالي.
</p>

<h3 id="dragging">
	سحب Dragging الفأرة
</h3>

<p>
	يعني السحب تحريكَ الفأرة أثناء الضغط باستمرار على أحد أزرارها، فمثلًا يمكنك نقل الملفات بين المجلدات من خلال سحب أيقونات المجلدات، أو يمكنك نقل المواعيد في تطبيق التقويم من خلال سحبها باستخدام الفأرة.
</p>

<p>
	توفر وحدة PyAutoGUI الدالتين <code>pyautogui.dragTo()‎</code> و <code>pyautogui.drag()‎</code> لسحب مؤشر الفأرة إلى موقع جديد أو موقع متعلق بموقعه الحالي. تستخدم الدالتان <code>dragTo()‎</code> و <code>drag()‎</code> وسطاء الدالتين <code>moveTo()‎</code> و <code>move()‎</code> نفسها وهي: حركة الإحداثي x أو الحركة أفقيًا وحركة الإحداثي y أو الحركة عموديًا ومدة زمنية اختيارية. لا يطبّق نظام ماك السحب تطبيقًا صحيحًا عندما تتحرك الفأرة بسرعة كبيرة، لذا يوصَى بتمرير وسيط الكلمات المفتاحية <code>duration</code>.
</p>

<p>
	لنجرّب هذه الدوال، لذا افتح تطبيق رسمٍ مثل تطبيق الرسام MS Paint على ويندوز أو تطبيق Paintbrush على نظام ماك ، أو تطبيق GNU Paint على نظام لينكس، حيث سنستخدم وحدة PyAutoGUI للرسم في هذه التطبيقات. إن لم يكن لديك تطبيق رسم، فيمكنك استخدام تطبيق <a href="https://sumopaint.com/" rel="external nofollow">sumopaint</a> عبر الإنترنت.
</p>

<p>
	أدخِل ما يلي في نافذة ملفٍ جديد في محرّرك واحفظه بالاسم spiralDraw.py مع وجود مؤشر الفأرة على لوحة الرسم الخاصة بتطبيق الرسم وتحديد أداة القلم Pencil أو الفرشاة Brush:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_18" style=""><span class="pln">   </span><span class="kwd">import</span><span class="pln"> pyautogui</span><span class="pun">,</span><span class="pln"> time
</span><span class="pun">➊</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">sleep</span><span class="pun">(</span><span class="lit">5</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➋</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">click</span><span class="pun">()</span><span class="pln">    </span><span class="com"># النقر لتنشيط النافذة</span><span class="pln">
   distance </span><span class="pun">=</span><span class="pln"> </span><span class="lit">300</span><span class="pln">
   change </span><span class="pun">=</span><span class="pln"> </span><span class="lit">20</span><span class="pln">
   </span><span class="kwd">while</span><span class="pln"> distance </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">0</span><span class="pun">:</span><span class="pln">
    </span><span class="pun">➌</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">drag</span><span class="pun">(</span><span class="pln">distance</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> duration</span><span class="pun">=</span><span class="lit">0.2</span><span class="pun">)</span><span class="pln">   </span><span class="com"># التحرك يمينًا</span><span class="pln">
    </span><span class="pun">➍</span><span class="pln"> distance </span><span class="pun">=</span><span class="pln"> distance </span><span class="pun">–</span><span class="pln"> change
    </span><span class="pun">➎</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">drag</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> distance</span><span class="pun">,</span><span class="pln"> duration</span><span class="pun">=</span><span class="lit">0.2</span><span class="pun">)</span><span class="pln">   </span><span class="com"># التحرك للأسفل</span><span class="pln">
    </span><span class="pun">➏</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">drag</span><span class="pun">(-</span><span class="pln">distance</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> duration</span><span class="pun">=</span><span class="lit">0.2</span><span class="pun">)</span><span class="pln">  </span><span class="com"># التحرك يسارًا</span><span class="pln">
       distance </span><span class="pun">=</span><span class="pln"> distance </span><span class="pun">–</span><span class="pln"> change
       pyautogui</span><span class="pun">.</span><span class="pln">drag</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="pln">distance</span><span class="pun">,</span><span class="pln"> duration</span><span class="pun">=</span><span class="lit">0.2</span><span class="pun">)</span><span class="pln">  </span><span class="com"># التحرك للأعلى</span></pre>

<p>
	سيكون هناك تأخير لمدة خمس ثوانٍ ➊ عند تشغيل هذا البرنامج لتتمكّن من تحريك مؤشر الفأرة على نافذة برنامج الرسم مع تحديد أداة القلم أو الفرشاة، ثم سيتحكّم برنامج spiralDraw.py في الفأرة وينقر لتنشيط نافذة برنامج الرسم ➋. النافذة النشطة هي النافذة التي تقبل حاليًا الإدخال من لوحة المفاتيح، وستؤثّر الإجراءات التي تتخذها مثل الكتابة أو سحب الفأرة على تلك النافذة، وتُعرَف النافذة النشطة أيضًا بالنافذة المُركَّزة أو النافذة الأمامية.
</p>

<p>
	يرسم برنامج spiralDraw.py نمطًا حلزونيًا مربعًا مثل النمط الموجود على يسار الشكل الآتي بعد أن يصبح برنامج الرسم نشطًا. يمكنك أيضًا إنشاء صورة حلزونية مربعة باستخدام الوحدة Pillow التي ناقشناها في المقال السابق، ولكن يتيح لك إنشاء الصورة من خلال التحكم في الفأرة لرسمها في برنامج الرسام MS Paint الاستفادةَ من أنماط الفرشاة المتنوعة لهذا البرنامج كما في الشكل الموجود على يمين الشكل التالي، بالإضافة إلى ميزات متقدمة أخرى مثل التدرجات أو أداة التعبئة، حيث يمكنك تحديد إعدادات الفرشاة مسبقًا بنفسك أو جعل شيفرة بايثون الخاصة بك تحدّد هذه الإعدادات، ثم يمكنك تشغيل برنامج الرسم الحلزوني.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="157696" href="https://academy.hsoub.com/uploads/monthly_2024_09/03_000073.jpg.4506b9cb0200dd50772a3cb761cd6bf5.jpg" rel=""><img alt="03 000073" class="ipsImage ipsImage_thumbnailed" data-fileid="157696" data-unique="mxlvdg65g" src="https://academy.hsoub.com/uploads/monthly_2024_09/03_000073.jpg.4506b9cb0200dd50772a3cb761cd6bf5.jpg"> </a>
</p>

<p style="text-align: center;">
	نتائج مثال استخدام الدالة <code>pyautogui.drag()‎</code> المرسومة باستخدام فُرش برنامج الرسام المختلفة
</p>

<p>
	يبدأ المتغير <code>distance</code> عند القيمة 200، لذلك يسحب استدعاء الدالة <code>drag()‎</code> الأول المؤشر بمقدار 200 بكسل إلى اليمين، ويستغرق ذلك 0.2 ثانية ➌ في التكرار الأول <a href="https://wiki.hsoub.com/Python#while" rel="external">لحلقة while</a>، ثم تُقلَّل قيمة المتغير <code>distance</code> إلى القيمة 195 ➍، ويسحب استدعاء الدالة <code>drag()‎</code> الثاني المؤشر بمقدار 195 بكسل للأسفل ➎. يسحب استدعاء الدالة <code>drag()‎</code> الثالث المؤشر بمقدار ‎-195 أفقيًا (أي بمقدار 195 إلى اليسار) ➏، وتُقلَّل قيمة المتغير <code>distance</code> إلى 190، ويسحب استدعاء <code>drag()‎</code> الأخير المؤشر بمقدار 190 بكسل للأعلى. تُسحَب الفأرة إلى اليمين والأسفل واليسار والأعلى في كل تكرار، وتكون قيمة المتغير <code>distance</code> أصغر قليلًا مما كانت عليه في التكرار السابق. يمكنك تحريك مؤشر الفأرة لرسم شكل حلزوني مربع من خلال تكرار هذه الشيفرة البرمجية.
</p>

<p>
	يمكنك رسم هذا الحلزوني يدويًا (أو باستخدام الفأرة)، ولكن يجب أن تعمل ببطء لتكون دقيقًا جدًا، ولكن يمكن للوحدة PyAutoGUI إنجاز ذلك في بضع ثوانٍ.
</p>

<p>
	<strong>ملاحظة</strong>: لا تستطيع الوحدة PyAutoGUI حاليًا إرسال نقرات الفأرة أو ضغطات المفاتيح إلى برامج معينة مثل برامج مكافحة الفيروسات (لمنع الفيروسات من تعطيل البرنامج) أو ألعاب الفيديو على نظام ويندوز (التي تستخدم طريقة مختلفة لتلقي دخل الفأرة ولوحة المفاتيح). يمكنك التحقق من توثيق الوحدة PyAutoGUI على <a href="https://pyautogui.readthedocs.io/" rel="external nofollow">موقعها الرسمي</a> لمعرفة ما إذا كانت هذه الميزات مدعومة في نظامك.
</p>

<h3 id="-8">
	التمرير بالفأرة
</h3>

<p>
	دالة الوحدة PyAutoGUI الأخيرة الخاصة بالفأرة هي الدالة <code>scroll()‎</code> التي نمرّر إليها وسيطًا نوعه عدد صحيح يمثّل عدد الوحدات التي تريد تمرير الفأرة عبرها للأعلى أو للأسفل، حيث يختلف حجم هذه الوحدة باختلاف نظام التشغيل والتطبيق، لذا يجب اأن تجرّب لمعرفة مقدار التمرير في حالتك، ويُطبَّق التمرير عند الموضع الحالي لمؤشر الفأرة. يؤدي تمرير عدد صحيح موجب إلى التمرير للأعلى، بينما يؤدي تمرير عدد صحيح سالب إلى التمرير للأسفل. شغّل ما يلي في الصدفة التفاعلية للمحرّر Mu أثناء وجود مؤشر الفأرة على نافذة هذا المحرّر:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_20" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">scroll</span><span class="pun">(</span><span class="lit">200</span><span class="pun">)</span></pre>

<p>
	سترى أن برنامج Mu يُمرَّر للأعلى إذا كان مؤشر الفأرة على حقل نصي يمكن تمريره للأعلى.
</p>

<h2 id="-9">
	تخطيط حركات الفأرة
</h2>

<p>
	إحدى صعوبات كتابة برنامج يؤتمت عملية النقر على الشاشة هي العثور على إحداثيات x و y للأشياء التي ترغب في النقر عليها، ولكن يمكن أن تساعدك الدالة <code>pyautogui.mouseInfo()‎</code> في هذا الأمر، حيث يُفترَض استدعاء هذه الدالة من الصدفة التفاعلية، وليس كجزء من برنامجك. تشغِّل هذه الدالة تطبيقًا صغيرًا اسمه MouseInfo المُضمَّن مع الوحدة PyAutoGUI، وتبدو نافذة هذا التطبيق كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="157705" href="https://academy.hsoub.com/uploads/monthly_2024_09/04_000019.jpg.1d87edff5762f16bc61fcbffeeca9fe1.jpg" rel=""><img alt="04 000019" class="ipsImage ipsImage_thumbnailed" data-fileid="157705" data-unique="wrefzd94a" src="https://academy.hsoub.com/uploads/monthly_2024_09/04_000019.jpg.1d87edff5762f16bc61fcbffeeca9fe1.jpg"> </a>
</p>

<p style="text-align: center;">
	نافذة التطبيق MouseInfo
</p>

<p>
	أدخِل الآن ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_22" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> pyautogui
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">mouseInfo</span><span class="pun">()</span></pre>

<p>
	يؤدي ذلك إلى ظهور نافذة تطبيق MouseInfo، حيث توفر لك هذه النافذة معلومات حول الموضع الحالي لمؤشر الفأرة، بالإضافة إلى لون البكسل الموجود تحت مؤشر الفأرة كمجموعة RGB مكونة من ثلاثة أعداد صحيحة وكقيمة ست عشرية، حيث يظهر اللون نفسه في مربع اللون Color الموجود في النافذة.
</p>

<p>
	يمكنك تسجيل معلومات الإحداثيات أو البكسلات من خلال النقر على أحد أزرار النسخ <code>Copy</code> أو التسجيل <code>Log</code> الثمانية، حيث ستنسخ أزرار <code>Copy All</code> و <code>Copy XY</code> و <code>Copy RGB</code> و <code>Copy RGB Hex</code> معلوماتها الخاصة في الحافظة Clipboard، وستكتب الأزرار <code>Log All</code> و <code>Log XY</code> و <code>Log RGB</code> و <code>Log RGB Hex</code> معلوماتها الخاصة في الحقل النصي الكبير من هذه النافذة، ويمكنك حفظ النص الموجود في هذا الحقل لتسجيل النص بالنقر على زر الحفظ <code>Save Log</code>.
</p>

<p>
	لاحظ تحديد مربع الاختيار ‎<code>3 Sec. Button Delay</code>‎ افتراضيًا، مما يتسبب في تأخير لمدة 3 ثوانٍ بين النقر على زر النسخ <code>Copy</code> أو التسجيل <code>Log</code> وحدوث النسخ أو التسجيل، ويمنحك ذلك وقتًا قصيرًا للنقر على الزر ثم تحريك الفأرة إلى الموضع المطلوب. قد يكون من الأسهل إلغاء تحديد مربع الاختيار <code>3‎ Sec. Button Delay</code>، وتحريك الفأرة إلى موضعٍ معين، ثم الضغط على المفاتيح من F1 إلى F8 لنسخ موضع الفأرة أو تسجيله. يمكنك إلقاء نظرة على قوائم النسخ والتسجيل الموجودة أعلى نافذة تطبيق MouseInfo لمعرفة المفاتيح المرتبطة بهذه الأزرار.
</p>

<p>
	ألغِ مثلًا تحديد مربع الاختيار ‎<code>3 Sec. Button Delay</code>‎، ثم حرّك الفأرة على الشاشة أثناء الضغط على الزر F6، ولاحظ كيفية تسجيل إحداثيات x و y للفأرة في الحقل النصي الكبير في منتصف النافذة، حيث يمكنك لاحقًا استخدام هذه الإحداثيات في سكربتات PyAutoGUI الخاصة بك.
</p>

<p>
	اطّلع على <a href="https://mouseinfo.readthedocs.io/" rel="external nofollow">توثيق تطبيق MouseInfo</a> الكامل لمزيدٍ من المعلومات.
</p>

<h2 id="-10">
	العمل مع الشاشات
</h2>

<p>
	ليس من الضروري أن تنقر أو تكتب برامجُك لأتمتة واجهة المستخدم الرسومية دون رؤية ما يحدث، إذ تحتوي الوحدة PyAutoGUI على ميزات لقطة الشاشة التي يمكنها إنشاء ملف صورة بناءً على محتويات الشاشة الحالية، ويمكن لهذه الدوال أيضًا إعادة كائن <code>Image</code> الخاص بالوحدة Pillow لمظهر الشاشة الحالي. اطّلع على المقال السابق وثبّت الوحدة <code>pillow</code> قبل الاستمرار في هذا القسم.
</p>

<p>
	يجب أن تثبّت برنامج <code>scrot</code> على الحواسيب التي تعمل على نظام لينكس لاستخدام دوال لقطة الشاشة في الوحدة PyAutoGUI، لذا شغّل الأمر <code>sudo apt install scrot</code> في نافذةالطرفية لتثبيت هذا البرنامج. إذا كنت تستخدم نظام تشغيل ويندوز أو ماك، فانتقل إلى الخطوة التالية من هذا القسم.
</p>

<h3 id="-11">
	الحصول على لقطة الشاشة
</h3>

<p>
	يمكنك التقاط لقطات شاشة في <a href="https://academy.hsoub.com/programming/python/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1815/" rel="">لغة بايثون</a> من خلال استدعاء الدالة <code>pyautogui.screenshot()‎</code>، لذا أدخِل ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_24" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> pyautogui
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> im </span><span class="pun">=</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">screenshot</span><span class="pun">()</span></pre>

<p>
	سيحتوي المتغير <code>im</code> على كائن <code>Image</code> للقطة الشاشة، وبالتالي يمكنك الآن استدعاء التوابع مع كائن <code>Image</code> في المتغير <code>im</code> مثل أيّ كائن <code>Image</code> آخر. اطّلع على المقال السابق للحصول على مزيدٍ من التفاصيل حول كائنات <code>Image</code>.
</p>

<h3 id="-12">
	تحليل لقطة الشاشة
</h3>

<p>
	لنفترض أن إحدى الخطوات في برنامجك لأتمتة واجهة المستخدم الرسومية هي النقر على زر رمادي، حيث يمكنك التقاط لقطة شاشة قبل استدعاء التابع <code>click()‎</code> وإلقاء نظرة على البكسل الذي سينقر سكربتك عليه، فإن لم يكن لون هذا البكسل رماديًا مثل الزر الرمادي، فهذا يعني أن برنامجك يعلم أن هناك خطأً ما، وبالتالي قد تتحرك النافذة بطريقة غير متوقعة، أو قد يوقِف مربع حوار منبثق الزر. يمكن لبرنامجك عندها رؤية أنه لا ينقر على الشيء الصحيح ويوقِف نفسه بدلًا من الاستمرار وإحداث فوضى من خلال النقر على الشيء الخطأ.
</p>

<p>
	يمكنك الحصول على قيمة لون RGB لبكسل معين على الشاشة باستخدام الدالة <code>pixel()‎</code>. أدخِل مثلًا ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_26" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> pyautogui
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">pixel</span><span class="pun">((</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">))</span><span class="pln">
</span><span class="pun">(</span><span class="lit">176</span><span class="pun">,</span><span class="pln"> </span><span class="lit">176</span><span class="pun">,</span><span class="pln"> </span><span class="lit">175</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">pixel</span><span class="pun">((</span><span class="lit">50</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">))</span><span class="pln">
</span><span class="pun">(</span><span class="lit">130</span><span class="pun">,</span><span class="pln"> </span><span class="lit">135</span><span class="pun">,</span><span class="pln"> </span><span class="lit">144</span><span class="pun">)</span></pre>

<p>
	مرّر مجموعة من الإحداثيات مثل ‎(0, 0)‎ أو ‎(50, 200)‎ إلى الدالة <code>pixel()‎</code> التي ستخبرك بلون البكسل عند تلك الإحداثيات في صورتك. القيمة المُعادة من الدالة <code>pixel()‎</code> هي مجموعة RGB مكونة من ثلاثة أعداد صحيحة تمثّل مقدار اللون الأحمر والأخضر والأزرق في البكسل، ولا توجد قيمة رابعة تمثّل قيمة ألفا Alpha، لأن صور لقطة الشاشة معتمة Opaque تمامًا.
</p>

<p>
	تعيد الدالة <code>pixelMatchesColor()‎</code> الخاصة بوحدة PyAutoGUI القيمة <code>True</code> إذا تطابق البكسل الموجود عند إحداثيات x و y المُعطاة على الشاشة مع اللون المُعطَى. يُعَد الوسيطان الأول والثاني أعدادًا صحيحة تمثّل إحداثيات x و y، والوسيط الثالث هو مجموعة مكونة من ثلاثة أعداد صحيحة تمثّل لون RGB الذي يجب أن يتطابق مع البكسل الموجود على الشاشة. أدخِل مثلًا ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_28" style=""><span class="pln">   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> pyautogui
</span><span class="pun">➊</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">pixel</span><span class="pun">((</span><span class="lit">50</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">))</span><span class="pln">
   </span><span class="pun">(</span><span class="lit">130</span><span class="pun">,</span><span class="pln"> </span><span class="lit">135</span><span class="pun">,</span><span class="pln"> </span><span class="lit">144</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➋</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">pixelMatchesColor</span><span class="pun">(</span><span class="lit">50</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="lit">130</span><span class="pun">,</span><span class="pln"> </span><span class="lit">135</span><span class="pun">,</span><span class="pln"> </span><span class="lit">144</span><span class="pun">))</span><span class="pln">
   </span><span class="kwd">True</span><span class="pln">
</span><span class="pun">➌</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">pixelMatchesColor</span><span class="pun">(</span><span class="lit">50</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="lit">255</span><span class="pun">,</span><span class="pln"> </span><span class="lit">135</span><span class="pun">,</span><span class="pln"> </span><span class="lit">144</span><span class="pun">))</span><span class="pln">
   </span><span class="kwd">False</span></pre>

<p>
	استخدمنا الدالة <code>pixel()‎</code> للحصول على مجموعة RGB التي تمثّل لون البكسل عند إحداثيات مُحدَّدة ➊، وسنمرّر الآن الإحداثيات نفسها ومجموعة RGB إلى الدالة <code>pixelMatchesColor()‎</code> ➋، والتي يجب أن تعيد القيمة <code>True</code>. نغيّر بعد ذلك قيمةً من مجموعة RGB ونستدعي الدالة <code>pixelMatchesColor()‎</code> مرةً أخرى مع الإحداثيات نفسها ➌، حيث يجب أن تعيد القيمة <code>False</code>. يمكن أن يكون استدعاء هذه الدالة مفيدًا عندما تكون برامجك لأتمتة واجهة المستخدم الرسومية على وشك استدعاء التابع <code>click()‎</code> . لاحظ أن اللون عند الإحداثيات المحددة يجب أن يكون متطابقًا تمامًا، حيث إذا كان مختلفًا قليلًا مثل ‎(255, 255, 254)‎ بدلًا من ‎(255, 255, 255)‎، فستعيد الدالة ‎<code>pixelMatchesColor()</code>‎ القيمة <code>False</code>.
</p>

<h2 id="-13">
	التعرف على الصور
</h2>

<p>
	إن لم تكن على معرفة مسبقة بالمكان الذي يجب أن تنقر عليه الوحدة PyAutoGUI، فيمكنك استخدام ميزة التعرّف على الصور، لذا أعطِ وحدة PyAutoGUI صورةً لما تريد النقر عليه ودعه يكتشف الإحداثيات.
</p>

<p>
	إذا التقطتَ مسبقًا لقطة شاشة للحصول على صورة زر الإرسال Submit في الملف submit.png مثلًا، فستعيد الدالة <code>locateOnScreen()‎</code> إحداثيات مكان وجود تلك الصورة. لنتعرّف على كيفية عمل هذه الدالة، لذا حاول التقاط لقطة شاشة لمنطقة صغيرة من شاشتك، ثم احفظ الصورة وأدخِل ما يلي في الصدفة التفاعلية مع وضع اسم ملف لقطة الشاشة الخاصة بك مكان <code>'submit.png'</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_30" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> pyautogui
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> b </span><span class="pun">=</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">locateOnScreen</span><span class="pun">(</span><span class="str">'submit.png'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> b
</span><span class="typ">Box</span><span class="pun">(</span><span class="pln">left</span><span class="pun">=</span><span class="lit">643</span><span class="pun">,</span><span class="pln"> top</span><span class="pun">=</span><span class="lit">745</span><span class="pun">,</span><span class="pln"> width</span><span class="pun">=</span><span class="lit">70</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">=</span><span class="lit">29</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> b</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln">
</span><span class="lit">643</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> b</span><span class="pun">.</span><span class="pln">left
</span><span class="lit">643</span></pre>

<p>
	يُعَد كائن <code>Box</code> مجموعةً مسماة تعيدها الدالة <code>locateOnScreen()‎</code> وله إحداثي x للحافة اليسرى وإحداثي y للحافة العلوية والعرض والارتفاع لمكان العثور على الصورة الأول على الشاشة. إذا طبّقتَ ذلك على حاسوبك باستخدام لقطة شاشتك، فستكون القيمة المُعادة مختلفة عن القيمة الموضحة في مثالنا.
</p>

<p>
	إذا تعذر العثور على الصورة على الشاشة، فستعيد الدالة <code>locateOnScreen()‎</code> القيمة <code>None</code>. لاحظ أن الصورة الموجودة على الشاشة يجب أن تتطابق تمامًا مع الصورة المُقدَّمة للتعرّف عليها، حيث إذا كانت الصورة مختلفة ببكسل واحد، فسترفع الدالة <code>locateOnScreen()‎</code> الاستثناء <code>ImageNotFoundException</code>. إذا غيّرتَ دقة الشاشة، فقد لا تتطابق الصور من لقطات الشاشة السابقة مع الصور الموجودة على شاشتك الحالية، لذا يمكنك تغيير القياسات في إعدادات العرض لنظام تشغيلك كما هو موضّح في الشكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="157702" href="https://academy.hsoub.com/uploads/monthly_2024_09/05_000113.jpg.d64acd48bb8913dc5f41565e513d7cae.jpg" rel=""><img alt="05 000113" class="ipsImage ipsImage_thumbnailed" data-fileid="157702" data-unique="z5d4dd4k7" src="https://academy.hsoub.com/uploads/monthly_2024_09/05_000113.jpg.d64acd48bb8913dc5f41565e513d7cae.jpg"> </a>
</p>

<p style="text-align: center;">
	إعدادات قياسات العرض في نظام ويندوز 10 (على اليسار) ونظام ماك (على اليمين)
</p>

<p>
	إذا عُثِر على الصورة في عدة أماكن على الشاشة، فستعيد الدالة <code>locateAllOnScreen()‎</code> كائن <code>Generator</code> الذي يمكنك تمريره إلى التابع <code>list()‎</code> لإعادة قائمة من المجموعات المكونة من أربعة أعداد صحيحة، حيث ستوجد مجموعة واحدة مكونة من أربعة أعداد صحيحة لكل موقع توجد فيه الصورة على الشاشة. تابع مثال الصدفة التفاعلية من خلال إدخال ما يلي مع وضع ملف الصورة الخاص بك مكان <code>'submit.png'</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_32" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> list</span><span class="pun">(</span><span class="pln">pyautogui</span><span class="pun">.</span><span class="pln">locateAllOnScreen</span><span class="pun">(</span><span class="str">'submit.png'</span><span class="pun">))</span><span class="pln">
</span><span class="pun">[(</span><span class="lit">643</span><span class="pun">,</span><span class="pln"> </span><span class="lit">745</span><span class="pun">,</span><span class="pln"> </span><span class="lit">70</span><span class="pun">,</span><span class="pln"> </span><span class="lit">29</span><span class="pun">),</span><span class="pln"> </span><span class="pun">(</span><span class="lit">1007</span><span class="pun">,</span><span class="pln"> </span><span class="lit">801</span><span class="pun">,</span><span class="pln"> </span><span class="lit">70</span><span class="pun">,</span><span class="pln"> </span><span class="lit">29</span><span class="pun">)]</span></pre>

<p>
	تمثل كل مجموعة من هذه المجموعات المكونة من أربعة أعداد صحيحة منطقةً من الشاشة، حيث تظهَر الصورة في موقعين في المثال السابق. إذا وُجِدت صورتُك في منطقةٍ واحدة فقط، فسيؤدي استخدام التابع <code>list()‎</code> والدالة <code>locateAllOnScreen()‎</code> إلى إعادة قائمة تحتوي على مجموعة واحدة فقط.
</p>

<p>
	نحصل على المجموعة المكونة من أربعة أعداد صحيحة التي تمثّل الصورة التي تريد تحديدها، ثم يمكننا النقر على مركز هذه المنطقة من خلال تمرير هذه المجموعة إلى التابع <code>click()‎</code>. لندخل الآن مثلًا ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_34" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">click</span><span class="pun">((</span><span class="lit">643</span><span class="pun">,</span><span class="pln"> </span><span class="lit">745</span><span class="pun">,</span><span class="pln"> </span><span class="lit">70</span><span class="pun">,</span><span class="pln"> </span><span class="lit">29</span><span class="pun">))</span></pre>

<p>
	يمكنك أيضًا تمرير اسم ملف الصورة مباشرةً إلى التابع <code>click()‎</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_36" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">click</span><span class="pun">(</span><span class="str">'submit.png'</span><span class="pun">)</span></pre>

<p>
	تقبل الدالتان <code>moveTo()‎</code> و <code>dragTo()‎</code> أيضًا وسطاء لاسم ملف الصورة. تذكّر أن الدالة <code>locateOnScreen()‎</code> ترفع استثناءً إن لم تتمكّن من العثور على الصورة على الشاشة، لذا يجب أن تستدعيها من داخل تعليمة <code>try</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_38" style=""><span class="kwd">try</span><span class="pun">:</span><span class="pln">
    location </span><span class="pun">=</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">locateOnScreen</span><span class="pun">(</span><span class="str">'submit.png'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">except</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Image could not be found.'</span><span class="pun">)</span></pre>

<p>
	سيؤدي استثناء عدم العثور على الصورة على الشاشة إلى تعطّل برنامجك إن لم تستخدم تعليمات <code>try</code> و <code>except</code>، لذا من الجيد استخدام تعليمات <code>try</code> و <code>except</code> عند استدعاء الدالة <code>locateOnScreen()‎</code> لأنك لا تستطيع التأكّد من أن برنامجك سيعثر على الصورة دائمًا.
</p>

<h2 id="-14">
	الحصول على معلومات النافذة
</h2>

<p>
	يُعَد التعرّف على الصور طريقة ضعيفة للعثور على الأشياء التي تظهر على الشاشة، حيث إذا كان هناك بكسل واحد بلون مختلف، فلن تتمكن الدالة <code>pyautogui.locateOnScreen()‎</code> من العثور على الصورة، لذا إذا كنت بحاجة إلى العثور على مكان وجود نافذة معينة على الشاشة، فمن الأسرع والأكثر موثوقية استخدام ميزات النافذة الخاصة بوحدة PyAutoGUI.
</p>

<p>
	<strong>ملاحظة</strong>: تعمل ميزات النافذة الخاصة بوحدة PyAutoGUI على نظام تشغيل ويندوز فقط، ولا تعمل على نظام تشغيل ماك أو لينكس ابتداءً من الإصدار 0.9.46، وتأتي هذه الميزات من احتواء الوحدة PyAutoGUI على الوحدة PyGetWindow.
</p>

<h3 id="-15">
	الحصول على النافذة النشطة
</h3>

<p>
	النافذة النشطة على شاشتك هي النافذة الموجودة حاليًا في المقدمة والتي تقبل الإدخال من لوحة المفاتيح. إذا كنت تكتب حاليًا شيفرة برمجية في المحرّر Mu، فإن نافذته هي النافذة النشطة، حيث ستُنشَّط نافذة واحدة فقط من بين جميع النوافذ التي تظهر على شاشتك في كل مرة. استدعِ الدالة <code>pyautogui.getActiveWindow()‎</code> في الصدفة التفاعلية للحصول على كائن <code>Window</code> أو كائن <code>Win32Window</code> عند التشغيل على نظام ويندوز.
</p>

<p>
	يمكنك استرداد أيٍّ من سمات الكائن <code>Window</code> التي تمثّل حجمه وموضعه وعنوانه بعد الحصول عليه وهذه السمات هي:
</p>

<ul>
	<li>
		<code>left</code> و <code>right</code> و <code>top</code> و <code>bottom</code>: عدد صحيح واحد يمثّل الإحداثي x أو y لطرف النافذة.
	</li>
	<li>
		<code>topleft</code> و <code>topright</code> و <code>bottomleft</code> و <code>bottomright</code>: مجموعة مسماة مكونة من عددين صحيحين يمثّلان إحداثيات (x, y) لزاوية النافذة.
	</li>
	<li>
		<code>midleft</code> و <code>midright</code> و <code>midleft</code> و <code>midright</code>: مجموعة مسماة مكونة من عددين صحيحين يمثلان إحداثيات (x, y) لمنتصف طرف النافذة.
	</li>
	<li>
		<code>width</code> و <code>height</code>: عدد صحيح واحد يمثّل أحد أبعاد النافذة بالبكسل.
	</li>
	<li>
		<code>size</code>: مجموعة مسماة مكونة من عددين صحيحين يمثّلان عرض وارتفاع النافذة (width, height).
	</li>
	<li>
		<code>area</code>: عدد صحيح واحد يمثل مساحة النافذة بالبكسل.
	</li>
	<li>
		<code>center</code>: مجموعة مسماة مكونة من عددين صحيحين يمثلان إحداثيات (x, y) لمركز النافذة.
	</li>
	<li>
		<code>centerx</code> و <code>centery</code> : عدد صحيح واحد يمثل إحداثي x أو y لمركز النافذة.
	</li>
	<li>
		<code>box</code>: مجموعة مسماة مكونة من أربعة أعداد صحيحة لقياسات يسار وأعلى وعرض وارتفاع النافذة (left, top, width, height).
	</li>
	<li>
		<code>title</code>: سلسلة من النص الموجود في شريط العنوان أعلى النافذة.
	</li>
</ul>

<p>
	أدخِل مثلًا ما يلي في الصدفة التفاعلية للحصول على معلومات موضع النافذة وحجمها وعنوانها من كائن <code>Window</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_40" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> pyautogui
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> fw </span><span class="pun">=</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">getActiveWindow</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> fw
</span><span class="typ">Win32Window</span><span class="pun">(</span><span class="pln">hWnd</span><span class="pun">=</span><span class="lit">2034368</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> str</span><span class="pun">(</span><span class="pln">fw</span><span class="pun">)</span><span class="pln">
</span><span class="str">'&lt;Win32Window left="500", top="300", width="2070", height="1208", title="Mu 1.0.1 – test1.py"&gt;'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> fw</span><span class="pun">.</span><span class="pln">title
</span><span class="str">'Mu 1.0.1 – test1.py'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> fw</span><span class="pun">.</span><span class="pln">size
</span><span class="pun">(</span><span class="lit">2070</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1208</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> fw</span><span class="pun">.</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> fw</span><span class="pun">.</span><span class="pln">top</span><span class="pun">,</span><span class="pln"> fw</span><span class="pun">.</span><span class="pln">right</span><span class="pun">,</span><span class="pln"> fw</span><span class="pun">.</span><span class="pln">bottom
</span><span class="pun">(</span><span class="lit">500</span><span class="pun">,</span><span class="pln"> </span><span class="lit">300</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2070</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1208</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> fw</span><span class="pun">.</span><span class="pln">topleft
</span><span class="pun">(</span><span class="lit">256</span><span class="pun">,</span><span class="pln"> </span><span class="lit">144</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> fw</span><span class="pun">.</span><span class="pln">area
</span><span class="lit">2500560</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">click</span><span class="pun">(</span><span class="pln">fw</span><span class="pun">.</span><span class="pln">left </span><span class="pun">+</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> fw</span><span class="pun">.</span><span class="pln">top </span><span class="pun">+</span><span class="pln"> </span><span class="lit">20</span><span class="pun">)</span></pre>

<p>
	يمكنك الآن استخدام هذه السمات لحساب الإحداثيات الدقيقة في النافذة، فمثلًا إذا كنت تعلم أن الزر الذي تريد النقر عليه يقع دائمًا على بعد 10 بكسلات على اليمين و20 بكسلًا للأسفل من الزاوية العلوية اليسرى للنافذة، وأن الزاوية العلوية اليسرى للنافذة تقع عند إحداثيات الشاشة ‎(300, 500)‎، فسيؤدي استدعاء التابع <code>pyautogui.click(310, 520)‎</code> (أو <code>pyautogui.click(fw.left + 10, fw.top + 20)‎</code> إذا احتوى المتغير <code>fw</code> على كائن <code>Window</code> الخاص بالنافذة) إلى النقر على هذا الزر، وبالتالي لن تضطر إلى الاعتماد على الدالة <code>locateOnScreen()‎</code> الأبطأ والأقل موثوقية للعثور على الزر.
</p>

<h3 id="-16">
	طرق أخرى للحصول على النافذة
</h3>

<p>
	تُعَد الدالة <code>getActiveWindow()‎</code> مفيدةً للحصول على النافذة النشطة في وقت استدعاء الدالة، ولكن ستحتاج إلى استخدام بعض الدوال الأخرى للحصول على كائنات <code>Window</code> للنوافذ الأخرى على الشاشة، حيث تعيد الدوال الأربع التالية قائمةً بكائنات <code>Window</code>، وإن لم تتمكّن من العثور على أيّ نوافذ، فستعيد قائمةً فارغة:
</p>

<ul>
	<li>
		<code>pyautogui.getAllWindows()‎</code>: تعيد قائمةً بكائنات <code>Window</code> لكل نافذة مرئية على الشاشة.
	</li>
	<li>
		<code>pyautogui.getWindowsAt(x, y)‎</code>: تعيد قائمةً بكائنات <code>Window</code> لكل نافذة مرئية تتضمن النقطة (x, y).
	</li>
	<li>
		<code>pyautogui.getWindowsWithTitle(title)‎</code>: تعيد قائمةً بكائنات <code>Window</code> لكل نافذة مرئية تتضمن السلسلة النصية <code>title</code> في شريط العنوان الخاص بها.
	</li>
	<li>
		<code>pyautogui.getActiveWindow()‎</code>: تعيد كائن <code>Window</code> للنافذة التي تتلقّى تركيز لوحة المفاتيح حاليًا.
	</li>
	<li>
		تحتوي الوحدة PyAutoGUI أيضًا على الدالة <code>pyautogui.getAllTitles()‎</code> التي تعيد قائمةً بالسلاسل النصية لكل نافذة مرئية.
	</li>
</ul>

<h3 id="-17">
	معالجة النوافذ
</h3>

<p>
	يمكن لسمات النوافذ أن تفعل أكثر من مجرد إخبارك بحجم النافذة وموضعها، إذ يمكنك أيضًا ضبط قيمها لتغيير حجم النافذة أو تحريكها، فمثلًا أدخِل ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_42" style=""><span class="pln">   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> pyautogui
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> fw </span><span class="pun">=</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">getActiveWindow</span><span class="pun">()</span><span class="pln">
</span><span class="pun">➊</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> fw</span><span class="pun">.</span><span class="pln">width </span><span class="com"># الحصول على العرض الحالي للنافذة</span><span class="pln">
   </span><span class="lit">1669</span><span class="pln">
</span><span class="pun">➋</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> fw</span><span class="pun">.</span><span class="pln">topleft </span><span class="com"># الحصول على الموضع الحالي للنافذة</span><span class="pln">
   </span><span class="pun">(</span><span class="lit">174</span><span class="pun">,</span><span class="pln"> </span><span class="lit">153</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➌</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> fw</span><span class="pun">.</span><span class="pln">width </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1000</span><span class="pln"> </span><span class="com"># تغيير حجم العرض</span><span class="pln">
</span><span class="pun">➍</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> fw</span><span class="pun">.</span><span class="pln">topleft </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="lit">800</span><span class="pun">,</span><span class="pln"> </span><span class="lit">400</span><span class="pun">)</span><span class="pln"> </span><span class="com"># تحريك النافذة</span></pre>

<p>
	نستخدم أولًا سمات كائن <code>Window</code> لمعرفة معلومات حول حجم النافذة ➊ وموضعها ➋. يجب أن تتحرك النافذة ➍ وتصبح أضيق ➌ كما في الشكل التالي بعد استدعاء هذه <a href="https://wiki.hsoub.com/Python#.D8.A7.D9.84.D8.AF.D9.88.D8.A7.D9.84" rel="external">الدوال</a> في المحرّر Mu:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="157697" href="https://academy.hsoub.com/uploads/monthly_2024_09/06_000058.jpg.cc1e9c7c253cf484709434725ce221ea.jpg" rel=""><img alt="06 000058" class="ipsImage ipsImage_thumbnailed" data-fileid="157697" data-unique="bruoqw80s" src="https://academy.hsoub.com/uploads/monthly_2024_09/06_000058.thumb.jpg.dc5d784386c596eb5716f7f2cd4f4454.jpg"> </a>
</p>

<p style="text-align: center;">
	نافذة المحرّر Mu قبل (في الأعلى) وبعد (في الأسفل) باستخدام سمات كائن <code>Window</code> لتحريك النافذة وتغيير حجمها
</p>

<p>
	يمكنك أيضًا اكتشاف وتغيير حالات تصغير النافذة وتكبيرها وتنشيطها، لذا جرّب إدخال ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_44" style=""><span class="pln">   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> pyautogui
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> fw </span><span class="pun">=</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">getActiveWindow</span><span class="pun">()</span><span class="pln">
</span><span class="pun">➊</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> fw</span><span class="pun">.</span><span class="pln">isMaximized </span><span class="com"># ‫تعيد القيمة True عند تكبير النافذة</span><span class="pln">
   </span><span class="kwd">False</span><span class="pln">
</span><span class="pun">➋</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> fw</span><span class="pun">.</span><span class="pln">isMinimized </span><span class="com"># تعيد القيمة‫ True عند تصغير النافذة</span><span class="pln">
   </span><span class="kwd">False</span><span class="pln">
</span><span class="pun">➌</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> fw</span><span class="pun">.</span><span class="pln">isActive </span><span class="com"># تعيد القيمة‫ True إذا كانت النافذة نشطة</span><span class="pln">
   </span><span class="kwd">True</span><span class="pln">
</span><span class="pun">➍</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> fw</span><span class="pun">.</span><span class="pln">maximize</span><span class="pun">()</span><span class="pln"> </span><span class="com"># تكبير النافذة</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> fw</span><span class="pun">.</span><span class="pln">isMaximized
   </span><span class="kwd">True</span><span class="pln">
</span><span class="pun">➎</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> fw</span><span class="pun">.</span><span class="pln">restore</span><span class="pun">()</span><span class="pln"> </span><span class="com"># التراجع عن إجراء التصغير/التكبير</span><span class="pln">
</span><span class="pun">➏</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> fw</span><span class="pun">.</span><span class="pln">minimize</span><span class="pun">()</span><span class="pln"> </span><span class="com"># تصغير النافذة</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> time
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="com"># ‫الانتظار 5 ثوانٍ أثناء تنشيط نافذة مختلفة:</span><span class="pln">
</span><span class="pun">➐</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">sleep</span><span class="pun">(</span><span class="lit">5</span><span class="pun">);</span><span class="pln"> fw</span><span class="pun">.</span><span class="pln">activate</span><span class="pun">()</span><span class="pln">
</span><span class="pun">➑</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> fw</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln"> </span><span class="com"># سيؤدي ذلك إلى إغلاق النافذة التي تكتب فيها</span></pre>

<p>
	تحتوي السمات <code>isMaximized</code> ➊ و <code>isMinimized</code> ➋ و <code>isActive</code> ➌ على قيمٍ منطقية تشير إلى ما إذا كانت النافذة حاليًا في حالة التكبير أو التصغير أو التنشيط أم لا، بينما تغير التوابع <code>maximize()‎</code> ➍ و <code>minimize()‎</code> ➏ و <code>activate()‎</code> ➐ و <code>restore()‎</code> ➎ حالة النافذة، وسيعيد التابع <code>restore()</code> النافذة إلى حجمها وموضعها السابق بعد تكبير النافذة أو تصغيرها باستخدام التابعين <code>maximize()‎</code> و <code>minimize()‎</code>. يغلق التابع <code>close()‎</code> النافذة، ولكن كن حذرًا عند استخدام هذا التابع، لأنه قد يتجاوز أي مربعات حوار للرسائل التي تطلب منك حفظ عملك قبل الخروج من التطبيق.
</p>

<p>
	يمكن العثور على التوثيق الكامل لميزة التحكم في النوافذ الخاصة بوحدة PyAutoGUI على <a href="https://pyautogui.readthedocs.io/" rel="external nofollow">موقعها الرسمي</a>. يمكنك أيضًا استخدام هذه الميزات بصورة منفصلة عن الوحدة PyAutoGUI مع الوحدة PyGetWindow الذي يمكنك الاطلاع على توثيقها على <a href="https://pygetwindow.readthedocs.io/" rel="external nofollow">موقعها الرسمي</a>.
</p>

<h2 id="-18">
	التحكم بلوحة المفاتيح
</h2>

<p>
	تحتوي الوحدة PyAutoGUI أيضًا على دوال لإرسال ضغطات مفاتيح افتراضية إلى حاسوبك، والتي تمكّنك من ملء الاستمارات أو إدخال نصٍ في التطبيقات.
</p>

<h3 id="-19">
	إرسال سلسلة نصية من لوحة المفاتيح
</h3>

<p>
	ترسل الدالة <code>pyautogui.write()‎</code> ضغطات مفاتيح افتراضية إلى الحاسوب، حيث يعتمد ما تفعله هذه الضغطات على النافذة النشطة والحقل النصي المُركَّز عليه، لذا قد ترغب أولًا في إرسال نقرة بالفأرة إلى الحقل النصي الذي تريده للتأكد من التركيز عليه.
</p>

<p>
	لنستخدم لغة بايثون لكتابة النص "Hello, world!‎" في نافذة محرر الملفات. افتح أولًا نافذة جديدة في محرّر ملفاتك وَضَعها في الزاوية العلوية اليسرى من شاشتك بحيث تنقر وحدة PyAutoGUI في المكان المناسب للتركيز عليها، ثم أدخِل ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_46" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">click</span><span class="pun">(</span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">);</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">write</span><span class="pun">(</span><span class="str">'Hello, world!'</span><span class="pun">)</span></pre>

<p>
	لاحظ كيف أننا وضعنا أمرين على السطر نفسه، وفصلنا بينهما بفاصلة منقوطة، مما يمنع الصدفة التفاعلية من مطالبتك بالإدخال بين تشغيل هاتين التعليمتين، ويمنعك من التركيز على نافذة جديدة عن طريق الخطأ بين الاستدعائين <code>click()‎</code> و <code>write()‎</code> الذي يمكن أن يفسد مثالنا.
</p>

<p>
	سترسل شيفرة بايثون أولًا نقرة افتراضية بالفأرة إلى الإحداثيات ‎(100, 200)‎، والتي يجب أن تنقر على نافذة محرّر الملفات لنقل التركيز إليها، وسيرسل استدعاء الدالة <code>write()‎</code> النص "Hello, world!‎" إلى النافذة، مما يجعلها تبدو كما في الشكل التالي، وبالتالي أصبح لديك الآن شيفرة برمجية يمكن أن تكتب نيابةً عنك.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="157704" href="https://academy.hsoub.com/uploads/monthly_2024_09/07_000001.jpg.145a5357ae12d60eda6988c77c71057e.jpg" rel=""><img alt="07 000001" class="ipsImage ipsImage_thumbnailed" data-fileid="157704" data-unique="7t0lvk31q" src="https://academy.hsoub.com/uploads/monthly_2024_09/07_000001.jpg.145a5357ae12d60eda6988c77c71057e.jpg"> </a>
</p>

<p style="text-align: center;">
	استخدام وحدة PyAutogGUI للنقر على نافذة محرّر الملفات وكتابة النص "Hello, world!‎" فيها
</p>

<p>
	ستكتب الدالة ‎<code>write()‎</code>‎ افتراضيًا السلسلة النصية الكاملة مباشرةً، ولكن يمكنك تمرير وسيطٍ ثانٍ اختياري لإضافة توقف قصير بين كل محرف والآخر، وهذا الوسيط هو عدد صحيح أو عشري يمثل عدد الثواني للتوقف مؤقتًا، فمثلًا سينتظر الاستدعاء <code>pyautogui.write('Hello, world!', 0.25)‎</code> ربع ثانية بعد كتابة الحرف H، وربع ثانية أخرى بعد كتابة الحرف e، وإلخ. قد يكون تأثير الآلة الكاتبة التدريجي مفيدًا للتطبيقات الأبطأ التي لا يمكنها معالجة ضغطات المفاتيح بسرعة كافية للتماشي مع الوحدة PyAutoGUI. ستحاكي أيضًا الوحدة PyAutoGUI الضغط باستمرار على مفتاح <code>SHIFT</code> آليًا بالنسبة للمحارف A أو ! .
</p>

<h3 id="-20">
	أسماء المفاتيح
</h3>

<p>
	لا يُعَد تمثيل كافة المفاتيح باستخدام محارف نصية مفردة أمرًا سهلًا مثل تمثيل المفتاح SHIFT أو مفتاح السهم الأيسر بمحرف واحد، لذا تمثل وحدة PyAutoGUI مفاتيح لوحة المفاتيح هذه بقيم سلاسل نصية قصيرة، فمثلًا نمثّل مفتاح ESC باستخدام السلسلة النصية <code>'esc'</code> و نمثّل مفتاح ENTER باستخدام السلسلة النصية <code>'enter'</code>.
</p>

<p>
	يمكن تمرير قائمة بالسلاسل النصية لهذه المفاتيح إلى الدالة <code>write()‎</code> بدلًا من تمرير وسيط سلسلة نصية واحدة، فمثلًا يضغط الاستدعاء التالي على المفتاح A ثم على المفتاح B ثم على مفتاح السهم الأيسر مرتين ويضغط أخيرًا على المفتاحين X و Y:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_48" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">write</span><span class="pun">([</span><span class="str">'a'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'b'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'left'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'left'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'X'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Y'</span><span class="pun">])</span></pre>

<p>
	يؤدي الضغط على مفتاح السهم الأيسر إلى تحريك مؤشر لوحة المفاتيح، لذا سينتج عن ذلك الخرج XYab. يوضّح الجدول الآتي السلاسل النصية لمفاتيح لوحة المفاتيح الخاصة بوحدة PyAutoGUI، والتي يمكنك تمريرها إلى الدالة <code>write()‎</code> لمحاكاة الضغط على أيّ مجموعة من المفاتيح. يمكنك أيضًا الاطلاع على قائمة <code>pyautogui.KEYBOARD_KEYS</code> لرؤية جميع السلاسل النصية لمفاتيح لوحة المفاتيح المحتملة التي ستقبلها وحدة PyAutoGUI. تشير السلسلة النصية <code>'shift'</code> إلى مفتاح SHIFT الأيسر وهي تكافئ السلسلة النصية <code>'shiftleft'</code>، وينطبق الأمر نفسه على السلاسل النصية <code>'ctrl'</code> و <code>'alt'</code> و <code>'win'</code> التي تشير جميعها إلى مفتاح الجهة اليسرى من لوحة المفاتيح.
</p>

<p>
	يوضح الجدول التالي قيم سمات <code>PyKeyboard</code>:
</p>

<table>
	<thead>
		<tr>
			<th>
				السلسلة النصية لمفتاح لوحة المفاتيح
			</th>
			<th>
				معناها
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				<code>'a'</code> و <code>'b'</code> و <code>'c'</code> و <code>'A'</code> و <code>'B'</code> و <code>'C'</code> و <code>'1'</code> و <code>'2'</code> و <code>'3'</code> و <code>'!'</code> و <code>'@'</code> و <code>'#'</code> وإلخ
			</td>
			<td>
				مفاتيح المحارف المفردة
			</td>
		</tr>
		<tr>
			<td>
				<code>'enter'</code> (أو <code>'return'</code> أو <code>'‎\n'</code>)
			</td>
			<td>
				مفتاح ENTER
			</td>
		</tr>
		<tr>
			<td>
				<code>'esc'</code>
			</td>
			<td>
				مفتاح ESC
			</td>
		</tr>
		<tr>
			<td>
				<code>'shiftleft'</code> و <code>'shiftright'</code>
			</td>
			<td>
				مفتاحا SHIFT الأيسر والأيمن
			</td>
		</tr>
		<tr>
			<td>
				<code>'altleft'</code> و <code>'altright'</code>
			</td>
			<td>
				مفتاحا ALT الأيسر والأيمن
			</td>
		</tr>
		<tr>
			<td>
				<code>'ctrlleft'</code> و <code>'ctrlright'</code>
			</td>
			<td>
				مفتاحا CTRL الأيسر والأيمن
			</td>
		</tr>
		<tr>
			<td>
				<code>'tab'</code> (أو <code>'‎\t'</code>)
			</td>
			<td>
				مفتاح TAB
			</td>
		</tr>
		<tr>
			<td>
				<code>'backspace'</code> و <code>'delete'</code>
			</td>
			<td>
				مفتاح BACKSPACE ومفتاح DELETE
			</td>
		</tr>
		<tr>
			<td>
				<code>'pageup'</code> و <code>'pagedown'</code>
			</td>
			<td>
				مفتاح PAGE UP ومفتاح PAGE DOWN
			</td>
		</tr>
		<tr>
			<td>
				<code>'home'</code> و <code>'end'</code>
			</td>
			<td>
				مفتاح HOME ومفتاح END
			</td>
		</tr>
		<tr>
			<td>
				<code>'up'</code> و <code>'down'</code> و <code>'left'</code> و <code>'right'</code>
			</td>
			<td>
				مفاتيح الأسهم للأعلى وللأسفل وإلى اليسار وإلى اليمين
			</td>
		</tr>
		<tr>
			<td>
				<code>'f1'</code> و <code>'f2'</code> و <code>'f3'</code> وإلخ
			</td>
			<td>
				المفاتيح من F1 إلى F12
			</td>
		</tr>
		<tr>
			<td>
				<code>'volumemute'</code> و <code>'volumedown'</code> و <code>'volumeup'</code>
			</td>
			<td>
				مفاتيح كتم الصوت وخفض مستوى الصوت ورفع مستوى الصوت. لا تحتوي بعض لوحات المفاتيح على هذه المفاتيح، ولكن سيبقى نظام تشغيل حاسوبك قادرًا على فهم محاكاة هذه الضغطات للمفاتيح
			</td>
		</tr>
		<tr>
			<td>
				<code>'pause'</code>
			</td>
			<td>
				مفتاح PAUSE
			</td>
		</tr>
		<tr>
			<td>
				<code>'capslock'</code> و <code>'numlock'</code> و <code>'scrolllock'</code>
			</td>
			<td>
				مفتاح CAPS LOCK ومفتاح NUM LOCK ومفتاح SCROLL LOCK
			</td>
		</tr>
		<tr>
			<td>
				<code>'insert'</code>
			</td>
			<td>
				مفتاح INS أو INSERT
			</td>
		</tr>
		<tr>
			<td>
				<code>'printscreen'</code>
			</td>
			<td>
				مفتاح PRTSC أو PRINT SCREEN
			</td>
		</tr>
		<tr>
			<td>
				<code>'winleft'</code> و <code>'winright'</code>
			</td>
			<td>
				مفتاح WIN الأيسر والأيمن على نظام ويندوز
			</td>
		</tr>
		<tr>
			<td>
				<code>'command'</code>
			</td>
			<td>
				مفتاح Command على نظام ماك ‎ <img alt="01 000064" class="ipsImage ipsImage_thumbnailed" data-fileid="157699" data-unique="jekua3n8k" src="https://academy.hsoub.com/uploads/monthly_2024_09/01_000064.jpg.a5cf06c5fc868ce86d099e5e65a17d4c.jpg">
			</td>
		</tr>
		<tr>
			<td>
				<code>'option'</code>
			</td>
			<td>
				مفتاح OPTION على نظام ماك
			</td>
		</tr>
	</tbody>
</table>

<h3 id="-21">
	الضغط على لوحة المفاتيح وتحريرها
</h3>

<p>
	ترسل الدالتان <code>pyautogui.keyDown()‎</code> و <code>pyautogui.keyUp()‎</code> ضغطات مفاتيح افتراضية وتحريرها إلى الحاسوب مثل الدالتين <code>mouseDown()‎</code> و <code>mouseUp()‎</code>، ونمرّر إلى هاتين الدالتين سلسلة نصية لمفاتيح لوحة المفاتيح (اطّلع على الجدول السابق) كوسيطٍ لها. توفّر وحدة PyAutoGUI الدالة <code>pyautogui.press()‎</code> التي تستدعي هاتين الدالتين لمحاكاة ضغطة كاملة على المفاتيح.
</p>

<p>
	شغّل الشيفرة البرمجية التالية التي ستكتب محرف إشارة الدولار $ الذي نحصل عليه من خلال الضغط على مفتاح SHIFT مع الضغط على الرقم 4:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_50" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">keyDown</span><span class="pun">(</span><span class="str">'shift'</span><span class="pun">);</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">press</span><span class="pun">(</span><span class="str">'4'</span><span class="pun">);</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">keyUp</span><span class="pun">(</span><span class="str">'shift'</span><span class="pun">)</span></pre>

<p>
	تضغط التعليمة السابقة على مفتاح SHIFT، ثم تضغط على (وتحرر) الرقم 4، ثم تحرّر مفتاح SHIFT. إذا أردتَ كتابة سلسلة نصية في حقل نصي، فستكون الدالة <code>write()‎</code> أكثر ملاءمة، ولكن ستكون الدالة <code>press()‎</code> الطريقة الأبسط بالنسبة للتطبيقات التي تأخذ أوامرًا ذات مفتاح واحد.
</p>

<h3 id="hotkey">
	مجموعات مفاتيح التشغيل السريع Hotkey أو الاختصارات
</h3>

<p>
	مفاتيح التشغيل السريع أو الاختصارات هي مجموعة من الضغطات على المفاتيح لاستدعاء بعض وظائف التطبيق، فمفتاح التشغيل السريع الشائع لنسخ تحديدٍ مثلًا هو CTRL-C في نظامي تشغيل ويندوز ولينكس أو ‎<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="157699" href="https://academy.hsoub.com/uploads/monthly_2024_09/01_000064.jpg.a5cf06c5fc868ce86d099e5e65a17d4c.jpg" rel=""><img alt="01 000064" class="ipsImage ipsImage_thumbnailed" data-fileid="157699" data-unique="jekua3n8k" src="https://academy.hsoub.com/uploads/monthly_2024_09/01_000064.jpg.a5cf06c5fc868ce86d099e5e65a17d4c.jpg"> </a>-C في نظام تشغيل ماك. يضغط المستخدم مع الاستمرار على مفتاح CTRL، ثم يضغط على المفتاح C، ثم يحرّر المفتاحين C و CTRL، حيث يمكننا تطبيق ذلك باستخدام الدالتين <code>keyDown()‎</code> و <code>keyUp()‎</code> الخاصتين بالوحدة PyAutoGUI من خلال إدخال ما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_52" style=""><span class="pln">pyautogui</span><span class="pun">.</span><span class="pln">keyDown</span><span class="pun">(</span><span class="str">'ctrl'</span><span class="pun">)</span><span class="pln">
pyautogui</span><span class="pun">.</span><span class="pln">keyDown</span><span class="pun">(</span><span class="str">'c'</span><span class="pun">)</span><span class="pln">
pyautogui</span><span class="pun">.</span><span class="pln">keyUp</span><span class="pun">(</span><span class="str">'c'</span><span class="pun">)</span><span class="pln">
pyautogui</span><span class="pun">.</span><span class="pln">keyUp</span><span class="pun">(</span><span class="str">'ctrl'</span><span class="pun">)</span></pre>

<p>
	يُعَد ذلك معقدًا إلى حدٍ ما، لذا استخدم الدالة <code>pyautogui.hotkey()‎</code> بدلًا من ذلك، حيث تأخذ هذه الدالة عدة وسطاء تمثّل السلسلة النصية لمفاتيح لوحة المفاتيح، وتضغط عليها بالترتيب، ثم تحررها بالترتيب العكسي، إذ ستكون الشيفرة البرمجية الخاصية بمثال CTRL-C ببساطة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_54" style=""><span class="pln">pyautogui</span><span class="pun">.</span><span class="pln">hotkey</span><span class="pun">(</span><span class="str">'ctrl'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'c'</span><span class="pun">)</span></pre>

<p>
	تُعَد هذه الدالة مفيدة خاصةً لمجموعات مفاتيح التشغيل السريع الأكبر حجمًا، فمثلًا تعرض مجموعة مفاتيح التشغيل السريع Ctrl-Alt-Shift-S لوحة الأنماط Style في برنامج وورد Word، حيث يمكنك استخدام الاستدعاء <code>hotkey('ctrl', 'alt', 'shift', 's')‎</code> فقط بدلًا من إجراء ثمانية استدعاءات لدوال مختلفة (أربعة استدعاءات للدالة <code>keyDown()‎</code> وأربعة استدعاءات للدالة <code>keyUp()‎</code>).
</p>

<h2 id="-22">
	إعداد سكربتات أتمتة واجهة المستخدم الرسومية
</h2>

<p>
	تُعَد سكربتات أتمتة واجهة المستخدم الرسومية طريقةً رائعة لأتمتة المهام المملة، ولكنها قد تكون صعبة التحقيق، حيث إذا كان هناك نافذة في مكان خاطئ على سطح المكتب أو ظهرت بعض النوافذ المنبثقة بطريقة غير متوقعة، فقد ينقر السكربت الخاص بك على الأشياء الخاطئة على الشاشة، لذا إليك بعض النصائح لإعداد سكربتات أتمتة واجهة المستخدم الرسومية:
</p>

<ul>
	<li>
		استخدم دقة الشاشة نفسها في كل مرة تشغّل فيها السكربت حتى لا يتغير موضع النوافذ.
	</li>
	<li>
		يجب تكبير نافذة التطبيق التي ينقر عليها السكربت الخاص بك بحيث تكون الأزرار والقوائم في المكان نفسه في كل مرة تشغّل فيها السكربت.
	</li>
	<li>
		أضِف فترات توقف كافية أثناء انتظار تحميل المحتوى، إذ لا تريد أن يبدأ السكربت بالنقر قبل أن يصبح التطبيق جاهزًا.
	</li>
	<li>
		استخدم الدالة <code>locateOnScreen()‎</code> للعثور على الأزرار والقوائم التي يمكنك النقر عليها بدلًا من الاعتماد على إحداثيات XY. إن لم يتمكّن السكربت الخاص بك من العثور على الشيء الذي يريد النقر عليه، فأوقِف البرنامج بدلًا من السماح له بمواصلة النقر عشوائيًا.
	</li>
	<li>
		استخدم الدالة <code>getWindowsWithTitle()‎</code> للتأكّد من وجود نافذة التطبيق التي تعتقد أن السكربت الخاص بك ينقر عليها، واستخدم التابع <code>activate()‎</code> لوضع تلك النافذة في المقدمة.
	</li>
	<li>
		استخدم الوحدة <code>logging</code> التي تحدثنا عنها في مقالٍ سابق للاحتفاظ بملفٍ يسجّل ما فعله السكربت الخاص بك، وبالتالي إذا أوقفتَ السكربت في منتصف العملية، فيمكنك تعديله للمتابعة من مكان توقف هذا السكربت.
	</li>
	<li>
		أضِف أكبر عدد ممكن من عمليات التحقق إلى السكربت الخاص بك، فمثلًا يمكن أن يفشل السكربت إذا ظهرت نافذة منبثقة غير متوقعة أو إذا فقدَ حاسوبك اتصاله بالإنترنت.
	</li>
	<li>
		قد ترغب في الإشراف على السكربت عندما يبدأ لأول مرة للتأكد من أنه يعمل بصورة صحيحة.
	</li>
</ul>

<p>
	قد ترغب أيضًا في التوقف مؤقتًا في بداية السكربت الخاص بك حتى يتمكّن المستخدم من إعداد النافذة التي سينقر عليها السكربت، حيث تحتوي وحدة PyAutoGUI على الدالة <code>sleep()‎</code> التي تعمل بطريقة مماثلة للدالة <code>time.sleep()‎</code>، ولكنها توفّر عليك الاضطرار إلى إضافة التعليمة <code>import time</code> إلى السكربتات الخاصة بك، وتوجد أيضًا الدالة <code>countdown()‎</code> التي تطبع أرقام العد التنازلي لمنح المستخدم إشارة مرئية إلى أن السكربت سيستأنف عمله قريبًا. أدخِل مثلًا ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_56" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> pyautogui
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">sleep</span><span class="pun">(</span><span class="lit">3</span><span class="pun">)</span><span class="pln"> </span><span class="com"># إيقاف البرنامج مؤقتًا لمدة 3 ثوانٍ</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">countdown</span><span class="pun">(</span><span class="lit">10</span><span class="pun">)</span><span class="pln"> </span><span class="com"># العد التنازلي لمدة 10 ثوانٍ</span><span class="pln">
</span><span class="lit">10</span><span class="pln"> </span><span class="lit">9</span><span class="pln"> </span><span class="lit">8</span><span class="pln"> </span><span class="lit">7</span><span class="pln"> </span><span class="lit">6</span><span class="pln"> </span><span class="lit">5</span><span class="pln"> </span><span class="lit">4</span><span class="pln"> </span><span class="lit">3</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Starting in '</span><span class="pun">,</span><span class="pln"> end</span><span class="pun">=</span><span class="str">''</span><span class="pun">);</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">countdown</span><span class="pun">(</span><span class="lit">3</span><span class="pun">)</span><span class="pln">
</span><span class="typ">Starting</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> </span><span class="lit">3</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> </span><span class="lit">1</span></pre>

<p>
	يمكن أن تساعد هذه النصائح في جعل سكربتات أتمتة واجهة المستخدم الرسومية أسهل في الاستخدام وأكثر قدرة على الاستعادة من الظروف غير المتوقعة.
</p>

<h2 id="pyautogui-1">
	مراجعة لدوال وحدة PyAutoGUI
</h2>

<p>
	يغطي هذا المقال العديد من الدوال المختلفة، لذا سنوضّح فيما يلي مرجعًا موجزًا سريعًا لهذه الدوال:
</p>

<ul>
	<li>
		<code>moveTo(x, y)‎</code>: تحرك مؤشر الفأرة إلى إحداثيات <code>x</code> و <code>y</code> المُحدَّدة.
	</li>
	<li>
		<code>move(xOffset, yOffset)‎</code>: تحرك مؤشر الفأرة بالنسبة إلى موضعه الحالي.
	</li>
	<li>
		<code>dragTo(x, y)‎</code>: تحرك مؤشر الفأرة أثناء الضغط المستمر على زر الفأرة الأيسر.
	</li>
	<li>
		<code>drag(xOffset, yOffset)‎</code>: تحرّك مؤشر الفأرة نسبة إلى موضعه الحالي أثناء الضغط المستمر على زر الفأرة الأيسر.
	</li>
	<li>
		<code>click(x, y, button)‎</code>: تحاكي النقر (بزر الفأرة الأيسر افتراضيًا).
	</li>
	<li>
		<code>rightClick()‎</code>: تحاكي النقر بالزر الأيمن.
	</li>
	<li>
		<code>middleClick()‎</code>: تحاكي النقر بالزر الأوسط.
	</li>
	<li>
		<code>doubleClick()‎</code>: تحاكي النقر المزدوج على الزر الأيسر.
	</li>
	<li>
		<code>mouseDown(x, y, button)‎</code>: تحاكي الضغط على الزر المُحدَّد في الموضع <code>x, y</code>.
	</li>
	<li>
		<code>mouseUp(x, y, button)‎</code>: تحاكي تحرير الزر المُحدَّد في الموضع <code>x, y</code>.
	</li>
	<li>
		<code>scroll(units)‎</code>: تحاكي عجلة التمرير في الفأرة، حيث نمرّر وسيطًا موجبًا للتمرير للأعلى، ونمرّر وسيطًا سالبًا للتمرير للأسفل.
	</li>
	<li>
		<code>write(message)‎</code>: تكتب المحارف الموجودة في سلسلة الرسالة النصية المُحدَّدة.
	</li>
	<li>
		<code>write([key1, key2, key3])‎</code>: تكتب سلاسل نصية لمفاتيح لوحة المفاتيح المُحدَّدة.
	</li>
	<li>
		<code>press(key)‎</code>: تضغط على السلسلة النصية لمفتاحٍ محدَّد من لوحة المفاتيح.
	</li>
	<li>
		<code>keyDown(key)‎</code>: تحاكي الضغط على مفتاح لوحة المفاتيح المُحدَّد.
	</li>
	<li>
		<code>keyUp(key)‎</code>: تحاكي تحرير مفتاح لوحة المفاتيح المُحدَّد.
	</li>
	<li>
		<code>hotkey([key1, key2, key3])‎</code>: تحاكي الضغط على السلاسل النصية لمفاتيح لوحة المفاتيح المُحدَّدة بالترتيب ثم تحرّرها بترتيب عكسي.
	</li>
	<li>
		<code>screenshot()‎</code>: تعيد لقطة الشاشة بوصفها كائن <code>Image</code>. اطّلع على المقال السابق للحصول على معلومات حول كائنات <code>Image</code>.
	</li>
	<li>
		<code>getActiveWindow()‎</code> و <code>getAllWindows()‎</code> و <code>getWindowsAt()‎</code> و <code>getWindowsWithTitle()‎</code>: تعيد هذه الدوال كائنات <code>Window</code> التي يمكنها تغيير حجم نوافذ التطبيقات وإعادة تموضعها على سطح المكتب.
	</li>
	<li>
		<code>getAllTitles()‎</code>: تعيد قائمةً بالسلاسل النصية لشريط عنوان كلّ نافذةٍ على سطح المكتب.
	</li>
</ul>

<h2 id="captcha">
	اختبارات كابتشا Captcha وأخلاقيات استخدام الحواسيب
</h2>

<p>
	تُعَد اختبارات كابتشا Captcha اختصارًا للعبارة الإنجليزية "Completely Automated Public Turing test to tell Computers and Humans Apart" أو "اختبار تورينج العام الآلي بالكامل للتمييز بين الحواسيب والبشر"، وهي الاختبارات الصغيرة التي تطلب منك كتابة حروف موجودة في صورة غير واضحة أو النقر على صور صنابير إطفاء الحرائق مثلًا. يسهُل على البشر اجتياز هذه الاختبارات، ولكن يكاد يكون من المستحيل على البرامج حلها بالرغم من أننا نجدها مزعجة. يمكنك أن ترى بعد قراءة هذا المقال مدى سهولة كتابة سكربت يمكنه التسجيل في مليارات حسابات البريد الإلكتروني المجانية مثلًا أو إغراق المستخدمين برسائل مزعجة، لذا تعمل اختبارات كابتشا على تخفيف ذلك من خلال المطالبة بخطوة لا يمكن إلا للبشر اجتيازها.
</p>

<p>
	لا تطبق جميع مواقع الويب اختبارات كابتشا، وقد تكون عرضةً لإساءة الاستخدام من المبرمجين غير الأخلاقيين، إذ يُعَد تعلّم البرمجة مهارة قوية ومهمة، ولكن قد تميل إلى إساءة استخدام هذه القوة لتحقيق مكاسب شخصية أو حتى لمجرد التفاخر، فلا يُعَد الباب المفتوح مبررًا للتعدّي على ممتلكات الآخرين، لذا تقع مسؤولية برامجك على عاتقك الشخصي بوصفك مبرمجًا. لا يُعَد التحايل على الأنظمة لإحداث ضررٍ أو انتهاك الخصوصية أو الحصول على ميزة غير عادلة معيارًا للذكاء، لذا نأمل أن تركز على عملك دون الضرر بالآخرين.
</p>

<h2 id="-23">
	تطبيق عملي: ملء الاستمارات آليًا
</h2>

<p>
	يُعَد ملء الاستمارات مهمةً روتينية مملةً جدًا، إذًا لنفترض مثلًا أن لديك كمية هائلة من البيانات في جدول بيانات، ويجب أن تعيد كتابتها في واجهة استمارة تطبيق آخر دون وجود شخص آخر لإنجاز ذلك نيابةً عنك. تحتوي بعض التطبيقات على ميزة الاستيراد التي تسمح لك برفع جدول بيانات يحتوي على المعلومات، ولكن قد لا توجد طريقة أخرى سوى النقر والكتابة دون اهتمام لساعات متواصلة في بعض الأحيان، لذا لنحاول إيجاد وسيلة لأتمتة هذه المهمة المملة.
</p>

<p>
	استمارة هذا المشروع هي استمارة على <a href="https://academy.hsoub.com/apps/productivity/google-drive/google-docs/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%85%D8%B3%D8%AA%D9%86%D8%AF%D8%A7%D8%AA-%D8%AC%D9%88%D8%AC%D9%84-google-docs-r281/" rel="">مستندات جوجل Google Docs</a>، والتي يمكنك العثور عليها على <a href="https://autbor.com/form" rel="external nofollow">autbor.com</a>، وتبدو كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="157698" href="https://academy.hsoub.com/uploads/monthly_2024_09/08_000096.jpg.3d92efff837342c52fe894b20b4d1b7b.jpg" rel=""><img alt="08 000096" class="ipsImage ipsImage_thumbnailed" data-fileid="157698" data-unique="x2s4j5ssa" src="https://academy.hsoub.com/uploads/monthly_2024_09/08_000096.thumb.jpg.3583a5d78b64ac0f06260a920f1cae79.jpg"> </a>
</p>

<p style="text-align: center;">
	الاستمارة المستخدمَة في هذا المشروع
</p>

<p>
	إليك الخطوات العامة لما يجب أن يفعله برنامجك:
</p>

<ol>
	<li>
		النقر على الحقل النصي الأول من الاستمارة.
	</li>
	<li>
		التنقل عبر الاستمارة، وكتابة المعلومات في كل حقل.
	</li>
	<li>
		النقر على زر الإرسال Submit.
	</li>
	<li>
		تكرار العملية مع المجموعة التالية من البيانات.
	</li>
</ol>

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

<ol>
	<li>
		استدعاء الدالة <code>pyautogui.click()‎</code> للنقر على الاستمارة وزر الإرسال Submit.
	</li>
	<li>
		استدعاء الدالة <code>pyautogui.write()‎</code> لإدخال النص في الحقول.
	</li>
	<li>
		معالجة الاستثناء <code>KeyboardInterrupt</code> حتى يتمكّن المستخدم من الضغط على الاختصار CTRL-C للإنهاء.
	</li>
</ol>

<p>
	افتح نافذة جديدة في محرّرك لإنشاء ملف جديد واحفظه بالاسم formFiller.py.
</p>

<h3 id="-24">
	الخطوة الأولى: معرفة الخطوات ملء الاستمارة
</h3>

<p>
	يجب أولًا معرفة ضغطات المفاتيح ونقرات الفأرة التي ستملأ الاستمارة قبل كتابة الشيفرة البرمجية. يمكن أن يساعدك التطبيق الذي يُشغّله استدعاء الدالة <code>pyautogui.mouseInfo()‎</code> في معرفة إحداثيات الفأرة المُحدَّدة، ويجب معرفة إحداثيات الحقل النصي الأول فقط، ثم يمكنك الضغط على مفتاح TAB لنقل التركيز إلى الحقل التالي بعد النقر على الحقل الأول، مما يوفّر عليك الاضطرار إلى معرفة إحداثيات x و y للنقر على كل حقل.
</p>

<p>
	إليك خطوات إدخال البيانات في الاستمارة:
</p>

<ol>
	<li>
		انقل تركيز لوحة المفاتيح على حقل الاسم Name بحيث يؤدي الضغط على المفاتيح إلى كتابة نص في الحقل.
	</li>
	<li>
		اكتب اسمًا، ثم اضغط على مفتاح TAB.
	</li>
	<li>
		اكتب خوفك الأكبر في الحقل Greatest Fear ثم اضغط على مفتاح TAB.
	</li>
	<li>
		اضغط على مفتاح السهم للأسفل عددًا صحيحًا من المرات لتحديد مصدر قواك السحرية Wizard Power Source، إذ ستضغط مرة واحدة لخيار العصا السحرية wand ومرتين لخيار التعويذة amulet وثلاث مرات لخيار الكرة البلورية crystal ball وأربع مرات لخيار المال money، ثم اضغط على مفتاح TAB. لاحظ أنه يجب أن تضغط على مفتاح السهم للأسفل مرة أخرى لكل خيار في نظام ماك macOS، وقد تحتاج إلى الضغط على مفتاح ENTER أيضًا بالنسبة لبعض المتصفحات.
	</li>
	<li>
		اضغط على مفتاح السهم الأيمن لتحديد إجابة سؤال RoboCop، واضغط عليه مرة واحدة للخيار 2 أو مرتين للخيار 3 أو ثلاث مرات للخيار 4 أو أربع مرات للخيار 5 أو اضغط على مفتاح المسافة لتحديد الخيار 1 المُحدَّد افتراضيًا، ثم اضغط على مفتاح TAB.
	</li>
	<li>
		اكتب تعليقًا إضافيًا في الحقل Additional Comments، ثم اضغط على مفتاح TAB.
	</li>
	<li>
		اضغط على مفتاح ENTER للنقر على زر الإرسال Submit.
	</li>
	<li>
		سينقلك المتصفح إلى صفحة أخرى بعد إرسال الاستمارة، حيث يجب اتباع رابط في هذه الصفحة للعودة إلى صفحة الاستمارة.
	</li>
</ol>

<p>
	قد تعمل المتصفحات الأخرى على أنظمة تشغيل مختلفة بطريقة مختلفة قليلًا عن الخطوات التي ذكرناها، لذا تأكد من أن هذه المجموعات من ضغطات المفاتيح تعمل على حاسوبك قبل تشغيل البرنامج.
</p>

<h3 id="-25">
	الخطوة الثانية: إعداد الإحداثيات
</h3>

<p>
	حمّل مثال الاستمارة التي نزّلتها (الشكل السابق) في المتصفح من خلال الانتقال إلى الرابط <a href="https://autbor.com/form" rel="external nofollow">autbor.com</a>، واجعل شيفرتك البرمجية كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_61" style=""><span class="com">#! python3</span><span class="pln">
</span><span class="com"># formFiller.py - ملء الاستمارة آليًا</span><span class="pln">

</span><span class="kwd">import</span><span class="pln"> pyautogui</span><span class="pun">,</span><span class="pln"> time

</span><span class="com"># منح المستخدم فرصةً لإنهاء السكربت</span><span class="pln">

</span><span class="com"># الانتظار حتى تحميل صفحة الاستمارة</span><span class="pln">

</span><span class="com"># ‫ملء حقل الاسم Name</span><span class="pln">

</span><span class="com"># ‫ملء حقل مخاوفك الكبرى Greatest Fear(s)‎</span><span class="pln">

</span><span class="com"># ملء حقل مصدر قواك السحرية‫ Source of Wizard Powers</span><span class="pln">

</span><span class="com"># ‫ملء الحقل RoboCop</span><span class="pln">

</span><span class="com"># ‫ملء حقل التعليقات الإضافية Additional Comments</span><span class="pln">

</span><span class="com"># الضغط على زر الإرسال‫ Submit</span><span class="pln">

</span><span class="com"># الانتظار حتى تحميل صفحة الاستمارة</span><span class="pln">

</span><span class="com"># النقر على رابط إرسال رد آخر</span></pre>

<p>
	تحتاج الآن البيانات التي تريد إدخالها فعليًا في هذه الاستمارة، حيث قد تأتي هذه البيانات في العالم الحقيقي من جدول بيانات أو ملف نص عادي أو من موقع ويب، وقد تتطلب شيفرة برمجية إضافية لتحميلها في البرنامج، ولكننا سنكتب كل هذه البيانات ضمن متغير في مثالنا، لذا أضِف ما يلي إلى برنامجك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_63" style=""><span class="com">#! python3</span><span class="pln">
</span><span class="com"># formFiller.py - ملء الاستمارة آليًا</span><span class="pln">

</span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">

formData </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[{</span><span class="str">'name'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Alice'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'fear'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'eavesdroppers'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'source'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'wand'</span><span class="pun">,</span><span class="pln">
            </span><span class="str">'robocop'</span><span class="pun">:</span><span class="pln"> </span><span class="lit">4</span><span class="pun">,</span><span class="pln"> </span><span class="str">'comments'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Tell Bob I said hi.'</span><span class="pun">},</span><span class="pln">
            </span><span class="pun">{</span><span class="str">'name'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Bob'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'fear'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'bees'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'source'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'amulet'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'robocop'</span><span class="pun">:</span><span class="pln"> </span><span class="lit">4</span><span class="pun">,</span><span class="pln">
            </span><span class="str">'comments'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'n/a'</span><span class="pun">},</span><span class="pln">
            </span><span class="pun">{</span><span class="str">'name'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Carol'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'fear'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'puppets'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'source'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'crystal ball'</span><span class="pun">,</span><span class="pln">
            </span><span class="str">'robocop'</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="str">'comments'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Please take the puppets out of the
            break room.'</span><span class="pun">},</span><span class="pln">
            </span><span class="pun">{</span><span class="str">'name'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Alex Murphy'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'fear'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'ED-209'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'source'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'money'</span><span class="pun">,</span><span class="pln">
            </span><span class="str">'robocop'</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5</span><span class="pun">,</span><span class="pln"> </span><span class="str">'comments'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Protect the innocent. Serve the public
            trust. Uphold the law.'</span><span class="pun">},</span><span class="pln">
           </span><span class="pun">]</span><span class="pln">

</span><span class="pun">--</span><span class="pln">snip</span><span class="pun">–</span></pre>

<p>
	تحتوي القائمة <code>formData</code> على أربعة قواميس لأربعة أسماء مختلفة، ويحتوي كل قاموس على أسماء الحقول النصية كمفاتيحٍ له والردود كقيمٍ له. أخيرًا، نضبط المتغير <code>PAUSE</code> الخاص بوحدة PyAutoGUI للانتظار لمدة نصف ثانية بعد كل استدعاء دالة، ونذكّر المستخدم بالنقر على المتصفح لجعله النافذة النشطة. أضِف ما يلي إلى برنامجك بعد تعليمة إسناد قيمٍ إلى القائمة <code>formData</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_65" style=""><span class="pln">pyautogui</span><span class="pun">.</span><span class="pln">PAUSE </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0.5</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Ensure that the browser window is active and the form is loaded!'</span><span class="pun">)</span></pre>

<h3 id="-26">
	الخطوة الثالثة: البدء في كتابة البيانات
</h3>

<p>
	سنكرّر<a href="https://wiki.hsoub.com/Python#for" rel="external"> حلقة for</a> على كلٍّ من القواميس الموجودة في قائمة <code>formData</code>، ونمرّر القيم الموجودة في القاموس إلى دوال وحدة PyAutoGUI التي ستكتب فعليًا في الحقول النصية.
</p>

<p>
	أضِف الشيفرة البرمجية التالية إلى برنامجك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_67" style=""><span class="com">#! python3</span><span class="pln">
</span><span class="com"># formFiller.py - ملء الاستمارة آليًا</span><span class="pln">

</span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">

</span><span class="kwd">for</span><span class="pln"> person </span><span class="kwd">in</span><span class="pln"> formData</span><span class="pun">:</span><span class="pln">
    </span><span class="com"># منح المستخدم فرصةً لإنهاء السكربت</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'&gt;&gt;&gt; 5-SECOND PAUSE TO LET USER PRESS CTRL-C &lt;&lt;&lt;'</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">➊</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">sleep</span><span class="pun">(</span><span class="lit">5</span><span class="pun">)</span><span class="pln">

</span><span class="pun">--</span><span class="pln">snip</span><span class="pun">–</span></pre>

<p>
	يحتوي السكربت على توقف مؤقت لمدة خمس ثوانٍ ➊ كميزة أمانٍ صغيرة، مما يمنح المستخدم فرصةً للضغط على Ctrl-C (أو تحريك مؤشر الفأرة إلى الزاوية العلوية اليسرى من الشاشة لرفع استثناء <code>FailSafeException</code>) لإيقاف تشغيل البرنامج في حالة أنه يعمل شيئًا غير متوقع. أضِف ما يلي بعد الشيفرة البرمجية التي تنتظر إعطاء الصفحة وقتًا للتحميل:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_69" style=""><span class="com">#! python3</span><span class="pln">
</span><span class="com"># formFiller.py - ملء الاستمارة آليًا</span><span class="pln">

</span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">

  </span><span class="pun">➊</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Entering %s info...'</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> </span><span class="pun">(</span><span class="pln">person</span><span class="pun">[</span><span class="str">'name'</span><span class="pun">]))</span><span class="pln">
  </span><span class="pun">➋</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">write</span><span class="pun">([</span><span class="str">'\t'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'\t'</span><span class="pun">])</span><span class="pln">

     </span><span class="com"># ‫ملء حقل الاسم Name</span><span class="pln">
  </span><span class="pun">➌</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">write</span><span class="pun">(</span><span class="pln">person</span><span class="pun">[</span><span class="str">'name'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">'\t'</span><span class="pun">)</span><span class="pln">

     </span><span class="com"># ملء حقل مخاوفك الكبرى‫ Greatest Fear(s)‎</span><span class="pln">
  </span><span class="pun">➍</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">write</span><span class="pun">(</span><span class="pln">person</span><span class="pun">[</span><span class="str">'fear'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">'\t'</span><span class="pun">)</span><span class="pln">

</span><span class="pun">--</span><span class="pln">snip</span><span class="pun">–</span></pre>

<p>
	نضيف استدعاء الدالة <code>print()‎</code> من حينٍ لآخر لعرض حالة البرنامج في نافذته الطرفية لإعلام المستخدم بما يحدث ➊.
</p>

<p>
	حصلت الاستمارة على وقتها الكافي للتحميل، لذا نستدعي الدالة <code>pyautogui.write(['\t', '\t'])‎</code> للضغط على مفتاح TAB مرتين والتركيز على حقل الاسم Name ➋، ثم نستدعي الدالة <code>write()‎</code> مرة أخرى لإدخال السلسلة النصية في <code>person['name']‎</code> ➌. نضيف المحرف <code>‎'\t'‎</code> إلى نهاية السلسلة النصية التي نمرّرها إلى الدالة <code>write()‎</code> لمحاكاة الضغط على مفتاح TAB، مما ينقل تركيز لوحة المفاتيح إلى الحقل التالي وهو Greatest Fear(s)‎. يؤدي استدعاءٌ آخر للدالة <code>write()‎</code> إلى كتابة السلسلة النصية في <code>person['fear']‎</code> ضمن هذا الحقل ثم ينتقل إلى الحقل التالي في الاستمارة ➍.
</p>

<h3 id="-27">
	الخطوة الرابعة: التعامل مع قوائم التحديد وأزرار الاختيار
</h3>

<p>
	تُعَد القائمة المنسدلة لسؤال "القوى السحرية Wizard Powers" وأزرار الاختيار الخاصة بحقل RoboCop أصعب في التعامل من الحقول النصية، حيث يمكنك النقر على هذه الخيارات باستخدام الفأرة من خلال معرفة إحداثيات x و y لكل خيار ممكن، ولكن من الأسهل استخدام مفاتيح الأسهم في لوحة المفاتيح لإجراء التحديد بدلًا من ذلك.
</p>

<p>
	أضِف ما يلي إلى برنامجك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_71" style=""><span class="com">#! python3</span><span class="pln">
</span><span class="com"># formFiller.py - ملء الاستمارة آليًا</span><span class="pln">

</span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">

     </span><span class="com"># ملء حقل مصدر قواك السحرية‫ Source of Wizard Powers</span><span class="pln">
  </span><span class="pun">➊</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> person</span><span class="pun">[</span><span class="str">'source'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="str">'wand'</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">➋</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">write</span><span class="pun">([</span><span class="str">'down'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'\t'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.5</span><span class="pun">)</span><span class="pln">
     </span><span class="kwd">elif</span><span class="pln"> person</span><span class="pun">[</span><span class="str">'source'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="str">'amulet'</span><span class="pun">:</span><span class="pln">
         pyautogui</span><span class="pun">.</span><span class="pln">write</span><span class="pun">([</span><span class="str">'down'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'down'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'\t'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.5</span><span class="pun">)</span><span class="pln">
     </span><span class="kwd">elif</span><span class="pln"> person</span><span class="pun">[</span><span class="str">'source'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="str">'crystal ball'</span><span class="pun">:</span><span class="pln">
         pyautogui</span><span class="pun">.</span><span class="pln">write</span><span class="pun">([</span><span class="str">'down'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'down'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'down'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'\t'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.5</span><span class="pun">)</span><span class="pln">
     </span><span class="kwd">elif</span><span class="pln"> person</span><span class="pun">[</span><span class="str">'source'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="str">'money'</span><span class="pun">:</span><span class="pln">
         pyautogui</span><span class="pun">.</span><span class="pln">write</span><span class="pun">([</span><span class="str">'down'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'down'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'down'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'down'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'\t'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.5</span><span class="pun">)</span><span class="pln">

     </span><span class="com"># ملء الحقل‫ RoboCop</span><span class="pln">
  </span><span class="pun">➌</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> person</span><span class="pun">[</span><span class="str">'robocop'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="lit">1</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">➍</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">write</span><span class="pun">([</span><span class="str">' '</span><span class="pun">,</span><span class="pln"> </span><span class="str">'\t'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.5</span><span class="pun">)</span><span class="pln">
     </span><span class="kwd">elif</span><span class="pln"> person</span><span class="pun">[</span><span class="str">'robocop'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="lit">2</span><span class="pun">:</span><span class="pln">
         pyautogui</span><span class="pun">.</span><span class="pln">write</span><span class="pun">([</span><span class="str">'right'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'\t'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.5</span><span class="pun">)</span><span class="pln">
     </span><span class="kwd">elif</span><span class="pln"> person</span><span class="pun">[</span><span class="str">'robocop'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="lit">3</span><span class="pun">:</span><span class="pln">
         pyautogui</span><span class="pun">.</span><span class="pln">write</span><span class="pun">([</span><span class="str">'right'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'right'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'\t'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.5</span><span class="pun">)</span><span class="pln">
     </span><span class="kwd">elif</span><span class="pln"> person</span><span class="pun">[</span><span class="str">'robocop'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="lit">4</span><span class="pun">:</span><span class="pln">
         pyautogui</span><span class="pun">.</span><span class="pln">write</span><span class="pun">([</span><span class="str">'right'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'right'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'right'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'\t'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.5</span><span class="pun">)</span><span class="pln">
     </span><span class="kwd">elif</span><span class="pln"> person</span><span class="pun">[</span><span class="str">'robocop'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="lit">5</span><span class="pun">:</span><span class="pln">
         pyautogui</span><span class="pun">.</span><span class="pln">write</span><span class="pun">([</span><span class="str">'right'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'right'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'right'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'right'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'\t'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.5</span><span class="pun">)</span><span class="pln">

</span><span class="pun">--</span><span class="pln">snip</span><span class="pun">–</span></pre>

<p>
	تذكر أنك كتبتَ شيفرة برمجية لمحاكاة الضغط على مفتاح TAB بعد ملء حقل المخاوف الكبرى Greatest Fear(s)‎، وبالتالي سيؤدي الضغط على مفتاح السهم للأسفل إلى الانتقال إلى العنصر التالي في قائمة التحديد بعد التركيز على القائمة المنسدلة. يجب أن يرسل برنامجك عددًا من ضغطات مفاتيح السهم للأسفل قبل الانتقال إلى الحقل التالي اعتمادًا على القيمة الموجودة في<code>person['source']‎</code>، حيث إذا كانت قيمة مفتاح <code>'source'</code> الموجودة في قاموس هذا المستخدم هي <code>'wand'</code> ➊، فسنحاكي الضغط على مفتاح السهم للأسفل مرة واحدة (لتحديد القيمة Wand) والضغط على مفتاح TAB ➋. إذا كانت القيمة الموجودة في مفتاح <code>'source'</code> هي <code>'amulet'</code>، فسنحاكي الضغط على مفتاح السهم للأسفل مرتين والضغط على مفتاح TAB، وينطبق الشيء نفسه بالنسبة للإجابات المُحتمَلة الأخرى. يضيف الوسيط 0.5 في استدعاءات الدالة <code>write()‎</code> توقفًا مؤقتًا لمدة نصف ثانية بين المفاتيح حتى لا يتحرك البرنامج بسرعة كبيرة في الاستمارة.
</p>

<p>
	يمكن تحديد أزرار الاختيار الخاصة بسؤال RoboCop باستخدام مفاتيح الأسهم إلى اليمين، أو إذا أردتَ تحديد الخيار الأول ➌، فاضغط على على شريط المسافة فقط ➍.
</p>

<h3 id="-28">
	الخطوة الخامسة: إرسال الاستمارة ثم الانتظار
</h3>

<p>
	يمكنك ملء حقل التعليقات الإضافية Additional Comments باستخدام الدالة <code>write()‎</code> من خلال تمرير <code>person['comments']‎</code> كوسيطٍ لها. يمكنك كتابة مفتاح <code>‎'\t'‎</code> إضافي لنقل تركيز لوحة المفاتيح إلى الحقل التالي أو إلى زر الإرسال Submit. سيؤدي استدعاء الدالة <code>pyautogui.press('enter')‎</code> إلى محاكاة الضغط على مفتاح ENTER وإرسال الاستمارة بعد التركيز على زر الإرسال Submit، ثم سينتظر برنامجك خمس ثوانٍ حتى تحميل الصفحة التالية.
</p>

<p>
	ستحتوي الصفحة الجديدة بعد تحميلها على رابط إرسال ردٍ آخر الذي سيوجّه المتصفح إلى صفحة استمارة جديدة فارغة، حيث خزّنا إحداثيات هذا الرابط بوصفها مجموعةً في المتغير <code>submitAnotherLink</code> في الخطوة الثانية، لذا مرّر هذه الإحداثيات إلى الدالة <code>pyautogui.click()‎</code> للنقر على هذا الرابط.
</p>

<p>
	يمكن لحلقة <code>for</code> الخارجية الخاصة بالسكربت الاستمرار إلى التكرار التالي وإدخال معلومات الشخص التالي في الاستمارة عندما تكون الاستمارة الجديدة جاهزة.
</p>

<p>
	أكمِل برنامجك بإضافة الشيفرة البرمجية التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_73" style=""><span class="com">#! python3</span><span class="pln">
</span><span class="com"># formFiller.py - ملء الاستمارة آليًا</span><span class="pln">

</span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">

    </span><span class="com"># ‫ملء حقل التعليقات الإضافية Additional Comments</span><span class="pln">
    pyautogui</span><span class="pun">.</span><span class="pln">write</span><span class="pun">(</span><span class="pln">person</span><span class="pun">[</span><span class="str">'comments'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">'\t'</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># الضغط على زر الإرسال‫ Submit من خلال الضغط على مفتاح Enter</span><span class="pln">
    time</span><span class="pun">.</span><span class="pln">sleep</span><span class="pun">(</span><span class="lit">0.5</span><span class="pun">)</span><span class="pln"> </span><span class="com"># Wait for the button to activate.</span><span class="pln">
    pyautogui</span><span class="pun">.</span><span class="pln">press</span><span class="pun">(</span><span class="str">'enter'</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># الانتظار حتى تحميل صفحة الاستمارة</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Submitted form.'</span><span class="pun">)</span><span class="pln">
    time</span><span class="pun">.</span><span class="pln">sleep</span><span class="pun">(</span><span class="lit">5</span><span class="pun">)</span><span class="pln">

    </span><span class="com"># النقر على رابط إرسال رد آخر</span><span class="pln">
    pyautogui</span><span class="pun">.</span><span class="pln">click</span><span class="pun">(</span><span class="pln">submitAnotherLink</span><span class="pun">[</span><span class="lit">0</span><span class="pun">],</span><span class="pln"> submitAnotherLink</span><span class="pun">[</span><span class="lit">1</span><span class="pun">])</span></pre>

<p>
	سيُدخِل البرنامج المعلومات الخاصة بكل شخص بعد انتهاء حلقة <code>for</code> الرئيسية، حيث يوجد في مثالنا أربعة أشخاص للإدخال فقط، ولكن إذا كان لديك 4000 شخص، فستوفر كتابة برنامج لإنجاز ذلك عليك الكثير من الوقت والكتابة.
</p>

<h2 id="-29">
	عرض مربعات الرسائل
</h2>

<p>
	تميل جميع البرامج التي كتبناها حتى الآن إلى استخدام خرجٍ يحتوي على نصٍ عادي باستخدام الدالة <code>print()‎</code> ودخلٍ يحتوي على نصٍ عادي باستخدام الدالة <code>input()‎</code>، ولكن ستستخدم برامج PyAutoGUI سطح المكتب بأكمله، إذ يُحتمَل فقدان النافذة النصية التي يعمل فيها برنامجك سواء كانت نافذة المحرّر Mu أو نافذة طرفية Terminal عندما ينقر برنامج PyAutoGUI الخاص بك ويتفاعل مع النوافذ الأخرى، مما يصعّب الحصول على الدخل والخرج من المستخدم عند إخفاء نوافذ المحرّر Mu أو نافذة الطرفية تحت نوافذ أخرى. يمكن حل هذه المشكلة باستخدام وحدة PyAutoGUI التي تقدّم مربعات رسائل منبثقة لتقديم إشعارات للمستخدم وتلقي الدخل منه، إذ توجد أربع دوال لمربعات الرسائل وهي:
</p>

<ul>
	<li>
		<code>pyautogui.alert(text)‎</code>: تعرض النص <code>text</code> وتحتوي على زر موافقة OK واحد. +<code>pyautogui.confirm(text)‎</code>: تعرض النص <code>text</code> وتحتوي على زر موافقة OK وزر إلغاء Cancel، وتعرض إما <code>'OK'</code> أو <code>'Cancel'</code> اعتمادًا على الزر الذي نقرنا عليه.
	</li>
	<li>
		<code>pyautogui.prompt(text)‎</code>: تعرض النص <code>text</code> وتحتوي على حقل نصي ليكتب المستخدم فيه، والذي تعيده كسلسلة نصية.
	</li>
	<li>
		<code>pyautogui.password(text)‎</code>: تماثل الدالة <code>prompt()‎</code>، ولكنها تعرض علامات نجمية على النص المُدخَل حتى يتمكّن المستخدم من إدخال معلومات حساسة مثل كلمة المرور.
	</li>
</ul>

<p>
	تحتوي هذه الدوال أيضًا على معاملٍ ثانٍ اختياري يقبل قيمة سلسلة نصية لاستخدامها بوصفها عنوانًا في شريط العنوان الخاص بمربع الرسالة. لن تعود هذه الدوال حتى ينقر المستخدم على الزر الموجود عليها، لذلك يمكن استخدامها أيضًا لإدخال فترات توقف مؤقت في برامج PyAutoGUI الخاصة بك. أدخِل مثلًا ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5088_75" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> pyautogui
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">alert</span><span class="pun">(</span><span class="str">'This is a message.'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Important'</span><span class="pun">)</span><span class="pln">
</span><span class="str">'OK'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">confirm</span><span class="pun">(</span><span class="str">'Do you want to continue?'</span><span class="pun">)</span><span class="pln"> </span><span class="com"># اضغط على زر الإلغاء</span><span class="pln">
</span><span class="str">'Cancel'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">prompt</span><span class="pun">(</span><span class="str">"What is your cat's name?"</span><span class="pun">)</span><span class="pln">
</span><span class="str">'Zophie'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pyautogui</span><span class="pun">.</span><span class="pln">password</span><span class="pun">(</span><span class="str">'What is the password?'</span><span class="pun">)</span><span class="pln">
</span><span class="str">'hunter2'</span></pre>

<p>
	تبدو مربعات الرسائل المنبثقة التي تنتجها السطور السابقة كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="157700" href="https://academy.hsoub.com/uploads/monthly_2024_09/09_000043.jpg.c18f88aa2ccde23872aecb99780fe912.jpg" rel=""><img alt="09 000043" class="ipsImage ipsImage_thumbnailed" data-fileid="157700" data-unique="shtkur1yn" src="https://academy.hsoub.com/uploads/monthly_2024_09/09_000043.jpg.c18f88aa2ccde23872aecb99780fe912.jpg"> </a>
</p>

<p>
	النوافذ من أعلى اليسار إلى أسفل اليمين هي: النوافذ التي تنشئها الدوال <code>alert()‎</code> و <code>confirm()‎</code> و <code>prompt()‎</code> و<code>password()‎</code>
</p>

<p>
	يمكن استخدام هذه الدوال لتقديم إشعارات أو طرح أسئلة على المستخدم أثناء تفاعل باقي البرنامج مع الحاسوب من خلال الفأرة ولوحة المفاتيح. اطّلع على التوثيق الكامل عبر الإنترنت للوحدة PyMsgBox على <a href="https://pymsgbox.readthedocs.io" rel="external nofollow">موقعها الرسمي</a>.
</p>

<h2 id="-31">
	مشاريع للتدريب
</h2>

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

<h3 id="-32">
	برنامج لإبقاء الحالة "مشغول" على برنامج المراسلة الفورية
</h3>

<p>
	تحدّد العديد من برامج المراسلة الفورية ما إذا كنت في وضع السكون أو كنت بعيدًا عن حاسوبك من خلال اكتشاف عدم وجود حركة للفأرة خلال فترة زمنية معينة مثل 10 دقائق. قد تكون بعيدًا عن حاسوبك ولكنك لا تريد أن يرى الآخرون حالة المراسلة الفورية الخاصة بك وهي في وضع السكون، لذا اكتب برنامجًا لتحريك مؤشر الفأرة قليلًا كل 10 ثوانٍ، حيث يجب أن تكون الحركة صغيرة وغير متكررة بدرجة كافية حتى لا تعترض طريقك إذا أردتَ استخدام حاسوبك أثناء تشغيل السكربت.
</p>

<h3 id="clipboard">
	استخدام الحافظة Clipboard لقراءة حقل نصي
</h3>

<p>
	يمكنك إرسال ضغطات المفاتيح إلى الحقول النصية في التطبيق باستخدام الدالة <code>pyautogui.write()‎</code>، ولكن لا يمكنك استخدام وحدة PyAutoGUI وحدها لقراءة النص الموجود فعليًا ضمن الحقل النصي، لذا يمكن أن تساعد الوحدة Pyperclip في ذلك. استخدم الوحدة PyAutoGUI للحصول على نافذة محرّر نصوص مثل المحرّر Mu أو المفكرة Notepad، وإحضارها إلى مقدمة الشاشة من خلال النقر عليها، ثم النقر داخل الحقل النصي، وإرسال مفتاح التشغيل السريع CTRL-A أو ‎<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="157699" href="https://academy.hsoub.com/uploads/monthly_2024_09/01_000064.jpg.a5cf06c5fc868ce86d099e5e65a17d4c.jpg" rel=""><img alt="01 000064" class="ipsImage ipsImage_thumbnailed" data-fileid="157699" data-unique="jekua3n8k" src="https://academy.hsoub.com/uploads/monthly_2024_09/01_000064.jpg.a5cf06c5fc868ce86d099e5e65a17d4c.jpg"> </a>-A لتحديد الكل وإرسال مفتاح التشغيل السريع Ctrl-C أو ‎<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="157699" href="https://academy.hsoub.com/uploads/monthly_2024_09/01_000064.jpg.a5cf06c5fc868ce86d099e5e65a17d4c.jpg" rel=""><img alt="01 000064" class="ipsImage ipsImage_thumbnailed" data-fileid="157699" data-unique="jekua3n8k" src="https://academy.hsoub.com/uploads/monthly_2024_09/01_000064.jpg.a5cf06c5fc868ce86d099e5e65a17d4c.jpg"> </a>-C للنسخ إلى الحافظة، ويمكن لسكربت بايثون بعد ذلك قراءة نص الحافظة من خلال تشغيل التعليمتين <code>import pyperclip</code> و <code>pyperclip.paste()‎</code>.
</p>

<p>
	اكتب برنامجًا يتبع هذا الإجراء لنسخ النص من الحقول النصية في النافذة، لذا استخدم الدالة <code>pyautogui.getWindowsWithTitle('Notepad')‎</code> (أو أي محرّر نصوص تختاره) للحصول على كائن <code>Window</code>. يمكن لسمات <code>top</code> و <code>left</code> لكائن <code>Window</code> أن تخبرك بمكان هذه النافذة، وسيضمن التابع <code>activate()‎</code> وجودها في مقدمة الشاشة. يمكنك بعد ذلك النقر على الحقل النصي الرئيسي لمحرّر النصوص من خلال إضافة 100 أو 200 بكسل مثلًا إلى قيم السمات <code>top</code> و <code>left</code> باستخدام التابع <code>pyautogui.click()‎</code> لنقل تركيز لوحة المفاتيح إلى هذا الحقل، ثم استدعِ الدالتين <code>pyautogui.hotkey('ctrl', 'a')‎</code> و <code>pyautogui.hotkey('ctrl', 'c')‎</code> لتحديد النص بأكمله ونسخه إلى الحافظة. أخيرًا، استدعِ الدالة <code>pyperclip.paste()‎</code> لاسترداد النص من الحافظة ولصقه في برنامج بايثون. يمكنك بعد ذلك استخدام هذه السلسلة النصية كما تريد، ولكن مرّرها إلى الدالة <code>print()‎</code> حاليًا.
</p>

<p>
	لاحظ أن دوال النافذة الخاصة بوحدة PyAutoGUI تعمل فقط على نظام ويندوز بدءًا من الإصدار 1.0.0 من وحدة PyAutoGUI، ولن تعمل على نظام ماك macOS أو لينكس.
</p>

<h3 id="-33">
	بوت المراسلة الفورية
</h3>

<p>
	تستخدم برامج المراسلة الفورية بروتوكولاتٍ خاصة تصعّب كتابة وحدات بايثون التي يمكنها التفاعل مع هذه البرامج، ولكن لا يمكن لهذه البروتوكولات الخاصة منعك من كتابة أداة أتمتة لواجهة المستخدم الرسومية.
</p>

<p>
	يحتوي تطبيق واتس أب Whatsapp على شريط بحث يتيح لك إدخال اسم مستخدم في قائمة أصدقائك وفتح نافذة مراسلة عند الضغط على مفتاح ENTER، حيث ينتقل تركيز لوحة المفاتيح إلى النافذة الجديدة آليًا، وتمتلك تطبيقات المراسلة الفورية الأخرى طرقًا مشابهة لفتح نوافذ الرسائل الجديدة. اكتب برنامجًا يرسل رسالة إشعار آليًا إلى مجموعة مختارة من الأشخاص في قائمة أصدقائك، وقد يضطر برنامجك إلى التعامل مع حالات استثنائية مثل ظهور نافذة الدردشة في إحداثيات مختلفة على الشاشة، أو ظهور مربعات التأكيد التي تقاطع رسائلك. يجب على برنامجك التقاط لقطات شاشة لتوجيه تفاعل واجهة المستخدم الرسومية واعتماد طرقٍ لاكتشاف متى لا تُرسَل ضغطات المفاتيح الافتراضية.
</p>

<p>
	<strong>ملاحظة</strong>: قد ترغب في إعداد بعض الحسابات التجريبية الوهمية حتى لا ترسل رسائل غير مرغوب فيها إلى أصدقائك الحقيقيين عن طريق الخطأ أثناء كتابة هذا البرنامج.
</p>

<h2 id="-34">
	الخلاصة
</h2>

<p>
	تتيح لك أتمتة واجهة المستخدم الرسومية باستخدام وحدة <code>pyautogui</code> التفاعل مع التطبيقات الموجودة على حاسوبك من خلال التحكم في الفأرة ولوحة المفاتيح، حيث يُعَد هذا النهج مرنًا بما يكفي لفعل أيّ شيء يمكن للمستخدم تطبيقه، ولكن يتمثّل جانبه السلبي في أن هذه البرامج لا تستطيع رؤية ما تنقر عليه أو تكتبه. حاول التأكد من أن برامج أتمتة واجهة المستخدم الرسومية عند كتابتها ستتعطّل بسرعة عند إعطائها تعليمات سيئة، إذ قد يكون تعطل البرنامج أمرًا مزعجًا، ولكنه أفضل بكثير من استمرار البرنامج مع وجود الخطأ.
</p>

<p>
	يمكنك تحريك مؤشر الفأرة على الشاشة ومحاكاة نقرات الفأرة وضغطات المفاتيح واختصارات لوحة المفاتيح باستخدام وحدة PyAutoGUI التي يمكنها أيضًا التحقق من الألوان على الشاشة، ويمكنها أن تزوّد برنامج أتمتة واجهة المستخدم الرسومية الخاص بك بفكرة كافية عن محتويات الشاشة لمعرفة خروجه عن المسار الصحيح أم لا، ويمكنك إعطاء الوحدة PyAutoGUI لقطة شاشة والسماح لها بمعرفة إحداثيات المنطقة التي تريد النقر عليها.
</p>

<p>
	يمكنك الجمع بين جميع ميزات PyAutoGUI لأتمتة أيّ مهمة متكررة على حاسوبك. قد تكون مشاهدة مؤشر الفأرة يتحرك من تلقاء نفسه ورؤية النص يظهر على الشاشة آليًا أمرًا مملًا للغاية، ولكن يوجد شعور معين بالرضا يأتي من رؤية كيف أنقذك ذكاؤك من إنجاز المهام المملة.
</p>

<p>
	ترجمة -وبتصرُّف- للمقال <a href="https://automatetheboringstuff.com/2e/chapter20/" rel="external nofollow">Controlling the Keyboard and Mouse with GUI Automation</a> لصاحبه Al Sweigart.
</p>

<h2 id="-35">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%A9-%D8%A7%D9%84%D8%B5%D9%88%D8%B1-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-python-r2408/" rel="">معالجة الصور باستخدام لغة بايثون Python</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D8%A3%D8%AF%D9%88%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D8%A9-%D9%81%D9%8A-%D8%A8%D9%86%D8%A7%D8%A1-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%B1%D8%B3%D9%88%D9%85%D9%8A%D8%A9-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1771/" rel="">الأدوات المستخدمة في بناء الواجهات الرسومية في بايثون</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D8%AC%D8%AF%D9%88%D9%84%D8%A9-%D8%A7%D9%84%D9%85%D9%87%D8%A7%D9%85-%D9%88%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D8%A8%D8%B1%D8%A7%D9%85%D8%AC-%D8%A3%D8%AE%D8%B1%D9%89-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2394/" rel="">جدولة المهام وتشغيل برامج أخرى باستخدام لغة بايثون</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D8%A7%D9%84%D8%B1%D8%B3%D9%88%D9%85%D9%8A%D8%A9-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-tkinter-r1501/" rel="">واجهات المستخدم الرسومية في بايثون باستخدام TKinter</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2409</guid><pubDate>Sun, 15 Sep 2024 15:04:01 +0000</pubDate></item><item><title>&#x645;&#x639;&#x627;&#x644;&#x62C;&#x629; &#x627;&#x644;&#x635;&#x648;&#x631; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x644;&#x63A;&#x629; &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646; Python</title><link>https://academy.hsoub.com/programming/python/%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%A9-%D8%A7%D9%84%D8%B5%D9%88%D8%B1-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-python-r2408/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_09/----.png.df6cc1c9a0deb0b256d377b990117f59.png" /></p>
<p>
	ستصادف ملفات الصور الرقمية طوال الوقت إذا كان لديك كاميرا رقمية أو حتى إذا رفعتَ صورًا من هاتفك على حسابك على فيسبوك أو انستغرام مثلًا، وقد تعرف كيفية استخدام برامج الرسوميات الأساسية مثل الرسام Microsoft Paint أو Paintbrush، أو حتى التطبيقات الأكثر تقدمًا مثل <a href="https://academy.hsoub.com/design/graphic/photoshop/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D8%AC-%D8%A3%D8%AF%D9%88%D8%A8%D9%8A-%D9%81%D9%88%D8%AA%D9%88%D8%B4%D9%88%D8%A8-adobe-photoshop-r541/" rel="">أدوبي فوتوشوب Adobe Photoshop</a>، ولكن إذا كنت بحاجة إلى تعديل عددٍ كبير من الصور، فيمكن أن يكون إنجاز ذلك يدويًا مهمة طويلة ومملة.
</p>

<p>
	تُعَد Pillow <a href="https://wiki.hsoub.com/Python/modules" rel="external">وحدةَ بايثون</a> خارجية للتفاعل مع ملفات الصور، وتحتوي هذه الوحدة على العديد من الدوال التي تسهّل قص محتوى الصورة وتغيير حجمه وتعديله. يمكن <a href="https://academy.hsoub.com/programming/python/%D9%85%D9%85%D9%8A%D8%B2%D8%A7%D8%AA-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86/" rel="">لبايثون Python</a> تعديل مئات أو آلاف الصور آليًا بسهولة بفضل القدرة على معالجة الصور باستخدام الطريقة نفسها التي تستخدمها برامجٌ مثل برنامج الرسام أو الفوتوشوب. يمكنك تثبيت وحدة Pillow من خلال تشغيل الأمر <code>pip install --user -U pillow==9.2.0</code>.
</p>

<h2 id="">
	أساسيات الصور الحاسوبية
</h2>

<p>
	يجب أن تفهم أساسيات كيفية تعامل الحواسيب مع الألوان والإحداثيات في الصور وكيفية العمل مع الألوان والإحداثيات في الوحدة Pillow من أجل معالجة الصور، لذا ثبّت هذه الوحدة قبل المتابعة.
</p>

<h3 id="rgba">
	الألوان وقيم RGBA
</h3>

<p>
	تمثّل البرامج الحاسوبية لونًا في صورة بوصفه قيمة RGBA، والتي هي مجموعة من الأعداد التي تحدّد مقدار اللون الأحمر والأخضر والأزرق وقيمة ألفا Alpha (أو الشفافية Transparency) في اللون. كل قيمة من قيم هذه المكونات هي عددٌ صحيح قيمته من 0 (عدم وجود لون على الإطلاق) إلى 255 (الحد الأقصى). تُسنَد قيم RGBA إلى البكسلات، والبكسل Pixel هو أصغر نقطة من لون واحد يمكن أن تظهرها شاشة الحاسوب، فهناك ملايين البكسلات على الشاشة، ويعبّر إعداد RGB الخاص بالبكسل عن درجة اللون التي يجب أن يعرضها بدقة. تحتوي الصور أيضًا على قيمة ألفا لإنشاء قيم RGBA، حيث إذا عُرِضت صورة على الشاشة فوق صورة خلفية أو خلفية سطح مكتب، فإن قيمة ألفا تحدِّد مقدار الخلفية التي يمكنك رؤيتها عبر بكسل الصورة.
</p>

<p>
	تُمثَّل قيم RGBA في الوحدة Pillow باستخدام مجموعة Tuple مكونة من أربع قيم صحيحة، فمثلًا يُمثَّل اللون الأحمر باستخدام المجموعة <code>‎(255, 0, 0, 255)‎</code>، حيث يحتوي هذا اللون على الحد الأقصى من اللون الأحمر، ولا يحتوي على اللون الأخضر أو الأزرق، ويحتوي على الحد الأقصى من قيمة ألفا، مما يعني أنه مُعتَم Opaque تمامًا. يُمثَّل اللون الأخضر باستخدام المجموعة <code>‎(0, 255, 0, 255)‎</code>، ويُمثَّل اللون الأزرق باستخدام المجموعة <code>‎(0, 0, 255, 255)‎</code>، ويُمثَّل اللون الأبيض الذي هو مزيج من كل الألوان باستخدام المجموعة <code>‎(255, 255, 255, 255)‎</code>، واللون الأسود الذي لا لون له هو <code>‎(0, 0, 0, 255)‎</code>. إذا كانت قيمة الشفافية للون هي 0، فسيكون اللون غير مرئي، وتصبح قيم RGB غير مهمة، وبالتالي سيبدو اللون الأحمر غير المرئي مثل اللون الأسود غير المرئي.
</p>

<p>
	تستخدم الوحدة Pillow أسماء الألوان المعيارية التي تستخدمها<a href="https://academy.hsoub.com/programming/html/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D9%84%D8%BA%D8%A9-html-r1687/" rel=""> لغة HTML</a>، حيث يوضح الجدول التالي مجموعة مختارة من أسماء الألوان المعيارية وقيم RGBA الخاصة بها:
</p>

<table>
	<thead>
		<tr>
			<th>
				اسم اللون
			</th>
			<th>
				قيمة RGBA الخاصة به
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				White (الأبيض)
			</td>
			<td>
				‎(255, 255, 255, 255)‎
			</td>
		</tr>
		<tr>
			<td>
				Green (الأخضر)
			</td>
			<td>
				‎(0, 128, 0, 255)‎
			</td>
		</tr>
		<tr>
			<td>
				Gray (الرمادي)
			</td>
			<td>
				‎(128, 128, 128, 255)‎
			</td>
		</tr>
		<tr>
			<td>
				Black (الأسود)
			</td>
			<td>
				‎(0, 0, 0, 255)‎
			</td>
		</tr>
		<tr>
			<td>
				Red (الأحمر)
			</td>
			<td>
				‎(255, 0, 0, 255)‎
			</td>
		</tr>
		<tr>
			<td>
				Blue (الأزرق)
			</td>
			<td>
				‎(0, 0, 255, 255)‎
			</td>
		</tr>
		<tr>
			<td>
				Yellow (الأصفر)
			</td>
			<td>
				‎(255, 255, 0, 255)‎
			</td>
		</tr>
		<tr>
			<td>
				Purple (البنفسجي)
			</td>
			<td>
				‎(128, 0, 128, 255)‎
			</td>
		</tr>
	</tbody>
</table>

<p>
	توفّر الوحدة Pillow الدالة <code>ImageColor.getcolor()‎</code>، وبالتالي لن تكون مضطرًا إلى حفظ قيم RGBA للألوان التي تريد استخدامها، وتأخذ هذه الدالة سلسلة نصية تمثّل اسم اللون كوسيطٍ أول لها والسلسلة النصية <code>'RGBA'</code> كوسيطٍ ثانٍ لها، وتعيد مجموعة RGBA. أدخِل ما يلي في الصدفة التفاعلية Interactive Shell لمعرفة كيفية عمل هذه الدالة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8784_8" style=""><span class="pun">➊</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> PIL </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">ImageColor</span><span class="pln">
</span><span class="pun">➋</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="typ">ImageColor</span><span class="pun">.</span><span class="pln">getcolor</span><span class="pun">(</span><span class="str">'red'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'RGBA'</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">(</span><span class="lit">255</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">255</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➌</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="typ">ImageColor</span><span class="pun">.</span><span class="pln">getcolor</span><span class="pun">(</span><span class="str">'RED'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'RGBA'</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">(</span><span class="lit">255</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">255</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="typ">ImageColor</span><span class="pun">.</span><span class="pln">getcolor</span><span class="pun">(</span><span class="str">'Black'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'RGBA'</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">255</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="typ">ImageColor</span><span class="pun">.</span><span class="pln">getcolor</span><span class="pun">(</span><span class="str">'chocolate'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'RGBA'</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">(</span><span class="lit">210</span><span class="pun">,</span><span class="pln"> </span><span class="lit">105</span><span class="pun">,</span><span class="pln"> </span><span class="lit">30</span><span class="pun">,</span><span class="pln"> </span><span class="lit">255</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="typ">ImageColor</span><span class="pun">.</span><span class="pln">getcolor</span><span class="pun">(</span><span class="str">'CornflowerBlue'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'RGBA'</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">(</span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="lit">149</span><span class="pun">,</span><span class="pln"> </span><span class="lit">237</span><span class="pun">,</span><span class="pln"> </span><span class="lit">255</span><span class="pun">)</span></pre>

<p>
	يجب أولًا استيراد الوحدة <code>ImageColor</code> من PIL ➊ (وليس من Pillow). السلسلة النصية لاسم اللون التي تمررها إلى الدالة <code>ImageColor.getcolor()‎</code> غير حساسة لحالة الأحرف، لذا يعطي تمرير السلسلة النصية 'red' ➋ وتمرير السلسلة النصية 'RED' ➌ مجموعة RGBA نفسها، ويمكنك أيضًا تمرير أسماء ألوان غير اعتيادية مثل <code>'chocolate'</code> و <code>'Cornflower Blue'</code>.
</p>

<p>
	تدعم الوحدة Pillow عددًا كبيرًا من أسماء الألوان من <code>'aliceblue'</code> إلى <code>'whitesmoke'</code>، حيث يمكنك العثور على القائمة الكاملة لأكثر من 100 اسم لون معياري في الموارد الموجودة على <a href="https://nostarch.com/automatestuff2/" rel="external nofollow">nostarch</a>.
</p>

<h3 id="-1">
	الإحداثيات والمجموعات المربعة
</h3>

<p>
	تُعنوَن بكسلات الصورة باستخدام إحداثيات x و y، والتي تُحدِّد موقع البكسل الأفقي والعمودي على التوالي في الصورة، وتكون نقطة الأصل Origin (أو مبدأ الإحداثيات) هي البكسل الموجود في الزاوية العلوية اليسرى من الصورة ونحدّدها بالصيغة ‎(0, 0)‎، حيث يمثل الصفر الأول الإحداثي x الذي يبدأ من الصفر عند نقطة الأصل وتزداد قيمته من اليسار إلى اليمين، ويمثل الصفر الثاني الإحداثي y الذي يبدأ من الصفر عند نقطة الأصل وتزداد قيمته نزولًا إلى أسفل الصورة. تزداد إحداثيات y باتجاه الأسفل، إذ يُعَد ذلك عكس الطريقة التي استخدمناها سابقًا لإحداثيات y في حصص الرياضيات في المدرسة. يوضح الشكل التالي كيفية عمل هذا النظام من الإحداثيات:
</p>

<p style="text-align: center;">
	<img alt="01_000023-معرب.png" class="ipsImage ipsImage_thumbnailed" data-fileid="157690" data-ratio="96.32" data-unique="ih9wzdu7r" width="272" src="https://academy.hsoub.com/uploads/monthly_2024_09/01_000023-.png.8e7f41f9c6ef293bd1cf2cbe8f59c615.png">
</p>

<p style="text-align: center;">
	إحداثيات x و y لصورة أبعادها 28‎×27 لأحد أنواع أجهزة تخزين البيانات القديمة
</p>

<p>
	تأخذ العديد من <a href="https://wiki.hsoub.com/Python#.D8.A7.D9.84.D8.AF.D9.88.D8.A7.D9.84" rel="external">دوال</a> وتوابع الوحدة Pillow وسيطًا نوعه مجموعة مربعة Box Tuple، وهذا يعني أن الوحدة Pillow تتوقع مجموعةً مؤلفة من أربعة إحداثيات صحيحة تمثل منطقةً مستطيلة في الصورة، والأعداد الصحيحة الأربعة هي بالترتيب كما يلي:
</p>

<ul>
	<li>
		Left: الإحداثي x للحافة اليسرى من المربع.
	</li>
	<li>
		Top: الإحداثي y للحافة العلوية من المربع.
	</li>
	<li>
		Right: الإحداثي x لبكسل واحد على يمين الحافة اليمنى القصوى للمربع، ويجب أن يكون هذا العدد الصحيح أكبر من العدد الصحيح الأيسر Left.
	</li>
	<li>
		Bottom: الإحداثي y لبكسل واحد تحت الحافة السفلية للمربع، ويجب أن يكون هذا العدد الصحيح أكبر من العدد الصحيح العلوي Top.
	</li>
</ul>

<p>
	لاحظ أن المربع يتضمن الإحداثيات اليسرى والعليا حتى الوصول إلى الإحداثيات اليمنى والسفلى ولكنه لا يتضمنها، فمثلًا تمثل المجموعة المربعة <code>‎(3, 1, 9, 6)‎</code> جميع البكسلات الموجودة في المربع الأسود في الشكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="157685" href="https://academy.hsoub.com/uploads/monthly_2024_09/02_000115.jpg.01cafd939cdef8f17026b8b223742959.jpg" rel=""><img alt="02 000115" class="ipsImage ipsImage_thumbnailed" data-fileid="157685" data-unique="ls506vont" src="https://academy.hsoub.com/uploads/monthly_2024_09/02_000115.jpg.01cafd939cdef8f17026b8b223742959.jpg"> </a>
</p>

<p style="text-align: center;">
	المنطقة التي تمثّلها المجموعة المربعة ‎(3, 1, 9, 6)‎
</p>

<h2 id="pillow">
	معالجة الصور باستخدام الوحدة Pillow
</h2>

<p>
	عرفنا كيفية عمل الألوان والإحداثيات في الوحدة Pillow، وسنستخدمها الآن لمعالجة الصور. سنستخدم الصورة التالية لجميع أمثلة الصدفة التفاعلية في هذا المقال:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="157682" href="https://academy.hsoub.com/uploads/monthly_2024_09/03_000061.jpg.980c4d787eb6c7a4b50ba4a3315e93fb.jpg" rel=""><img alt="03 000061" class="ipsImage ipsImage_thumbnailed" data-fileid="157682" data-unique="yyvhkutko" src="https://academy.hsoub.com/uploads/monthly_2024_09/03_000061.jpg.980c4d787eb6c7a4b50ba4a3315e93fb.jpg"> </a>
</p>

<p style="text-align: center;">
	القطة زوفي Zophie
</p>

<p>
	ضع ملف الصورة zophie.png في مجلد العمل الحالي، ثم ستكون جاهزًا لتحميل صورة هذه القطة إلى شيفرة بايثون كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8784_10" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> PIL </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Image</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> catIm </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="str">'zophie.png'</span><span class="pun">)</span></pre>

<p>
	يمكنك تحميل الصورة من خلال استيراد الوحدة <code>Image</code> من الوحدة Pillow واستدعاء الدالة <code>Image.open()‎</code>، ثم تمرّر اسم ملف الصورة إلى هذه الدالة، ويمكنك بعد ذلك تخزين الصورة المُحمَّلة في المتغير <code>CatIm</code>. اسم الوحدة Pillow هو <code>PIL</code> لجعلها متوافقة مع الإصدارات السابقة من وحدةٍ أقدم اسمها Python Imaging Library، ولذلك يجب تشغيل التعليمة <code>from PIL import Image</code> بدلًا من التعليمة <code>from Pillow import Image</code>، ويجب استخدام تعليمة الاستيراد <code>from PIL import Image</code> بدلًا من التعليمة <code>import PIL</code> وفقًا للطريقة التي أعدّ بها منشئو Pillow هذه الوحدة.
</p>

<p>
	إن لم يكن ملف الصورة موجودًا في مجلد العمل الحالي، فغيّر مجلد العمل إلى المجلد الذي يحتوي على ملف الصورة من خلال استدعاء الدالة <code>os.chdir()‎</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8784_12" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> os
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">chdir</span><span class="pun">(</span><span class="str">'C:\\folder_with_image_file'</span><span class="pun">)</span></pre>

<p>
	تعيد الدالة <code>Image.open()‎</code> قيمة من نوع البيانات كائن <code>Image</code>، وهي الطريقة التي تمثّل بها الوحدة Pillow الصورة بوصفها قيمة بايثون. يمكنك تحميل كائن <code>Image</code> من ملف صورة (بأيّ صيغة) من خلال تمرير السلسلة النصية التي تمثل اسم الملف إلى الدالة <code>Image.open()‎</code>، ويمكن حفظ أيّ تغييرات تجريها على كائن <code>Image</code> في ملف صورة (بأيّ صيغة أيضًا) باستخدام التابع <code>save()‎</code>. تُجرَى جميع عمليات التدوير وتغيير الحجم والقص والرسم وغيرها من عمليات معالجة الصور من خلال استدعاءات التوابع الموافقة لهذه العمليات مع كائن <code>Image</code>.
</p>

<p>
	سنفترض أنك استوردتَ الوحدة <code>Image</code> الخاصة بالوحدة Pillow وأن لديك صورة القطة Zophie مُخزَّنة في المتغير <code>catIm</code> لاختصار الأمثلة في هذا المقال. تأكّد من وجود الملف zophie.png في مجلد العمل الحالي حتى تتمكّن الدالة <code>Image.open()‎</code> من العثور عليه، وإلّا فيجب تحديد المسار المطلق الكامل في وسيط السلسلة النصية للدالة <code>Image.open()‎</code>.
</p>

<h3 id="image">
	العمل مع نوع البيانات Image
</h3>

<p>
	يحتوي الكائن <code>Image</code> على العديد من السمات Attributes المفيدة التي توفر لك معلومات أساسية حول ملف الصورة الذي جرى تحميله منه مثل: عرضه وارتفاعه، واسم الملف، وصيغة الرسوميات (مثل JPEG أو GIF أو PNG).
</p>

<p>
	أدخل مثلًا ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8784_14" style=""><span class="pln">   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> PIL </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Image</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> catIm </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="str">'zophie.png'</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> catIm</span><span class="pun">.</span><span class="pln">size
</span><span class="pun">➊</span><span class="pln"> </span><span class="pun">(</span><span class="lit">816</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1088</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➋</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> width</span><span class="pun">,</span><span class="pln"> height </span><span class="pun">=</span><span class="pln"> catIm</span><span class="pun">.</span><span class="pln">size
</span><span class="pun">➌</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> width
   </span><span class="lit">816</span><span class="pln">
</span><span class="pun">➍</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> height
   </span><span class="lit">1088</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> catIm</span><span class="pun">.</span><span class="pln">filename
   </span><span class="str">'zophie.png'</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> catIm</span><span class="pun">.</span><span class="pln">format
   </span><span class="str">'PNG'</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> catIm</span><span class="pun">.</span><span class="pln">format_description
   </span><span class="str">'Portable network graphics'</span><span class="pln">
</span><span class="pun">➎</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> catIm</span><span class="pun">.</span><span class="pln">save</span><span class="pun">(</span><span class="str">'zophie.jpg'</span><span class="pun">)</span></pre>

<p>
	ننشئ كائن <code>Image</code> من ملف الصورة zophie.png ونخزّن هذا الكائن في المتغير <code>catIm</code>، ثم يمكننا أن نرى أن سمة الحجم <code>size</code> الخاصة بهذا الكائن تحتوي على مجموعةٍ Tuple تتألف من عرض الصورة وارتفاعها بالبكسل ➊. يمكننا إسناد القيم الموجودة في هذه المجموعة إلى المتغيرين <code>width</code> و <code>height</code> ➋ للوصول إلى العرض ➌ والارتفاع ➍ لوحدهما. تمثّل السمة <code>filename</code> اسم الملف الأصلي، وتمثّل السمات <code>format</code> و <code>format_description</code> -التي هي سلاسل نصية- صيغة الصورة للملف الأصلي، مع كون السمة <code>format_description</code> أكثر تفصيلًا.
</p>

<p>
	أخيرًا، يؤدي استدعاء التابع <code>save()‎</code> وتمرير <code>'zophie.jpg'</code> إلى هذا التابع إلى حفظ صورة جديدة بالاسم zophie.jpg على قرص حاسوبك الصلب ➎. ترى الوحدة Pillow أن امتداد الملف هو <code>‎.jpg</code> وتحفظ الصورة تلقائيًا بصيغة الصورة JPEG. يُفترَض أن يكون لديك الآن صورتان هما zophie.png و zophie.jpg على قرص حاسوبك الصلب، حيث يعتمد هذان الملفان على الصورة نفسها، ولكنهما غير متطابقين بسبب اختلاف صيغتيهما.
</p>

<p>
	توفر الوحدة Pillow أيضًا الدالة <code>Image.new()‎</code> التي تعيد كائن <code>Image</code>، حيث تشبه هذه الدالة إلى حدٍ كبيرالدالة <code>Image.open()‎</code>، باستثناء أن الصورة التي يمثلها كائن الدالة <code>Image.new()‎</code> فارغة. وسطاء الدالة <code>Image.new()‎</code> هي كما يلي:
</p>

<ul>
	<li>
		السلسلة النصية <code>'RGBA'</code> التي تضبط نمط الألوان على القيمة RGBA، إذ توجد أنماط أخرى لن نوضّحها في هذا المقال.
	</li>
	<li>
		حجم الصورة الذي نمثّله بمجموعة مكونة من عددين صحيحين لعرض الصورة الجديدة وارتفاعها.
	</li>
	<li>
		لون الخلفية الذي يجب أن تبدأ به الصورة، ونمثّله بمجموعة مكونة من أربعة أعداد صحيحة لقيمة RGBA، حيث يمكنك استخدام القيمة التي تعيدها الدالة <code>ImageColor.getcolor()‎</code> لهذا الوسيط، ولكن تدعم الدالة <code>Image.new()‎</code> بدلًا من ذلك تمرير سلسلة نصية تمثّل اسم اللون المعياري فقط.
	</li>
</ul>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8784_16" style=""><span class="pln">   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> PIL </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Image</span><span class="pln">
</span><span class="pun">➊</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> im </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">.</span><span class="pln">new</span><span class="pun">(</span><span class="str">'RGBA'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">),</span><span class="pln"> </span><span class="str">'purple'</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> im</span><span class="pun">.</span><span class="pln">save</span><span class="pun">(</span><span class="str">'purpleImage.png'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➋</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> im2 </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">.</span><span class="pln">new</span><span class="pun">(</span><span class="str">'RGBA'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="lit">20</span><span class="pun">,</span><span class="pln"> </span><span class="lit">20</span><span class="pun">))</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> im2</span><span class="pun">.</span><span class="pln">save</span><span class="pun">(</span><span class="str">'transparentImage.png'</span><span class="pun">)</span></pre>

<p>
	ننشئ كائن <code>Image</code> لصورة عرضها 100 بكسل وطولها 200 بكسل مع خلفية بنفسجية ➊، ثم نحفظ هذه الصورة في الملف purpleImage.png. نستدعي بعد ذلك الدالة <code>Image.new()‎</code> مرةً أخرى لإنشاء كائن <code>Image</code> آخر مع تمرير المجموعة ‎(20, 20)‎ التي تمثّل الأبعاد دون تمرير شيء للون الخلفية ➋. يُعَد اللون الأسود غير المرئي <code>‎(0, 0, 0, 0)‎</code> هو اللون الافتراضي المُستخدَم عند عدم تحديد وسيط اللون، وبالتالي فإن الصورة الثانية لها خلفية شفافة، ثم نحفظ هذا المربع الشفاف الذي أبعاده 20×20 في الملف transparentImage.png.
</p>

<h3 id="-2">
	قص الصور
</h3>

<p>
	يمثّل قص الصورة تحديد منطقة مستطيلة من الصورة وإزالة كل شيء خارج هذا المستطيل. يأخذ التابع <code>crop()‎</code> مع كائنات <code>Image</code> مجموعة مربعة ويعيد كائن <code>Image</code> يمثل الصورة التي قصّها. يترك التابع <code>crop()‎</code> كائن <code>Image</code> الأصلي دون تغيير بعد القص، ويعيد كائن <code>Image</code> جديد. تذكّر أن المجموعة المربعة (أي الجزء المقصوص في هذه الحالة) يتضمّن العمود الأيسر والصف العلوي من البكسلات وحتى الوصول إلى العمود الأيمن والصف السفلي من البكسلات دون تضمينها.
</p>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8784_18" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> PIL </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Image</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> catIm </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="str">'zophie.png'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> croppedIm </span><span class="pun">=</span><span class="pln"> catIm</span><span class="pun">.</span><span class="pln">crop</span><span class="pun">((</span><span class="lit">335</span><span class="pun">,</span><span class="pln"> </span><span class="lit">345</span><span class="pun">,</span><span class="pln"> </span><span class="lit">565</span><span class="pun">,</span><span class="pln"> </span><span class="lit">560</span><span class="pun">))</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> croppedIm</span><span class="pun">.</span><span class="pln">save</span><span class="pun">(</span><span class="str">'cropped.png'</span><span class="pun">)</span></pre>

<p>
	ننشئ كائن <code>Image</code> جديد للصورة المقصوصة، ونخزّن هذا الكائن في المتغير <code>croppedIm</code>، ثم نستدعي التابع <code>save()‎</code> مع <code>croppedIm</code> لحفظ الصورة المقصوصة في الملف cropped.png. سينشَأ الملف الجديد cropped.png من الصورة الأصلية كما في الشكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="157681" href="https://academy.hsoub.com/uploads/monthly_2024_09/04_000003.jpg.ecb55ce1b56e1f6109d71483f8b78659.jpg" rel=""><img alt="04 000003" class="ipsImage ipsImage_thumbnailed" data-fileid="157681" data-unique="w0kqi75oq" src="https://academy.hsoub.com/uploads/monthly_2024_09/04_000003.jpg.ecb55ce1b56e1f6109d71483f8b78659.jpg"> </a>
</p>

<p style="text-align: center;">
	تكون الصورة الجديدة هي الجزء المقصوص من الصورة الأصلية
</p>

<h3 id="-3">
	نسخ ولصق الصور في صور أخرى
</h3>

<p>
	يعيد التابع <code>copy()‎</code> كائن <code>Image</code> جديد يحتوي على الصورة نفسها للكائن <code>Image</code> الذي استدعيناه معه، ويُعَد ذلك مفيدًا إذا كنت بحاجة إلى إجراء تغييرات على الصورة ولكنك تريد الاحتفاظ بنسخة دون تغييرات من النسخة الأصلية، فمثلًا أدخِل ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8784_20" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> PIL </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Image</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> catIm </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="str">'zophie.png'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> catCopyIm </span><span class="pun">=</span><span class="pln"> catIm</span><span class="pun">.</span><span class="pln">copy</span><span class="pun">()</span></pre>

<p>
	يحتوي المتغيران <code>catIm</code> و <code>catCopyIm</code> على كائني <code>Image</code> منفصلين، وكلاهما لهما الصورة نفسهما. أصبح لديك الآن كائن <code>Image</code> مُخزَّن في المتغير <code>catCopyIm</code>، ويمكنك الآن تعديل المتغير <code>catCopyIm</code> كما تريد وحفظه باسم ملف جديد، مع ترك الملف zophie.png دون تغيير. لنحاول مثلًا تعديل المتغير <code>catCopyIm</code> باستخدام التابع <code>paste()‎</code>، حيث يُستدعَى هذا التابع مع كائن <code>Image</code> ويلصق صورة أخرى فوقه. إذًا لنتابع مثال الصدفة من خلال لصق صورة أصغر على <code>catCopyIm</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8784_22" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> faceIm </span><span class="pun">=</span><span class="pln"> catIm</span><span class="pun">.</span><span class="pln">crop</span><span class="pun">((</span><span class="lit">335</span><span class="pun">,</span><span class="pln"> </span><span class="lit">345</span><span class="pun">,</span><span class="pln"> </span><span class="lit">565</span><span class="pun">,</span><span class="pln"> </span><span class="lit">560</span><span class="pun">))</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> faceIm</span><span class="pun">.</span><span class="pln">size
</span><span class="pun">(</span><span class="lit">230</span><span class="pun">,</span><span class="pln"> </span><span class="lit">215</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> catCopyIm</span><span class="pun">.</span><span class="pln">paste</span><span class="pun">(</span><span class="pln">faceIm</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">))</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> catCopyIm</span><span class="pun">.</span><span class="pln">paste</span><span class="pun">(</span><span class="pln">faceIm</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="lit">400</span><span class="pun">,</span><span class="pln"> </span><span class="lit">500</span><span class="pun">))</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> catCopyIm</span><span class="pun">.</span><span class="pln">save</span><span class="pun">(</span><span class="str">'pasted.png'</span><span class="pun">)</span></pre>

<p>
	نمرّر أولًا مجموعة مربعة للمنطقة المستطيلة في الصورة zophie.png التي تحتوي على وجه القطة إلى التابع <code>crop()‎</code>، مما يؤدي إلى إنشاء كائن <code>Image</code> يمثل جزءًا مقصوصًا أبعاده 230‎×215، والذي نخزّنه في المتغير <code>faceIm</code>. يمكننا الآن لصق <code>faceIm</code> فوق <code>catCopyIm</code>، حيث يأخذ التابع <code>paste()‎</code> وسيطين هما: كائن <code>Image</code> المصدَر "Source" ومجموعة من إحداثيات x و y لمكان لصق الزاوية العلوية اليسرى من كائن <code>Image</code> المصدر على كائن <code>Image</code> الرئيسي. استدعينا في مثالنا التابع <code>paste()‎</code> مرتين مع <code>catCopyIm</code>، ومرّرنا المجموعة ‎(0, 0)‎ في المرة الأولى والمجموعة ‎(400, 500)‎ في المرة الثانية، مما يؤدي إلى لصق <code>faceIm</code> على <code>catCopyIm</code> مرتين، حيث نلصق الزاوية العلوية اليسرى من <code>faceIm</code> عند الإحداثيات ‎(0, 0)‎ على <code>catCopyIm</code> في المرة الأولى، ونلصق الزاوية العلوية اليسرى من <code>faceIm</code> عند الإحداثيات ‎(400, 500)‎. أخيرًا، نحفظ المتغير <code>catCopyIm</code> المُعدَّل في الملف pasted.png، وستبدو الصورة كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="157679" href="https://academy.hsoub.com/uploads/monthly_2024_09/05_000093.jpg.028af1f7b36ace8976152666e38c846f.jpg" rel=""><img alt="05 000093" class="ipsImage ipsImage_thumbnailed" data-fileid="157679" data-unique="2ztku1tgz" src="https://academy.hsoub.com/uploads/monthly_2024_09/05_000093.jpg.028af1f7b36ace8976152666e38c846f.jpg"> </a>
</p>

<p style="text-align: center;">
	القطة زوفي بعد لصق وجهها مرتين
</p>

<p>
	<strong>ملاحظة</strong>: لا يستخدم التابعان <code>copy()‎</code> و <code>paste()‎</code> في الوحدة Pillow حافظة Clipboard حاسوبك بالرغم من أن اسميهما يدلان على ذلك.
</p>

<p>
	لاحظ أن التابع <code>paste()‎</code> يعدّل كائن <code>Image</code> في المكان نفسه، أي أنه لا يعيد كائن <code>Image</code> جديد مع الصورة المُلصَقة، ولكن إذا أردتَ استدعاء هذا التابع مع الاحتفاظ أيضًا بالنسخة غير المُعدَّلة من الصورة الأصلية، فيجب نسخ الصورة أولًا ثم استدعاء التابع <code>paste()‎</code> مع تلك النسخة.
</p>

<p>
	لنفترض أنك تريد وضع رأس القطة على الصورة بأكملها كما في الشكل الآتي، حيث يمكنك تحقيق هذا التأثير باستخدام بضع <a href="https://wiki.hsoub.com/Python/for" rel="external">حلقات for</a> فقط، لذا تابع مثال الصدفة التفاعلية من خلال إدخال ما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8784_24" style=""><span class="pln">   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> catImWidth</span><span class="pun">,</span><span class="pln"> catImHeight </span><span class="pun">=</span><span class="pln"> catIm</span><span class="pun">.</span><span class="pln">size
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> faceImWidth</span><span class="pun">,</span><span class="pln"> faceImHeight </span><span class="pun">=</span><span class="pln"> faceIm</span><span class="pun">.</span><span class="pln">size
</span><span class="pun">➊</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> catCopyTwo </span><span class="pun">=</span><span class="pln"> catIm</span><span class="pun">.</span><span class="pln">copy</span><span class="pun">()</span><span class="pln">
</span><span class="pun">➋</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> left </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> catImWidth</span><span class="pun">,</span><span class="pln"> faceImWidth</span><span class="pun">):</span><span class="pln">
        </span><span class="pun">➌</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> top </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> catImHeight</span><span class="pun">,</span><span class="pln"> faceImHeight</span><span class="pun">):</span><span class="pln">
               </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> top</span><span class="pun">)</span><span class="pln">
               catCopyTwo</span><span class="pun">.</span><span class="pln">paste</span><span class="pun">(</span><span class="pln">faceIm</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> top</span><span class="pun">))</span><span class="pln">
   </span><span class="lit">0</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
   </span><span class="lit">0</span><span class="pln"> </span><span class="lit">215</span><span class="pln">
   </span><span class="lit">0</span><span class="pln"> </span><span class="lit">430</span><span class="pln">
   </span><span class="lit">0</span><span class="pln"> </span><span class="lit">645</span><span class="pln">
   </span><span class="lit">0</span><span class="pln"> </span><span class="lit">860</span><span class="pln">
   </span><span class="lit">0</span><span class="pln"> </span><span class="lit">1075</span><span class="pln">
   </span><span class="lit">230</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
   </span><span class="lit">230</span><span class="pln"> </span><span class="lit">215</span><span class="pln">
   </span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">
   </span><span class="lit">690</span><span class="pln"> </span><span class="lit">860</span><span class="pln">
   </span><span class="lit">690</span><span class="pln"> </span><span class="lit">1075</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> catCopyTwo</span><span class="pun">.</span><span class="pln">save</span><span class="pun">(</span><span class="str">'tiled.png'</span><span class="pun">)</span></pre>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="157694" href="https://academy.hsoub.com/uploads/monthly_2024_09/06_000045.jpg.30aa7f0f6bfca5726af660e6f0751820.jpg" rel=""><img alt="06 000045" class="ipsImage ipsImage_thumbnailed" data-fileid="157694" data-unique="dzi507981" src="https://academy.hsoub.com/uploads/monthly_2024_09/06_000045.jpg.30aa7f0f6bfca5726af660e6f0751820.jpg"> </a>
</p>

<p style="text-align: center;">
	حلقات <code>for</code> المتداخلة المستخدمة مع التابع <code>paste()‎</code> لتكرار وجه القطة
</p>

<p>
	نخزّن عرض وارتفاع <code>catIm</code> في المتغيرين <code>catImWidth</code> و <code>catImHeight</code>، ثم ننشئ نسخة من <code>catIm</code> ونخزّنها في المتغير <code>catCopyTwo</code> ➊. أصبح لدينا الآن نسخة يمكننا لصقها، وبالتالي نبدأ بتكرار لصق <code>faceIm</code> على <code>catCopyTwo</code>، حيث يبدأ المتغير <code>left</code> الخاص بحلقة <code>for</code> الخارجية من القيمة 0 ويزداد بمقدار <code>faceImWidth(230)‎</code> ➋، ويبدأ المتغير <code>top</code> الخاص بحلقة <code>for</code> الداخلية من القيمة 0 ويزداد بمقدار <code>faceImHeight(215)‎</code> ➌. تعطي حلقات <code>for</code> المتداخلة هذه قيمًا للمتغيرين <code>left</code> و <code>top</code> للصق شبكةٍ من صور <code>faceIm</code> فوق كائن <code>Image</code> الذي هو <code>catCopyTwo</code> كما هو موضّح في الشكل السابق. نطبع قيم المتغيرين <code>left</code> و <code>top</code> لرؤية كيفية عمل هذه الحلقات المتداخلة، ثم نحفظ الصورة <code>catCopyTwo</code> المُعدَّلة في الملف tiled.png بعد اكتمال اللصق.
</p>

<h3 id="-4">
	تغيير حجم الصورة
</h3>

<p>
	يُستدعَى التابع <code>resize()‎</code> مع الكائن <code>Image</code> ويعيد كائن <code>Image</code> جديد مع العرض والارتفاع المُحدَّدين، حيث يقبل هذا التابع وسيطًا هو مجموعة مكونة من عددين صحيحين يمثلان العرض والارتفاع الجديدين للصورة المُعادة. أدخِل مثلًا ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8784_26" style=""><span class="pln">   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> PIL </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Image</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> catIm </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="str">'zophie.png'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➊</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> width</span><span class="pun">,</span><span class="pln"> height </span><span class="pun">=</span><span class="pln"> catIm</span><span class="pun">.</span><span class="pln">size
</span><span class="pun">➋</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> quartersizedIm </span><span class="pun">=</span><span class="pln"> catIm</span><span class="pun">.</span><span class="pln">resize</span><span class="pun">((</span><span class="pln">int</span><span class="pun">(</span><span class="pln">width </span><span class="pun">/</span><span class="pln"> </span><span class="lit">2</span><span class="pun">),</span><span class="pln"> int</span><span class="pun">(</span><span class="pln">height </span><span class="pun">/</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)))</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> quartersizedIm</span><span class="pun">.</span><span class="pln">save</span><span class="pun">(</span><span class="str">'quartersized.png'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➌</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> svelteIm </span><span class="pun">=</span><span class="pln"> catIm</span><span class="pun">.</span><span class="pln">resize</span><span class="pun">((</span><span class="pln">width</span><span class="pun">,</span><span class="pln"> height </span><span class="pun">+</span><span class="pln"> </span><span class="lit">300</span><span class="pun">))</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> svelteIm</span><span class="pun">.</span><span class="pln">save</span><span class="pun">(</span><span class="str">'svelte.png'</span><span class="pun">)</span></pre>

<p>
	نسند القيمتين الموجودتين في المجموعة <code>catIm.size</code> إلى المتغيرين <code>width</code> و <code>height</code> ➊، حيث يؤدي استخدام هذين المتغيرين بدلًا من <code>catIm.size[0]‎</code> و <code>catIm.size[1]‎</code> إلى جعل بقية الشيفرة البرمجية أكثر قابلية للقراءة.
</p>

<p>
	يمرّر استدعاء التابع <code>resize()‎</code> الأول القيمة <code>int(width / 2)‎</code> للعرض الجديد والقيمة <code>int(height / 2)‎</code> للارتفاع الجديد ➋، لذا سيكون لكائن <code>Image</code> الذي يعيده التابع <code>resize()‎</code> نصف طول ونصف عرض الصورة الأصلية أو ربع حجم الصورة الأصلية. يقبل التابع <code>resize()‎</code> الأعداد الصحيحة فقط في وسيط المجموعة الخاص به، ولذلك يجب تغليف عمليتي القسمة على 2 باستدعاء الدالة <code>int()‎</code>.
</p>

<p>
	يحافظ تغيير الحجم على النسب نفسها للعرض والارتفاع، ولكن لا حاجة إلى أن يكون العرض والارتفاع الجديدين المُمرَّرين إلى التابع <code>resize()‎</code> متناسبين مع الصورة الأصلية. يحتوي المتغير <code>svelteIm</code> على كائن <code>Image</code> له العرض الأصلي ولكن يكون ارتفاعه أكبر من الطول الأصلي بمقدار 300 بكسل ➌، مما يمنح القطة مظهرًا أرشق. لاحظ أن التابع <code>resize()‎</code> لا يعدّل كائن <code>Image</code> ذاته، بل يعيد كائن <code>Image</code> جديد.
</p>

<h3 id="-5">
	تدوير وقلب الصور
</h3>

<p>
	يمكن تدوير الصور باستخدام التابع <code>rotate()‎</code> الذي يعيد كائن <code>Image</code> جديد للصورة المُدوَّرة ويترك كائن <code>Image</code> الأصلي دون تغيير. وسيط التابع <code>rotate()‎</code> هو عدد صحيح أو عدد عشري يمثّل عدد الدرجات لتدوير الصورة بعكس اتجاه عقارب الساعة. أدخِل مثلًا ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8784_28" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> PIL </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Image</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> catIm </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="str">'zophie.png'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> catIm</span><span class="pun">.</span><span class="pln">rotate</span><span class="pun">(</span><span class="lit">90</span><span class="pun">).</span><span class="pln">save</span><span class="pun">(</span><span class="str">'rotated90.png'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> catIm</span><span class="pun">.</span><span class="pln">rotate</span><span class="pun">(</span><span class="lit">180</span><span class="pun">).</span><span class="pln">save</span><span class="pun">(</span><span class="str">'rotated180.png'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> catIm</span><span class="pun">.</span><span class="pln">rotate</span><span class="pun">(</span><span class="lit">270</span><span class="pun">).</span><span class="pln">save</span><span class="pun">(</span><span class="str">'rotated270.png'</span><span class="pun">)</span></pre>

<p>
	لاحظ كيفية سَلسَلة استدعاءات التوابع من خلال استدعاء التابع <code>save()‎</code> مباشرةً مع كائن <code>Image</code> الذي يعيده التابع <code>rotate()‎</code>. يؤدي الاستدعاء الأول للتابعين <code>rotate()‎</code> و <code>save()‎</code> إلى إنشاء كائن <code>Image</code> جديد يمثّل الصورة المُدوَّرة بعكس اتجاه عقارب الساعة بمقدار 90 درجة وحفظ الصورة المُدوَّرة في الملف rotated90.png، ويفعل الاستدعاءان الثاني والثالث الشيء نفسه، ولكن بمقدار 180 درجة و270 درجة. ستبدو النتائج كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="157687" href="https://academy.hsoub.com/uploads/monthly_2024_09/07_000139.jpg.09747d644efc93ebf27fdf0feb85fe27.jpg" rel=""><img alt="07 000139" class="ipsImage ipsImage_thumbnailed" data-fileid="157687" data-unique="b66jtfhom" src="https://academy.hsoub.com/uploads/monthly_2024_09/07_000139.jpg.09747d644efc93ebf27fdf0feb85fe27.jpg"> </a>
</p>

<p style="text-align: center;">
	الصورة الأصلية (على اليسار) والصورة المُدوَّرة بعكس اتجاه عقارب الساعة بمقدار 90 و 180 و 270 درجة
</p>

<p>
	لاحظ أن عرض الصورة وارتفاعها يتغيران عند تدوير الصورة بمقدار 90 أو 270 درجة، ولكن إذا دوّرتَ الصورة بمقدار آخر، فستحافظ الصورة على الأبعاد الأصلية. نستخدم في نظام ويندوز Windows خلفية سوداء لملء أي فراغات ناتجة عن التدوير، كما هو موضح في الشكل الآتي، بينما نستخدم في نظام ماك macOS بكسلات شفافة لهذه الفراغات.
</p>

<p>
	يحتوي التابع <code>rotate()‎</code> على وسيط كلمات مفتاحية Keyword Argument اختياري هو <code>expand</code>، حيث يمكن ضبط هذا الوسيط على القيمة <code>True</code> لتكبير أبعاد الصورة لتناسب الصورة الجديدة المُدوَّرة بالكامل. أدخِل مثلًا ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8784_30" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> catIm</span><span class="pun">.</span><span class="pln">rotate</span><span class="pun">(</span><span class="lit">6</span><span class="pun">).</span><span class="pln">save</span><span class="pun">(</span><span class="str">'rotated6.png'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> catIm</span><span class="pun">.</span><span class="pln">rotate</span><span class="pun">(</span><span class="lit">6</span><span class="pun">,</span><span class="pln"> expand</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">).</span><span class="pln">save</span><span class="pun">(</span><span class="str">'rotated6_expanded.png'</span><span class="pun">)</span></pre>

<p>
	يدوّر الاستدعاء الأول الصورة بمقدار 6 درجات ويحفظها في الملف rotate6.png كما هو موضَّح على يسار الشكل التالي، ويدوّر الاستدعاء الثاني الصورة بمقدار 6 درجات مع ضبط الوسيط <code>expand</code> على القيمة <code>True</code> ويحفظها في الملف rotate6_expanded.png كما هو موضَّح على يمين الشكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="157683" href="https://academy.hsoub.com/uploads/monthly_2024_09/08_000082.jpg.c923be4191384b7150ec9a1f50975dde.jpg" rel=""><img alt="08 000082" class="ipsImage ipsImage_thumbnailed" data-fileid="157683" data-unique="bhvzt8wog" src="https://academy.hsoub.com/uploads/monthly_2024_09/08_000082.jpg.c923be4191384b7150ec9a1f50975dde.jpg"> </a>
</p>

<p style="text-align: center;">
	تدوير الصورة بمقدار 6 درجات تدويرًا عاديًا (على اليسار) والتدوير مع الوسيط <code>expand=True</code> (على اليمين)
</p>

<p>
	يمكنك أيضًا قلب الصورة Mirror Flip باستخدام التابع <code>transpose()‎</code>، حيث يجب تمرير إما المعامل <code>Image.FLIP_LEFT_RIGHT</code> أو المعامل <code>Image.FLIP_TOP_BOTTOM</code> إلى هذا التابع. أدخِل مثلًا ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8784_32" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> catIm</span><span class="pun">.</span><span class="pln">transpose</span><span class="pun">(</span><span class="typ">Image</span><span class="pun">.</span><span class="pln">FLIP_LEFT_RIGHT</span><span class="pun">).</span><span class="pln">save</span><span class="pun">(</span><span class="str">'horizontal_flip.png'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> catIm</span><span class="pun">.</span><span class="pln">transpose</span><span class="pun">(</span><span class="typ">Image</span><span class="pun">.</span><span class="pln">FLIP_TOP_BOTTOM</span><span class="pun">).</span><span class="pln">save</span><span class="pun">(</span><span class="str">'vertical_flip.png'</span><span class="pun">)</span></pre>

<p>
	ينشئ التابع ‎<code>transpose()‎</code>‎ -مثل التابع <code>rotate()‎</code>- كائن <code>Image</code> جديد. مرّرنا في مثالنا المعامل <code>Image.FLIP_LEFT_RIGHT</code> إلى هذا التابع لقلب الصورة أفقيًا ثم حفظنا النتيجة في الملف horizontal<code>_</code>flip.png، ويمكننا قلب الصورة عموديًا من خلال تمرير <code>Image.FLIP_TOP_BOTTOM</code> ونحفظ النتيجة في الملف vertical<code>_</code>flip.png. ستبدو النتائج كما هو موضّح في الشكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="157693" href="https://academy.hsoub.com/uploads/monthly_2024_09/09_000030.jpg.b984d42aa49f415652a5d140631c8e47.jpg" rel=""><img alt="09 000030" class="ipsImage ipsImage_thumbnailed" data-fileid="157693" data-unique="xdbiuvyg2" src="https://academy.hsoub.com/uploads/monthly_2024_09/09_000030.jpg.b984d42aa49f415652a5d140631c8e47.jpg"> </a>
</p>

<p style="text-align: center;">
	الصورة الأصلية (على اليسار)، وقلب الصورة أفقيًا (في الوسط)، وقلب الصورة عموديًا (على اليمين)
</p>

<h3 id="-6">
	تغيير البكسلات الفردية
</h3>

<p>
	يمكن استرداد لون البكسل الفردي أو ضبطه باستخدام التابعين <code>getpixel()‎</code> و <code>putpixel()‎</code>، حيث يأخذ هذان التابعان مجموعةً تمثل إحداثيات x و y للبكسل، ويأخذ التابع <code>putpixel()‎</code> أيضًا وسيطًا إضافيًا هو مجموعة تمثّل لون البكسل، فهذا الوسيط هو مجموعة RGBA مؤلفة من أربعة أعداد صحيحة أو مجموعة RGB مؤلفة من ثلاثة أعداد صحيحة. أدخل مثلًا ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8784_34" style=""><span class="pln">   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> PIL </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Image</span><span class="pln">
</span><span class="pun">➊</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> im </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">.</span><span class="pln">new</span><span class="pun">(</span><span class="str">'RGBA'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">))</span><span class="pln">
</span><span class="pun">➋</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> im</span><span class="pun">.</span><span class="pln">getpixel</span><span class="pun">((</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">))</span><span class="pln">
   </span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➌</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> x </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">100</span><span class="pun">):</span><span class="pln">
           </span><span class="kwd">for</span><span class="pln"> y </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">50</span><span class="pun">):</span><span class="pln">
            </span><span class="pun">➍</span><span class="pln"> im</span><span class="pun">.</span><span class="pln">putpixel</span><span class="pun">((</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">),</span><span class="pln"> </span><span class="pun">(</span><span class="lit">210</span><span class="pun">,</span><span class="pln"> </span><span class="lit">210</span><span class="pun">,</span><span class="pln"> </span><span class="lit">210</span><span class="pun">))</span><span class="pln">

   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> PIL </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">ImageColor</span><span class="pln">
</span><span class="pun">➎</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> x </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">100</span><span class="pun">):</span><span class="pln">
           </span><span class="kwd">for</span><span class="pln"> y </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">50</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">):</span><span class="pln">
            </span><span class="pun">➏</span><span class="pln"> im</span><span class="pun">.</span><span class="pln">putpixel</span><span class="pun">((</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">),</span><span class="pln"> </span><span class="typ">ImageColor</span><span class="pun">.</span><span class="pln">getcolor</span><span class="pun">(</span><span class="str">'darkgray'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'RGBA'</span><span class="pun">))</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> im</span><span class="pun">.</span><span class="pln">getpixel</span><span class="pun">((</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">))</span><span class="pln">
   </span><span class="pun">(</span><span class="lit">210</span><span class="pun">,</span><span class="pln"> </span><span class="lit">210</span><span class="pun">,</span><span class="pln"> </span><span class="lit">210</span><span class="pun">,</span><span class="pln"> </span><span class="lit">255</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> im</span><span class="pun">.</span><span class="pln">getpixel</span><span class="pun">((</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">50</span><span class="pun">))</span><span class="pln">
   </span><span class="pun">(</span><span class="lit">169</span><span class="pun">,</span><span class="pln"> </span><span class="lit">169</span><span class="pun">,</span><span class="pln"> </span><span class="lit">169</span><span class="pun">,</span><span class="pln"> </span><span class="lit">255</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> im</span><span class="pun">.</span><span class="pln">save</span><span class="pun">(</span><span class="str">'putPixel.png'</span><span class="pun">)</span></pre>

<p>
	ننشئ أولًا صورةً جديدة، والتي هي مربع شفاف أبعاده 100×100 ➊، ثم نستدعي التابع <code>getpixel()‎</code> مع بعض الإحداثيات في هذه الصورة، مما يؤدي إلى إعادة المجموعة <code>‎(0, 0, 0, 0)‎</code> لأن الصورة شفافة ➋. يمكن تلوين البكسلات في هذه الصورة من خلال استخدام حلقات <code>for</code> متداخلة للمرور على جميع البكسلات في النصف العلوي من الصورة ➌ وتلوين كلّ بكسل باستخدام التابع <code>putpixel()‎</code> ➍، حيث مرّرنا مجموعة RGB هي <code>‎(210, 210, 210)‎</code> باللون الرمادي الفاتح إلى التابع <code>putpixel()‎</code>.
</p>

<p>
	لنفترض أننا نريد تلوين النصف السفلي من الصورة باللون الرمادي الداكن، ولكننا لا نعرف مجموعة RGB للون الرمادي الداكن، حيث لا يقبل التابع <code>putpixel()‎</code> اسم لون معياري مثل <code>'darkgray'</code>، لذلك يجب استخدام الدالة <code>ImageColor.getcolor()‎</code> للحصول على مجموعة اللون المقابلة للون <code>'darkgray'</code>. لنمر الآن ضمن حلقة على البكسلات الموجودة في النصف السفلي من الصورة ➎، ونمرّر القيمة المُعادة من الدالة <code>ImageColor.getcolor()‎</code> إلى التابع <code>putpixel()‎</code> ➏، ويجب أن يكون لدينا الآن صورة رمادية فاتحة في النصف العلوي ورمادية داكنة في النصف السفلي كما هو موضّح في الشكل التالي. يمكنك استدعاء التابع <code>getpixel()‎</code> مع بعض الإحداثيات للتأكد من أن لون أيّ بكسل هو ما تتوقعه. أخيرًا، احفظ الصورة في الملف putPixel.png.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="157689" href="https://academy.hsoub.com/uploads/monthly_2024_09/10_000121.jpg.c4678d7e28a612684897787b29a72dc4.jpg" rel=""><img alt="10 000121" class="ipsImage ipsImage_thumbnailed" data-fileid="157689" data-unique="knmwcwkaz" src="https://academy.hsoub.com/uploads/monthly_2024_09/10_000121.jpg.c4678d7e28a612684897787b29a72dc4.jpg"> </a>
</p>

<p style="text-align: center;">
	الصورة putPixel.png
</p>

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

<h2 id="-7">
	تطبيق عملي: إضافة شعار إلى صورة
</h2>

<p>
	لنفترض أن لديك مهمة مملة تتمثل في تغيير حجم آلاف الصور وإضافة شعار صغير يمثل علامة مائية في زاوية كل من هذه الصور، إذ قد يستغرق ذلك الأمر وقتًا طويلًا باستخدام برنامج رسوميات أساسي مثل برنامج الرسام Paint أو Paintbrush. يمكن لتطبيق رسوميات أكثر تقدمًا مثل برنامج الفوتوشوب Photoshop إنجاز معالجةٍ لمجموعة من الصور، ولكنه يكلف مئات الدولارات. إذًا لنكتب سكربتًا ينجز هذه المهمة نيابةً عنك.
</p>

<p>
	لنفترض أن الشكل التالي هو الشعار الذي تريد إضافته إلى الزاوية اليمنى السفلية من كل صورة، وهذا الشعار هو رمزٌ لقطة سوداء ذات حدود بيضاء مع جعل بقية الصورة شفافة:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="157686" href="https://academy.hsoub.com/uploads/monthly_2024_09/11_000000.jpg.e1253994cc14929a478658e46b670cd0.jpg" rel=""><img alt="11 000000" class="ipsImage ipsImage_thumbnailed" data-fileid="157686" data-unique="xz29n8253" src="https://academy.hsoub.com/uploads/monthly_2024_09/11_000000.jpg.e1253994cc14929a478658e46b670cd0.jpg"> </a>
</p>

<p style="text-align: center;">
	الشعار المراد إضافته إلى الصورة
</p>

<p>
	إليك الخطوات العامة التي يجب أن يطبّقها برنامجك:
</p>

<ol>
	<li>
		تحميل صورة الشعار.
	</li>
	<li>
		المرور ضمن حلقة على جميع الملفات ذات الامتداد <code>‎.png</code> و <code>‎.jpg</code> الموجودة في مجلد العمل.
	</li>
	<li>
		التحقق مما إذا كانت الصورة أعرض أو أطول من 300 بكسل.
	</li>
	<li>
		إذا كان الأمر كذلك، فيجب تقليل العرض أو الارتفاع (الأكبر) إلى 300 بكسل وتقليص البعد الآخر بمقدارٍ متناسب معه.
	</li>
	<li>
		لصق صورة الشعار في زاوية الصورة.
	</li>
	<li>
		حفظ الصور المُعدَّلة في مجلد آخر.
	</li>
</ol>

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

<ol>
	<li>
		فتح الملف catlogo.png بوصفه كائن <code>Image</code>.
	</li>
	<li>
		المرور ضمن حلقة على السلاسل النصية التي يعيدها التابع <code>os.listdir('.')‎</code>.
	</li>
	<li>
		الحصول على عرض الصورة وارتفاعها من السمة <code>size</code>.
	</li>
	<li>
		حساب العرض والارتفاع الجديدين للصورة التي غيّرنا حجمها.
	</li>
	<li>
		استدعاء التابع <code>resize()‎</code> لتغيير حجم الصورة.
	</li>
	<li>
		استدعاء التابع <code>paste()‎</code> للصق الشعار.
	</li>
	<li>
		استدعاء التابع <code>save()‎</code> لحفظ التغييرات باستخدام اسم الملف الأصلي.
	</li>
</ol>

<h3 id="-8">
	الخطوة الأولى: فتح صورة الشعار
</h3>

<p>
	افتح تبويبًا جديدًا في محرّرك لإنشاء ملف جديد للمشروع، وأدخِل الشيفرة البرمجية التالية، واحفظها بالاسم resizeAndAddLogo.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8784_36" style=""><span class="pln">   </span><span class="com">#! python3</span><span class="pln">
   </span><span class="com"># resizeAndAddLogo.py - تغيير حجم جميع الصور الموجودة في مجلد العمل الحالي لتلائم مربعًا </span><span class="pln">
   </span><span class="com"># ‫أبعاده 300x300، ثم إضافة الصورة catlogo.png إلى الزاوية السفلية اليمنى</span><span class="pln">
   </span><span class="kwd">import</span><span class="pln"> os
   </span><span class="kwd">from</span><span class="pln"> PIL </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Image</span><span class="pln">

</span><span class="pun">➊</span><span class="pln"> SQUARE_FIT_SIZE </span><span class="pun">=</span><span class="pln"> </span><span class="lit">300</span><span class="pln">
</span><span class="pun">➋</span><span class="pln"> LOGO_FILENAME </span><span class="pun">=</span><span class="pln"> </span><span class="str">'catlogo.png'</span><span class="pln">

</span><span class="pun">➌</span><span class="pln"> logoIm </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="pln">LOGO_FILENAME</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➍</span><span class="pln"> logoWidth</span><span class="pun">,</span><span class="pln"> logoHeight </span><span class="pun">=</span><span class="pln"> logoIm</span><span class="pun">.</span><span class="pln">size

   </span><span class="com"># المرور ضمن حلقة على جميع الملفات الموجودة في مجلد العمل</span><span class="pln">

   </span><span class="com"># التحقق مما إذا كانت الصورة بحاجة إلى تغيير حجمها</span><span class="pln">

   </span><span class="com"># حساب العرض والارتفاع الجديدين لتغيير الحجم</span><span class="pln">

   </span><span class="com"># تغيير حجم الصورة</span><span class="pln">

   </span><span class="com"># إضافة الشعار</span><span class="pln">

   </span><span class="com"># حفظ التغييرات</span></pre>

<p>
	سهّلنا تغيير البرنامج لاحقًا من خلال إعداد الثاثبتين <code>SQUARE_FIT_SIZE</code> ➊ و <code>LOGO_FILENAME</code> ➋ في بداية البرنامج، فلنفترض أن الشعار الذي تضيفه ليس رمزًا لقطة، أو لنفترض أنك تقلّل البعد الأكبر للصور الناتجة إلى قيمة مغايرة عن 300 بكسل، إذ يمكنك فتح الشيفرة البرمجية وتغيير تلك القيم مرة واحدة فقط باستخدام هذه الثوابت في بداية البرنامج، أو يمكنك إجراء ذلك بحيث تأخذ قيم هذه الثوابت من وسطاء <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%A7-%D9%87%D9%88-%D8%B3%D8%B7%D8%B1-%D8%A7%D9%84%D8%A3%D9%88%D8%A7%D9%85%D8%B1-%D8%9F-r353/" rel="">سطر الأوامر</a>. إن لم تستخدم هذه الثوابت، فيجب عليك البحث في الشيفرة البرمجية عن جميع نسخ القيم <code>300</code> و <code>'catlogo.png'</code> ووضع قيمٍ أخرى لمشروعك الجديد مكانها، وبالتالي يجعل استخدام الثوابت برنامجك أعم.
</p>

<p>
	تعيد الدالة <code>Image.open()‎</code> كائن <code>Image</code> للشعار ➌. نسند القيم الواردة من السمة <code>logoIm.size</code> إلى المتغيرين <code>logoWidth</code> و <code>logoHeight</code> لسهولة القراءة ➍.
</p>

<p>
	<strong>ملاحظة</strong>: تُعَد بقية البرنامج شيفرة هيكلية للتعليقات الموجودة في نهاية الشيفرة البرمجية السابقة حاليًا.
</p>

<h3 id="-9">
	الخطوة الثانية: المرور ضمن حلقة على جميع الملفات وفتح الصور
</h3>

<p>
	يجب الآن العثور على جميع الملفات ذات الامتداد <code>‎.png</code> و <code>‎.jpg</code> في مجلد العمل الحالي، ولكننا لا نريد إضافة صورة الشعار إلى صورة الشعار نفسها، لذلك يجب على البرنامج تخطي أيّ صورة لاسم ملف مماثل لقيمة الثابت <code>LOGO_FILENAME</code>. إذًا أضِف ما يلي إلى شيفرتك البرمجية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8784_38" style=""><span class="pln">   </span><span class="com">#! python3</span><span class="pln">
   </span><span class="com"># resizeAndAddLogo.py - تغيير حجم جميع الصور الموجودة في مجلد العمل الحالي لتلائم مربعًا </span><span class="pln">
   </span><span class="com"># ‫أبعاده 300x300، ثم إضافة الصورة catlogo.png إلى الزاوية السفلية اليمنى</span><span class="pln">

   </span><span class="kwd">import</span><span class="pln"> os
   </span><span class="kwd">from</span><span class="pln"> PIL </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Image</span><span class="pln">

   </span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">

   os</span><span class="pun">.</span><span class="pln">makedirs</span><span class="pun">(</span><span class="str">'withLogo'</span><span class="pun">,</span><span class="pln"> exist_ok</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
   </span><span class="com"># المرور ضمن حلقة على جميع الملفات الموجودة في مجلد العمل</span><span class="pln">
</span><span class="pun">➊</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> filename </span><span class="kwd">in</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">listdir</span><span class="pun">(</span><span class="str">'.'</span><span class="pun">):</span><span class="pln">
    </span><span class="pun">➋</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> </span><span class="pun">(</span><span class="pln">filename</span><span class="pun">.</span><span class="pln">endswith</span><span class="pun">(</span><span class="str">'.png'</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">or</span><span class="pln"> filename</span><span class="pun">.</span><span class="pln">endswith</span><span class="pun">(</span><span class="str">'.jpg'</span><span class="pun">))</span><span class="pln"> \
          </span><span class="kwd">or</span><span class="pln"> filename </span><span class="pun">==</span><span class="pln"> LOGO_FILENAME</span><span class="pun">:</span><span class="pln">
        </span><span class="pun">➌</span><span class="pln"> </span><span class="kwd">continue</span><span class="pln">    </span><span class="com"># تخطي الملفات التي ليست صورًا وملف الشعار نفسه</span><span class="pln">

    </span><span class="pun">➍</span><span class="pln"> im </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="pln">filename</span><span class="pun">)</span><span class="pln">
       width</span><span class="pun">,</span><span class="pln"> height </span><span class="pun">=</span><span class="pln"> im</span><span class="pun">.</span><span class="pln">size

   </span><span class="pun">--</span><span class="pln">snip</span><span class="pun">–</span></pre>

<p>
	أولًا، ينشئ استدعاء التابع <code>os.makedirs()‎</code> مجلدًا بالاسم withLogo لتخزين الصور النهائية مع الشعار بدلًا من الكتابة فوق ملفات الصور الأصلية، ويمنع وسيط الكلمات المفتاحية <code>exist_ok=True</code> التابع <code>os.makedirs()‎</code> من رفع استثناء إذا كان المجلد withLogo موجودًا مسبقًا. نمر ضمن حلقة على جميع الملفات الموجودة في مجلد العمل باستخدام التابع <code>os.listdir('.')‎</code> ➊، وتتحقق تعليمة <code>if</code> الطويلة ➋ عبر هذه الحلقة مما إذا كانت جميع أسماء الملفات لا تنتهي بالامتداد <code>‎.png</code> أو <code>‎.jpg</code>، فإذا كان الأمر كذلك أو كان الملف صورة الشعار نفسه، فيجب أن تتخطاه الحلقة وتستخدم التعليمة <code>continue</code> ➌ للانتقال إلى الملف التالي. إذا كان اسم الملف <code>filename</code> ينتهي بالامتداد <code>‎.png</code> أو <code>‎.jpg</code> (وليس ملف الشعار)، فيمكنك فتحه بوصفه كائن <code>Image</code> ➍ وضبط العرض <code>width</code> والارتفاع <code>height</code> الخاصين به.
</p>

<h3 id="-10">
	الخطوة الثالثة: تغيير حجم الصور
</h3>

<p>
	يجب أن يغيّر البرنامج حجم الصورة فقط إذا كان العرض أو الارتفاع أكبر من قيمة الثابت <code>SQUARE_FIT_SIZE</code> (أي 300 بكسل في مثالنا) فقط، لذا ضع الشيفرة البرمجية الخاصة بتغيير الحجم ضمن تعليمة <code>if</code> التي تتحقق من متغيرات العرض <code>width</code> والارتفاع <code>height</code>. إذًا أضِف الشيفرة البرمجية التالية إلى برنامجك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8784_40" style=""><span class="com">#! python3</span><span class="pln">
</span><span class="com"># resizeAndAddLogo.py - تغيير حجم جميع الصور الموجودة في مجلد العمل الحالي لتلائم مربعًا </span><span class="pln">
</span><span class="com"># ‫أبعاده 300x300، ثم إضافة الصورة catlogo.png إلى الزاوية السفلية اليمنى</span><span class="pln">

</span><span class="kwd">import</span><span class="pln"> os
</span><span class="kwd">from</span><span class="pln"> PIL </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Image</span><span class="pln">

</span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">

     </span><span class="com"># التحقق مما إذا كانت الصورة بحاجة إلى تغيير حجمها</span><span class="pln">
     </span><span class="kwd">if</span><span class="pln"> width </span><span class="pun">&gt;</span><span class="pln"> SQUARE_FIT_SIZE </span><span class="kwd">and</span><span class="pln"> height </span><span class="pun">&gt;</span><span class="pln"> SQUARE_FIT_SIZE</span><span class="pun">:</span><span class="pln">
         </span><span class="com"># حساب العرض والارتفاع الجديدين لتغيير الحجم</span><span class="pln">
         </span><span class="kwd">if</span><span class="pln"> width </span><span class="pun">&gt;</span><span class="pln"> height</span><span class="pun">:</span><span class="pln">
            </span><span class="pun">➊</span><span class="pln"> height </span><span class="pun">=</span><span class="pln"> int</span><span class="pun">((</span><span class="pln">SQUARE_FIT_SIZE </span><span class="pun">/</span><span class="pln"> width</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> height</span><span class="pun">)</span><span class="pln">
               width </span><span class="pun">=</span><span class="pln"> SQUARE_FIT_SIZE
         </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
            </span><span class="pun">➋</span><span class="pln"> width </span><span class="pun">=</span><span class="pln"> int</span><span class="pun">((</span><span class="pln">SQUARE_FIT_SIZE </span><span class="pun">/</span><span class="pln"> height</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> width</span><span class="pun">)</span><span class="pln">
               height </span><span class="pun">=</span><span class="pln"> SQUARE_FIT_SIZE

           </span><span class="com"># تغيير حجم الصورة</span><span class="pln">
           </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Resizing %s...'</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> </span><span class="pun">(</span><span class="pln">filename</span><span class="pun">))</span><span class="pln">
        </span><span class="pun">➌</span><span class="pln"> im </span><span class="pun">=</span><span class="pln"> im</span><span class="pun">.</span><span class="pln">resize</span><span class="pun">((</span><span class="pln">width</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">))</span><span class="pln">

</span><span class="pun">--</span><span class="pln">snip</span><span class="pun">–</span></pre>

<p>
	إذا كانت الصورة بحاجة إلى تغيير حجمها، فيجب معرفة ما إذا كان عرض الصورة أو ارتفاعها أكبر من قيمة الثابت <code>SQUARE_FIT_SIZE</code> (أي 300 بكسل في مثالنا)، وإذا كان العرض <code>width</code> أكبر من الارتفاع <code>height</code>، فيجب تقليل الارتفاع بمقدار النسبة نفسها لتقليل العرض ➊، وهذه النسبة هي قيمة الثابت <code>SQUARE_FIT_SIZE</code> مقسومة على العرض الحالي، وتساوي قيمة الارتفاع <code>height</code> الجديدة النسبة مضروبة بقيمة الارتفاع <code>height</code> الحالية. يعيد معامل القسمة قيمة عشرية، ولكن يتطلب التابع <code>resize()‎</code> أن تكون الأبعاد أعدادًا صحيحة، لذا تذكّر تحويل النتيجة إلى عدد صحيح باستخدام الدالة <code>int()‎</code>. أخيرًا، ستُضبَط قيمة العرض <code>width</code> الجديدة على قيمة الثابت SQUARE<code>_</code>FIT<code>_</code>SIZE.
</p>

<p>
	إذا كان الارتفاع <code>height</code> أكبر أو يساوي العرض <code>width</code> (عالجنا كلتا الحالتين في تعليمة <code>else</code>)، فستُجرَى العملية الحسابية نفسها باستثناء التبديل بين المتغيرين <code>height</code> و <code>width</code> ➋.
</p>

<p>
	سيحتوي المتغيران <code>width</code> و <code>height</code> على أبعاد الصورة الجديدة، لذا مرّرهما إلى التابع <code>resize()‎</code> وخزّن كائن <code>Image</code> المُعاد في المتغير <code>im</code> ➌.
</p>

<h3 id="-11">
	الخطوة الرابعة: إضافة الشعار وحفظ التغييرات
</h3>

<p>
	يجب لصق الشعار في الزاوية السفلية اليمنى سواء تغيّر حجم الصورة أم لا، ويعتمد المكان الذي يجب لصق الشعار فيه على حجم الصورة وحجم الشعار، حيث يوضح الشكل التالي كيفية حساب موضع اللصق. سيكون الإحداثي الأيسر لمكان لصق الشعار هو عرض الصورة مطروحًا منه عرض الشعار، وسيكون الإحداثي العلوي لمكان لصق الشعار هو ارتفاع الصورة مطروحًا منه ارتفاع الشعار.
</p>

<p style="text-align: center;">
	<img alt="12_000011-معرب.png" class="ipsImage ipsImage_thumbnailed" data-fileid="157684" data-ratio="66.76" data-unique="z6vgid4b2" width="340" src="https://academy.hsoub.com/uploads/monthly_2024_09/12_000011-.png.0731dfdb0aec9993beaba372d53ff962.png">
</p>

<p style="text-align: center;">
	يجب أن تكون الإحداثيات اليسرى والعلوية لوضع الشعار في الزاوية السفلية اليمنى هي عرض/ارتفاع الصورة مطروحًا منه عرض/ارتفاع الشعار
</p>

<p>
	يجب أن تحفظ شيفرتك البرمجية كائن <code>Image</code> المُعدَّل بعد لصق الشعار في الصورة. إذًا أضِف ما يلي إلى برنامجك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8784_42" style=""><span class="com">#! python3</span><span class="pln">
</span><span class="com"># resizeAndAddLogo.py - تغيير حجم جميع الصور الموجودة في مجلد العمل الحالي لتلائم مربعًا </span><span class="pln">
</span><span class="com"># ‫أبعاده 300x300، ثم إضافة الصورة catlogo.png إلى الزاوية السفلية اليمنى</span><span class="pln">

</span><span class="kwd">import</span><span class="pln"> os
</span><span class="kwd">from</span><span class="pln"> PIL </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Image</span><span class="pln">

</span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">

     </span><span class="com"># التحقق مما إذا كانت الصورة بحاجة إلى تغيير حجمها</span><span class="pln">
     </span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">

     </span><span class="com"># إضافة الشعار</span><span class="pln">
  </span><span class="pun">➊</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Adding logo to %s...'</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> </span><span class="pun">(</span><span class="pln">filename</span><span class="pun">))</span><span class="pln">
  </span><span class="pun">➋</span><span class="pln"> im</span><span class="pun">.</span><span class="pln">paste</span><span class="pun">(</span><span class="pln">logoIm</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">width </span><span class="pun">-</span><span class="pln"> logoWidth</span><span class="pun">,</span><span class="pln"> height </span><span class="pun">-</span><span class="pln"> logoHeight</span><span class="pun">),</span><span class="pln"> logoIm</span><span class="pun">)</span><span class="pln">

     </span><span class="com"># حفظ التغييرات</span><span class="pln">
  </span><span class="pun">➌</span><span class="pln"> im</span><span class="pun">.</span><span class="pln">save</span><span class="pun">(</span><span class="pln">os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="str">'withLogo'</span><span class="pun">,</span><span class="pln"> filename</span><span class="pun">))</span></pre>

<p>
	تطبع الشيفرة البرمجية الجديدة رسالة تخبر المستخدم بإضافة الشعار ➊، وتلصق <code>logoIm</code> على <code>im</code> عند الإحداثيات المحسوبة ➋، وتحفظ التغييرات في اسم ملف ضمن المجلد withLogo ➌. سيبدو الخرج كما يلي عند تشغيل هذا البرنامج مع الملف zophie.png بوصفه الصورة الوحيدة في مجلد العمل:
</p>

<pre class="ipsCode">Resizing zophie.png...
Adding logo to zophie.png…
</pre>

<p>
	سنغيّر الصورة zophie.png إلى صورة بحجم ‎225×300 بكسل تشبه الشكل التالي، وتذكّر أن التابع <code>paste()‎</code> لن يلصق البكسلات الشفافة إن لم تمرّر <code>logoIm</code> إلى الوسيط الثالث للتابع <code>paste()‎</code>. يمكن لهذا البرنامج تغيير حجم مئات الصور وإضافة شعار إليها تلقائيًا في بضع دقائق فقط.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="157688" href="https://academy.hsoub.com/uploads/monthly_2024_09/13_000106.jpg.813e277a3c31c73690cf635537f22e91.jpg" rel=""><img alt="13 000106" class="ipsImage ipsImage_thumbnailed" data-fileid="157688" data-unique="6ulvl7ubd" src="https://academy.hsoub.com/uploads/monthly_2024_09/13_000106.jpg.813e277a3c31c73690cf635537f22e91.jpg"> </a>
</p>

<p style="text-align: center;">
	غيّرنا حجم الصورة zophie.png وأضفنا الشعار (على اليسار).
</p>

<p>
	إذا نسيت الوسيط الثالث، فستُنسَخ البكسلات الشفافة في الشعار بوصفها بكسلات بيضاء (على اليمين)
</p>

<h3 id="-12">
	أفكار لبرامج مماثلة
</h3>

<p>
	يمكن أن تكون القدرة على دمج الصور أو تعديل أحجامها دفعة واحدة مفيدة في العديد من التطبيقات، حيث يمكنك كتابة برامج مماثلة تنجز المهام التالية:
</p>

<ul>
	<li>
		إضافة نص أو <a href="https://academy.hsoub.com/programming/general/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%B9%D9%86%D9%88%D8%A7%D9%86-url-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9%D9%87-r1435/" rel="">عنوان URL</a> لموقع ويب إلى الصور.
	</li>
	<li>
		إضافة علامات زمنية Timestamps إلى الصور.
	</li>
	<li>
		نسخ الصور أو نقلها إلى مجلدات مختلفة بناءً على أحجامها.
	</li>
	<li>
		إضافة علامة مائية شفافة تقريبًا إلى الصورة لمنع الآخرين من نسخها.
	</li>
</ul>

<h2 id="-13">
	الرسم على الصور
</h2>

<p>
	إذا أردتَ رسم خطوط أو مستطيلات أو دوائر أو أشكال بسيطة أخرى على صورة ما، فاستخدم الوحدة <code>ImageDraw</code> الخاصة بوحدة Pillow. أدخِل مثلًا ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8784_44" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> PIL </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">,</span><span class="pln"> </span><span class="typ">ImageDraw</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> im </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">.</span><span class="pln">new</span><span class="pun">(</span><span class="str">'RGBA'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="lit">200</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">),</span><span class="pln"> </span><span class="str">'white'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> draw </span><span class="pun">=</span><span class="pln"> </span><span class="typ">ImageDraw</span><span class="pun">.</span><span class="typ">Draw</span><span class="pun">(</span><span class="pln">im</span><span class="pun">)</span></pre>

<p>
	نستورد أولًا الوحدتين <code>Image</code> و <code>ImageDraw</code>، ثم ننشئ صورة جديدة، والتي هي صورة بيضاء أبعادها 200‎×200 ، ونخزّن كائن <code>Image</code> في المتغير <code>im</code>. نمرّر كائن <code>Image</code> إلى الدالة <code>ImageDraw.Draw()‎</code> للحصول على كائن <code>ImageDraw</code>، حيث يحتوي هذا الكائن على عدة توابع لرسم الأشكال والنصوص على كائن <code>Image</code>. نخزّن بعد ذلك كائن <code>ImageDraw</code> في المتغير <code>draw</code> حتى نتمكّن من استخدامه بسهولة في المثال التالي.
</p>

<h3 id="-14">
	رسم الأشكال
</h3>

<p>
	ترسم توابع الكائن <code>ImageDraw</code> التالية أنواعًا مختلفة من الأشكال على الصورة، وتُعَد معاملات <code>fill</code> و <code>outline</code> لهذه التوابع اختيارية وستُضبَط افتراضيًا على اللون الأبيض إذا تُرِكت دون تحديد.
</p>

<h4 id="-15">
	رسم النقاط
</h4>

<p>
	يرسم التابع <code>point(xy, fill)‎</code> بكسلات فردية، حيث يمثل الوسيط <code>xy</code> قائمةً من النقاط التي تريد رسمها، إذ يمكن أن تكون هذه القائمة قائمةً بمجموعات Tuples إحداثيات x و y مثل <code>‎[(x, y), (x, y), ...]‎</code>، أو قائمة بإحداثيات x و y بدون مجموعات مثل <code>‎[x1, y1, x2, y2, ...]‎</code>. يمثّل الوسيط <code>fill</code> لون النقاط وهو إما مجموعة RGBA أو سلسلة نصية لاسم اللون مثل <code>'red'</code>، ويُعَد هذا الوسيط اختياريًا.
</p>

<h4 id="-16">
	رسم الخطوط
</h4>

<p>
	يرسم التابع <code>line(xy, fill, width)‎</code> خطًا أو سلسلةً من الخطوط، حيث يمثّل الوسيط <code>xy</code> إما قائمة من المجموعات مثل <code>‎[(x, y), (x, y), ...]‎</code>، أو قائمةً من الأعداد الصحيحة مثل <code>‎[x1, y1, x2, y2, ...]‎</code>، وتُعَد كلّ نقطة واحدةً من النقاط المتصلة على الخطوط التي ترسمها. يمثّل الوسيط <code>fill</code> الاختياري لون الخطوط باستخدام اسم اللون أو مجموعة RGBA، ويمثّل الوسيط <code>width</code> الاختياري عرض الخطوط وتكون قيمته الافتراضية 1 إذا تُرِك دون تحديد.
</p>

<h4 id="-17">
	رسم المستطيلات
</h4>

<p>
	يرسم التابع <code>rectangle(xy, fill, outline)‎</code> مستطيلًا، حيث يمثّل الوسيط <code>xy</code> مجموعة مربعة وفق الصيغة <code>(left, top, right, bottom)</code>، إذ تحدّد القيم <code>left</code> و <code>top</code> إحداثيات x و y للزاوية العلوية اليسرى للمستطيل، بينما تحدد القيم <code>right</code> و <code>bottom</code> الزاوية السفلية اليمنى. يمثّل الوسيط الاختياري <code>fill</code> اللونَ الذي سيملأ الجزء الداخلي من المستطيل، ويمثّل الوسيط الاختياري <code>outline</code> لون المخطط المحيط بالمستطيل.
</p>

<h4 id="-18">
	رسم الأشكال البيضاوية
</h4>

<p>
	يرسم التابع <code>ellipse(xy, fill, outline)‎</code> شكلًا بيضاويًا، وإذا كان عرض وارتفاع هذا الشكل متطابقين، فسيرسم هذا التابع دائرة. يمثّل الوسيط <code>xy</code> المجموعة المربعة <code>(left, top, right, bottom)</code> التي تمثّل مربعًا يحتوي على الشكل البيضاوي بدقة، ويمثّل الوسيط الاختياري <code>fill</code> لون الجزء الداخلي من الشكل البيضاوي، ويمثل الوسيط الاختياري <code>outline</code> لون المخطط المحيط بالشكل البيضاوي.
</p>

<h4 id="-19">
	رسم المضلعات
</h4>

<p>
	يرسم التابع <code>polygon(xy, fill, outline)‎</code> مضلعًا عشوائيًا، حيث يمثّل الوسيط <code>xy</code> قائمةً من المجموعات مثل <code>‎[(x, y), (x, y), ...]‎</code> أو أعدادًا صحيحة مثل <code>‎[x1, y1, x2, y2, ...]‎</code>، والتي تمثّل النقاط التي تربط أضلاع المضلع، ويُربَط الزوج الأخير من الإحداثيات بالزوج الأول تلقائيًا. يمثل الوسيط الاختياري <code>fill</code> لون الجزء الداخلي من المضلع، ويمثّل الوسيط الاختياري <code>outline</code> لون المخطط المحيط بالمضلع.
</p>

<h4 id="-20">
	تطبيق عملي لرسم الأشكال
</h4>

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

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8784_46" style=""><span class="pln">   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> PIL </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">,</span><span class="pln"> </span><span class="typ">ImageDraw</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> im </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">.</span><span class="pln">new</span><span class="pun">(</span><span class="str">'RGBA'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="lit">200</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">),</span><span class="pln"> </span><span class="str">'white'</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> draw </span><span class="pun">=</span><span class="pln"> </span><span class="typ">ImageDraw</span><span class="pun">.</span><span class="typ">Draw</span><span class="pun">(</span><span class="pln">im</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➊</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> draw</span><span class="pun">.</span><span class="pln">line</span><span class="pun">([(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">),</span><span class="pln"> </span><span class="pun">(</span><span class="lit">199</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">),</span><span class="pln"> </span><span class="pun">(</span><span class="lit">199</span><span class="pun">,</span><span class="pln"> </span><span class="lit">199</span><span class="pun">),</span><span class="pln"> </span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">199</span><span class="pun">),</span><span class="pln"> </span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)],</span><span class="pln"> fill</span><span class="pun">=</span><span class="str">'black'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➋</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> draw</span><span class="pun">.</span><span class="pln">rectangle</span><span class="pun">((</span><span class="lit">20</span><span class="pun">,</span><span class="pln"> </span><span class="lit">30</span><span class="pun">,</span><span class="pln"> </span><span class="lit">60</span><span class="pun">,</span><span class="pln"> </span><span class="lit">60</span><span class="pun">),</span><span class="pln"> fill</span><span class="pun">=</span><span class="str">'blue'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➌</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> draw</span><span class="pun">.</span><span class="pln">ellipse</span><span class="pun">((</span><span class="lit">120</span><span class="pun">,</span><span class="pln"> </span><span class="lit">30</span><span class="pun">,</span><span class="pln"> </span><span class="lit">160</span><span class="pun">,</span><span class="pln"> </span><span class="lit">60</span><span class="pun">),</span><span class="pln"> fill</span><span class="pun">=</span><span class="str">'red'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➍</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> draw</span><span class="pun">.</span><span class="pln">polygon</span><span class="pun">(((</span><span class="lit">57</span><span class="pun">,</span><span class="pln"> </span><span class="lit">87</span><span class="pun">),</span><span class="pln"> </span><span class="pun">(</span><span class="lit">79</span><span class="pun">,</span><span class="pln"> </span><span class="lit">62</span><span class="pun">),</span><span class="pln"> </span><span class="pun">(</span><span class="lit">94</span><span class="pun">,</span><span class="pln"> </span><span class="lit">85</span><span class="pun">),</span><span class="pln"> </span><span class="pun">(</span><span class="lit">120</span><span class="pun">,</span><span class="pln"> </span><span class="lit">90</span><span class="pun">),</span><span class="pln"> </span><span class="pun">(</span><span class="lit">103</span><span class="pun">,</span><span class="pln"> </span><span class="lit">113</span><span class="pun">)),</span><span class="pln">
   fill</span><span class="pun">=</span><span class="str">'brown'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➎</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> i </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">):</span><span class="pln">
           draw</span><span class="pun">.</span><span class="pln">line</span><span class="pun">([(</span><span class="pln">i</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">),</span><span class="pln"> </span><span class="pun">(</span><span class="lit">200</span><span class="pun">,</span><span class="pln"> i </span><span class="pun">-</span><span class="pln"> </span><span class="lit">100</span><span class="pun">)],</span><span class="pln"> fill</span><span class="pun">=</span><span class="str">'green'</span><span class="pun">)</span><span class="pln">

   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> im</span><span class="pun">.</span><span class="pln">save</span><span class="pun">(</span><span class="str">'drawing.png'</span><span class="pun">)</span></pre>

<p>
	ننشئ كائن <code>Image</code> لصورة بيضاء أبعادها 200‎×200، ونمرّره إلى الدالة <code>ImageDraw.Draw()‎</code> للحصول على كائن <code>ImageDraw</code> الذي نخزّنه في المتغير <code>draw</code>، حيث يمكنك استدعاء توابع الرسم مع هذا المتغير. رسمنا مخططًا رفيعًا باللون الأسود عند حواف الصورة ➊، ومستطيلًا أزرق تكون زاويته العلوية اليسرى عند النقطة ‎(20, 30)‎ وزاويته السفلية اليمنى عند النقطة ‎(60, 60)‎ ➋، وشكلًا بيضاويًا باللون الأحمر نحدّده باستخدام مربعٍ من النقطة ‎(120, 30)‎ إلى النقطة ‎(160, 60)‎ ➌، ومضلعًا بنيًا بخمس نقاط ➍، ونمطًا Pattern من الخطوط الخضراء مرسومة باستخدام حلقة <code>for</code> ➎. سيبدو الملف drawing.png الناتج كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="157680" href="https://academy.hsoub.com/uploads/monthly_2024_09/14_000051.jpg.44acf2c6a3baac8cae97eabccb60dce6.jpg" rel=""><img alt="14 000051" class="ipsImage ipsImage_thumbnailed" data-fileid="157680" data-unique="mu6imaqrr" src="https://academy.hsoub.com/uploads/monthly_2024_09/14_000051.jpg.44acf2c6a3baac8cae97eabccb60dce6.jpg"> </a>
</p>

<p style="text-align: center;">
	صورة drawing.png الناتجة
</p>

<p>
	توجد العديد من توابع رسم الأشكال الأخرى لكائنات <code>ImageDraw</code>، لذا اطّلع على توثيقها الكامل على <a href="https://pillow.readthedocs.io/en/latest/reference/ImageDraw.html" rel="external nofollow">موقع وحدة Pillow الرسمي</a>.
</p>

<h3 id="-21">
	رسم النصوص
</h3>

<p>
	يحتوي كائن <code>ImageDraw</code> أيضًا على التابع <code>text()‎</code> لرسم النصوص على الصور، حيث يأخذ هذا التابع أربعة وسطاء هي: <code>xy</code> و <code>text</code> و <code>fill</code> و <code>font</code>:
</p>

<ul>
	<li>
		الوسيط <code>xy</code> هو مجموعة مكونة من عددين صحيحين تحدّد الزاوية العلوية اليسرى من مربع النص.
	</li>
	<li>
		الوسيط <code>text</code> هو سلسلة النص الذي تريد كتابته.
	</li>
	<li>
		الوسيط <code>fill</code> الاختياري هو لون النص.
	</li>
	<li>
		الوسيط <code>font</code> الاختياري هو كائن <code>ImageFont</code>، حيث يُستخدَم هذا الوسيط لضبط خط النص وحجمه (سنوضّح هذا الوسيط بمزيد من التفصيل في القسم التالي).
	</li>
</ul>

<p>
	من الصعب معرفة حجم كتلة النص التي لها خط معين مسبقًا، لذا توفّر الوحدة <code>ImageDraw</code> التابع <code>textsize()‎</code>، ووسيطه الأول هو سلسلة النص الذي تريد قياس حجمه، والوسيط الثاني هو كائن <code>ImageFont</code> اختياري. يعيد التابع <code>textsize()‎</code> مجموعة مكونة من عددين صحيحين تمثّل العرض والارتفاع الذي سيكون عليه النص الذي له خطٌ محدّد إذا كان مكتوبًا على الصورة، حيث يمكنك استخدام هذا العرض والارتفاع لمساعدتك في حساب المكان الذي تريد وضع النص فيه على صورتك.
</p>

<p>
	تُعَد الوسطاء الثلاثة الأولى للتابع <code>text()‎</code> واضحةً، ولكن لنلقِ نظرة على الوسيط الرابع الاختياري كائن <code>ImageFont</code> قبل أن نستخدم التابع <code>text()‎</code> لرسم نص على صورة.
</p>

<p>
	يأخذ كل من التابعين <code>text()‎</code> و <code>textsize()‎</code> كائن <code>ImageFont</code> اختياري بوصفه الوسيط الأخير لهما. لننشئ أحد هذه الكائنات من خلال تشغيل التعليمة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8784_48" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> PIL </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">ImageFont</span></pre>

<p>
	استوردنا الوحدة <code>ImageFont</code> الخاصة بوحدة Pillow، ويمكننا الآن استدعاء الدالة <code>ImageFont.truetype()‎</code> التي تأخذ وسيطين. الوسيط الأول هو سلسلة نصية لملف TrueType الخاص بالخط، وهو ملف الخط الفعلي الموجود على قرص حاسوبك الصلب، ويكون لملف TrueType الامتداد <code>‎.ttf</code>، حيث يمكن العثور عليه في المجلدات التالية:
</p>

<ul>
	<li>
		على نظام ويندوز: <code>C:\Windows\Fonts</code>.
	</li>
	<li>
		على نظام ماك: <code>‎/Library/Fonts</code> و <code>‎/System/Library/Fonts</code>.
	</li>
	<li>
		على <a href="https://academy.hsoub.com/programming/general/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%86%D8%B8%D8%A7%D9%85-%D9%84%D9%8A%D9%86%D9%83%D8%B3-linux-%D9%88%D8%A3%D8%A8%D8%B1%D8%B2-%D9%85%D9%85%D9%8A%D8%B2%D8%A7%D8%AA%D9%87-%D9%88%D8%B9%D9%8A%D9%88%D8%A8%D9%87-r2252/" rel="">نظام لينكس</a>: <code>‎/usr/share/fonts/truetype</code>.
	</li>
</ul>

<p>
	لا تحتاج إلى إدخال هذه المسارات بوصفها جزءًا من السلسلة النصية لملف TrueType لأن <a href="https://wiki.hsoub.com/Python" rel="external">لغة بايثون</a> تعرف أنها ستبحث تلقائيًا عن الخطوط في هذه المجلدات، ولكنها ستعرض خطأً إن لم تتمكّن من العثور على الخط الذي حدّدته.
</p>

<p>
	الوسيط الثاني للدالة <code>ImageFont.truetype()‎</code> هو عدد صحيح يمثّل حجم الخط بالنقاط بدلًا من البكسلات. ضع في بالك أن الوحدة Pillow تنشئ صور PNG بكثافة 72 بكسلًا لكل بوصة افتراضيًا، والنقطة هي 1/72 من البوصة.
</p>

<p>
	أدخِل مثلًا ما يلي في الصدفة التفاعلية مع وضع اسم المجلد الفعلي الذي يستخدمه نظام تشغيلك مكان الثابت <code>FONT_FOLDER</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8784_50" style=""><span class="pln">   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> PIL </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">,</span><span class="pln"> </span><span class="typ">ImageDraw</span><span class="pun">,</span><span class="pln"> </span><span class="typ">ImageFont</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> os
</span><span class="pun">➊</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> im </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Image</span><span class="pun">.</span><span class="pln">new</span><span class="pun">(</span><span class="str">'RGBA'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="lit">200</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">),</span><span class="pln"> </span><span class="str">'white'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➋</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> draw </span><span class="pun">=</span><span class="pln"> </span><span class="typ">ImageDraw</span><span class="pun">.</span><span class="typ">Draw</span><span class="pun">(</span><span class="pln">im</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➌</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> draw</span><span class="pun">.</span><span class="pln">text</span><span class="pun">((</span><span class="lit">20</span><span class="pun">,</span><span class="pln"> </span><span class="lit">150</span><span class="pun">),</span><span class="pln"> </span><span class="str">'Hello'</span><span class="pun">,</span><span class="pln"> fill</span><span class="pun">=</span><span class="str">'purple'</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> fontsFolder </span><span class="pun">=</span><span class="pln"> </span><span class="str">'FONT_FOLDER'</span><span class="pln"> </span><span class="com"># مثلًا ‘/Library/Fonts'</span><span class="pln">
</span><span class="pun">➍</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> arialFont </span><span class="pun">=</span><span class="pln"> </span><span class="typ">ImageFont</span><span class="pun">.</span><span class="pln">truetype</span><span class="pun">(</span><span class="pln">os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">fontsFolder</span><span class="pun">,</span><span class="pln"> </span><span class="str">'arial.ttf'</span><span class="pun">),</span><span class="pln"> </span><span class="lit">32</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➎</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> draw</span><span class="pun">.</span><span class="pln">text</span><span class="pun">((</span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="lit">150</span><span class="pun">),</span><span class="pln"> </span><span class="str">'Howdy'</span><span class="pun">,</span><span class="pln"> fill</span><span class="pun">=</span><span class="str">'gray'</span><span class="pun">,</span><span class="pln"> font</span><span class="pun">=</span><span class="pln">arialFont</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> im</span><span class="pun">.</span><span class="pln">save</span><span class="pun">(</span><span class="str">'text.png'</span><span class="pun">)</span></pre>

<p>
	نستورد الوحدات <code>Image</code> و <code>ImageDraw</code> و <code>ImageFont</code> و <code>os</code>، ثم ننشئ كائن <code>Image</code> لصورة بيضاء جديدة أبعادها 200‎×200 ➊، وننشئ كائن <code>ImageDraw</code> من الكائن <code>Image</code> ➋. نستخدم التابع <code>text()‎</code> لرسم النص "Hello" عند النقطة ‎(20, 150)‎ باللون البنفسجي ➌. لم نمرّر الوسيط الرابع الاختياري في استدعاء التابع <code>text()‎</code>، لذا لم نخصّص خط وحجم هذا النص.
</p>

<p>
	يمكن ضبط خط وحجم النص من خلال تخزين اسم المجلد (مثل ‎/Library/Fonts) في المتغير <code>fontsFolder</code>، ثم نستدعي الدالة <code>ImageFont.truetype()‎</code>، ونمرر إليها ملف <code>‎.ttf</code> للخط الذي نريده متبوعًا بعدد صحيح يمثّل حجم الخط ➍. نخزّن الكائن <code>Font</code> الذي نحصل عليه من الدالة <code>ImageFont.truetype()‎</code> في المتغير <code>arialFont</code> مثلًا، ثم نمرّر هذا المتغير إلى التابع <code>text()‎</code> في وسيط الكلمات المفتاحية الأخير. يرسم استدعاء التابع <code>text()‎</code> ➎ النص "Howdy" عند النقطة ‎(100, 150)‎ باللون الرمادي بخط Arial وبحجم 32 نقطة.
</p>

<p>
	سيبدو الملف text.png الناتج كما في الشكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="157695" href="https://academy.hsoub.com/uploads/monthly_2024_09/15_000146.jpg.39e937a8572ec7c28b5752bd195f7400.jpg" rel=""><img alt="15 000146" class="ipsImage ipsImage_thumbnailed" data-fileid="157695" data-unique="82nyfg31n" src="https://academy.hsoub.com/uploads/monthly_2024_09/15_000146.jpg.39e937a8572ec7c28b5752bd195f7400.jpg"> </a>
</p>

<p style="text-align: center;">
	صورة text.png الناتجة
</p>

<h2 id="-23">
	مشاريع للتدريب
</h2>

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

<h3 id="-24">
	توسيع وإصلاح برامج تطبيقنا العملي
</h3>

<p>
	يعمل برنامج resizeAndAddLogo.py الموجود في هذا المقال مع ملفات PNG و JPEG، ولكن تدعم الوحدة Pillow العديد من صيغ الصور الأخرى، إذًا لنوسّع هذا البرنامج لمعالجة صور GIF و BMP أيضًا. توجد مشكلة صغيرة هي أن البرنامج لا يعدّل ملفات PNG و JPEG إلّا إذا كانت امتدادات الملفات الخاصة بها بأحرف صغيرة، فمثلًا سيعالج هذا البرنامج الملف zophie.png ولن يعالج الملف zophie.PNG، لذا عدّل الشيفرة البرمجية بحيث يكون التحقق من امتداد الملف غير حساس لحالة الأحرف.
</p>

<p>
	أخيرًا، يُفترَض أن يكون الشعار المُضاف إلى الزاوية السفلية اليمنى مجرد علامة صغيرة، ولكن إذا كانت الصورة بحجم الشعار نفسه تقريبًا، فستبدو النتيجة كما في الشكل التالي، لذا عدّل البرنامج resizeAndAddLogo.py بحيث يجب أن يكون عرض وارتفاع الصورة على الأقل ضعف عرض وارتفاع صورة الشعار قبل لصقه، وإلّا فيجب تخطي إضافة الشعار.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="157691" href="https://academy.hsoub.com/uploads/monthly_2024_09/16_000005.jpg.452dd1a7f315b6c718f00d9dc6ac1ebd.jpg" rel=""><img alt="16 000005" class="ipsImage ipsImage_thumbnailed" data-fileid="157691" data-unique="ghaa7nk2n" src="https://academy.hsoub.com/uploads/monthly_2024_09/16_000005.jpg.452dd1a7f315b6c718f00d9dc6ac1ebd.jpg"> </a>
</p>

<p style="text-align: center;">
	ستبدو النتائج غير جميلة عندما لا تكون الصورة أكبر بكثير من الشعار
</p>

<h3 id="-25">
	تحديد مجلدات الصور الموجودة على القرص الصلب
</h3>

<p>
	قد تنقل الملفات من الكاميرا الرقمية إلى مجلدات مؤقتة في مكانٍ ما على القرص الصلب ثم تنسى هذه المجلدات، لذا من الجيد كتابة برنامج يمكنه فحص القرص الصلب بأكمله والعثور على مجلدات الصور التي نسيت مكانها.
</p>

<p>
	حاول كتابة برنامج يمر على كل مجلد موجودٍ على قرص حاسوبك الصلب ويعثر على مجلدات الصور المُحتملة، لذا يجب عليك أولًا تحديد ما يعنيه مجلد الصور، إذًا لنفترض أن مجلد الصور هو أيّ مجلد أكثر من نصف ملفاته صور، ولكن يجب أيضًا تحديد ما هي ملفات الصور، حيث يجب أن يكون لملف الصورة الامتداد <code>‎.png</code> أو <code>‎.jpg</code>. تُعَد الصور الرقمية صورًا كبيرة، إذ يجب أن يكون عرض وارتفاع ملف الصورة أكبر من 500 بكسل، فمعظم صور الكاميرا الرقمية يبلغ عرضها وارتفاعها عدة آلاف من البكسلات.
</p>

<p>
	إليك شيفرة هيكلية تقريبية لما قد يبدو عليه هذا البرنامج:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8784_52" style=""><span class="com">#! python3</span><span class="pln">
</span><span class="com"># استيراد الوحدات وكتابة التعليقات لوصف هذا البرنامج</span><span class="pln">

</span><span class="kwd">for</span><span class="pln"> foldername</span><span class="pun">,</span><span class="pln"> subfolders</span><span class="pun">,</span><span class="pln"> filenames </span><span class="kwd">in</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">walk</span><span class="pun">(</span><span class="str">'C:\\'</span><span class="pun">):</span><span class="pln">
    numPhotoFiles </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
    numNonPhotoFiles </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> filename </span><span class="kwd">in</span><span class="pln"> filenames</span><span class="pun">:</span><span class="pln">
        </span><span class="com"># ‫التحقق مما إذا كان امتداد الملف ليس ‎.png أو ‎.jpg</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> TODO</span><span class="pun">:</span><span class="pln">
            numNonPhotoFiles </span><span class="pun">+=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
            </span><span class="kwd">continue</span><span class="pln">    </span><span class="com"># الانتقال إلى اسم الملف التالي</span><span class="pln">

        </span><span class="com"># ‫فتح ملف الصورة باستخدام الوحدة Pillow</span><span class="pln">

        </span><span class="com"># التحقق مما إذا كان العرض والارتفاع أكبر من 500</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> TODO</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># الصورة كبيرة بما يكفي لعدّها صورة</span><span class="pln">
            numPhotoFiles </span><span class="pun">+=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
        </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
            </span><span class="com"># الصورة صغيرة جدًا بحيث لا يمكن عدّها صورة</span><span class="pln">
            numNonPhotoFiles </span><span class="pun">+=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">

    </span><span class="com"># إذا كان أكثر من نصف الملفات هي صور، فاطبع المسار المطلق للمجلد</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> TODO</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">TODO</span><span class="pun">)</span></pre>

<p>
	يجب أن يطبع البرنامج عند تشغيله المسار المطلق لأي مجلدات صور على الشاشة.
</p>

<h3 id="-26">
	برنامج لإنشاء بطاقات جلوس مخصصة
</h3>

<p>
	أنجزنا في مقالٍ سابق مشروعًا تدريبيًا لإنشاء دعوات مخصصة لقائمة من الضيوف موجودة في ملف نص عادي، لذا أضِف على هذا المشروع لإنشاء صور لبطاقات الجلوس المخصصة لضيوفك باستخدام الوحدة <code>pillow</code>. أنشئ ملف صورة باسم الضيف وبعض زخارف الزهور لكل من الضيوف المدرجين في الملف guests.txt الذي يتوفّر على الموارد الموجودة على موقع <a href="https://nostarch.com/automatestuff2/" rel="external nofollow">nostarch</a>، وتتوفر أيضًا صورة زهرة ذات ملكية عامة دون حقوق نشر في هذه الموارد.
</p>

<p>
	أضِف مستطيلًا أسود على حواف صورة الدعوة بحيث تكون كدليل للقص عند طباعة الصورة للتأكد من أن جميع بطاقات جلوس لها الحجم نفسه. تُضبَط ملفات PNG التي تنتجها وحدة Pillow على 72 بكسلًا لكل بوصة، لذا تتطلب البطاقة التي أبعادها 4‎×5 بوصة صورةً بحجم 288‎×360 بكسل.
</p>

<h2 id="-27">
	الخلاصة
</h2>

<p>
	تتكون الصور من مجموعة من البكسلات، وكل بكسل له قيمة RGBA للون الخاص به ويمكن تحديد مكانه باستخدام إحداثيات x و y، وهناك صيغتان شائعتان للصور هما JPEG و PNG، حيث يمكن لوحدة <code>pillow</code> التعامل مع هذه الصيغ للصور وغيرها من الصيغ.
</p>

<p>
	إذا حمّلنا صورة إلى كائن <code>Image</code>، فستُخزَّن أبعاد العرض والارتفاع الخاصة بها بوصفها مجموعة مكونة من عددين صحيحين في السمة <code>size</code>. تمتلك كائنات نوع البيانات <code>Image</code> أيضًا توابعًا لمعالجة الصور الشائعة وهي: <code>crop()‎</code> و <code>copy()‎</code> و <code>paste()‎</code> و <code>resize()‎</code> و <code>rotate()‎</code> و <code>transpose()‎</code>. يمكنك حفظ كائن <code>Image</code> في ملف صورة من خلال استدعاء التابع <code>save()‎</code>.
</p>

<p>
	إذا أردتَ أن يرسم برنامجك أشكالًا على الصور، فاستخدم توابع الوحدة <code>ImageDraw</code> لرسم النقاط والخطوط والمستطيلات والأشكال البيضاوية والمضلعات، وتوفر هذه الوحدة أيضًا توابعًا لرسم النصوص مع استخدام الخط والحجم الذي تختاره.
</p>

<p>
	توفّر التطبيقات المتقدمة وذات الكلفة العالية مثل برنامج الفوتوشوب ميزات معالجة آلية لحزمة من الصور، ولكن يمكنك استخدام سكربتات بايثون لإجراء العديد من التعديلات نفسها مجانًا. كتبنا في المقالات السابقة برامج بايثون للتعامل مع الملفات النصية العادية وجداول البيانات وملفات PDF وغيرها، ووسّعنا قدراتك البرمجية لمعالجة الصور أيضًا باستخدام وحدة <code>pillow</code>.
</p>

<p>
	ترجمة -وبتصرُّف- للمقال <a href="https://automatetheboringstuff.com/2e/chapter19/" rel="external nofollow">Manipulating Images</a> لصاحبه Al Sweigart.
</p>

<h2 id="-28">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/%D8%A5%D8%B1%D8%B3%D8%A7%D9%84-%D8%A7%D9%84%D8%B1%D8%B3%D8%A7%D8%A6%D9%84-%D8%A7%D9%84%D9%86%D8%B5%D9%8A%D8%A9-%D8%A7%D9%84%D9%82%D8%B5%D9%8A%D8%B1%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2400/" rel="">إرسال الرسائل النصية القصيرة باستخدام لغة بايثون</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%A9-%D8%A7%D9%84%D9%86%D8%B5%D9%88%D8%B5-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-python-r2344/https://academy.hsoub.com/programming/python/%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%A9-%D8%A7%D9%84%D9%86%D8%B5%D9%88%D8%B5-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-python-r2344/" rel="">معالجة النصوص باستخدام لغة بايثون Python</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D9%85%D8%B4%D8%A7%D8%B1%D9%8A%D8%B9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D8%B9%D9%85%D9%84%D9%8A%D8%A9-%D8%AA%D9%86%D8%A7%D8%B3%D8%A8-%D8%A7%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-r2185/" rel="">مشاريع بايثون عملية تناسب المبتدئين</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D8%A3%D9%87%D9%85-10-%D9%85%D9%83%D8%AA%D8%A8%D8%A7%D8%AA-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D8%AA%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D9%81%D9%8A-%D8%A7%D9%84%D9%85%D8%B4%D8%A7%D8%B1%D9%8A%D8%B9-%D8%A7%D9%84%D8%B5%D8%BA%D9%8A%D8%B1%D8%A9-r654/" rel="">أهم 10 مكتبات بايثون تستخدم في المشاريع الصغيرة</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2408</guid><pubDate>Mon, 09 Sep 2024 15:00:00 +0000</pubDate></item><item><title>&#x625;&#x631;&#x633;&#x627;&#x644; &#x627;&#x644;&#x631;&#x633;&#x627;&#x626;&#x644; &#x627;&#x644;&#x646;&#x635;&#x64A;&#x629; &#x627;&#x644;&#x642;&#x635;&#x64A;&#x631;&#x629; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x644;&#x63A;&#x629; &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646;</title><link>https://academy.hsoub.com/programming/python/%D8%A5%D8%B1%D8%B3%D8%A7%D9%84-%D8%A7%D9%84%D8%B1%D8%B3%D8%A7%D8%A6%D9%84-%D8%A7%D9%84%D9%86%D8%B5%D9%8A%D8%A9-%D8%A7%D9%84%D9%82%D8%B5%D9%8A%D8%B1%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2400/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_09/------.png.f4e2d25eef40ad92ed3f41abcbd32349.png" /></p>
<p>
	يمكنك كتابة برامج لإرسال رسائل البريد الإلكتروني والرسائل النصية القصيرة SMS لإعلامك بالأشياء حتى عندما تكون بعيدًا عن حاسوبك. إذا أجريتَ أتمتةً لمهمة تستغرق بضع ساعات لإنجازها، فلن ترغب في العودة إلى حاسوبك كل بضع دقائق للتحقق من حالة البرنامج، لذا يمكن لبرنامجك إرسال رسالة نصية إلى هاتفك عند الانتهاء فقط، مما يحرّرك من التركيز على أشياء أكثر أهمية عندما تكون بعيدًا عن حاسوبك.
</p>

<h2 id="sms">
	إرسال رسائل نصية باستخدام بوابات البريد الإلكتروني لخدمة الرسائل القصيرة SMS
</h2>

<p>
	تكون الهواتف الذكية في متناول أيدينا أكثر من الحواسيب، لذلك تُعَد الرسائل النصية وسيلةً فورية وموثوقية لإرسال الإشعارات أكثر من البريد الإلكتروني، وتكون الرسائل النصية أقصر، ممّا يزيد من احتمالية أن يتمكّن الشخص من قراءتها. الطريقة الأسهل والأكثر موثوقية لإرسال رسائل نصية هي استخدام بوابة البريد الإلكتروني لخدمة الرسائل القصيرة SMS (أو Short Message Service)، وهذه البوابة هي خادم بريد إلكتروني يُعِدّه مزوّد الهاتف المحمول لتلقي الرسائل النصية عبر البريد الإلكتروني ثم يوجّهها إلى المستلم بوصفها رسالة نصية.
</p>

<p>
	يمكنك كتابة برنامج لإرسال رسائل البريد الإلكتروني باستخدام الوحدتين <code>ezgmail</code> أو <code>smtplib</code>، حيث يشكّل كلٌّ من رقم الهاتف وخادم البريد الإلكتروني لشركة الهاتف عنوانَ البريد الإلكتروني للمستلم، وسيكون موضوع ونص Body البريد الإلكتروني هو نص الرسالة النصية، فمثلًا يمكنك إرسال رسالة نصية إلى رقم الهاتف 415‎-555-1234 الذي يملكه عميل شركة Verizon من خلال إرسال بريد إلكتروني إلى العنوان 4155551234‎@vtext.com.
</p>

<p>
	يمكنك العثور على بوابة البريد الإلكتروني لخدمة الرسائل القصيرة SMS الخاصة بمزوّد الهاتف المحمول من خلال إجراء بحث على الويب عن اسم مزوّد بوابة البريد الإلكتروني لخدمة الرسائل القصيرة، ولكن يوضّح الجدول الآتي هذه البوابات للعديد من مزوّدي الخدمة المشهورين. يمتلك العديد من المزوّدين <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">خوادم</a> بريد إلكتروني منفصلة للرسائل النصية القصيرة، والتي تحدّد أن تحتوي الرسائل على 160 محرفًا، وخوادم أخرى منفصلة لخدمة رسائل الوسائط المتعددة MMS، والتي ليس لها حد أقصى لعدد المحارف. إذا أردتَ إرسال صورة، فيجب استخدام بوابة MMS وإرفاق الملف بالبريد الإلكتروني.
</p>

<p>
	إن لم تعرف مزوّد الهاتف المحمول للمستلم، فيمكنك تجربة استخدام موقعٍ للبحث عن شركة الاتصالات، والذي يجب أن يوفّر شركة الاتصالات الخاصة برقم الهاتف، حيث يمكنك العثور على هذه المواقع من خلال البحث في الويب عن مزود الهاتف المحمول لرقمٍ ما. ستتيح لك العديد من هذه المواقع البحث عن الأرقام مجانًا بالرغم من أنها ستفرض عليك رسومًا إذا كنت بحاجة إلى البحث عن مئات أو آلاف أرقام الهواتف من خلال واجهة برمجة التطبيقات الخاصة بها.
</p>

<p>
	يوضّح الجدول التالي بوابات البريد الإلكتروني لخدمة الرسائل القصيرة الخاصة بمزوّدي خدمات الهاتف المحمول:
</p>

<table>
	<thead>
		<tr>
			<th>
				مزود الهاتف الخليوي
			</th>
			<th>
				بوابة SMS
			</th>
			<th>
				بوابة MMS
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				AT&amp;T
			</td>
			<td>
				البوابة number@txt.att.net
			</td>
			<td>
				البوابة number@mms.att.net
			</td>
		</tr>
		<tr>
			<td>
				Boost Mobile
			</td>
			<td>
				البوابة number@sms.myboostmobile.com
			</td>
			<td>
				بوابة SMS نفسها
			</td>
		</tr>
		<tr>
			<td>
				Cricket
			</td>
			<td>
				البوابة number@sms.cricketwireless.net
			</td>
			<td>
				البوابة number@mms.cricketwireless.net
			</td>
		</tr>
		<tr>
			<td>
				Google Fi
			</td>
			<td>
				البوابة number@msg.fi.google.com
			</td>
			<td>
				بوابة SMS نفسها
			</td>
		</tr>
		<tr>
			<td>
				Metro PCS
			</td>
			<td>
				البوابة number@mymetropcs.com
			</td>
			<td>
				بوابة SMS نفسها
			</td>
		</tr>
		<tr>
			<td>
				Republic Wireless
			</td>
			<td>
				البوابة number@text.republicwireless.com
			</td>
			<td>
				بوابة SMS نفسها
			</td>
		</tr>
		<tr>
			<td>
				Sprint
			</td>
			<td>
				البوابة number@messaging.sprintpcs.com
			</td>
			<td>
				البوابة number@pm.sprint.com
			</td>
		</tr>
		<tr>
			<td>
				T-Mobile
			</td>
			<td>
				البوابة number@tmomail.net
			</td>
			<td>
				بوابة SMS نفسها
			</td>
		</tr>
		<tr>
			<td>
				U.S. Cellular
			</td>
			<td>
				البوابة number@email.uscc.net
			</td>
			<td>
				البوابة number@mms.uscc.net
			</td>
		</tr>
		<tr>
			<td>
				Verizon
			</td>
			<td>
				البوابة number@vtext.com
			</td>
			<td>
				البوابة number@vzwpix.com
			</td>
		</tr>
		<tr>
			<td>
				Virgin Mobile
			</td>
			<td>
				البوابة number@vmobl.com
			</td>
			<td>
				البوابة number@vmpix.com
			</td>
		</tr>
		<tr>
			<td>
				XFinity Mobile
			</td>
			<td>
				البوابة number@vtext.com
			</td>
			<td>
				البوابة number@mypixmessages.com
			</td>
		</tr>
	</tbody>
</table>

<p>
	تُعَد بوابات البريد الإلكتروني لخدمة SMS مجانية وسهلة الاستخدام، ولكن لها بعضٌ من العيوب الرئيسية وهي:
</p>

<ul>
	<li>
		لا يوجد أيّ ضمان بأن النص سيصل مباشرةً أو قد لا يصل أبدًا.
	</li>
	<li>
		لا توجد طريقة لمعرفة فشل النص في الوصول.
	</li>
	<li>
		لا توجد طريقة للرد خاصةٌ بمستلم النص.
	</li>
	<li>
		قد تحظرك بوابات SMS إذا أرسلتَ عددًا كبيرًا جدًا من رسائل البريد الإلكتروني، ولا توجد طريقة لمعرفة عدد الرسائل التي ستكون "أكثر من الحد المسموح".
	</li>
	<li>
		لا يعني أن بوابة SMS تسلّم رسالة نصية اليوم أنها ستعمل غدًا.
	</li>
</ul>

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

<h2 id="twilio">
	إرسال رسائل نصية باستخدام خدمة Twilio
</h2>

<p>
	ستتعلّم في هذا القسم كيفية التسجيل في خدمة Twilio المجانية واستخدام وحدة بايثون الخاصة بها لإرسال رسائل نصية. خدمة Twilio هي خدمة بوابة SMS، مما يعني أنها تسمح لك بإرسال رسائل نصية من برامجك عبر الإنترنت. يحتوي الحساب التجريبي المجاني لخدمة Twilio على كمية محدودة من الرصيد وستكون النصوص مسبوقة بجملة أن النص مُرسَل من حساب Twilio تجريبي "Sent from a Twilio trial account"، ولكن قد تكون هذه الخدمة التجريبية مناسبة لبرامجك الشخصية. ليست خدمة Twilio خدمة بوابة SMS الوحيدة، فإن لم تفضل استخدام Twilio، فيمكنك العثور على خدمات بديلة من خلال البحث عبر الإنترنت عن "free sms" أو "gateway" أو "python sms api" أو حتى "بدائل twilio".
</p>

<p>
	ثبّت الوحدة <code>twilio</code> باستخدام الأمر <code>pip install --user --upgrade twilio</code> على نظام ويندوز Windows (أو استخدم الأداة <code>pip3</code> على نظامي ماك macOS ولينكس Linux) قبل التسجيل للحصول على حساب Twilio.
</p>

<p>
	<strong>ملاحظة</strong>: يُعَد هذا القسم خاصًا بالولايات المتحدة الأمريكية، ولكن تقدم Twilio خدمات الرسائل النصية القصيرة لدول أخرى غير الولايات المتحدة، لذا اطّلع على <a href="https://www.twilio.com/" rel="external nofollow">موقع Twilio الرسمي</a> لمزيد من المعلومات، حيث ستعمل وحدة <code>twilio</code> ودوالها باستخدام الطريقة نفسها خارج الولايات المتحدة الأمريكية.
</p>

<h3 id="twilio-1">
	التسجيل للحصول على حساب Twilio
</h3>

<p>
	انتقل إلى <a href="https://www.twilio.com/" rel="external nofollow">موقع Twilio الرسمي</a> واملأ استمارة التسجيل، ولكن يجب التحقق من رقم الهاتف المحمول الذي تريد إرسال الرسائل النصية إليه بعد التسجيل للحصول على حساب جديد. انتقل إلى صفحة معرّفات المتصل التي جرى التحقق منها Verified Caller IDs وأضِف رقم هاتف يمكنك الوصول إليه، ثم سترسل خدمة Twilio رمزًا إلى هذا الرقم والذي يجب أن تدخله للتحقق منه، حيث يكون هذا التحقق ضروريًا لمنع الأشخاص من استخدام الخدمة لإرسال رسائل نصية غير مرغوب فيها إلى أرقام هواتف عشوائية. ستتمكّن الآن من إرسال رسائل نصية إلى رقم الهاتف باستخدام الوحدة <code>twilio</code>.
</p>

<p>
	توفّر خدمة Twilio لحسابك التجريبي رقم هاتف لاستخدامه بوصفه مرسلًا للرسائل النصية، وستحتاج أيضًا معرّف SID ومفتاح الاستيثاق auth token الخاصين بحسابك، إذ يمكنك العثور على هذا المعرّف والمفتاح في صفحة لوحة التحكم Dashboard عندما تسجّل الدخول إلى حسابك على Twilio، حيث تعمل هذه القيم بوصفها اسم مستخدم وكلمة مرور Twilio عند تسجيل الدخول من برنامج <a href="https://wiki.hsoub.com/Python" rel="external">بايثون</a>.
</p>

<h3 id="">
	إرسال رسائل نصية
</h3>

<p>
	ثبّت الوحدة <code>twilio</code> وسجّل على حساب Twilio، ثم تحقق من رقم هاتفك وسجّل رقم هاتف Twilio، ثم ستحصل على المعرّف SID ومفتاح الاستيثاق الخاصين بحسابك، وستكون أخيرًا جاهزًا لإرسال رسائل نصية لنفسك من سكربتات بايثون الخاصة بك .
</p>

<p>
	تُعَد شيفرة بايثون الفعلية بسيطةً إلى حدٍ ما بالمقارنة مع جميع خطوات التسجيل. أدخِل ما يلي في الصدفة التفاعلية أثناء اتصال حاسوبك بالإنترنت، مع استبدال قيم المتغيرات <code>accountSID</code> و <code>authToken</code> و <code>myTwilioNumber</code> و <code>myCellPhone</code> بمعلوماتك الحقيقية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2479_10" style=""><span class="pun">➊</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">from</span><span class="pln"> twilio</span><span class="pun">.</span><span class="pln">rest </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Client</span><span class="pln">
    </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> accountSID </span><span class="pun">=</span><span class="pln"> </span><span class="str">'ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'</span><span class="pln">
    </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> authToken  </span><span class="pun">=</span><span class="pln"> </span><span class="str">'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'</span><span class="pln">
 </span><span class="pun">➋</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> twilioCli </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Client</span><span class="pun">(</span><span class="pln">accountSID</span><span class="pun">,</span><span class="pln"> authToken</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> myTwilioNumber </span><span class="pun">=</span><span class="pln"> </span><span class="str">'+14955551234'</span><span class="pln">
    </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> myCellPhone </span><span class="pun">=</span><span class="pln"> </span><span class="str">'+14955558888'</span><span class="pln">
 </span><span class="pun">➌</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> message </span><span class="pun">=</span><span class="pln"> twilioCli</span><span class="pun">.</span><span class="pln">messages</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="pln">body</span><span class="pun">=</span><span class="str">'Mr. Watson - Come here - I want
    to see you.'</span><span class="pun">,</span><span class="pln"> from_</span><span class="pun">=</span><span class="pln">myTwilioNumber</span><span class="pun">,</span><span class="pln"> to</span><span class="pun">=</span><span class="pln">myCellPhone</span><span class="pun">)</span></pre>

<p>
	يُفترَض أن تتلقى رسالة نصية بعد لحظات قليلة من كتابة السطر الأخير، وهذه الرسالة النصية هي: "Sent from your Twilio trial account - Mr. Watson - Come here - I want to see you".
</p>

<p>
	يجب استيراد الوحدة <code>twilio</code> باستخدام التعليمة <code>from twilio.rest import Client</code>، وليس باستخدام التعليمة <code>import twilio</code> فقط ➊ وفقًا للطريقة التي جرى فيها إعداد هذه الوحدة. خزّن معرّف SID الخاص بحسابك في المتغير <code>accountSID</code> وخزّن مفتاح الاستيثاق الخاص بك في المتغير <code>authToken</code> ثم استدعِ الدالة <code>Client()‎</code> ومرّر إليها <code>accountSID</code> و <code>authToken</code>. يعيد استدعاء الدالة <code>Client()‎</code> كائن <code>Client</code> ➋، حيث يحتوي هذا الكائن على السمة Attribute التي هي <code>messages</code>، والتي بدورها تحتوي على التابع <code>create()‎</code> الذي يمكنك استخدامه لإرسال رسائل نصية، وهو التابع الذي يوجّه خوادم Twilio لإرسال رسالتك النصية. خزّن رقم Twilio ورقم هاتفك المحمول في المتغيرين <code>myTwilioNumber</code> و <code>myCellPhone</code>، ثم استدعِ التابع <code>create()‎</code> ومرّر إليه وسطاء الكلمات المفتاحية Keyword Arguments التي تحدد نص الرسالة النصية ورقم المرسل (<code>myTwilioNumber</code>) ورقم المستلم (<code>myCellPhone</code>) ➌.
</p>

<p>
	يحتوي الكائن <code>Message</code> الذي يعيده التابع <code>create()‎</code> على معلومات حول الرسالة النصية المُرسَلة. تابع مثال الصدفة التفاعلية من خلال إدخال ما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2479_12" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> message</span><span class="pun">.</span><span class="pln">to
</span><span class="str">'+14955558888'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> message</span><span class="pun">.</span><span class="pln">from_
</span><span class="str">'+14955551234'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> message</span><span class="pun">.</span><span class="pln">body
</span><span class="str">'Mr. Watson - Come here - I want to see you.'</span></pre>

<p>
	يجب أن تحتوي السمات <code>to</code> و <code>from_‎</code> و <code>body</code> على رقم هاتفك المحمول ورقم Twilio والرسالة على التوالي. لاحظ أن رقم الهاتف المرسِل موجود في السمة <code>from_‎</code> مع شرطة سفلية في النهاية وليس <code>from</code>، لأن الكلمة <code>from</code> هي كلمة مفتاحية في <a href="https://academy.hsoub.com/files/15-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86/" rel="">لغة بايثون</a>، إذ لا بد أنك رأيتها مستخدمةً في صيغة تعليمة الاستيراد <code>from modulename import *‎</code> مثلًا، لذلك لا يمكن استخدامها بوصفها اسمًا للسمة. تابع مثال الصدفة التفاعلية بما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2479_14" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> message</span><span class="pun">.</span><span class="pln">status
</span><span class="str">'queued'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> message</span><span class="pun">.</span><span class="pln">date_created
datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">(</span><span class="lit">2023</span><span class="pun">,</span><span class="pln"> </span><span class="lit">7</span><span class="pun">,</span><span class="pln"> </span><span class="lit">8</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">36</span><span class="pun">,</span><span class="pln"> </span><span class="lit">18</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> message</span><span class="pun">.</span><span class="pln">date_sent </span><span class="pun">==</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">
</span><span class="kwd">True</span></pre>

<p>
	يجب أن تعطي السمة <code>status</code> سلسلة نصية، ويجب أن تعطي السمات <code>date_created</code> و <code>date_sent</code> كائن <code>datetime</code> إذا أُنشِئت وأُرسِلت الرسالة. قد يبدو غريبًا ضبط السمة <code>status</code> على القيمة <code>'queued'</code> وضبط السمة <code>date_sent</code> على القيمة <code>None</code> عندما تتلقى الرسالة النصية مسبقًا، والسبب في ذلك هو أنك التقطتَ الكائن <code>Message</code> في المتغير <code>message</code> قبل إرسال النص فعليًا. يجب إعادة جلب الكائن <code>Message</code> حتى تتمكّن من رؤية أحدث نسخة من السمتين <code>status</code> و <code>date_sent</code>. تحتوي كل رسالة من رسائل Twilio على معرّف سلسلة نصية SID فريد يمكن استخدامه لجلب آخر تحديث من الكائن <code>Message</code>. تابع مثال الصدفة التفاعلية من خلال إدخال ما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2479_17" style=""><span class="pln">   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> message</span><span class="pun">.</span><span class="pln">sid
   </span><span class="str">'SM09520de7639ba3af137c6fcb7c5f4b51'</span><span class="pln">
</span><span class="pun">➊</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> updatedMessage </span><span class="pun">=</span><span class="pln"> twilioCli</span><span class="pun">.</span><span class="pln">messages</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">message</span><span class="pun">.</span><span class="pln">sid</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> updatedMessage</span><span class="pun">.</span><span class="pln">status
   </span><span class="str">'delivered'</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> updatedMessage</span><span class="pun">.</span><span class="pln">date_sent
   datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">(</span><span class="lit">2023</span><span class="pun">,</span><span class="pln"> </span><span class="lit">7</span><span class="pun">,</span><span class="pln"> </span><span class="lit">8</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">36</span><span class="pun">,</span><span class="pln"> </span><span class="lit">18</span><span class="pun">)</span></pre>

<p>
	يعطي إدخال التعليمة <code>message.sid</code> معرّف SID الطويل الخاص بهذه الرسالة، ويمكنك استرداد كائن <code>Message</code> جديد مع أحدث المعلومات من خلال تمرير هذا المعرّف SID إلى التابع <code>get()‎</code> الخاص بعميل Twilio ➊، حيث تكون السمات <code>status</code> و <code>date_sent</code> صحيحة في كائن <code>Message</code> الجديد.
</p>

<p>
	تُضبَط السمة <code>status</code> على إحدى القيم التالية التي يكون نوعها سلسلة نصية: <code>'queued'</code> أو <code>'sending'</code> أو <code>'sent'</code> أو <code>'delivered'</code> أو <code>'undelivered'</code> أو <code>'failed'</code>.
</p>

<p>
	<strong>ملاحظة</strong>: يُعَد استلام الرسائل النصية باستخدام خدمة Twilio أكثر تعقيدًا بعض الشيء من إرسالها، إذ تتطلب خدمة Twilio أن يكون لديك موقع ويب يشغّل تطبيقه الويب، ويُعَد ذلك خارج نطاق هذا المقال.
</p>

<h2 id="-1">
	تطبيق عملي: وحدة لإرسال رسائل نصية
</h2>

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

<p>
	سنوضّح فيما يلي برنامج بايثون صغير يحتوي على الدالة <code>textmyself()‎</code> التي ترسل رسالة نمرّرها إلى هذه الدالة كوسيط نوعه سلسلة نصية. افتح تبويبًا جديدًا في محرّرك لإنشاء ملف جديد وأدخِل الشيفرة البرمجية التالية، مع وضع معلوماتك الخاصة مكان معرّف SID ومفتاح الاستيثاق الخاصين بالحساب وأرقام الهاتف، واحفظ الملف بالاسم textMyself.py.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2479_19" style=""><span class="pln">   </span><span class="com">#! python3</span><span class="pln">
   </span><span class="com"># textMyself.py - ‫تعريف الدالة textmyself()‎ التي ترسل رسالة نصية نمرّرها إليها بوصفها سلسلة نصية</span><span class="pln">

   </span><span class="com"># ‫القيم المُحدَّدة مسبقًا:</span><span class="pln">
   accountSID </span><span class="pun">=</span><span class="pln"> </span><span class="str">'ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'</span><span class="pln">
   authToken  </span><span class="pun">=</span><span class="pln"> </span><span class="str">'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'</span><span class="pln">
   myNumber </span><span class="pun">=</span><span class="pln"> </span><span class="str">'+15559998888'</span><span class="pln">
   twilioNumber </span><span class="pun">=</span><span class="pln"> </span><span class="str">'+15552225678'</span><span class="pln">
   </span><span class="kwd">from</span><span class="pln"> twilio</span><span class="pun">.</span><span class="pln">rest </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Client</span><span class="pln">

</span><span class="pun">➊</span><span class="pln"> </span><span class="kwd">def</span><span class="pln"> textmyself</span><span class="pun">(</span><span class="pln">message</span><span class="pun">):</span><span class="pln">
    </span><span class="pun">➋</span><span class="pln"> twilioCli </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Client</span><span class="pun">(</span><span class="pln">accountSID</span><span class="pun">,</span><span class="pln"> authToken</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">➌</span><span class="pln"> twilioCli</span><span class="pun">.</span><span class="pln">messages</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="pln">body</span><span class="pun">=</span><span class="pln">message</span><span class="pun">,</span><span class="pln"> from_</span><span class="pun">=</span><span class="pln">twilioNumber</span><span class="pun">,</span><span class="pln"> to</span><span class="pun">=</span><span class="pln">myNumber</span><span class="pun">)</span></pre>

<p>
	يخزِّن هذا البرنامج معرّف SID ومفتاح الاستيثاق الخاصين بالحساب والرقم المرسِل والرقم المستلِم، ثم يعرّف الدالة <code>textmyself()‎</code> التي تأخذ وسيطًا ➊، وينشئ كائن <code>Client</code> ➋، ويستدعي التابع <code>create()‎</code> مع الرسالة التي مرّرتها ➌.
</p>

<p>
	إذا أردتَ إتاحة الدالة <code>textmyself()‎</code> لبرامجك الأخرى، فما عليك سوى وضع الملف textMyself.py في المجلد نفسه الذي يحتوي على سكربت بايثون الخاص بك، وإذا أردتَ أن يرسل أحد برامجك رسالة نصية إليك، فأضِف إليه ما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2479_21" style=""><span class="kwd">import</span><span class="pln"> textmyself
textmyself</span><span class="pun">.</span><span class="pln">textmyself</span><span class="pun">(</span><span class="str">'The boring task is finished.'</span><span class="pun">)</span></pre>

<p>
	يجب التسجيل في خدمة Twilio وكتابة الشيفرة البرمجية الخاصة بإرسال الرسائل النصية مرة واحدة فقط، ثم يمكنك إرسال رسالة نصية من أيٍّ من برامجك الأخرى من خلال كتابة سطرين فقط من الشيفرة البرمجية.
</p>

<h2 id="-3">
	مشاريع للتدريب
</h2>

<p>
	حاول كتابة البرامج التي تؤدي المهام التي سنوضّحها فيما يلي لكسب خبرة عملية أكبر من خلال استخدام المعلومات التي حصلتَ عليها من المقال السابق وهذا المقال.
</p>

<h3 id="-4">
	برنامج لإرسال رسائل بريد إلكتروني لإنجاز مهمة روتينية عشوائية
</h3>

<p>
	اكتب برنامجًا يأخذ قائمةً بعناوين البريد الإلكتروني لأشخاص وقائمةً بمهام روتينية يجب تنفيذها ويسند المهام الروتينية للأشخاص عشوائيًا، وأرسل بريدًا إلكترونيًا لكل شخص بالمهام الروتينية المُسنَدة إليه. احتفظ أيضًا بسجل للمهام الروتينية المُسنَدة مسبقًا لكل شخص لتتمكّن من التأكد من أن البرنامج يتجنب إعطاء أيّ شخص المهمة الروتينية نفسها التي أنجزها سابقًا، ويمكنك جدولة البرنامج لتشغيله مرة واحدة في الأسبوع تلقائيًا.
</p>

<p>
	إذا مرّرتَ قائمةً إلى الدالة <code>random.choice()‎</code>، فستعيد عنصرًا مُحدَّدًا عشوائيًا من القائمة. يمكن أن يبدو جزء من شيفرتك البرمجية كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2479_23" style=""><span class="pln">chores </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">'dishes'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'bathroom'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'vacuum'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'walk dog'</span><span class="pun">]</span><span class="pln">
randomChore </span><span class="pun">=</span><span class="pln"> random</span><span class="pun">.</span><span class="pln">choice</span><span class="pun">(</span><span class="pln">chores</span><span class="pun">)</span><span class="pln">
chores</span><span class="pun">.</span><span class="pln">remove</span><span class="pun">(</span><span class="pln">randomChore</span><span class="pun">)</span><span class="pln">    </span><span class="com"># أُنجِزت هذه المهمة الروتينية، لذا يجب إزالتها</span></pre>

<h3 id="-5">
	برنامج للتذكير بإحضار المظلة
</h3>

<p>
	وضّحنا في مقالٍ سابق كيفية استخدام الوحدة <code>requests</code> لاستخراج البيانات من <a href="https://www.weather.gov/" rel="external nofollow">موقع الطقس</a>، لذا اكتب برنامجًا يعمل قبل أن تستيقظ في الصباح مباشرةً ويتحقق مما إذا كانت السماء تمطر في ذلك اليوم. إذا كانت ستمطر، فاطلب من البرنامج أن يرسل لك رسالة تذكيرية بضرورة إحضار مظلة قبل مغادرة المنزل.
</p>

<h3 id="-6">
	برنامج لإلغاء الاشتراك التلقائي
</h3>

<p>
	اكتب برنامجًا يبحث في حساب بريدك الإلكتروني ليجد جميع روابط إلغاء الاشتراك في جميع رسائل بريدك الإلكتروني، ويفتحها في المتصفح تلقائيًا. يجب على هذا البرنامج أن يسجّل الدخول إلى خادم IMAP الخاص بمزوّد بريدك الإلكتروني، وينزّل جميع رسائل بريدك الإلكتروني، ويمكنك استخدام المكتبة Beautiful Soup التي وضّحناها في مقالٍ سابق للتحقق من النسخة التي فيها كلمة إلغاء الاشتراك Unsubscribe ضمن الوسم link في شيفرة <a href="https://academy.hsoub.com/programming/html/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D9%84%D8%BA%D8%A9-html-r1687/" rel="">HTML</a>.
</p>

<p>
	يمكنك استخدام الدالة <code>webbrowser.open()‎</code> لفتح جميع هذه الروابط <a href="https://academy.hsoub.com/programming/general/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%B9%D9%86%D9%88%D8%A7%D9%86-url-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9%D9%87-r1435/" rel="">لعناوين URL</a> تلقائيًا في المتصفح بعد الحصول على قائمة بهذه العناوين، ثم يجب المرور على الخطوات الإضافية وإكمالها يدويًا لإلغاء الاشتراك بهذه القوائم، حيث يتضمن ذلك النقر على الرابط للتأكيد في معظم الحالات.
</p>

<p>
	يوفّر هذا السكربت عليك الاضطرار إلى المرور على جميع رسائل بريدك الإلكتروني بحثًا عن روابط إلغاء الاشتراك، ويمكنك بعد ذلك إعطاء هذا السكربت إلى أصدقائك حتى يتمكنوا من تشغيله على حسابات بريدهم الإلكتروني، ولكن تأكّد من أن كلمة مرور بريدك الإلكتروني غير مكتوبة في شيفرتك المصدرية.
</p>

<h3 id="-7">
	التحكم في حاسوبك من خلال البريد الإلكتروني
</h3>

<p>
	اكتب برنامجًا يتحقق من حساب البريد الإلكتروني كل 15 دقيقة بحثًا عن أيّ تعليمات ترسلها إليه عبر البريد الإلكتروني وينفّذ تلك التعليمات تلقائيًا. يُعَد BitTorrent مثلًا نظام تنزيل يستخدم تقنية الند للند peer-to-peer، حيث يمكنك تنزيل ملفات الوسائط الكبيرة على حاسوبك المنزلي باستخدام برنامج BitTorrent مجاني مثل البرنامج qBittorrent. إذا أرسلتَ رابط BitTorrent (رابطًا قانونيًا وليس رابط قرصنة) إلى البرنامج عبر البريد الإلكتروني، فسيتحقق البرنامج من بريده الإلكتروني ويعثر على هذه الرسالة ويستخرج الرابط، ثم يشغّل برنامج qBittorrent لبدء تنزيل الملف. يمكنك بهذه الطريقة جعل حاسوبك المنزلي يبدأ التنزيلات أثناء تواجدك بعيدًا عن المنزل، ويمكن الانتهاء من التنزيل (القانوني وغير المقرصن) بحلول وقت عودتك إلى المنزل.
</p>

<p>
	وضّحنا في مقالٍ سابق السابق كيفية تشغيل البرامج على حاسوبك باستخدام الدالة <code>subprocess.Popen()‎</code>، فمثلًا سيؤدي الاستدعاء التالي إلى تشغيل برنامج qBittorrent مع ملف تورنت:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_2479_27" style=""><span class="pln">qbProcess </span><span class="pun">=</span><span class="pln"> subprocess</span><span class="pun">.</span><span class="typ">Popen</span><span class="pun">([</span><span class="str">'C:\\Program Files (x86)\\qBittorrent\\
qbittorrent.exe'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'shakespeare_complete_works.torrent'</span><span class="pun">])</span></pre>

<p>
	يجب أن يتأكد البرنامج من أن رسائل البريد الإلكتروني تأتي منك، إذ قد ترغب في اشتراط أن تحتوي رسائل البريد الإلكتروني على كلمة مرور، لأنه من السهل إلى حدٍ ما أن يزيّف المخترقون عنوان "من from" في رسائل البريد الإلكتروني. يجب أن يحذف البرنامج رسائل البريد الإلكتروني التي يجدها حتى لا يكرر التعليمات في كل مرة يتحقق فيها من حساب البريد الإلكتروني، واجعل البرنامج أيضًا يرسل لك بريدًا إلكترونيًا أو رسالة تأكيد في كل مرة ينفّذ فيها أمرًا. من الجيد استخدام <a href="https://academy.hsoub.com/programming/python/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%AF%D9%88%D8%A7%D9%84-functions-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r292/" rel="">دوال</a> التسجيل الموضّحة في مقالٍ سابق لكتابة سجل ملف نصي يمكنك التحقق منه في حالة ظهور أخطاء، نظرًا لأنك لن تجلس أمام الحاسوب الذي يشغّل البرنامج.
</p>

<p>
	يتمتع برنامج qBittorrent وتطبيقات BitTorrent الأخرى بميزةٍ تمكّنه من الإغلاق تلقائيًا بعد اكتمال التنزيل، حيث وضّحنا في مقالٍ سابق كيف يمكنك تحديد موعد إنهاء التطبيق المُشغَّل باستخدام التابع <code>wait()‎</code> لكائنات <code>Popen</code>. سيوقِف استدعاء التابع <code>wait()‎</code> التنفيذ حتى يتوقف البرنامج qBittorrent، ثم يمكن لبرنامجك إرسال بريد إلكتروني أو رسالة نصية إليك لإعلامك باكتمال التنزيل.
</p>

<p>
	هناك الكثير من الميزات المحتملة التي يمكنك إضافتها إلى هذا المشروع، ولكن إذا واجهتك مشكلة، فيمكنك تنزيل مثال تطبيق هذا البرنامج من <a href="https://nostarch.com/automatestuff2/" rel="external nofollow">nostarch</a>.
</p>

<h2 id="-8">
	الخلاصة
</h2>

<p>
	تختلف الرسائل النصية عن البريد الإلكتروني بعض الشيء، لأنه هناك حاجة إلى أكثر من مجرد اتصال بالإنترنت لإرسال رسائل نصية قصيرة SMS على عكس البريد الإلكتروني، حيث توفر خدمات مثل خدمة Twilio وحداتٍ تسمح بإرسال رسائل نصية من برامجك. ستتمكّن بعد إجراء عملية الإعداد الأولية من إرسال الرسائل النصية باستخدام سطرين فقط من الشيفرة البرمجية.
</p>

<p>
	ستتمكّن باستخدام هذه الوحدات مع مهاراتك الأخرى من برمجة الشروط المُحدَّدة التي بموجبها يجب على برامجك إرسال الإشعارات أو التذكيرات، وبالتالي ستصل برامجك الآن إلى ما هو أبعد من حاسوبك الذي تعمل عليه.
</p>

<p>
	ترجمة -وبتصرُّف- للقسم Sending Text Messages من مقال <a href="https://automatetheboringstuff.com/2e/chapter18/" rel="external nofollow">Sending Email and Text Messages</a> لصاحبه Al Sweigart.
</p>

<h2 id="-9">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/%D8%A5%D8%B1%D8%B3%D8%A7%D9%84-%D8%B1%D8%B3%D8%A7%D8%A6%D9%84-%D8%A7%D9%84%D8%A8%D8%B1%D9%8A%D8%AF-%D8%A7%D9%84%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2395/" rel="">إرسال رسائل البريد الإلكتروني باستخدام لغة بايثون</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D8%AC%D8%AF%D9%88%D9%84%D8%A9-%D8%A7%D9%84%D9%85%D9%87%D8%A7%D9%85-%D9%88%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D8%A8%D8%B1%D8%A7%D9%85%D8%AC-%D8%A3%D8%AE%D8%B1%D9%89-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2394/" rel="">جدولة المهام وتشغيل برامج أخرى باستخدام لغة بايثون</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D9%82%D8%B1%D8%A7%D8%A1%D8%A9-%D9%85%D8%B3%D8%AA%D9%86%D8%AF%D8%A7%D8%AA-%D8%AC%D8%AF%D8%A7%D9%88%D9%84-%D8%A5%D9%83%D8%B3%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-python-r2374/" rel="">قراءة مستندات جداول إكسل باستخدام لغة بايثون Python</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D9%81%D9%8A-%D9%85%D8%B3%D8%AA%D9%86%D8%AF%D8%A7%D8%AA-%D8%A5%D9%83%D8%B3%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-python-r2375/" rel="">الكتابة في مستندات إكسل باستخدام لغة بايثون Python</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2400</guid><pubDate>Mon, 02 Sep 2024 15:00:00 +0000</pubDate></item><item><title>&#x625;&#x631;&#x633;&#x627;&#x644; &#x631;&#x633;&#x627;&#x626;&#x644; &#x627;&#x644;&#x628;&#x631;&#x64A;&#x62F; &#x627;&#x644;&#x625;&#x644;&#x643;&#x62A;&#x631;&#x648;&#x646;&#x64A; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x644;&#x63A;&#x629; &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646;</title><link>https://academy.hsoub.com/programming/python/%D8%A5%D8%B1%D8%B3%D8%A7%D9%84-%D8%B1%D8%B3%D8%A7%D8%A6%D9%84-%D8%A7%D9%84%D8%A8%D8%B1%D9%8A%D8%AF-%D8%A7%D9%84%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2395/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_08/------.png.f5ea7f1f1b57f8380fbef58d3cb67893.png" /></p>
<p>
	يحتاج التحقق من البريد الإلكتروني والرد عليه الكثير من الوقت، ولا يمكنك كتابة برنامج للتعامل مع جميع رسائل بريدك الإلكتروني، إذ تتطلب كل رسالة ردًا خاصًا بها، ولكن يمكنك أتمتة الكثير من المهام الأخرى المتعلقة بالبريد الإلكتروني بعد أن تعرف كيفية كتابة البرامج التي يمكنها إرسال واستقبال رسائل البريد الإلكتروني.
</p>

<p>
	قد يكون لديك مثلًا جدول بيانات يحتوي على الكثير من سجلات العملاء وتريد إرسال رسالة تحتوي على استمارة Form خاصة بكل عميل اعتمادًا على تفاصيل عمره وموقعه، وقد لا تتمكن البرمجيات التجارية من فعل ذلك نيابةً عنك، ولكن يمكنك كتابة برنامجك الخاص لإرسال هذه الرسائل عبر البريد الإلكتروني، مما يوفّر عليك الكثير من الوقت لنسخ ولصق رسائل البريد الإلكتروني التي تحتوي على استمارات.
</p>

<p>
	سنوضّح في هذا المقال الوحدة EZGmail التي تُعَد طريقة بسيطة لإرسال وقراءة رسائل البريد الإلكتروني من حسابات جيميل Gmail، وهي <a href="https://wiki.hsoub.com/Python" rel="external">بايثون Python</a> لاستخدام بروتوكولات البريد الإلكتروني المعيارية SMTP و IMAP.
</p>

<p>
	<strong>ملاحظة</strong>: نوصي بشدة بإعداد حساب بريد إلكتروني منفصل لأيّ سكربتات ترسل أو تستقبل رسائل البريد الإلكتروني، مما يؤدي إلى منع الأخطاء الموجودة في برامجك من التأثير على حساب بريدك الإلكتروني الشخصي مثل حذف رسائل البريد الإلكتروني أو إرسال رسائل غير مرغوب بها Spam إلى جهات الاتصال الخاصة بك عن طريق الخطأ. يُفضَّل أن تجري أولًا تشغيلًا تجريبيًا من خلال تعليق الشيفرة البرمجية التي ترسل رسائل البريد الإلكتروني أو تحذفها فعليًا ووضع استدعاء مؤقت للدالة <code>print()‎</code> مكانها، وبالتالي يمكنك اختبار برنامجك قبل تشغيله تشغيلًا حقيقيًا.
</p>

<h2 id="gmailapi">
	إرسال واستقبال رسائل البريد الإلكتروني باستخدام واجهة برمجة تطبيقات جيميل Gmail <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>
</h2>

<p>
	يمتلك جيميل ما يقرب من ثلث حصة سوق عملاء البريد الإلكتروني، إذ لا بد أنّ لديك عنوان بريد إلكتروني واحد على الأقل على جيميل. يتميز جيميل بتدابير الأمان الإضافية ومكافحة البريد الالكتروني غير المرغوب به، لذا من الأسهل التحكم في حساب جيميل باستخدام الوحدة EZGmail بدلًا من التحكم به باستخدام الوحدتين <code>smtplib</code> و <code>imapclient</code> اللتين سنناقشهما لاحقًا في هذا المقال. كتب Al Sweigart وحدة EZGmail، حيث تعمل هذه الوحدة فوق واجهة برمجة تطبيقات جيميل الرسمية وتوفّر دوالًا تسهّل استخدام جيميل من شيفرة بايثون. اطّلع على تفاصيل EZGmail الكاملة على <a href="https://github.com/asweigart/ezgmail/" rel="external nofollow">GitHub</a>، حيث لا تنتِج جوجل هذه الوحدة وليست تابعة لها، واطّلع على <a href="https://developers.google.com/gmail/api/reference/rest?hl=ar" rel="external nofollow">التوثيق الرسمي</a> لواجهة برمجة تطبيقات جيميل Gmail <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>.
</p>

<p>
	يمكنك تثبيت وحدة EZGmail من خلال تشغيل الأمر <code>pip install --user --upgrade ezgmail</code> على نظام ويندوز، أو استخدم الأداة <code>pip3</code> على نظامي ماك macOS ولينكس Linux. يضمن الخيار <code>‎--upgrade</code> تثبيت أحدث إصدار من الحزمة، وهو أمر ضروري للتفاعل مع خدمة دائمة التغير عبر الإنترنت مثل واجهة برمجة تطبيقات جيميل.
</p>

<h3 id="">
	تفعيل واجهة برمجة تطبيقات جيميل
</h3>

<p>
	يجب عليك أولًا التسجيل للحصول على حساب بريد إلكتروني على <a href="https://gmail.com/" rel="external nofollow">جيميل</a> قبل أن تكتب شيفرتك البرمجية. انتقل بعد ذلك إلى <a href="https://developers.google.com/gmail/api/quickstart/python?hl=ar" rel="external nofollow">صفحة البدء السريع لاستخدام بايثون</a>، وانقر على زر تفعيل واجهة برمجة تطبيقات جيميل Enable the Gmail <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> في تلك الصفحة، واملأ الاستمارة التي ستظهر.
</p>

<p>
	ستقدم الصفحة رابطًا للملف credentials.json بعد ملء الاستمارة، حيث يجب أن تنزّل هذا الملف وتضعه في المجلد نفسه لملف <code>‎.py</code> الخاص بك. يحتوي الملف credentials.json على معرّف العميل Client ID ومعلومات العميل السرية Client Secret، والتي يجب عليك التعامل معها مثل كلمة مرور حسابك على جيميل وعدم مشاركتها مع أيّ شخص آخر.
</p>

<p>
	لندخِل الآن الشيفرة البرمجية التالية في الصدفة التفاعلية Interactive Shell:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_10" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> ezgmail</span><span class="pun">,</span><span class="pln"> os
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">chdir</span><span class="pun">(</span><span class="pln">r</span><span class="str">'C:\path\to\credentials_json_file'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ezgmail</span><span class="pun">.</span><span class="pln">init</span><span class="pun">()</span></pre>

<p>
	تأكّد من ضبط مجلد العمل الحالي على المجلد نفسه الذي يوجد به الملف credentials.json وأنك متصل بالإنترنت. تفتح الدالة <code>ezgmail.init()‎</code> متصفحك على صفحة تسجيل الدخول إلى جوجل، لذا أدخِل عنوان جيميل وكلمة مرورك. قد تحذّرك الصفحة بعدم التحقق من هذا التطبيق This app isn’t verified"‎"، ولكن لا بأس بذلك. انقر بعد ذلك على "خيارات متقدمة Advanced"، وانقر على خيار الانتقال إلى صفحة البدء السريع (غير آمن) "Go to Quickstart (unsafe)‎". (إذا أردتَ كتابة سكربتات بايثون لأشخاص آخرين ولا تريد ظهور هذا التحذير لهم، فيجب أن تتعرّف على عملية التحقق من تطبيق جوجل، والتي لن نناقشها في هذا المقال. انقر على خيار "السماح Allow" ثم أغلق المتصفح عندما تعرض الصفحةُ التالية الرسالةَ "تريد صفحة البدء السريع الوصول إلى حسابك جوجل Quickstart wants to access your Google Account".
</p>

<p>
	يتولّد بعد ذلك ملف token.json لمنح سكربتات بايثون الخاصة بك إمكانية الوصول إلى حساب جيميل الذي أدخلته، ولن يفتح المتصفح إلّا على صفحة تسجيل الدخول إن لم يتمكّن من العثور على ملف token.json موجودٍ مسبقًا. يمكن لسكربتات بايثون الخاصة بك باستخدام الملفين credentials.json و token.json إرسالَ رسائل البريد الإلكتروني وقراءتها من حسابك على جيميل دون مطالبتك بتضمين كلمة مرور جيميل في شيفرتك المصدرية.
</p>

<h3 id="-1">
	إرسال رسائل البريد الإلكتروني من حساب جيميل
</h3>

<p>
	يجب أن تكون وحدة EZGmail قادرةً على إرسال بريد إلكتروني باستخدام استدعاء دالةٍ واحد بعد حصولك على الملف token.json كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_12" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> ezgmail
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ezgmail</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">'recipient@example.com'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Subject line'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Body of the email'</span><span class="pun">)</span></pre>

<p>
	إذا أردتَ إرفاق ملفاتٍ ببريدك الإلكتروني، فيمكنك توفير وسيط قائمة إضافي للدالة <code>send()‎</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_14" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ezgmail</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">'recipient@example.com'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Subject line'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Body of the email'</span><span class="pun">,</span><span class="pln">
</span><span class="pun">[</span><span class="str">'attachment1.jpg'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'attachment2.mp3'</span><span class="pun">])</span></pre>

<p>
	لاحظ أنه -كجزء من ميزات الأمان ومكافحة الرسائل غير المرغوب بها- قد لا يرسل جيميل رسائل بريد إلكتروني متكررة تحتوي على النص نفسه لأنها يمكن أن تكون رسائلًا غير مرغوب بها، أو رسائل بريد إلكتروني تحتوي على مرفقات ملفات لها الامتداد ‎<code>.exe</code>‎ أو ‎<code>.zip</code>‎ لأنها يمكن أن تكون فيروسات.
</p>

<p>
	يمكنك أيضًا توفير وسطاء الكلمات المفتاحية Keyword Arguments الاختيارية <code>cc</code> و <code>bcc</code> لإرسال نسخ مطابقة Carbon Copies ونسخ مطابقة مخفية Blind Carbon Copies:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_16" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> ezgmail
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ezgmail</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">'recipient@example.com'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Subject line'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Body of the email'</span><span class="pun">,</span><span class="pln">
cc</span><span class="pun">=</span><span class="str">'friend@example.com'</span><span class="pun">,</span><span class="pln"> bcc</span><span class="pun">=</span><span class="str">'otherfriend@example.com,someoneelse@example.com'</span><span class="pun">)</span></pre>

<p>
	إذا أردتَ أن تتذكر عنوان جيميل الذي ضُبِط الملف token.json عليه، فيمكنك فحص المتغير <code>ezgmail.EMAIL_ADDRESS</code>، حيث يُملَأ هذا المتغير فقط بعد استدعاء الدالة <code>ezgmail.init()‎</code> أو أي دالة أخرى خاصة بالوحدة EZGmail.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_18" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> ezgmail
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ezgmail</span><span class="pun">.</span><span class="pln">init</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ezgmail</span><span class="pun">.</span><span class="pln">EMAIL_ADDRESS
</span><span class="str">'example@gmail.com'</span></pre>

<p>
	تأكّد من التعامل مع الملف token.json بالطريقة نفسها للتعامل مع كلمة مرورك، حيث إذا حصل شخصٌ آخر على هذا الملف، فيمكنه الوصول إلى حسابك على جيميل بالرغم من أنه لن يتمكّن من تغيير كلمة مرور حسابك على جيميل. يمكنك إبطال ملفات token.json الصادرة مسبقًا من خلال الانتقال إلى الرابط <code><a href="https://security.google.com/settings/security/permissions?pli=1/%E2%80%8E" ipsnoembed="false" rel="external nofollow">https://security.google.com/settings/security/permissions?pli=1/‎</a></code>، ثم أبطِل الوصول إلى تطبيق البدء السريع Quickstart، ولكن يجب تشغيل الدالة <code>ezgmail.init()‎</code> ومتابعة عملية تسجيل الدخول مرة أخرى للحصول على ملف token.json جديد.
</p>

<h3 id="-2">
	قراءة رسائل البريد الإلكتروني من حساب جيميل
</h3>

<p>
	ينظّم جيميل رسائل البريد الإلكتروني التي تمثل ردودًا على بعضها البعض ضمن سلاسل محادثات Conversation Threads. إذا سجّلتَ الدخول إلى جيميل في متصفح الويب أو من خلال أحد التطبيقات، فسترى سلاسل رسائل البريد الإلكتروني بدلًا من رسائل البريد الإلكتروني الفردية، حتى لو احتوت إحدى تلك السلاسل على رسالة بريد إلكتروني واحدة فقط.
</p>

<p>
	تحتوي الوحدة EZGmail على كائنات <code>GmailThread</code> و <code>GmailMessage</code> لتمثيل سلاسل المحادثات ورسائل البريد الإلكتروني الفردية على التوالي، ويحتوي الكائن <code>GmailThread</code> على سمةٍ Attribute هي السمة <code>messages</code> التي تحتوي على قائمة بكائنات <code>GmailMessage</code>. تعيد الدالة <code>unread()‎</code> قائمةً بكائنات <code>GmailThread</code> لجميع رسائل البريد الإلكتروني غير المقروءة، والتي يمكن بعد ذلك تمريرها إلى الدالة <code>ezgmail.summary()‎</code> لطباعة ملخصٍ لسلاسل المحادثات في تلك القائمة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_20" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> ezgmail
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> unreadThreads </span><span class="pun">=</span><span class="pln"> ezgmail</span><span class="pun">.</span><span class="pln">unread</span><span class="pun">()</span><span class="pln"> </span><span class="com"># ‫قائمة بكائنات GmailThread</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ezgmail</span><span class="pun">.</span><span class="pln">summary</span><span class="pun">(</span><span class="pln">unreadThreads</span><span class="pun">)</span><span class="pln">
</span><span class="typ">Al</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Jon</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Do</span><span class="pln"> you want to watch </span><span class="typ">RoboCop</span><span class="pln"> this weekend</span><span class="pun">?</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Dec</span><span class="pln"> </span><span class="lit">09</span><span class="pln">
</span><span class="typ">Jon</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Thanks</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> stopping me </span><span class="kwd">from</span><span class="pln"> buying </span><span class="typ">Bitcoin</span><span class="pun">.</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Dec</span><span class="pln"> </span><span class="lit">09</span></pre>

<p>
	تُعَد الدالة <code>summary()‎</code> مفيدة لعرض ملخصٍ سريع لسلاسل رسائل البريد الإلكتروني، ولكن يمكنك الوصول إلى رسائل محددة (وأجزاء منها) من خلال فحص السمة <code>messages</code> الخاصة بالكائن <code>GmailThread</code>، حيث تحتوي هذه السمة على قائمة بكائنات <code>GmailMessage</code> التي تشكّل سلسلة المحادثات، وتحتوي هذه الكائنات على سمات <code>subject</code> و <code>body</code> و <code>timestamp</code> و <code>sender</code> و <code>recipient</code> التي توصف البريد الإلكتروني.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_22" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">unreadThreads</span><span class="pun">)</span><span class="pln">
</span><span class="lit">2</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> str</span><span class="pun">(</span><span class="pln">unreadThreads</span><span class="pun">[</span><span class="lit">0</span><span class="pun">])</span><span class="pln">
</span><span class="str">"&lt;GmailThread len=2 snippet= Do you want to watch RoboCop this weekend?'&gt;"</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">unreadThreads</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">messages</span><span class="pun">)</span><span class="pln">
</span><span class="lit">2</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> str</span><span class="pun">(</span><span class="pln">unreadThreads</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">messages</span><span class="pun">[</span><span class="lit">0</span><span class="pun">])</span><span class="pln">
</span><span class="str">"&lt;GmailMessage from='Al Sweigart &lt;al@inventwithpython.com&gt;' to='Jon Doe
&lt;example@gmail.com&gt;' timestamp=datetime.datetime(2018, 12, 9, 13, 28, 48)
subject='RoboCop' snippet='Do you want to watch RoboCop this weekend?'&gt;"</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> unreadThreads</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">messages</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">subject
</span><span class="str">'RoboCop'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> unreadThreads</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">messages</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">body
</span><span class="str">'Do you want to watch RoboCop this weekend?\r\n'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> unreadThreads</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">messages</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">timestamp
datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">(</span><span class="lit">2018</span><span class="pun">,</span><span class="pln"> </span><span class="lit">12</span><span class="pun">,</span><span class="pln"> </span><span class="lit">9</span><span class="pun">,</span><span class="pln"> </span><span class="lit">13</span><span class="pun">,</span><span class="pln"> </span><span class="lit">28</span><span class="pun">,</span><span class="pln"> </span><span class="lit">48</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> unreadThreads</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">messages</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">sender
</span><span class="str">'Al Sweigart &lt;al@inventwithpython.com&gt;'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> unreadThreads</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">messages</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">recipient
</span><span class="str">'Jon Doe &lt;example@gmail.com&gt;'</span></pre>

<p>
	تعيد الدالة <code>ezgmail.recent()‎</code> أحدث 25 سلسلة محادثات في حسابك على جيميل كما تفعل الدالة <code>ezgmail.unread()‎</code>، ولكن يمكنك تمرير وسيط الكلمات المفتاحية <code>maxResults</code> الاختياري لتغيير هذا الحد كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_24" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> recentThreads </span><span class="pun">=</span><span class="pln"> ezgmail</span><span class="pun">.</span><span class="pln">recent</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">recentThreads</span><span class="pun">)</span><span class="pln">
</span><span class="lit">25</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> recentThreads </span><span class="pun">=</span><span class="pln"> ezgmail</span><span class="pun">.</span><span class="pln">recent</span><span class="pun">(</span><span class="pln">maxResults</span><span class="pun">=</span><span class="lit">100</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">recentThreads</span><span class="pun">)</span><span class="pln">
</span><span class="lit">46</span></pre>

<h3 id="-3">
	البحث عن رسائل البريد الإلكتروني في حساب جيميل
</h3>

<p>
	يمكنك البحث عن رسائل بريد إلكتروني محددة باستخدام الطريقة نفسها التي تستخدمها لإدخال استعلامات في <a href="https://gmail.com/%E2%80%8E" rel="external nofollow">مربع البحث على جيميل</a> من خلال استدعاء الدالة <code>ezgmail.search()‎</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_26" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> resultThreads </span><span class="pun">=</span><span class="pln"> ezgmail</span><span class="pun">.</span><span class="pln">search</span><span class="pun">(</span><span class="str">'RoboCop'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">resultThreads</span><span class="pun">)</span><span class="pln">
</span><span class="lit">1</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ezgmail</span><span class="pun">.</span><span class="pln">summary</span><span class="pun">(</span><span class="pln">resultThreads</span><span class="pun">)</span><span class="pln">
</span><span class="typ">Al</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Jon</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Do</span><span class="pln"> you want to watch </span><span class="typ">RoboCop</span><span class="pln"> this weekend</span><span class="pun">?</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Dec</span><span class="pln"> </span><span class="lit">09</span></pre>

<p>
	يجب أن يؤدي الاستدعاء السابق للدالة <code>search()‎</code> إلى النتائج نفسها عندما تدخل الكلمة "RoboCop" في مربع البحث كما في الشكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="156798" href="https://academy.hsoub.com/uploads/monthly_2024_08/01_000038.jpg.e7e8a93e0638525f1e7fef4f06158f6f.jpg" rel=""><img alt="01 000038" class="ipsImage ipsImage_thumbnailed" data-fileid="156798" data-unique="ujfcfmq4s" src="https://academy.hsoub.com/uploads/monthly_2024_08/01_000038.jpg.e7e8a93e0638525f1e7fef4f06158f6f.jpg"> </a>
</p>

<p>
	البحث عن رسائل البريد الإلكتروني "RoboCop" في موقع جيميل الإلكتروني
</p>

<p>
	تعيد الدالة <code>search()‎</code> قائمةً بكائنات <code>GmailThread</code> كما تفعل الدالتان <code>unread()‎</code> و <code>recent()‎</code>، ويمكنك أيضًا تمرير أيٍّ من معاملات البحث الخاصة التي يمكنك إدخالها في مربع البحث إلى الدالة <code>search()‎</code> مثل المعاملات التالية:
</p>

<ul>
	<li>
		<code>'label:UNREAD'</code>: لرسائل البريد الإلكتروني غير المقروءة.
	</li>
	<li>
		<code>'from:al@inventwithpython.com'</code>: لرسائل البريد الإلكتروني الواردة من <code>al@inventwithpython.com</code>.
	</li>
	<li>
		<code>'subject:hello'</code>: لرسائل البريد الإلكتروني التي تحتوي على الكلمة "hello" في موضوعها.
	</li>
	<li>
		<code>'has:attachment'</code>: لرسائل البريد الإلكتروني التي تحتوي على ملفات مرفقة.
	</li>
</ul>

<p>
	<strong>ملاحظة</strong>: اطّلع على القائمة الكاملة <a href="https://support.google.com/mail/answer/7190?hl=en/%E2%80%8E" rel="external nofollow">لمعاملات البحث</a>.
</p>

<h3 id="-4">
	تنزيل المرفقات من حساب جيميل
</h3>

<p>
	تحتوي كائنات <code>GmailMessage</code> على السمة <code>attachments</code>، والتي هي قائمة بأسماء الملفات المُرفَقة مع الرسالة، حيث يمكنك تمرير أيٍّ من هذه الأسماء إلى التابع <code>downloadAttachment()‎</code> الخاص بكائن <code>GmailMessage</code> لتنزيل الملفات، ويمكنك أيضًا تنزيلها جميعًا دفعةً واحدة باستخدام التابع <code>downloadAllAttachments()‎</code>. تحفظ الوحدة EZGmail المرفقات في مجلد العمل الحالي افتراضيًا، ولكن يمكنك تمرير وسيط الكلمات المفتاحية الإضافي <code>downloadFolder</code> إلى التابعين <code>downloadAttachment()‎</code> و <code>downloadAllAttachments()‎</code> أيضًا لتنزيل المجلد. لندخِل مثلًا ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_28" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> ezgmail
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> threads </span><span class="pun">=</span><span class="pln"> ezgmail</span><span class="pun">.</span><span class="pln">search</span><span class="pun">(</span><span class="str">'vacation photos'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> threads</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">messages</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">attachments
</span><span class="pun">[</span><span class="str">'tulips.jpg'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'canal.jpg'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'bicycles.jpg'</span><span class="pun">]</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> threads</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">messages</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">downloadAttachment</span><span class="pun">(</span><span class="str">'tulips.jpg'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> threads</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">messages</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">downloadAllAttachments</span><span class="pun">(</span><span class="pln">downloadFolder</span><span class="pun">=</span><span class="str">'vacat
ion2023'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">[</span><span class="str">'tulips.jpg'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'canal.jpg'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'bicycles.jpg'</span><span class="pun">]</span></pre>

<p>
	إذا وُجِد ملف يحمل اسم الملف المرفق نفسه، فسيحل الملف المرفق الذي نزّلناه محله تلقائيًا.
</p>

<p>
	تحتوي الوحدة EZGmail على ميزات إضافية، حيث يمكنك العثور عليها ضمن توثيقها الكامل على <a href="https://github.com/asweigart/ezgmail/" rel="external nofollow">Github</a>.
</p>

<h2 id="smtp">
	بروتوكول SMTP
</h2>

<p>
	تستخدم الحواسيب <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-http-r73/" rel="">بروتوكول HTTP</a> لإرسال صفحات الويب عبر الإنترنت، ويُستخدَم بروتوكول نقل البريد البسيط Simple Mail Transfer Protocol -أو SMTP اختصارًا- لإرسال البريد الإلكتروني، حيث يتمتع هذان البروتوكولان بالمقدار نفسه من الأهمية. يحدّد بروتوكول SMTP كيفية تنسيق رسائل البريد الإلكتروني وتشفيرها ونقلها بين خوادم البريد وجميع التفاصيل الأخرى التي يعالجها حاسوبك بعد النقر على زر الإرسال، ولكنك لست بحاجة إلى معرفة هذه التفاصيل التقنية، لأن الوحدة <code>smtplib</code> الخاصة بلغة بايثون تبسّطها إلى بضع دوال.
</p>

<p>
	يتعامل بروتوكول SMTP فقط مع إرسال رسائل البريد الإلكتروني إلى المستخدمين الآخرين، ويتعامل بروتوكول مختلف هو بروتوكول IMAP مع استرداد رسائل البريد الإلكتروني المرسَلة إليك، حيث سنوضّح هذا البروتوكول لاحقًا.
</p>

<p>
	يوفّر معظم مزوّدي خدمات البريد الإلكتروني المستندة إلى الويب -بالإضافة إلى بروتوكولَي SMTP و IMAP- حاليًا إجراءات أمنية أخرى للحماية من البريد غير المرغوب به والتصيد الاحتيالي Phishing واستخدامات البريد الإلكتروني الضارة الأخرى. تمنع هذه الإجراءات سكربتات بايثون من تسجيل الدخول إلى حساب بريد إلكتروني باستخدام وحدتي <code>smtplib</code> و <code>imapclient</code>، ولكن تحتوي العديد من هذه الخدمات على <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%A7-%D9%87%D9%8A-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D9%84%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-api%D8%9F-r1512/" rel="">واجهات برمجة التطبيقات <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr></a> ووحدات بايثون محددة تسمح للسكربتات بالوصول إليها. سنشرح في هذا المقال الوحدة الخاصة بخدمة جيميل، ولكنك ستحتاج إلى الرجوع إلى التوثيق الرسمي للخدمات الأخرى.
</p>

<h2 id="-5">
	إرسال البريد الإلكتروني
</h2>

<p>
	قد تكون على دراية بإرسال رسائل البريد الإلكتروني من أوت لوك Outlook أو ثندربرد Thunderbird أو من خلال موقع ويب مثل جيميل Gmail أو بريد ياهو Yahoo Mail، ولكن لسوء الحظ لا <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D8%A3%D8%AF%D9%88%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D8%A9-%D9%81%D9%8A-%D8%A8%D9%86%D8%A7%D8%A1-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%B1%D8%B3%D9%88%D9%85%D9%8A%D8%A9-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1771/" rel="">تقدّم لغة بايثون واجهة مستخدم رسومية</a> جميلة مثل تلك التي تقدّمها هذه الخدمات، لذا يمكنك بدلًا من ذلك استدعاء الدوال لإجراء الخطوات الرئيسية من بروتوكول SMTP كما هو موضّح في مثال الصدفة التفاعلية الآتي.
</p>

<p>
	<strong>ملاحظة</strong>: لا تدخِل المثال التالي في الصدفة التفاعلية، إذ لن ينجح الأمر، لأن <code>smtp.example.com</code> و <code>bob@example.com</code> و <code>MY_SECRET_PASSWORD</code> و <code>alice@example.com</code> هي عناصر بديلة، إذ تُعَد هذه الشيفرة البرمجية مجرد نظرة عامة على عملية إرسال بريد إلكتروني باستخدام بايثون.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_33" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> smtplib
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> smtpObj </span><span class="pun">=</span><span class="pln"> smtplib</span><span class="pun">.</span><span class="pln">SMTP</span><span class="pun">(</span><span class="str">'smtp.example.com'</span><span class="pun">,</span><span class="pln"> </span><span class="lit">587</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> smtpObj</span><span class="pun">.</span><span class="pln">ehlo</span><span class="pun">()</span><span class="pln">
</span><span class="pun">(</span><span class="lit">250</span><span class="pun">,</span><span class="pln"> b</span><span class="str">'mx.example.com at your service, [216.172.148.131]\nSIZE 35882577\
n8BITMIME\nSTARTTLS\nENHANCEDSTATUSCODES\nCHUNKING'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> smtpObj</span><span class="pun">.</span><span class="pln">starttls</span><span class="pun">()</span><span class="pln">
</span><span class="pun">(</span><span class="lit">220</span><span class="pun">,</span><span class="pln"> b</span><span class="str">'2.0.0 Ready to start TLS'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> smtpObj</span><span class="pun">.</span><span class="pln">login</span><span class="pun">(</span><span class="str">'bob@example.com'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'MY_SECRET_PASSWORD'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">(</span><span class="lit">235</span><span class="pun">,</span><span class="pln"> b</span><span class="str">'2.7.0 Accepted'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> smtpObj</span><span class="pun">.</span><span class="pln">sendmail</span><span class="pun">(</span><span class="str">'bob@example.com'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'alice@example.com'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Subject: So
long.\nDear Alice, so long and thanks for all the fish. Sincerely, Bob'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">{}</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> smtpObj</span><span class="pun">.</span><span class="pln">quit</span><span class="pun">()</span><span class="pln">
</span><span class="pun">(</span><span class="lit">221</span><span class="pun">,</span><span class="pln"> b</span><span class="str">'2.0.0 closing connection ko10sm23097611pbd.52 - gsmtp'</span><span class="pun">)</span></pre>

<p>
	سنوضّح في الأقسام التالية كل خطوة من هذه الشيفرة البرمجية مع استبدال العناصر البديلة بمعلوماتك للاتصال بخادم SMTP وتسجيل الدخول إليه، وإرسال بريد إلكتروني، وقطع الاتصال <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">بالخادم</a>.
</p>

<h3 id="smtp-1">
	الاتصال بخادم SMTP
</h3>

<p>
	إذا أعددتَ ثندربيرد أو أوت لوك أو أي برنامج آخر للاتصال بحساب بريدك الإلكتروني، فقد تكون على دراية بضبط خادم ومنفذ SMTP، حيث ستكون هذه الإعدادات مختلفة بحسب مزوّد البريد الإلكتروني، ولكن يجب أن يمكنك البحث عبر الويب عن إعدادات مزوّدك لبروتوكول SMTP للحصول على الخادم والمنفذ لاستخدامهما.
</p>

<p>
	يكون عادةً اسم النطاق Domain لخادم SMTP هو اسم نطاق مزوّد بريدك الإلكتروني مع وجود البادئة <code>smtp.‎</code> قبله، فمثلًا خادم SMTP الخاص بشركة Verizon موجودٌ على النطاق smtp.verizon.net. يسرد الجدول التالي بعضًا من مزوّدي البريد الإلكتروني وخوادم SMTP الخاصة بهم، حيث يُعَد المنفذ Port قيمةً صحيحة وتكون دائمًا تقريبًا 587، ويستخدمه معيار تشفير الأوامر <abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل"><abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">TLS</abbr></abbr>.
</p>

<table>
	<thead>
		<tr>
			<th>
				مزوّد البريد الإلكتروني
			</th>
			<th>
				اسم نطاق خادم SMTP
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				Gmail*‎
			</td>
			<td>
				اسم النطاق smtp.gmail.com
			</td>
		</tr>
		<tr>
			<td>
				Outlook.com/Hotmail.com*‎
			</td>
			<td>
				اسم النطاق smtp-mail.outlook.com
			</td>
		</tr>
		<tr>
			<td>
				Yahoo Mail*‎
			</td>
			<td>
				اسم النطاق smtp.mail.yahoo.com
			</td>
		</tr>
		<tr>
			<td>
				AT&amp;T‎
			</td>
			<td>
				اسم النطاق smpt.mail.att.net (المنفذ 465)
			</td>
		</tr>
		<tr>
			<td>
				Comcast‎
			</td>
			<td>
				اسم النطاق smtp.comcast.net
			</td>
		</tr>
		<tr>
			<td>
				Verizon‎
			</td>
			<td>
				اسم النطاق smtp.verizon.net (المنفذ 465)
			</td>
		</tr>
	</tbody>
</table>

<p>
	<strong>ملاحظة</strong>: تمنع الإجراءات الأمنية الإضافية شيفرة بايثون من تسجيل الدخول إلى هذه الخوادم التي وضعنا بجانب اسمها المحرف (*) باستخدام الوحدة <code>smtplib</code>، ولكن يمكن لوحدة EZGmail تجاوز هذه الصعوبة لحسابات جيميل.
</p>

<p>
	إذا حصلتَ على اسم النطاق ومعلومات المنفذ لمزوّد بريدك الإلكتروني، فيمكنك إنشاء كائن <code>SMTP</code> من خلال استدعاء الدالة <code>smptlib.SMTP()‎</code>، وتمرير اسم النطاق كوسيط من نوع <a href="https://academy.hsoub.com/programming/python/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%B3%D9%84%D8%A7%D8%B3%D9%84-%D8%A7%D9%84%D9%86%D8%B5%D9%8A%D8%A9-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-r407/" rel="">السلسلة النصية </a>والمنفذ كوسيط من نوع عدد صحيح إليها. يمثل الكائن <code>SMTP</code> اتصالًا بخادم بريد SMTP ويمتلك توابع لإرسال رسائل البريد الإلكتروني، فمثلًا ينشئ الاستدعاء التالي كائن <code>SMTP</code> للاتصال بخادم بريد إلكتروني وهمي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_37" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> smtpObj </span><span class="pun">=</span><span class="pln"> smtplib</span><span class="pun">.</span><span class="pln">SMTP</span><span class="pun">(</span><span class="str">'smtp.example.com'</span><span class="pun">,</span><span class="pln"> </span><span class="lit">587</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> type</span><span class="pun">(</span><span class="pln">smtpObj</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&lt;</span><span class="kwd">class</span><span class="pln"> </span><span class="str">'smtplib.SMTP'</span><span class="pun">&gt;</span></pre>

<p>
	يُظهِر إدخال الدالة <code>type(smtpObj)‎</code> وجود كائن <code>SMTP</code> مخزّنٍ في المتغير <code>smtpObj</code>، حيث ستحتاج إلى هذا الكائن لاستدعاء التوابع التي تسجل دخولك وترسل رسائل البريد الإلكتروني. إن لم ينجح استدعاء الدالة <code>smptlib.SMTP()‎</code>، فقد لا يدعم خادم SMTP الخاص بك بروتوكول <abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل"><abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">TLS</abbr></abbr> على المنفذ 587، وبالتالي يجب إنشاء كائن <code>SMTP</code> باستخدام الدالة <code>smtplib.SMTP_SSL()‎</code> والمنفذ 465 بدلًا من ذلك.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_39" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> smtpObj </span><span class="pun">=</span><span class="pln"> smtplib</span><span class="pun">.</span><span class="pln">SMTP_SSL</span><span class="pun">(</span><span class="str">'smtp.example.com'</span><span class="pun">,</span><span class="pln"> </span><span class="lit">465</span><span class="pun">)</span></pre>

<p>
	<strong>ملاحظة</strong>: إن لم تكن متصلًا بالإنترنت، فسترفع شيفرة بايثون استثناء <code>socket.gaierror: [Errno 11004] getaddrinfo failed</code> أو أيّ استثناء آخر مشابه.
</p>

<p>
	لا تُعَد الاختلافات بين <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%B1%D8%A7%D9%82%D8%A8%D8%A9-%D8%B4%D9%87%D8%A7%D8%AF%D8%A7%D8%AA-ssltls-%D9%81%D9%8A-%D8%A7%D9%84%D9%85%D9%88%D9%82%D8%B9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A3%D8%AF%D8%A7%D8%A9-checkmk-r684/" rel="">بروتوكولَي <abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل"><abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">TLS</abbr></abbr> و <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة"><abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr></abbr></a> مهمة بالنسبة لبرامجك، فما عليك سوى معرفة معيار التشفير الذي يستخدمه خادم SMTP الخاص بك حتى تعرف كيفية الاتصال به. سيحتوي المتغير <code>smtpObj</code> في كافة أمثلة الصدفة التفاعلية التالية على كائن <code>SMTP</code> الذي تعيده الدالة <code>smtplib.SMTP()‎</code> أو الدالة <code>smtplib.SMTP_SSL()‎</code>.
</p>

<h3 id="hellosmtp">
	إرسال رسالة الترحيب "Hello" الخاصة ببروتوكول SMTP
</h3>

<p>
	إذا حصلنا على كائن <code>SMTP</code>، فيمكننا استدعاء التابع <code>ehlo()‎</code> للترحيب بخادم البريد الإلكتروني SMTP، حيث يُعَد هذا الترحيب الخطوة الأولى في بروتوكول SMTP وهو مهم لتأسيس اتصال مع الخادم. لا حاجة لمعرفة تفاصيل هذه البروتوكولات، ولكن تأكّد من استدعاء التابع <code>ehlo()‎</code> أولًا بعد الحصول على كائن <code>SMTP</code>، وإلّا ستؤدي استدعاءات التوابع اللاحقة إلى حدوث أخطاء. إليك مثال على استدعاء التابع <code>ehlo()‎</code> وقيمته المُعادة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_42" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> smtpObj</span><span class="pun">.</span><span class="pln">ehlo</span><span class="pun">()</span><span class="pln">
</span><span class="pun">(</span><span class="lit">250</span><span class="pun">,</span><span class="pln"> b</span><span class="str">'mx.example.com at your service, [216.172.148.131]\nSIZE 35882577\
n8BITMIME\nSTARTTLS\nENHANCEDSTATUSCODES\nCHUNKING'</span><span class="pun">)</span></pre>

<p>
	إذا كان العنصر الأول في المجموعة Tuple المُعادة هو العدد الصحيح 250 (رمز النجاح في بروتوكول SMTP)، فهذا يعني أن الترحيب قد نجح.
</p>

<h3 id="tls">
	بدء تشفير <abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل"><abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">TLS</abbr></abbr>
</h3>

<p>
	إذا كنتَ متصلًا بالمنفذ 587 على خادم SMTP (أي أنك تستخدم تشفير <abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل"><abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">TLS</abbr></abbr>)، فيجب استدعاء التابع <code>starttls()‎</code> لاحقًا، حيث تؤدي هذه الخطوة المطلوبة إلى تفعيل التشفير على اتصالك. إذا كنت متصلًا بالمنفذ 465 (أي أنك تستخدم بروتوكول <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة"><abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr></abbr>)، فهذا يعني التشفير مُعَد مسبقًا، ويجب عليك تخطي هذه الخطوة.
</p>

<p>
	إليك مثال لاستدعاء التابع <code>starttls()‎</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_45" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> smtpObj</span><span class="pun">.</span><span class="pln">starttls</span><span class="pun">()</span><span class="pln">
</span><span class="pun">(</span><span class="lit">220</span><span class="pun">,</span><span class="pln"> b</span><span class="str">'2.0.0 Ready to start TLS'</span><span class="pun">)</span></pre>

<p>
	يضع التابع <code>starttls()‎</code> اتصال SMTP الخاص بك في وضع <abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل"><abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">TLS</abbr></abbr>، ويخبرك العدد 220 الموجود في القيمة المُعادة أن الخادم جاهز.
</p>

<h3 id="smtp-2">
	تسجيل الدخول إلى خادم SMTP
</h3>

<p>
	إذا أعددتَ اتصالك المشفّر بخادم SMTP، فيمكنك تسجيل الدخول باستخدام اسم المستخدم الخاص بك (وهو عنوان بريدك الإلكتروني عادةً) وكلمة مرور بريدك الإلكتروني من خلال استدعاء التابع <code>login()‎</code>.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_47" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> smtpObj</span><span class="pun">.</span><span class="pln">login</span><span class="pun">(</span><span class="str">'my_email_address@example.com'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'MY_SECRET_PASSWORD'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">(</span><span class="lit">235</span><span class="pun">,</span><span class="pln"> b</span><span class="str">'2.7.0 Accepted'</span><span class="pun">)</span></pre>

<p>
	مرّر سلسلةً نصية تمثّل عنوان بريدك الإلكتروني كوسيطٍ أول وسلسلة نصية تمثّل كلمة مرورك كوسيطٍ ثانٍ إلى التابع <code>login()‎</code>، وتعني القيمة 235 الموجودة في القيمة المُعادة أن الاستيثاق Authentication ناجح. ترفع شيفرة بايثون الاستثناء <code>smtplib.SMTPAuthenticationError</code> لكلمات المرور غير الصحيحة.
</p>

<p>
	<strong>ملاحظة</strong>: كن حذرًا بشأن وضع كلمات المرور في شيفرتك المصدرية، حيث إذا نسخ شخصٌ ما برنامجك، فسيكون بإمكانه الوصول إلى حساب بريدك الإلكتروني، لذا يُفضَّل استدعاء الدالة <code>input()‎</code> وجعل المستخدم يكتب كلمة المرور. قد يكون اضطرارك إلى إدخال كلمة المرور في كل مرة تشغّل فيها برنامجك أمرًا غير مريح، ولكن تمنعك هذه الطريقة من ترك كلمة مرورك في ملف غير مشفّر على حاسوبك بحيث يمكن للمخترق أو للص الذي يسرق حاسوبك المحمول مثلًا الحصول عليها بسهولة.
</p>

<h3 id="-6">
	إرسال رسالة عبر البريد الإلكتروني
</h3>

<p>
	سجّلنا الدخول إلى خادم SMTP الخاص بمزوّد بريدك الإلكتروني، وبالتالي يمكننا الآن استدعاء التابع <code>sendmail()‎</code> لإرسال البريد الإلكتروني فعليًا، حيث يبدو استدعاء هذا التابع كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_49" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> smtpObj</span><span class="pun">.</span><span class="pln">sendmail</span><span class="pun">(</span><span class="str">'my_email_address@example.com
'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'recipient@example.com'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Subject: So long.\nDear Alice, so long and thanks for all the fish.
Sincerely, Bob'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">{}</span></pre>

<p>
	يتطلب التابع <code>sendmail()‎</code> ثلاثة وسطاء هي:
</p>

<ul>
	<li>
		عنوان بريدك الإلكتروني كسلسلة نصية (للعنوان "from مِن" الخاص بالبريد الإلكتروني).
	</li>
	<li>
		عنوان البريد الإلكتروني للمستلم كسلسلة نصية أو قائمةً من السلاسل النصية لمستلمين متعددين (للعنوان "to إلى").
	</li>
	<li>
		نص Body البريد الإلكتروني كسلسلة نصية.
	</li>
</ul>

<p>
	يجب أن تبدأ السلسلة النصية لنص البريد الإلكتروني بالعبارة <code>'Subject: \n'</code> لسطر موضوع البريد الإلكتروني، حيث يفصل محرف السطر الجديد <code>'‎\n'</code> سطر الموضوع عن النص الرئيسي للبريد الإلكتروني.
</p>

<p>
	القيمة المُعادة من التابع <code>sendmail()‎</code> هي قاموس، إذ سيكون هناك زوج مفتاح-قيمة واحد في القاموس لكل مستلمٍ فشل تسليم البريد الإلكتروني إليه، ويعني القاموس الفارغ أن البريد الإلكتروني اُرسِل بنجاح إلى جميع المستلمين.
</p>

<h3 id="smtp-3">
	قطع الاتصال بخادم SMTP
</h3>

<p>
	تأكّد من استدعاء التابع <code>quit()‎</code> عند الانتهاء من إرسال رسائل البريد الإلكتروني، مما يؤدي إلى قطع اتصال برنامجك بخادم SMTP.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_51" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> smtpObj</span><span class="pun">.</span><span class="pln">quit</span><span class="pun">()</span><span class="pln">
</span><span class="pun">(</span><span class="lit">221</span><span class="pun">,</span><span class="pln"> b</span><span class="str">'2.0.0 closing connection ko10sm23097611pbd.52 - gsmtp'</span><span class="pun">)</span></pre>

<p>
	تعني القيمة 221 الموجودة في القيمة المُعادة انتهاءَ الجلسة.
</p>

<h2 id="imap">
	البروتوكول IMAP
</h2>

<p>
	يُعَد البروتوكول SMTP بروتوكول إرسالٍ لرسائل البريد الإلكتروني، ولكن يحدّد بروتوكول الوصول إلى رسائل الإنترنت Internet Message Access Protocol -أو IMAP اختصارًا- كيفية الاتصال بخادم مزوّد البريد الإلكتروني لاسترداد رسائل البريد الإلكتروني المُرسَلة إلى عنوان بريدك الإلكتروني. تحتوي لغة بايثون على الوحدة <code>imaplib</code>، ولكن تُعَد الوحدة <code>imapclient</code> الخارجية أسهل في الاستخدام. يقدّم هذا المقال مقدمة لاستخدام الوحدة IMAPClient، لذا اطلّع على <a href="https://imapclient.readthedocs.io/%E2%80%8E" rel="external nofollow">توثيقها الرسمي الكامل</a> على موقعها الرسمي.
</p>

<p>
	تنزّل الوحدة <code>imapclient</code> رسائل البريد الإلكتروني من خادم IMAP بتنسيقٍ معقد إلى حد ما، لذا قد تحتاج إلى تحويلها من هذا التنسيق إلى قيم سلاسل نصية بسيطة. تنفّذ الوحدة <code>pyzmail</code> المهمة الصعبة المتمثلة في تحليل رسائل البريد الإلكتروني نيابةً عنك، لذا اطّلع على <a href="https://www.magiksys.net/pyzmail/%E2%80%8E" rel="external nofollow">التوثيق الكامل</a> لهذه الوحدة.
</p>

<p>
	ثبّت الوحدتين <code>imapclient</code> و <code>pyzmail</code> من النافذة الطرفية Terminal باستخدام الأمرين <code>pip install --user -U imapclient==2.1.0</code> و <code>pip install --user -U pyzmail36== 1.0.4</code> على نظام ويندوز Windows، أو باستخدام الأداة <code>pip3</code> على نظامي ماك macOS و<a href="https://academy.hsoub.com/programming/general/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%86%D8%B8%D8%A7%D9%85-%D9%84%D9%8A%D9%86%D9%83%D8%B3-linux-%D9%88%D8%A3%D8%A8%D8%B1%D8%B2-%D9%85%D9%85%D9%8A%D8%B2%D8%A7%D8%AA%D9%87-%D9%88%D8%B9%D9%8A%D9%88%D8%A8%D9%87-r2252/" rel="">لينكس Linux</a>.
</p>

<h2 id="imap-1">
	استرداد وحذف رسائل البريد الإلكتروني باستخدام بروتوكول IMAP
</h2>

<p>
	يُعَد البحث عن بريد إلكتروني واسترداده في لغة بايثون عملية متعددة الخطوات وتتطلب كلًا من الوحدتين الخارجيتين <code>imapclient</code> و <code>pyzmail</code>. إليك مثال كامل لتسجيل الدخول إلى خادم IMAP والبحث عن رسائل البريد الإلكتروني وجلبها، ثم استخراج نص رسائل البريد الإلكتروني منها:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_54" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> imapclient
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> imapObj </span><span class="pun">=</span><span class="pln"> imapclient</span><span class="pun">.</span><span class="typ">IMAPClient</span><span class="pun">(</span><span class="str">'imap.example.com'</span><span class="pun">,</span><span class="pln"> ssl</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> imapObj</span><span class="pun">.</span><span class="pln">login</span><span class="pun">(</span><span class="str">'my_email_address@example.com'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'MY_SECRET_PASSWORD'</span><span class="pun">)</span><span class="pln">
</span><span class="str">'my_email_address@example.com Jane Doe authenticated (Success)'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> imapObj</span><span class="pun">.</span><span class="pln">select_folder</span><span class="pun">(</span><span class="str">'INBOX'</span><span class="pun">,</span><span class="pln"> readonly</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="typ">UIDs</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> imapObj</span><span class="pun">.</span><span class="pln">search</span><span class="pun">([</span><span class="str">'SINCE 05-Jul-2023'</span><span class="pun">])</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="typ">UIDs</span><span class="pln">
</span><span class="pun">[</span><span class="lit">40032</span><span class="pun">,</span><span class="pln"> </span><span class="lit">40033</span><span class="pun">,</span><span class="pln"> </span><span class="lit">40034</span><span class="pun">,</span><span class="pln"> </span><span class="lit">40035</span><span class="pun">,</span><span class="pln"> </span><span class="lit">40036</span><span class="pun">,</span><span class="pln"> </span><span class="lit">40037</span><span class="pun">,</span><span class="pln"> </span><span class="lit">40038</span><span class="pun">,</span><span class="pln"> </span><span class="lit">40039</span><span class="pun">,</span><span class="pln"> </span><span class="lit">40040</span><span class="pun">,</span><span class="pln"> </span><span class="lit">40041</span><span class="pun">]</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> rawMessages </span><span class="pun">=</span><span class="pln"> imapObj</span><span class="pun">.</span><span class="pln">fetch</span><span class="pun">([</span><span class="lit">40041</span><span class="pun">],</span><span class="pln"> </span><span class="pun">[</span><span class="str">'BODY[]'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'FLAGS'</span><span class="pun">])</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> pyzmail
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> message </span><span class="pun">=</span><span class="pln"> pyzmail</span><span class="pun">.</span><span class="typ">PyzMessage</span><span class="pun">.</span><span class="pln">factory</span><span class="pun">(</span><span class="pln">rawMessages</span><span class="pun">[</span><span class="lit">40041</span><span class="pun">][</span><span class="pln">b</span><span class="str">'BODY[]'</span><span class="pun">])</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> message</span><span class="pun">.</span><span class="pln">get_subject</span><span class="pun">()</span><span class="pln">
</span><span class="str">'Hello!'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> message</span><span class="pun">.</span><span class="pln">get_addresses</span><span class="pun">(</span><span class="str">'from'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">[(</span><span class="str">'Edward Snowden'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'esnowden@nsa.gov'</span><span class="pun">)]</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> message</span><span class="pun">.</span><span class="pln">get_addresses</span><span class="pun">(</span><span class="str">'to'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">[(</span><span class="str">'Jane Doe'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'jdoe@example.com'</span><span class="pun">)]</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> message</span><span class="pun">.</span><span class="pln">get_addresses</span><span class="pun">(</span><span class="str">'cc'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">[]</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> message</span><span class="pun">.</span><span class="pln">get_addresses</span><span class="pun">(</span><span class="str">'bcc'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">[]</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> message</span><span class="pun">.</span><span class="pln">text_part </span><span class="pun">!=</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">
</span><span class="kwd">True</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> message</span><span class="pun">.</span><span class="pln">text_part</span><span class="pun">.</span><span class="pln">get_payload</span><span class="pun">().</span><span class="pln">decode</span><span class="pun">(</span><span class="pln">message</span><span class="pun">.</span><span class="pln">text_part</span><span class="pun">.</span><span class="pln">charset</span><span class="pun">)</span><span class="pln">
</span><span class="str">'Follow the money.\r\n\r\n-Ed\r\n'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> message</span><span class="pun">.</span><span class="pln">html_part </span><span class="pun">!=</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">
</span><span class="kwd">True</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> message</span><span class="pun">.</span><span class="pln">html_part</span><span class="pun">.</span><span class="pln">get_payload</span><span class="pun">().</span><span class="pln">decode</span><span class="pun">(</span><span class="pln">message</span><span class="pun">.</span><span class="pln">html_part</span><span class="pun">.</span><span class="pln">charset</span><span class="pun">)</span><span class="pln">
</span><span class="str">'&lt;div dir="ltr"&gt;&lt;div&gt;So long, and thanks for all the fish!&lt;br&gt;&lt;br&gt;&lt;/div&gt;-
Al&lt;br&gt;&lt;/div&gt;\r\n'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> imapObj</span><span class="pun">.</span><span class="pln">logout</span><span class="pun">()</span></pre>

<p>
	لا حاجة لحفظ جميع هذه الخطوات، إذ يمكنك العودة إلى هذا المثال العام لتحديث ذاكرتك بعد أن استعراض جميع الخطوات بالتفصيل.
</p>

<h3 id="imap-2">
	الاتصال بخادم IMAP
</h3>

<p>
	احتجنا كائن <code>SMTP</code> للاتصال بخادم SMTP وإرسال البريد الإلكتروني، وبالمثل نحتاج كائن <code>IMAPClient</code> للاتصال بخادم IMAP وتلقي البريد الإلكتروني، ولكن يجب أولًا الحصول على اسم النطاق لخادم IMAP الخاص بمزوّد بريدك الإلكتروني، والذي سيكون مختلفًا عن اسم نطاق خادم SMTP. يوضّح الجدول التالي خوادم IMAP للعديد من مزوّدي البريد الإلكتروني:
</p>

<table>
	<thead>
		<tr>
			<th>
				مزوّد البريد الإلكتروني
			</th>
			<th>
				اسم نطاق خادم IMAP
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				Gmail*‎
			</td>
			<td>
				اسم النطاق imap.gmail.com
			</td>
		</tr>
		<tr>
			<td>
				Outlook.com/Hotmail.com*‎
			</td>
			<td>
				اسم النطاق imap-mail.outlook.com
			</td>
		</tr>
		<tr>
			<td>
				Yahoo Mail*‎
			</td>
			<td>
				اسم النطاق imap.mail.yahoo.com
			</td>
		</tr>
		<tr>
			<td>
				AT&amp;T‎
			</td>
			<td>
				اسم النطاق imap.mail.att.net
			</td>
		</tr>
		<tr>
			<td>
				Comcast‎
			</td>
			<td>
				اسم النطاق imap.comcast.net
			</td>
		</tr>
		<tr>
			<td>
				Verizon‎
			</td>
			<td>
				اسم النطاق incoming.verizon.net
			</td>
		</tr>
	</tbody>
</table>

<p>
	<strong>ملاحظة</strong>: تمنع الإجراءات الأمنية الإضافية شيفرة بايثون من تسجيل الدخول إلى هذه الخوادم التي وضعنا بجانب اسمها المحرف (*) باستخدام الوحدة <code>imapclient</code>.
</p>

<p>
	نحصل على اسم النطاق لخادم IMAP، ثم يمكننا استدعاء الدالة <code>imapclient.IMAPClient()‎</code> لإنشاء كائن <code>IMAPClient</code>. يتطلب معظم مزوّدي البريد الإلكتروني تشفير <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة"><abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr></abbr>، لذا مرّر وسيط الكلمات المفتاحية <code><abbr title="Secure Socket Layer | طبقة المنافذ الآمنة"><abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">ssl</abbr></abbr>=True</code> إلى هذه الدالة، ولندخل مثلًا ما يلي في الصدفة التفاعلية مع استخدام اسم النطاق الخاص بمزوّدك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_56" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> imapclient
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> imapObj </span><span class="pun">=</span><span class="pln"> imapclient</span><span class="pun">.</span><span class="typ">IMAPClient</span><span class="pun">(</span><span class="str">'imap.example.com'</span><span class="pun">,</span><span class="pln"> ssl</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span></pre>

<p>
	سيحتوي المتغير <code>imapObj</code> على كائن <code>IMAPClient</code> الذي تعيده الدالة <code>imapclient.IMAPClient()‎</code> في كافة أمثلة الصدفة التفاعلية الموجودة في الأقسام التالية، والعميل Client هو الكائن الذي يتصل بالخادم.
</p>

<h3 id="imap-3">
	تسجيل الدخول إلى خادم IMAP
</h3>

<p>
	نحصل على كائن <code>IMAPClient</code>، ثم يمكننا استدعاء التابع <code>login()‎</code> الخاص بهذا الكائن، وتمرير اسم المستخدم (وهو عنوان بريدك الإلكتروني عادةً) وكلمة المرور كسلاسل نصية إلى هذا التابع.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_58" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> imapObj</span><span class="pun">.</span><span class="pln">login</span><span class="pun">(</span><span class="str">'my_email_address@example.com'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'MY_SECRET_PASSWORD'</span><span class="pun">)</span><span class="pln">
</span><span class="str">'my_email_address@example.com Jane Doe authenticated (Success)'</span></pre>

<p>
	<strong>ملاحظة</strong>: تذكّر ألّا تكتب كلمة المرور مباشرة في شيفرتك البرمجية، لذا صمّم برنامجك لقبول كلمة المرور التي تعيدها الدالة <code>input()‎</code>.
</p>

<p>
	إذا رفض خادم IMAP اسم المستخدم/كلمة المرور، فسترفع شيفرة بايثون استثناء <code>imaplib.error</code>.
</p>

<h3 id="-7">
	البحث عن رسالة البريد الإلكتروني
</h3>

<p>
	تُعَد عملية استرداد البريد الإلكتروني التي تهمك عمليةً مكونة من خطوتين بعد أن تسجّل الدخول، حيث يجب أولًا تحديد المجلد الذي تريد البحث فيه، ثم يجب استدعاء التابع <code>search()‎</code> الخاص بكائن <code>IMAPClient</code> وتمرير السلسلة النصية التي تمثّل الكلمات المفتاحية للبحث باستخدام بروتوكول IMAP.
</p>

<h4 id="-8">
	تحديد المجلد
</h4>

<p>
	يحتوي كل حساب تقريبًا على مجلد البريد الوارد <code>INBOX</code> افتراضيًا، ولكن يمكنك أيضًا الحصول على قائمة المجلدات من خلال استدعاء التابع <code>list_folders()‎</code> الخاص بالكائن <code>IMAPClient</code>، مما يؤدي إلى إعادة قائمة من المجموعات Tuples، حيث تحتوي كل مجموعة على معلومات حول مجلد واحد. تابع مثال الصدفة التفاعلية من خلال إدخال ما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_60" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> pprint
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pprint</span><span class="pun">.</span><span class="pln">pprint</span><span class="pun">(</span><span class="pln">imapObj</span><span class="pun">.</span><span class="pln">list_folders</span><span class="pun">())</span><span class="pln">
</span><span class="pun">[((</span><span class="str">'\\HasNoChildren'</span><span class="pun">,),</span><span class="pln"> </span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Drafts'</span><span class="pun">),</span><span class="pln">
</span><span class="pun">((</span><span class="str">'\\HasNoChildren'</span><span class="pun">,),</span><span class="pln"> </span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Filler'</span><span class="pun">),</span><span class="pln">
 </span><span class="pun">((</span><span class="str">'\\HasNoChildren'</span><span class="pun">,),</span><span class="pln"> </span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'INBOX'</span><span class="pun">),</span><span class="pln">
 </span><span class="pun">((</span><span class="str">'\\HasNoChildren'</span><span class="pun">,),</span><span class="pln"> </span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Sent'</span><span class="pun">),</span><span class="pln">
</span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">
 </span><span class="pun">((</span><span class="str">'\\HasNoChildren'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'\\Flagged'</span><span class="pun">),</span><span class="pln"> </span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Starred'</span><span class="pun">),</span><span class="pln">
 </span><span class="pun">((</span><span class="str">'\\HasNoChildren'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'\\Trash'</span><span class="pun">),</span><span class="pln"> </span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Trash'</span><span class="pun">)]</span></pre>

<p>
	القيم الثلاث في كل مجموعة مثل <code>‎(('\\HasNoChildren',), '/', 'INBOX')‎</code> هي كما يلي:
</p>

<ul>
	<li>
		مجموعة من رايات Flags المجلد (لن نوضّح في هذا المقال ما تمثله هذه الرايات، ويمكنك تجاهل هذا الحقل).
	</li>
	<li>
		المُحدِّد Delimiter المُستخدَم في سلسلة الاسم النصية لفصل المجلدات الأب عن المجلدات الفرعية.
	</li>
	<li>
		الاسم الكامل للمجلد.
	</li>
</ul>

<p>
	يمكنك تحديد مجلدٍ للبحث فيه من خلال تمرير اسم المجلد بوصفه سلسلة نصية إلى التابع <code>select_folder()‎</code> الخاص بالكائن <code>IMAPClient</code>كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_62" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> imapObj</span><span class="pun">.</span><span class="pln">select_folder</span><span class="pun">(</span><span class="str">'INBOX'</span><span class="pun">,</span><span class="pln"> readonly</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span></pre>

<p>
	يمكنك تجاهل القيمة التي يعيدها التابع <code>select_folder()‎</code>، وإذا كان المجلد المحدَّد غير موجود، فسترفع شيفرة بايثون استثناء <code>imaplib.error</code>. يمنعك وسيط الكلمات المفتاحية <code>readonly=True</code> من إجراء تغييرات أو حذفٍ عن طريق الخطأ على أيٍّ من رسائل البريد الإلكتروني الموجودة في هذا المجلد أثناء استدعاءات التوابع اللاحقة. إن لم تكن ترغب في حذف رسائل البريد الإلكتروني، فيُفضَّل دائمًا ضبط الوسيط <code>readonly</code> على القيمة <code>True</code>.
</p>

<h4 id="-9">
	إجراء البحث
</h4>

<p>
	حدّدنا المجلد، ويمكننا الآن البحث عن رسائل البريد الإلكتروني باستخدام التابع <code>search()‎</code> الخاص بالكائن <code>IMAPClient</code>، حيث يكون وسيط هذا التابع قائمةً من السلاسل النصية، وتكون كل سلسلة نصية بتنسيق مفاتيح بحث IMAP، حيث سنوضّح في الجدول الآتي مفاتيح البحث Search Keys. لاحظ أن بعض خوادم IMAP قد يكون لها طرق تطبيق مختلفة فيما يتعلق بكيفية التعامل مع الرايات ومفاتيح البحث الخاصة بها، لذا قد يتطلب الأمر بعض التجارب في الصدفة التفاعلية لمعرفة كيف تتصرّف بالضبط.
</p>

<p>
	يمكنك تمرير عدة سلاسل نصية لمفاتيح بحث IMAP في وسيط القائمة إلى التابع <code>search()‎</code>، وتكون الرسائل المُعادة هي الرسائل التي تتطابق مع جميع مفاتيح البحث. إذا أردتَ المطابقة مع أيٍّ من مفاتيح البحث، فاستخدم مفتاح البحث <code>OR</code>، ولاحظ أن مفتاحَ البحث <code>NOT</code> يتبعه مفتاح بحث كامل، وأن مفتاحَ البحث <code>OR</code> يتبعه مفتاحا بحث كاملان.
</p>

<table>
	<thead>
		<tr>
			<th>
				مفتاح البحث
			</th>
			<th>
				معناه
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				‎<code>'ALL'</code>‎
			</td>
			<td>
				يعيد جميع الرسائل الموجودة في المجلد. قد تواجهك قيود حجم الوحدة <code>imaplib</code> إذا طلبت جميع الرسائل الموجودة في مجلد كبير، لذا اطلع على القسم "قيود الحجم" التي سنوضحها لاحقًا.
			</td>
		</tr>
		<tr>
			<td>
				‎ <code>'BEFORE date'</code>‎و <code>'ON date'</code> و <code>'SINCE date'</code>
			</td>
			<td>
				تعيد مفاتيح البحث الثلاثة هذه الرسائل التي استلمها خادم IMAP قبل التاريخ <code>date</code> المُحدَّد أو فيه أو بعده على التوالي، حيث يجب أن يكون التاريخ بالتنسيق <code>‎05-Jul-2023</code>. يطابق مفتاح البحث <code>'SINCE 05-Jul-2023'</code> الرسائل في تاريخ 5 من الشهر السابع وبعده، ولكن يطابق مفتاح البحث <code>'BEFORE 05-Jul-2023'</code> الرسائل قبل تاريخ 5 من الشهر السابع فقط دون مطابقة رسائل هذا التاريخ.
			</td>
		</tr>
		<tr>
			<td>
				<code>'SUBJECT string'</code> و <code>'BODY string'</code> و ‎<code>'TEXT string'</code>‎
			</td>
			<td>
				تعيد الرسائل التي تكون فيها السلسلة النصية <code>string</code> موجودة في موضوع Subject الرسالة أو نصها Body أو أيٍّ منهما على التوالي. إذا احتوت السلسلة النصية <code>string</code> على مسافات، فأحِطها بعلامات اقتباس مزدوجة مثل: <code>‎'TEXT "search with spaces"'‎</code>.
			</td>
		</tr>
		<tr>
			<td>
				<code>'FROM string'</code> و <code>'TO string'</code> و <code>'CC string'</code> و ‎<code>'BCC string'</code>‎
			</td>
			<td>
				تعيد جميع الرسائل التي تكون فيها السلسلة النصية <code>string</code> موجودة في عنوان البريد الإلكتروني "من from"، أو عناوين "إلى to"، أو عناوين "cc" (نسخة مطابقة)، أو عناوين "bcc" (نسخة مطابقة مخفية) على التوالي. إذا كانت هناك عناوين بريد إلكتروني متعددة في السلسلة النصية <code>string</code>، فافصل بينها بمسافات وأحِط كلها بعلامات اقتباس مزدوجة مثل: <code>‎'CC "firstcc@example.com secondcc@example.com"'‎</code>.
			</td>
		</tr>
		<tr>
			<td>
				<code>'SEEN'</code> و ‎<code>'UNSEEN'</code>‎
			</td>
			<td>
				تعيد جميع الرسائل مع أو بدون الراية ‎\Seen على التوالي، حيث تحصل رسالة البريد الإلكتروني على الراية ‎‎\Seen‎ إذا وصلنا إليها باستخدام استدعاء التابع <code>fetch()‎</code> التي سنوضّحها لاحقًا أو إذا نقرنا عليها عند التحقق من البريد الإلكتروني في برنامج بريد إلكتروني أو متصفح ويب. من الشائع أن نقول أن البريد الإلكتروني "مقروء Read" بدلًا من "مُشاهَد Seen"، لكنهما يعنيان الشيء نفسه.
			</td>
		</tr>
		<tr>
			<td>
				<code>'ANSWERED'</code> و <code>'UNANSWERED'</code>
			</td>
			<td>
				تعيد جميع الرسائل مع أو بدون الراية ‎\Answered على التوالي، حيث تحصل الرسالة على الراية ‎\Answered عند الرد عليها.
			</td>
		</tr>
		<tr>
			<td>
				<code>'DELETED'</code> و <code>'UNDELETED'</code>
			</td>
			<td>
				تعيد جميع الرسائل مع أو بدون الراية ‎\Deleted على التوالي. تُعطَى رسائل البريد الإلكتروني المحذوفة باستخدام التابع <code>delete_messages()‎</code> الرايةَ ‎\Deleted ولكنها لا تُحذَف نهائيًا حتى نستدعي التابع <code>expunge()‎</code> (اطّلع على القسم "حذف رسائل البريد الإلكتروني" التي سنوضّحها لاحقًا). لاحظ أن بعض مزوّدي خدمة البريد الإلكتروني يحذفون نهائيًا Expunge رسائل البريد الإلكتروني تلقائيًا.
			</td>
		</tr>
		<tr>
			<td>
				<code>'DRAFT'</code> و <code>'UNDRAFT'</code>
			</td>
			<td>
				تعيد جميع الرسائل مع أو بدون الراية ‎\Draft على التوالي. تُحفَظ عادةً رسائل المسودات في مجلد منفصل هو مجلد المسودات <code>Drafts</code> بدلًا من مجلد البريد الوارد <code>INBOX</code>.
			</td>
		</tr>
		<tr>
			<td>
				<code>'FLAGGED'</code> و <code>'UNFLAGGED'</code>
			</td>
			<td>
				تعيد جميع الرسائل مع أو بدون الراية ‎\Flagged على التوالي، حيث تُستخدَم هذه الراية عادةً لوضع علامة على رسائل البريد الإلكتروني بوصفها "مهمة Important" أو "عاجلة Urgent".
			</td>
		</tr>
		<tr>
			<td>
				<code>'LARGER N'</code> و <code>'SMALLER N'</code>
			</td>
			<td>
				تعيد جميع الرسائل الأكبر أو الأصغر من N بايت على التوالي.
			</td>
		</tr>
		<tr>
			<td>
				<code>'NOT search-key'</code>
			</td>
			<td>
				يعيد الرسائل التي لا يعيدها مفتاح البحث <code>search-key</code>.
			</td>
		</tr>
		<tr>
			<td>
				<code>'OR search-key1 search-key2'</code>
			</td>
			<td>
				يعيد الرسائل التي تطابق مفتاح البحث <code>search-key</code> الأول أو الثاني.
			</td>
		</tr>
	</tbody>
</table>

<p>
	إليك فيما يلي بعض الأمثلة على استدعاءات التابع <code>search()‎</code> مع معانيها:
</p>

<ul>
	<li>
		<code>imapObj.search(['ALL'])‎</code>: يعيد جميع الرسائل الموجودة في المجلد المُحدَّد حاليًا.
	</li>
	<li>
		<code>imapObj.search(['ON 05-Jul-2023'])‎</code>: يعيد جميع الرسائل المُرسَلة في 5 من الشهر السادس من عام 2023.
	</li>
	<li>
		<code>imapObj.search(['SINCE 01-Jan-2023', 'BEFORE 01-Feb-2023', 'UNSEEN'])‎</code>: يعيد جميع الرسائل غير المقروءة المُرسَلة في الشهر الأول من عام 2023. لاحظ أن ذلك يعني الرسائل المُرسَلة في 1 من الشهر الأول وما بعده من الشهر الأول ولا يتضمّن 1 من الشهر الثاني.
	</li>
	<li>
		<code>imapObj.search(['SINCE 01-Jan-2023', 'FROM alice@example.com'])‎</code>: يعيد جميع الرسائل المُرسَلة من العنوان alice@example.com منذ بداية عام 2023.
	</li>
	<li>
		<code>imapObj.search(['SINCE 01-Jan-2023', 'NOT FROM alice@example.com'])‎</code>: يعيد جميع الرسائل المُرسَلة من الجميع باستثناء العنوان alice@example.com منذ بداية عام 2023.
	</li>
	<li>
		<code>imapObj.search(['OR FROM alice@example.com FROM bob@example.com'])‎</code>: يعيد جميع الرسائل المُرسَلة من العنوان alice@example.com أو العنوان bob@example.com.
	</li>
	<li>
		<code>imapObj.search(['FROM alice@example.com', 'FROM bob@example.com'])‎</code>: لا يعيد هذا البحث أيّ رسائل مطلقًا، لأن الرسائل يجب أن تتطابق مع جميع كلمات البحث المفتاحية. لا يمكن أن يكون هناك سوى عنوان "من from" واحد فقط، فمن المستحيل أن تكون الرسالة من العنوان alice@example.com والعنوان bob@example.com.
	</li>
</ul>

<p>
	لا يعيد التابع <code>search()‎</code> رسائل البريد الإلكتروني، بل يعيد المعرّفات الفريدة UID لرسائل البريد الإلكتروني بوصفها قيمًا صحيحة. يمكنك بعد ذلك تمرير هذه المعرّفات الفريدة إلى التابع <code>fetch()‎</code> للحصول على محتوى البريد الإلكتروني.
</p>

<p>
	تابع مثال الصدفة التفاعلية من خلال إدخال ما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_64" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="typ">UIDs</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> imapObj</span><span class="pun">.</span><span class="pln">search</span><span class="pun">([</span><span class="str">'SINCE 05-Jul-2023'</span><span class="pun">])</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="typ">UIDs</span><span class="pln">
</span><span class="pun">[</span><span class="lit">40032</span><span class="pun">,</span><span class="pln"> </span><span class="lit">40033</span><span class="pun">,</span><span class="pln"> </span><span class="lit">40034</span><span class="pun">,</span><span class="pln"> </span><span class="lit">40035</span><span class="pun">,</span><span class="pln"> </span><span class="lit">40036</span><span class="pun">,</span><span class="pln"> </span><span class="lit">40037</span><span class="pun">,</span><span class="pln"> </span><span class="lit">40038</span><span class="pun">,</span><span class="pln"> </span><span class="lit">40039</span><span class="pun">,</span><span class="pln"> </span><span class="lit">40040</span><span class="pun">,</span><span class="pln"> </span><span class="lit">40041</span><span class="pun">]</span></pre>

<p>
	خزّنا قائمة معرّفات الرسائل (للرسائل المُستلمة في 5 من الشهر السابع وما بعده) التي يعيدها التابع <code>search()‎</code> في المتغير <code>UIDs</code>. تكون قائمة المعرّفات UIDs المُعادة على حاسوبك مختلفة عن القائمة الموضحة في مثالنا، لأنها فريدة لحساب بريد إلكتروني معين. استخدم قيم المعرّف الفريد UID التي تلقيتها وليس القيم الواردة في هذا المقال عندما تمرّرها لاحقًا إلى استدعاءات دوال أخرى.
</p>

<h4 id="-10">
	قيود الحجم
</h4>

<p>
	إذا تطابق بحثك مع عدد كبير من رسائل البريد الإلكتروني، فقد ترفع شيفرة بايثون الاستثناء <code>imaplib.error: got more than 10000 bytes</code>، وعندها يجب قطع الاتصال بخادم IMAP وإعادة الاتصال به والمحاولة مرة أخرى.
</p>

<p>
	وُضِع هذا القيد لمنع برامج بايثون الخاصة بك من استهلاك الكثير من الذاكرة، ولكن يكون الحد الأقصى للحجم الافتراضي غالبًا صغيرًا جدًا، إذ يمكنك تغييره من 10000 بايت إلى 10000000 بايت من خلال تشغيل الشيفرة البرمجية التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_66" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> imaplib
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> imaplib</span><span class="pun">.</span><span class="pln">_MAXLINE </span><span class="pun">=</span><span class="pln"> </span><span class="lit">10000000</span></pre>

<p>
	يُفترَض أن تمنع الشيفرة البرمجية السابقة ظهور رسالة الخطأ مرة أخرى، لذا قد ترغب في جعل هذين السطرين جزءًا من كل برنامج IMAP تكتبه.
</p>

<h3 id="-11">
	جلب بريد إلكتروني ووضع علامة عليه كمقروء
</h3>

<p>
	يمكنك استدعاء التابع <code>fetch()‎</code> الخاص بكائن <code>IMAPClient</code> للحصول على محتوى البريد الإلكتروني الفعلي بعد حصولك على قائمة المعرّفات الفريدة UID التي ستكون الوسيط الأول لهذا التابع، والوسيط الثاني هو القائمة <code>‎['BODY[]']‎</code> التي تطلب من التابع <code>fetch()‎</code> تنزيل كل المحتوى الخاص بنص رسائل البريد الإلكتروني المُحدَّدة في قائمة المعرّفات الفريدة UID الخاصة بك.
</p>

<p>
	لنتابع الآن مثالنا على الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_68" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> rawMessages </span><span class="pun">=</span><span class="pln"> imapObj</span><span class="pun">.</span><span class="pln">fetch</span><span class="pun">(</span><span class="typ">UIDs</span><span class="pun">,</span><span class="pln"> </span><span class="pun">[</span><span class="str">'BODY[]'</span><span class="pun">])</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> pprint
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pprint</span><span class="pun">.</span><span class="pln">pprint</span><span class="pun">(</span><span class="pln">rawMessages</span><span class="pun">)</span><span class="pln">
</span><span class="pun">{</span><span class="lit">40040</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="str">'BODY[]'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Delivered-To: my_email_address@example.com\r\n'</span><span class="pln">
                   </span><span class="str">'Received: by 10.76.71.167 with SMTP id '</span><span class="pln">
</span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">
                   </span><span class="str">'\r\n'</span><span class="pln">
                   </span><span class="str">'------=_Part_6000970_707736290.1404819487066--\r\n'</span><span class="pun">,</span><span class="pln">
         </span><span class="str">'SEQ'</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5430</span><span class="pun">}}</span></pre>

<p>
	استورد الوحدة <code>pprint</code> ومرّر القيمة المُعادة من التابع <code>fetch()‎</code> والمُخزَّنة في المتغير <code>rawMessages</code> إلى الدالة <code>pprint.pprint()‎</code> "لطباعتها بمظهر جميل Pretty Print"، وسترى أن هذه القيمة المُعادة هي قاموس متداخل للرسائل ذات المعرفات الفريدة UID بوصفها مفاتيحًا. تُخزَّن كل رسالة كقاموس له مفتاحان هما: <code>‎'BODY[]'‎</code> و <code>'SEQ'</code>، حيث يُربَط المفتاح <code>‎'BODY[]'‎</code> مع النص الفعلي للبريد الإلكتروني. يُعَد المفتاح <code>'SEQ'</code> مُخصَّصًا للرقم التسلسلي Sequence Number، والذي له دورٌ مماثل للمعرّف الفريد (UID)، ولكن يمكنك تجاهله. يُعَد محتوى الرسالة الموجود في المفتاح <code>‎'BODY[]'‎</code> غير مفهوم إلى حد كبير، فهو بتنسيق اسمه RFC 822، وهو مصمم لتقرأه خوادم IMAP، ولكن لا حاجة إلى فهم هذا التنسيق، إذ سنوضّحه لاحقًا عند شرح وحدة <code>pyzmail</code> في هذا المقال.
</p>

<p>
	استدعينا الدالة <code>select_folder()‎</code> مع وسيط الكلمات المفتاحية <code>readonly=True</code> عند تحديد مجلد للبحث فيه، حيث يؤدي ذلك إلى منعك من حذف رسالة بريد إلكتروني عن طريق الخطأ، ولكنه يعني أيضًا عدم وضع علامة على رسائل البريد الإلكتروني بوصفها مقروءة إذا جلبتها باستخدام التابع <code>fetch()‎</code>، لذا إذا أردتَ وضع علامة على رسائل البريد الإلكتروني بوصفها مقروءة عند جلبها، فيجب تمرير الوسيط <code>readonly=False</code> إلى الدالة <code>select_folder()‎</code>. إذا كان المجلد المُحدَّد في وضع القراءة فقط، فيمكنك إعادة تحديد المجلد الحالي باستدعاء آخر للدالة <code>select_folder()‎</code> مع وسيط الكلمات المفتاحية <code>readonly=False</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_70" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> imapObj</span><span class="pun">.</span><span class="pln">select_folder</span><span class="pun">(</span><span class="str">'INBOX'</span><span class="pun">,</span><span class="pln"> readonly</span><span class="pun">=</span><span class="kwd">False</span><span class="pun">)</span></pre>

<h3 id="rawmessage">
	الحصول على عناوين البريد الإلكتروني من رسالة خام Raw Message
</h3>

<p>
	لا تُعَد الرسائل الخام التي يعيدها التابع <code>fetch()‎</code> مفيدة جدًا للأشخاص الذين يريدون قراءة رسائل بريدهم الإلكتروني فقط، لذا تحلّل الوحدة <code>pyzmail</code> هذه الرسائل الخام وتعيدها بوصفها كائنات <code>PyzMessage</code>، مما يجعل أقسام الموضوع والنص والحقل "إلى To" والحقل "من From" والأقسام الأخرى من البريد الإلكتروني قابلة للوصول بسهولة من شيفرة بايثون الخاصة بك.
</p>

<h2 id="-12">
	تطبيق عملي: إرسال رسائل البريد الإلكتروني للتذكير الأعضاء بدفع مستحقاتهم
</h2>

<p>
	لنفترض أنك تطوعتَ لتعقّب دفع الأعضاء لمستحقاتهم في نادي تطوع إلزامي، حيث تُعَد هذه المهمة مملةً جدًا، إذ تتضمّن استخدام جدول بيانات يحتوي جميع الأشخاص الذين دفعوا في كلّ شهر وإرسال رسائل تذكير عبر البريد الإلكتروني إلى الأشخاص الذين لم يدفعوا، وبالتالي ستمر على كامل جدول البيانات بنفسك وتنسخ وتلصق رسالة البريد الإلكتروني نفسها لكلّ مَن تأخر في سداد مستحقاته، إذًا لنكتب سكربتًا ينفّذ هذه المهمة نيابةً عنك.
</p>

<p>
	إليك الخطوات العامة التي سيطبّقها برنامجك:
</p>

<ol>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D9%82%D8%B1%D8%A7%D8%A1%D8%A9-%D9%85%D8%B3%D8%AA%D9%86%D8%AF%D8%A7%D8%AA-%D8%AC%D8%AF%D8%A7%D9%88%D9%84-%D8%A5%D9%83%D8%B3%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-python-r2374/" rel="">قراءة البيانات من جدول بيانات إكسل</a>.
	</li>
	<li>
		البحث عن جميع الأعضاء الذين لم يسدّدوا مستحقاتهم للشهر الأخير.
	</li>
	<li>
		البحث عن عناوين بريدهم الإلكتروني وإرسال رسائل تذكير مُخصَّصة لهم.
	</li>
</ol>

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

<ol>
	<li>
		فتح وقراءة خلايا مستند إكسل باستخدام الوحدة <code>openpyxl</code> كما تعلّمنا في <a href="https://academy.hsoub.com/programming/python/%D9%82%D8%B1%D8%A7%D8%A1%D8%A9-%D9%85%D8%B3%D8%AA%D9%86%D8%AF%D8%A7%D8%AA-%D8%AC%D8%AF%D8%A7%D9%88%D9%84-%D8%A5%D9%83%D8%B3%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-python-r2374/" rel="">مقالٍ سابق</a>.
	</li>
	<li>
		إنشاء قاموس للأعضاء الذين لم يسددوا مستحقاتهم.
	</li>
	<li>
		تسجيل الدخول إلى خادم SMTP من خلال استدعاء <code>smtplib.SMTP()‎</code> و <code>ehlo()‎</code> و <code>starttls()‎</code> و<code>login()‎</code>.
	</li>
	<li>
		إرسال رسالة تذكير مُخصَّصة عبر البريد الإلكتروني من خلال استدعاء التابع <code>sendmail()‎</code> إلى جميع الأعضاء الذين لم يسددوا مستحقاتهم.
	</li>
</ol>

<p>
	افتح تبويبًا جديدًا في محرّرك لإنشاء ملف جديد واحفظه بالاسم sendDuesReminders.py.
</p>

<h3 id="-13">
	الخطوة الأولى: فتح ملف إكسل
</h3>

<p>
	لنفترض أن جدول بيانات إكسل الذي تستخدمه لتعقّب دفعات مستحقات العضوية يشبه الشكل التالي، وهو موجود في ملف اسمه duesRecords.xlsx ويمكنك تنزيله من <a href="https://nostarch.com/automatestuff2/" rel="external nofollow">nostarch</a>.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="156799" href="https://academy.hsoub.com/uploads/monthly_2024_08/02_000130.jpg.de3e016911aeec6affbab4be09533734.jpg" rel=""><img alt="02 000130" class="ipsImage ipsImage_thumbnailed" data-fileid="156799" data-unique="w63y60dew" src="https://academy.hsoub.com/uploads/monthly_2024_08/02_000130.jpg.de3e016911aeec6affbab4be09533734.jpg"> </a>
</p>

<p style="text-align: center;">
	جدول تعقّب دفعات مستحقات الأعضاء
</p>

<p>
	يحتوي جدول البيانات على اسم كل عضو وعنوان بريده الإلكتروني، ويكون لكل شهر عمودٌ لتعقّب حالات الدفع الخاصة بالأعضاء، حيث تُميَّز الخلية الخاصة بكل عضو بالكلمة "paid" بعد دفع المستحقات.
</p>

<p>
	يجب أن يفتح البرنامج الملف duesRecords.xlsx ويعرف العمود الخاص بالشهر الأخير من خلال قراءة السمة <code>sheet.max_column</code>. اطّلع على المقال الخاص بالتعامل مع جداول بيانات إكسل باستخدام بايثون لمزيد من المعلومات حول الوصول إلى الخلايا في ملفات جداول بيانات إكسل باستخدام الوحدة <code>openpyxl</code>.
</p>

<p>
	أدخِل الشيفرة البرمجية التالية في تبويب محرّر ملفاتك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_74" style=""><span class="pln">   </span><span class="com">#! python3</span><span class="pln">
   </span><span class="com"># sendDuesReminders.py - إرسال رسائل البريد الإلكتروني بناءً على حالة الدفع في جدول البيانات</span><span class="pln">

   </span><span class="kwd">import</span><span class="pln"> openpyxl</span><span class="pun">,</span><span class="pln"> smtplib</span><span class="pun">,</span><span class="pln"> sys

   </span><span class="com"># فتح جدول البيانات والحصول على حالة المستحقات الأخيرة</span><span class="pln">

</span><span class="pun">➊</span><span class="pln"> wb </span><span class="pun">=</span><span class="pln"> openpyxl</span><span class="pun">.</span><span class="pln">load_workbook</span><span class="pun">(</span><span class="str">'duesRecords.xlsx'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➋</span><span class="pln"> sheet </span><span class="pun">=</span><span class="pln"> wb</span><span class="pun">.</span><span class="pln">get_sheet_by_name</span><span class="pun">(</span><span class="str">'Sheet1'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➌</span><span class="pln"> lastCol </span><span class="pun">=</span><span class="pln"> sheet</span><span class="pun">.</span><span class="pln">max_column
</span><span class="pun">➍</span><span class="pln"> latestMonth </span><span class="pun">=</span><span class="pln"> sheet</span><span class="pun">.</span><span class="pln">cell</span><span class="pun">(</span><span class="pln">row</span><span class="pun">=</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> column</span><span class="pun">=</span><span class="pln">lastCol</span><span class="pun">).</span><span class="pln">value

   </span><span class="com"># التحقق من حالة الدفع لكل عضو</span><span class="pln">

   </span><span class="com"># تسجيل الدخول إلى حساب البريد الإلكتروني</span><span class="pln">

   </span><span class="com"># إرسال رسائل التذكير عبر البريد الإلكتروني</span></pre>

<p>
	نستورد الوحدات <code>openpyxl</code> و <code>smtplib</code> و <code>sys</code>، ثم نفتح الملف duesRecords.xlsx ونخزّن كائن <code>Workbook</code> الناتج في المتغير <code>wb</code> ➊. نحصل بعد ذلك على الورقة Sheet1 ونخزّن الكائن <code>Worksheet</code> الناتج في المتغير <code>sheet</code> ➋. أصبح لدينا كائن <code>Worksheet</code>، وبالتالي يمكننا الآن الوصول إلى الصفوف والأعمدة والخلايا، حيث نخزّن العمود الأعلى في المتغير <code>lastCol</code> ➌، ثم نستخدم الصف رقم 1 والعمود <code>lastCol</code> للوصول إلى الخلية التي يجب أن تحتوي على الشهر الأخير، حيث نحصل على القيمة الموجودة في هذه الخلية ونخزّنها في المتغير <code>latestMonth</code> ➍.
</p>

<h3 id="-14">
	الخطوة الثانية: البحث عن جميع الأعضاء الذين لم يدفعوا مستحقاتهم
</h3>

<p>
	حدّدنا رقم العمود للشهر الأخير (المُخزَّن في المتغير <code>lastCol</code>)، ويمكننا الآن المرور ضمن حلقة على جميع الصفوف بعد الصف الأول الذي يحتوي على ترويسات الأعمدة لمعرفة الأعضاء الذين يكون لديهم النص "paid" في الخلية الخاصة بمستحقات ذلك الشهر. إن لم يدفع العضو، فيمكنك الحصول على اسم العضو وعنوان بريده الإلكتروني من العمودين 1 و2 على التوالي، حيث ستُدخِل هذه المعلومات في القاموس <code>unpaidMembers</code> الذي سيتعقّب جميع الأعضاء الذين لم يدفعوا في الشهر الأخير. أدخِل الشيفرة البرمجية التالية إلى برنامج sendDuesReminder.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_76" style=""><span class="pln">   </span><span class="com">#! python3</span><span class="pln">
   </span><span class="com"># sendDuesReminders.py - إرسال رسائل البريد الإلكتروني بناءً على حالة الدفع في جدول البيانات</span><span class="pln">

   </span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">

   </span><span class="com"># التحقق من حالة الدفع لكل عضو</span><span class="pln">
   unpaidMembers </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">
</span><span class="pun">➊</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> r </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">2</span><span class="pun">,</span><span class="pln"> sheet</span><span class="pun">.</span><span class="pln">max_row </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pun">):</span><span class="pln">
    </span><span class="pun">➋</span><span class="pln"> payment </span><span class="pun">=</span><span class="pln"> sheet</span><span class="pun">.</span><span class="pln">cell</span><span class="pun">(</span><span class="pln">row</span><span class="pun">=</span><span class="pln">r</span><span class="pun">,</span><span class="pln"> column</span><span class="pun">=</span><span class="pln">lastCol</span><span class="pun">).</span><span class="pln">value
       </span><span class="kwd">if</span><span class="pln"> payment </span><span class="pun">!=</span><span class="pln"> </span><span class="str">'paid'</span><span class="pun">:</span><span class="pln">
        </span><span class="pun">➌</span><span class="pln"> name </span><span class="pun">=</span><span class="pln"> sheet</span><span class="pun">.</span><span class="pln">cell</span><span class="pun">(</span><span class="pln">row</span><span class="pun">=</span><span class="pln">r</span><span class="pun">,</span><span class="pln"> column</span><span class="pun">=</span><span class="lit">1</span><span class="pun">).</span><span class="pln">value
        </span><span class="pun">➍</span><span class="pln"> email </span><span class="pun">=</span><span class="pln"> sheet</span><span class="pun">.</span><span class="pln">cell</span><span class="pun">(</span><span class="pln">row</span><span class="pun">=</span><span class="pln">r</span><span class="pun">,</span><span class="pln"> column</span><span class="pun">=</span><span class="lit">2</span><span class="pun">).</span><span class="pln">value
        </span><span class="pun">➎</span><span class="pln"> unpaidMembers</span><span class="pun">[</span><span class="pln">name</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> email</span></pre>

<p>
	تُعِد الشيفرة البرمجية السابقة القاموس الفارغ <code>unpaidMembers</code>، ثم تمر ضمن حلقة على جميع الصفوف بعد الصف الأول ➊، ثم تُخزَّن القيمة الموجودة في العمود الأخير ضمن المتغير <code>payment</code> لكل صف ➋. إذا لم يساوِ المتغير <code>payment</code> القيمة <code>'paid'</code>، فستُخزَّن قيمة العمود الأول في المتغير <code>name</code> ➌، وتُخزَّن قيمة العمود الثاني في المتغير <code>email</code> ➍، ويُضاف المتغيران <code>name</code> و <code>email</code> إلى القاموس <code>unpaidMembers</code> ➎.
</p>

<h3 id="-15">
	الخطوة الثالثة: إرسال رسائل تذكير مخصصة عبر البريد الإلكتروني
</h3>

<p>
	حصلنا على قائمة بجميع الأعضاء غير الدافعين، وحان الوقت الآن لإرسال رسائل تذكير لهؤلاء الأعضاء عبر البريد الإلكتروني. أدخِل الشيفرة البرمجية التالية إلى برنامجك، ولكن مع عنوان بريدك الإلكتروني ومعلومات مزوّدك الحقيقية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_78" style=""><span class="com">#! python3</span><span class="pln">
</span><span class="com"># sendDuesReminders.py - إرسال رسائل البريد الإلكتروني بناءً على حالة الدفع في جدول البيانات</span><span class="pln">

</span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">

</span><span class="com"># تسجيل الدخول إلى حساب البريد الإلكتروني</span><span class="pln">
smtpObj </span><span class="pun">=</span><span class="pln"> smtplib</span><span class="pun">.</span><span class="pln">SMTP</span><span class="pun">(</span><span class="str">'smtp.example.com'</span><span class="pun">,</span><span class="pln"> </span><span class="lit">587</span><span class="pun">)</span><span class="pln">
smtpObj</span><span class="pun">.</span><span class="pln">ehlo</span><span class="pun">()</span><span class="pln">
smtpObj</span><span class="pun">.</span><span class="pln">starttls</span><span class="pun">()</span><span class="pln">
smtpObj</span><span class="pun">.</span><span class="pln">login</span><span class="pun">(</span><span class="str">'my_email_address@example.com'</span><span class="pun">,</span><span class="pln"> sys</span><span class="pun">.</span><span class="pln">argv</span><span class="pun">[</span><span class="lit">1</span><span class="pun">])</span></pre>

<p>
	أنشئ كائن <code>SMTP</code> من خلال استدعاء الدالة <code>smtplib.SMTP()‎</code> ومرّر إليها اسم النطاق والمنفذ الخاص بمزوّدك، ثم استدعِ التوابع <code>ehlo()‎</code> و <code>starttls()‎</code>، ثم استدعِ التابع <code>login()‎</code> ومرّر إليه عنوان بريدك الإلكتروني والقائمة <code>sys.argv[1]‎</code> التي ستخزّن السلسلة النصية لكلمة مرورك، حيث ستدخِل كلمة المرور بوصفها وسيط سطر أوامر في كل مرة تشغّل فيها البرنامج لتجنّب حفظ كلمة مرورك في شيفرتك المصدرية.
</p>

<p>
	يسجّل برنامجك الدخول إلى حساب بريدك الإلكتروني، ثم يجب أن يمر على القاموس <code>unpaidMembers</code> ويرسل بريدًا إلكترونيًا مُخصَّصًا إلى عنوان البريد الإلكتروني لكل عضو. أضِف ما يلي إلى برنامج sendDuesReminders.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9166_80" style=""><span class="pln">   </span><span class="com">#! python3</span><span class="pln">
   </span><span class="com"># sendDuesReminders.py - إرسال رسائل البريد الإلكتروني بناءً على حالة الدفع في جدول البيانات</span><span class="pln">

   </span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">

   </span><span class="com"># إرسال رسائل التذكير عبر البريد الإلكتروني</span><span class="pln">
   </span><span class="kwd">for</span><span class="pln"> name</span><span class="pun">,</span><span class="pln"> email </span><span class="kwd">in</span><span class="pln"> unpaidMembers</span><span class="pun">.</span><span class="pln">items</span><span class="pun">():</span><span class="pln">
    </span><span class="pun">➊</span><span class="pln"> body </span><span class="pun">=</span><span class="pln"> </span><span class="str">"Subject: %s dues unpaid.\nDear %s,\nRecords show that you have not
   paid dues for %s. Please make this payment as soon as possible. Thank you!'"</span><span class="pln"> </span><span class="pun">%</span><span class="pln">
   </span><span class="pun">(</span><span class="pln">latestMonth</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">,</span><span class="pln"> latestMonth</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">➋</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Sending email to %s...'</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> email</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">➌</span><span class="pln"> sendmailStatus </span><span class="pun">=</span><span class="pln"> smtpObj</span><span class="pun">.</span><span class="pln">sendmail</span><span class="pun">(</span><span class="str">'my_email_address@example.com'</span><span class="pun">,</span><span class="pln"> email</span><span class="pun">,</span><span class="pln">
body</span><span class="pun">)</span><span class="pln">

    </span><span class="pun">➍</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> sendmailStatus </span><span class="pun">!=</span><span class="pln"> </span><span class="pun">{}:</span><span class="pln">
           </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'There was a problem sending email to %s: %s'</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> </span><span class="pun">(</span><span class="pln">email</span><span class="pun">,</span><span class="pln">
           sendmailStatus</span><span class="pun">))</span><span class="pln">
   smtpObj</span><span class="pun">.</span><span class="pln">quit</span><span class="pun">()</span></pre>

<p>
	تمر الشيفرة البرمجية السابقة ضمن حلقة على الأسماء ورسائل البريد الإلكتروني الموجودة في القاموس <code>unpaidMembers</code>، ونخصّص رسالةً لكل عضو لم يدفع، حيث تتضمّن هذه الرسالة الشهر الأخير واسم العضو، ونخزّنها في المتغير <code>body</code> ➊. نطبع بعد ذلك خرجًا يفيد بأننا نرسل بريدًا إلكترونيًا إلى عنوان البريد الإلكتروني لهذا العضو ➋، ثم نستدعي التابع <code>sendmail()‎</code>، ونمرّر إليه عنوان البريد الإلكتروني والرسالة المُخصَّصة ➌، ونخزّن القيمة المُعادة في المتغير <code>sendmailStatus</code>. تذكّر أن التابع <code>sendmail()‎</code> يعيد قيمة قاموس غير فارغ إذا أبلغ خادم SMTP عن خطأ أثناء إرسال هذا البريد الإلكتروني، لذا يتحقق الجزء الأخير من حلقة <code>for</code> ➍ مما إذا كان القاموس المُعاد غير فارغ، فإذا كان كذلك، فسيطبع عنوان البريد الإلكتروني للمستلم والقاموس المُعاد. نستدعي التابع <code>quit()‎</code> لقطع الاتصال بخادم SMTP بعد أن ينتهي البرنامج من إرسال كافة رسائل البريد الإلكتروني.
</p>

<p>
	ستكون النتيجة كما يلي عند تشغيل البرنامج:
</p>

<pre class="ipsCode">Sending email to alice@example.com...
Sending email to bob@example.com...
Sending email to eve@example.com…
</pre>

<p>
	يتلقّى المستلمون رسالة بريد إلكتروني حول دفعاتهم الفائتة والتي ستشبه البريد الإلكتروني الذي ترسله يدويًا.
</p>

<h2 id="-16">
	الخلاصة
</h2>

<p>
	نتواصل مع بعضنا بعضًا عبر الإنترنت وعبر شبكات الهاتف المحمول باستخدام العديد من الطرق، ولكن يُعَد البريد الإلكتروني والرسائل النصية الطرق الأكثر انتشارًا، حيث يمكن لبرامجك التواصل عبر هذه القنوات، مما يمنحها ميزات إشعارات جديدة. يمكنك أيضًا كتابة برامج تعمل على حواسيب مختلفة وتتواصل مع بعضها بعضًا مباشرةً عبر البريد الإلكتروني، حيث يرسل أحد البرامج رسائل البريد الإلكتروني باستخدام بروتوكول SMTP بينما يستردها البرنامج الآخر باستخدام بروتوكول IMAP.
</p>

<p>
	توفّر وحدة <code>smtplib</code> الخاصة بلغة بايثون دوالًا لاستخدام بروتوكول SMTP لإرسال رسائل البريد الإلكتروني عبر خادم SMTP الخاص بمزوّد بريدك الإلكتروني، وتتيح الوحدتان <code>imapclient</code> و <code>pyzmail</code> الخارجيتان الوصولَ إلى خوادم IMAP واسترداد رسائل البريد الإلكتروني المرسلة إليك. يُعَد بروتوكول IMAP أكثر تعقيدًا من بروتوكول SMTP، ولكنه قوي جدًا ويسمح بالبحث عن رسائل بريد إلكتروني معينة وتنزيلها وتحليلها لاستخراج موضوع ونص البريد الإلكتروني بوصفها قيمًا نوعها سلسلة نصية.
</p>

<p>
	لا تسمح بعض خدمات البريد الإلكتروني الشائعة مثل جيميل بأن تستخدم بروتوكولات SMTP و IMAP المعيارية للوصول إلى خدماتها كإجراء احترازي من الرسائل غير المرغوب بها وبهدف الأمان، ولكن تعمل وحدة EZGmail بمثابة مغلِّف مناسب لواجهة برمجة تطبيقات جيميل، مما يسمح لسكربتات بايثون الخاصة بك بالوصول إلى حسابك على جيميل. نوصي بشدة بإعداد حساب جيميل منفصل لاستخدام سكربتاتك حتى لا تتسبب الأخطاء المحتملة في برنامجك في حدوث مشكلات لحسابك الشخصي على جيميل.
</p>

<p>
	ترجمة -وبتصرُّف- للقسم Sending Email من مقال <a href="https://automatetheboringstuff.com/2e/chapter18/" rel="external nofollow">Sending Email and Text Messages</a> لصاحبه Al Sweigart.
</p>

<h2 id="-17">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/%D8%AC%D8%AF%D9%88%D9%84%D8%A9-%D8%A7%D9%84%D9%85%D9%87%D8%A7%D9%85-%D9%88%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D8%A8%D8%B1%D8%A7%D9%85%D8%AC-%D8%A3%D8%AE%D8%B1%D9%89-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2394/" rel="">جدولة المهام وتشغيل برامج أخرى باستخدام لغة بايثون</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D9%82%D8%B1%D8%A7%D8%A1%D8%A9-%D9%85%D8%B3%D8%AA%D9%86%D8%AF%D8%A7%D8%AA-%D8%AC%D8%AF%D8%A7%D9%88%D9%84-%D8%A5%D9%83%D8%B3%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-python-r2374/" rel="">قراءة مستندات جداول إكسل باستخدام لغة بايثون Python</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D9%81%D9%8A-%D9%85%D8%B3%D8%AA%D9%86%D8%AF%D8%A7%D8%AA-%D8%A5%D9%83%D8%B3%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-python-r2375/" rel="">الكتابة في مستندات إكسل باستخدام لغة بايثون Python</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2395</guid><pubDate>Fri, 30 Aug 2024 15:05:01 +0000</pubDate></item><item><title>&#x62C;&#x62F;&#x648;&#x644;&#x629; &#x627;&#x644;&#x645;&#x647;&#x627;&#x645; &#x648;&#x62A;&#x634;&#x63A;&#x64A;&#x644; &#x628;&#x631;&#x627;&#x645;&#x62C; &#x623;&#x62E;&#x631;&#x649; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x644;&#x63A;&#x629; &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646;</title><link>https://academy.hsoub.com/programming/python/%D8%AC%D8%AF%D9%88%D9%84%D8%A9-%D8%A7%D9%84%D9%85%D9%87%D8%A7%D9%85-%D9%88%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D8%A8%D8%B1%D8%A7%D9%85%D8%AC-%D8%A3%D8%AE%D8%B1%D9%89-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2394/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_08/-------.png.5beb55e33e59349e3212d50bfa446580.png" /></p>
<p>
	تعرّفنا في <a href="https://academy.hsoub.com/programming/python/%D8%A7%D8%B3%D8%AA%D8%AE%D8%B1%D8%A7%D8%AC-%D8%A7%D9%84%D9%88%D9%82%D8%AA-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%88%D8%AD%D8%AF%D8%AA%D9%8A%D9%86-time-%D9%88-datetime-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2393/" rel="">المقال السابق</a> على كيفية الحصول على الوقت باستخدام الوحدتين <code>time</code> و <code>datetime</code> في لغة بايثون، وسنوضّح في هذا المقال كيفية كتابة البرامج التي تشغّل Launch برامجًا أخرى وفقًا لجدولٍ زمني محدّد باستخدام وحدتي <code>subprocess</code> و <code>threading</code>، فأسرع طريقة لكتابة البرامج في أغلب الأحيان هي الاستفادة من التطبيقات التي كتبها أشخاص آخرون مسبقًا.
</p>

<h2 id="multithreading">
	تعدّد الخيوط Multithreading
</h2>

<p>
	لنفترض أنك تريد جدولة شيفرتك البرمجية لتشغيلها بعد تأخير محدّد أو في وقت معيَّن، حيث يمكنك إضافة شيفرة برمجية كما يلي في بداية برنامجك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3871_9" style=""><span class="kwd">import</span><span class="pln"> time</span><span class="pun">,</span><span class="pln"> datetime

startTime </span><span class="pun">=</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">(</span><span class="lit">2029</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">31</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">while</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">.</span><span class="pln">now</span><span class="pun">()</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln"> startTime</span><span class="pun">:</span><span class="pln">
    time</span><span class="pun">.</span><span class="pln">sleep</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Program now starting on Halloween 2029'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span></pre>

<p>
	تحدّد الشيفرة البرمجية السابقة وقت البدء في 31 من الشهر العاشر من عام 2029، وتستمر في استدعاء الدالة <code>time.sleep(1)‎</code> حتى الوصول إلى وقت البدء، ولا يستطيع برنامجك فعل أيّ شيء أثناء انتظار انتهاء حلقة استدعاءات <code>time.sleep()‎</code>، ويبقى متوقفًا حتى يوم الهالوين من عام 2029، لأن برامج بايثون افتراضيًا لها خيط Thread تنفيذ واحد.
</p>

<p>
	يمكنك فهم ما هو خيط التنفيذ من خلال تذكّر ما ناقشناه في مقالٍ سابق حول التحكم في التدفق عندما تخيّلت تنفيذ برنامجٍ ما على أنه وضع إصبعك على سطرٍ من الشيفرة البرمجية في برنامجك والانتقال إلى السطر التالي أو المكان التي ترسله تعليمة التحكم في التدفق، حيث يحتوي البرنامج ذو الخيط الواحد Single-threaded على إصبع واحد فقط، ولكن يحتوي البرنامج متعدد الخيوط Multithreaded على أصابع متعددة. يستمر كل إصبع في التحرك إلى السطر التالي من الشيفرة البرمجية كما تحدِّده تعليمات التحكم في التدفق، ولكن يمكن أن تكون الأصابع في أماكن مختلفة في البرنامج لتنفيذ أسطر مختلفة من الشيفرة البرمجية في الوقت ذاته. لاحظ أن جميع البرامج التي مرّت معنا حتى الآن ذات خيط واحد.
</p>

<p>
	يمكنك تنفيذ الشيفرة البرمجية المؤجَّلة أو المجدولة في خيط منفصل باستخدام وحدة بايثون <code>threading</code> بدلًا من أن تنتظر شيفرتك البرمجية بأكملها انتهاء الدالة <code>time.sleep()‎</code>، حيث سيتوقف الخيط المنفصل مؤقتًا عند استدعاءات <code>time.sleep</code>، بينما يمكن لبرنامجك تنفيذ أشياء أخرى في الخيط الأصلي.
</p>

<p>
	ننشئ خيطًا منفصلًا من خلال إنشاء كائن <code>Thread</code> أولًا باستخدام استدعاء الدالة <code>threading.Thread()‎</code>. إذًا لندخِل الشيفرة البرمجية التالية في ملفٍ جديد ونحفظه بالاسم threadDemo.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3871_11" style=""><span class="pln">   </span><span class="kwd">import</span><span class="pln"> threading</span><span class="pun">,</span><span class="pln"> time
   </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Start of program.'</span><span class="pun">)</span><span class="pln">

</span><span class="pun">➊</span><span class="pln"> </span><span class="kwd">def</span><span class="pln"> takeANap</span><span class="pun">():</span><span class="pln">
       time</span><span class="pun">.</span><span class="pln">sleep</span><span class="pun">(</span><span class="lit">5</span><span class="pun">)</span><span class="pln">
       </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Wake up!'</span><span class="pun">)</span><span class="pln">

</span><span class="pun">➋</span><span class="pln"> threadObj </span><span class="pun">=</span><span class="pln"> threading</span><span class="pun">.</span><span class="typ">Thread</span><span class="pun">(</span><span class="pln">target</span><span class="pun">=</span><span class="pln">takeANap</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➌</span><span class="pln"> threadObj</span><span class="pun">.</span><span class="pln">start</span><span class="pun">()</span><span class="pln">

   </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'End of program.'</span><span class="pun">)</span></pre>

<p>
	عرّفنا في الشيفرة البرمجية السابقة الدالة التي نريد استخدامها في خيطٍ جديد ➊، واستدعينا الدالة <code>threading.Thread()‎</code> ومرّرنا لها وسيط الكلمات المفتاحية <code>target=takeANap</code> ➋ لإنشاء كائن <code>Thread</code>، وهذا يعني أن الدالة التي نريد استدعاءها في الخيط الجديد هي <code>takeANap()‎</code>. لاحظ أن وسيط الكلمات المفتاحية Keyword Argument الذي هو <code>target=takeANap</code> وليس <code>target=takeANap()‎</code>، لأنك تريد تمرير الدالة <code>takeANap()‎</code> كوسيط، ولا تريد استدعاءها وتمرير قيمتها المُعادة.
</p>

<p>
	خزّنا الكائن <code>Thread</code> الذي أنشأته الدالة <code>threading.Thread()‎</code> في المتغير <code>threadObj</code>، ثم استدعينا الدالة <code>threadObj.start()‎</code> ➌ لإنشاء الخيط الجديد والبدء في تنفيذ الدالة المستهدفة في الخيط الجديد. سيكون الناتج كما يلي عند تشغيل هذا البرنامج:
</p>

<pre class="ipsCode">Start of program.
End of program.
Wake up!
</pre>

<p>
	قد يكون الخرج السابق مربكًا بعض الشيء، حيث إذا كانت التعليمة <code>print('End of program.')‎</code> هي السطر الأخير من البرنامج، فقد تعتقد أنه آخر شيء سيُطبَع، ولكن تُشغَّل الدالة المستهدفة للمتغير <code>threadObj</code> في خيط تنفيذ جديد عند استدعاء الدالة <code>threadObj.start()‎</code>، لذا تظهر العبارة <code>Wake up!‎</code> في النهاية. فكّر في الأمر كإصبعٍ ثانٍ يظهر في بداية الدالة <code>takeANap()‎</code>، حيث يستمر الخيط الرئيسي في تنفيذ التعليمة <code>print('End of program.')‎</code>، بينما يتوقف الخيط الجديد الذي كان ينفّذ استدعاء <code>time.sleep(5)‎</code> مؤقتًا لمدة 5 ثوانٍ، ويطبع عبارة <code>‎'Wake up!'‎</code> بعد أن يستيقظ من غفوته لمدة 5 ثوان، ثم يعود من الدالة <code>takeANap()‎</code>، وبالتالي فإن عبارة <code>‎'Wake up!'‎</code> هي آخر ما يطبعه البرنامج زمنيًا.
</p>

<p>
	ينتهي البرنامج عادةً عند تشغيل السطر الأخير من الشيفرة البرمجية في الملف أو عند استدعاء الدالة <code>sys.exit()‎</code>، ولكن يحتوي البرنامج threadDemo.py على خيطين هما: الأول هو الخيط الأصلي الذي بدأ في بداية البرنامج وينتهي بعد التعليمة <code>print('End of program.')‎</code>، والخيط الثاني الذي ينشأ عند استدعاء الدالة <code>threadObj.start()‎</code> ويبدأ عند بداية الدالة <code>takeANap()‎</code> وينتهي بعد العودة من الدالة <code>takeANap()‎</code>.
</p>

<p>
	لن ينتهي برنامج <a href="https://wiki.hsoub.com/Python" rel="external">بايثون</a> حتى تنتهي جميع خيوطه. لاحظ أن الخيط الثاني لا يزال ينفّذ الاستدعاء <code>time.sleep(5)‎</code> عند تشغيل البرنامج threadDemo.py بالرغم من انتهاء الخيط الأصلي.
</p>

<h3 id="">
	تمرير الوسطاء إلى الدالة المستهدفة للخيط
</h3>

<p>
	إذا أخذت الدالة المستهدفة التي تريد تشغيلها في الخيط الجديد وسطاءً، فيمكنك تمرير وسطائها إلى الدالة <code>threading.Thread()‎</code>. لنفترض مثلًا أنك تريد تشغيل استدعاء الدالة <code>print()‎</code> التالية في خيطها الخاص:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3871_13" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Cats'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Dogs'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Frogs'</span><span class="pun">,</span><span class="pln"> sep</span><span class="pun">=</span><span class="str">' &amp; '</span><span class="pun">)</span><span class="pln">
</span><span class="typ">Cats</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln"> </span><span class="typ">Dogs</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln"> </span><span class="typ">Frogs</span></pre>

<p>
	يحتوي استدعاء الدالة <code>print()‎</code> السابق على ثلاث وسطاء عادية هي: <code>'Cats'</code> و <code>'Dogs'</code> و <code>'Frogs'</code> ووسيط كلمات مفتاحية واحد هو <code>sep=' &amp; '‎</code>، حيث يمكن تمرير الوسطاء العادية كقائمة إلى وسيط الكلمات المفتاحية <code>args</code> في الدالة <code>threading.Thread()‎</code>، ويمكن تحديد وسيط الكلمات المفتاحية كقاموس لوسيط الكلمات المفتاحية <code>kwargs</code> في الدالة <code>threading.Thread()‎</code>.
</p>

<p>
	لندخِل الآن ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3871_15" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> threading
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> threadObj </span><span class="pun">=</span><span class="pln"> threading</span><span class="pun">.</span><span class="typ">Thread</span><span class="pun">(</span><span class="pln">target</span><span class="pun">=</span><span class="kwd">print</span><span class="pun">,</span><span class="pln"> args</span><span class="pun">=[</span><span class="str">'Cats'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Dogs'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Frogs'</span><span class="pun">],</span><span class="pln">
kwargs</span><span class="pun">={</span><span class="str">'sep'</span><span class="pun">:</span><span class="pln"> </span><span class="str">' &amp; '</span><span class="pun">})</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> threadObj</span><span class="pun">.</span><span class="pln">start</span><span class="pun">()</span><span class="pln">
</span><span class="typ">Cats</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln"> </span><span class="typ">Dogs</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln"> </span><span class="typ">Frogs</span></pre>

<p>
	نتأكد من تمرير الوسطاء <code>'Cats'</code> و <code>'Dogs'</code> و <code>'Frogs'</code> إلى الدالة <code>print()‎</code> في الخيط الجديد من خلال تمرير <code>args=['Cats', 'Dogs', 'Frogs']‎</code> إلى الدالة <code>threading.Thread()‎</code>، ونتأكد من تمرير وسيط الكلمات المفتاحية <code>sep=' &amp; '‎</code> إلى الدالة <code>print()‎</code> في الخيط الجديد من خلال تمرير <code>kwargs={'sep': '&amp; '}‎</code> إلى الدالة <code>threading.Thread()‎</code>. يؤدي استدعاء الدالة <code>threadObj.start()‎</code> إلى إنشاء خيط جديد لاستدعاء الدالة <code>print()‎</code>، وستمرّر إليها <code>'Cats'</code> و <code>'Dogs'</code> و <code>'Frogs'</code> كوسطاء والقيمة <code>' &amp; '</code> لوسيط الكلمات المفتاحية <code>sep</code>.
</p>

<p>
	تُعَد الطريقة التالية غير صحيحة لإنشاء الخيط الجديد الذي يستدعي الدالة <code>print()‎</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3871_17" style=""><span class="pln">threadObj </span><span class="pun">=</span><span class="pln"> threading</span><span class="pun">.</span><span class="typ">Thread</span><span class="pun">(</span><span class="pln">target</span><span class="pun">=</span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Cats'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Dogs'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Frogs'</span><span class="pun">,</span><span class="pln"> sep</span><span class="pun">=</span><span class="str">' &amp; '</span><span class="pun">))</span></pre>

<p>
	تؤدي الطريقة السابقة إلى استدعاء الدالة <code>print()‎</code> وتمرير القيمة المُعادة الخاصة بها كوسيط الكلمات المفتاحية <code>target</code>، حيث تكون القيمة المُعادة الخاصة بالدالة <code>print()‎</code> دائمًا <code>None</code>، ولا تؤدي إلى تمرير الدالة <code>print()‎</code> نفسها، لذا استخدم وسطاء الكلمات المفتاحية <code>args</code> و <code>kwargs</code> الخاصة بالدالة <code>threading.Thread()‎</code> عند تمرير الوسطاء إلى دالة في خيطٍ جديد.
</p>

<h3 id="concurrency">
	مشاكل التزامن Concurrency
</h3>

<p>
	يمكنك بسهولة إنشاء عدة خيوط جديدة وتشغيلها جميعًا في الوقت نفسه، ولكن يمكن أن تسبّب الخيوط المتعددة أيضًا مشكلات في التزامن التي تحدث عندما تقرأ الخيوطُ المتغيرات وتكتبها في الوقت نفسه، مما يؤدّي إلى تصادم الخيوط مع بعضها البعض. قد يكون من الصعب إعادة إنتاج مشكلات التزامن بصورة متناسقة، مما يصعّب تنقيح أخطائها Debug.
</p>

<p>
	تُعَد البرمجة متعددة الخيوط موضوعًا واسعًا ولن نناقشه في هذا المقال، ولكن ما عليك أن تضعه في بالك هو أنه يجب ألّا تسمح أبدًا لخيوط متعددة بقراءة أو كتابة المتغيرات نفسها لتجنب مشكلات التزامن. تأكّد من أن الدالة المستهدفة لكائن <code>Thread</code> الجديد عند إنشائه تستخدم المتغيرات المحلية فقط في تلك الدالة، مما سيؤدّي إلى تجنب مشكلات التزامن التي يصعب تنقيح أخطائها في برامجك.
</p>

<h2 id="xkcdcomics">
	تطبيق عملي: برنامج متعدد الخيوط لتنزيل قصص XKCD الهزلية Comics
</h2>

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

<p>
	يحتوي البرنامج متعدد الخيوط على بعض الخيوط التي تنزّل القصص الهزلية، وتنشئ بعض الخيوط الأخرى الاتصالات وتكتب ملفات الصور الهزلية على القرص الصلب، حيث يستخدم هذا البرنامج <a href="https://academy.hsoub.com/devops/networking/%D8%A2%D9%84%D9%8A%D8%A9-%D8%B9%D9%85%D9%84-%D8%B4%D8%A8%D9%83%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA-r571/" rel="">اتصال الإنترنت</a> الخاص بك بكفاءة أكبر وينزّل مجموعة القصص الهزلية بسرعة أكبر. افتح تبويبًا جديدًا في محرّرك لإنشاء ملف جديد واحفظه بالاسم threadedDownloadXkcd.py، وستعدّل هذا البرنامج لإضافة خيوط متعددة، فالشيفرة البرمجية المُعدَّلة بالكامل متاحة للتنزيل على <a href="https://nostarch.com/automatestuff2/" rel="external nofollow">nostarch</a>.
</p>

<h3 id="-1">
	الخطوة الأولى: تعديل البرنامج لاستخدام دالة
</h3>

<p>
	سيكون هذا البرنامج في أغلبه مشابهًا لشيفرة التنزيل البرمجية التي كتبناها في مقالٍ سابق، لذا سنتخطّى شرح <code>requests</code> وشيفرة المكتبة Beautiful Soup. التغييرات الرئيسية التي يجب أن تجريها هي استيراد الوحدة <code>threading</code> وإنشاء الدالة <code>downloadXkcd()‎</code> التي تأخذ أرقام البداية والنهاية للقصة الهزلية كمعاملاتٍ لها.
</p>

<p>
	سيؤدّي استدعاء الدالة <code>downloadXkcd(140, 280)‎</code> مثلًا إلى تكرار شيفرة التنزيل لتنزيل القصص الهزلية <a href="https://xkcd.com/140/" rel="external nofollow">140</a> و <a href="https://xkcd.com/141/" rel="external nofollow">141</a> و <a href="https://xkcd.com/142/" rel="external nofollow">142</a> وحتى القصة الهزلية <a href="https://xkcd.com/279/" rel="external nofollow">279</a>. سيستدعي كلُّ خيط تنشئه الدالةَ <code>downloadXkcd()‎</code> ويمرّر مجالًا مختلفًا من القصص الهزلية لتنزيلها.
</p>

<p>
	أضِف الشيفرة البرمجية التالية إلى برنامج threadedDownloadXkcd.py الخاص بك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3871_21" style=""><span class="pln">   </span><span class="com">#! python3</span><span class="pln">
   </span><span class="com"># threadedDownloadXkcd.py - تنزيل قصص‫ XKCD الهزلية باستخدام خيوط متعددة</span><span class="pln">

   </span><span class="kwd">import</span><span class="pln"> requests</span><span class="pun">,</span><span class="pln"> os</span><span class="pun">,</span><span class="pln"> bs4</span><span class="pun">,</span><span class="pln"> threading
</span><span class="pun">➊</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">makedirs</span><span class="pun">(</span><span class="str">'xkcd'</span><span class="pun">,</span><span class="pln"> exist_ok</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">    </span><span class="com"># ‫تخزين القصص الهزلية في المجلد ‎./xkcd</span><span class="pln">

</span><span class="pun">➋</span><span class="pln"> </span><span class="kwd">def</span><span class="pln"> downloadXkcd</span><span class="pun">(</span><span class="pln">startComic</span><span class="pun">,</span><span class="pln"> endComic</span><span class="pun">):</span><span class="pln">
     </span><span class="pun">➌</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> urlNumber </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="pln">startComic</span><span class="pun">,</span><span class="pln"> endComic</span><span class="pun">):</span><span class="pln">
           </span><span class="com"># تنزيل الصفحة</span><span class="pln">
           </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Downloading page https://xkcd.com/%s...'</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> </span><span class="pun">(</span><span class="pln">urlNumber</span><span class="pun">))</span><span class="pln">
        </span><span class="pun">➍</span><span class="pln"> res </span><span class="pun">=</span><span class="pln"> requests</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="str">'https://xkcd.com/%s'</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> </span><span class="pun">(</span><span class="pln">urlNumber</span><span class="pun">))</span><span class="pln">
           res</span><span class="pun">.</span><span class="pln">raise_for_status</span><span class="pun">()</span><span class="pln">

        </span><span class="pun">➎</span><span class="pln"> soup </span><span class="pun">=</span><span class="pln"> bs4</span><span class="pun">.</span><span class="typ">BeautifulSoup</span><span class="pun">(</span><span class="pln">res</span><span class="pun">.</span><span class="pln">text</span><span class="pun">,</span><span class="pln"> </span><span class="str">'html.parser'</span><span class="pun">)</span><span class="pln">

           </span><span class="com"># البحث عن عنوان‫ URL للصورة الهزلية</span><span class="pln">
        </span><span class="pun">➏</span><span class="pln"> comicElem </span><span class="pun">=</span><span class="pln"> soup</span><span class="pun">.</span><span class="pln">select</span><span class="pun">(</span><span class="str">'#comic img'</span><span class="pun">)</span><span class="pln">
           </span><span class="kwd">if</span><span class="pln"> comicElem </span><span class="pun">==</span><span class="pln"> </span><span class="pun">[]:</span><span class="pln">
               </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Could not find comic image.'</span><span class="pun">)</span><span class="pln">
           </span><span class="kwd">else</span><span class="pun">:</span><span class="pln">
            </span><span class="pun">➐</span><span class="pln"> comicUrl </span><span class="pun">=</span><span class="pln"> comicElem</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">get</span><span class="pun">(</span><span class="str">'src'</span><span class="pun">)</span><span class="pln">
               </span><span class="com"># تنزيل الصورة</span><span class="pln">
               </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Downloading image %s...'</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> </span><span class="pun">(</span><span class="pln">comicUrl</span><span class="pun">))</span><span class="pln">
            </span><span class="pun">➑</span><span class="pln"> res </span><span class="pun">=</span><span class="pln"> requests</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="str">'https:'</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> comicUrl</span><span class="pun">)</span><span class="pln">
               res</span><span class="pun">.</span><span class="pln">raise_for_status</span><span class="pun">()</span><span class="pln">

               </span><span class="com"># حفظ الصورة في المجلد‫ ‎./xkcd</span><span class="pln">
               imageFile </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="pln">os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="str">'xkcd'</span><span class="pun">,</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">basename</span><span class="pun">(</span><span class="pln">comicUrl</span><span class="pun">)),</span><span class="pln">
</span><span class="str">'wb'</span><span class="pun">)</span><span class="pln">
               </span><span class="kwd">for</span><span class="pln"> chunk </span><span class="kwd">in</span><span class="pln"> res</span><span class="pun">.</span><span class="pln">iter_content</span><span class="pun">(</span><span class="lit">100000</span><span class="pun">):</span><span class="pln">
                   imageFile</span><span class="pun">.</span><span class="pln">write</span><span class="pun">(</span><span class="pln">chunk</span><span class="pun">)</span><span class="pln">
               imageFile</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">

</span><span class="com"># إنشاء وبدء كائنات الخيط‫ Thread</span><span class="pln">
</span><span class="com"># انتظار انتهاء جميع الخيوط</span></pre>

<p>
	نستورد <a href="https://academy.hsoub.com/programming/general/%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%88%D8%AD%D8%AF%D8%A7%D8%AA-r1333/" rel="">الوحدات</a> التي نحتاجها، ثم ننشئ مجلدًا لتخزين القصص الهزلية ➊، ونبدأ بتعريف الدالة <code>downloadxkcd()‎</code> ➋، ثم نمر ضمن حلقة على جميع الأرقام الموجودة في المجال المحدَّد ➌ وننزّل كل صفحة ➍. نستخدم المكتبة Beautiful Soup للبحث في شيفرة <a href="https://wiki.hsoub.com/HTML" rel="external">HTML</a> لكل صفحة ➎ والعثور على الصورة الهزلية ➏، حيث إذا لم نعثر على صورة هزلية في الصفحة، فإننا نطبع رسالة، وإلّا سنحصل على <a href="https://academy.hsoub.com/programming/general/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%B9%D9%86%D9%88%D8%A7%D9%86-url-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9%D9%87-r1435/" rel="">عنوان URL</a> للصورة ➐ وننزّلها ➑. أخيرًا، نحفظ الصورة في المجلد الذي أنشأناه.
</p>

<h3 id="-2">
	الخطوة الثانية: إنشاء وبدء الخيوط
</h3>

<p>
	عرّفنا الدالة <code>downloadxkcd()‎</code> وسننشئ الآن الخيوط المتعددة التي يستدعي كل منها الدالة <code>downloadxkcd()‎</code> لتنزيل مجالات مختلفة من القصص الهزلية من موقع XKCD. أضِف الشيفرة البرمجية التالية إلى البرنامج threadedDownloadXkcd.py بعد تعريف الدالة <code>downloadxkcd()‎</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3871_28" style=""><span class="com">#! python3</span><span class="pln">
</span><span class="com"># threadedDownloadXkcd.py - تنزيل قصص‫ XKCD الهزلية باستخدام خيوط متعددة</span><span class="pln">

</span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">

</span><span class="com"># ‫إنشاء وبدء كائنات الخيط Thread</span><span class="pln">
downloadThreads </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">             </span><span class="com"># قائمة بجميع كائنات الخيط‫ Thread</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> i </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">140</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">):</span><span class="pln">    </span><span class="com"># التكرار 14 مرة وإنشاء 14 خيطًا</span><span class="pln">
    start </span><span class="pun">=</span><span class="pln"> i
    end </span><span class="pun">=</span><span class="pln"> i </span><span class="pun">+</span><span class="pln"> </span><span class="lit">9</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> start </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0</span><span class="pun">:</span><span class="pln">
        start </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="com"># لا توجد قصة هزلية رقمها 0، لذا اضبط المتغير على القيمة 1</span><span class="pln">
    downloadThread </span><span class="pun">=</span><span class="pln"> threading</span><span class="pun">.</span><span class="typ">Thread</span><span class="pun">(</span><span class="pln">target</span><span class="pun">=</span><span class="pln">downloadXkcd</span><span class="pun">,</span><span class="pln"> args</span><span class="pun">=(</span><span class="pln">start</span><span class="pun">,</span><span class="pln"> end</span><span class="pun">))</span><span class="pln">
    downloadThreads</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">downloadThread</span><span class="pun">)</span><span class="pln">
    downloadThread</span><span class="pun">.</span><span class="pln">start</span><span class="pun">()</span></pre>

<p>
	ننشئ أولًا قائمة فارغة <code>downloadThreads</code>، والتي ستساعدنا على تعقّب العديد من كائنات <code>Thread</code> التي سننشئها، ثم نبدأ حلقة <code>for</code>، حيث ننشئ في كل تكرار من هذه الحلقة كائن <code>Thread</code> باستخدام الدالة <code>threading.Thread()‎</code>، ونلحِق هذا الكائن بالقائمة، ونستدعي التابع <code>start()‎</code> لبدء تشغيل الدالة <code>downloadXkcd()‎</code> في الخيط الجديد. تضبط <a href="https://wiki.hsoub.com/Python/for" rel="external">حلقة for</a> المتغير <code>i</code> على القيم من 0 إلى 140 بخطوة مقدارها 10، لذا يجب ضبط المتغير <code>i</code> على القيمة 0 في التكرار الأول، وعلى القيمة 10 في التكرار الثاني، وعلى القيمة 20 في التكرار الثالث، وإلخ. نمرّر الوسيط <code>args=(start, end)‎</code> إلى الدالة <code>threading.Thread()‎</code>، لذا سيكون الوسيطان المُمرَّران إلى الدالة <code>downloadXkcd()‎</code> هما 1 و9 في التكرار الأول، و10 و19 في التكرار الثاني، و20 و29 في التكرار الثالث، وإلخ.
</p>

<p>
	سينتقل الخيط الرئيسي إلى التكرار التالي من <a href="https://wiki.hsoub.com/Python/for" rel="external">حلقة for</a> وينشئ الخيط التالي عند استدعاء التابع <code>start()‎</code> الخاص بالكائن <code>Thread</code> ويبدأ الخيط الجديد بتشغيل الشيفرة البرمجية الموجودة ضمن الدالة <code>downloadXkcd()‎</code>.
</p>

<h3 id="-3">
	الخطوة الثالثة: انتظار انتهاء جميع الخيوط
</h3>

<p>
	يتحرك الخيط الرئيسي كالمعتاد بينما تنزّل الخيوطُ الأخرى التي أنشأناها القصصَ الهزلية، ولكن لنفترض أن هناك بعض التعليمات البرمجية التي لا تريد تشغيلها في الخيط الرئيسي حتى يكتمل تنفيذ جميع الخيوط الأخرى، حيث سيتوقّف استدعاء التابع <code>join()‎</code> الخاص بالكائن <code>Thread</code> حتى انتهاء هذا الخيط. يمكن للخيط الرئيسي استدعاء التابع <code>join()‎</code> على كلٍّ من الخيوط الأخرى باستخدام حلقة <code>for</code> للتكرار على كافة كائنات <code>Thread</code> الموجودة في القائمة <code>downloadThreads</code>. أضِف الآن ما يلي إلى نهاية برنامجك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3871_31" style=""><span class="com">#! python3</span><span class="pln">
</span><span class="com"># threadedDownloadXkcd.py - تنزيل قصص‫ XKCD الهزلية باستخدام خيوط متعددة</span><span class="pln">

</span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">

</span><span class="com"># الانتظار حتى انتهاء جميع الخيوط</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> downloadThread </span><span class="kwd">in</span><span class="pln"> downloadThreads</span><span class="pun">:</span><span class="pln">
    downloadThread</span><span class="pun">.</span><span class="pln">join</span><span class="pun">()</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Done.'</span><span class="pun">)</span></pre>

<p>
	لن تُطبَع السلسلة النصية <code>‎'Done.'‎</code> حتى إعادة جميع استدعاءات التابع <code>join()‎</code>، حيث إذا اكتمل كائن <code>Thread</code> عند استدعاء التابع <code>join()‎</code> الخاص به، فسيعود التابع مباشرةً ببساطة. إذا أردتَ توسيع هذا البرنامج باستخدام شيفرة برمجية تُشغَّل فقط بعد تنزيل كافة القصص الهزلية، فيمكنك وضع شيفرتك البرمجية الجديدة مكان السطر <code>print('Done.')‎</code>.
</p>

<h2 id="launching">
	تشغيل Launching برامج أخرى من برنامج بايثون
</h2>

<p>
	يمكن لبرنامج بايثون الخاص بك بدء برامج أخرى على حاسوبك باستخدام الدالة <code>Popen()‎</code> الموجودة في الوحدة المُدمَجة <code>subprocess</code>، حيث يرمز الحرف P في اسم هذه الدالة إلى العملية Process. إذا كان لديك نسخ Instances متعددة من تطبيق مفتوح، فستُعَد كلّ نسخة من هذه النسخ عمليةً منفصلة للبرنامج نفسه، فمثلًا إذا فتحتَ نوافذ متعددة لمتصفح الويب في الوقت نفسه، فإن كلّ نافذة من تلك النوافذ هي عملية مختلفة لبرنامج متصفح الويب. يوضّح الشكل التالي مثالًا لعمليات آلة حاسبة متعددة مفتوحة في وقتٍ واحد:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="156794" href="https://academy.hsoub.com/uploads/monthly_2024_08/01_000087.jpg.e7c7fe0cb8f161256ed5217d88653fc3.jpg" rel=""><img alt="01 000087" class="ipsImage ipsImage_thumbnailed" data-fileid="156794" data-unique="ayb7rptyc" src="https://academy.hsoub.com/uploads/monthly_2024_08/01_000087.jpg.e7c7fe0cb8f161256ed5217d88653fc3.jpg"> </a>
</p>

<p style="text-align: center;">
	ست عمليات مُشغَّلة لبرنامج الآلة الحاسبة نفسه
</p>

<p>
	يمكن أن يكون لكل عملية خيوط متعددة، ولا يمكن للعملية قراءة وكتابة متغيرات عملية أخرى مباشرةً على عكس الخيوط. إذا افترضنا أن برنامجًا متعدد الخيوط يحتوي على أصابع متعددة تتبع الشيفرة البرمجية، فإن فتح عمليات متعددة للبرنامج نفسه يشبه وجود صديق لديه نسخة منفصلة من شيفرة البرنامج البرمجية، وكلاكما تنفّذان البرنامج نفسه بصورة مستقلة.
</p>

<p>
	إذا أردتَ بدء برنامج خارجي من سكربت بايثون الخاص بك، فمرّر اسم ملف البرنامج إلى الدالة <code>subprocess.Popen()‎</code>. انقر بزر الفأرة الأيمن على عنصر القائمة "ابدأ Start" الخاص بالتطبيق وحدّد الخيار "خصائص Properties" لعرض اسم ملف التطبيق في <span ipsnoautolink="true">نظام ويندوز</span>، وانقر مع الضغط على مفتاح <code>CTRL</code> على التطبيق وحدّد الخيار "إظهار محتويات الحزمة Show Package Contents" للعثور على مسار الملف القابل للتنفيذ في نظام ماك macOS. ستعيد بعد ذلك الدالة <code>Popen()‎</code> مباشرةً، وضع في بالك أن البرنامج الذي شغّلناه لا يعمل في الخيط نفسه لبرنامج بايثون الخاص بك.
</p>

<p>
	أدخِل ما يلي في الصدفة التفاعلية على حاسوبٍ يعمل بنظام ويندوز:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3871_33" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> subprocess
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> subprocess</span><span class="pun">.</span><span class="typ">Popen</span><span class="pun">(</span><span class="str">'C:\\Windows\\System32\\calc.exe'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">subprocess</span><span class="pun">.</span><span class="typ">Popen</span><span class="pln"> object at </span><span class="lit">0x0000000003055A58</span><span class="pun">&gt;</span></pre>

<p>
	أدخِل ما يلي على نظام يعمل بنظام أوبنتو لينكس Ubuntu Linux:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3871_35" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> subprocess
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> subprocess</span><span class="pun">.</span><span class="typ">Popen</span><span class="pun">(</span><span class="str">'/snap/bin/gnome-calculator'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">subprocess</span><span class="pun">.</span><span class="typ">Popen</span><span class="pln"> object at </span><span class="lit">0x7f2bcf93b20</span><span class="pun">&gt;</span></pre>

<p>
	تختلف العملية قليلًا على نظام ماك macOS، لذا اطّلع على قسم "فتح الملفات باستخدام التطبيقات الافتراضية" من هذا المقال لمزيد من التفاصيل.
</p>

<p>
	تكون القيمة المُعادة هي كائن <code>Popen</code> الذي له تابعان مفيدان هما: <code>poll()‎</code> و <code>wait()‎</code>، حيث يمكنك التفكير في التابع <code>poll()‎</code> بأنك تسأل سائقك "هل وصلنا؟" مرارًا وتكرارًا حتى الوصول إلى وِجهتك، ويعيد هذا التابع القيمة <code>None</code> إذا كانت العملية لا تزال قيد التشغيل في وقت استدعاء هذا التابع. إذا أُنهي البرنامج، فسيعيد العدد الصحيح لرمز الخروج exit code الخاص بالعملية، حيث يُستخدَم رمز الخروج للإشارة إلى أن العملية انتهت بدون أخطاء (رمز الخروج 0) أو ما إذا قد تسبّب خطأٌ ما في إنهاء العملية (رمز خروج غير صفري قيمته 1 عادةً، ولكنه قد يختلف اعتمادًا على البرنامج).
</p>

<p>
	يشبه التابع <code>wait()‎</code> الانتظار حتى يصل السائق إلى وِجهتك، حيث يوقِف هذا التابع التنفيذ حتى تنتهي العملية التي شغّلناها، ويُعَد ذلك مفيدًا إذا أردتَ أن يتوقف برنامجك مؤقتًا حتى ينتهي المستخدم من البرنامج الآخر. القيمة المُعادة من التابع <code>wait()‎</code> هي العدد الصحيح لرمز الخروج الخاص بالعملية.
</p>

<p>
	أدخِل ما يلي في الصدفة التفاعلية على نظام ويندوز، ولاحظ أن استدعاء التابع <code>wait()‎</code> سيوقِف التنفيذ حتى إنهاء برنامج الرسام MS Paint الذي شغّلناه:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3871_37" style=""><span class="pln">   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> subprocess
</span><span class="pun">➊</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> paintProc </span><span class="pun">=</span><span class="pln"> subprocess</span><span class="pun">.</span><span class="typ">Popen</span><span class="pun">(</span><span class="str">'c:\\Windows\\System32\\mspaint.exe'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➋</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> paintProc</span><span class="pun">.</span><span class="pln">poll</span><span class="pun">()</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="kwd">None</span><span class="pln">
   </span><span class="kwd">True</span><span class="pln">
</span><span class="pun">➌</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> paintProc</span><span class="pun">.</span><span class="pln">wait</span><span class="pun">()</span><span class="pln"> </span><span class="com"># ‫لن يعود حتى إغلاق برنامج الرسام MS Paint</span><span class="pln">
   </span><span class="lit">0</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> paintProc</span><span class="pun">.</span><span class="pln">poll</span><span class="pun">()</span><span class="pln">
   </span><span class="lit">0</span></pre>

<p>
	فتحنا في المثال السابق عملية برنامج الرسام MS Paint ➊، وتحقّقنا مما إذا كان التابع <code>poll()‎</code> يعيد القيمة <code>None</code> ➋ بينما لا تزال العملية قيد التشغيل، حيث ينبغي أن يكون ذلك صحيحًا لأنها لا تزال قيد التشغيل. أغلقنا بعد ذلك برنامج الرسام MS Paint واستدعينا التابع <code>wait()‎</code> للعملية المنتهية ➌. سيعيد الآن التابعان <code>wait()‎</code> و <code>poll()‎</code> القيمة 0، مما يشير إلى أن العملية قد انتهت بدون أخطاء.
</p>

<p>
	<strong>ملاحظة</strong>: إذا شغّلت برنامج الآلة الحاسبة calc.exe على نظام ويندوز 10 باستخدام الدالة <code>subprocess.Popen()‎</code>، فستلاحظ أن التابع <code>wait()‎</code> يعيد مباشرةً على عكس برنامج الرسام mspaint.exe بالرغم من أن تطبيق الآلة الحاسبة لا يزال قيد التشغيل، والسبب أن برنامج الآلة الحاسبة calc.exe يطلِق تطبيق الآلة الحاسبة ثم يغلق نفسه مباشرةً. يُعَد برنامج الآلة الحاسبة الخاص بنظام ويندوز "تطبيق متجر مايكروسوفت موثوق به"، ولن ندخل في تفاصيله في هذا المقال، ولكن يكفي أن نقول أنه يمكن تشغيل البرامج بعدة طرقٍ خاصة بالتطبيق و<a href="https://academy.hsoub.com/apps/operating-systems/%D9%86%D8%B8%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84/" rel="">بنظام التشغيل</a>.
</p>

<h3 id="popen">
	تمرير وسطاء سطر الأوامر إلى الدالة Popen()‎
</h3>

<p>
	يمكنك تمرير وسطاء سطر الأوامر إلى العمليات التي تنشئها باستخدام الدالة <code>Popen()‎</code> من خلال تمرير قائمة تمثّل الوسيط الوحيد لهذه الدالة. ستكون السلسلة النصية الأولى في هذه القائمة هي اسم الملف التنفيذي للبرنامج الذي تريد تشغيله، وتكون جميع السلاسل النصية اللاحقة وسطاء سطر الأوامر التي تمرّرها إلى البرنامج عندما يبدأ. تمثّل هذه القائمة قيمة <code>sys.argv</code> للبرنامج الذي شغّلناه.
</p>

<p>
	لا تستخدم معظم التطبيقات ذات واجهة المستخدم الرسومية Graphical User Interface -أو GUI اختصارًا- وسطاء سطر الأوامر على نطاق واسع كما تفعل البرامج المستندة إلى <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%A7-%D9%87%D9%88-%D8%B3%D8%B7%D8%B1-%D8%A7%D9%84%D8%A3%D9%88%D8%A7%D9%85%D8%B1-%D8%9F-r353/" rel="">سطر الأوامر</a> أو البرامج المستندة إلى الطرفية Terminal، ولكن تقبل معظم تطبيقات واجهة المستخدم الرسومية وسيطًا واحدًا للملف الذي ستفتحه التطبيقات مباشرةً عندما تبدأ. إذا استخدمتَ نظام ويندوز، فأنشئ مثلًا ملفًا نصيًا بسيطًا بالاسم C:\Users\Al\hello.txt، ثم أدخِل ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3871_40" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> subprocess</span><span class="pun">.</span><span class="typ">Popen</span><span class="pun">([</span><span class="str">'C:\\Windows\\notepad.exe'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'C:\\Users\Al\\hello.txt'</span><span class="pun">])</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">subprocess</span><span class="pun">.</span><span class="typ">Popen</span><span class="pln"> object at </span><span class="lit">0x00000000032DCEB8</span><span class="pun">&gt;</span></pre>

<p>
	لن يؤدي ذلك إلى تشغيل تطبيق المفكرة Notepad فحسب، بل سيؤدي أيضًا إلى فتح الملف C:\Users\Al\hello.txt مباشرةً.
</p>

<h3 id="taskschedulerlaunchdcron">
	أدوات مجدول المهام Task Scheduler و Launchd و cron
</h3>

<p>
	إذا كنت خبيرًا في استخدام الحاسوب، فقد تكون على دراية بأداة مجدول المهام Task Scheduler على ويندوز أو أداة launchd على نظام ماك macOS أو أداة الجدولة cron على <a href="https://academy.hsoub.com/programming/general/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%86%D8%B8%D8%A7%D9%85-%D9%84%D9%8A%D9%86%D9%83%D8%B3-linux-%D9%88%D8%A3%D8%A8%D8%B1%D8%B2-%D9%85%D9%85%D9%8A%D8%B2%D8%A7%D8%AA%D9%87-%D9%88%D8%B9%D9%8A%D9%88%D8%A8%D9%87-r2252/" rel="">نظام لينكس</a>، حيث تتيح لك هذه الأدوات المُوثَّقة جيدًا والموثوقة جدولةَ تشغيل التطبيقات في أوقات محددة.
</p>

<p>
	يوفّر عليك استخدام المجدول المُدمَج مع نظام تشغيلك كتابةَ الشيفرة البرمجية الخاصة بالتحقق من ساعتك لجدولة برامجك، ولكن يمكنك استخدام الدالة <code>time.sleep()‎</code> إذا أردتَ فقط إيقاف البرنامج مؤقتًا لفترة وجيزة، أو يمكنك تكرار شيفرتك البرمجية حتى تاريخ ووقت محدَّدين واستدعاء <code>time.sleep(1)‎</code> في كل مرة خلال الحلقة بدلًا من استخدام المجدول الخاص بنظام التشغيل.
</p>

<h3 id="-4">
	فتح المواقع باستخدام شيفرة بايثون
</h3>

<p>
	يمكن للدالة <code>webbrowser.open()‎</code> تشغيل متصفح ويب من برنامجك إلى موقع ويب محدّد بدلًا من فتح تطبيق المتصفح باستخدام الدالة <code>subprocess.Popen()‎</code>.
</p>

<h3 id="-5">
	تشغيل سكربتات بايثون الأخرى
</h3>

<p>
	يمكنك تشغيل سكربت بايثون من شيفرة بايثون أخرى مثل أيّ تطبيق آخر، فما عليك فعله سوى تمرير ملف بايثون python.exe القابل للتنفيذ إلى الدالة <code>Popen()‎</code> واسم ملف سكربت <code>‎.py</code> الذي تريد تشغيله بوصفه وسيطًا لهذه الدالة، فمثلًا سيشغّل ما يلي السكربت hello.py الذي كتبناه في مقالٍ سابق:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3871_42" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> subprocess</span><span class="pun">.</span><span class="typ">Popen</span><span class="pun">([</span><span class="str">'C:\\Users\\&lt;YOUR USERNAME&gt;\\AppData\\Local\\Programs\\
Python\\Python38\\python.exe'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'hello.py'</span><span class="pun">])</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">subprocess</span><span class="pun">.</span><span class="typ">Popen</span><span class="pln"> object at </span><span class="lit">0x000000000331CF28</span><span class="pun">&gt;</span></pre>

<p>
	نمرّر إلى الدالة <code>Popen()‎</code> قائمةً تحتوي على سلسلة نصية تمثل مسار ملف بايثون القابل للتنفيذ وسلسلة نصية تمثّل اسم ملف السكربت، وإذا احتاج السكربت الذي تشغّله إلى وسطاء سطر الأوامر، فيمكنك إضافتها إلى القائمة بعد اسم ملف السكربت. موقع ملف بايثون القابل للتنفيذ على نظام ويندوز هو:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1849_21" style=""><span class="pln"> C</span><span class="pun">:</span><span class="pln">\Users\&lt;YOUR USERNAME</span><span class="pun">&gt;</span><span class="pln">\AppData\Local\Programs\Python\Python38\python</span><span class="pun">.</span><span class="pln">exe</span><span class="pun">،</span><span class="pln"> </span><span class="pun">وعلى</span><span class="pln"> </span><span class="pun">نظام</span><span class="pln"> </span><span class="pun">ماك</span><span class="pln"> macOS </span><span class="pun">هو</span><span class="pln"> </span><span class="pun">‎/</span><span class="typ">Library</span><span class="pun">/</span><span class="typ">Frameworks</span><span class="pun">/</span><span class="typ">Python</span><span class="pun">.</span><span class="pln">framework</span><span class="pun">/</span><span class="typ">Versions</span><span class="pun">/</span><span class="lit">3.8</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">python3</span><span class="pun">،</span></pre>

<p>
	وعلى نظام لينكس هو:
</p>

<pre class="ipsCode" id="ips_uid_1849_19"> ‎/usr/bin/python3.8</pre>

<p>
	إذا شغّل برنامج بايثون الخاص بك برنامجَ بايثون آخر، فسيُشغَّل كلا البرنامجين ضمن عمليات منفصلة ولن يتمكن كلّ منهما من مشاركة متغيرات الآخر على عكس استيراد برنامج بايثون بوصفه وحدة Module.
</p>

<h3 id="-6">
	فتح الملفات باستخدام التطبيقات الافتراضية
</h3>

<p>
	سيؤدّي النقر المزدوج على ملف <code>‎.txt</code> على حاسوبك إلى تشغيل التطبيق المرتبط بلاحقة الملف <code>‎.txt</code> تلقائيًا، وسيكون لحاسوبك ارتباطات متعددة بامتدادات الملفات المُعَدّة مسبقًا، ويمكن لبايثون أيضًا فتح الملفات بهذه الطريقة باستخدام الدالة <code>Popen()‎</code>.
</p>

<p>
	يحتوي كل نظام تشغيل على برنامج يطبّق شيئًا يعادل النقر المزدوج على ملف مستند لفتحه، وهو البرنامج <code>start</code> على نظام ويندوز، والبرنامج <code>open</code> على نظام ماك macOS، والبرنامج <code>see</code> على نظام أوبنتو لينكس. أدخِل مثلًا ما يلي في الصدفة التفاعلية مع تمرير <code>'start'</code> أو <code>'open'</code> أو <code>'see'</code> إلى الدالة <code>Popen()‎</code> اعتمادًا على نظامك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3871_44" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> fileObj </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="str">'hello.txt'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'w'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> fileObj</span><span class="pun">.</span><span class="pln">write</span><span class="pun">(</span><span class="str">'Hello, world!'</span><span class="pun">)</span><span class="pln">
</span><span class="lit">12</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> fileObj</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> subprocess
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> subprocess</span><span class="pun">.</span><span class="typ">Popen</span><span class="pun">([</span><span class="str">'start'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'hello.txt'</span><span class="pun">],</span><span class="pln"> shell</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span></pre>

<p>
	كتبنا في مثالنا السابق عبارة <code>Hello, world!‎</code> في ملف hello.txt جديد، ثم استدعينا الدالة <code>Popen()‎</code>، ومرّرنا إليها قائمة تحتوي على اسم البرنامج (وهو <code>'start'</code> في مثالنا لنظام ويندوز) واسم الملف. مرّرنا أيضًا وسيط الكلمات المفتاحية <code>shell=True</code>، ويُعَد هذا الوسيط مطلوبًا فقط على نظام ويندوز. يعرِف نظام التشغيل جميع ارتباطات الملفات ويمكنه معرفة أنه يجب عليه تشغيل برنامج المفكرة Notepad.exe مثلًا للتعامل مع الملف hello.txt.
</p>

<p>
	يُستخدَم البرنامج <code>open</code> لفتح ملفات المستندات والبرامج على نظام ماك macOS. إذًا لندخِل ما يلي في الصدفة التفاعلية إذا كان حاسوبك عليه نظام ماك، ويجب أن يفتح تطبيق الآلة الحاسبة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3871_47" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> subprocess</span><span class="pun">.</span><span class="typ">Popen</span><span class="pun">([</span><span class="str">'open'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'/Applications/Calculator.app/'</span><span class="pun">])</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">subprocess</span><span class="pun">.</span><span class="typ">Popen</span><span class="pln"> object at </span><span class="lit">0x10202ff98</span><span class="pun">&gt;</span></pre>

<h2 id="-7">
	تطبيق عملي: برنامج بسيط للعد التنازلي
</h2>

<p>
	يصعب العثور على تطبيق بسيط للمؤقت الزمني، وبالمثل قد يكون من الصعب العثور على تطبيق بسيط للعد التنازلي، إذًا لنكتب برنامجًا للعد التنازلي يشغّل المنبه في نهاية العد التنازلي.
</p>

<p>
	إليك الخطوات العامة التي سيفعلها برنامجك:
</p>

<ol>
	<li>
		العد التنازلي من العدد 60.
	</li>
	<li>
		تشغيل ملف صوتي alarm.wav عندما يصل العد التنازلي إلى الصفر.
	</li>
</ol>

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

<ol>
	<li>
		التوقف مؤقتًا لمدة ثانية واحدة بين عرض كل عدد في العد التنازلي من خلال استدعاء الدالة <code>time.sleep()‎</code>.
	</li>
	<li>
		استدعاء الدالة <code>subprocess.Popen()‎</code> لفتح الملف الصوتي باستخدام التطبيق الافتراضي.
	</li>
</ol>

<p>
	افتح تبويبًا جديدًا في محرّرك لإنشاء ملف جديد واحفظه بالاسم countdown.py.
</p>

<h3 id="-8">
	الخطوة الأولى: العد التنازلي
</h3>

<p>
	يتطلب هذا البرنامج الوحدة <code>time</code> لاستخدام الدالة <code>time.sleep()‎</code> ووحدة <code>subprocess</code> لاستخدام الدالة <code>subprocess.Popen()‎</code>. أدخِل الآن الشيفرة البرمجية التالية واحفظ الملف بالاسم countdown.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3871_49" style=""><span class="pln">   </span><span class="com">#! python3</span><span class="pln">
   </span><span class="com"># countdown.py - سكربت بسيط للعد التنازلي</span><span class="pln">

   </span><span class="kwd">import</span><span class="pln"> time</span><span class="pun">,</span><span class="pln"> subprocess

</span><span class="pun">➊</span><span class="pln"> timeLeft </span><span class="pun">=</span><span class="pln"> </span><span class="lit">60</span><span class="pln">
   </span><span class="kwd">while</span><span class="pln"> timeLeft </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">0</span><span class="pun">:</span><span class="pln">
    </span><span class="pun">➋</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">timeLeft</span><span class="pun">,</span><span class="pln"> end</span><span class="pun">=</span><span class="str">''</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">➌</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">sleep</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">➍</span><span class="pln"> timeLeft </span><span class="pun">=</span><span class="pln"> timeLeft </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1</span><span class="pln">

  </span><span class="com"># تشغيل الملف الصوتي في نهاية العد التنازلي</span></pre>

<p>
	استوردنا الوحدتين <code>time</code> و <code>subprocess</code>، ثم أنشأنا متغيرًا بالاسم <code>timeLeft</code> ليحتفظ بعدد الثواني المتبقية في العد التنازلي ➊. يمكن أن تبدأ عند القيمة 60، أو يمكنك تغيير القيمة إلى ما تريده، أو يمكنك ضبطها من وسيط سطر الأوامر.
</p>

<p>
	يمكنك في حلقة <code>while</code> عرض العدد المتبقي ➋، والتوقف مؤقتًا لمدة ثانية واحدة ➌، ثم إنقاص المتغير <code>timeLeft</code> بمقدار 1 ➍ قبل بدء الحلقة مرة أخرى، وستستمر الحلقة في التكرار طالما أن المتغير <code>timeLeft</code> أكبر من 0، ثم سينتهي العد التنازلي.
</p>

<h3 id="-9">
	الخطوة الثانية: تشغيل الملف الصوتي
</h3>

<p>
	توجد وحدات خارجية لتشغيل الملفات الصوتية بتنسيقات مختلفة، ولكن الطريقة السريعة والسهلة لذلك هي تشغيل أيّ تطبيق يستخدمه المستخدم لتشغيل الملفات الصوتية. سيكتشف نظام التشغيل من امتداد الملف ‎<code>.wav</code>‎ التطبيقَ الذي يجب تشغيله لتشغيل الملف، ويمكن أن يكون ملف ‎<code>.wav</code>‎ له أحد تنسيقات الملفات الصوتية الأخرى مثل <code>‎.mp3</code> أو <code>‎.ogg</code>.
</p>

<p>
	يمكنك استخدام أيّ ملف صوتي موجود على حاسوبك لتشغيله في نهاية العد التنازلي، أو يمكنك تنزيل <a href="https://nostarch.com/automatestuff2/" rel="external nofollow">alarm.wav</a>.
</p>

<p>
	أضِف ما يلي إلى شيفرتك البرمجية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3871_51" style=""><span class="com">#! python3</span><span class="pln">
</span><span class="com"># countdown.py - سكربت بسيط للعد التنازلي</span><span class="pln">

</span><span class="kwd">import</span><span class="pln"> time</span><span class="pun">,</span><span class="pln"> subprocess

</span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">

</span><span class="com"># تشغيل الملف الصوتي في نهاية العد التنازلي</span><span class="pln">
subprocess</span><span class="pun">.</span><span class="typ">Popen</span><span class="pun">([</span><span class="str">'start'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'alarm.wav'</span><span class="pun">],</span><span class="pln"> shell</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span></pre>

<p>
	سيعمل الملف alarm.wav (أو الملف الصوتي الذي تختاره) بعد انتهاء الحلقة لإعلام المستخدم بانتهاء العد التنازلي. تأكّد من تضمين <code>'start'</code> في القائمة التي تمرّرها إلى الدالة <code>Popen()‎</code> وتمرير وسيط الكلمات المفتاحية <code>shell=True</code> على نظام ويندوز، وتأكّد من تمرير <code>'open'</code> بدلًا من <code>'start'</code> وإزالة <code>shell=True</code> على نظام ماك macOS.
</p>

<p>
	يمكنك حفظ ملف نصي في مكانٍ ما مع رسالة مثل الرسالة "انتهى وقت الاستراحة!" بدلًا من تشغيل ملف صوتي، حيث يمكنك استخدام الدالة <code>Popen()‎</code> لفتحه في نهاية العد التنازلي، مما يؤدي إلى إنشاء نافذة منبثقة تحتوي على رسالة بفعالية، أو يمكنك استخدام الدالة <code>webbrowser.open()‎</code> لفتح موقع ويب محدَّد في نهاية العد التنازلي. يمكن أن يكون منبه برنامج العد التنازلي الخاص بك أيّ شيء تريده على عكس بعض تطبيقات العد التنازلي المجانية التي تجدها على الإنترنت.
</p>

<h3 id="-10">
	أفكار لبرامج مماثلة
</h3>

<p>
	يمثّل العد التنازلي تأخيرًا بسيطًا قبل مواصلة تنفيذ البرنامج، ويمكنك استخدامه أيضًا لتطبيقات وميزات أخرى كما يلي:
</p>

<ul>
	<li>
		استخدام الدالة <code>time.sleep()‎</code> لمنح المستخدم فرصة الضغط على الاختصار <code>CTRL-C</code> لإلغاء إجراءٍ ما مثل حذف الملفات. يمكن لبرنامجك طباعة رسالة "اضغط على CTRL-C للإلغاء Press CTRL-C to cancel"، ثم معالجة أيّ استثناءات <code>KeyboardInterrupt</code> باستخدام تعليمات <code>try</code> و <code>except</code>.
	</li>
	<li>
		يمكنك استخدام كائنات <code>timedelta</code> مع العد التنازلي طويل الأجل لقياس عدد الأيام والساعات والدقائق والثواني حتى نقطة ما (مثل عيد ميلاد أو ذكرى سنوية) في المستقبل.
	</li>
</ul>

<h2 id="-12">
	مشاريع للتدريب
</h2>

<p>
	حاول كتابة البرامج التي تؤدي المهام التي سنوضّحها فيما يلي لكسب خبرة عملية أكبر من خلال استخدام المعلومات التي حصلتَ عليها من المقال السابق وهذا المقال.
</p>

<h3 id="-13">
	برنامج المؤقت الزمني ولكن بمظهر أجمل
</h3>

<p>
	وسّع مشروع المؤقت الزمني Stopwatch الذي ناقشناه في المقال السابق من خلال استخدام توابع السلاسل النصية <code>rjust()‎</code> و <code>ljust()‎</code> "لتجميل" الخرج، فبدلًا من الخرج التالي:
</p>

<pre class="ipsCode">Lap #1: 3.56 (3.56)
Lap #2: 8.63 (5.07)
Lap #3: 17.68 (9.05)
Lap #4: 19.11 (1.43)
</pre>

<p>
	سيبدو الخرج كما يلي:
</p>

<pre class="ipsCode">Lap # 1:   3.56 (  3.56)
Lap # 2:   8.63 (  5.07)
Lap # 3:  17.68 (  9.05)
Lap # 4:  19.11 (  1.43)
</pre>

<p>
	لاحظ أنك ستحتاج إلى نسخٍ نوعها سلاسل نصية من المتغيرات <code>lapNum</code> و <code>lapTime</code> و <code>totalTime</code> التي نوعها أعداد صحيحة وأعداد عشرية لاستدعاء توابع السلاسل النصية عليها. استخدم بعد ذلك وحدة <code>pyperclip</code> التي وضّحناها في مقالٍ سابق لنسخ الخرج النصي إلى الحافظة حتى يتمكّن المستخدم من لصق الخرج بسرعة في ملف نصي أو بريد إلكتروني.
</p>

<h3 id="-14">
	برنامج لتنزيل القصص الهزلية المجدول على الويب
</h3>

<p>
	اكتب برنامجًا يفحص مواقع الويب الخاصة بالعديد من القصص الهزلية على الويب وينزّل الصور تلقائيًا عند تحديث القصص الهزلية عن آخر زيارة للبرنامج. يمكن لمجدول نظام تشغيلك -مثل Scheduled Tasks على ويندوز وlaunchd على نظام ماكmacOS، وcron على نظام لينكس- تشغيل برنامج بايثون الخاص بك مرة واحدة يوميًا، ويمكن لبرنامج بايثون نفسه تنزيل القصة الهزلية ثم نسخها إلى سطح المكتب بحيث يسهل العثور عليها، مما يحرّرك من الاضطرار إلى التحقق من موقع الويب بنفسك للتأكد من تحديثه.
</p>

<h2 id="-15">
	الخلاصة
</h2>

<p>
	تُستخدَم الوحدة <code>threading</code> لإنشاء خيوط متعددة، والتي تُعَد مفيدةً عندما تريد تنزيل ملفات متعددة أو إنجاز مهام أخرى في وقت واحد، ولكن تأكد من أن الخيط يقرأ ويكتب المتغيرات المحلية فقط، وإلا فقد تواجه مشكلات في التزامن.
</p>

<p>
	يمكن لبرامج بايثون الخاصة بك تشغيل تطبيقات أخرى باستخدام الدالة <code>subprocess.Popen()‎</code>، حيث يمكن تمرير وسطاء سطر الأوامر إلى استدعاء هذه الدالة لفتح مستندات محددة باستخدام التطبيق. يمكنك أيضًا استخدام برنامج <code>start</code> أو <code>open</code> أو <code>see</code> مع الدالة <code>Popen()‎</code> لاستخدام ارتباطات الملفات الخاصة بحاسوبك لمعرفة التطبيق الذي سيُستخدَم لفتح مستند تلقائيًا، ويمكن لبرامج بايثون الخاصة بك الاستفادة من إمكاناتها لتلبية احتياجات الأتمتة الخاصة بك باستخدام التطبيقات الأخرى الموجودة على حاسوبك.
</p>

<p>
	ترجمة -وبتصرُّف- للقسم Scheduling Tasks, and Launching Programs من مقال <a href="https://automatetheboringstuff.com/2e/chapter17/" rel="external nofollow">Keeping Time, Scheduling Tasks, and Launching Programs</a> لصاحبه Al Sweigart.
</p>

<h2 id="-16">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/%D8%A7%D8%B3%D8%AA%D8%AE%D8%B1%D8%A7%D8%AC-%D8%A7%D9%84%D9%88%D9%82%D8%AA-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%88%D8%AD%D8%AF%D8%AA%D9%8A%D9%86-time-%D9%88-datetime-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2393/" rel="">استخراج الوقت باستخدام الوحدتين time و datetime في لغة بايثون</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D9%82%D8%B1%D8%A7%D8%A1%D8%A9-%D9%85%D8%B3%D8%AA%D9%86%D8%AF%D8%A7%D8%AA-%D8%AC%D8%AF%D8%A7%D9%88%D9%84-%D8%A5%D9%83%D8%B3%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-python-r2374/" rel="">قراءة مستندات جداول إكسل باستخدام لغة بايثون Python</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D9%85%D8%B4%D8%A7%D8%B1%D9%8A%D8%B9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D8%B9%D9%85%D9%84%D9%8A%D8%A9-%D8%AA%D9%86%D8%A7%D8%B3%D8%A8-%D8%A7%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-r2185/" rel="">مشاريع بايثون عملية تناسب المبتدئين</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2394</guid><pubDate>Sun, 25 Aug 2024 15:00:00 +0000</pubDate></item><item><title>&#x627;&#x633;&#x62A;&#x62E;&#x631;&#x627;&#x62C; &#x627;&#x644;&#x648;&#x642;&#x62A; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x627;&#x644;&#x648;&#x62D;&#x62F;&#x62A;&#x64A;&#x646; time &#x648; datetime &#x641;&#x64A; &#x644;&#x63A;&#x629; &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646;</title><link>https://academy.hsoub.com/programming/python/%D8%A7%D8%B3%D8%AA%D8%AE%D8%B1%D8%A7%D8%AC-%D8%A7%D9%84%D9%88%D9%82%D8%AA-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%88%D8%AD%D8%AF%D8%AA%D9%8A%D9%86-time-%D9%88-datetime-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2393/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_08/-----time--datetime---.png.11a6942bb6267dbdbc4e75dc96aababd.png" /></p>
<p>
	قد تشغّل برامجك أثناء جلوسك أمام الحاسوب، ولكنك ستفضّل تشغيلها دون إشرافك المباشر، إذ يمكن لساعة حاسوبك جدولة البرامج لتشغيل الشيفرة البرمجية في وقت وتاريخ محدَّد أو على فترات زمنية منتظمة، فمثلًا يمكن أن يستخلص Scrape برنامجك البيانات من موقع ويب كل ساعة للتحقق من التغييرات أو يجري مهمةً تستخدم وحدة المعالجة المركزية بصورة كبيرة في الساعة 4 صباحًا أثناء نومك، حيث توفر وحدتا <code>time</code> و <code>datetime</code> في لغة بايثون الدوال التي تقدّم هذه الوظائف.
</p>

<h2 id="time">
	الوحدة time
</h2>

<p>
	تُضبَط ساعة نظام حاسوبك على تاريخٍ ووقت ومنطقة زمنية محددة، حيث تسمح الوحدة <code>time</code> المُدمَجة لبرامج بايثون الخاصة بك بقراءة ساعة النظام للوقت الحالي، وتُعَد الدالتان <code>time.time()‎</code> و <code>time.sleep()‎</code> الأكثر فائدة في هذه الوحدة.
</p>

<h3 id="timetime">
	الدالة time.time()‎
</h3>

<p>
	توقيت يونيكس Unix Epoch هو مرجع زمني شائع الاستخدام في البرمجة، وهو الساعة 12 صباحًا في 1 من الشهر الأول من عام 1970 بالتوقيت العالمي المنسق Coordinated Universal Time -أو UTC اختصارًا، حيث تعيد الدالة <code>time.time()‎</code> عدد الثواني منذ تلك اللحظة التي تمثّل توقيت يونيكس بوصفها قيمةً عشرية Float، والتي تُعَد مجرد عددٍ مع فاصلة عشرية، ويسمى هذا العدد الذي تعيده الدالة <code>time.time()‎</code> بعلامة يونيكس الزمنية Epoch Timestamp. أدخِل ما يلي مثلًا في الصدفة التفاعلية Interactive Shell:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_534_8" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> time
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">time</span><span class="pun">()</span><span class="pln">
</span><span class="lit">1543813875.3518236</span></pre>

<p>
	استدعينا الدالة <code>time.time()‎</code> في 2 من الشهر 12 من عام 2018 في الساعة 9:11 مساءً بتوقيت المحيط الهادئ، وتكون القيمة المُعادة هي عدد الثواني التي مرّت بين توقيت يونيكس ولحظة استدعاء الدالة <code>time.time()‎</code>.
</p>

<p>
	يمكن استخدام علامات يونيكس الزمنية لفحص أداء Profile الشيفرة البرمجية، أي قياس المدة التي يستغرقها تشغيل جزء من هذه الشيفرة البرمجية. إذا استدعيتَ الدالة <code>time.time()‎</code> في بداية مقطع الشيفرة البرمجية الذي تريد قياس المدة التي يستغرقها تشغيله وفي نهايته مرةً أخرى، فيمكنك طرح العلامة الزمنية الأولى من الثانية لإيجاد الوقت المنقضي بين هذين الاستدعاءين. افتح تبويبًا جديدًا في محرّرك لإنشاء ملفٍ جديد وأدخِل البرنامج التالي مثلًا:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_534_10" style=""><span class="pln">    </span><span class="kwd">import</span><span class="pln"> time
</span><span class="pun">➊</span><span class="pln"> </span><span class="kwd">def</span><span class="pln"> calcProd</span><span class="pun">():</span><span class="pln">
       </span><span class="com"># حساب حاصل ضرب أول 100,000 عدد</span><span class="pln">
       product </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
       </span><span class="kwd">for</span><span class="pln"> i </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100000</span><span class="pun">):</span><span class="pln">
           product </span><span class="pun">=</span><span class="pln"> product </span><span class="pun">*</span><span class="pln"> i
       </span><span class="kwd">return</span><span class="pln"> product

</span><span class="pun">➋</span><span class="pln"> startTime </span><span class="pun">=</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">time</span><span class="pun">()</span><span class="pln">
   prod </span><span class="pun">=</span><span class="pln"> calcProd</span><span class="pun">()</span><span class="pln">
</span><span class="pun">➌</span><span class="pln"> endTime </span><span class="pun">=</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">time</span><span class="pun">()</span><span class="pln">
</span><span class="pun">➍</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'The result is %s digits long.'</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> </span><span class="pun">(</span><span class="pln">len</span><span class="pun">(</span><span class="pln">str</span><span class="pun">(</span><span class="pln">prod</span><span class="pun">))))</span><span class="pln">
</span><span class="pun">➎</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Took %s seconds to calculate.'</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> </span><span class="pun">(</span><span class="pln">endTime </span><span class="pun">-</span><span class="pln"> startTime</span><span class="pun">))</span></pre>

<p>
	نعرّف الدالة <code>calcProd()‎</code> ➊ للتكرار ضمن حلقة على الأعداد الصحيحة من 1 إلى 99,999 وإعادة ناتج ضربها. نستدعي الدالة <code>time.time()‎</code> ونخزّنها في المتغير <code>startTime</code> ➋، ثم نستدعي الدالة <code>time.time()‎</code> مرةً أخرى بعد استدعاء الدالة <code>calcProd()‎</code> مباشرةً ونخزّنها في المتغير <code>endTime</code> ➌، ثم نطبع عدد أرقام حاصل الضرب الذي تعيده الدالة <code>calcProd()‎</code> ➍ والمدة التي استغرقها تشغيل هذه الدالة ➎.
</p>

<p>
	احفظ البرنامج السابق بالاسم calcProd.py وشغّله، حيث سيبدو خرجه كما يلي:
</p>

<pre class="ipsCode">The result is 456569 digits long.
Took 2.844162940979004 seconds to calculate.
</pre>

<p>
	<strong>ملاحظة</strong>: هناك طريقة أخرى لفحص أداء شيفرتك البرمجية باستخدام الدالة <code>cProfile.run()‎</code> التي توفّر مستوًى أعلى من التفاصيل بدلًا من استخدام تقنية الدالة <code>time.time()‎</code> البسيطة. اطلّع على مقال <a href="https://academy.hsoub.com/programming/python/%D9%82%D9%8A%D8%A7%D8%B3-%D8%A3%D8%AF%D8%A7%D8%A1-%D9%88%D8%B3%D8%B1%D8%B9%D8%A9-%D8%AA%D9%86%D9%81%D9%8A%D8%B0-%D8%B4%D9%8A%D9%81%D8%B1%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2127/" rel="">قياس أداء وسرعة تنفيذ شيفرة بايثون</a> لمزيدٍ من التفاصيل عن الدالة <code>cProfile.run()‎</code>.
</p>

<p>
	تُعَد القيمة المُعادة من الدالة <code>time.time()‎</code> مفيدةً للحصول على عدد الثواني منذ توقيت يونيكس إلى لحظة استدعائها بوصفها قيمةً عشرية، ولكن لا يستطيع البشر قراءتها، لذا توجد دالة أخرى هي الدالة <code>time.ctime()‎</code> التي تعيد سلسلةً نصية تمثّل وصفًا للوقت الحالي. يمكنك أيضًا اختياريًا تمرير عدد الثواني منذ توقيت يونيكس الذي تعيده الدالة <code>time.time()‎</code> إلى الدالة <code>time.ctime()‎</code> للحصول على قيمة <a href="https://academy.hsoub.com/programming/python/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%B3%D9%84%D8%A7%D8%B3%D9%84-%D8%A7%D9%84%D9%86%D8%B5%D9%8A%D8%A9-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-r407/" rel="">السلسلة النصية</a> التي تمثّل ذلك الوقت. لندخِل الآن ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_534_15" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> time
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">ctime</span><span class="pun">()</span><span class="pln">
</span><span class="str">'Mon Jun 15 14:00:38 2023'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> thisMoment </span><span class="pun">=</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">time</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">ctime</span><span class="pun">(</span><span class="pln">thisMoment</span><span class="pun">)</span><span class="pln">
</span><span class="str">'Mon Jun 15 14:00:45 2023'</span></pre>

<h3 id="timesleep">
	الدالة time.sleep()‎
</h3>

<p>
	إذا أردتَ إيقاف برنامجك مؤقتًا لفترة من الوقت، فاستدعِ الدالة <code>time.sleep()‎</code> ومرّر إليها عدد الثواني التي تريد أن يبقى فيها برنامجك متوقفًا مؤقتًا. إذًا لندخِل ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_534_17" style=""><span class="pln">   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> time
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> i </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">3</span><span class="pun">):</span><span class="pln">
        </span><span class="pun">➊</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Tick'</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">➋</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">sleep</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">➌</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Tock'</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">➍</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">sleep</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">

   </span><span class="typ">Tick</span><span class="pln">
   </span><span class="typ">Tock</span><span class="pln">
   </span><span class="typ">Tick</span><span class="pln">
   </span><span class="typ">Tock</span><span class="pln">
   </span><span class="typ">Tick</span><span class="pln">
   </span><span class="typ">Tock</span><span class="pln">
</span><span class="pun">➎</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">sleep</span><span class="pun">(</span><span class="lit">5</span><span class="pun">)</span></pre>

<p>
	تطبع <a href="https://wiki.hsoub.com/Python/for" rel="external">حلقة for</a> الكلمة <code>Tick</code> ➊، وتتوقف مؤقتًا لمدة ثانية واحدة ➋، ثم تطبع الكلمة <code>Tock</code> ➌، وتتوقف مؤقتًا لمدة ثانية واحدة ➍، ثم تطبع الكلمة <code>Tick</code>، وتتوقف مؤقتًا، وهكذا حتى طباعة كلٍّ من الكلمتين <code>Tick</code> و <code>Tock</code> ثلاث مرات.
</p>

<p>
	تُعَد الدالة <code>time.sleep()‎</code> مُعطِّلة، أي أنها لن تعيد شيئًا ولن تحرّر برنامجك لتنفيذ شيفرة برمجية أخرى إلّا بعد انقضاء عدد الثواني التي مررتها إلى الدالة <code>time.sleep()‎</code>، فمثلًا إذا أدخلتَ <code>time.sleep(5)‎</code> ➎، فلن تظهر تعليمة المطالبة التالية (‎<code>&gt;&gt;&gt;</code>‎) إلّا بعد مرور 5 ثوانٍ.
</p>

<h2 id="">
	تقريب الأعداد
</h2>

<p>
	سترى في أغلب الأحيان عند التعامل مع الأوقات قيمًا عشرية تحتوي على العديد من الأعداد بعد الفاصلة العشرية، حيث يمكن تسهيل التعامل مع هذه القيم من خلال تقصيرها باستخدام الدالة ‎<code>round()</code>‎ المُدمَجة مع <a href="https://academy.hsoub.com/files/15-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86/" rel="">لغة بايثون</a>، والتي تقرّب الأعداد العشرية إلى الدقة التي تحدّدها، حيث تدخِل العدد الذي تريد تقريبه، بالإضافة إلى وسيط ثانٍ اختياري يمثّل عدد الأعداد بعد الفاصلة العشرية التي تريد تقريبه إليها. إذا حذفتَ الوسيط الثاني، فستقرّب الدالة ‎<code>round()</code>‎ العدد إلى أقرب عدد صحيح كامل بدون فاصلة عشرية. لندخِل مثلًا ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_534_19" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> time
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> now </span><span class="pun">=</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">time</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> now
</span><span class="lit">1543814036.6147408</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> round</span><span class="pun">(</span><span class="pln">now</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln">
</span><span class="lit">1543814036.61</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> round</span><span class="pun">(</span><span class="pln">now</span><span class="pun">,</span><span class="pln"> </span><span class="lit">4</span><span class="pun">)</span><span class="pln">
</span><span class="lit">1543814036.6147</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> round</span><span class="pun">(</span><span class="pln">now</span><span class="pun">)</span><span class="pln">
</span><span class="lit">1543814037</span></pre>

<p>
	استوردنا الوحدة <code>time</code> وخزّنا الدالة <code>time.time()‎</code> في المتغير <code>now</code>، ثم استدعينا الدالة <code>round(now, 2)‎</code> لتقريب <code>now</code> إلى عددين بعد الفاصلة العشرية، واستدعينا الدالة <code>round(now, 4)‎</code> للتقريب إلى أربعة أعداد بعد الفاصلة العشرية، واستدعينا الدالة <code>round(now)‎</code> للتقريب إلى أقرب عدد صحيح.
</p>

<h2 id="superstopwatch">
	تطبيق عملي: برنامج المؤقت الزمني الفائق Super Stopwatch
</h2>

<p>
	لنفترض أنك تريد تعقّب مقدار الوقت الذي تقضيه لإنجاز المهام المملة التي لم تؤتمتها بعد، فليس لديك مؤقت زمني فعلي ومن الصعب العثور على تطبيق مؤقت زمني مجاني لحاسوبك المحمول أو هاتفك الذكي غير مملوءٍ الإعلانات ولا يرسل نسخة من سجل متصفحك إلى المسوّقين، فمذكور أن هذا التطبيق يمكنه ذلك في اتفاقية ترخيصه التي وافقت عليها ولم تقرأها على الأغلب. إذًا لنكتب برنامج مؤقت زمني بسيط باستخدام لغة بايثون.
</p>

<p>
	إليك الخطوات العامة التي سيطبقها برنامجك:
</p>

<ol>
	<li>
		تعقّب مقدار الوقت المنقضي بين الضغطات على مفتاح <code>ENTER</code>، حيث تبدأ كل ضغطة "دورةً Lap" جديدة في المؤقت.
	</li>
	<li>
		طباعة رقم الدورة والوقت الإجمالي ووقت الدورة.
	</li>
</ol>

<p>
	يجب أن تطبق شيفرتك البرمجية الخطوات التالية:
</p>

<ol>
	<li>
		إيجاد الوقت الحالي من خلال استدعاء الدالة <code>time.time()‎</code> وتخزينه بوصفه علامة زمنية في بداية البرنامج، وفي بداية كل دورة أيضًا.
	</li>
	<li>
		الاحتفاظ بعدّاد دورات وزيادته في كل مرة يضغط فيها المستخدم على مفتاح <code>ENTER</code>.
	</li>
	<li>
		حساب الوقت المنقضي من خلال طرح العلامات الزمنية.
	</li>
	<li>
		معالجة الاستثناء <code>KeyboardInterrupt</code> حتى يتمكّن المستخدم من الضغط على الاختصار <code>CTRL-C</code> للإنهاء.
	</li>
</ol>

<p>
	افتح تبويبًا جديدًا في محرّرك لإنشاء ملف جديد واحفظه بالاسم stopwatch.py.
</p>

<h3 id="-1">
	الخطوة الأولى: إعداد البرنامج لتعقّب الأوقات
</h3>

<p>
	يحتاج برنامج المؤقت الزمني إلى استخدام الوقت الحالي، لذا يجب استيراد الوحدة <code>time</code>، ويجب أن يطبع برنامجك أيضًا بعض التعليمات المختصَرة للمستخدم قبل استدعاء الدالة <code>input()‎</code>، إذ يمكن أن يبدأ المؤقت بعد أن يضغط المستخدم على مفتاح <code>ENTER</code>، ثم ستبدأ الشيفرة البرمجية في تعقّب أوقات الدورات.
</p>

<p>
	أدخِل الآن الشيفرة البرمجية التالية في محرّر ملفاتك، واكتب تعليقاتٍ في النهاية، والتي تمثّل عناصر بديلة لما تبقى من شيفرتك البرمجية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_534_21" style=""><span class="com">#! python3</span><span class="pln">
</span><span class="com"># stopwatch.py - برنامج مؤقت زمني بسيط</span><span class="pln">

</span><span class="kwd">import</span><span class="pln"> time

</span><span class="com"># عرض تعليمات البرنامج</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Press ENTER to begin. Afterward, press ENTER to "click" the stopwatch.
Press Ctrl-C to quit.'</span><span class="pun">)</span><span class="pln">
input</span><span class="pun">()</span><span class="pln">                    </span><span class="com"># الضغط على مفتاح‫ Enter للبدء</span><span class="pln">
</span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Started.'</span><span class="pun">)</span><span class="pln">
startTime </span><span class="pun">=</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">time</span><span class="pun">()</span><span class="pln">    </span><span class="com"># الحصول على وقت بدء الدورة الأولى</span><span class="pln">
lastTime </span><span class="pun">=</span><span class="pln"> startTime
lapNum </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">

</span><span class="com"># البدء بتعقّب أوقات الدورات</span></pre>

<p>
	كتبنا الشيفرة البرمجية لعرض التعليمات للمستخدم وبدء الدورة الأولى وتسجيل الوقت وضبط عدد الدورات على القيمة 1.
</p>

<h3 id="-2">
	الخطوة الثانية: تعقّب أوقات الدورات وطباعتها
</h3>

<p>
	لنكتب الآن الشيفرة البرمجية لبدء دورات جديدة، وحساب المدة التي استغرقتها الدورة السابقة، وحساب إجمالي الوقت المنقضي منذ بدء المؤقت الزمني، حيث سنعرض وقت الدورة والوقت الإجمالي ونزيد عدد الدورات بمقدار 1 عند كل دورة جديدة. إذًا أضِف الشيفرة البرمجية التالية إلى برنامجك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_534_23" style=""><span class="pln">   </span><span class="com">#! python3</span><span class="pln">
   </span><span class="com"># stopwatch.py - برنامج مؤقت زمني بسيط</span><span class="pln">

   </span><span class="kwd">import</span><span class="pln"> time

   </span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">
   </span><span class="com"># البدء بتعقّب أوقات الدورات</span><span class="pln">
</span><span class="pun">➊</span><span class="pln"> </span><span class="kwd">try</span><span class="pun">:</span><span class="pln">
    </span><span class="pun">➋</span><span class="pln"> </span><span class="kwd">while</span><span class="pln"> </span><span class="kwd">True</span><span class="pun">:</span><span class="pln">
        input</span><span class="pun">()</span><span class="pln">
        </span><span class="pun">➌</span><span class="pln"> lapTime </span><span class="pun">=</span><span class="pln"> round</span><span class="pun">(</span><span class="pln">time</span><span class="pun">.</span><span class="pln">time</span><span class="pun">()</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> lastTime</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">➍</span><span class="pln"> totalTime </span><span class="pun">=</span><span class="pln"> round</span><span class="pun">(</span><span class="pln">time</span><span class="pun">.</span><span class="pln">time</span><span class="pun">()</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> startTime</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">➎</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Lap #%s: %s (%s)'</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> </span><span class="pun">(</span><span class="pln">lapNum</span><span class="pun">,</span><span class="pln"> totalTime</span><span class="pun">,</span><span class="pln"> lapTime</span><span class="pun">),</span><span class="pln"> end</span><span class="pun">=</span><span class="str">''</span><span class="pun">)</span><span class="pln">
           lapNum </span><span class="pun">+=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
           lastTime </span><span class="pun">=</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">time</span><span class="pun">()</span><span class="pln"> </span><span class="com"># إعادة ضبط وقت الدورة الأخيرة</span><span class="pln">
</span><span class="pun">➏</span><span class="pln"> </span><span class="kwd">except</span><span class="pln"> </span><span class="typ">KeyboardInterrupt</span><span class="pun">:</span><span class="pln">
       </span><span class="com"># معالجة الاستثناء‫ Ctrl-C لمنع عرض رسالة الخطأ</span><span class="pln">
       </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'\nDone.'</span><span class="pun">)</span></pre>

<p>
	إذا ضغط المستخدم على الاختصار <code>CTRL-C</code> لإيقاف المؤقت الزمني، فسيظهر الاستثناء <code>KeyboardInterrupt</code>، وسيتعطل البرنامج إذا لم يكن تنفيذه موجودًا ضمن تعليمة <code>try</code>، لذا غلّفنا هذا الجزء من البرنامج ضمن تعليمة <code>try</code> ➊، وسنعالج الاستثناء ضمن التعليمة <code>except</code> ➏، لذا ينتقل تنفيذ البرنامج إلى التعليمة <code>except</code> لطباعة الكلمة <code>Done</code> بدلًا من رسالة الخطأ <code>KeyboardInterrupt</code> عند الضغط على الاختصار <code>CTRL-C</code> ورفع الاستثناء. يكون التنفيذ ضمن حلقة لا نهائية ➋ حتى حدوث الاستثناء، حيث تستدعي هذه الحلقة الدالة <code>input()‎</code> وتنتظر حتى يضغط المستخدم على مفتاح <code>ENTER</code> لإنهاء الدورة. إذا انتهت الدورة، فسنحسب المدة التي استغرقتها الدورة من خلال طرح وقت بدء الدورة <code>lastTime</code> من الوقت الحالي <code>time.time()‎</code> ➌، ويمكننا حساب إجمالي الوقت المنقضي من خلال طرح وقت البدء الإجمالي للمؤقت الزمني <code>startTime</code> من الوقت الحالي ➍.
</p>

<p>
	ستحتوي نتائج حسابات الوقت على العديد من الأعداد بعد الفاصلة العشرية مثل العد 4.766272783279419، لذا استخدمنا الدالة <code>round()‎</code> لتقريب القيمة العشرية إلى عددين في التعلمتين ➌ و➍.
</p>

<p>
	نطبع بعد ذلك رقم الدورة والوقت الإجمالي المنقضي ووقت الدورة ➎. يطبع المستخدم، الذي يضغط على مفتاح <code>ENTER</code> لاستدعاء الدالة <code>input()‎</code>، سطرًا جديدًا على الشاشة، لذا مرّر الوسيط <code>end=''‎</code> إلى الدالة <code>print()‎</code> لتجنب مضاعفة المسافة بين المخرجات. نتجهّز للدورة التالية بعد طباعة معلومات الدورة من خلال إضافة القيمة 1 إلى العدّاد <code>lapNum</code> ونضبط المتغير <code>lastTime</code> على الوقت الحالي الذي يمثّل وقت بدء الدورة التالية.
</p>

<h3 id="-3">
	أفكار لبرامج مماثلة
</h3>

<p>
	يفتح تعقّب الوقت العديدَ من الاحتمالات أمام برامجك، حيث يمكنك كتابة هذه البرامج بنفسك بالرغم من أنه يمكنك تنزيل تطبيقاتٍ تنفّذ بعضًا منها، ولكن ستكون البرامج التي تكتبها بنفسك مجانية وغير مملوءة بالإعلانات والميزات عديمة الفائدة، لذلك جرّب كتابة برامج مماثلة تطبّق ما يلي:
</p>

<ul>
	<li>
		إنشاء تطبيق جدول حضور زمني Timesheet بسيط يسجّل متى كتبتَ اسم شخصٍ ما ويستخدم الوقت الحالي لتسجيل زمن دخوله أو خروجه.
	</li>
	<li>
		إضافة ميزة إلى برنامجك لعرض الوقت المنقضي منذ بدء عمليةٍ ما مثل عملية التنزيل التي تستخدم الوحدة <code>requests</code>.
	</li>
	<li>
		التحقّق خلال فترات زمنية متقطعة من مدة تشغيل البرنامج ومنح المستخدم فرصة لإلغاء المهام التي تستغرق وقتًا طويلًا.
	</li>
</ul>

<h2 id="datetime">
	الوحدة datetime
</h2>

<p>
	تُعَد الوحدة <code>time</code> مفيدةً للحصول على علامة يونيكس الزمنية للعمل معها، ولكن إذا أردتَ عرض التاريخ بتنسيق أسهل أو إجراء العمليات الحسابية باستخدام التواريخ مثل معرفة التاريخ الذي كان قبل 205 يومًا أو التاريخ الذي سيكون بعد 123 يومًا من الآن، فيجب أن تستخدم الوحدة <code>datetime</code>.
</p>

<p>
	تمتلك الوحدة <code>datetime</code> نوع البيانات <code>datetime</code> الخاص بها، حيث تمثل القيم التي نوعها <code>datetime</code> لحظةً محددة من الزمن. لندخِل الآن ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_534_25" style=""><span class="pln">   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> datetime
</span><span class="pun">➊</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">.</span><span class="pln">now</span><span class="pun">()</span><span class="pln">
</span><span class="pun">➋</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">(</span><span class="lit">2024</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">27</span><span class="pun">,</span><span class="pln"> </span><span class="lit">11</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">49</span><span class="pun">,</span><span class="pln"> </span><span class="lit">55</span><span class="pun">,</span><span class="pln"> </span><span class="lit">53</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➌</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> dt </span><span class="pun">=</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">(</span><span class="lit">2023</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">21</span><span class="pun">,</span><span class="pln"> </span><span class="lit">16</span><span class="pun">,</span><span class="pln"> </span><span class="lit">29</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➍</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> dt</span><span class="pun">.</span><span class="pln">year</span><span class="pun">,</span><span class="pln"> dt</span><span class="pun">.</span><span class="pln">month</span><span class="pun">,</span><span class="pln"> dt</span><span class="pun">.</span><span class="pln">day
   </span><span class="pun">(</span><span class="lit">2023</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">21</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➎</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> dt</span><span class="pun">.</span><span class="pln">hour</span><span class="pun">,</span><span class="pln"> dt</span><span class="pun">.</span><span class="pln">minute</span><span class="pun">,</span><span class="pln"> dt</span><span class="pun">.</span><span class="pln">second
   </span><span class="pun">(</span><span class="lit">16</span><span class="pun">,</span><span class="pln"> </span><span class="lit">29</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span></pre>

<p>
	يؤدي استدعاء الدالة <code>datetime.datetime.now()‎</code> ➊ إلى إعادة الكائن <code>datetime</code> ➋ للتاريخ والوقت الحاليين وفقًا لساعة حاسوبك، حيث يتضمن هذا الكائن السنة والشهر واليوم والساعة والدقيقة والثانية والميكروثانية للحظة الحالية. يمكنك أيضًا استرداد الكائن <code>datetime</code> للحظة معينة باستخدام الدالة <code>datetime.datetime()‎</code> ➌ وتمرير أعداد صحيحة إليها، والتي تمثّل السنة والشهر واليوم والساعة والدقيقة والثانية للحظة التي تريدها، وتُخزَّن هذه الأعداد الصحيحة في سمات Attributes خاصة بالكائن <code>datetime</code>، وهذه السمات هي <code>year</code> و <code>month</code> و <code>day</code> ➍ و <code>hour</code> و <code>minute</code> و <code>second</code> ➎.
</p>

<p>
	يمكن تحويل علامة يونيكس الزمنية إلى كائن <code>datetime</code> باستخدام الدالة <code>datetime.datetime.fromtimestamp()‎</code>، ويُحوَّل التاريخ والوقت الخاصين بكائن <code>datetime</code> إلى المنطقة الزمنية المحلية. إذًا أدخِل ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_534_27" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> datetime</span><span class="pun">,</span><span class="pln"> time
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">.</span><span class="pln">fromtimestamp</span><span class="pun">(</span><span class="lit">1000000</span><span class="pun">)</span><span class="pln">
datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">(</span><span class="lit">1970</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">12</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">,</span><span class="pln"> </span><span class="lit">46</span><span class="pun">,</span><span class="pln"> </span><span class="lit">40</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">.</span><span class="pln">fromtimestamp</span><span class="pun">(</span><span class="pln">time</span><span class="pun">.</span><span class="pln">time</span><span class="pun">())</span><span class="pln">
datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">(</span><span class="lit">2023</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">21</span><span class="pun">,</span><span class="pln"> </span><span class="lit">16</span><span class="pun">,</span><span class="pln"> </span><span class="lit">30</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">604980</span><span class="pun">)</span></pre>

<p>
	يؤدي استدعاء الدالة <code>datetime.datetime.fromtimestamp()‎</code> وتمرير القيمة <code>1000000</code> إليها إلى إعادة كائن <code>datetime</code> للحظة التي تكون بعد 1,000,000 ثانية من توقيت يونيكس، بينما يؤدي تمرير الدالة <code>time.time()‎</code>، التي تمثّل علامة يونيكس الزمنية للحظة الحالية، إلى الدالة <code>datetime.datetime.fromtimestamp()‎</code> إلى إعادة كائن <code>datetime</code> للحظة الحالية، إذ يفعل التعبيران <code>datetime.datetime.now()‎</code> و <code>datetime.datetime.fromtimestamp(time.time())‎</code> الشيء نفسه، حيث يعطيان كائن <code>datetime</code> للوقت الحالي.
</p>

<p>
	يمكنك مقارنة كائنات <code>datetime</code> مع بعضها البعض باستخدام عوامل المقارنة لمعرفة أيّ منها يسبق الآخر، حيث يكون لكائن <code>datetime</code> الأحدث القيمة الأكبر. لندخِل الآن ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_534_29" style=""><span class="pun">➊</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> halloween2023 </span><span class="pun">=</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">(</span><span class="lit">2023</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">31</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➋</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> newyears2024 </span><span class="pun">=</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">(</span><span class="lit">2024</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> oct31_2023 </span><span class="pun">=</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">(</span><span class="lit">2023</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">31</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➌</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> halloween2023 </span><span class="pun">==</span><span class="pln"> oct31_2023
   </span><span class="kwd">True</span><span class="pln">
</span><span class="pun">➍</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> halloween2023 </span><span class="pun">&gt;</span><span class="pln"> newyears2024
   </span><span class="kwd">False</span><span class="pln">
</span><span class="pun">➎</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> newyears2024 </span><span class="pun">&gt;</span><span class="pln"> halloween2023
   </span><span class="kwd">True</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> newyears2024 </span><span class="pun">!=</span><span class="pln"> oct31_2023
   </span><span class="kwd">True</span></pre>

<p>
	أنشأنا كائن <code>datetime</code> للحظة الأولى (منتصف الليل) من 31 من الشهر العاشر من عام 2023، وخزّناه في المتغير <code>halloween2023</code> ➊، ثم أنشأنا كائن <code>datetime</code> للحظة الأولى من 1 من الشهر الأول من عام 2024، وخزّناه في المتغير <code>newyears2024</code> ➋، ثم أنشأنا كائنًا آخر لمنتصف ليل يوم 31 من الشهر العاشر من عام 2023، وخزّناه في المتغير <code>oct31_2023</code>. تُظهِر المقارنة بين المتغيرين <code>halloween2023</code> و <code>oct31_2023</code> أنهما متساويان ➌، وتُظهِر مقارنة المتغيرين <code>newyears2024</code> و <code>halloween2023</code> أن <code>newyears2024</code> أكبر (أو أحدث) من <code>halloween2023</code> ➍ ➎.
</p>

<h3 id="timedelta">
	نوع البيانات timedelta
</h3>

<p>
	توفر الوحدة <code>datetime</code> أيضًا نوع البيانات <code>timedelta</code> الذي يمثل مدة زمنية بدلًا من توفير لحظة زمنية. لندخِل الآن ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_534_31" style=""><span class="pun">➊</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> delta </span><span class="pun">=</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">timedelta</span><span class="pun">(</span><span class="pln">days</span><span class="pun">=</span><span class="lit">11</span><span class="pun">,</span><span class="pln"> hours</span><span class="pun">=</span><span class="lit">10</span><span class="pun">,</span><span class="pln"> minutes</span><span class="pun">=</span><span class="lit">9</span><span class="pun">,</span><span class="pln"> seconds</span><span class="pun">=</span><span class="lit">8</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➋</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> delta</span><span class="pun">.</span><span class="pln">days</span><span class="pun">,</span><span class="pln"> delta</span><span class="pun">.</span><span class="pln">seconds</span><span class="pun">,</span><span class="pln"> delta</span><span class="pun">.</span><span class="pln">microseconds
   </span><span class="pun">(</span><span class="lit">11</span><span class="pun">,</span><span class="pln"> </span><span class="lit">36548</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> delta</span><span class="pun">.</span><span class="pln">total_seconds</span><span class="pun">()</span><span class="pln">
   </span><span class="lit">986948.0</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> str</span><span class="pun">(</span><span class="pln">delta</span><span class="pun">)</span><span class="pln">
   </span><span class="str">'11 days, 10:09:08'</span></pre>

<p>
	نستخدم الدالة <code>datetime.timedelta()‎</code> لإنشاء كائن <code>timedelta</code>، حيث تأخذ هذه الدالة وسطاء الكلمات المفتاحية Keyword Arguments التي هي <code>weeks</code> و <code>days</code> و <code>hours</code> و <code>minutes</code> و <code>seconds</code> و <code>milliseconds</code> و <code>microseconds</code>، ولا توجد وسطاء الكلمات المفتاحية <code>month</code> و <code>year</code>، إذ يُعَد الشهر أو السنة مقدارًا متغيرًا من الزمن اعتمادًا على شهرٍ أو سنة معينة. يحتوي الكائن <code>timedelta</code> على المدة الإجمالية المُمثَّلة بالأيام والثواني والميكروثانية، حيث تُخزَّن هذه الأعداد في السمات <code>days</code> و <code>seconds</code> و <code>microseconds</code>. يعيد التابع <code>total_seconds()‎</code> المدة بعدد الثواني فقط، بينما يؤدي تمرير كائن <code>timedelta</code> إلى الدالة <code>str()‎</code> إلى إعادة تمثيل سلسلة نصية مُنسَّقة جيدًا وقابلة للقراءة البشرية لهذا الكائن.
</p>

<p>
	مرّرنا في المثال السابق وسطاء الكلمات المفتاحية إلى الدالة <code>datetime.timedelta()‎</code> لتحديد مدة 11 يومًا و10 ساعات و9 دقائق و8 ثوانٍ، وخزّنا كائن <code>timedelta</code> المُعاد في المتغير <code>delta</code> ➊. تخزِّن السمة <code>days</code> الخاصة بكائن <code>timedelta</code> القيمة 11، وتخزن السمة <code>seconds</code> القيمة 36548 (أي 10 ساعات و9 دقائق و8 ثوانٍ من خلال التعبير عنها بالثواني) ➋. يخبرنا استدعاء الدالة <code>total_seconds()‎</code> أن 11 يومًا و10 ساعات و9 دقائق و8 ثوانٍ هي 986,948 ثانية، ويعيد تمرير كائن <code>timedelta</code> إلى الدالة <code>str()‎</code> سلسلةً نصية تشرح المدة بوضوح.
</p>

<p>
	يمكن استخدام المعاملات الحسابية لإجراء عملية حسابية للتاريخ على قيم <code>datetime</code>، فمثلًا أدخِل ما يلي في الصدفة التفاعلية لحساب التاريخ بعد 1000 يوم من الآن:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_534_33" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> dt </span><span class="pun">=</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">.</span><span class="pln">now</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> dt
datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">(</span><span class="lit">2018</span><span class="pun">,</span><span class="pln"> </span><span class="lit">12</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">18</span><span class="pun">,</span><span class="pln"> </span><span class="lit">38</span><span class="pun">,</span><span class="pln"> </span><span class="lit">50</span><span class="pun">,</span><span class="pln"> </span><span class="lit">636181</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> thousandDays </span><span class="pun">=</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">timedelta</span><span class="pun">(</span><span class="pln">days</span><span class="pun">=</span><span class="lit">1000</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> dt </span><span class="pun">+</span><span class="pln"> thousandDays
datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">(</span><span class="lit">2021</span><span class="pun">,</span><span class="pln"> </span><span class="lit">8</span><span class="pun">,</span><span class="pln"> </span><span class="lit">28</span><span class="pun">,</span><span class="pln"> </span><span class="lit">18</span><span class="pun">,</span><span class="pln"> </span><span class="lit">38</span><span class="pun">,</span><span class="pln"> </span><span class="lit">50</span><span class="pun">,</span><span class="pln"> </span><span class="lit">636181</span><span class="pun">)</span></pre>

<p>
	أنشأنا أولًا كائن <code>datetime</code> للحظة الحالية وخزّناه في المتغير <code>dt</code>، ثم أنشأنا كائن <code>timedelta</code> لمدة 1000 يوم وخزّناه في المتغير <code>thousandDays</code>، ثم جمعنا <code>dt</code> و <code>thousandDays</code> للحصول على كائن <code>datetime</code> للتاريخ بعد 1000 يوم من الآن. تجري شيفرة بايثون عملية حسابية للتاريخ لمعرفة أن 1000 يوم بعد 2 من الشهر 12 من عام 2018 ستكون في 18 من الشهر الثامن من عام 2021. يُعَد ذلك مفيدًا لأنه يجب أن تتذكّر عدد الأيام في كل شهر والعامل المشترك للسنوات الكبيسة وغيرها من التفاصيل الصعبة عندما تحسب 1000 يوم بعد تاريخ معين، لذا تعالج الوحدة <code>datetime</code> جميع تلك الأمور نيابةً عنك.
</p>

<p>
	يمكن جمع كائنات <code>timedelta</code> أو طرحها من كائنات <code>datetime</code> أو كائنات <code>timedelta</code> الأخرى باستخدام المعاملَين <code>+</code> و <code>-</code>، ويمكن ضرب كائن <code>timedelta</code> أو قسمته على عدد صحيح أو قيم عشرية باستخدام المعاملَين <code>*</code> و <code>/</code>. لندخِل مثلًا ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_534_35" style=""><span class="pun">➊</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> oct21st </span><span class="pun">=</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">(</span><span class="lit">2019</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">21</span><span class="pun">,</span><span class="pln"> </span><span class="lit">16</span><span class="pun">,</span><span class="pln"> </span><span class="lit">29</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➋</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> aboutThirtyYears </span><span class="pun">=</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">timedelta</span><span class="pun">(</span><span class="pln">days</span><span class="pun">=</span><span class="lit">365</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="lit">30</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> oct21st
   datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">(</span><span class="lit">2019</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">21</span><span class="pun">,</span><span class="pln"> </span><span class="lit">16</span><span class="pun">,</span><span class="pln"> </span><span class="lit">29</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> oct21st </span><span class="pun">-</span><span class="pln"> aboutThirtyYears
   datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">(</span><span class="lit">1989</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">28</span><span class="pun">,</span><span class="pln"> </span><span class="lit">16</span><span class="pun">,</span><span class="pln"> </span><span class="lit">29</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> oct21st </span><span class="pun">-</span><span class="pln"> </span><span class="pun">(</span><span class="lit">2</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> aboutThirtyYears</span><span class="pun">)</span><span class="pln">
   datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">(</span><span class="lit">1959</span><span class="pun">,</span><span class="pln"> </span><span class="lit">11</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">,</span><span class="pln"> </span><span class="lit">16</span><span class="pun">,</span><span class="pln"> </span><span class="lit">29</span><span class="pun">)</span></pre>

<p>
	أنشأنا كائن <code>datetime</code> ليوم 21 من الشهر العاشر من عام 2019 ➊، وأنشأنا كائن <code>timedelta</code> لمدة 30 عامًا تقريبًا بافتراض أن السنة تتكون من 365 يومًا ➋. يؤدي طرح <code>aboutThirtyYears</code> من <code>oct21st</code> إلى الحصول على كائن <code>datetime</code> للتاريخ قبل 30 عامًا من تاريخ 21 من الشهر العاشر من عام 2019، ويؤدي طرح <code>‎2 * aboutThirtyYears</code> من <code>oct21st</code> إلى إعادة كائن <code>datetime</code> للتاريخ قبل 60 عامًا من تاريخ 21 من الشهر العاشر من عام 2019.
</p>

<h3 id="-4">
	الإيقاف المؤقت حتى تاريخ محدد
</h3>

<p>
	يتيح التابع <code>time.sleep()‎</code> إيقافَ برنامجٍ ما مؤقتًا لعددٍ محدّدٍ من الثواني، حيث يمكنك إيقاف برامجك مؤقتًا حتى تاريخ محدّد باستخدام <a href="https://wiki.hsoub.com/Python#while" rel="external">حلقة while</a>، فمثلًا ستستمر الشيفرة البرمجية التالية في التكرار حتى يوم الهالوين من عام 2024:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_534_37" style=""><span class="kwd">import</span><span class="pln"> datetime
</span><span class="kwd">import</span><span class="pln"> time
halloween2024 </span><span class="pun">=</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">(</span><span class="lit">2024</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">31</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">while</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">.</span><span class="pln">now</span><span class="pun">()</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln"> halloween2016</span><span class="pun">:</span><span class="pln">
    time</span><span class="pun">.</span><span class="pln">sleep</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span></pre>

<p>
	سيؤدي استدعاء <code>time.sleep(1)‎</code> إلى إيقاف برنامج بايثون الخاص بك مؤقتًا حتى لا يضيع الحاسوب دورات معالجة <a href="https://academy.hsoub.com/programming/os-embedded-systems/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%88%D8%AD%D8%AF%D8%A9-%D8%A7%D9%84%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%A9-%D8%A7%D9%84%D9%85%D8%B1%D9%83%D8%B2%D9%8A%D8%A9-%D9%88%D8%B9%D9%85%D9%84%D9%8A%D8%A7%D8%AA%D9%87%D8%A7-%D9%81%D9%8A-%D9%85%D8%B9%D9%85%D8%A7%D8%B1%D9%8A%D8%A9-%D8%A7%D9%84%D8%AD%D8%A7%D8%B3%D9%88%D8%A8-r1716/" rel="">لوحدة المعالجة المركزية</a> بعد التحقق من الوقت مرارًا وتكرارًا، لذا تتحقق حلقة <code>while</code> من الشرط مرة واحدة في الثانية وتستمر إلى باقي البرنامج بعد يوم الهالوين من عام 2024 (أو أيّ تاريخ تبرمجه للتوقف).
</p>

<h3 id="datetime-1">
	تحويل كائنات datetime إلى سلاسل نصية
</h3>

<p>
	لا تُعَد علامات يونيكس الزمنية وكائنات <code>datetime</code> سهلة القراءة، لذا نستخدم التابع <code>strftime()‎</code> لعرض كائن <code>datetime</code> بوصفها سلسلة نصية، حيث يشير الحرف f الموجود في اسم الدالة <code>strftime()‎</code> إلى التنسيق format. يستخدم التابع <code>strftime()‎</code> موجّهات Directives مشابهة لتنسيق سلاسل بايثون النصية، حيث يحتوي الجدول التالي على قائمة كاملة بموجّهات التابع <code>strftime()‎</code>:
</p>

<table>
	<thead>
		<tr>
			<th>
				موجّه التابع strftime()‎
			</th>
			<th>
				معناه
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				‎%Y
			</td>
			<td>
				العام مع القرن مثل <code>'2024'</code>
			</td>
		</tr>
		<tr>
			<td>
				‎%y
			</td>
			<td>
				العام بدون القرن من <code>'00'</code> إلى <code>'99'</code> (من عام 1970 إلى 2069 مثلًا)
			</td>
		</tr>
		<tr>
			<td>
				‎%m
			</td>
			<td>
				الشهر كعدد عشري من <code>'01'</code> إلى <code>'12'</code>
			</td>
		</tr>
		<tr>
			<td>
				‎%B
			</td>
			<td>
				اسم الشهر كاملًا مثل <code>'November'</code>
			</td>
		</tr>
		<tr>
			<td>
				‎%b
			</td>
			<td>
				اسم الشهر المختصر مثل <code>'Nov'</code>
			</td>
		</tr>
		<tr>
			<td>
				‎%d
			</td>
			<td>
				يوم من الشهر من <code>'01'</code> إلى <code>'31'</code>
			</td>
		</tr>
		<tr>
			<td>
				‎%j
			</td>
			<td>
				يوم من السنة من <code>'001'</code> إلى <code>'366'</code>
			</td>
		</tr>
		<tr>
			<td>
				‎%w
			</td>
			<td>
				يوم من الأسبوع من <code>'0'</code> (الأحد) إلى <code>'6'</code> (السبت)
			</td>
		</tr>
		<tr>
			<td>
				‎%A
			</td>
			<td>
				الاسم الكامل ليوم من الأسبوع مثل <code>'Monday'</code>
			</td>
		</tr>
		<tr>
			<td>
				‎%a
			</td>
			<td>
				الاسم المختصر ليوم من الأسبوع مثل <code>'Mon'</code>
			</td>
		</tr>
		<tr>
			<td>
				‎%H
			</td>
			<td>
				الساعة (نظام 24 ساعة) من <code>'00'</code> إلى <code>'23'</code>
			</td>
		</tr>
		<tr>
			<td>
				‎%I
			</td>
			<td>
				الساعة (نظام 12 ساعة) من <code>'01'</code> إلى <code>'12'</code>
			</td>
		</tr>
		<tr>
			<td>
				‎%M
			</td>
			<td>
				الدقيقة من <code>'00'</code> إلى <code>'59'</code>
			</td>
		</tr>
		<tr>
			<td>
				‎%S
			</td>
			<td>
				الثانية من <code>'00'</code> إلى <code>'59'</code>
			</td>
		</tr>
		<tr>
			<td>
				‎%p
			</td>
			<td>
				صباحًا <code>'AM'</code> أو مساءً <code>'PM'</code>
			</td>
		</tr>
		<tr>
			<td>
				‎%%
			</td>
			<td>
				المحرف <code>'%'</code> حرفيًا
			</td>
		</tr>
	</tbody>
</table>

<p>
	مرّر سلسلة نصية ذات تنسيقٍ مخصَّص تحتوي على موجّهات التنسيق (مع أيّ خطوط مائلة ونقطتين وغير ذلك) إلى التابع <code>strftime()‎</code>، وسيعيد هذا التابع معلومات كائن <code>datetime</code> بوصفها سلسلة نصية مُنسَّقة. لندخِل الآن ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_534_39" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> oct21st </span><span class="pun">=</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">(</span><span class="lit">2023</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">21</span><span class="pun">,</span><span class="pln"> </span><span class="lit">16</span><span class="pun">,</span><span class="pln"> </span><span class="lit">29</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> oct21st</span><span class="pun">.</span><span class="pln">strftime</span><span class="pun">(</span><span class="str">'%Y/%m/%d %H:%M:%S'</span><span class="pun">)</span><span class="pln">
</span><span class="str">'2023/10/21 16:29:00'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> oct21st</span><span class="pun">.</span><span class="pln">strftime</span><span class="pun">(</span><span class="str">'%I:%M %p'</span><span class="pun">)</span><span class="pln">
</span><span class="str">'04:29 PM'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> oct21st</span><span class="pun">.</span><span class="pln">strftime</span><span class="pun">(</span><span class="str">"%B of '%y"</span><span class="pun">)</span><span class="pln">
</span><span class="str">"October of '23"</span></pre>

<p>
	خزّنا في المثال السابق كائن <code>datetime</code> ليوم 21 من الشهر العاشر من عام 2023 في الساعة 4:29 مساءً في المتغير <code>oct21st</code>. يعيد تمرير سلسلة التنسيق المخصَّصة <code>'‎%Y/%m/%d %H:%M:%S'</code> إلى التابع <code>strftime()‎</code> سلسلةً نصية تحتوي على القيم 2023 و10 و21 المفصولة بخطوط مائلة والقيم 16 و29 و00 المفصولة بنقطتين، ويؤدي تمرير السلسلة النصية <code>'‎%I:%M% p'</code> إلى إعادة <code>'‎04:29 PM'</code>، ويؤدي تمرير السلسلة النصية <code>"‎%B of '%y"</code> إلى إعادة <code>‎"October of '23"‎</code>. لاحظ أننا لا نضع <code>datetime.datetime</code> قبل التابع <code>strftime()‎</code>.
</p>

<h3 id="datetime-2">
	تحويل السلاسل النصية إلى كائنات datetime
</h3>

<p>
	إذا كان لديك سلسلة نصية تمثّل معلومات التاريخ مثل <code>'2023/10/21‎ 16:29:00'</code> أو <code>'October 21, 2023'</code> وتريد تحويلها إلى كائن <code>datetime</code>، فاستخدم الدالة <code>datetime.datetime.strptime()‎</code>، حيث تُعَد هذه الدالة عكس التابع <code>strftime()‎</code>. يجب تمرير سلسلة تنسيق مُخصَّصة تستخدم الموجّهات نفسها التي يستخدمها التابع <code>strftime()‎</code> إلى الدالة <code>strptime()‎</code> حتى تعرف كيفية تحليل السلسلة النصية وفهمها، ويشير الحرف p الموجود في اسم الدالة <code>strptime()‎</code> إلى التحليل Parse. لندخِل الآن ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_534_41" style=""><span class="pun">➊</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">.</span><span class="pln">strptime</span><span class="pun">(</span><span class="str">'October 21, 2023'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'%B %d, %Y'</span><span class="pun">)</span><span class="pln">
   datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">(</span><span class="lit">2023</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">21</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">.</span><span class="pln">strptime</span><span class="pun">(</span><span class="str">'2023/10/21 16:29:00'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'%Y/%m/%d %H:%M:%S'</span><span class="pun">)</span><span class="pln">
   datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">(</span><span class="lit">2023</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">21</span><span class="pun">,</span><span class="pln"> </span><span class="lit">16</span><span class="pun">,</span><span class="pln"> </span><span class="lit">29</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">.</span><span class="pln">strptime</span><span class="pun">(</span><span class="str">"October of '23"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"%B of '%y"</span><span class="pun">)</span><span class="pln">
   datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">(</span><span class="lit">2023</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">.</span><span class="pln">strptime</span><span class="pun">(</span><span class="str">"November of '63"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"%B of '%y"</span><span class="pun">)</span><span class="pln">
   datetime</span><span class="pun">.</span><span class="pln">datetime</span><span class="pun">(</span><span class="lit">2063</span><span class="pun">,</span><span class="pln"> </span><span class="lit">11</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span></pre>

<p>
	يمكن الحصول على كائن <code>datetime</code> من السلسلة النصية <code>'October 21, 2023'</code> من خلال تمرير هذه السلسلة كوسيطٍ أول إلى الدالة <code>strptime()‎</code> وتمرير سلسلة التنسيق المُخصصة المقابلة للسلسلة النصية <code>'October 21, 2023'</code> كوسيطٍ ثانٍ ➊. يجب أن تتطابق السلسلة النصية التي تحتوي على معلومات التاريخ مع سلسلة التنسيق المخصصة تمامًا، وإلّا سيرفع بايثون استثناء <code>ValueError</code>.
</p>

<h2 id="-5">
	مراجعة لدوال بايثون الخاصة بالوقت
</h2>

<p>
	يمكن أن تتضمن التواريخ والأوقات في بايثون عددًا من أنواع البيانات والدوال المختلفة، لذا سنوضح فيما يلي الأنواع الثلاثة المختلفة من القيم المُستخدَمة لتمثيل الوقت:
</p>

<ul>
	<li>
		علامة يونيكس الزمنية التي تستخدمها الوحدة <code>time</code>، وهي قيمة عشرية أو صحيحة لعدد الثواني منذ الساعة 12 صباحًا في 1 من الشهر الأول من عام 1970 بالتوقيت العالمي المنسق.
	</li>
	<li>
		كائن <code>datetime</code> الخاص بالوحدة <code>datetime</code>، والذي يحتوي على أعداد صحيحة مخزَّنة في السمات <code>year</code> و <code>month</code> و <code>day</code> و <code>hour</code> و <code>minute</code> و <code>second</code>.
	</li>
	<li>
		كائن <code>timedelta</code> الخاص بالوحدة <code>datetime</code>، والذي يمثّل مدة زمنية وليس لحظة مُحدَّدة.
	</li>
</ul>

<p>
	إليك مراجعة لدوال الوقت ومعاملاتها والقيم التي تعيدها:
</p>

<ul>
	<li>
		<code>time.time()‎</code>: تعيد هذه الدالة القيمة العشرية لعلامة يونيكس الزمنية للحظة الحالية.
	</li>
	<li>
		<code>time.sleep(seconds)‎</code>:توقِف هذه الدالة البرنامج لعددٍ من الثواني التي يحدّدها الوسيط <code>seconds</code>.
	</li>
	<li>
		<code>datetime.datetime(year, month, day, hour, minute, second)‎</code>: تعيد هذه الدالة كائن <code>datetime</code> للحظة التي تحدّدها وسطاؤها. إذا لم تتوفّر قيم للوسطاء <code>hour</code> أو <code>minute</code> أو <code>second</code>، فستكون قيمها الافتراضية 0.
	</li>
	<li>
		<code>datetime.datetime.now()‎</code>: تعيد هذه الدالة كائن <code>datetime</code> للحظة الحالية.
	</li>
	<li>
		<code>datetime.datetime.fromtimestamp(epoch)‎</code>: تعيد هذه الدالة كائن <code>datetime</code> للحظة التي يمثلها وسيط العلامة الزمنية <code>epoch</code>.
	</li>
	<li>
		<code>datetime.timedelta(weeks, days, hours, minutes, seconds, milliseconds, microseconds)‎</code>: تعيد هذه الدالة كائن <code>timedelta</code> الذي يمثل مدة زمنية، وتُعَد وسطاء الكلمات المفتاحية لهذه الدالة اختيارية ولا تتضمن <code>month</code> أو <code>year</code>.
	</li>
	<li>
		<code>total_seconds()‎</code>: يعيد هذا التابع الخاص بكائنات <code>timedelta</code> عدد الثواني التي يمثّلها كائن <code>timedelta</code>.
	</li>
	<li>
		<code>strftime(format)‎</code>: يعيد هذا التابع سلسلة نصية للوقت الذي يمثّله كائن <code>datetime</code> بتنسيقٍ مخصَّص يعتمد على سلسلة التنسيق <code>format</code>. اطّلع على الجدول السابق للحصول على تفاصيل التنسيق.
	</li>
	<li>
		<code>datetime.datetime.strptime(time_string, format)‎</code>: تعيد هذه الدالة كائن <code>datetime</code> للحظة التي يحدّدها الوسيط <code>time_string</code>، وتُحلَّل باستخدام وسيط سلسلة التنسيق <code>format</code>. اطّلع على الجدول السابق للحصول على تفاصيل التنسيق.
	</li>
</ul>

<h2 id="-6">
	الخلاصة
</h2>

<p>
	يُعَد توقيت يونيكس (1 من الشهر الأول من عام 1970 عند منتصف الليل بالتوقيت العالمي المنسَّق) وقتًا مرجعيًا معياريًا للعديد من لغات البرمجة بما في ذلك لغة بايثون. تعيد الوحدة الخاصة بالدالة <code>time.time()‎</code> علامة يونيكس الزمنية (أي قيمة عشرية لعدد الثواني منذ توقيت يونيكس)، ولكن تُعَد الوحدة <code>datetime</code> أفضل لإجراء العمليات الحسابية الرياضية على التاريخ وتنسيق أو تحليل السلاسل النصية باستخدام معلومات التاريخ. ستوقَِف الدالة <code>time.sleep()‎</code> التنفيذ (أي لن تعود) لعددٍ معين من الثواني، حيث يمكنك استخدام ذلك لإضافة فترات توقف مؤقتة إلى برنامجك.
</p>

<p>
	ترجمة -وبتصرُّف- للقسم Keeping Time من مقال <a href="https://automatetheboringstuff.com/2e/chapter17/" rel="external nofollow">Keeping Time, Scheduling Tasks, and Launching Programs</a> لصاحبه Al Sweigart.
</p>

<h2 id="-7">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D9%85%D9%84%D9%81%D8%A7%D8%AA-csv-%D9%88%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-json-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2388/" rel="">العمل مع ملفات CSV وبيانات JSON باستخدام لغة بايثون</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%88%D8%AD%D8%AF%D8%A7%D8%AA-modules-%D9%88%D8%A7%D9%84%D8%AD%D8%B2%D9%85-packages-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r329/" rel="">الوحدات Modules والحزم Packages في بايثون</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D9%85%D8%B5%D8%B7%D9%84%D8%AD%D8%A7%D8%AA-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r1982/" rel="">مصطلحات بايثون البرمجية</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2393</guid><pubDate>Fri, 16 Aug 2024 15:00:00 +0000</pubDate></item><item><title>&#x627;&#x644;&#x639;&#x645;&#x644; &#x645;&#x639; &#x645;&#x644;&#x641;&#x627;&#x62A; CSV &#x648;&#x628;&#x64A;&#x627;&#x646;&#x627;&#x62A; JSON &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x644;&#x63A;&#x629; &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646;</title><link>https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D9%85%D9%84%D9%81%D8%A7%D8%AA-csv-%D9%88%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-json-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2388/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_08/---CSV--JSON---.png.836624c55bc9279a5f50c08c475599ec.png" /></p>
<p>
	تعلّمنا في <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D9%85%D8%B3%D8%AA%D9%86%D8%AF%D8%A7%D8%AA-pdf-%D9%88%D9%85%D8%B3%D8%AA%D9%86%D8%AF%D8%A7%D8%AA-word-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2387/" rel="">المقال السابق</a> كيفية استخراج النص من مستندات PDF ووورد التي تُعَدّ ملفاتٍ بتنسيق ثنائي، إذ تتطلب هذه الملفات استخدام وحدات <a href="https://academy.hsoub.com/files/15-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86/" rel="">بايثون Python</a> خاصة للوصول إلى بياناتها، بينما تُعَد ملفات CSV و JSON مجرد ملفاتٍ نصية عادية، حيث يمكنك عرضها في محرر نصوص مثل محرر النصوص Mu. تحتوي لغة بايثون مسبقًا على وحدتين خاصتين هما <code>csv</code> و <code>json</code>، حيث توفّر كلٌّ منهما دوالًا لمساعدتك في العمل مع تنسيقات هذه الملفات.
</p>

<p>
	يرمز الاختصار CSV إلى "القيم المفصولة بفواصل Comma-separated Values"، و<a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D9%85%D9%84%D9%81%D8%A7%D8%AA-csv-r1644/" rel="">ملفات CSV</a> هي جداول بيانات بسيطة مُخزَّنة بوصفها ملفات نصية عادية، وتسهّل وحدة <code>csv</code> في <a href="https://academy.hsoub.com/files/15-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86/" rel="">بايثون</a> تحليل ملفات CSV. يُنطَق الاختصار JSON بالطريقة "JAY-sawn" أو "Jason"، ولكن لا يهم كيف تنطقها لأن الناس سيقولون أنك تنطقها بطريقة خاطئة في كلتا الحالتين، وهو تنسيق يخزن المعلومات بوصفها شيفرة <a href="https://academy.hsoub.com/files/27-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA/" rel="">جافاسكربت</a> مصدرية في ملفات نصية عادية. <a href="https://academy.hsoub.com/programming/javascript/%D8%B5%D9%8A%D8%BA%D8%A9-json-%D9%88%D8%AA%D9%88%D8%A7%D8%A8%D8%B9%D9%87%D8%A7-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r826/" rel="">JSON</a> هو اختصار لترميز الكائنات باستعمال جافاسكربت JavaScript Object Notation، ولكن لا تحتاج إلى معرفة <a href="https://academy.hsoub.com/programming/javascript/%D9%85%D8%A7-%D9%87%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-%D8%9F-r524/" rel="">لغة البرمجة جافاسكربت</a> لاستخدام ملفات JSON، ولكن من المفيد معرفة تنسيق ملفات JSON لأنه يُستخدَم في العديد من <a href="https://academy.hsoub.com/programming/python/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r1524/" rel="">تطبيقات الويب</a>.
</p>

<h2 id="csv">
	وحدة CSV
</h2>

<p>
	يمثّل كل سطر في ملف CSV صفًا في جدول البيانات، حيث تفصل الفواصل بين الخلايا الموجودة في الصف، فمثلًا سيبدو جدول البيانات <a href="https://nostarch.com/automatestuff2/" rel="external nofollow">example.xlsx</a> في ملف CSV كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5894_9" style=""><span class="lit">4</span><span class="pun">/</span><span class="lit">5</span><span class="pun">/</span><span class="lit">2015</span><span class="pln"> </span><span class="lit">13</span><span class="pun">:</span><span class="lit">34</span><span class="pun">,</span><span class="typ">Apples</span><span class="pun">,</span><span class="lit">73</span><span class="pln">
</span><span class="lit">4</span><span class="pun">/</span><span class="lit">5</span><span class="pun">/</span><span class="lit">2015</span><span class="pln"> </span><span class="lit">3</span><span class="pun">:</span><span class="lit">41</span><span class="pun">,</span><span class="typ">Cherries</span><span class="pun">,</span><span class="lit">85</span><span class="pln">
</span><span class="lit">4</span><span class="pun">/</span><span class="lit">6</span><span class="pun">/</span><span class="lit">2015</span><span class="pln"> </span><span class="lit">12</span><span class="pun">:</span><span class="lit">46</span><span class="pun">,</span><span class="typ">Pears</span><span class="pun">,</span><span class="lit">14</span><span class="pln">
</span><span class="lit">4</span><span class="pun">/</span><span class="lit">8</span><span class="pun">/</span><span class="lit">2015</span><span class="pln"> </span><span class="lit">8</span><span class="pun">:</span><span class="lit">59</span><span class="pun">,</span><span class="typ">Oranges</span><span class="pun">,</span><span class="lit">52</span><span class="pln">
</span><span class="lit">4</span><span class="pun">/</span><span class="lit">10</span><span class="pun">/</span><span class="lit">2015</span><span class="pln"> </span><span class="lit">2</span><span class="pun">:</span><span class="lit">07</span><span class="pun">,</span><span class="typ">Apples</span><span class="pun">,</span><span class="lit">152</span><span class="pln">
</span><span class="lit">4</span><span class="pun">/</span><span class="lit">10</span><span class="pun">/</span><span class="lit">2015</span><span class="pln"> </span><span class="lit">18</span><span class="pun">:</span><span class="lit">10</span><span class="pun">,</span><span class="typ">Bananas</span><span class="pun">,</span><span class="lit">23</span><span class="pln">
</span><span class="lit">4</span><span class="pun">/</span><span class="lit">10</span><span class="pun">/</span><span class="lit">2015</span><span class="pln"> </span><span class="lit">2</span><span class="pun">:</span><span class="lit">40</span><span class="pun">,</span><span class="typ">Strawberries</span><span class="pun">,</span><span class="lit">98</span></pre>

<p>
	سنستخدم هذا الملف لأمثلة الصدفة التفاعلية Interactive Shell الموجودة في هذا المقال، حيث يمكنك <a href="https://nostarch.com/automatestuff2" rel="external nofollow">تنزيله</a> أو إدخال النص في محرّر النصوص وحفظه بالاسم example.csv.
</p>

<p>
	تُعَد ملفات CSV بسيطة، ولا تحتوي على العديد من ميزات جدول بيانات إكسل Excel مثل الميزات التالية:
</p>

<ul>
	<li>
		ليس لديها أنواع لقيمها، فكل شيء فيها هو سلسلة نصية String.
	</li>
	<li>
		ليس لديها إعدادات لحجم الخط أو لونه.
	</li>
	<li>
		ليس لديها أوراق عمل متعددة.
	</li>
	<li>
		لا يمكنها تحديد عرض الخلية وارتفاعها.
	</li>
	<li>
		لا يمكنها أن تحتوي على خلايا مدموجة.
	</li>
	<li>
		لا يمكنها تضمين الصور أو المخططات.
	</li>
</ul>

<p>
	ميزة ملفات CSV الأساسية هي البساطة، إذ تدعمها العديد من أنواع البرامج على نطاقٍ واسع، ويمكن عرضها في برامج تحرير النصوص بما في ذلك محرّر النصوص Mu، وتُعَد ملفات CSV طريقةً مباشرة لتمثيل بيانات جداول البيانات. تنسيق ملف CSV هو مجرد ملف نصي يحتوي على قيم يُفصَل بينها بفواصل.
</p>

<p>
	تُعَد ملفات CSV مجرد ملفات نصية، لذا قد تقرأها بوصفها سلسلة نصية ثم تعالج تلك السلسلة باستخدام التقنيات التي تعلمتها سابقًا في مقالٍ سابق، فمثلًا يمكنك استدعاء التابع <code>split(',')‎</code> في كل سطر من النص للحصول على القيم المفصولة بفواصل بوصفها قائمةً من السلاسل النصية، لأن كل خلية في ملف CSV مفصولة عن غيرها من الخلايا بفاصلة، ولكن لا تمثّل جميع الفواصل في ملف CSV هذه الحدود بين الخلايا، إذ تحتوي ملفات CSV أيضًا على مجموعة خاصة بها من محارف الهروب Escape Characters للسماح بتضمين الفواصل والمحارف الأخرى كجزء من القيم، حيث لا يعالج التابع <code>split()‎</code> محارف الهروب. يجب عليك دائمًا استخدام وحدة <code>csv</code> لقراءة ملفات CSV وكتابتها بسبب هذه المخاطر المحتملة.
</p>

<h2 id="reader">
	كائنات reader
</h2>

<p>
	يمكنك قراءة البيانات من ملف CSV باستخدام وحدة <code>csv</code> من خلال إنشاء كائن <code>reader</code> الذي يتيح لك التكرار على الأسطر الموجودة في ملف CSV. أدخِل ما يلي في الصدفة التفاعلية مع وضع الملف example.csv في مجلد العمل الحالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5894_11" style=""><span class="pun">➊</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> csv
</span><span class="pun">➋</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> exampleFile </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="str">'example.csv'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➌</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> exampleReader </span><span class="pun">=</span><span class="pln"> csv</span><span class="pun">.</span><span class="pln">reader</span><span class="pun">(</span><span class="pln">exampleFile</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➍</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> exampleData </span><span class="pun">=</span><span class="pln"> list</span><span class="pun">(</span><span class="pln">exampleReader</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➎</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> exampleData
   </span><span class="pun">[[</span><span class="str">'4/5/2015 13:34'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Apples'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'73'</span><span class="pun">],</span><span class="pln"> </span><span class="pun">[</span><span class="str">'4/5/2015 3:41'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Cherries'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'85'</span><span class="pun">],</span><span class="pln">
   </span><span class="pun">[</span><span class="str">'4/6/2015 12:46'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Pears'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'14'</span><span class="pun">],</span><span class="pln"> </span><span class="pun">[</span><span class="str">'4/8/2015 8:59'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Oranges'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'52'</span><span class="pun">],</span><span class="pln">
   </span><span class="pun">[</span><span class="str">'4/10/2015 2:07'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Apples'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'152'</span><span class="pun">],</span><span class="pln"> </span><span class="pun">[</span><span class="str">'4/10/2015 18:10'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Bananas'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'23'</span><span class="pun">],</span><span class="pln">
   </span><span class="pun">[</span><span class="str">'4/10/2015 2:40'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Strawberries'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'98'</span><span class="pun">]]</span></pre>

<p>
	تحتوي لغة بايثون على وحدة <code>csv</code>، لذا يمكننا استيرادها ➊ دون الحاجة إلى تثبيتها أولًا. يمكنك قراءة ملف CSV باستخدام وحدة <code>csv</code> من خلال فتحه أولًا باستخدام الدالة <code>open()‎</code> ➋ كما تفعل مع أيّ ملف نصي آخر، ولكننا لا نستدعي التابع <code>read()‎</code> أو <code>readlines()‎</code> لكائن <code>File</code> الذي تعيده الدالة <code>open()‎</code>، بل نمرّره إلى الدالة <code>csv.reader()‎</code> ➌، مما يؤدي إلى إعادة كائن <code>reader</code> لتستخدمه. لاحظ أنك لا تمرّر <a href="https://academy.hsoub.com/programming/python/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%B3%D9%84%D8%A7%D8%B3%D9%84-%D8%A7%D9%84%D9%86%D8%B5%D9%8A%D8%A9-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-r407/" rel="">السلسلة النصية</a> التي تمثّل اسم الملف مباشرةً إلى الدالة <code>csv.reader()‎</code>.
</p>

<p>
	أكثر الطرق مباشرةً للوصول إلى القيم الموجودة في كائن <code>reader</code> هي تحويله إلى قائمة <a href="https://wiki.hsoub.com/Python" rel="external">بايثون</a> عادية من خلال تمريره إلى التابع <code>list()‎</code> ➍، حيث يعيد استخدام التابع <code>list()‎</code> لكائن <code>reader</code> قائمةً من القوائم، والتي يمكنك تخزينها في متغير مثل المتغير <code>exampleData</code> الذي يؤدي إدخاله في الصدفة إلى عرض قائمةٍ القوائم ➎.
</p>

<p>
	أصبح لديك ملف CSV بوصفه قائمةً من القوائم، ويمكنك الآن الوصول إلى القيمة الموجودة في صف وعمود محدّد باستخدام التعبير <code>exampleData[row][col]‎</code>، حيث يكون <code>row</code> هو فهرس إحدى القوائم الموجودة في المتغير <code>exampleData</code>، ويكون <code>col</code> هو فهرس العنصر الذي تريده من تلك القائمة. لندخِل الآن ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5894_13" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> exampleData</span><span class="pun">[</span><span class="lit">0</span><span class="pun">][</span><span class="lit">0</span><span class="pun">]</span><span class="pln">
</span><span class="str">'4/5/2015 13:34'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> exampleData</span><span class="pun">[</span><span class="lit">0</span><span class="pun">][</span><span class="lit">1</span><span class="pun">]</span><span class="pln">
</span><span class="str">'Apples'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> exampleData</span><span class="pun">[</span><span class="lit">0</span><span class="pun">][</span><span class="lit">2</span><span class="pun">]</span><span class="pln">
</span><span class="str">'73'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> exampleData</span><span class="pun">[</span><span class="lit">1</span><span class="pun">][</span><span class="lit">1</span><span class="pun">]</span><span class="pln">
</span><span class="str">'Cherries'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> exampleData</span><span class="pun">[</span><span class="lit">6</span><span class="pun">][</span><span class="lit">1</span><span class="pun">]</span><span class="pln">
</span><span class="str">'Strawberries'</span></pre>

<p>
	لاحظ أن <code>exampleData[0][0]‎</code> ينتقل إلى القائمة الأولى ويعطي السلسلة النصية الأولى، وينتقل <code>exampleData[0][2]‎</code> إلى القائمة الأولى ويعطينا السلسلة النصية الثالثة وإلخ.
</p>

<h2 id="readerfor">
	قراءة البيانات من كائنات reader في حلقة for
</h2>

<p>
	يجب استخدام كائن <code>reader</code> في حلقة <code>for</code> بالنسبة لملفات CSV الكبيرة، مما يؤدي إلى تجنّب تحميل الملف بأكمله إلى الذاكرة دفعة واحدة، إذًا لندخِل ما يلي مثلًا في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5894_15" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> csv
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> exampleFile </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="str">'example.csv'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> exampleReader </span><span class="pun">=</span><span class="pln"> csv</span><span class="pun">.</span><span class="pln">reader</span><span class="pun">(</span><span class="pln">exampleFile</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> row </span><span class="kwd">in</span><span class="pln"> exampleReader</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Row #'</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> str</span><span class="pun">(</span><span class="pln">exampleReader</span><span class="pun">.</span><span class="pln">line_num</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">' '</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> str</span><span class="pun">(</span><span class="pln">row</span><span class="pun">))</span><span class="pln">

</span><span class="typ">Row</span><span class="pln"> </span><span class="com">#1 ['4/5/2015 13:34', 'Apples', '73']</span><span class="pln">
</span><span class="typ">Row</span><span class="pln"> </span><span class="com">#2 ['4/5/2015 3:41', 'Cherries', '85']</span><span class="pln">
</span><span class="typ">Row</span><span class="pln"> </span><span class="com">#3 ['4/6/2015 12:46', 'Pears', '14']</span><span class="pln">
</span><span class="typ">Row</span><span class="pln"> </span><span class="com">#4 ['4/8/2015 8:59', 'Oranges', '52']</span><span class="pln">
</span><span class="typ">Row</span><span class="pln"> </span><span class="com">#5 ['4/10/2015 2:07', 'Apples', '152']</span><span class="pln">
</span><span class="typ">Row</span><span class="pln"> </span><span class="com">#6 ['4/10/2015 18:10', 'Bananas', '23']</span><span class="pln">
</span><span class="typ">Row</span><span class="pln"> </span><span class="com">#7 ['4/10/2015 2:40', 'Strawberries', '98']</span></pre>

<p>
	استوردنا وحدة <code>csv</code> وأنشأنا كائن <code>reader</code> من ملف CSV، ويمكنك بعد ذلك التكرار ضمن حلقة على الصفوف الموجودة في كائن <code>reader</code>، حيث يمثل كلّ صف قائمةً من القيم، وتمثّل كل قيمة خلية. يطبع استدعاء الدالة <code>print()‎</code> رقم الصف الحالي ومحتوياته، ويمكنك الحصول على رقم الصف من خلال استخدام المتغير <code>line_num</code> الخاص بكائن <code>reader</code>، والذي يحتوي على رقم السطر الحالي. يمكن تكرار كائن <code>reader</code> مرة واحدة فقط، لذا يجب استدعاء الدالة <code>csv.reader</code> لإنشاء كائن <code>reader</code> وإعادة قراءة ملف CSV.
</p>

<h2 id="writer">
	كائنات writer
</h2>

<p>
	يتيح كائن <code>writer</code> كتابة البيانات في ملف CSV، حيث يمكنك استخدام الدالة <code>csv.writer()‎</code> لإنشاء هذا الكائن. لندخِل الآن ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5894_17" style=""><span class="pln">   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> csv
</span><span class="pun">➊</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> outputFile </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="str">'output.csv'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'w'</span><span class="pun">,</span><span class="pln"> newline</span><span class="pun">=</span><span class="str">''</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➋</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> outputWriter </span><span class="pun">=</span><span class="pln"> csv</span><span class="pun">.</span><span class="pln">writer</span><span class="pun">(</span><span class="pln">outputFile</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> outputWriter</span><span class="pun">.</span><span class="pln">writerow</span><span class="pun">([</span><span class="str">'spam'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'eggs'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'meat'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'beef'</span><span class="pun">])</span><span class="pln">
   </span><span class="lit">21</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> outputWriter</span><span class="pun">.</span><span class="pln">writerow</span><span class="pun">([</span><span class="str">'Hello, world!'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'eggs'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'meat'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'beef'</span><span class="pun">])</span><span class="pln">
   </span><span class="lit">32</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> outputWriter</span><span class="pun">.</span><span class="pln">writerow</span><span class="pun">([</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">3.141592</span><span class="pun">,</span><span class="pln"> </span><span class="lit">4</span><span class="pun">])</span><span class="pln">
   </span><span class="lit">16</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> outputFile</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span></pre>

<p>
	استدعِ أولًا الدالة <code>open()‎</code> ومرّر لها القيمة <code>'w'</code> لفتح الملف في وضع الكتابة ➊، مما يؤدي إلى إنشاء الكائن الذي يمكنك بعد ذلك تمريره إلى الدالة <code>csv.writer()‎</code> ➋ لإنشاء كائن <code>writer</code>.
</p>

<p>
	يجب أيضًا في نظام ويندوز تمرير سلسلة نصية فارغة لوسيط الكلمات المفتاحية Keyword Argument الذي هو <code>newline</code> للدالة <code>open()‎</code>، وإذا نسيت ضبط الوسيط <code>newline</code>، فستكون الصفوف في الملف output.csv مزدوجة المسافات كما هو موضّح في الشكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="156156" href="https://academy.hsoub.com/uploads/monthly_2024_08/01_000110.jpg.d2e574be63a5b43cf09e5befc9a563d0.jpg" rel=""><img alt="01 000110" class="ipsImage ipsImage_thumbnailed" data-fileid="156156" data-unique="v1peumse6" src="https://academy.hsoub.com/uploads/monthly_2024_08/01_000110.jpg.d2e574be63a5b43cf09e5befc9a563d0.jpg"> </a>
</p>

<p>
	إذا نسيت وسيط الكلمات المفتاحية <code>newline=''‎</code> في الدالة <code>open()‎</code>، فسيكون ملف CSV مزدوج المسافة
</p>

<p>
	يأخذ التابع <code>writerow()‎</code> الخاص بكائنات <code>writer</code> وسيطًا من نوع قائمة، حيث تُوضَع كل قيمة من هذه القائمة في خليتها الخاصة في ملف CSV الناتج، ويعيد هذا التابع عدد المحارف المكتوبة في الملف لهذا الصف بما في ذلك محارف السطر الجديد.
</p>

<p>
	ينتج عن الشيفرة البرمجية السابقة ملف output.csv الذي يبدو كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5894_19" style=""><span class="pln">spam</span><span class="pun">,</span><span class="pln">eggs</span><span class="pun">,</span><span class="pln">meat</span><span class="pun">,</span><span class="pln">beef
</span><span class="str">"Hello, world!"</span><span class="pun">,</span><span class="pln">eggs</span><span class="pun">,</span><span class="pln">meat</span><span class="pun">,</span><span class="pln">beef
</span><span class="lit">1</span><span class="pun">,</span><span class="lit">2</span><span class="pun">,</span><span class="lit">3.141592</span><span class="pun">,</span><span class="lit">4</span></pre>

<p>
	لاحظ كيف يهرّب الكائن <code>writer</code> تلقائيًا الفاصلة الموجودة في القيمة <code>‎'Hello, world!'‎</code> مع علامات الاقتباس المزدوجة في ملف CSV، إذ توفّر وحدة <code>csv</code> عليك الاضطرار إلى معالجة هذه الحالات الخاصة بنفسك.
</p>

<h2 id="delimiterlineterminator">
	وسطاء الكلمات المفتاحية delimiter و lineterminator
</h2>

<p>
	لنفترض أنك تريد الفصل بين الخلايا باستخدام محرف جدولة Tab بدلًا من الفاصلة وتريد أن تكون الصفوف ذات مسافات مزدوجة، لندخِل مثلًا ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5894_21" style=""><span class="pln">   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> csv
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> csvFile </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="str">'example.tsv'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'w'</span><span class="pun">,</span><span class="pln"> newline</span><span class="pun">=</span><span class="str">''</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➊</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> csvWriter </span><span class="pun">=</span><span class="pln"> csv</span><span class="pun">.</span><span class="pln">writer</span><span class="pun">(</span><span class="pln">csvFile</span><span class="pun">,</span><span class="pln"> delimiter</span><span class="pun">=</span><span class="str">'\t'</span><span class="pun">,</span><span class="pln"> lineterminator</span><span class="pun">=</span><span class="str">'\n\n'</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> csvWriter</span><span class="pun">.</span><span class="pln">writerow</span><span class="pun">([</span><span class="str">'apples'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'oranges'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'grapes'</span><span class="pun">])</span><span class="pln">
   </span><span class="lit">24</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> csvWriter</span><span class="pun">.</span><span class="pln">writerow</span><span class="pun">([</span><span class="str">'eggs'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'meat'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'beef'</span><span class="pun">])</span><span class="pln">
   </span><span class="lit">17</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> csvWriter</span><span class="pun">.</span><span class="pln">writerow</span><span class="pun">([</span><span class="str">'spam'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'spam'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'spam'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'spam'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'spam'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'spam'</span><span class="pun">])</span><span class="pln">
   </span><span class="lit">32</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> csvFile</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span></pre>

<p>
	يؤدي ذلك إلى تغيير محارف المحدِّد Delimiter والفاصل بين السطور Line Terminator في ملفك، فالمحدِّد هو المحرف الذي يظهر بين الخلايا في الصف، والمحدِّد الافتراضي لملف CSV هو الفاصلة، بينما يكون الفاصل بين السطور هو المحرف الذي يأتي في نهاية الصف، فالفاصل بين السطور الافتراضي هو محرف السطر الجديد. يمكنك تغيير هذه المحارف إلى قيم مختلفة باستخدام وسطاء الكلمات المفتاحية Keyword Arguments التي هي <code>delimiter</code> و <code>lineterminator</code> باستخدام الدالة <code>csv.writer()‎</code>.
</p>

<p>
	يؤدي تمرير الوسطاء <code>delimiter='\t'‎</code> و <code>lineterminator='\n\n'‎</code> ➊ إلى تغيير المحرف بين الخلايا إلى محرف الجدولة والمحرف بين الصفوف إلى محرفي سطر جديد. نستدعي بعد ذلك الدالة ‎<code>writerow()</code>‎ ثلاث مرات لتعطينا ثلاثة صفوف.
</p>

<p>
	ينتج عن ذلك ملف بالاسم example.tsv يحتوي ما يلي:
</p>

<pre class="ipsCode">apples  oranges grapes

eggs    meat   beef

spam    spam    spam    spam    spam    spam
</pre>

<p>
	فصلنا بين الخلايا بمحارف جدولة، وبالتالي سنستخدم امتداد الملف <code>‎.tsv</code> للقيم المفصول بينها بمحارف جدولة.
</p>

<h2 id="dictreaderdictwritercsv">
	كائنات DictReader و DictWriter الخاصة بملفات CSV
</h2>

<p>
	من الأسهل العمل مع كائنات <code>DictReader</code> و <code>DictWriter</code> بدلًا من كائنات <code>reader</code> و <code>writer</code> بالنسبة لملفات CSV التي تحتوي على صفوف الترويسات، إذ تقرأ وتكتب كائنات <code>reader</code> و <code>writer</code> صفوف ملف CSV باستخدام القوائم، وتطبّق كائنات <code>DictReader</code> و <code>DictWriter</code> الخاصة بملفات CSV الوظائف نفسها ولكن باستخدام القواميس Dictionaries، وتستخدم الصف الأول من ملف CSV بوصفها مفاتيحًا لهذه القواميس.
</p>

<p>
	نزّل الملف <a href="https://nostarch.com/automatestuff2/" rel="external nofollow">exampleWithHeader.csv</a> الذي هو الملف example.csv نفسه باستثناء أنه يحتوي على ترويسات الأعمدة Timestamp و Fruit و Quantity في الصف الأول. أدخِل ما يلي في الصدفة التفاعلية لقراءة هذا الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5894_24" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> csv
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> exampleFile </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="str">'exampleWithHeader.csv'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> exampleDictReader </span><span class="pun">=</span><span class="pln"> csv</span><span class="pun">.</span><span class="typ">DictReader</span><span class="pun">(</span><span class="pln">exampleFile</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> row </span><span class="kwd">in</span><span class="pln"> exampleDictReader</span><span class="pun">:</span><span class="pln">
</span><span class="pun">...</span><span class="pln">     </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">row</span><span class="pun">[</span><span class="str">'Timestamp'</span><span class="pun">],</span><span class="pln"> row</span><span class="pun">[</span><span class="str">'Fruit'</span><span class="pun">],</span><span class="pln"> row</span><span class="pun">[</span><span class="str">'Quantity'</span><span class="pun">])</span><span class="pln">
</span><span class="pun">...</span><span class="pln">
</span><span class="lit">4</span><span class="pun">/</span><span class="lit">5</span><span class="pun">/</span><span class="lit">2015</span><span class="pln"> </span><span class="lit">13</span><span class="pun">:</span><span class="lit">34</span><span class="pln"> </span><span class="typ">Apples</span><span class="pln"> </span><span class="lit">73</span><span class="pln">
</span><span class="lit">4</span><span class="pun">/</span><span class="lit">5</span><span class="pun">/</span><span class="lit">2015</span><span class="pln"> </span><span class="lit">3</span><span class="pun">:</span><span class="lit">41</span><span class="pln"> </span><span class="typ">Cherries</span><span class="pln"> </span><span class="lit">85</span><span class="pln">
</span><span class="lit">4</span><span class="pun">/</span><span class="lit">6</span><span class="pun">/</span><span class="lit">2015</span><span class="pln"> </span><span class="lit">12</span><span class="pun">:</span><span class="lit">46</span><span class="pln"> </span><span class="typ">Pears</span><span class="pln"> </span><span class="lit">14</span><span class="pln">
</span><span class="lit">4</span><span class="pun">/</span><span class="lit">8</span><span class="pun">/</span><span class="lit">2015</span><span class="pln"> </span><span class="lit">8</span><span class="pun">:</span><span class="lit">59</span><span class="pln"> </span><span class="typ">Oranges</span><span class="pln"> </span><span class="lit">52</span><span class="pln">
</span><span class="lit">4</span><span class="pun">/</span><span class="lit">10</span><span class="pun">/</span><span class="lit">2015</span><span class="pln"> </span><span class="lit">2</span><span class="pun">:</span><span class="lit">07</span><span class="pln"> </span><span class="typ">Apples</span><span class="pln"> </span><span class="lit">152</span><span class="pln">
</span><span class="lit">4</span><span class="pun">/</span><span class="lit">10</span><span class="pun">/</span><span class="lit">2015</span><span class="pln"> </span><span class="lit">18</span><span class="pun">:</span><span class="lit">10</span><span class="pln"> </span><span class="typ">Bananas</span><span class="pln"> </span><span class="lit">23</span><span class="pln">
</span><span class="lit">4</span><span class="pun">/</span><span class="lit">10</span><span class="pun">/</span><span class="lit">2015</span><span class="pln"> </span><span class="lit">2</span><span class="pun">:</span><span class="lit">40</span><span class="pln"> </span><span class="typ">Strawberries</span><span class="pln"> </span><span class="lit">98</span></pre>

<p>
	يضبط الكائنُ <code>DictReader</code> ضمن الحلقة الصفَّ <code>row</code> على كائن القاموس مع المفاتيح المشتقة من الترويسات الموجودة في الصف الأول، ولكنه يَضبط الصف <code>row</code> على الكائن <code>OrderedDict</code> الذي يمكنك استخدامه بالطريقة نفسها لاستخدام القاموس، ولكننا لن نشرح الفرق بين هاتين الطريقتين في هذا المقال. يعني استخدام الكائن <code>DictReader</code> أنك لا تحتاج إلى <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%A7-%D9%87%D9%8A-%D8%A3%D9%83%D9%88%D8%A7%D8%AF-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-r2244/" rel="">شيفرة برمجية</a> إضافية لتخطي معلومات ترويسة الصف الأول، لأن الكائن <code>DictReader</code> يفعل ذلك نيابةً عنك.
</p>

<p>
	إذا حاولتَ استخدام كائنات <code>DictReader</code> مع الملف example.csv الذي لا يحتوي على ترويسات أعمدة في الصف الأول، فسيستخدم كائن <code>DictReader</code> مفاتيح القاموس <code>‎'4/5/2015 13:34'‎</code> و <code>'Apples'</code> و <code>'73'</code>، حيث يمكننا تجنب ذلك من خلال تزويد الدالة <code>DictReader()‎</code> بوسيطٍ ثانٍ يحتوي على أسماء الترويسات التي تريدها:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5894_26" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> csv
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> exampleFile </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="str">'example.csv'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> exampleDictReader </span><span class="pun">=</span><span class="pln"> csv</span><span class="pun">.</span><span class="typ">DictReader</span><span class="pun">(</span><span class="pln">exampleFile</span><span class="pun">,</span><span class="pln"> </span><span class="pun">[</span><span class="str">'time'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'name'</span><span class="pun">,</span><span class="pln">
</span><span class="str">'amount'</span><span class="pun">])</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> row </span><span class="kwd">in</span><span class="pln"> exampleDictReader</span><span class="pun">:</span><span class="pln">
</span><span class="pun">...</span><span class="pln">     </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">row</span><span class="pun">[</span><span class="str">'time'</span><span class="pun">],</span><span class="pln"> row</span><span class="pun">[</span><span class="str">'name'</span><span class="pun">],</span><span class="pln"> row</span><span class="pun">[</span><span class="str">'amount'</span><span class="pun">])</span><span class="pln">
</span><span class="pun">...</span><span class="pln">
</span><span class="lit">4</span><span class="pun">/</span><span class="lit">5</span><span class="pun">/</span><span class="lit">2015</span><span class="pln"> </span><span class="lit">13</span><span class="pun">:</span><span class="lit">34</span><span class="pln"> </span><span class="typ">Apples</span><span class="pln"> </span><span class="lit">73</span><span class="pln">
</span><span class="lit">4</span><span class="pun">/</span><span class="lit">5</span><span class="pun">/</span><span class="lit">2015</span><span class="pln"> </span><span class="lit">3</span><span class="pun">:</span><span class="lit">41</span><span class="pln"> </span><span class="typ">Cherries</span><span class="pln"> </span><span class="lit">85</span><span class="pln">
</span><span class="lit">4</span><span class="pun">/</span><span class="lit">6</span><span class="pun">/</span><span class="lit">2015</span><span class="pln"> </span><span class="lit">12</span><span class="pun">:</span><span class="lit">46</span><span class="pln"> </span><span class="typ">Pears</span><span class="pln"> </span><span class="lit">14</span><span class="pln">
</span><span class="lit">4</span><span class="pun">/</span><span class="lit">8</span><span class="pun">/</span><span class="lit">2015</span><span class="pln"> </span><span class="lit">8</span><span class="pun">:</span><span class="lit">59</span><span class="pln"> </span><span class="typ">Oranges</span><span class="pln"> </span><span class="lit">52</span><span class="pln">
</span><span class="lit">4</span><span class="pun">/</span><span class="lit">10</span><span class="pun">/</span><span class="lit">2015</span><span class="pln"> </span><span class="lit">2</span><span class="pun">:</span><span class="lit">07</span><span class="pln"> </span><span class="typ">Apples</span><span class="pln"> </span><span class="lit">152</span><span class="pln">
</span><span class="lit">4</span><span class="pun">/</span><span class="lit">10</span><span class="pun">/</span><span class="lit">2015</span><span class="pln"> </span><span class="lit">18</span><span class="pun">:</span><span class="lit">10</span><span class="pln"> </span><span class="typ">Bananas</span><span class="pln"> </span><span class="lit">23</span><span class="pln">
</span><span class="lit">4</span><span class="pun">/</span><span class="lit">10</span><span class="pun">/</span><span class="lit">2015</span><span class="pln"> </span><span class="lit">2</span><span class="pun">:</span><span class="lit">40</span><span class="pln"> </span><span class="typ">Strawberries</span><span class="pln"> </span><span class="lit">98</span></pre>

<p>
	لا يحتوي الصف الأول من الملف example.csv على أيّ نص لعناوين الأعمدة، لذا أنشأنا عناوينا الخاصة <code>'time'</code> و <code>'name'</code> و <code>'amount'</code>.
</p>

<p>
	تستخدم كائنات <code>DictWriter</code> أيضًا القواميس لإنشاء ملفات CSV. إذا أردتَ أن يحتوي ملفك على صف الترويسات، فاكتب هذا الصف من خلال استدعاء الدالة ‎<code>writeheader()</code>‎، وإلّا فتخطى استدعاء هذه الدالة لحذف صف الترويسات من الملف. اكتب بعد ذلك كل صف من صفوف الملف CSV باستخدام استدعاء التابع <code>writerow()‎</code> مع تمرير قاموسٍ يستخدم الترويسات بوصفها مفاتيحًا ويحتوي على البيانات المراد كتابتها في الملف.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5894_28" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> csv
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> outputFile </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="str">'output.csv'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'w'</span><span class="pun">,</span><span class="pln"> newline</span><span class="pun">=</span><span class="str">''</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> outputDictWriter </span><span class="pun">=</span><span class="pln"> csv</span><span class="pun">.</span><span class="typ">DictWriter</span><span class="pun">(</span><span class="pln">outputFile</span><span class="pun">,</span><span class="pln"> </span><span class="pun">[</span><span class="str">'Name'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Pet'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Phone'</span><span class="pun">])</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> outputDictWriter</span><span class="pun">.</span><span class="pln">writeheader</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> outputDictWriter</span><span class="pun">.</span><span class="pln">writerow</span><span class="pun">({</span><span class="str">'Name'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Alice'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Pet'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'cat'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Phone'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'555-
1234'</span><span class="pun">})</span><span class="pln">
</span><span class="lit">20</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> outputDictWriter</span><span class="pun">.</span><span class="pln">writerow</span><span class="pun">({</span><span class="str">'Name'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Bob'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Phone'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'555-9999'</span><span class="pun">})</span><span class="pln">
</span><span class="lit">15</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> outputDictWriter</span><span class="pun">.</span><span class="pln">writerow</span><span class="pun">({</span><span class="str">'Phone'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'555-5555'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Name'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Carol'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Pet'</span><span class="pun">:</span><span class="pln">
</span><span class="str">'dog'</span><span class="pun">})</span><span class="pln">
</span><span class="lit">20</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> outputFile</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span></pre>

<p>
	يبدو الملف output.csv الذي تنشئه الشيفرة البرمجية السابقة كما يلي:
</p>

<pre class="ipsCode" id="ips_uid_5894_32">Name,Pet,Phone
Alice,cat,555-1234
Bob,,555-9999
Carol,dog,555-5555</pre>

<p>
	لاحظ أن ترتيب أزواج القيمة-المفتاح key-value في القواميس التي مرّرتها إلى الدالة <code>writerow()‎</code> غير مهم، فهي مكتوبة بترتيب المفاتيح المعطاة إلى الدالة <code>DictWriter()‎</code>، فمثلًا لا يزال رقم الهاتف يظهر في آخر الخرج بالرغم من أنك مرّرتَ المفتاح <code>Phone</code> وقيمته قبل مفاتيح وقيم <code>Name</code> و <code>Pet</code> في الصف الرابع. لاحظ أيضًا أن أيّ مفاتيح مفقودة مثل <code>'Pet'</code> في <code>{'Name': 'Bob', 'Phone': '555-9999'}</code> ستكون ببساطة فارغة في ملف CSV.
</p>

<h2 id="csv-1">
	تطبيق عملي: إزالة الترويسة من ملفات CSV
</h2>

<p>
	لنفترض أن لديك مهمة مملة تتمثل في إزالة السطر الأول من عدة مئات من ملفات CSV، إذ قد تدخل هذه الملفات في عملية آلية تتطلب البيانات فقط وليس الترويسات الموجودة في أعلى الأعمدة، حيث يمكنك فتح كل ملف في إكسل وحذف الصف الأول، ثم تعيد حفظ الملف، ولكن قد يستغرق ذلك ساعات، إذًا لنكتب برنامجًا ينفّذ هذه المهمة بدلًا من ذلك.
</p>

<p>
	يجب أن يفتح البرنامج كل ملفٍ امتداده <code>‎.csv</code> في مجلد العمل الحالي، ويقرأ محتويات ملف CSV، ثم يعيد كتابة المحتويات بدون الصف الأول في ملف يحمل الاسم نفسه، مما يؤدي إلى وضع المحتويات الجديدة لملف CSV بدون الترويسات مكان المحتويات القديمة.
</p>

<p>
	<strong>ملاحظة</strong>: تأكد من إنشاء نسخة احتياطية للملفات أولًا عندما تكتب برنامجًا يعدّل الملفات، فأنت لا تريد مسح ملفاتك الأصلية عن طريق الخطأ في حالة عدم عمل البرنامج بالطريقة التي تتوقعها.
</p>

<p>
	إليك الخطوات العامة التي سيطبّقها برنامجك:
</p>

<ol>
	<li>
		البحث عن جميع ملفات CSV في مجلد العمل الحالي.
	</li>
	<li>
		قراءة المحتويات الكاملة لكل ملف.
	</li>
	<li>
		كتابة المحتويات مع تخطي السطر الأول في ملف CSV جديد.
	</li>
</ol>

<p>
	ولكن سيحتاج برنامجك إلى تطبيق الخطوات التالية من ناحية الشيفرة البرمجية:
</p>

<ol>
	<li>
		المرور على قائمة الملفات باستخدام التابع <code>os.listdir()‎</code> مع تخطي الملفات التي ليست ملفات CSV.
	</li>
	<li>
		إنشاء كائن <code>reader</code> الخاص بوحدة CSV وقراءة محتويات الملف باستخدام السمة Attribute التي هي <code>line_num</code> لمعرفة السطر الذي يجب تخطيه.
	</li>
	<li>
		إنشاء كائن <code>writer</code> الخاص بوحدة CSV وكتابة بيانات القراءة في الملف الجديد.
	</li>
</ol>

<p>
	افتح نافذة محرّر جديدة لإنشاء ملف جديد واحفظه بالاسم removeCsvHeader.py لهذا المشروع.
</p>

<h3 id="csv-2">
	الخطوة الأولى: المرور على جميع ملفات CSV
</h3>

<p>
	أول شيء يجب على برنامجك فعله هو المرور على قائمة جميع أسماء ملفات CSV الموجودة في مجلد العمل الحالي، لذا اجعل الملف removeCsvHeader.py يبدو كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5894_34" style=""><span class="com">#! python3</span><span class="pln">
</span><span class="com"># removeCsvHeader.py - إزالة الترويسات من جميع ملفات‫ CSV الموجودة في مجلد العمل الحالي</span><span class="pln">

</span><span class="kwd">import</span><span class="pln"> csv</span><span class="pun">,</span><span class="pln"> os

os</span><span class="pun">.</span><span class="pln">makedirs</span><span class="pun">(</span><span class="str">'headerRemoved'</span><span class="pun">,</span><span class="pln"> exist_ok</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span><span class="pln">

</span><span class="com"># المرور ضمن حلقة على جميع الملفات الموجودة في مجلد العمل الحالي</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> csvFilename </span><span class="kwd">in</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">listdir</span><span class="pun">(</span><span class="str">'.'</span><span class="pun">):</span><span class="pln">
     </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> csvFilename</span><span class="pun">.</span><span class="pln">endswith</span><span class="pun">(</span><span class="str">'.csv'</span><span class="pun">):</span><span class="pln">
      </span><span class="pun">➊</span><span class="pln"> </span><span class="kwd">continue</span><span class="pln">    </span><span class="com"># تخطي الملفات التي ليست ملفات‫ CSV</span><span class="pln">

     </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Removing header from '</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> csvFilename </span><span class="pun">+</span><span class="pln"> </span><span class="str">'...'</span><span class="pun">)</span><span class="pln">

     </span><span class="com"># ‫قراءة ملف CSV مع تخطي الصف الأول</span><span class="pln">

     </span><span class="com"># ‫كتابة ملف CSV</span></pre>

<p>
	يؤدي استدعاء التابع <code>os.makedirs()‎</code> إلى إنشاء المجلد <code>headerRemoved</code> الذي سنكتب فيه جميع ملفات CSV التي ليس لها ترويسات. ستقودك حلقة <code>for</code> التي نكررها على التابع <code>os.listdir('.')‎</code> إلى هذه النتيجة جزئيًا، ولكنها ستتكرر على جميع الملفات الموجودة في مجلد العمل، لذلك يجب إضافة بعض الشيفرة البرمجية في بداية الحلقة التي تتخطى أسماء الملفات التي لا تنتهي بالامتداد <code>‎.csv</code>. تجعل التعليمة <code>continue</code> ➊ <a href="https://wiki.hsoub.com/Python/for" rel="external">حلقة for</a> تنتقل إلى اسم الملف التالي عندما تصل إلى ملف ليس ملف CSV.
</p>

<p>
	اطبع رسالةً توضّح ملف CSV الذي يعمل عليه البرنامج حتى يكون هناك بعض المخرجات أثناء تنفيذ البرنامج، ثم أضف بعض التعليقات لما يجب أن يفعله باقي البرنامج.
</p>

<h3 id="csv-3">
	الخطوة الثانية: قراءة ملف CSV
</h3>

<p>
	لا يزيل البرنامج السطر الأول من ملف CSV، بل ينشئ نسخةً جديدة من ملف CSV بدون السطر الأول، وستحل النسخة محل الملف الأصلي لأن اسم ملف النسخة هو اسم الملف الأصلي نفسه.
</p>

<p>
	سيحتاج برنامجك إلى طريقة لتعقّب المرور على الصف الأول حاليًا، لذا أضِف ما يلي إلى الملف removeCsvHeader.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5894_36" style=""><span class="com">#! python3</span><span class="pln">
</span><span class="com"># removeCsvHeader.py - إزالة الترويسات من جميع ملفات‫ CSV الموجودة في مجلد العمل الحالي</span><span class="pln">

</span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">

    </span><span class="com"># ‫قراءة ملف CSV مع تخطي الصف الأول</span><span class="pln">
    csvRows </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">
    csvFileObj </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="pln">csvFilename</span><span class="pun">)</span><span class="pln">
    readerObj </span><span class="pun">=</span><span class="pln"> csv</span><span class="pun">.</span><span class="pln">reader</span><span class="pun">(</span><span class="pln">csvFileObj</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> row </span><span class="kwd">in</span><span class="pln"> readerObj</span><span class="pun">:</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> readerObj</span><span class="pun">.</span><span class="pln">line_num </span><span class="pun">==</span><span class="pln"> </span><span class="lit">1</span><span class="pun">:</span><span class="pln">
            </span><span class="kwd">continue</span><span class="pln">    </span><span class="com"># تخطي الصف الأول</span><span class="pln">
        csvRows</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">row</span><span class="pun">)</span><span class="pln">
    csvFileObj</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">

    </span><span class="com"># ‫كتابة ملف CSV</span></pre>

<p>
	يمكن استخدام السمة <code>line_num</code> الخاصة بكائن <code>reader</code> لتحديد السطر الموجود في ملف CSV الذي يقرأه حاليًا، حيث توجد حلقة <code>for</code> أخرى للمرور على الصفوف التي يعيدها كائن <code>reader</code> الخاص بوحدة CSV، وستُلحَق جميع الصفوف باستثناء الصف الأول بالقائمة <code>csvRows</code>.
</p>

<p>
	تتحقق الشيفرة البرمجية السابقة مما إذا كان <code>readerObj.line_num</code> مضبوطًا على القيمة 1 أثناء تكرار حلقة <code>for</code> على كل صف. إذا كان ذلك صحيحًا، فستُنفَّذ تعليمة <code>continue</code> للانتقال إلى الصف التالي دون إلحاقه بالقائمة <code>csvRows</code>، وستكون قيمة الشرط <code>False</code> دائمًا بالنسبة لكل صفٍ لاحق، وسيُلحَق هذا الصف بالقائمة <code>csvRows</code>.
</p>

<h3 id="csv-4">
	الخطوة الثالثة: كتابة ملف CSV بدون الصف الأول
</h3>

<p>
	أصبحت القائمة <code>csvRows</code> تحتوي على كافة الصفوف باستثناء الصف الأول، ويجب الآن كتابة هذه القائمة في ملف CSV الموجود في المجلد headerRemoved. أضِف ما يلي إلى الملف removeCsvHeader.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5894_38" style=""><span class="pln">   </span><span class="com">#! python3</span><span class="pln">
   </span><span class="com"># removeCsvHeader.py - إزالة الترويسات من جميع ملفات‫ CSV الموجودة في مجلد العمل الحالي</span><span class="pln">

   </span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">

   </span><span class="com"># المرور ضمن حلقة على جميع الملفات الموجودة في مجلد العمل الحالي</span><span class="pln">
</span><span class="pun">➊</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> csvFilename </span><span class="kwd">in</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">listdir</span><span class="pun">(</span><span class="str">'.'</span><span class="pun">):</span><span class="pln">
       </span><span class="kwd">if</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> csvFilename</span><span class="pun">.</span><span class="pln">endswith</span><span class="pun">(</span><span class="str">'.csv'</span><span class="pun">):</span><span class="pln">
           </span><span class="kwd">continue</span><span class="pln">    </span><span class="com">#  تخطي الملفات التي ليست ملفات‫ CSV</span><span class="pln">

       </span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">

       </span><span class="com">#  ‫كتابة ملف CSV</span><span class="pln">
       csvFileObj </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="pln">os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="str">'headerRemoved'</span><span class="pun">,</span><span class="pln"> csvFilename</span><span class="pun">),</span><span class="pln"> </span><span class="str">'w'</span><span class="pun">,</span><span class="pln">
                    newline</span><span class="pun">=</span><span class="str">''</span><span class="pun">)</span><span class="pln">
       csvWriter </span><span class="pun">=</span><span class="pln"> csv</span><span class="pun">.</span><span class="pln">writer</span><span class="pun">(</span><span class="pln">csvFileObj</span><span class="pun">)</span><span class="pln">
       </span><span class="kwd">for</span><span class="pln"> row </span><span class="kwd">in</span><span class="pln"> csvRows</span><span class="pun">:</span><span class="pln">
           csvWriter</span><span class="pun">.</span><span class="pln">writerow</span><span class="pun">(</span><span class="pln">row</span><span class="pun">)</span><span class="pln">
       csvFileObj</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span></pre>

<p>
	يكتب الكائن <code>writer</code> الخاص بوحدة CSV القائمة الناتجة في ملف CSV موجود ضمن المجلد <code>headerRemoved</code> باستخدام المتغير <code>csvFilename</code> الذي استخدمناه أيضًا في كائن <code>reader</code> الخاص بوحدة CSV، مما يؤدي إلى الكتابة فوق الملف الأصلي. نمر بعد إنشاء الكائن <code>writer</code> على القوائم الفرعية المُخزَّنة في القائمة <code>csvRows</code> ونكتب كل قائمة فرعية في الملف. تنتقل حلقة <code>for</code> الخارجية ➊ إلى اسم الملف التالي من <code>os.listdir('.')‎</code> بعد تنفيذ الشيفرة البرمجية، وسيكتمل البرنامج عند الانتهاء من تلك الحلقة.
</p>

<p>
	اختبر برنامجك من خلال تنزيل الملف المضغوط <a href="https://nostarch.com/automatestuff2/" rel="external nofollow">removeCsvHeader.zip</a> وفك ضغطه في مجلدٍ ما، ثم شغّل البرنامج removeCsvHeader.py في هذا المجلد، وسيكون الخرج كما يلي:
</p>

<pre class="ipsCode">Removing header from NAICS_data_1048.csv...
Removing header from NAICS_data_1218.csv...
--snip--
Removing header from NAICS_data_9834.csv...
Removing header from NAICS_data_9986.csv…
</pre>

<p>
	يجب أن يطبع هذا البرنامج اسم ملفٍ في كل مرة يزيل فيها السطر الأول من ملف CSV.
</p>

<h3 id="">
	أفكار لبرامج مماثلة
</h3>

<p>
	تشبه البرامجُ التي يمكنك كتابتها لملفات CSV أنواعَ البرامج التي يمكنك كتابتها لملفات إكسل، لأنهما ملفات جداول بيانات، حيث يمكنك كتابة برامج تنفّذ ما يلي:
</p>

<ul>
	<li>
		مقارنة البيانات بين صفوف مختلفة في ملف CSV واحد أو بين ملفات CSV متعددة.
	</li>
	<li>
		نسخ بيانات محددة من ملف CSV إلى ملف إكسل أو العكس.
	</li>
	<li>
		التحقق من وجود بيانات غير صالحة أو أخطاء في تنسيق ملفات CSV وتنبيه المستخدم بهذه الأخطاء.
	</li>
	<li>
		قراءة البيانات من ملف CSV بوصفها دخلًا لبرامج بايثون الخاصة بك.
	</li>
</ul>

<h2 id="jsonapi">
	JSON وواجهات برمجة التطبيقات <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>
</h2>

<p>
	يُعَد ترميز الكائنات باستعمال جافاسكربت JavaScript Object Notation -أو JSON اختصارًا- طريقةً شائعة لتنسيق البيانات بوصفها سلسلة نصية واحدة يمكن أن يقرأها البشر، وهو الطريقة الأصيلة التي تكتب بها برامج جافاسكربت هياكلَ البيانات الخاصة بها وتشبه ما ستنتجه دالة <code>pprint()‎</code> في بايثون. لست بحاجةٍ لمعرفة لغة جافاسكربت لتتمكّن من التعامل مع البيانات المكتوبة بتنسيق JSON.
</p>

<p>
	إليك مثال للبيانات المكتوبة بتنسيق JSON:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5894_40" style=""><span class="pun">{</span><span class="str">"name"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Zophie"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"isCat"</span><span class="pun">:</span><span class="pln"> true</span><span class="pun">,</span><span class="pln">
 </span><span class="str">"miceCaught"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="str">"napsTaken"</span><span class="pun">:</span><span class="pln"> </span><span class="lit">37.5</span><span class="pun">,</span><span class="pln">
 </span><span class="str">"felineIQ"</span><span class="pun">:</span><span class="pln"> null</span><span class="pun">}</span></pre>

<p>
	تُعَد معرفة JSON أمرًا مفيدًا، لأن العديد من مواقع الويب تقدم محتوى JSON بوصفه وسيلةً للبرامج للتفاعل مع موقع الويب، ويُعرَف ذلك بتوفير <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%A7-%D9%87%D9%8A-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D9%84%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-api%D8%9F-r1512/" rel="">واجهة برمجة تطبيقات Application Programming Interface</a> -أو <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> اختصارًا. يشبه الوصول إلى واجهة برمجة التطبيقات الوصولَ إلى أيّ صفحة ويب أخرى باستخدام <a href="https://academy.hsoub.com/programming/general/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%B9%D9%86%D9%88%D8%A7%D9%86-url-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9%D9%87-r1435/" rel="">عنوان URL</a>، ولكن الفرق بينهما هو أن البيانات التي تعيدها واجهة برمجة التطبيقات تكون منسَّقة لتفهمها الأجهزة باستخدام JSON مثلًا، إذ ليس من السهل أن يقرأ البشر واجهات برمجة التطبيقات.
</p>

<p>
	تتيح العديد من مواقع الويب بياناتها بتنسيق JSON، حيث توفر فيسبوك Facebook وتويتر Twitter وياهو Yahoo وجوجل Google وتمبلر Tumblr وويكيبيديا Wikipedia وفليكر Flickr و Data.gov وريديت Reddit و IMDb وروتن توميتوز Rotten Tomatoes ولينكد إن LinkedIn والعديد من المواقع الشهيرة الأخرى واجهات برمجة تطبيقات لتستخدمها البرامج. تتطلب بعض هذه المواقع التسجيل الذي يكون مجانيًا دائمًا، ويجب أن تعثر على توثيق عناوين URL التي يحتاج برنامجك إلى طلبها للحصول على البيانات التي تريدها، بالإضافة إلى التنسيق العام لهياكل بيانات JSON المُعادة. يجب توفير هذا التوثيق من خلال أيّ موقع يقدم واجهة برمجة التطبيقات، حيث إذا توافرت صفحة للمطورين "Developers"، فابحث عن التوثيق هناك.
</p>

<p>
	يمكنك كتابة البرامج التي تنفّذ ما يلي باستخدام واجهات برمجة التطبيقات:
</p>

<ul>
	<li>
		استخلاص البيانات الخام من المواقع، حيث يكون الوصول إلى واجهات برمجة التطبيقات أسهل من تنزيل صفحات الويب وتحليل شيفرة <a href="https://wiki.hsoub.com/HTML" rel="external">HTML</a> باستخدام مكتبة Beautiful Soup.
	</li>
	<li>
		تنزيل المنشورات الجديدة تلقائيًا من أحد حساباتك على شبكة التواصل الاجتماعي ونشرها على حسابٍ آخر، فمثلًا يمكنك أخذ منشوراتك على تمبلر ونشرها على فيسبوك.
	</li>
	<li>
		إنشاء "موسوعة أفلام" لمجموعة أفلامك الشخصية من خلال سحب البيانات من IMDb و Rotten Tomatoes وويكيبيديا ووضعها في ملف نصي واحد على حاسوبك.
	</li>
</ul>

<p>
	<strong>ملاحظة</strong>: يمكنك رؤية بعض الأمثلة على واجهات برمجة تطبيقات JSON في الموارد الموجودة على موقع <a href="https://nostarch.com/automatestuff2/" rel="external nofollow">nostarch</a>.
</p>

<p>
	لا تعد JSON الطريقة الوحيدة لتنسيق البيانات في سلسلة نصية يمكن أن يقرأها البشر، إذ توجد العديد من اللغات الأخرى بما في ذلك لغات XML (لغة التوصيف الموسَّعة eXtensible Markup Language) و TOML (أو Tom’s Obvious, Minimal Language) و YML (أو Yet another Markup Language) و INI (أو Initialization) وتنسيقات ASN.1 القديمة (Abstract Syntax Notation One)، حيث توفر جميع هذه اللغات هيكلًا لتمثيل البيانات بوصفها نصًا يمكن أن يقرأه البشر. لن نغطّي هذه اللغات في هذا المقال، لأن تنسيق JSON أصبح التنسيق البديل الأكثر استخدامًا على نطاق واسع، ولكن توجد وحدات بايثون خارجية يمكنها التعامل معها بسهولة.
</p>

<h2 id="json">
	وحدة json
</h2>

<p>
	تتعامل وحدة <code>json</code> في بايثون مع جميع تفاصيل الترجمة بين سلسلة نصية تحتوي على بيانات JSON وقيم بايثون الخاصة بالدوال <code>json.loads()‎</code> و <code>json.dumps()‎</code>. لا يمكن لتنسيق JSON تخزين كل أنواع قيم بايثون، إذ يمكن أن يحتوي على قيم لأنواع بياناتٍ مُحدَّدة فقط وهي: السلاسل النصية Strings والأعداد الصحيحة Integers والأعداد العشرية Floats والقيم المنطقية Booleans والقوائم Lists والقواميس Dictionaries والنوع <code>NoneType</code>. كما لا يمكن لتنسيق JSON أن يمثل كائنات خاصة بلغة بايثون مثل كائنات <code>File</code> أو كائنات <code>reader</code> أو <code>writer</code> الخاصة بوحدة CSV أو كائنات <code>Regex</code> أو كائنات <code>WebElement</code> الخاصة بالوحدة Selenium.
</p>

<h3 id="jsonloads">
	قراءة بيانات JSON باستخدام الدالة loads()‎
</h3>

<p>
	يمكنك ترجمة سلسلة نصية تحتوي على بيانات JSON إلى قيمة في لغة بايثون من خلال تمريرها إلى الدالة <code>json.loads()‎</code>، حيث يعني اسم هذه الدالة loads تحميل سلسلة نصية "load string" ولا يعني مجموعة التحميلات "loads". لندخِل الآن ما يلي في الصدفة التفاعلية Interactive Shell:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5894_42" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> stringOfJsonData </span><span class="pun">=</span><span class="pln"> </span><span class="str">'{"name": "Zophie", "isCat": true, "miceCaught": 0,
"felineIQ": null}'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> json
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> jsonDataAsPythonValue </span><span class="pun">=</span><span class="pln"> json</span><span class="pun">.</span><span class="pln">loads</span><span class="pun">(</span><span class="pln">stringOfJsonData</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> jsonDataAsPythonValue
</span><span class="pun">{</span><span class="str">'isCat'</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">True</span><span class="pun">,</span><span class="pln"> </span><span class="str">'miceCaught'</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="str">'name'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Zophie'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'felineIQ'</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">}</span></pre>

<p>
	نستورد أولًا الوحدة <code>json</code>، ثم يمكننا استدعاء الدالة <code>loads()‎</code> وتمرير سلسلة نصية من بيانات JSON إليها، حيث تستخدم سلاسل JSON النصية دائمًا علامات اقتباس مزدوجة، وتعيد هذه الدالة البيانات بوصفها قاموس بايثون. تُعَد قواميس بايثون غير مرتبة، لذا يمكن أن تظهر أزواج مفتاح-قيمة بترتيب مختلف عند طباعة <code>jsonDataAsPythonValue</code>.
</p>

<h3 id="jsondumps">
	كتابة بيانات JSON باستخدام الدالة dumps()‎
</h3>

<p>
	تترجم الدالة <code>json.dumps()‎</code> قيمة بايثون إلى سلسلة نصية من البيانات بتنسيق JSON، حيث يعني اسم هذه الدالة dumps تفريغ سلسلة نصية "dump string" ولا يعني الجمع "dumps"). لندخِل الآن ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5894_44" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pythonValue </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="str">'isCat'</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">True</span><span class="pun">,</span><span class="pln"> </span><span class="str">'miceCaught'</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="str">'name'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Zophie'</span><span class="pun">,</span><span class="pln">
</span><span class="str">'felineIQ'</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">None</span><span class="pun">}</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> json
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> stringOfJsonData </span><span class="pun">=</span><span class="pln"> json</span><span class="pun">.</span><span class="pln">dumps</span><span class="pun">(</span><span class="pln">pythonValue</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> stringOfJsonData
</span><span class="str">'{"isCat": true, "felineIQ": null, "miceCaught": 0, "name": "Zophie" }'</span></pre>

<p>
	يمكن أن يكون نوع القيمة أحد أنواع بيانات بايثون الأساسية فقط وهي: قاموس أو قائمة أو عدد صحيح أو عدد عشري أو سلسلة نصية أو قيمة منطقية أو <code>None</code>.
</p>

<h2 id="-1">
	تطبيق عملي: جلب بيانات الطقس الحالية
</h2>

<p>
	يبدو التحقق من الطقس أمرًا بسيطًا إلى حد ما، حيث يمكنك فتح متصفح الويب الخاص بك والنقر على شريط العناوين، ثم كتابة عنوان URL لموقع ويب خاص بالطقس أو البحث عن موقع ثم النقر على الرابط، وانتظار تحميل الصفحة، والاطلاع على جميع الإعلانات وإلخ. هناك الكثير من الخطوات المملة التي يمكنك تخطيها إذا كان لديك برنامج ينزّل توقعات الطقس للأيام القليلة القادمة ويطبعها ضمن نصٍ عادي، حيث يستخدم هذا البرنامج الوحدة <code>requests</code> لتنزيل البيانات من الويب، فالخطوات العامة التي يطبّقها هذا البرنامج هي ما يلي:
</p>

<ol>
	<li>
		قراءة الموقع المطلوب من <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%A7-%D9%87%D9%88-%D8%B3%D8%B7%D8%B1-%D8%A7%D9%84%D8%A3%D9%88%D8%A7%D9%85%D8%B1-%D8%9F-r353/" rel="">سطر الأوامر</a>.
	</li>
	<li>
		تنزيل بيانات JSON الخاصة بالطقس من الموقع OpenWeatherMap.org.
	</li>
	<li>
		تحويل سلسلة بيانات JSON إلى هيكل بيانات بايثون.
	</li>
	<li>
		طباعة حالة الطقس لهذا اليوم واليومين القادمين.
	</li>
</ol>

<p>
	لذا ستطبّق الشيفرة البرمجية الخطوات التالية:
</p>

<ol>
	<li>
		ضم السلاسل النصية إلى القائمة <code>sys.argv</code> للحصول على الموقع.
	</li>
	<li>
		استدعاء الدالة <code>requests.get()‎</code> لتنزيل بيانات الطقس.
	</li>
	<li>
		استدعاء الدالة <code>json.loads()‎</code> لتحويل بيانات JSON إلى هيكل بيانات بايثون.
	</li>
	<li>
		طباعة توقعات الطقس.
	</li>
</ol>

<p>
	افتح نافذة محرّر جديدة لإنشاء ملف جديد لهذا المشروع واحفظه بالاسم getOpenWeather.py، ثم انتقل إلى الموقع <a href="https://openweathermap.org/api/" rel="external nofollow">OpenWeatherMap</a> في متصفحك وسجّل فيه على حساب مجاني للحصول على مفتاح <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>، ويُسمَّى أيضًا معرّف التطبيق app ID، والذي يمثل رمز سلسلة نصية يبدو مثل الرمز <code>'30144aba38018987d84710d0e319281e'</code> بالنسبة لخدمة OpenWeatherMap. لا حاجة للدفع مقابل هذه الخدمة إلّا إذا أردتَ إجراء أكثر من 60 استدعاء لواجهة برمجة التطبيقات في الدقيقة. حافظ على سرية مفتاح <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>، إذ يمكن لأيّ شخص يعرفه كتابة سكربتات تأخذ من حصة الاستخدام الخاصة بحسابك.
</p>

<h3 id="-2">
	الخطوة الأولى: الحصول على الموقع من وسيط سطر الأوامر
</h3>

<p>
	يأتي دخل هذا البرنامج من سطر الأوامر، لذا اجعل برنامج getOpenWeather.py كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5894_46" style=""><span class="com">#! python3</span><span class="pln">
</span><span class="com"># getOpenWeather.py - طباعة الطقس لموقعٍ ما من سطر الأوامر</span><span class="pln">

APPID </span><span class="pun">=</span><span class="pln"> </span><span class="str">'YOUR_APPID_HERE'</span><span class="pln">

</span><span class="kwd">import</span><span class="pln"> json</span><span class="pun">,</span><span class="pln"> requests</span><span class="pun">,</span><span class="pln"> sys

</span><span class="com"># حساب الموقع من وسطاء سطر الأوامر</span><span class="pln">
</span><span class="kwd">if</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">sys</span><span class="pun">.</span><span class="pln">argv</span><span class="pun">)</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">2</span><span class="pun">:</span><span class="pln">
    </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Usage: getOpenWeather.py city_name, 2-letter_country_code'</span><span class="pun">)</span><span class="pln">
    sys</span><span class="pun">.</span><span class="pln">exit</span><span class="pun">()</span><span class="pln">
location </span><span class="pun">=</span><span class="pln"> </span><span class="str">' '</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">sys</span><span class="pun">.</span><span class="pln">argv</span><span class="pun">[</span><span class="lit">1</span><span class="pun">:])</span><span class="pln">

</span><span class="com"># ‫تنزيل بيانات JSON من واجهة برمجة تطبيقات OpenWeatherMap.org</span><span class="pln">

</span><span class="com"># تحميل بيانات‫ JSON في متغير بايثون</span></pre>

<p>
	تُخزَّن وسطاء سطر الأوامر ضمن القائمة <code>sys.argv</code> في لغة بايثون، ويجب ضبط المتغير <code>APPID</code> على قيمة مفتاح <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> الخاص بحسابك، إذ ستفشل طلباتك لخدمة الطقس بدون هذا المفتاح، ثم سيتحقق البرنامج من وجود أكثر من وسيط سطر أوامر بعد سطر Shebang (الذي يبدأ بالرمز !#) والتعليمة <code>import</code>. تذكّر أن القائمة <code>sys.argv</code> تحتوي دائمًا على عنصر واحد على الأقل <code>sys.argv[0]‎</code>، والذي يحتوي على اسم ملف سكربت بايثون. إذا كان هناك عنصر واحد فقط في القائمة، فهذا يعني أن المستخدم لم يقدّم موقعًا في سطر الأوامر، وستُعرَض رسالة الاستخدام "usage" للمستخدم قبل انتهاء البرنامج.
</p>

<p>
	تتطلب خدمة OpenWeatherMap تنسيق الاستعلام بالشكل: اسم المدينة ثم فاصلة ثم رمز البلد المكون من حرفين مثل الرمز "US" للولايات المتحدة الأمريكية، حيث يمكنك العثور على قائمة بهذه الرموز على <a href="https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2" rel="external nofollow">ويكيبيديا</a>. يعرض هذا السكربت الطقس للمدينة الأولى المُدرَجة في نص JSON المُعاد، ولكن ستُضمَّن جميع المدن التي لها الاسم نفسه مثل مدينة بورتلاند Portland في ولاية أوريجون ومدينة بورتلاند بولاية ماين، بالرغم من أن نص JSON سيتضمّن معلومات خطوط الطول والعرض للتمييز بين هذه المدن.
</p>

<p>
	تُقسَم وسطاء سطر الأوامر بناءً على الفراغات، حيث سيجعل وسيط سطر الأوامر <code>San Francisco, US</code> القائمة <code>sys.argv</code> تحتوي على <code>['getOpenWeather.py', 'San', 'Francisco,', 'US']</code>، لذلك استدعِ التابع <code>join()‎</code> لضم جميع السلاسل النصية باستثناء السلسلة النصية الأولى في القائمة <code>sys.argv</code>، وخزّن هذه السلسلة النصية الناتجة عن الضم في متغير اسمه <code>location</code>.
</p>

<h3 id="json-1">
	الخطوة الثانية: تنزيل بيانات JSON
</h3>

<p>
	يوفّر موقع OpenWeatherMap.org معلومات الطقس بتنسيق JSON في الزمن الحقيقي، ولكن يجب عليك أولًا التسجيل في الموقع للحصول على مفتاح <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> مجاني، حيث يُستخدَم هذا المفتاح لتقييد عدد مرات تقديم الطلبات على الخادم، مما يؤدي إلى إبقاء تكاليف حيز النطاق التراسلي منخفضة. يجب أن ينزّل برنامجك الصفحة الموجودة على الرابط:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_5894_50" style=""><span class="pln"> https://api.openweathermap.org/data/2.5/forecast/daily?q=</span><span class="tag">&lt;Location&gt;</span><span class="pln">&amp;cnt=3&amp;APPID=</span><span class="tag">&lt;APIkey&gt;</span><span class="pln">‎</span></pre>

<p>
	حيث <code>&lt;Location&gt;</code> هو اسم المدينة التي تريد الطقس فيها و <code>&lt;<abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> key&gt;</code> هو مفتاح <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> الشخصي الخاص بك. أضِف ما يلي إلى برنامج getOpenWeather.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5894_48" style=""><span class="com">#! python3</span><span class="pln">
</span><span class="com"># getOpenWeather.py - طباعة الطقس لموقعٍ ما من سطر الأوامر</span><span class="pln">

</span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">

</span><span class="com"># ‫تنزيل بيانات JSON من واجهة برمجة تطبيقات OpenWeatherMap.org</span><span class="pln">
url </span><span class="pun">=</span><span class="str">'https://api.openweathermap.org/data/2.5/forecast/daily?q=%s&amp;cnt=3&amp;APPID=%s '</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> </span><span class="pun">(</span><span class="pln">location</span><span class="pun">,</span><span class="pln">
APPID</span><span class="pun">)</span><span class="pln">
response </span><span class="pun">=</span><span class="pln"> requests</span><span class="pun">.</span><span class="pln">get</span><span class="pun">(</span><span class="pln">url</span><span class="pun">)</span><span class="pln">
response</span><span class="pun">.</span><span class="pln">raise_for_status</span><span class="pun">()</span><span class="pln">

</span><span class="com"># ‫ألغِ التعليق لرؤية نص JSON الخام:</span><span class="pln">
</span><span class="com">#print(response.text)    </span><span class="pln">

</span><span class="com"># تحميل بيانات‫ JSON في متغير بايثون</span></pre>

<p>
	يأتي الموقع <code>location</code> من وسطاء سطر الأوامر، ويمكننا إنشاء عنوان URL الذي نريد الوصول إليه من خلال استخدام العنصر البديل <code>s%</code> وإدراج أيّ سلسلة نصية مُخزَّنة في <code>location</code> في ذلك المكان من السلسلة النصية التي تمثّل عنوان URL. نخزّن النتيجة في المتغير <code>url</code> الذي نمرّره إلى الدالة <code>requests.get()‎</code>، حيث يعيد استدعاء الدالة <code>requests.get()‎</code> الكائن <code>Response</code>، والذي يمكنك التحقق من وجود أخطاء فيه من خلال استدعاء الدالة <code>raise_for_status()‎</code>. إن لم يظهر أيّ استثناء، فسيكون النص المُنزَّل موجودًا في <code>response.text</code>.
</p>

<h3 id="json-2">
	الخطوة الثالثة: تحميل بيانات JSON وطباعة الطقس
</h3>

<p>
	يحتوي المتغير العضو <code>response.text</code> على سلسلة نصية كبيرة من البيانات المُنسَّقة بتنسيق JSON، حيث يمكنك تحويلها إلى قيمة بايثون من خلال استدعاء الدالة <code>json.loads()‎</code>، وستبدو بيانات JSON كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5894_52" style=""><span class="pun">{</span><span class="str">'city'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="str">'coord'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="str">'lat'</span><span class="pun">:</span><span class="pln"> </span><span class="lit">37.7771</span><span class="pun">,</span><span class="pln"> </span><span class="str">'lon'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">-</span><span class="lit">122.42</span><span class="pun">},</span><span class="pln">
          </span><span class="str">'country'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'United States of America'</span><span class="pun">,</span><span class="pln">
          </span><span class="str">'id'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'5391959'</span><span class="pun">,</span><span class="pln">
          </span><span class="str">'name'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'San Francisco'</span><span class="pun">,</span><span class="pln">
          </span><span class="str">'population'</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pun">},</span><span class="pln">
 </span><span class="str">'cnt'</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3</span><span class="pun">,</span><span class="pln">
 </span><span class="str">'cod'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'200'</span><span class="pun">,</span><span class="pln">
 </span><span class="str">'list'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[{</span><span class="str">'clouds'</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln">
           </span><span class="str">'deg'</span><span class="pun">:</span><span class="pln"> </span><span class="lit">233</span><span class="pun">,</span><span class="pln">
           </span><span class="str">'dt'</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1402344000</span><span class="pun">,</span><span class="pln">
           </span><span class="str">'humidity'</span><span class="pun">:</span><span class="pln"> </span><span class="lit">58</span><span class="pun">,</span><span class="pln">
           </span><span class="str">'pressure'</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1012.23</span><span class="pun">,</span><span class="pln">
           </span><span class="str">'speed'</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1.96</span><span class="pun">,</span><span class="pln">
           </span><span class="str">'temp'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="str">'day'</span><span class="pun">:</span><span class="pln"> </span><span class="lit">302.29</span><span class="pun">,</span><span class="pln">
                    </span><span class="str">'eve'</span><span class="pun">:</span><span class="pln"> </span><span class="lit">296.46</span><span class="pun">,</span><span class="pln">
                    </span><span class="str">'max'</span><span class="pun">:</span><span class="pln"> </span><span class="lit">302.29</span><span class="pun">,</span><span class="pln">
                    </span><span class="str">'min'</span><span class="pun">:</span><span class="pln"> </span><span class="lit">289.77</span><span class="pun">,</span><span class="pln">
                    </span><span class="str">'morn'</span><span class="pun">:</span><span class="pln"> </span><span class="lit">294.59</span><span class="pun">,</span><span class="pln">
                    </span><span class="str">'night'</span><span class="pun">:</span><span class="pln"> </span><span class="lit">289.77</span><span class="pun">},</span><span class="pln">
           </span><span class="str">'weather'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[{</span><span class="str">'description'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'sky is clear'</span><span class="pun">,</span><span class="pln">
                        </span><span class="str">'icon'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'01d'</span><span class="pun">,</span><span class="pln">
</span><span class="pun">--</span><span class="pln">snip</span><span class="pun">–</span></pre>

<p>
	يمكنك رؤية هذه البيانات من خلال تمرير المتغير <code>weatherData</code> إلى الدالة <code>pprint.pprint()‎</code>، وقد ترغب في الاطلاع على موقع <a href="https://openweathermap.org/" rel="external nofollow">openweathermap.org</a> لمزيد من التوثيق الذي يوضّح معنى هذه الحقول، فمثلًا سيخبرك التوثيق عبر الإنترنت أن القيمة <code>302.29</code> بعد <code>'day'</code> هي درجة الحرارة أثناء النهار بواحدة الكلفن وليست بواحدة فهرنهايت أو الدرجة المئوية.
</p>

<p>
	يوجد وصف الطقس الذي تريده بعد <code>'main'</code> و <code>'description'</code>، لذا أضِف ما يلي إلى برنامج getOpenWeather.py لطباعته بدقة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5894_54" style=""><span class="pln">   </span><span class="pun">!</span><span class="pln"> python3
   </span><span class="com"># getOpenWeather.py - طباعة الطقس لموقعٍ ما من سطر الأوامر</span><span class="pln">

   </span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">

   </span><span class="com">#  تحميل بيانات‫ JSON في متغير بايثون</span><span class="pln">
   weatherData </span><span class="pun">=</span><span class="pln"> json</span><span class="pun">.</span><span class="pln">loads</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">text</span><span class="pun">)</span><span class="pln">

   </span><span class="com"># طباعة وصف الطقس</span><span class="pln">
</span><span class="pun">➊</span><span class="pln"> w </span><span class="pun">=</span><span class="pln"> weatherData</span><span class="pun">[</span><span class="str">'list'</span><span class="pun">]</span><span class="pln">
   </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Current weather in %s:'</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> </span><span class="pun">(</span><span class="pln">location</span><span class="pun">))</span><span class="pln">
   </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">w</span><span class="pun">[</span><span class="lit">0</span><span class="pun">][</span><span class="str">'weather'</span><span class="pun">][</span><span class="lit">0</span><span class="pun">][</span><span class="str">'main'</span><span class="pun">],</span><span class="pln"> </span><span class="str">'-'</span><span class="pun">,</span><span class="pln"> w</span><span class="pun">[</span><span class="lit">0</span><span class="pun">][</span><span class="str">'weather'</span><span class="pun">][</span><span class="lit">0</span><span class="pun">][</span><span class="str">'description'</span><span class="pun">])</span><span class="pln">
   </span><span class="kwd">print</span><span class="pun">()</span><span class="pln">
   </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Tomorrow:'</span><span class="pun">)</span><span class="pln">
   </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">w</span><span class="pun">[</span><span class="lit">1</span><span class="pun">][</span><span class="str">'weather'</span><span class="pun">][</span><span class="lit">0</span><span class="pun">][</span><span class="str">'main'</span><span class="pun">],</span><span class="pln"> </span><span class="str">'-'</span><span class="pun">,</span><span class="pln"> w</span><span class="pun">[</span><span class="lit">1</span><span class="pun">][</span><span class="str">'weather'</span><span class="pun">][</span><span class="lit">0</span><span class="pun">][</span><span class="str">'description'</span><span class="pun">])</span><span class="pln">
   </span><span class="kwd">print</span><span class="pun">()</span><span class="pln">
   </span><span class="kwd">print</span><span class="pun">(</span><span class="str">'Day after tomorrow:'</span><span class="pun">)</span><span class="pln">
   </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">w</span><span class="pun">[</span><span class="lit">2</span><span class="pun">][</span><span class="str">'weather'</span><span class="pun">][</span><span class="lit">0</span><span class="pun">][</span><span class="str">'main'</span><span class="pun">],</span><span class="pln"> </span><span class="str">'-'</span><span class="pun">,</span><span class="pln"> w</span><span class="pun">[</span><span class="lit">2</span><span class="pun">][</span><span class="str">'weather'</span><span class="pun">][</span><span class="lit">0</span><span class="pun">][</span><span class="str">'description'</span><span class="pun">])</span></pre>

<p>
	لاحظ كيف تخزّن الشيفرة البرمجية السابقة المتغير <code>weatherData['list']‎</code> في المتغير <code>w</code> ليوفر عليك بعض الجهد عند الكتابة ➊، حيث يمكنك استخدام <code>w[0]‎</code> و <code>w[1]‎</code> و <code>w[2]‎</code> لاسترداد القواميس الخاصة بطقس اليوم والغد وبعد الغد على التوالي، ويحتوي كلّ قاموس من هذه القواميس على مفتاح <code>'weather'</code> الذي يحتوي على قيمة من النوع قائمة، ويهمنا منها عنصر القائمة الأول عند الفهرس 0، وهو قاموس متداخل يحتوي على عدة مفاتيح أخرى. نطبع القيم المخزنة في المفتاحين <code>'main'</code> و <code>'description'</code>، حيث نفصل بينها بشرطة واصلة.
</p>

<p>
	سيبدو الخرج كما يلي عند تشغيل هذا البرنامج باستخدام وسيط سطر الأوامر <code>getOpenWeather.py San Francisco, CA</code>:
</p>

<pre class="ipsCode">Current weather in San Francisco, CA:
Clear - sky is clear

Tomorrow:
Clouds - few clouds

Day after tomorrow:
Clear - sky is clear
</pre>

<h3 id="-3">
	أفكار لبرامج مماثلة
</h3>

<p>
	يمكن أن يشكّل الوصول إلى بيانات الطقس الأساس الذي نبني عليه العديد من أنواع البرامج الأخرى، حيث يمكنك إنشاء برامج مماثلة لتنفيذ ما يلي:
</p>

<ul>
	<li>
		جمع تنبؤات الطقس للعديد من مواقع التخييم أو مسارات المشي لمسافات طويلة لمعرفة أيّ منها سيكون فيها الطقس الأفضل.
	</li>
	<li>
		جدولة برنامج للتحقق من الطقس بانتظام وإرسال تنبيه عند حدوث الصقيع إذا أردتَ نقل نباتاتك إلى الداخل، حيث سنوضّح لاحقًا الجدولة وكيفية إرسال البريد الإلكتروني.
	</li>
	<li>
		سحب بيانات الطقس من مواقع متعددة لإظهارها دفعة واحدة، أو حساب وإظهار متوسط توقعات الطقس المتعددة.
	</li>
</ul>

<h2 id="csv-5">
	مشروع للتدريب: محوّل ملف إكسل إلى ملف CSV
</h2>

<p>
	يمكن لإكسل حفظ جدول بيانات في ملف CSV باستخدام بضع نقرات بالماوس، ولكن إذا أردتَ تحويل مئاتٍ من ملفات إكسل إلى ملفات CSV، فسيستغرق الأمر ساعات من النقر، لذا استخدم وحدة <code>openpyxl</code> لكتابة برنامجٍ يقرأ جميع ملفات إكسل الموجودة في مجلد العمل الحالي ويخرجها بوصفها ملفات CSV.
</p>

<p>
	قد يحتوي ملف إكسل واحد على أوراق متعددة، لذا يجب إنشاء ملف CSV واحد لكل ورقة، ويجب أن تكون أسماء ملفات CSV هي <code>‎&lt;excel filename&gt;_&lt;sheet title&gt;.csv</code>، حيث يكون <code>&lt;excel filename&gt;</code> هو اسم ملف إكسل بدون امتداد الملف مثل <code>'spam_data'</code> وليس <code>'spam_data.xlsx'</code> ويكون <code>&lt;sheet title&gt;</code> هو السلسلة النصية التي تأتي من المتغير <code>title</code> الخاص بكائن <code>Worksheet</code>.
</p>

<p>
	سيتضمن هذا البرنامج العديد من حلقات <code>for</code> المتداخلة، وسيبدو هيكل هذا البرنامج كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_5894_56" style=""><span class="kwd">for</span><span class="pln"> excelFile </span><span class="kwd">in</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">listdir</span><span class="pun">(</span><span class="str">'.'</span><span class="pun">):</span><span class="pln">
    </span><span class="com"># ‫تخطي الملفات التي ليست ملفات xlsx، وتحميل كائن المصنف workbook</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> sheetName </span><span class="kwd">in</span><span class="pln"> wb</span><span class="pun">.</span><span class="pln">get_sheet_names</span><span class="pun">():</span><span class="pln">
        </span><span class="com"># المرور ضمن حلقة على كل ورقة في المصنف</span><span class="pln">
        sheet </span><span class="pun">=</span><span class="pln"> wb</span><span class="pun">.</span><span class="pln">get_sheet_by_name</span><span class="pun">(</span><span class="pln">sheetName</span><span class="pun">)</span><span class="pln">

        </span><span class="com"># إنشاء اسم ملف‫ CSV من اسم ملف إكسل وعنوان الورقة</span><span class="pln">
        </span><span class="com"># ‫إنشاء كائن csv.writer لملف CSV</span><span class="pln">

        </span><span class="com"># المرور ضمن حلقة على كل صف في الورقة</span><span class="pln">
        </span><span class="kwd">for</span><span class="pln"> rowNum </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> sheet</span><span class="pun">.</span><span class="pln">max_row </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pun">):</span><span class="pln">
            rowData </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">    </span><span class="com"># إلحاق كل خلية بهذه القائمة</span><span class="pln">
            </span><span class="com"># المرور ضمن حلقة على كل خلية في الصف</span><span class="pln">
            </span><span class="kwd">for</span><span class="pln"> colNum </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> sheet</span><span class="pun">.</span><span class="pln">max_column </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pun">):</span><span class="pln">
                </span><span class="com"># إلحاق بيانات كل خلية بالقائمة‫ rowData</span><span class="pln">

            </span><span class="com"># ‫كتابة القائمة rowData في ملف CSV</span><span class="pln">

        csvFile</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span></pre>

<p>
	نزّل الملف المضغوط <a href="https://nostarch.com/automatestuff2/" rel="external nofollow">excelSpreadsheets.zip</a> وفك ضغط جداول البيانات في المجلد نفسه الموجود فيه برنامجك، حيث يمكنك استخدام جداول البيانات هذه كملفات لاختبار البرنامج عليها.
</p>

<h2 id="-5">
	الخلاصة
</h2>

<p>
	تُعد CSV و JSON من تنسيقات النصوص العادية الشائعة لتخزين البيانات، حيث يسهُل على البرامج تحليلها مع بقاء قابليتها للقراءة، لذا تُستخدَم لجداول البيانات أو بيانات تطبيقات الويب البسيطة. تبسّط وحدتا <code>csv</code> و <code>json</code> عملية القراءة والكتابة في ملفات CSV و JSON بصورة كبيرة.
</p>

<p>
	تعلّمنا سابقًا كيفية استخدام بايثون لتحليل معلومات مجموعة واسعة من تنسيقات الملفات، فإحدى المهام الشائعة هي أخذ البيانات من مجموعة متنوعة من التنسيقات وتحليلها للحصول على المعلومات المُحدَّدة التي تحتاجها، وتكون هذه المهام مُحدَّدة لدرجة أن البرمجيات التجارية لن تفيدك في إنجازها على النحو الأمثل، لذا يمكنك جعل حاسوبك يتعامل مع كميات كبيرة من البيانات المُقدَّمة بهذه التنسيقات من خلال كتابة السكربتات الخاصة بك.
</p>

<p>
	سنبتعد في المقالات اللاحقة عن تنسيقات البيانات وسنتعلّم كيفية جعل برامجك يتواصل معك من خلال إرسال رسائل البريد الإلكتروني والرسائل النصية.
</p>

<p>
	ترجمة -وبتصرُّف- للمقال <a href="https://automatetheboringstuff.com/2e/chapter16/" rel="external nofollow">Working with CSV files and JSON data</a> لصاحبه Al Sweigart.
</p>

<h2 id="-6">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D9%85%D8%B3%D8%AA%D9%86%D8%AF%D8%A7%D8%AA-pdf-%D9%88%D9%85%D8%B3%D8%AA%D9%86%D8%AF%D8%A7%D8%AA-word-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2387/" rel="">العمل مع مستندات PDF ومستندات Word باستخدام بايثون</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%AC%D8%AF%D8%A7%D9%88%D9%84-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%AC%D9%88%D8%AC%D9%84-google-sheets-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2386/" rel="">العمل مع جداول بيانات جوجل Google Sheets باستخدام لغة بايثون</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D9%81%D9%8A-%D9%85%D8%B3%D8%AA%D9%86%D8%AF%D8%A7%D8%AA-%D8%A5%D9%83%D8%B3%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-python-r2375/" rel="">الكتابة في مستندات إكسل باستخدام لغة بايثون Python</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D9%82%D8%B1%D8%A7%D8%A1%D8%A9-%D9%85%D8%B3%D8%AA%D9%86%D8%AF%D8%A7%D8%AA-%D8%AC%D8%AF%D8%A7%D9%88%D9%84-%D8%A5%D9%83%D8%B3%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-python-r2374/" rel="">قراءة مستندات جداول إكسل باستخدام لغة بايثون Python</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/apps/productivity/google-drive/google-docs/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%85%D8%B3%D8%AA%D9%86%D8%AF%D8%A7%D8%AA-%D8%AC%D9%88%D8%AC%D9%84-google-docs-r281/" rel="">مقدمة إلى تطبيق مستندات جوجل Google Docs</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2388</guid><pubDate>Mon, 12 Aug 2024 15:00:00 +0000</pubDate></item><item><title>&#x627;&#x644;&#x639;&#x645;&#x644; &#x645;&#x639; &#x645;&#x633;&#x62A;&#x646;&#x62F;&#x627;&#x62A; PDF &#x648;&#x645;&#x633;&#x62A;&#x646;&#x62F;&#x627;&#x62A; Word &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646;</title><link>https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D9%85%D8%B3%D8%AA%D9%86%D8%AF%D8%A7%D8%AA-pdf-%D9%88%D9%85%D8%B3%D8%AA%D9%86%D8%AF%D8%A7%D8%AA-word-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2387/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_08/------PDF-----.png.1a3e5d82e3d02e5561c739fc4ee5ec07.png" /></p>
<p>
	تُعَد مستندات بي دي إف PDF ومستندات وورد Word ملفات ثنائية، مما يجعلها أكثر تعقيدًا من الملفات النصية العادية، إذ تخزِّن الكثير من المعلومات المتعلقة بالخطوط والألوان وتخطيط الصفحات بالإضافة إلى النصوص. إذا أدرتَ أن تقرأ برامجك أو تكتبها في ملفات PDF أو مستندات وورد، فستحتاج إلى تطبيق أكثر من مجرد تمرير أسماء الملفات إلى الدالة <code>open()‎</code>، لذا توجد وحدات بايثون Python التي تسهّل عليك التفاعل مع ملفات PDF ومستندات وورد مثل الوحدتين PyPDF2 و Python-Docx اللتين سنوضّحهما في هذا المقال.
</p>

<h2 id="pdf">
	مستندات PDF
</h2>

<p>
	يرمز الاختصار PDF إلى صيغة المستندات المنقولة Portable Document Format التي تستخدم امتداد الملف <code>‎.pdf</code>. تدعم ملفات PDF العديد من الميزات، ولكن سنركز في هذا المقال على المهمتين اللتين ستفعلهما باستخدام هذه الملفات في أغلب الأحيان وهما: قراءة محتوى النصوص من ملفات PDF وإنشاء ملفات PDF جديدة من مستندات موجودة مسبقًا.
</p>

<p>
	سنستخدم الوحدة PyPDF2 ذات الإصدار 1.26.0 للعمل مع ملفات PDF، لذا من المهم أن تثبّت هذا الإصدار لأن الإصدارات اللاحقة من وحدة PyPDF2 قد تكون غير متوافقة مع شيفرتنا البرمجية، إذًا شغّل الأمر <code>pip install --user PyPDF2==1.26.0</code> من سطر الأوامر لتثبيت هذه الوحدة، ولاحظ أن اسم الوحدة حساس لحالة الحروف، لذا تأكد من أن الحرف y صغير والحروف الأخرى كبيرة. اتبع الإرشادات الخاصة بتثبيت الوحدات الخارجية التي سنوضّحها في مقال لاحق من هذه السلسلة. إذا جرى تثبيت هذه الوحدة بصورة صحيحة، فيُفترض ألّا يؤدي تشغيل الأمر <code>import PyPDF2</code> في الصدفة التفاعلية Interactive Shell إلى عرض أيّ أخطاء.
</p>

<p>
	<strong>ملاحظة</strong>: تُعَد ملفات PDF رائعة لتجهيز النصوص بحيث تسهل طباعتها وقراءتها، ولكن ليس من السهل على البرمجيات تحليلها إلى نصوص عادية، لذلك قد ترتكب الوحدة PyPDF2 أخطاءً عند استخراج النص من ملف PDF، وقد لا تتمكّن من فتح بعض ملفات PDF، ولا يوجد الكثير لفعله لحل هذه المشكلة، إذ قد تكون وحدة PyPDF2 ببساطة غير قادرة على العمل مع بعض ملفات PDF، ولكننا لم نجد بعد أيّ ملفات PDF لا يمكن فتحها باستخدام وحدة PyPDF2.
</p>

<h3 id="pdf-1">
	استخراج النص من ملفات PDF
</h3>

<p>
	لا تمتلك وحدة PyPDF2 طريقةً لاستخراج الصور أو المخططات أو الوسائط الأخرى من مستندات PDF، ولكن يمكنها استخراج النص وإعادته بوصفه سلسلة بايثون. سنستخدم في مثالنا مستند PDF الموضّح في الشكل التالي للبدء في تعلم كيفية عمل وحدة PyPDF2:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="155645" href="https://academy.hsoub.com/uploads/monthly_2024_08/01_000002.jpg.667e74825095c50e73b362be4e8bce56.jpg" rel=""><img alt="01 000002" class="ipsImage ipsImage_thumbnailed" data-fileid="155645" data-unique="7ui68ubtc" src="https://academy.hsoub.com/uploads/monthly_2024_08/01_000002.jpg.667e74825095c50e73b362be4e8bce56.jpg"> </a>
</p>

<p style="text-align: center;">
	صفحة PDF التي سنستخرج النص منها
</p>

<p>
	نزّل <a href="https://nostarch.com/automatestuff2/" rel="external nofollow">ملف PDF</a> الموضّح في الشكل السابق وأدخِل ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8906_8" style=""><span class="pln">   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">PyPDF2</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdfFileObj </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="str">'meetingminutes.pdf'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'rb'</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdfReader </span><span class="pun">=</span><span class="pln"> </span><span class="typ">PyPDF2</span><span class="pun">.</span><span class="typ">PdfFileReader</span><span class="pun">(</span><span class="pln">pdfFileObj</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➊</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdfReader</span><span class="pun">.</span><span class="pln">numPages
   </span><span class="lit">19</span><span class="pln">
</span><span class="pun">➋</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pageObj </span><span class="pun">=</span><span class="pln"> pdfReader</span><span class="pun">.</span><span class="pln">getPage</span><span class="pun">(</span><span class="lit">0</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➌</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pageObj</span><span class="pun">.</span><span class="pln">extractText</span><span class="pun">()</span><span class="pln">
   </span><span class="str">'OOFFFFIICCIIAALL  BBOOAARRDD  MMIINNUUTTEESS   Meeting of March 7,
   2015        \n     The Board of Elementary and Secondary Education shall
   provide leadership and create policies for education that expand opportunities
   for children, empower families and communities, and advance Louisiana in an
   increasingly competitive global market. BOARD  of ELEMENTARY and  SECONDARY
   EDUCATION  '</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdfFileObj</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span></pre>

<p>
	نستورد أولًا وحدة PyPDF2، ثم نفتح الملف meetingminutes.pdf للقراءة في الوضع الثنائي ونخزّنه في المتغير <code>pdfFileObj</code>. يمكن الحصول على كائن <code>PdfFileReader</code> الذي يمثل ملف PDF من خلال استدعاء الدالة <code>PyPDF2.PdfFileReader()‎</code> وتمرير المتغير <code>pdfFileObj</code> إليها، ثم خزّن هذا الكائن في المتغير <code>pdfReader</code>.
</p>

<p>
	يُخزَّن إجمالي عدد صفحات المستند في السمة Attribute التي هي <code>numPages</code> الخاصة بالكائن <code>PdfFileReader</code> ➊، ويحتوي ملف PDF في مثالنا على 19 صفحة، ولكن نريد استخراج النص من الصفحة الأولى فقط من خلال الحصول على كائن <code>Page</code> من كائن <code>PdfFileReader</code>، حيث يمثل كائن <code>Page</code> صفحة واحدة من ملف PDF. يمكنك الحصول على كائن <code>Page</code> من خلال استدعاء التابع <code>getPage()‎</code> ➋ الخاص بكائن <code>PdfFileReader</code> وتمرير رقم الصفحة التي تريدها إليه، ورقم الصفحة هو 0 في مثالنا.
</p>

<p>
	تستخدم الوحدة PyPDF2 فهرسًا مستنِدًا إلى القيمة 0 للحصول على الصفحات، فالصفحة الأولى هي الصفحة 0، والصفحة الثانية هي الصفحة 1 وإلخ، إذ تُستخدَم هذه الطريقة دائمًا حتى لو كانت الصفحات مرقَّمة بطريقة مختلفة في المستند. لنفترض مثلًا أن لديك ملف PDF يمثّل مقطعًا من تقرير أطول، ويتألف هذا المقطع من ثلاث صفحات، وأرقام الصفحات هي 42 و 43 و 44. يمكن الحصول على الصفحة الأولى من هذا المستند من خلال استدعاء التابع <code>pdfReader.getPage(0)‎</code> وليس من خلال استدعاء <code>getPage(42)‎</code> أو <code>getPage(1)‎</code>.
</p>

<p>
	نحصل على كائن <code>Page</code>، ثم نستدعي التابع <code>extractText()‎</code> الخاص بهذا الكائن لإعادة سلسلة نصية تمثل النص الموجود في الصفحة ➌. لاحظ أن استخراج النص ليس مثاليًا، فالنص "Charles E. “Chas” Roemer, President" من ملف PDF غير موجود في السلسلة النصية التي يعيدها التابع <code>extractText()‎</code>، وتكون المسافات غير مفعّلة في بعض الأحيان، ولكن قد يكون هذا المحتوى التقريبي لنص ملف PDF كافيًا لبرنامجك.
</p>

<h3 id="pdf-2">
	فك تشفير ملفات PDF
</h3>

<p>
	تحتوي بعض مستندات PDF على ميزة تشفير تمنع قراءتها حتى يضع الشخص الذي يفتح المستند كلمة المرور. لندخِل ما يلي في الصدفة التفاعلية مع ملف PDF الذي نزلته، وهذا الملف مُشفَّر باستخدام كلمة المرور <code>rosebud</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8906_10" style=""><span class="pln">   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">PyPDF2</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdfReader </span><span class="pun">=</span><span class="pln"> </span><span class="typ">PyPDF2</span><span class="pun">.</span><span class="typ">PdfFileReader</span><span class="pun">(</span><span class="pln">open</span><span class="pun">(</span><span class="str">'encrypted.pdf'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'rb'</span><span class="pun">))</span><span class="pln">
</span><span class="pun">➊</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdfReader</span><span class="pun">.</span><span class="pln">isEncrypted
   </span><span class="kwd">True</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdfReader</span><span class="pun">.</span><span class="pln">getPage</span><span class="pun">(</span><span class="lit">0</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➋</span><span class="pln"> </span><span class="typ">Traceback</span><span class="pln"> </span><span class="pun">(</span><span class="pln">most recent call last</span><span class="pun">):</span><span class="pln">
     </span><span class="typ">File</span><span class="pln"> </span><span class="str">"&lt;pyshell#173&gt;"</span><span class="pun">,</span><span class="pln"> line </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">module</span><span class="pun">&gt;</span><span class="pln">
       pdfReader</span><span class="pun">.</span><span class="pln">getPage</span><span class="pun">()</span><span class="pln">
     </span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">
     </span><span class="typ">File</span><span class="pln"> </span><span class="str">"C:\Python34\lib\site-packages\PyPDF2\pdf.py"</span><span class="pun">,</span><span class="pln"> line </span><span class="lit">1173</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> getObject
       </span><span class="kwd">raise</span><span class="pln"> utils</span><span class="pun">.</span><span class="typ">PdfReadError</span><span class="pun">(</span><span class="str">"file has not been decrypted"</span><span class="pun">)</span><span class="pln">
   </span><span class="typ">PyPDF2</span><span class="pun">.</span><span class="pln">utils</span><span class="pun">.</span><span class="typ">PdfReadError</span><span class="pun">:</span><span class="pln"> file has </span><span class="kwd">not</span><span class="pln"> been decrypted
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdfReader </span><span class="pun">=</span><span class="pln"> </span><span class="typ">PyPDF2</span><span class="pun">.</span><span class="typ">PdfFileReader</span><span class="pun">(</span><span class="pln">open</span><span class="pun">(</span><span class="str">'encrypted.pdf'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'rb'</span><span class="pun">))</span><span class="pln">
</span><span class="pun">➌</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdfReader</span><span class="pun">.</span><span class="pln">decrypt</span><span class="pun">(</span><span class="str">'rosebud'</span><span class="pun">)</span><span class="pln">
   </span><span class="lit">1</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pageObj </span><span class="pun">=</span><span class="pln"> pdfReader</span><span class="pun">.</span><span class="pln">getPage</span><span class="pun">(</span><span class="lit">0</span><span class="pun">)</span></pre>

<p>
	تحتوي جميع كائنات <code>PdfFileReader</code> على السمة <code>isEncrypted</code> التي تكون قيمتها <code>True</code> إذا كان ملف PDF مُشفرًا، وتكون قيمتها <code>False</code> إن لم يكن ملف PDF مُشفَّرًا ➊، وستؤدي أيّ محاولة لاستدعاء دالةٍ تقرأ الملف قبل فك تشفيره باستخدام كلمة المرور الصحيحة إلى حدوث خطأ ➋.
</p>

<p>
	<strong>ملاحظة</strong>: يوجد خطأٌ في الإصدار 1.26.0 من وحدة PyPDF2، حيث يؤدي استدعاء التابع <code>getPage()‎</code> لملف PDF مشفَّر قبل استدعاء الدالة <code>decrypt()‎</code> لهذا الملف إلى فشل استدعاءات التابع <code>getPage()‎</code> المستقبلية مع ظهور الخطأ <code>IndexError: list index out of range</code>، ولذلك أعاد المثال السابق فتح الملف باستخدام كائن <code>PdfFileReader</code> جديد.
</p>

<p>
	يمكن قراءة ملف PDF مشفَّر من خلال استدعاء الدالة <code>decrypt()‎</code> وتمرير كلمة المرور بوصفها سلسلة نصية إليه ➌، وسترى أن استدعاء التابع <code>getPage()‎</code> لم يعُد يسبّب خطأً بعد استدعاء الدالة <code>decrypt()‎</code> مع كلمة المرور الصحيحة، بينما إذا أعطيتَ كلمة مرور خطأ، فستعيد الدالة <code>decrypt()‎</code> القيمة 0 وسيفشل التابع <code>getPage()‎</code>. لاحظ أن التابع <code>decrypt()‎</code> يفك تشفير الكائن <code>PdfFileReader</code> فقط وليس ملف PDF الفعلي، إذ يبقى الملف الموجود على قرص حاسوبك الصلب مشفَّرًا بعد انتهاء البرنامج، وبالتالي يجب أن يستدعي برنامجُك التابعَ <code>decrypt()‎</code> عند تشغيله مرة أخرى.
</p>

<h3 id="pdf-3">
	إنشاء ملفات PDF
</h3>

<p>
	يمكن للكائن <code>PdfFileWriter</code> الخاص بالوحدة PyPDF2 إنشاء ملفات PDF جديدة، ولكن لا تستطيع وحدة PyPDF2 كتابة نص عشوائي في ملف PDF كما تفعل شيفرة بايثون مع ملفات النصوص العادية، لذا تقتصر إمكانات كتابة ملفات PDF في وحدة PyPDF2 على نسخ الصفحات من ملفات PDF الأخرى وتدوير الصفحات ودمجها وتشفير الملفات.
</p>

<p>
	لا تسمح وحدة PyPDF2 بتعديل ملف PDF مباشرةً، لذا يجب إنشاء ملف PDF جديد ثم نسخ المحتوى من مستند موجود مسبقًا. ستتبع الأمثلة الواردة في هذا القسم النهج العام التالي:
</p>

<ol>
	<li>
		فتح ملفٍ أو أكثر من ملفات PDF الموجودة مسبقًا (ملفات PDF المصدر) في كائنات <code>PdfFileReader</code>.
	</li>
	<li>
		إنشاء كائن <code>PdfFileWriter</code> جديد.
	</li>
	<li>
		نسخ الصفحات من كائنات <code>PdfFileReader</code> إلى كائن <code>PdfFileWriter</code>.
	</li>
	<li>
		استخدام كائن <code>PdfFileWriter</code> لكتابة ملف PDF الناتج.
	</li>
</ol>

<p>
	يؤدي إنشاء كائن <code>PdfFileWriter</code> إلى إنشاء قيمةٍ تمثّل مستند PDF في شيفرة بايثون فقط دون إنشاء ملف PDF الفعلي، ولذلك يجب استدعاء التابع <code>write()‎</code> الخاص بهذا الكائن. يأخذ هذا التابع كائن <code>File</code> عادي مفتوح في وضع الكتابة الثنائي، حيث يمكنك الحصول على كائن <code>File</code> من خلال استدعاء الدالة <code>open()‎</code> الخاصة بلغة بايثون مع وسيطين هما: السلسلة النصية التي تريد أن تمثّل اسم ملف PDF والوسيط <code>'wb'</code> الذي يشير إلى أنه يجب فتح الملف في وضع الكتابة الثنائي. لا تقلق إذا كان ذلك مربكًا بعض الشيء، حيث سنرى كيفية ذلك في الأمثلة البرمجية التالية.
</p>

<h3 id="">
	نسخ الصفحات
</h3>

<p>
	يمكنك استخدام وحدة PyPDF2 لنسخ الصفحات من مستند PDF إلى آخر، مما يتيح لك دمج ملفات PDF متعددة أو قص الصفحات غير المرغوب فيها أو إعادة ترتيب الصفحات.
</p>

<p>
	<a href="https://nostarch.com/automatestuff2/" rel="external nofollow">نزّل</a> الملفين meetingminutes.pdf و meetingminutes2.pdf وضعهما في مجلد العمل الحالي، ثم أدخِل ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8906_12" style=""><span class="pln">   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">PyPDF2</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdf1File </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="str">'meetingminutes.pdf'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'rb'</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdf2File </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="str">'meetingminutes2.pdf'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'rb'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➊</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdf1Reader </span><span class="pun">=</span><span class="pln"> </span><span class="typ">PyPDF2</span><span class="pun">.</span><span class="typ">PdfFileReader</span><span class="pun">(</span><span class="pln">pdf1File</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➋</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdf2Reader </span><span class="pun">=</span><span class="pln"> </span><span class="typ">PyPDF2</span><span class="pun">.</span><span class="typ">PdfFileReader</span><span class="pun">(</span><span class="pln">pdf2File</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➌</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdfWriter </span><span class="pun">=</span><span class="pln"> </span><span class="typ">PyPDF2</span><span class="pun">.</span><span class="typ">PdfFileWriter</span><span class="pun">()</span><span class="pln">

   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> pageNum </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="pln">pdf1Reader</span><span class="pun">.</span><span class="pln">numPages</span><span class="pun">):</span><span class="pln">
         </span><span class="pun">➍</span><span class="pln"> pageObj </span><span class="pun">=</span><span class="pln"> pdf1Reader</span><span class="pun">.</span><span class="pln">getPage</span><span class="pun">(</span><span class="pln">pageNum</span><span class="pun">)</span><span class="pln">
         </span><span class="pun">➎</span><span class="pln"> pdfWriter</span><span class="pun">.</span><span class="pln">addPage</span><span class="pun">(</span><span class="pln">pageObj</span><span class="pun">)</span><span class="pln">

   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> pageNum </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="pln">pdf2Reader</span><span class="pun">.</span><span class="pln">numPages</span><span class="pun">):</span><span class="pln">
         </span><span class="pun">➍</span><span class="pln"> pageObj </span><span class="pun">=</span><span class="pln"> pdf2Reader</span><span class="pun">.</span><span class="pln">getPage</span><span class="pun">(</span><span class="pln">pageNum</span><span class="pun">)</span><span class="pln">
         </span><span class="pun">➎</span><span class="pln"> pdfWriter</span><span class="pun">.</span><span class="pln">addPage</span><span class="pun">(</span><span class="pln">pageObj</span><span class="pun">)</span><span class="pln">

</span><span class="pun">➏</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdfOutputFile </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="str">'combinedminutes.pdf'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'wb'</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdfWriter</span><span class="pun">.</span><span class="pln">write</span><span class="pun">(</span><span class="pln">pdfOutputFile</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdfOutputFile</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdf1File</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdf2File</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span></pre>

<p>
	افتح ملفي PDF في وضع القراءة الثنائي وخزّن كائني <code>File</code> الناتجين في المتغيرين <code>pdf1File</code> و <code>pdf2File</code>، ثم استدعِ الدالة <code>PyPDF2.PdfFileReader()‎</code> ومرّر المتغير <code>pdf1File</code> إليها للحصول على كائن <code>PdfFileReader</code> للملف meetingminutes.pdf ➊، ثم استدعيها مرةً أخرى ومرّر المتغير <code>pdf2File</code> إليها للحصول على كائن <code>PdfFileReader</code> للملف meetingminutes2.pdf ➋، ثم أنشئ كائن <code>PdfFileWriter</code> جديد، والذي يمثل مستند PDF فارغ ➌.
</p>

<p>
	انسخ بعد ذلك جميع الصفحات من ملفي PDF المصدر وأضِفها إلى كائن <code>PdfFileWriter</code>، واحصل على كائن <code>Page</code> من خلال استدعاء التابع <code>getPage()‎</code> لكائن <code>PdfFileReader</code> ➍، ثم مرّر كائن <code>Page</code> إلى التابع <code>addPage()‎</code> الخاص بالكائن <code>PdfFileReader</code> ➎. نفّذ هذه الخطوات أولًا للمتغير <code>pdf1Reader</code> ثم للمتغير <code>pdf2Reader</code> مرة أخرى، ثم اكتب ملف PDF جديد اسمه combinedminutes.pdf عند الانتهاء من نسخ الصفحات من خلال تمرير كائن <code>File</code> إلى التابع <code>write()‎</code> الخاص بالكائن <code>PdfFileWriter</code> ➏.
</p>

<p>
	<strong>ملاحظة</strong>: لا يمكن لوحدة PyPDF2 إدراج صفحات في منتصف كائن <code>PdfFileWriter</code>، إذ يضيف التابع <code>addPage()‎</code> الصفحات إلى نهاية الملف فقط.
</p>

<p>
	أنشأنا ملف PDF جديد يدمج صفحاتٍ من الملفين meetingminutes.pdf وmeetingminutes2.pdf في مستند واحد. تذكّر أنه يجب فتح كائن <code>File</code> الذي مرّرناه إلى الدالة <code>PyPDF2.PdfFileReader()‎</code> في وضع القراءة الثنائي من خلال تمرير الوسيط <code>'rb'</code> بوصفه وسيطًا ثانيًا للدالة <code>open()‎</code>، ويجب فتح كائن <code>File</code> الذي مرّرناه إلى الدالة <code>PyPDF2.PdfFileReader()‎</code> في وضع الكتابة الثنائي باستخدام الوسيط <code>'wb'</code>.
</p>

<h3 id="-1">
	تدوير الصفحات
</h3>

<p>
	يمكن تدوير صفحات ملف PDF بمقدار مضاعفات 90 درجة باستخدام التوابع <code>rotateClockwise()‎</code> و <code>rotateCounterClockwise()‎</code>. لنمرّر أحد الأعداد الصحيحة 90 أو 180 أو 270 إلى هذه التوابع، ولندخِل ما يلي في الصدفة التفاعلية مع ملف meetingminutes.pdf الموجود في مجلد العمل الحالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8906_14" style=""><span class="pln">   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">PyPDF2</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> minutesFile </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="str">'meetingminutes.pdf'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'rb'</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdfReader </span><span class="pun">=</span><span class="pln"> </span><span class="typ">PyPDF2</span><span class="pun">.</span><span class="typ">PdfFileReader</span><span class="pun">(</span><span class="pln">minutesFile</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➊</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> page </span><span class="pun">=</span><span class="pln"> pdfReader</span><span class="pun">.</span><span class="pln">getPage</span><span class="pun">(</span><span class="lit">0</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➋</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> page</span><span class="pun">.</span><span class="pln">rotateClockwise</span><span class="pun">(</span><span class="lit">90</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">{</span><span class="str">'/Contents'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="typ">IndirectObject</span><span class="pun">(</span><span class="lit">961</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">),</span><span class="pln"> </span><span class="typ">IndirectObject</span><span class="pun">(</span><span class="lit">962</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">),</span><span class="pln">
   </span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">
   </span><span class="pun">}</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdfWriter </span><span class="pun">=</span><span class="pln"> </span><span class="typ">PyPDF2</span><span class="pun">.</span><span class="typ">PdfFileWriter</span><span class="pun">()</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdfWriter</span><span class="pun">.</span><span class="pln">addPage</span><span class="pun">(</span><span class="pln">page</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➌</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> resultPdfFile </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="str">'rotatedPage.pdf'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'wb'</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdfWriter</span><span class="pun">.</span><span class="pln">write</span><span class="pun">(</span><span class="pln">resultPdfFile</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> resultPdfFile</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> minutesFile</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span></pre>

<p>
	استخدمنا التابع <code>getPage(0)‎</code> لتحديد الصفحة الأولى من ملف PDF ➊، ثم استدعينا التابع <code>rotateClockwise(90)‎</code> لتلك الصفحة ➋، ثم كتبنا ملف PDF جديد مع الصفحة التي دوّرناها وحفظناه بالاسم rotatedPage.pdf ➌.
</p>

<p>
	سيحتوي ملف PDF الناتج على صفحة واحدة مع تدويرها بمقدار 90 درجة باتجاه عقارب الساعة كما هو موضّح في الشكل التالي، وتحتوي القيم المُعادة من التابعين <code>rotateClockwise()‎</code> و <code>rotateCounterClockwise()‎</code> على الكثير من المعلومات التي يمكنك تجاهلها.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="155651" href="https://academy.hsoub.com/uploads/monthly_2024_08/02_000098.jpg.a88816ec3cad241ca79bf0c5787fbe71.jpg" rel=""><img alt="02 000098" class="ipsImage ipsImage_thumbnailed" data-fileid="155651" data-unique="6j4m1rolu" src="https://academy.hsoub.com/uploads/monthly_2024_08/02_000098.jpg.a88816ec3cad241ca79bf0c5787fbe71.jpg"> </a>
</p>

<p style="text-align: center;">
	ملف rotatedPage.pdf مع تدوير الصفحة بمقدار 90 درجة باتجاه عقارب الساعة
</p>

<h3 id="-2">
	دمج الصفحات
</h3>

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

<p>
	نزّل الملف <a href="https://nostarch.com/automatestuff2/" rel="external nofollow">watermark.pdf</a>، ثم ضعه في مجلد العمل الحالي مع الملف meetingminutes.pdf، ثم أدخِل ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8906_16" style=""><span class="pln">   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">PyPDF2</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> minutesFile </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="str">'meetingminutes.pdf'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'rb'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➊</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdfReader </span><span class="pun">=</span><span class="pln"> </span><span class="typ">PyPDF2</span><span class="pun">.</span><span class="typ">PdfFileReader</span><span class="pun">(</span><span class="pln">minutesFile</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➋</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> minutesFirstPage </span><span class="pun">=</span><span class="pln"> pdfReader</span><span class="pun">.</span><span class="pln">getPage</span><span class="pun">(</span><span class="lit">0</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➌</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdfWatermarkReader </span><span class="pun">=</span><span class="pln"> </span><span class="typ">PyPDF2</span><span class="pun">.</span><span class="typ">PdfFileReader</span><span class="pun">(</span><span class="pln">open</span><span class="pun">(</span><span class="str">'watermark.pdf'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'rb'</span><span class="pun">))</span><span class="pln">
</span><span class="pun">➍</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> minutesFirstPage</span><span class="pun">.</span><span class="pln">mergePage</span><span class="pun">(</span><span class="pln">pdfWatermarkReader</span><span class="pun">.</span><span class="pln">getPage</span><span class="pun">(</span><span class="lit">0</span><span class="pun">))</span><span class="pln">
</span><span class="pun">➎</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdfWriter </span><span class="pun">=</span><span class="pln"> </span><span class="typ">PyPDF2</span><span class="pun">.</span><span class="typ">PdfFileWriter</span><span class="pun">()</span><span class="pln">
</span><span class="pun">➏</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdfWriter</span><span class="pun">.</span><span class="pln">addPage</span><span class="pun">(</span><span class="pln">minutesFirstPage</span><span class="pun">)</span><span class="pln">

</span><span class="pun">➐</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> pageNum </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> pdfReader</span><span class="pun">.</span><span class="pln">numPages</span><span class="pun">):</span><span class="pln">
           pageObj </span><span class="pun">=</span><span class="pln"> pdfReader</span><span class="pun">.</span><span class="pln">getPage</span><span class="pun">(</span><span class="pln">pageNum</span><span class="pun">)</span><span class="pln">
           pdfWriter</span><span class="pun">.</span><span class="pln">addPage</span><span class="pun">(</span><span class="pln">pageObj</span><span class="pun">)</span><span class="pln">

   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> resultPdfFile </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="str">'watermarkedCover.pdf'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'wb'</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdfWriter</span><span class="pun">.</span><span class="pln">write</span><span class="pun">(</span><span class="pln">resultPdfFile</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> minutesFile</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> resultPdfFile</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span></pre>

<p>
	أنشأنا في المثال السابق كائن <code>PdfFileReader</code> للملف meetingminutes.pdf ➊، واستدعينا التابع <code>getPage(0)‎</code> للحصول على كائن <code>Page</code> للصفحة الأولى وتخزين هذا الكائن في المتغير <code>minutesFirstPage</code> ➋. أنشأنا بعد ذلك كائن <code>PdfFileReader</code> للملف watermark.pdf ➌، واستدعينا التابع <code>mergePage()‎</code> للمتغير <code>minutesFirstPage</code> ➍، فالوسيط الذي نمرّره إلى التابع <code>mergePage()‎</code> هو كائن <code>Page</code> للصفحة الأولى من الملف watermark.pdf.
</p>

<p>
	استدعينا التابع <code>mergePage()‎</code> للمتغير <code>minutesFirstPage</code>، وبالتالي أصبح هذا المتغير يمثّل الصفحة الأولى التي وضعنا عليها علامة مائية، ثم أنشأنا كائن <code>PdfFileWriter</code> ➎ وأضفنا الصفحة الأولى التي وضعنا عليها علامة مائية ➏، ثم مررنا بحلقة على بقية الصفحات الموجودة في الملف meetingminutes.pdf، وأضفناها إلى الكائن <code>PdfFileWriter</code> ➐. أخيرًا، فتحنا ملف PDF جديد اسمه watermarkedCover.pdf، وكتبنا محتويات الكائن <code>PdfFileWriter</code> فيه.
</p>

<p>
	يبين الشكل التالي النتائج، حيث يحتوي ملف PDF الجديد watermarkedCover.pdf على جميع محتويات الملف meetingminutes.pdf، وتحمل الصفحة الأولى فيه علامة مائية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="155652" href="https://academy.hsoub.com/uploads/monthly_2024_08/03_000044.jpg.db8f159d57e857535a8ad813b040f554.jpg" rel=""><img alt="03 000044" class="ipsImage ipsImage_thumbnailed" data-fileid="155652" data-unique="2xcjoouw1" src="https://academy.hsoub.com/uploads/monthly_2024_08/03_000044.jpg.db8f159d57e857535a8ad813b040f554.jpg"> </a>
</p>

<p style="text-align: center;">
	ملف PDF الأصلي (على اليسار) وملف PDF للعلامة مائية (في الوسط) وملف PDF لدمج الملفين (على اليمين)
</p>

<h3 id="pdf-4">
	تشفير ملفات PDF
</h3>

<p>
	يمكن لكائن <code>PdfFileWriter</code> أيضًا إضافة تشفيرٍ إلى مستند PDF. إذًا لندخِل ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8906_18" style=""><span class="pln">   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">PyPDF2</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdfFile </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="str">'meetingminutes.pdf'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'rb'</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdfReader </span><span class="pun">=</span><span class="pln"> </span><span class="typ">PyPDF2</span><span class="pun">.</span><span class="typ">PdfFileReader</span><span class="pun">(</span><span class="pln">pdfFile</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdfWriter </span><span class="pun">=</span><span class="pln"> </span><span class="typ">PyPDF2</span><span class="pun">.</span><span class="typ">PdfFileWriter</span><span class="pun">()</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> pageNum </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="pln">pdfReader</span><span class="pun">.</span><span class="pln">numPages</span><span class="pun">):</span><span class="pln">
           pdfWriter</span><span class="pun">.</span><span class="pln">addPage</span><span class="pun">(</span><span class="pln">pdfReader</span><span class="pun">.</span><span class="pln">getPage</span><span class="pun">(</span><span class="pln">pageNum</span><span class="pun">))</span><span class="pln">

</span><span class="pun">➊</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdfWriter</span><span class="pun">.</span><span class="pln">encrypt</span><span class="pun">(</span><span class="str">'swordfish'</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> resultPdf </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="str">'encryptedminutes.pdf'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'wb'</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> pdfWriter</span><span class="pun">.</span><span class="pln">write</span><span class="pun">(</span><span class="pln">resultPdf</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> resultPdf</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span></pre>

<p>
	استدعِ التابع <code>encrypt()‎</code> ومرّر إليه سلسلة كلمة المرور ➊ قبل استدعاء التابع <code>write()‎</code> للحفظ في الملف. يمكن أن تحتوي ملفات PDF على كلمة مرور المستخدم user password التي تسمح لك بعرض ملف PDF وكلمة مرور المالك owner password التي تسمح لك بضبط أذونات للطباعة والتعليق واستخراج النص وميزات أخرى، حيث تُعَد كلمة مرور المستخدم وكلمة مرور المالك الوسيطين الأول والثاني للتابع <code>encrypt()‎</code> على التوالي. إذا مرّرنا سلسلة نصية واحدة فقط كوسيط إلى التابع <code>encrypt()‎</code>، فسنستخدمها لكلمتي المرور.
</p>

<p>
	نسخنا في المثال السابق صفحات الملف meetingminutes.pdf إلى كائن <code>PdfFileWriter</code> الذي شفّرناه بكلمة المرور <code>swordfish</code>، وفتحنا ملف PDF جديد بالاسم encryptedminutes.pdf، وكتبنا محتويات الكائن <code>PdfFileWriter</code> في ملف PDF الجديد، ويجب إدخال كلمة المرور قبل التمكّن من عرض الملف encryptedminutes.pdf. قد ترغب في حذف الملف meetingminutes.pdf الأصلي غير المُشفَّر بعد التأكد من تشفير نسخته بصورة صحيحة.
</p>

<h2 id="pdf-5">
	تطبيق عملي: دمج صفحات مختارة من عدة ملفات PDF
</h2>

<p>
	لنفترض أن لديك مهمة مملة تتمثل في دمج عشرات من مستندات PDF في ملف PDF واحد، ويحتوي كل مستند على ورقة غلاف في الصفحة الأولى، ولكنك لا تريد تكرار ورقة الغلاف في النتيجة النهائية، إذ توجد الكثير من البرامج المجانية لدمج ملفات PDF، ولكن تدمج الكثير منها الملفات بأكملها مع بعضها البعض ببساطة. إذًا لنكتب برنامج بايثون لتخصيص الصفحات التي تريدها في ملف PDF الناتج عن دمج عدة مستندات PDF.
</p>

<p>
	إليك الخطوات العامة التي سيطبّقها برنامجك:
</p>

<ol>
	<li>
		البحث عن جميع ملفات PDF الموجودة في مجلد العمل الحالي.
	</li>
	<li>
		فرز أسماء الملفات بحيث تُضاف ملفات PDF بالترتيب.
	</li>
	<li>
		كتابة جميع الصفحات باستثناء الصفحة الأولى من كل ملف PDF في الملف الناتج.
	</li>
</ol>

<p>
	ولكن ستحتاج شيفرتك البرمجية إلى تطبيق الخطوات التالية من ناحية التنفيذ:
</p>

<ol>
	<li>
		استدعاء التابع <code>os.listdir()‎</code> للعثور على كافة الملفات الموجودة في مجلد العمل وإزالة الملفات التي ليست ملفات PDF.
	</li>
	<li>
		استدعاء تابع فرز القائمة <code>sort()‎</code> الخاصة بلغة بايثون لترتيب أسماء الملفات أبجديًا.
	</li>
	<li>
		إنشاء كائن <code>PdfFileWriter</code> لملف PDF الناتج.
	</li>
	<li>
		التكرار ضمن حلقة على كل ملف PDF لإنشاء كائن <code>PdfFileReader</code> له.
	</li>
	<li>
		التكرار ضمن حلقة على كل صفحة (ما عدا الصفحة الأولى) في كل ملف PDF.
	</li>
	<li>
		إضافة الصفحات إلى ملف PDF الناتج.
	</li>
	<li>
		كتابة ملف PDF الناتج في ملفٍ اسمه allminutes.pdf.
	</li>
</ol>

<p>
	افتح تبويبًا جديدًا لإنشاء ملفٍ جديد في محرّرك واحفظه بالاسم combinePdfs.py.
</p>

<h3 id="pdf-6">
	الخطوة الأولى: البحث عن جميع ملفات PDF
</h3>

<p>
	أولًا، يجب أن يحصل برنامجك على قائمةٍ بجميع الملفات التي لها الامتداد <code>‎.pdf</code> الموجودة في مجلد العمل الحالي وفرزها، لذا يجب أن تكون شيفرتك البرمجية تبدو كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8906_21" style=""><span class="pln">   </span><span class="com">#! python3</span><span class="pln">
   </span><span class="com"># combinePdfs.py - دمج جميع ملفات‫ PDF الموجودة في مجلد العمل الحالي في ملف PDF واحد</span><span class="pln">

</span><span class="pun">➊</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">PyPDF2</span><span class="pun">,</span><span class="pln"> os
   </span><span class="com"># ‫الحصول على جميع أسماء ملفات PDF</span><span class="pln">
   pdfFiles </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">
   </span><span class="kwd">for</span><span class="pln"> filename </span><span class="kwd">in</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">listdir</span><span class="pun">(</span><span class="str">'.'</span><span class="pun">):</span><span class="pln">
       </span><span class="kwd">if</span><span class="pln"> filename</span><span class="pun">.</span><span class="pln">endswith</span><span class="pun">(</span><span class="str">'.pdf'</span><span class="pun">):</span><span class="pln">
         </span><span class="pun">➋</span><span class="pln"> pdfFiles</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">filename</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➌</span><span class="pln"> pdfFiles</span><span class="pun">.</span><span class="pln">sort</span><span class="pun">(</span><span class="pln">key </span><span class="pun">=</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">lower</span><span class="pun">)</span><span class="pln">

</span><span class="pun">➍</span><span class="pln"> pdfWriter </span><span class="pun">=</span><span class="pln"> </span><span class="typ">PyPDF2</span><span class="pun">.</span><span class="typ">PdfFileWriter</span><span class="pun">()</span><span class="pln">

   </span><span class="com"># ‫التكرار ضمن حلقة على جميع ملفات PDF</span><span class="pln">

   </span><span class="com"># التكرار ضمن حلقة على جميع الصفحات (باستثناء الصفحة الأولى) وإضافتها</span><span class="pln">

   </span><span class="com"># ‫حفظ ملف PDF الناتج في ملف</span></pre>

<p>
	السطر الأول في الشيفرة البرمجية السابقة هو سطر Shebang (سطر يبدأ بالسلسلة النصية "‎#!‎")، والسطر الثاني هو التعليق الوصفي لما يفعله البرنامج، ثم تستورد الشيفرة البرمجية وحدات <code>os</code> و <code>PyPDF2</code> ➊. يعيد استدعاء التابع <code>os.listdir('.')‎</code> قائمةً بالملفات الموجودة في مجلد العمل الحالي، حيث تتكرر الشيفرة البرمجية ضمن حلقة على هذه القائمة وتضيف الملفات التي لها الامتداد <code>‎.pdf</code> فقط إلى القائمة <code>pdfFiles</code> ➋. تُفرَز بعد ذلك هذه القائمة وفق الترتيب الأبجدي باستخدام وسيط الكلمة المفتاحية Keyword Argument الذي هو <code>key = str.lower</code> الخاص بالتابع <code>sort()‎</code> ➌، ويُنشَأ كائن <code>PdfFileWriter</code> للاحتفاظ بصفحات PDF المدموجة ➍. أخيرًا، توجد بعض التعليقات التي توضّح ما تبقى من البرنامج.
</p>

<h3 id="pdf-7">
	الخطوة الثانية: فتح ملفات PDF
</h3>

<p>
	يجب الآن أن يقرأ البرنامج كلّ ملف PDF موجودٍ في القائمة <code>pdfFiles</code>، لذا أضِف ما يلي إلى برنامجك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8906_23" style=""><span class="com">#! python3</span><span class="pln">
</span><span class="com"># combinePdfs.py - دمج جميع ملفات‫ PDF الموجودة في مجلد العمل الحالي في ملف PDF واحد</span><span class="pln">

</span><span class="kwd">import</span><span class="pln"> </span><span class="typ">PyPDF2</span><span class="pun">,</span><span class="pln"> os

</span><span class="com">#  ‫الحصول على جميع أسماء ملفات PDF</span><span class="pln">
pdfFiles </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">
</span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">

</span><span class="com">#  ‫التكرار ضمن حلقة على جميع ملفات PDF</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> filename </span><span class="kwd">in</span><span class="pln"> pdfFiles</span><span class="pun">:</span><span class="pln">
    pdfFileObj </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="pln">filename</span><span class="pun">,</span><span class="pln"> </span><span class="str">'rb'</span><span class="pun">)</span><span class="pln">
    pdfReader </span><span class="pun">=</span><span class="pln"> </span><span class="typ">PyPDF2</span><span class="pun">.</span><span class="typ">PdfFileReader</span><span class="pun">(</span><span class="pln">pdfFileObj</span><span class="pun">)</span><span class="pln">
    </span><span class="com"># التكرار ضمن حلقة على جميع الصفحات (باستثناء الصفحة الأولى) وإضافتها</span><span class="pln">

</span><span class="com"># ‫حفظ ملف PDF الناتج في ملف</span></pre>

<p>
	تفتح الحلقة اسم ملف لكل ملف PDF في وضع القراءة الثنائي من خلال استدعاء الدالة <code>open()‎</code> مع الوسيط الثاني <code>'rb'</code>، حيث يعيد استدعاء الدالة <code>open()‎</code> كائن <code>File</code> المُمرَّر إلى الدالة <code>PyPDF2.PdfFileReader()‎</code> لإنشاء كائن <code>PdfFileReader</code> لملف PDF.
</p>

<h3 id="-3">
	الخطوة الثالثة: إضافة الصفحات
</h3>

<p>
	يجب التكرار ضمن حلقة على كل صفحة من كل ملف PDF باستثناء الصفحة الأولى. إذًا أضِف الشيفرة البرمجية التالية إلى برنامجك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8906_25" style=""><span class="com">#! python3</span><span class="pln">
</span><span class="com"># combinePdfs.py - دمج جميع ملفات‫ PDF الموجودة في مجلد العمل الحالي في ملف PDF واحد</span><span class="pln">

</span><span class="kwd">import</span><span class="pln"> </span><span class="typ">PyPDF2</span><span class="pun">,</span><span class="pln"> os

</span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">

</span><span class="com"># ‫التكرار ضمن حلقة على جميع ملفات PDF</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> filename </span><span class="kwd">in</span><span class="pln"> pdfFiles</span><span class="pun">:</span><span class="pln">
</span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">
     </span><span class="com"># التكرار ضمن حلقة على جميع الصفحات (باستثناء الصفحة الأولى) وإضافتها</span><span class="pln">
  </span><span class="pun">➊</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> pageNum </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> pdfReader</span><span class="pun">.</span><span class="pln">numPages</span><span class="pun">):</span><span class="pln">
         pageObj </span><span class="pun">=</span><span class="pln"> pdfReader</span><span class="pun">.</span><span class="pln">getPage</span><span class="pun">(</span><span class="pln">pageNum</span><span class="pun">)</span><span class="pln">
         pdfWriter</span><span class="pun">.</span><span class="pln">addPage</span><span class="pun">(</span><span class="pln">pageObj</span><span class="pun">)</span><span class="pln">

</span><span class="com"># ‫حفظ ملف PDF الناتج في ملف</span></pre>

<p>
	تنسخ الشيفرة البرمجية الموجودة داخل حلقة <code>for</code> كل كائن <code>Page</code> إلى كائن <code>PdfFileWriter</code>، ولكن تذكّر أنك تريد تخطي الصفحة الأولى. يجب أن تبدأ حلقتك من القيمة 1 ➊ لأن وحدة PyPDF2 تَعُدّ القيمة 0 هي الصفحة الأولى، ثم تصل إلى العدد الصحيح الموجود في <code>pdfReader.numPages</code> دون تضمينه في الحلقة.
</p>

<h3 id="-4">
	الخطوة الرابعة: حفظ النتائج
</h3>

<p>
	سيحتوي المتغير <code>pdfWriter</code> على كائن <code>PdfFileWriter</code> مع الصفحات الخاصة بجميع ملفات PDF المدموجة بعد الانتهاء من حلقات <code>for</code> المتداخلة، والخطوة الأخيرة هي كتابة هذا المحتوى في ملفٍ على القرص الصلب، لذا أضِف الشيفرة البرمجية التالية إلى برنامجك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8906_27" style=""><span class="com">#! python3</span><span class="pln">
</span><span class="com"># combinePdfs.py -  دمج جميع ملفات‫ PDF الموجودة في مجلد العمل الحالي في ملف PDF واحد</span><span class="pln">

</span><span class="kwd">import</span><span class="pln"> </span><span class="typ">PyPDF2</span><span class="pun">,</span><span class="pln"> os

</span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">

</span><span class="com"># ‫التكرار ضمن حلقة على جميع الملفات PDF</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> filename </span><span class="kwd">in</span><span class="pln"> pdfFiles</span><span class="pun">:</span><span class="pln">
</span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">
    </span><span class="com"># التكرار ضمن حلقة على جميع الصفحات (باستثناء الصفحة الأولى) وإضافتها</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> pageNum </span><span class="kwd">in</span><span class="pln"> range</span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> pdfReader</span><span class="pun">.</span><span class="pln">numPages</span><span class="pun">):</span><span class="pln">
    </span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">

</span><span class="com"># ‫حفظ ملف PDF الناتج في ملف</span><span class="pln">
pdfOutput </span><span class="pun">=</span><span class="pln"> open</span><span class="pun">(</span><span class="str">'allminutes.pdf'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'wb'</span><span class="pun">)</span><span class="pln">
pdfWriter</span><span class="pun">.</span><span class="pln">write</span><span class="pun">(</span><span class="pln">pdfOutput</span><span class="pun">)</span><span class="pln">
pdfOutput</span><span class="pun">.</span><span class="pln">close</span><span class="pun">()</span></pre>

<p>
	يؤدي تمرير <code>'wb'</code> إلى الدالة <code>open()‎</code> إلى فتح ملف PDF الناتج allminutes.pdf في وضع الكتابة الثنائي، وبالتالي يؤدي تمرير كائن <code>File</code> الناتج إلى التابع <code>write()‎</code> إلى إنشاء ملف PDF الفعلي، ويؤدي استدعاء التابع <code>close()‎</code> إلى إنهاء البرنامج.
</p>

<h3 id="-5">
	أفكار لبرامج مماثلة
</h3>

<p>
	تتيح لك القدرة على إنشاء ملفات PDF من صفحات ملفات PDF الأخرى إنشاءَ برامج يمكنها تطبيق ما يلي:
</p>

<ul>
	<li>
		قص صفحات محددة من ملفات PDF.
	</li>
	<li>
		إعادة ترتيب الصفحات في ملف PDF.
	</li>
	<li>
		إنشاء ملف PDF من الصفحات التي تحتوي على بعض النصوص التي يحدّدها التابع <code>extractText()‎</code>.
	</li>
</ul>

<h2 id="word">
	مستندات وورد Word
</h2>

<p>
	يمكن للغة بايثون إنشاء وتعديل مستندات وورد التي لها امتداد الملفات <code>‎.docx</code> باستخدام الوحدة <code>docx</code> التي يمكنك تثبيتها من خلال تشغيل الأمر <code>pip install --user -U python-docx==0.8.10</code>. اتبع الإرشادات الخاصة بتثبيت الوحدات الخارجية التي سنوضّحها في [مقالٍ لاحق](رابط مقال سنترجمه لاحقًا <a href="https://automatetheboringstuff.com/2e/appendixa/)." ipsnoembed="false" rel="external nofollow">https://automatetheboringstuff.com/2e/appendixa/).</a>
</p>

<p>
	<strong>ملاحظة</strong>: إذا استخدمتَ الأداة pip لتثبيت وحدة Python-Docx لأول مرة، فتأكد من تثبيت <code>python-docx</code> وليس <code>docx</code>، فاسم الحزمة <code>docx</code> مُخصَّص لوحدةٍ مختلفة لن نتحدّث عنها في هذا المقال، ولكن ستحتاج إلى تشغيل الأمر <code>import docx</code> وليس <code>import python-docx</code> عندما تريد استيراد الوحدة من حزمة <code>python-docx</code>.
</p>

<p>
	إن لم يكن لديك تطبيق وورد، فيمكنك استخدام ليبر أوفيس رايتر <a href="https://academy.hsoub.com/apps/productivity/liberoffice/libreoffice-writer/" rel="">LibreOffice Writer</a> وأوبن أوفيس رايتر OpenOffice Writer، وهما تطبيقان بديلان مجانيان لأنظمة ويندوز Windows وماك macOS ولينكس Linux، ويٌستخدمان لفتح ملفات <code>‎.docx</code>، حيث يمكنك تنزيلهما من موقعهما الرسمي، ويوجد التوثيق الكامل لوحدة Python-Docx على <a href="https://python-docx.readthedocs.io/en/latest/" rel="external nofollow">موقعها الرسمي</a>. سنركّز في هذا المقال على وورد في نظام تشغيل ويندوز بالرغم من وجود إصدار من وورد على نظام تشغيل ماك macOS.
</p>

<p>
	تحتوي ملفات <code>‎.docx</code> على هياكل متعددة مقارنةً بالنصوص العادية، ويُمثَّل كلّ هيكلٍ منها بثلاثة أنواع مختلفة من البيانات في الوحدة Python-Docx، حيث يمثِّل كائن <code>Document</code> المستند بأكمله، ويحتوي كائن <code>Document</code> على قائمةٍ من كائنات <code>Paragraph</code> للفقرات الموجودة في المستند، إذ تبدأ فقرة جديدة عندما يضغط المستخدم على زر <code>ENTER</code> أو <code>RETURN</code> أثناء الكتابة في مستند وورد، ويحتوي كل كائن من كائنات <code>Paragraph</code> على قائمة تضم كائن <code>Run</code> واحدًا أو أكثر. تحتوي الفقرة المكونة من جملة واحدة في الشكل التالي على أربعة كائنات <code>Run</code>:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="155649" href="https://academy.hsoub.com/uploads/monthly_2024_08/04_000138.jpg.c2d7c0e7328fcd3f1109b658aa1d4017.jpg" rel=""><img alt="04 000138" class="ipsImage ipsImage_thumbnailed" data-fileid="155649" data-unique="7941b2w0o" src="https://academy.hsoub.com/uploads/monthly_2024_08/04_000138.jpg.c2d7c0e7328fcd3f1109b658aa1d4017.jpg"> </a>
</p>

<p style="text-align: center;">
	كائنات <code>Run</code> الموجودة ضمن كائن <code>Paragraph</code>
</p>

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

<h3 id="-6">
	قراءة مستندات وورد
</h3>

<p>
	لنختبر الآن وحدة <code>docx</code>، لذا نزّل الملف <a href="https://nostarch.com/automatestuff2/" rel="external nofollow">demo.docx</a> واحفظه في مجلد العمل، ثم أدخِل ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8906_29" style=""><span class="pln">   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> docx
</span><span class="pun">➊</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc </span><span class="pun">=</span><span class="pln"> docx</span><span class="pun">.</span><span class="typ">Document</span><span class="pun">(</span><span class="str">'demo.docx'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">➋</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">doc</span><span class="pun">.</span><span class="pln">paragraphs</span><span class="pun">)</span><span class="pln">
   </span><span class="lit">7</span><span class="pln">
</span><span class="pun">➌</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">paragraphs</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">text
   </span><span class="str">'Document Title'</span><span class="pln">
</span><span class="pun">➍</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">paragraphs</span><span class="pun">[</span><span class="lit">1</span><span class="pun">].</span><span class="pln">text
   </span><span class="str">'A plain paragraph with some bold and some italic'</span><span class="pln">
</span><span class="pun">➎</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">doc</span><span class="pun">.</span><span class="pln">paragraphs</span><span class="pun">[</span><span class="lit">1</span><span class="pun">].</span><span class="pln">runs</span><span class="pun">)</span><span class="pln">
   </span><span class="lit">4</span><span class="pln">
</span><span class="pun">➏</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">paragraphs</span><span class="pun">[</span><span class="lit">1</span><span class="pun">].</span><span class="pln">runs</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">text
   </span><span class="str">'A plain paragraph with some '</span><span class="pln">
</span><span class="pun">➐</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">paragraphs</span><span class="pun">[</span><span class="lit">1</span><span class="pun">].</span><span class="pln">runs</span><span class="pun">[</span><span class="lit">1</span><span class="pun">].</span><span class="pln">text
   </span><span class="str">'bold'</span><span class="pln">
</span><span class="pun">➑</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">paragraphs</span><span class="pun">[</span><span class="lit">1</span><span class="pun">].</span><span class="pln">runs</span><span class="pun">[</span><span class="lit">2</span><span class="pun">].</span><span class="pln">text
   </span><span class="str">' and some '</span><span class="pln">
</span><span class="pun">➒</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">paragraphs</span><span class="pun">[</span><span class="lit">1</span><span class="pun">].</span><span class="pln">runs</span><span class="pun">[</span><span class="lit">3</span><span class="pun">].</span><span class="pln">text
   </span><span class="str">'italic'</span></pre>

<p>
	نفتح ملف <code>‎.docx</code> في بايثون ونستدعي الدالة <code>docx.Document()‎</code> ونمرّر إليها اسم الملف demo.docx ➊، مما يؤدي إلى إعادة كائن <code>Document</code> الذي يحتوي على السمة <code>paragraphs</code> التي تمثل قائمةً من كائنات <code>Paragraph</code>. إذا استدعينا الدالة <code>len()‎</code> للسمة <code>doc.paragraphs</code>، فستعيد القيمة 7، مما يخبرنا بوجود سبعة كائنات <code>Paragraph</code> في هذا المستند ➋. يمتلك كل كائن من كائنات <code>Paragraph</code> السمةَ <code>text</code> التي تحتوي على سلسلة نصية من النص الموجود في تلك الفقرة (بدون معلومات النمط). تحتوي السمة <code>text</code> الأولى في مثالنا على النص <code>'DocumentTitle'</code> ➌، وتحتوي السمة <code>text</code> الثانية على النص <code>'A plain paragraph with some bold and some italic'</code> ➍.
</p>

<p>
	يمتلك كل كائن <code>Paragraph</code> أيضًا على السمة <code>runs</code> التي تمثل قائمةً من كائنات <code>Run</code> التي تمتلك أيضًا السمة <code>text</code> التي تحتوي على نص كائن <code>Run</code> الخاص بها. لنلقِ نظرةً على سمات <code>text</code> في كائن <code>Paragraph</code> الثاني، والتي تمثّل النص <code>'A plain paragraph with some bold and some italic'</code>، حيث يعطي استدعاء الدالة <code>len()‎</code> لهذا الكائن القيمةَ 4، والتي تمثل وجود أربعة كائنات <code>Run</code> ➎. يحتوي كائن <code>Run</code> الأول على النص <code>‎'A plain paragraph with some '‎</code> ➏، ثم يتغير النص إلى نمط خط عريض، وبالتالي يبدأ النص <code>'bold'</code> كائن <code>Run</code> جديد ➐، ثم يعود النص إلى نمط خطٍ غير عريض، مما يعطي كائن <code>Run</code> ثالث، وهو النص <code>‎' and some '‎</code> ➑. أخيرًا، يحتوي كائن <code>Run</code> الرابع والأخير على النص <code>'italic'</code> بنمط خطٍ مائل ➒.
</p>

<p>
	ستتمكن برامج بايثون الآن باستخدام الوحدة Python-Docx من قراءة النص من ملف <code>‎.docx</code> واستخدامه مثل أيّ قيمة سلسلة نصية أخرى.
</p>

<h3 id="docx">
	الحصول على النص الكامل من ملف امتداده ‎.docx
</h3>

<p>
	إذا كان اهتمامك بالنص فقط دون الاهتمام بمعلومات التنسيق في مستند وورد، فيمكنك استخدام الدالة <code>getText()‎</code> التي تأخذ اسم ملف <code>‎.docx</code> وتعيد قيمة سلسلة نصية واحدة تمثّل النص الخاص بهذا الملف. افتح تبويبًا جديدًا لإنشاء ملف جديد في محرّرك، وأدخِل الشيفرة البرمجية التالية، واحفظ الملف بالاسم readDocx.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8906_32" style=""><span class="com">#! python3</span><span class="pln">

</span><span class="kwd">import</span><span class="pln"> docx

</span><span class="kwd">def</span><span class="pln"> getText</span><span class="pun">(</span><span class="pln">filename</span><span class="pun">):</span><span class="pln">
    doc </span><span class="pun">=</span><span class="pln"> docx</span><span class="pun">.</span><span class="typ">Document</span><span class="pun">(</span><span class="pln">filename</span><span class="pun">)</span><span class="pln">
    fullText </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> para </span><span class="kwd">in</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">paragraphs</span><span class="pun">:</span><span class="pln">
        fullText</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">para</span><span class="pun">.</span><span class="pln">text</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="str">'\n'</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">fullText</span><span class="pun">)</span></pre>

<p>
	تفتح الدالة <code>getText()‎</code> مستند وورد، وتتكرر ضمن حلقة على كافة كائنات <code>Paragraph</code> الموجودة في القائمة <code>paragraphs</code>، ثم تلحِق النص الخاص بها بالقائمة الموجودة في المتغير <code>fullText</code>. تُضَم السلاسل النصية الموجودة في المتغير <code>fullText</code> بعد انتهاء الحلقة مع محارف السطر الجديد.
</p>

<p>
	يمكن استيراد برنامج readDocx.py مثل أي وحدة أخرى، وإذا أردتَ النص فقط من مستند وورد، فيمكنك إدخال ما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8906_34" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> readDocx
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">print</span><span class="pun">(</span><span class="pln">readDocx</span><span class="pun">.</span><span class="pln">getText</span><span class="pun">(</span><span class="str">'demo.docx'</span><span class="pun">))</span><span class="pln">
</span><span class="typ">Document</span><span class="pln"> </span><span class="typ">Title</span><span class="pln">
A plain paragraph </span><span class="kwd">with</span><span class="pln"> some bold </span><span class="kwd">and</span><span class="pln"> some italic
</span><span class="typ">Heading</span><span class="pun">,</span><span class="pln"> level </span><span class="lit">1</span><span class="pln">
</span><span class="typ">Intense</span><span class="pln"> quote
first item </span><span class="kwd">in</span><span class="pln"> unordered list
first item </span><span class="kwd">in</span><span class="pln"> ordered list</span></pre>

<p>
	يمكنك أيضًا ضبط الدالة <code>getText()‎</code> لتعديل السلسلة النصية قبل إعادتها، فمثلًا يمكننا وضع مسافة بادئة لكل فقرة من خلال تعديل استدعاء التابع <code>append()‎</code> في الملف readDocx.py كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8906_36" style=""><span class="pln">fullText</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="str">'  '</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> para</span><span class="pun">.</span><span class="pln">text</span><span class="pun">)</span></pre>

<p>
	يمكننا إضافة مسافة مزدوجة بين الفقرات من خلال تغيير شيفرة استدعاء التابع <code>join()‎</code> إلى ما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8906_38" style=""><span class="kwd">return</span><span class="pln"> </span><span class="str">'\n\n'</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">fullText</span><span class="pun">)</span></pre>

<p>
	لاحظ أنك لا تحتاج سوى بضعة أسطر من الشيفرة البرمجية لكتابة الدوال التي تقرأ ملف <code>‎.docx</code> وتعيد سلسلة نصية من محتوى هذا الملف حسب رغبتك.
</p>

<h3 id="paragraphrun">
	تنسيق كائنات Paragraph وكائنات Run
</h3>

<p>
	يمكنك رؤية الأنماط في وورد ضمن نظام ويندوز بالضغط على المفاتيح Ctrl-Alt-Shift-S لعرض لوحة الأنماط Styles التي تشبه الشكل التالي، بينما يمكنك عرض لوحة الأنماط في نظام تشغيل ماك macOS بالنقر على عنصر قائمة العرض View ثم الأنماط Styles.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="155644" href="https://academy.hsoub.com/uploads/monthly_2024_08/05_000081.jpg.77e43040e074f923b82ed28c86be6fee.jpg" rel=""><img alt="05 000081" class="ipsImage ipsImage_thumbnailed" data-fileid="155644" data-unique="p02j0d6jq" src="https://academy.hsoub.com/uploads/monthly_2024_08/05_000081.jpg.77e43040e074f923b82ed28c86be6fee.jpg"> </a>
</p>

<p style="text-align: center;">
	عرض لوحة الأنماط بالضغط على المفاتيح CTRL-ALT-SHIFT-S في نظام ويندوز
</p>

<p>
	يستخدم برنامج وورد ومعالجات النصوص الأخرى أنماطًا للحفاظ على تناسق العرض المرئي لأنواع النصوص المتشابهة وسهولة تغييره، فمثلًا قد ترغب في ضبط فقرات النص لتكون بخطٍ من النوع Times New Roman وحجمه 11 نقطة ومتحاذٍ من جهة اليسار وغير مضبوط من جهة اليمين، حيث يمكنك إنشاء نمط باستخدام هذه الإعدادات وإسناده لجميع فقرات النص، وإذا أردتَ لاحقًا تغيير طريقة عرض جميع فقرات النص في المستند، فيمكنك تغيير النمط فقط، وستُحدَّث جميع تلك الفقرات تلقائيًا.
</p>

<p>
	هناك ثلاثة أنواع من الأنماط بالنسبة لمستندات وورد وهي: أنماط الفقرة Paragraph Styles التي يمكن تطبيقها على كائنات <code>Paragraph</code>، وأنماط المحارف Character Styles التي يمكن تطبيقها على كائنات <code>Run</code>، والأنماط المرتبطة Linked Styles التي يمكن تطبيقها على كلا النوعين من الكائنات. يمكنك إعطاء كائنات <code>Paragraph</code> وكائنات <code>Run</code> أنماطٍ من خلال ضبط السمة <code>style</code> الخاصة بها على سلسلةٍ نصية تمثّل اسم النمط، وإذا كانت هذه السمة مضبوطةً على القيمة <code>None</code>، فلن يكون هناك نمط مرتبط بكائن <code>Paragraph</code> أو كائن <code>Run</code>.
</p>

<p>
	إليك قيم السلاسل النصية لأنماط وورد الافتراضية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="155647" href="https://academy.hsoub.com/uploads/monthly_2024_08/06_defaultWordstyles.png.df6e8e8d77c7850c2c671b67b155aee9.png" rel=""><img alt="06 default word styles" class="ipsImage ipsImage_thumbnailed" data-fileid="155647" data-unique="k40l67tje" src="https://academy.hsoub.com/uploads/monthly_2024_08/06_defaultWordstyles.png.df6e8e8d77c7850c2c671b67b155aee9.png"> </a>
</p>

<p>
	يجب إضافة <code>‎' Char'‎</code> إلى نهاية اسم النمط عند استخدام نمط مرتبط بكائن <code>Run</code>، فمثلًا يمكنك ضبط النمط المرتبط Quote لكائن <code>Paragraph</code> من خلال استخدام <code>paragraphObj.style = 'Quote'‎</code>، ولكنك ستستخدم <code>runObj.style = 'Quote Char'‎</code> بالنسبة لكائن <code>Run</code>.
</p>

<p>
	الأنماط الوحيدة التي يمكن استخدامها في الإصدار 0.8.10 من وحدة Python-Docx هي أنماط وورد الافتراضية والأنماط الموجودة في ملف <code>‎.docx</code> المفتوح، ولا يمكن إنشاء أنماط جديدة، بالرغم من أن ذلك قد تغيّر في الإصدارات اللاحقة من وحدة Python-Docx.
</p>

<h3 id="-7">
	إنشاء مستندات وورد مع أنماط غير افتراضية
</h3>

<p>
	إذا أردتَ إنشاء مستندات وورد تستخدم أنماطٍ مختلفة عن الأنماط الافتراضية، فيجب فتح وورد على مستند فارغ وإنشاء الأنماط بنفسك من خلال النقر على زر "نمط جديد New Style" الموجود أسفل لوحة الأنماط كما هو موضح في الشكل التالي على نظام ويندوز:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="155653" href="https://academy.hsoub.com/uploads/monthly_2024_08/07_000029.jpg.65ca60b6892d3231da8a4a95c4b94687.jpg" rel=""><img alt="07 000029" class="ipsImage ipsImage_thumbnailed" data-fileid="155653" data-unique="qj82vkuqy" src="https://academy.hsoub.com/uploads/monthly_2024_08/07_000029.jpg.65ca60b6892d3231da8a4a95c4b94687.jpg"> </a>
</p>

<p style="text-align: center;">
	زر نمط جديد New Style (على اليسار) ونافذة إنشاء نمط جديد من التنسيق Create New Style from Formatting (على اليمين).
</p>

<p>
	سيؤدي الضغط على زر نمط جديد إلى فتح نافذة "إنشاء نمط جديد من التنسيق Create New Style from Formatting" حيث يمكنك إدخال النمط الجديد. ارجع بعد ذلك إلى الصدفة التفاعلية وافتح هذا المستند الفارغ باستخدام الدالة <code>docx.Document()‎</code>، واستخدمه كأساسٍ لمستند وورد الخاص بك. سيكون الاسم الذي أعطيته لهذا النمط متاحًا الآن للاستخدام مع وحدة Python-Docx.
</p>

<h3 id="run">
	سمات الكائن Run
</h3>

<p>
	يمكن تنسيق كائنات <code>Run</code> باستخدام سمات <code>text</code>، حيث يمكن ضبط كل سمة على قيمة من ثلاث قيم هي: القيمة <code>True</code> (تكون السمة مُفعَّلةً دائمًا بغض النظر عن الأنماط الأخرى المُطبَّقة على الكائن <code>Run</code>)، أو القيمة <code>False</code> (تكون السمة مُعطَّلة دائمًا)، أو القيمة <code>None</code> (الإعداد الافتراضي لأيّ نمطٍ مضبوط للكائن <code>Run</code>).
</p>

<p>
	يوضّح الجدول التالي السمات <code>text</code> التي يمكن ضبطها لكائنات <code>Run</code>:
</p>

<table>
	<thead>
		<tr>
			<th>
				السمة
			</th>
			<th>
				وصفها
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				السمة <code>bold</code>
			</td>
			<td>
				يظهر النص بخط عريض
			</td>
		</tr>
		<tr>
			<td>
				السمة <code>italic</code>
			</td>
			<td>
				يظهر النص بخط مائل
			</td>
		</tr>
		<tr>
			<td>
				السمة <code>underline</code>
			</td>
			<td>
				يوضَع خط تحت النص
			</td>
		</tr>
		<tr>
			<td>
				السمة <code>strike</code>
			</td>
			<td>
				يظهر النص مع خطٍ في وسطه
			</td>
		</tr>
		<tr>
			<td>
				السمة <code>double_strike</code>
			</td>
			<td>
				يظهر النص مع خط مزدوج في وسطه
			</td>
		</tr>
		<tr>
			<td>
				السمة <code>all_caps</code>
			</td>
			<td>
				يظهر النص بحروف كبيرة
			</td>
		</tr>
		<tr>
			<td>
				السمة <code>small_caps</code>
			</td>
			<td>
				يظهر النص بحروف كبيرة، وتكون الحروف الصغيرة أصغر بنقطتين
			</td>
		</tr>
		<tr>
			<td>
				السمة <code>shadow</code>
			</td>
			<td>
				يظهر النص مع ظل
			</td>
		</tr>
		<tr>
			<td>
				السمة <code>outline</code>
			</td>
			<td>
				يظهر النص مُحدَّدًا وليس ممتلئًا
			</td>
		</tr>
		<tr>
			<td>
				السمة <code>rtl</code>
			</td>
			<td>
				النص مكتوب من اليمين إلى اليسار
			</td>
		</tr>
		<tr>
			<td>
				السمة <code>imprint</code>
			</td>
			<td>
				يظهر النص مضغوطًا إلى داخل الصفحة
			</td>
		</tr>
		<tr>
			<td>
				السمة <code>emboss</code>
			</td>
			<td>
				يبدو النص مرتفعًا عن الصفحة ارتفاعًا بارزًا
			</td>
		</tr>
	</tbody>
</table>

<p>
	أدخِل مثلًا ما يلي في الصدفة التفاعلية لتغيير أنماط الملف demo.docx:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8906_40" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> docx
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc </span><span class="pun">=</span><span class="pln"> docx</span><span class="pun">.</span><span class="typ">Document</span><span class="pun">(</span><span class="str">'demo.docx'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">paragraphs</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">text
</span><span class="str">'Document Title'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">paragraphs</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">style </span><span class="com"># The exact id may be different:</span><span class="pln">
</span><span class="typ">_ParagraphStyle</span><span class="pun">(</span><span class="str">'Title'</span><span class="pun">)</span><span class="pln"> id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3095631007984</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">paragraphs</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">style </span><span class="pun">=</span><span class="pln"> </span><span class="str">'Normal'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">paragraphs</span><span class="pun">[</span><span class="lit">1</span><span class="pun">].</span><span class="pln">text
</span><span class="str">'A plain paragraph with some bold and some italic'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="pun">(</span><span class="pln">doc</span><span class="pun">.</span><span class="pln">paragraphs</span><span class="pun">[</span><span class="lit">1</span><span class="pun">].</span><span class="pln">runs</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">text</span><span class="pun">,</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">paragraphs</span><span class="pun">[</span><span class="lit">1</span><span class="pun">].</span><span class="pln">runs</span><span class="pun">[</span><span class="lit">1</span><span class="pun">].</span><span class="pln">text</span><span class="pun">,</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">
paragraphs</span><span class="pun">[</span><span class="lit">1</span><span class="pun">].</span><span class="pln">runs</span><span class="pun">[</span><span class="lit">2</span><span class="pun">].</span><span class="pln">text</span><span class="pun">,</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">paragraphs</span><span class="pun">[</span><span class="lit">1</span><span class="pun">].</span><span class="pln">runs</span><span class="pun">[</span><span class="lit">3</span><span class="pun">].</span><span class="pln">text</span><span class="pun">)</span><span class="pln">
</span><span class="pun">(</span><span class="str">'A plain paragraph with some '</span><span class="pun">,</span><span class="pln"> </span><span class="str">'bold'</span><span class="pun">,</span><span class="pln"> </span><span class="str">' and some '</span><span class="pun">,</span><span class="pln"> </span><span class="str">'italic'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">paragraphs</span><span class="pun">[</span><span class="lit">1</span><span class="pun">].</span><span class="pln">runs</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">style </span><span class="pun">=</span><span class="pln"> </span><span class="str">'QuoteChar'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">paragraphs</span><span class="pun">[</span><span class="lit">1</span><span class="pun">].</span><span class="pln">runs</span><span class="pun">[</span><span class="lit">1</span><span class="pun">].</span><span class="pln">underline </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">True</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">paragraphs</span><span class="pun">[</span><span class="lit">1</span><span class="pun">].</span><span class="pln">runs</span><span class="pun">[</span><span class="lit">3</span><span class="pun">].</span><span class="pln">underline </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">True</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">save</span><span class="pun">(</span><span class="str">'restyled.docx'</span><span class="pun">)</span></pre>

<p>
	استخدمنا في المثال السابق سمات <code>text</code> و <code>style</code> لرؤية ما هو موجود في الفقرات ضمن المستند بسهولة، إذ يمكننا أن نرى أنه من السهل تقسيم الفقرة إلى كائنات <code>Run</code> والوصول إلى كلٍّ منها على حدة، لذلك يمكننا الحصول على كائنات <code>Run</code> الأولى والثانية والرابعة في الفقرة الثانية، وتنسيق كلِّ منها، وحفظ النتائج في مستند جديد.
</p>

<p>
	ستكون للكلمات <code>Document Title</code> الموجودة في أعلى المستند restyled.docx النمط العادي Normal بدلًا من نمط العنوان Title، وسيكون لكائن <code>Run</code> الخاص بالنص <code>A plain paragraph with some</code> النمط QuoteChar، وسيكون لكائني <code>Run</code> الخاصين بالكلمتين <code>bold</code> و <code>italic</code> سمات <code>underline</code> المضبوطة على القيمة <code>True</code>. يوضح الشكل التالي كيف تبدو أنماط الفقرات وكائنات <code>Run</code> في المستند restyled.docx:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="155646" href="https://academy.hsoub.com/uploads/monthly_2024_08/08_000120.jpg.44b1bf73bbd1dba9cdc06e2b1f144202.jpg" rel=""><img alt="08 000120" class="ipsImage ipsImage_thumbnailed" data-fileid="155646" data-unique="a58zd9lc0" src="https://academy.hsoub.com/uploads/monthly_2024_08/08_000120.jpg.44b1bf73bbd1dba9cdc06e2b1f144202.jpg"> </a>
</p>

<p style="text-align: center;">
	ملف restyled.docx
</p>

<h3 id="-8">
	كتابة مستندات وورد
</h3>

<p>
	لندخِل ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8906_42" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> docx
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc </span><span class="pun">=</span><span class="pln"> docx</span><span class="pun">.</span><span class="typ">Document</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">add_paragraph</span><span class="pun">(</span><span class="str">'Hello, world!'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">docx</span><span class="pun">.</span><span class="pln">text</span><span class="pun">.</span><span class="typ">Paragraph</span><span class="pln"> object at </span><span class="lit">0x0000000003B56F60</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">save</span><span class="pun">(</span><span class="str">'helloworld.docx'</span><span class="pun">)</span></pre>

<p>
	يمكننا إنشاء ملف <code>‎.docx</code> من خلال استدعاء الدالة <code>docx.Document()‎</code> لإعادة كائن مستند <code>Document</code> وورد جديد وفارغ، ويضيف التابع <code>add_paragraph()‎</code> الخاص بالمستند فقرةً نصية جديدة إلى المستند ويعيد مرجعًا إلى كائن <code>Paragraph</code> المُضاف. نمرّر سلسلةً نصية تمثّل اسم الملف إلى التابع <code>save()‎</code> الخاص بالمستند عند الانتهاء من إضافة النص لحفظ الكائن <code>Document</code> في ملف. تؤدي الشيفرة البرمجية السابقة إلى إنشاء ملف بالاسم helloworld.docx في مجلد العمل الحالي والذي يبدو عند فتحه كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="155650" href="https://academy.hsoub.com/uploads/monthly_2024_08/09_000065.jpg.3c32c6b8f342deaada4dc18f6f03e7fd.jpg" rel=""><img alt="09 000065" class="ipsImage ipsImage_thumbnailed" data-fileid="155650" data-unique="aqu6w45u8" src="https://academy.hsoub.com/uploads/monthly_2024_08/09_000065.jpg.3c32c6b8f342deaada4dc18f6f03e7fd.jpg"> </a>
</p>

<p style="text-align: center;">
	مستند وورد الذي أنشأناه باستخدام الاستدعاء <code>add_paragraph('Hello, world!')‎</code>
</p>

<p>
	يمكنك إضافة فقرات من خلال استدعاء التابع <code>add_paragraph()‎</code> مرةً أخرى مع نص الفقرة الجديدة، أو يمكنك استدعاء التابع <code>add_run()‎</code> الخاص بالفقرة وتمرير سلسلة نصية إليه لإضافة نص إلى نهاية فقرة موجودة مسبقًا. إذًا لندخِل ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8906_44" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> docx
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc </span><span class="pun">=</span><span class="pln"> docx</span><span class="pun">.</span><span class="typ">Document</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">add_paragraph</span><span class="pun">(</span><span class="str">'Hello world!'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">docx</span><span class="pun">.</span><span class="pln">text</span><span class="pun">.</span><span class="typ">Paragraph</span><span class="pln"> object at </span><span class="lit">0x000000000366AD30</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> paraObj1 </span><span class="pun">=</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">add_paragraph</span><span class="pun">(</span><span class="str">'This is a second paragraph.'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> paraObj2 </span><span class="pun">=</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">add_paragraph</span><span class="pun">(</span><span class="str">'This is a yet another paragraph.'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> paraObj1</span><span class="pun">.</span><span class="pln">add_run</span><span class="pun">(</span><span class="str">' This text is being added to the second paragraph.'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">docx</span><span class="pun">.</span><span class="pln">text</span><span class="pun">.</span><span class="typ">Run</span><span class="pln"> object at </span><span class="lit">0x0000000003A2C860</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">save</span><span class="pun">(</span><span class="str">'multipleParagraphs.docx'</span><span class="pun">)</span></pre>

<p>
	لاحظ أن النص "This text is being added to the second paragraph.‎" أضيف إلى كائن <code>Paragraph</code> في المتغير <code>paraObj1</code>، وهو الفقرة الثانية المُضافة إلى المتغير <code>doc</code>. تعيد الدالتان <code>add_paragraph()‎</code> و <code>add_run()‎</code> كائنات <code>Paragraph</code> و <code>Run</code> على التوالي بحيث توفّر عليك عناء استخراجها في خطوة منفصلة. ضع في بالك أنه يمكن إضافة كائنات <code>Paragraph</code> الجديدة إلى نهاية المستند فقط، ويمكن إضافة كائنات <code>Run</code> الجديدة إلى نهاية كائن <code>Paragraph</code> فقط، وذلك اعتبارًا من الإصدار 0.8.10 من وحدة Python-Docx. أخيرًا، يمكن استدعاء التابع <code>save()‎</code> مرة أخرى لحفظ التغييرات الإضافية التي أجريتها.
</p>

<p>
	سيبدو المستند الناتج مثل المستند الموضّح في الشكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="155648" href="https://academy.hsoub.com/uploads/monthly_2024_08/10_000103.jpg.124ef2ecbd2a3c8e790ae090fbb711a4.jpg" rel=""><img alt="10 000103" class="ipsImage ipsImage_thumbnailed" data-fileid="155648" data-unique="1ah1t5xil" src="https://academy.hsoub.com/uploads/monthly_2024_08/10_000103.jpg.124ef2ecbd2a3c8e790ae090fbb711a4.jpg"> </a>
</p>

<p style="text-align: center;">
	المستند الذي يحتوي على كائنات <code>Paragraph</code> و <code>Run</code> المتعددة المُضافة
</p>

<p>
	تأخذ كلّ من الدالتين <code>add_paragraph()‎</code> و <code>add_run()‎</code> وسيطًا ثانيًا اختياريًا، وهو سلسلة نصية من نمط الكائن <code>Paragraph</code> أو <code>Run</code> كما في المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8906_46" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">add_paragraph</span><span class="pun">(</span><span class="str">'Hello, world!'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Title'</span><span class="pun">)</span></pre>

<p>
	يضيف السطر السابق فقرةً تحتوي على النص "Hello, world!‎" من نمط العنوان Title Style.
</p>

<h3 id="headings">
	إضافة العناوين Headings
</h3>

<p>
	يؤدي استدعاء الدالة <code>add_heading()‎</code> إلى إضافة فقرة تحتوي على أحد أنماط العناوين. لندخِل ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8906_48" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc </span><span class="pun">=</span><span class="pln"> docx</span><span class="pun">.</span><span class="typ">Document</span><span class="pun">()</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">add_heading</span><span class="pun">(</span><span class="str">'Header 0'</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">docx</span><span class="pun">.</span><span class="pln">text</span><span class="pun">.</span><span class="typ">Paragraph</span><span class="pln"> object at </span><span class="lit">0x00000000036CB3C8</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">add_heading</span><span class="pun">(</span><span class="str">'Header 1'</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">docx</span><span class="pun">.</span><span class="pln">text</span><span class="pun">.</span><span class="typ">Paragraph</span><span class="pln"> object at </span><span class="lit">0x00000000036CB630</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">add_heading</span><span class="pun">(</span><span class="str">'Header 2'</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">docx</span><span class="pun">.</span><span class="pln">text</span><span class="pun">.</span><span class="typ">Paragraph</span><span class="pln"> object at </span><span class="lit">0x00000000036CB828</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">add_heading</span><span class="pun">(</span><span class="str">'Header 3'</span><span class="pun">,</span><span class="pln"> </span><span class="lit">3</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">docx</span><span class="pun">.</span><span class="pln">text</span><span class="pun">.</span><span class="typ">Paragraph</span><span class="pln"> object at </span><span class="lit">0x00000000036CB2E8</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">add_heading</span><span class="pun">(</span><span class="str">'Header 4'</span><span class="pun">,</span><span class="pln"> </span><span class="lit">4</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">docx</span><span class="pun">.</span><span class="pln">text</span><span class="pun">.</span><span class="typ">Paragraph</span><span class="pln"> object at </span><span class="lit">0x00000000036CB3C8</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">save</span><span class="pun">(</span><span class="str">'headings.docx'</span><span class="pun">)</span></pre>

<p>
	وسطاء الدالة <code>add_heading()‎</code> هي سلسلة نصية تمثّل نص العنوان وعدد صحيح قيمته من 0 إلى 4، حيث يجعل العدد الصحيح 0 العنوان من النمط Title style، والذي يُستخدَم في الجزء العلوي من المستند، والأعداد الصحيحة من 1 إلى 4 مُخصَّصة لمستويات العناوين المختلفة، حيث يكون العدد 1 هو العنوان الرئيسي والعدد 4 هو العنوان الفرعي الأدنى. تعيد الدالة <code>add_heading()‎</code> كائن <code>Paragraph</code> لتوفّر عليك إجراء خطوة استخراجه من كائن <code>Document</code> في خطوة منفصلة.
</p>

<p>
	سيبدو ملف headings.docx الناتج مثل المستند الموضّح في الشكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="155654" href="https://academy.hsoub.com/uploads/monthly_2024_08/11_000105.jpg.6fc8cdcd19b946fd26970aaf52a7cde1.jpg" rel=""><img alt="11 000105" class="ipsImage ipsImage_thumbnailed" data-fileid="155654" data-unique="4pjafctmo" src="https://academy.hsoub.com/uploads/monthly_2024_08/11_000105.jpg.6fc8cdcd19b946fd26970aaf52a7cde1.jpg"> </a>
</p>

<p style="text-align: center;">
	مستند headings.docx الذي يحتوي على العناوين من 0 إلى 4
</p>

<h3 id="-9">
	إضافة فواصل الأسطر والصفحات
</h3>

<p>
	يمكن إضافة فاصل أسطر بدلًا من بدء فقرة جديدة بالكامل من خلال استدعاء التابع <code>add_break()‎</code> لكائن <code>Run</code> الذي تريد ظهور الفاصل بعده، وإذا أردتَ إضافة فاصل صفحات، فيجب تمرير القيمة <code>docx.enum.text.WD_BREAK.PAGE</code> بوصفها وسيطًا وحيدًا للتابع <code>add_break()‎</code> كما في السطر ➊ من المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8906_50" style=""><span class="pln">   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc </span><span class="pun">=</span><span class="pln"> docx</span><span class="pun">.</span><span class="typ">Document</span><span class="pun">()</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">add_paragraph</span><span class="pun">(</span><span class="str">'This is on the first page!'</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&lt;</span><span class="pln">docx</span><span class="pun">.</span><span class="pln">text</span><span class="pun">.</span><span class="typ">Paragraph</span><span class="pln"> object at </span><span class="lit">0x0000000003785518</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">➊</span><span class="pln"> </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">paragraphs</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">runs</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">add_break</span><span class="pun">(</span><span class="pln">docx</span><span class="pun">.</span><span class="pln">enum</span><span class="pun">.</span><span class="pln">text</span><span class="pun">.</span><span class="pln">WD_BREAK</span><span class="pun">.</span><span class="pln">PAGE</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">add_paragraph</span><span class="pun">(</span><span class="str">'This is on the second page!'</span><span class="pun">)</span><span class="pln">
   </span><span class="pun">&lt;</span><span class="pln">docx</span><span class="pun">.</span><span class="pln">text</span><span class="pun">.</span><span class="typ">Paragraph</span><span class="pln"> object at </span><span class="lit">0x00000000037855F8</span><span class="pun">&gt;</span><span class="pln">
   </span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">save</span><span class="pun">(</span><span class="str">'twoPage.docx'</span><span class="pun">)</span></pre>

<p>
	تؤدي الشيفرة البرمجية السابقة إلى إنشاء مستند وورد مؤلف من صفحتين مع وجود النص "This is on the first page!‎" في الصفحة الأولى والنص "This is on the second page!‎" في الصفحة الثانية. لا يزال هناك مساحة كبيرة في الصفحة الأولى بعد النص "This is on the first page!‎"، ولكننا أجبرنا الفقرة التالية على البدء في صفحة جديدة من خلال إدراج فاصل صفحات بعد كائن <code>Run</code> الأول للفقرة الأولى ➊.
</p>

<h3 id="-10">
	إضافة الصور
</h3>

<p>
	تحتوي كائنات <code>Document</code> على التابع <code>add_picture()‎</code> الذي يتيح إضافة صورةٍ إلى نهاية المستند. لنفترض أن لديك ملفًا اسمه zophie.png مثلًا في مجلد العمل الحالي، حيث يمكنك إضافة الصورة zophie.png إلى نهاية مستندك بعرض 1 بوصة وارتفاع 4 سنتيمترات من خلال إدخال ما يلي (يستخدم برنامج وورد الوحدات الإنجليزية والمترية):
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8906_52" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> doc</span><span class="pun">.</span><span class="pln">add_picture</span><span class="pun">(</span><span class="str">'zophie.png'</span><span class="pun">,</span><span class="pln"> width</span><span class="pun">=</span><span class="pln">docx</span><span class="pun">.</span><span class="pln">shared</span><span class="pun">.</span><span class="typ">Inches</span><span class="pun">(</span><span class="lit">1</span><span class="pun">),</span><span class="pln">
height</span><span class="pun">=</span><span class="pln">docx</span><span class="pun">.</span><span class="pln">shared</span><span class="pun">.</span><span class="typ">Cm</span><span class="pun">(</span><span class="lit">4</span><span class="pun">))</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">docx</span><span class="pun">.</span><span class="pln">shape</span><span class="pun">.</span><span class="typ">InlineShape</span><span class="pln"> object at </span><span class="lit">0x00000000036C7D30</span><span class="pun">&gt;</span></pre>

<p>
	الوسيط الأول للتابع <code>add_picture()‎</code> هو سلسلة نصية تمثّل اسم ملف الصورة، وتضبط وسطاء الكلمات المفتاحية <code>width</code> و <code>height</code> الاختيارية عرض الصورة وارتفاعها في المستند، وإذا تُرِكا دون ضبط، فسيكون العرض والارتفاع الافتراضي هو الحجم الطبيعي للصورة.
</p>

<p>
	قد تفضّل تحديد ارتفاع الصورة وعرضها بوحدات مألوفة مثل وحدات البوصة والسنتيمتر، لذا يمكنك استخدام الدالتين <code>docx.shared.Inches()‎</code> و <code>docx.shared.Cm()‎</code> عندما تحدّد وسطاء الكلمات المفتاحية <code>width</code> و <code>height</code>.
</p>

<h2 id="pdf-8">
	إنشاء ملفات PDF من مستندات وورد
</h2>

<p>
	لا تسمح لك وحدة PyPDF2 بإنشاء مستندات PDF مباشرةً، ولكن توجد طريقة لإنشاء ملفات PDF باستخدام بايثون إذا كنت تستخدم نظام ويندوز مع وجود مايكروسوفت وورد مثبَّتًا عليه، لذا ستحتاج إلى تثبيت الحزمة Pywin32 من خلال تشغيل الأمر <code>pip install --user -U pywin32==224</code>. إذا استخدمتَ هذه الحزمة مع وحدة <code>docx</code>، فيمكنك إنشاء مستندات وورد ثم تحويلها إلى ملفات PDF باستخدام السكربت التالي، لذا افتح تبويبًا جديدًا لإنشاء ملف جديد في محرّرك، وأدخِل الشيفرة البرمجية التالية، واحفظها بالاسم convertWordToPDF.py:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8906_54" style=""><span class="com"># يعمل هذا السكربت على نظام ويندوز فقط، ويجب أن يكون وورد مثبتًا عليه</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> win32com</span><span class="pun">.</span><span class="pln">client </span><span class="com"># install with "pip install pywin32==224"</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> docx
wordFilename </span><span class="pun">=</span><span class="pln"> </span><span class="str">'your_word_document.docx'</span><span class="pln">
pdfFilename </span><span class="pun">=</span><span class="pln"> </span><span class="str">'your_pdf_filename.pdf'</span><span class="pln">

doc </span><span class="pun">=</span><span class="pln"> docx</span><span class="pun">.</span><span class="typ">Document</span><span class="pun">()</span><span class="pln">
</span><span class="com"># ضع شيفرة إنشاء مستند وورد هنا</span><span class="pln">
doc</span><span class="pun">.</span><span class="pln">save</span><span class="pun">(</span><span class="pln">wordFilename</span><span class="pun">)</span><span class="pln">

wdFormatPDF </span><span class="pun">=</span><span class="pln"> </span><span class="lit">17</span><span class="pln"> </span><span class="com"># Word's numeric code for PDFs.</span><span class="pln">
wordObj </span><span class="pun">=</span><span class="pln"> win32com</span><span class="pun">.</span><span class="pln">client</span><span class="pun">.</span><span class="typ">Dispatch</span><span class="pun">(</span><span class="str">'Word.Application'</span><span class="pun">)</span><span class="pln">

docObj </span><span class="pun">=</span><span class="pln"> wordObj</span><span class="pun">.</span><span class="typ">Documents</span><span class="pun">.</span><span class="typ">Open</span><span class="pun">(</span><span class="pln">wordFilename</span><span class="pun">)</span><span class="pln">
docObj</span><span class="pun">.</span><span class="typ">SaveAs</span><span class="pun">(</span><span class="pln">pdfFilename</span><span class="pun">,</span><span class="pln"> </span><span class="typ">FileFormat</span><span class="pun">=</span><span class="pln">wdFormatPDF</span><span class="pun">)</span><span class="pln">
docObj</span><span class="pun">.</span><span class="typ">Close</span><span class="pun">()</span><span class="pln">
wordObj</span><span class="pun">.</span><span class="typ">Quit</span><span class="pun">()</span></pre>

<p>
	يمكنك كتابة برنامج ينتج عنه ملفات PDF مع المحتوى الخاص بك من خلال استخدام وحدة <code>docx</code> لإنشاء مستند وورد، ثم استخدام وحدة <code>win32com.client</code> الخاصة بحزمة Pywin32 لتحويله إلى ملف PDF. ضع استدعاءات دوال الوحدة <code>docx</code> مكان التعليق <code># ضع شيفرة إنشاء مستند وورد هنا</code> لإنشاء المحتوى الخاص بك لملف PDF في مستند وورد.
</p>

<p>
	قد تبدو هذه الطريقة لإنتاج ملفات PDF معقدة، ولكن تكون الحلول البرمجية الاحترافية معقدةً في أغلب الأحيان.
</p>

<h2 id="-12">
	مشاريع للتدريب
</h2>

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

<h3 id="pdf-9">
	برنامج للتأكد من تشفير ملفات PDF
</h3>

<p>
	استخدم الدالة <code>os.walk()‎</code> لكتابة سكربتٍ يمر على كل ملف PDF في المجلد ومجلداته الفرعية، وشفّر ملفات PDF باستخدام كلمة المرور المتوفرة في سطر الأوامر، واحفظ كل ملف PDF مشفّر مع إضافة اللاحقة <code>‎_encrypted.pdf</code> إلى اسم الملف الأصلي، ثم اطلب من البرنامج محاولة قراءة الملف وفك تشفيره للتأكد من تشفيره بصورة صحيحة قبل حذف الملف الأصلي.
</p>

<p>
	اكتب بعد ذلك برنامجًا يبحث عن جميع ملفات PDF المشفرة في المجلد ومجلداته الفرعية، وينشئ نسخة مشفَّرة من ملف PDF باستخدام كلمة المرور المتوفرة. إذا كانت كلمة المرور غير صحيحة، فيجب على البرنامج طباعة رسالة للمستخدم والانتقال إلى ملف PDF التالي.
</p>

<h3 id="-13">
	برنامج لإنشاء دعوات مخصصة في مستندات وورد
</h3>

<p>
	لنفترض أن لديك ملفًا نصيًا بأسماء الضيوف، حيث يحتوي الملف guests.txt على اسم شخص واحد في كل سطر كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8906_56" style=""><span class="typ">Prof</span><span class="pun">.</span><span class="pln"> </span><span class="typ">Plum</span><span class="pln">
</span><span class="typ">Miss</span><span class="pln"> </span><span class="typ">Scarlet</span><span class="pln">
</span><span class="typ">Col</span><span class="pun">.</span><span class="pln"> </span><span class="typ">Mustard</span><span class="pln">
</span><span class="typ">Al</span><span class="pln"> </span><span class="typ">Sweigart</span><span class="pln">
</span><span class="typ">RoboCop</span></pre>

<p>
	اكتب برنامجًا ينشئ مستند وورد يحتوي على دعوات مخصصة كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="155655" href="https://academy.hsoub.com/uploads/monthly_2024_08/12_000069.jpg.b305f48ce5c86c1e600362eb95d5927a.jpg" rel=""><img alt="12 000069" class="ipsImage ipsImage_thumbnailed" data-fileid="155655" data-unique="ejd5tzxbl" src="https://academy.hsoub.com/uploads/monthly_2024_08/12_000069.jpg.b305f48ce5c86c1e600362eb95d5927a.jpg"> </a>
</p>

<p style="text-align: center;">
	مستند وورد الذي أنشأناه باستخدام سكربت الدعوات المخصصة
</p>

<p>
	يمكن للوحدة Python-Docx استخدام الأنماط الموجودة مسبقًا في مستند وورد فقط، لذا يجب أولًا إضافة هذه الأنماط إلى ملف وورد فارغ ثم فتح هذا الملف باستخدام وحدة Python-Docx. يجب أن تكون هناك دعوة واحدة لكل صفحة في مستند وورد الناتج، لذا استدعِ التابع <code>add_break()‎</code> لإضافة فاصل صفحة بعد الفقرة الأخيرة من كل دعوة، وبالتالي يجب فتح مستند وورد واحد فقط لطباعة كافة الدعوات دفعةً واحدة.
</p>

<p>
	<strong>ملاحظة</strong>: يمكنك أيضًا تنزيل نموذج الملف <a href="https://nostarch.com/automatestuff2/" rel="external nofollow">guests.txt</a>.
</p>

<h3 id="pdf-10">
	برنامج لاستخدام هجوم القوة الغاشمة لكسر كلمة مرور ملفات PDF
</h3>

<p>
	لنفترض أن لديك ملف PDF مشفَّرًا نسيت كلمة مروره، ولكنك تتذكر أنه كان كلمة إنجليزية واحدة، وتُعَد محاولة تخمين كلمة المرور التي نسيتها مهمةً مملة جدًا، لذا يمكنك كتابة برنامج يفك تشفير ملف PDF من خلال تجربة جميع الكلمات الإنجليزية الممكنة حتى يجد الكلمة الصحيحة، ويسمى ذلك هجوم القوة الغاشمة لإيجاد كلمة المرور. نزّل الملف النصي <a href="https://nostarch.com/automatestuff2/" rel="external nofollow">dictionary.txt</a> الذي يحتوي على أكثر من 44000 كلمة إنجليزية بحيث توجد كلمة واحدة في كل سطر.
</p>

<p>
	استخدم مهارات قراءة الملفات التي تعلمتها سابقًا لإنشاء قائمةٍ بالسلاسل النصية التي تمثّل الكلمات من خلال قراءة الملف dictionary.txt، ثم المرور على كل كلمة في هذه القائمة، وتمريرها إلى التابع <code>decrypt()‎</code>. إذا أعاد هذا التابع العدد الصحيح 0، فستكون كلمة المرور خاطئة ويجب أن ينتقل برنامجك إلى كلمة المرور التالية، وإذا أعاد التابع <code>decrypt()‎</code> القيمة 1، فيجب أن يخرج برنامجك من الحلقة ويطبع كلمة المرور المُخترقة، ويجب عليك أيضًا تجربة كلٍّ من الحروف الكبيرة والصغيرة لكل كلمة. يستغرق استعراض جميع الكلمات الكبيرة والصغيرة البالغ عددها 88000 كلمة من ملف القاموس بضع دقائق، ولذلك يجب عدم استخدام كلمة إنجليزية بسيطة لكلمات المرور الخاصة بك.
</p>

<h2 id="-14">
	الخلاصة
</h2>

<p>
	لا تُعد المعلومات النصية مُخصَّصة للملفات النصية العادية فقط، إذ يُحتمَل أن تتعامل مع ملفات PDF ومستندات وورد في كثير من الأحيان، حيث يمكنك استخدام وحدة <code>PyPDF2</code> لقراءة وكتابة مستندات PDF، ولكن قد لا تؤدي قراءة النص من مستندات PDF دائمًا إلى ترجمة مثالية للسلاسل النصية بسبب تنسيق ملف PDF المعقد، وقد لا تكون بعض ملفات PDF قابلة للقراءة على الإطلاق، وبالتالي لن يحالفك الحظ في هذه الحالات إن لم تدعم التحديثات المستقبلية لوحدة PyPDF2 ميزات إضافية لملفات PDF.
</p>

<p>
	تُعَد مستندات وورد أكثر موثوقية، ويمكنك قراءتها باستخدام وحدة <code>docx</code> الخاصة بحزمة <code>python-docx</code>. يمكنك معالجة النصوص في مستندات وورد باستخدام كائنات <code>Paragraph</code> و <code>Run</code>، ويمكن أيضًا إعطاء هذه الكائنات أنماطًا، بالرغم من أن هذه الأنماط يجب أن تكون من مجموعة الأنماط الافتراضية أو الأنماط الموجودة في المستند مسبقًا. يمكنك إضافة فقرات وعناوين وفواصل وصور جديدة إلى المستند في نهايته فقط.
</p>

<p>
	ترجِع العديد من قيود التعامل مع ملفات PDF ومستندات وورد إلى أن هذه التنسيقات تهدف إلى عرضها بصورة جيدة للقرّاء، عوضًا عن سهولة تحليلها من طرف البرمجيات، لذا سنوضّح في المقال التالي تنسيقين شائعين آخرين لتخزين المعلومات هما: ملفات JSON و CSV المُصمَّمة لتستخدمها الحواسيب، وسترى أن لغة بايثون يمكنها العمل مع هذه التنسيقات بسهولة أكبر.
</p>

<p>
	ترجمة -وبتصرُّف- للمقال <a href="https://automatetheboringstuff.com/2e/chapter15/" rel="external nofollow">Working with PDF and Word documents</a> لصاحبه Al Sweigart.
</p>

<h2 id="-15">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%AC%D8%AF%D8%A7%D9%88%D9%84-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%AC%D9%88%D8%AC%D9%84-google-sheets-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2386/" rel="">العمل مع جداول بيانات جوجل Google Sheets باستخدام لغة بايثون</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D9%81%D9%8A-%D9%85%D8%B3%D8%AA%D9%86%D8%AF%D8%A7%D8%AA-%D8%A5%D9%83%D8%B3%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-python-r2375/" rel="">الكتابة في مستندات إكسل باستخدام لغة بايثون Python</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D9%82%D8%B1%D8%A7%D8%A1%D8%A9-%D9%85%D8%B3%D8%AA%D9%86%D8%AF%D8%A7%D8%AA-%D8%AC%D8%AF%D8%A7%D9%88%D9%84-%D8%A5%D9%83%D8%B3%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-python-r2374/" rel="">قراءة مستندات جداول إكسل باستخدام لغة بايثون Python</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/apps/productivity/google-drive/google-docs/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%85%D8%B3%D8%AA%D9%86%D8%AF%D8%A7%D8%AA-%D8%AC%D9%88%D8%AC%D9%84-google-docs-r281/" rel="">مقدمة إلى تطبيق مستندات جوجل Google Docs</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2387</guid><pubDate>Fri, 09 Aug 2024 15:00:00 +0000</pubDate></item><item><title>&#x627;&#x644;&#x639;&#x645;&#x644; &#x645;&#x639; &#x62C;&#x62F;&#x627;&#x648;&#x644; &#x628;&#x64A;&#x627;&#x646;&#x627;&#x62A; &#x62C;&#x648;&#x62C;&#x644; Google Sheets &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646;</title><link>https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%AC%D8%AF%D8%A7%D9%88%D9%84-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%AC%D9%88%D8%AC%D9%84-google-sheets-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r2386/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_08/2087954535_.png.0949a08f6756f24ced8ecf926a1c64d6.png" /></p>
<p>
	يُعَد تطبيق جداول بيانات جوجل <a href="https://academy.hsoub.com/apps/productivity/google-drive/google-sheets/" rel="">Google Sheets</a> تطبيقًا مجانيًا ومستندًا إلى الويب ومتاحًا لأيّ شخص لديه حساب جوجل Google أو عنوان جيميل Gmail، وأصبح منافسًا مفيدًا وغنيًا بالميزات لبرنامج إكسل <a href="https://academy.hsoub.com/apps/productivity/office/microsoft-excel/" rel="">Excel</a>. تحتوي جداول بيانات جوجل على واجهة برمجة تطبيقات <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> خاصة بها، ولكن يمكن أن تكون هذه الواجهة مربكةً بعض الشيء في عملية التعلم والاستخدام. سنغطّي في هذا المقال وحدة EZSheets الخارجية والموثقة على <a href="https://ezsheets.readthedocs.io/en/latest/" rel="external nofollow">موقعها الرسمي</a>، والتي لا تُعَد كاملة الميزات مثل واجهة برمجة تطبيقات جداول البيانات الرسمية من جوجل، ولكنها تسهّل تنفيذ مهام جداول البيانات الشائعة.
</p>

<h2 id="ezsheets">
	تثبيت وإعداد وحدة EZSheets
</h2>

<p>
	يمكنك تثبيت وحدة EZSheets من خلال فتح نافذة طرفية جديدة وتشغيل الأمر <code>pip install --user ezsheets</code>، وستثبِّت وحدة EZSheets أيضًا كجزء من هذا التثبيت الوحدات <code>google-api-python-client</code> و <code>google-auth-httplib2</code> و <code>google-auth-oauthlib</code>، حيث تسمح هذه الوحدات لبرنامجك بتسجيل الدخول إلى خوادم جوجل وإنشاء طلبات واجهة برمجة التطبيقات <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>. تعالج وحدة EZSheets عملية التفاعل مع هذه الوحدات، لذلك لا داعي للقلق بشأن كيفية عملها.
</p>

<h3 id="credentialstokenfiles">
	الحصول على الاعتماديات Credentials والملفات المفتاحية Token Files
</h3>

<p>
	يجب تفعيل جداول بيانات جوجل <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%A7-%D9%87%D9%8A-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D9%84%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-api%D8%9F-r1512/" rel="">وواجهات برمجة تطبيقات</a> جوجل درايف Google Drive على حسابك على جوجل قبل أن تتمكّن من استخدام وحدة EZSheets. انتقل إلى صفحتي الويب التاليتين وانقر على زر التفعيل <code>Enable <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr></code> الموجودة في أعلى كل منهما:
</p>

<ul>
	<li>
		<a href="https://console.developers.google.com/apis/library/sheets.googleapis.com/" rel="external nofollow">sheets.googleapis.com</a>
	</li>
	<li>
		<a href="https://console.developers.google.com/apis/library/drive.googleapis.com/" rel="external nofollow">drive.googleapis.com</a>
	</li>
</ul>

<p>
	يجب أيضًا أن تحصل على ثلاثة ملفات، والتي يجب حفظها في المجلد نفسه لسكربت <a href="https://academy.hsoub.com/files/15-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86/" rel="">بايثون Python</a> الذي امتداده <code>‎.py</code> ويستخدم وحدة EZSheets، وهذه الملفات هي:
</p>

<ul>
	<li>
		ملف الاعتماديات واسمه <code>credentials-sheets.json</code>.
	</li>
	<li>
		مفتاح Token جداول بيانات جوجل واسمه <code>token-sheets.pickle</code>.
	</li>
	<li>
		مفتاح Token جوجل درايف واسمه <code>token-drive.pickle</code>.
	</li>
</ul>

<p>
	يولّد ملف الاعتماديات ملفات المفاتيح، وأسهل طريقة للحصول على ملف الاعتماديات هي الانتقال إلى صفحة <a href="https://developers.google.com/sheets/api/quickstart/python/" rel="external nofollow">Google Sheets Python Quickstart</a> والنقر على زر التفعيل الملون باللون الأزرق <code>Enable the Google Sheets <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr></code> كما هو موضح في الشكل التالي، ولكن يجب أن تسجّل الدخول إلى حسابك في جوجل لعرض هذه الصفحة:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="155633" href="https://academy.hsoub.com/uploads/monthly_2024_08/01_000148.jpg.847effe5b763c41991ed7e50fe81e74d.jpg" rel=""><img alt="01 000148" class="ipsImage ipsImage_thumbnailed" data-fileid="155633" data-unique="b640viquq" src="https://academy.hsoub.com/uploads/monthly_2024_08/01_000148.jpg.847effe5b763c41991ed7e50fe81e74d.jpg"> </a>
</p>

<p style="text-align: center;">
	الحصول على ملف credentials.json
</p>

<p>
	سيؤدي النقر على هذا الزر إلى ظهور نافذة تحتوي على رابط تنزيل ضبط العميل <code>Download Client Configuration</code> الذي يتيح لك تنزيل ملف <code>credentials.json</code>. أعِد تسمية هذا الملف إلى الاسم <code>credentials-sheets.json</code> وضعه في المجلد نفسه لسكربتات بايثون الخاصة بك.
</p>

<p>
	شغّل الأمر <code>import ezsheets</code> لاستيراد وحدة EZSheets بعد الحصول على الملف <code>credentials-sheets.json</code>، حيث ستفتح نافذة متصفح جديدة لتتمكّن من تسجيل الدخول إلى حسابك على جوجل عند استيراد وحدة EZSheets في المرة الأولى. انقر بعد ذلك على زر السماح <code>Allow</code> كما هو موضح في الشكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="155635" href="https://academy.hsoub.com/uploads/monthly_2024_08/02_000089.jpg.4950821c8058603464bc054822c17f29.jpg" rel=""><img alt="02 000089" class="ipsImage ipsImage_thumbnailed" data-fileid="155635" data-unique="wuddgid8f" src="https://academy.hsoub.com/uploads/monthly_2024_08/02_000089.jpg.4950821c8058603464bc054822c17f29.jpg"> </a>
</p>

<p style="text-align: center;">
	السماح لصفحة Python Quickstart بالوصول إلى حسابك على جوجل
</p>

<p>
	سبب ظهور الرسالة السابقة هو أنك نزّلتَ ملف الاعتماديات من صفحة Google Sheets Python Quickstart، وستفتح هذه النافذة مرتين: الأولى للوصول إلى جداول بيانات جوجل والثانية للوصول إلى جوجل درايف، حيث تستخدم وحدةُ EZSheets الوصولَ إلى جوجل درايف لرفع جداول البيانات وتنزيلها وحذفها.
</p>

<p>
	ستطالبك نافذة المتصفح بإغلاقه بعد تسجيل الدخول، وسيظهر الملفان <code>token-sheets.pickle</code> و <code>token-drive.pickle</code> في المجلد نفسه الذي يوجد فيه الملف <code>credentials-sheets.json</code>. ستجري هذه العملية فقط في المرة الأولى التي تشغِّل فيها الأمر <code>import ezsheets</code>.
</p>

<p>
	إذا واجهتَ خطأً بعد النقر على زر السماح "Allow" وكانت الصفحة معطَّلة، فتأكّد أولًا من تفعيل جداول بيانات جوجل وواجهات برمجة تطبيقات جوجل درايف من الروابط الموجودة في بداية هذا القسم. قد يستغرق الأمر بضع دقائق حتى تتمكّن <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">خوادم</a> جوجل من تسجيل هذا التغيير، لذا قد تضطر إلى الانتظار قبل أن تتمكّن من استخدام وحدة EZSheets.
</p>

<p>
	<strong>ملاحظة</strong>: لا تشارك ملفات الاعتماديات أو المفاتيح مع أيّ شخص، وتعامل معها مثل كلمات المرور.
</p>

<h3 id="">
	إبطال ملف الاعتماديات
</h3>

<p>
	إذا شاركتَ ملفات الاعتماديات أو المفاتيح مع شخصٍ ما عن طريق الخطأ، فلن يتمكّن هذا الشخص من تغيير كلمة مرور حسابك على جوجل، ولكن سيكون لديه حق الوصول إلى جداول بياناتك. يمكنك إبطال هذه الملفات بالانتقال إلى صفحة <a href="https://console.developers.google.com/" rel="external nofollow">طرفية المطور</a> على منصة سحابة جوجل Google Cloud Platform، ولكن يجب تسجيل الدخول إلى حسابك على جوجل لعرض هذه الصفحة. انقر على رابط الاعتماديات Credentials في الشريط الجانبي، ثم انقر على أيقونة سلة المهملات بجانب ملف الاعتماديات الذي شاركته عن طريق الخطأ، كما هو موضح في الشكل التالي:
</p>

<p style="text-align: center;">
	<img alt="03_000036.png" class="ipsImage ipsImage_thumbnailed" data-fileid="155636" data-ratio="50.43" data-unique="mb90ufj03" width="694" src="https://academy.hsoub.com/uploads/monthly_2024_08/03_000036.png.43f688732f46a34ec791236b88b53483.png">
</p>

<p style="text-align: center;">
	صفحة الاعتماديات في طرفية المطور على منصة سحابة جوجل
</p>

<p>
	يمكن إنشاء ملف اعتماديات جديد من هذه الصفحة من خلال النقر على زر إنشاء الاعتماديات "Create Credentials" وتحديد خيار معرّف عميل OAuth أو "OAuth Client ID" كما هو موضح في الشكل السابق، ثم حدّد الخيار "أخرى Other" بالنسبة لنوع التطبيق وسمِّ الملف بأيّ اسم تريده. سيُدرَج بعد ذلك ملف الاعتماديات الجديد في الصفحة، ويمكنك النقر على أيقونة التنزيل لتنزيله. سيكون للملف الذي ستنزِّله اسم ملف طويل ومعقد، لذا يجب إعادة تسميته إلى اسم الملف الافتراضي الذي تحاول الوحدة EZSheets تحميله وهو <code>credentials-sheets.json</code>. يمكنك أيضًا إنشاء ملف اعتماديات جديد من خلال النقر على زر تفعيل واجهة برمجة تطبيقات جداول بيانات جوجل "Enable the Google Sheets <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>" المذكور في القسم السابق.
</p>

<h2 id="spreadsheet">
	كائنات جدول البيانات Spreadsheet
</h2>

<p>
	يمكن أن يحتوي جدول البيانات Spreadsheet في جداول بيانات جوجل على أوراق Sheets متعددة والتي تُسمَّى أيضًا أوراق عمل Worksheets، وتحتوي كل ورقة على أعمدة Columns وصفوف Rows من القيم. يوضح الشكل التالي جدول بيانات بعنوان بيانات التعليم "Education Data"، والذي يحتوي على ثلاث أوراق بعنوان الطلاب "Students" والصفوف "Classes" والموارد "Resources"، ويُسمَّى العمود الأول من كل ورقة A، ويسمى الصف الأول 1:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="155637" href="https://academy.hsoub.com/uploads/monthly_2024_08/04_000127.jpg.95c4ab70ea5c48b35702f39ef1e94150.jpg" rel=""><img alt="04 000127" class="ipsImage ipsImage_thumbnailed" data-fileid="155637" data-unique="1vo1r1nfw" src="https://academy.hsoub.com/uploads/monthly_2024_08/04_000127.jpg.95c4ab70ea5c48b35702f39ef1e94150.jpg"> </a>
</p>

<p style="text-align: center;">
	جدول بيانات بعنوان "Education Data" مكوَّن من ثلاث أوراق
</p>

<p>
	سيتمثّل معظم عملك في تعديل كائنات الورقة <code>Sheet</code>، ولكن يمكنك أيضًا تعديل كائنات جدول البيانات <code>Spreadsheet</code>، كما سنوضّح في القسم التالي.
</p>

<h3 id="-1">
	إنشاء جداول البيانات وتحميلها وسردها
</h3>

<p>
	يمكنك إنشاء كائن <code>Spreadsheet</code> جديد من جدول بيانات موجود مسبقًا أو جدول بيانات فارغ أو جدول بيانات مرفوع على جداول بيانات جوجل، حيث يمكن إنشاء كائن <code>Spreadsheet</code> من جدول بياناتٍ موجود مسبقًا على جداول بيانات جوجل، ولكن أن تعرف السلسلة النصية لمعرّف جدول البيانات. يمكن العثور على المعرّف الفريد لجداول بيانات جوجل في عنوان URL، بعد الجزء <code>spreadsheets/d/‎</code> وقبل الجزء <code>‎/edit</code>، فمثلًا يوجد جدول البيانات الموضّح في الشكل السابق على عنوان URL الذي هو
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1619_58" style=""><span class="pln">https://docs.google.com/spreadsheets/d/1J-Jx6Ne2K_vqI9J2SO-TAXOFbxx_9tUjwnkPC22LjeU/edit#gid=151537240/‎</span></pre>

<p>
	وبالتالي يكون معرّفه <code>1J-Jx6Ne2K_vqI9J2SO-TAXOFbxx_9tUjwnkPC22LjeU</code>.
</p>

<p>
	<strong>ملاحظة</strong>: معرّفات جداول البيانات المُستخدَمة في هذا المقال خاصة بجداول بيانات حساب جوجل الخاص بالكاتب، إذ لن تعمل إذا أدخلتها في صدفتك التفاعلية Interactive Shell، لذا انتقل إلى <a href="https://sheets.google.com" rel="external nofollow">جداول بيانات جوجل</a> لإنشاء جداول بيانات ضمن حسابك ثم احصل على المعرّفات من شريط العناوين.
</p>

<p>
	مرّر معرّف جدول بياناتك بوصفه <a href="https://academy.hsoub.com/programming/python/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%B3%D9%84%D8%A7%D8%B3%D9%84-%D8%A7%D9%84%D9%86%D8%B5%D9%8A%D8%A9-%D9%81%D9%8A-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-r407/" rel="">سلسلةً نصية</a> إلى الدالة <code>ezsheets.Spreadsheet()‎</code> للحصول على كائن <code>Spreadsheet</code> لجدول البيانات الخاص بهذا المعرّف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1619_10" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> ezsheets
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss </span><span class="pun">=</span><span class="pln"> ezsheets</span><span class="pun">.</span><span class="typ">Spreadsheet</span><span class="pun">(</span><span class="str">'1J-Jx6Ne2K_vqI9J2SO-TAXOFbxx_9tUjwnkPC22LjeU'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss
</span><span class="typ">Spreadsheet</span><span class="pun">(</span><span class="pln">spreadsheetId</span><span class="pun">=</span><span class="str">'1J-Jx6Ne2K_vqI9J2SO-TAXOFbxx_9tUjwnkPC22LjeU'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">title
</span><span class="str">'Education Data'</span></pre>

<p>
	يمكنك أيضًا الحصول على كائن <code>Spreadsheet</code> لجدول بيانات موجود مسبقًا من خلال تمرير <a href="https://academy.hsoub.com/programming/general/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%B9%D9%86%D9%88%D8%A7%D9%86-url-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9%D9%87-r1435/" rel="">عنوان URL</a> الكامل لجدول البيانات إلى تلك الدالة، أو إذا كان هناك جدول بيانات واحد فقط في حسابك على جوجل له العنوان نفسه، فيمكنك تمرير عنوان جدول البيانات بوصفه سلسلة نصية.
</p>

<p>
	يمكنك إنشاء جدول بيانات جديد وفارغ من خلال استدعاء الدالة <code>ezsheets.createSpreadsheet()‎</code> وتمرير سلسلةٍ نصية إليها، حيث تمثل هذه السلسلة النصية عنوان جدول البيانات الجديد. لندخِل مثلًا ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1619_12" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> ezsheets
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss </span><span class="pun">=</span><span class="pln"> ezsheets</span><span class="pun">.</span><span class="pln">createSpreadsheet</span><span class="pun">(</span><span class="str">'Title of My New Spreadsheet'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">title
</span><span class="str">'Title of My New Spreadsheet'</span></pre>

<p>
	يمكنك رفع <a href="https://academy.hsoub.com/apps/productivity/office/microsoft-excel/%D9%86%D8%B8%D8%B1%D8%A9-%D8%B9%D8%A7%D9%85%D8%A9-%D8%B9%D9%84%D9%89-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D8%AC-%D9%85%D8%A7%D9%8A%D9%83%D8%B1%D9%88%D8%B3%D9%88%D9%81%D8%AA-%D8%A5%D9%83%D8%B3%D9%84-microsoft-excel-r930/" rel="">جدول بيانات إكسل Exce</a>l أو أوبن أوفيس OpenOffice أو CSV أو TSV موجود مسبقًا إلى جداول بيانات جوجل من خلال تمرير اسم ملف جدول البيانات إلى الدالة <code>ezsheets.upload()‎</code>. إذًا لندخِل ما يلي في الصدفة التفاعلية مع وضع اسم ملف جدول بياناتك مكان الملف <code>my_spreadsheet.xlsx</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1619_15" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> ezsheets
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss </span><span class="pun">=</span><span class="pln"> ezsheets</span><span class="pun">.</span><span class="pln">upload</span><span class="pun">(</span><span class="str">'my_spreadsheet.xlsx'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">title
</span><span class="str">'my_spreadsheet'</span></pre>

<p>
	يمكنك سرد جداول البيانات الموجودة على حسابك على جوجل من خلال استدعاء الدالة <code>listSpreadsheets()‎</code> التي تعيد قاموسًا Dictionary مفاتيحه هي معرّفات جداول البيانات وقيمه هي عناوين جداول البيانات. إذًا لندخِل ما يلي في الصدفة التفاعلية بعد رفع جدول البيانات:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1619_17" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ezsheets</span><span class="pun">.</span><span class="pln">listSpreadsheets</span><span class="pun">()</span><span class="pln">
</span><span class="pun">{</span><span class="str">'1J-Jx6Ne2K_vqI9J2SO-TAXOFbxx_9tUjwnkPC22LjeU'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Education Data'</span><span class="pun">}</span></pre>

<p>
	يمكنك بعد الحصول على كائن <code>Spreadsheet</code> استخدام سماته وتوابعه للتعامل مع جدول البيانات المُستضاف على جداول بيانات جوجل عبر الإنترنت.
</p>

<h3 id="attributesspreadsheet">
	سمات Attributes كائن جدول البيانات Spreadsheet
</h3>

<p>
	توجد البيانات الفعلية في الأوراق الخاصة بجدول البيانات، ولكن يحتوي كائن <code>Spreadsheet</code> على السمات <code>title</code> و <code>spreadsheetId</code> و <code>url</code> و <code>sheetTitles</code> و <code>sheets</code> للتعامل مع جدول البيانات. لندخِل ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1619_19" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> ezsheets
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss </span><span class="pun">=</span><span class="pln"> ezsheets</span><span class="pun">.</span><span class="typ">Spreadsheet</span><span class="pun">(</span><span class="str">'1J-Jx6Ne2K_vqI9J2SO-TAXOFbxx_9tUjwnkPC22LjeU'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">title         </span><span class="com"># عنوان جدول البيانات</span><span class="pln">
</span><span class="str">'Education Data'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">title </span><span class="pun">=</span><span class="pln"> </span><span class="str">'Class Data'</span><span class="pln"> </span><span class="com"># تغيير العنوان</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">spreadsheetId </span><span class="com"># ‫المعرّف الفريد (وهو سمة للقراءة فقط)</span><span class="pln">
</span><span class="str">'1J-Jx6Ne2K_vqI9J2SO-TAXOFbxx_9tUjwnkPC22LjeU'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">url           </span><span class="com"># ‫عنوان URL الأصلي (وهو سمة للقراءة فقط)</span><span class="pln">
</span><span class="str">'https://docs.google.com/spreadsheets/d/1J-Jx6Ne2K_vqI9J2SO-
TAXOFbxx_9tUjwnkPC22LjeU/'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">sheetTitles     </span><span class="com"># ‫عناوين جميع كائنات الورقة Sheet</span><span class="pln">
</span><span class="pun">(</span><span class="str">'Students'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Classes'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Resources'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">sheets          </span><span class="com"># كائنات الورقة‫ Sheet في جدول البيانات بالترتيب</span><span class="pln">
</span><span class="pun">(&lt;</span><span class="typ">Sheet</span><span class="pln"> sheetId</span><span class="pun">=</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> title</span><span class="pun">=</span><span class="str">'Students'</span><span class="pun">,</span><span class="pln"> rowCount</span><span class="pun">=</span><span class="lit">1000</span><span class="pun">,</span><span class="pln"> columnCount</span><span class="pun">=</span><span class="lit">26</span><span class="pun">&gt;,</span><span class="pln"> </span><span class="pun">&lt;</span><span class="typ">Sheet</span><span class="pln">
sheetId</span><span class="pun">=</span><span class="lit">1669384683</span><span class="pun">,</span><span class="pln"> title</span><span class="pun">=</span><span class="str">'Classes'</span><span class="pun">,</span><span class="pln"> rowCount</span><span class="pun">=</span><span class="lit">1000</span><span class="pun">,</span><span class="pln"> columnCount</span><span class="pun">=</span><span class="lit">26</span><span class="pun">&gt;,</span><span class="pln"> </span><span class="pun">&lt;</span><span class="typ">Sheet</span><span class="pln">
sheetId</span><span class="pun">=</span><span class="lit">151537240</span><span class="pun">,</span><span class="pln"> title</span><span class="pun">=</span><span class="str">'Resources'</span><span class="pun">,</span><span class="pln"> rowCount</span><span class="pun">=</span><span class="lit">1000</span><span class="pun">,</span><span class="pln"> columnCount</span><span class="pun">=</span><span class="lit">26</span><span class="pun">&gt;)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln">              </span><span class="com"># كائن الورقة الأول في جدول البيانات </span><span class="pln">
</span><span class="pun">&lt;</span><span class="typ">Sheet</span><span class="pln"> sheetId</span><span class="pun">=</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> title</span><span class="pun">=</span><span class="str">'Students'</span><span class="pun">,</span><span class="pln"> rowCount</span><span class="pun">=</span><span class="lit">1000</span><span class="pun">,</span><span class="pln"> columnCount</span><span class="pun">=</span><span class="lit">26</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">[</span><span class="str">'Students'</span><span class="pun">]</span><span class="pln">     </span><span class="com"># يمكن أيضًا الوصول إلى الأوراق باستخدام العنوان</span><span class="pln">
</span><span class="pun">&lt;</span><span class="typ">Sheet</span><span class="pln"> sheetId</span><span class="pun">=</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> title</span><span class="pun">=</span><span class="str">'Students'</span><span class="pun">,</span><span class="pln"> rowCount</span><span class="pun">=</span><span class="lit">1000</span><span class="pun">,</span><span class="pln"> columnCount</span><span class="pun">=</span><span class="lit">26</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">del</span><span class="pln"> ss</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln">          </span><span class="com"># حذف كائن الورقة الأول في جدول البيانات</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">sheetTitles     </span><span class="com"># ‫أصبح كائن الورقة "Students" محذوفًا</span><span class="pln">
</span><span class="pun">(</span><span class="str">'Classes'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Resources'</span><span class="pun">)</span></pre>

<p>
	إذا عدّل شخصٌ ما جدول البيانات من موقع جداول بيانات جوجل، فيمكن للسكربت الخاص بك تحديث كائن <code>Spreadsheet</code> ليطابق البيانات الموجودة على الإنترنت من خلال استدعاء التابع <code>refresh()‎</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1619_21" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">refresh</span><span class="pun">()</span></pre>

<p>
	لن يحدّث هذا التابع سمات كائن <code>Spreadsheet</code> فحسب، بل سيحدّث البيانات الموجودة في كائنات <code>Sheet</code> التي يحتوي عليها كائن <code>Spreadsheet</code>، وستنعكس التغييرات التي تجريها على كائن <code>Spreadsheet</code> في جدول البيانات الموجود على الإنترنت ضمن الزمن الحقيقي.
</p>

<h3 id="-2">
	تنزيل ورفع جداول البيانات
</h3>

<p>
	يمكنك تنزيل جدول بيانات جوجل بعددٍ من التنسيقات مثل: إكسل وأوبن أوفيس OpenOffice و CSV و TSV و PDF، ويمكنك أيضًا تنزيله كملف مضغوط ZIP يحتوي على ملفات <a href="https://wiki.hsoub.com/HTML" rel="external">HTML</a> لبيانات جدول البيانات، حيث تحتوي الوحدة EZSheets على دوالٍ لكل خيار من هذه الخيارات كما سنوضح فيما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1619_23" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> ezsheets
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss </span><span class="pun">=</span><span class="pln"> ezsheets</span><span class="pun">.</span><span class="typ">Spreadsheet</span><span class="pun">(</span><span class="str">'1J-Jx6Ne2K_vqI9J2SO-TAXOFbxx_9tUjwnkPC22LjeU'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">title
</span><span class="str">'Class Data'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">downloadAsExcel</span><span class="pun">()</span><span class="pln"> </span><span class="com"># تنزيل جدول البيانات كملف إكسل</span><span class="pln">
</span><span class="str">'Class_Data.xlsx'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">downloadAsODS</span><span class="pun">()</span><span class="pln"> </span><span class="com"># تنزيل جدول البيانات كملف أوبن أوفيس</span><span class="pln">
</span><span class="str">'Class_Data.ods'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">downloadAsCSV</span><span class="pun">()</span><span class="pln"> </span><span class="com"># تنزيل الورقة الأولى فقط كملف‫ CSV</span><span class="pln">
</span><span class="str">'Class_Data.csv'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">downloadAsTSV</span><span class="pun">()</span><span class="pln"> </span><span class="com"># تنزيل الورقة الأولى فقط كملف‫ TSV</span><span class="pln">
</span><span class="str">'Class_Data.tsv'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">downloadAsPDF</span><span class="pun">()</span><span class="pln"> </span><span class="com"># ‫تنزيل جدول البيانات كملف PDF</span><span class="pln">
</span><span class="str">'Class_Data.pdf'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">downloadAsHTML</span><span class="pun">()</span><span class="pln"> </span><span class="com"># تنزيل جدول البيانات كملف مضغوط‫ ZIP مؤلَّفٍ من ملفات HTML</span><span class="pln">
</span><span class="str">'Class_Data.zip'</span></pre>

<p>
	لاحظ أن الملفات التي لها تنسيق CSV و TSV يمكن أن تحتوي على ورقة واحدة فقط، لذلك إذا نزّلت جدول بيانات من جداول بيانات جوجل بهذا التنسيق، فستحصل على الورقة الأولى فقط، ولكن يمكنك تنزيل أوراق أخرى من خلال تغيير السمة <code>index</code> الخاصة بكائن <code>Sheet</code> إلى القيمة 0.
</p>

<p>
	تعيد جميع <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D8%AF%D9%88%D8%A7%D9%84-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-python-r2336/" rel="">دوال</a> التنزيل سلسلة نصية لاسم الملف الذي جرى تنزيله، ويمكنك أيضًا تحديد اسم ملفك لجدول البيانات من خلال تمرير اسم الملف الجديد إلى دالة التنزيل كما يلي، ويجب أن تعيد الدالة اسم الملف المُحدَّث:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1619_25" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">downloadAsExcel</span><span class="pun">(</span><span class="str">'a_different_filename.xlsx'</span><span class="pun">)</span><span class="pln">
</span><span class="str">'a_different_filename.xlsx'</span></pre>

<h3 id="-3">
	حذف جداول البيانات
</h3>

<p>
	يمكننا حذف جدول بيانات من خلال استدعاء التابع <code>delete()‎</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1619_27" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> ezsheets
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss </span><span class="pun">=</span><span class="pln"> ezsheets</span><span class="pun">.</span><span class="pln">createSpreadsheet</span><span class="pun">(</span><span class="str">'Delete me'</span><span class="pun">)</span><span class="pln"> </span><span class="com"># إنشاء جدول البيانات</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ezsheets</span><span class="pun">.</span><span class="pln">listSpreadsheets</span><span class="pun">()</span><span class="pln"> </span><span class="com"># التأكد من إنشاء جدول بيانات</span><span class="pln">
</span><span class="pun">{</span><span class="str">'1aCw2NNJSZblDbhygVv77kPsL3djmgV5zJZllSOZ_mRk'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'Delete me'</span><span class="pun">}</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">delete</span><span class="pun">()</span><span class="pln"> </span><span class="com"># حذف جدول البيانات</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ezsheets</span><span class="pun">.</span><span class="pln">listSpreadsheets</span><span class="pun">()</span><span class="pln">
</span><span class="pun">{}</span></pre>

<p>
	سينقل التابع <code>delete()‎</code> جدول بياناتك إلى مجلد سلة المهملات على جوجل درايف، حيث يمكنك عرض محتويات <a href="https://drive.google.com/drive/trash" rel="external nofollow">مجلد سلة المهملات</a>، ولكن يمكن حذف جدول البيانات نهائيًا من خلال تمرير القيمة <code>True</code> لوسيط الكلمة المفتاحية Keyword Argument الذي هو <code>permanent</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1619_29" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">delete</span><span class="pun">(</span><span class="pln">permanent</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">)</span></pre>

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

<h2 id="sheet">
	كائنات الورقة Sheet
</h2>

<p>
	يحتوي كائن <code>Spreadsheet</code> على كائن <code>Sheet</code> واحد أو أكثر، حيث تمثّل كائنات <code>Sheet</code> صفوف وأعمدة البيانات الموجودة في الورقة، ويمكنك الوصول إلى هذه الأوراق باستخدام عامل الأقواس المربعة وعدد صحيح يمثل الفهرس. تحتوي السمة <code>sheets</code> على مجموعة Tuple من كائنات <code>Sheet</code> بالترتيب الذي تظهر به في جدول البيانات. يمكنك الوصول إلى كائنات <code>Sheet</code> في جدول البيانات من خلال إدخال ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1619_31" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> ezsheets
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss </span><span class="pun">=</span><span class="pln"> ezsheets</span><span class="pun">.</span><span class="typ">Spreadsheet</span><span class="pun">(</span><span class="str">'1J-Jx6Ne2K_vqI9J2SO-TAXOFbxx_9tUjwnkPC22LjeU'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">sheets    </span><span class="com"># ‫كائنات الورقة Sheet في جدول البيانات بالترتيب</span><span class="pln">
</span><span class="pun">(&lt;</span><span class="typ">Sheet</span><span class="pln"> sheetId</span><span class="pun">=</span><span class="lit">1669384683</span><span class="pun">,</span><span class="pln"> title</span><span class="pun">=</span><span class="str">'Classes'</span><span class="pun">,</span><span class="pln"> rowCount</span><span class="pun">=</span><span class="lit">1000</span><span class="pun">,</span><span class="pln"> columnCount</span><span class="pun">=</span><span class="lit">26</span><span class="pun">&gt;,</span><span class="pln">
</span><span class="pun">&lt;</span><span class="typ">Sheet</span><span class="pln"> sheetId</span><span class="pun">=</span><span class="lit">151537240</span><span class="pun">,</span><span class="pln"> title</span><span class="pun">=</span><span class="str">'Resources'</span><span class="pun">,</span><span class="pln"> rowCount</span><span class="pun">=</span><span class="lit">1000</span><span class="pun">,</span><span class="pln"> columnCount</span><span class="pun">=</span><span class="lit">26</span><span class="pun">&gt;)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">sheets</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="com"># الحصول على كائن الورقة الأول في جدول البيانات</span><span class="pln">
</span><span class="pun">&lt;</span><span class="typ">Sheet</span><span class="pln"> sheetId</span><span class="pun">=</span><span class="lit">1669384683</span><span class="pun">,</span><span class="pln"> title</span><span class="pun">=</span><span class="str">'Classes'</span><span class="pun">,</span><span class="pln"> rowCount</span><span class="pun">=</span><span class="lit">1000</span><span class="pun">,</span><span class="pln"> columnCount</span><span class="pun">=</span><span class="lit">26</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln">        </span><span class="com"># الحصول أيضًا على كائن الورقة الأول في جدول البيانات</span><span class="pln">
</span><span class="pun">&lt;</span><span class="typ">Sheet</span><span class="pln"> sheetId</span><span class="pun">=</span><span class="lit">1669384683</span><span class="pun">,</span><span class="pln"> title</span><span class="pun">=</span><span class="str">'Classes'</span><span class="pun">,</span><span class="pln"> rowCount</span><span class="pun">=</span><span class="lit">1000</span><span class="pun">,</span><span class="pln"> columnCount</span><span class="pun">=</span><span class="lit">26</span><span class="pun">&gt;</span></pre>

<p>
	يمكنك أيضًا الحصول على كائن <code>Sheet</code> باستخدام عامل الأقواس المربعة وسلسلة نصية تمثّل اسم الورقة، وتحتوي السمة <code>sheetTitles</code> الخاصة بكائن <code>Spreadsheet</code> على مجموعةٍ تمثّل جميع عناوين الأوراق. إذًا لندخِل مثلًا ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1619_33" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">sheetTitles </span><span class="com"># ‫عناوين جميع كائنات الورقة Sheet في جدول البيانات</span><span class="pln">
</span><span class="pun">(</span><span class="str">'Classes'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Resources'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">[</span><span class="str">'Classes'</span><span class="pun">]</span><span class="pln"> </span><span class="com"># يمكن أيضًا الوصول إلى الأوراق باستخدام العنوان</span><span class="pln">
</span><span class="pun">&lt;</span><span class="typ">Sheet</span><span class="pln"> sheetId</span><span class="pun">=</span><span class="lit">1669384683</span><span class="pun">,</span><span class="pln"> title</span><span class="pun">=</span><span class="str">'Classes'</span><span class="pun">,</span><span class="pln"> rowCount</span><span class="pun">=</span><span class="lit">1000</span><span class="pun">,</span><span class="pln"> columnCount</span><span class="pun">=</span><span class="lit">26</span><span class="pun">&gt;</span></pre>

<p>
	يمكنك بعد الحصول على كائن <code>Sheet</code> قراءة البيانات منه وكتابة البيانات فيه باستخدام توابع كائن <code>Sheet</code> كما سنوضّح في القسم التالي.
</p>

<h3 id="-4">
	قراءة وكتابة البيانات
</h3>

<p>
	تحتوي أوراق عمل جداول بيانات جوجل على أعمدة وصفوف من الخلايا التي تحتوي على بيانات كما هو الحال في جداول بيانات إكسل، حيث يمكنك استخدام عامل الأقواس المربعة لقراءة البيانات من هذه الخلايا وكتابتها فيها. أنشئ مثلًا جدول بيانات جديد وأضِف البيانات إليه من خلال إدخال ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1619_35" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> ezsheets
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss </span><span class="pun">=</span><span class="pln"> ezsheets</span><span class="pun">.</span><span class="pln">createSpreadsheet</span><span class="pun">(</span><span class="str">'My Spreadsheet'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> sheet </span><span class="pun">=</span><span class="pln"> ss</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="com"># الحصول على الورقة الأولى في جدول البيانات</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> sheet</span><span class="pun">.</span><span class="pln">title
</span><span class="str">'Sheet1'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> sheet </span><span class="pun">=</span><span class="pln"> ss</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> sheet</span><span class="pun">[</span><span class="str">'A1'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'Name'</span><span class="pln"> </span><span class="com"># ‫ضبط القيمة في الخلية A1</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> sheet</span><span class="pun">[</span><span class="str">'B1'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'Age'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> sheet</span><span class="pun">[</span><span class="str">'C1'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'Favorite Movie'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> sheet</span><span class="pun">[</span><span class="str">'A1'</span><span class="pun">]</span><span class="pln"> </span><span class="com"># قراءة القيمة في الخلية‫ A1</span><span class="pln">
</span><span class="str">'Name'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> sheet</span><span class="pun">[</span><span class="str">'A2'</span><span class="pun">]</span><span class="pln"> </span><span class="com"># تعيد الخلايا الفارغة سلسلة نصية فارغة</span><span class="pln">
</span><span class="str">''</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> sheet</span><span class="pun">[</span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="com"># العمود 2 والصف 1 هو عنوان الخلية‫ B1 نفسه</span><span class="pln">
</span><span class="str">'Age'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> sheet</span><span class="pun">[</span><span class="str">'A2'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'Alice'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> sheet</span><span class="pun">[</span><span class="str">'B2'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="lit">30</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> sheet</span><span class="pun">[</span><span class="str">'C2'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'RoboCop'</span></pre>

<p>
	يجب أن ينتج عن هذه التعليمات جدول بيانات جوجل يشبه الشكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="155640" href="https://academy.hsoub.com/uploads/monthly_2024_08/05_000074.jpg.5855e4c30c428d2943daa1a27b86eef9.jpg" rel=""><img alt="05 000074" class="ipsImage ipsImage_thumbnailed" data-fileid="155640" data-unique="w7de5ykt0" src="https://academy.hsoub.com/uploads/monthly_2024_08/05_000074.jpg.5855e4c30c428d2943daa1a27b86eef9.jpg"> </a>
</p>

<p style="text-align: center;">
	جدول البيانات الذي أنشأناه باستخدام تعليمات المثال السابق
</p>

<p>
	يمكن لعدة مستخدمين تحديث الورقة في الوقت ذاته، لذا يمكنك تحديث البيانات المحلية في كائن <code>Sheet</code> من خلال استدعاء التابع <code>refresh()‎</code> الخاص بهذا الكائن:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1619_37" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> sheet</span><span class="pun">.</span><span class="pln">refresh</span><span class="pun">()</span></pre>

<p>
	تُحمَّل كافة البيانات الموجودة في كائن <code>Sheet</code> عند تحميل كائن <code>Spreadsheet</code> لأول مرة، وبالتالي يمكن قراءة البيانات مباشرةً، ولكن تتطلب كتابة القيم في جدول البيانات عبر الإنترنت اتصالًا بالشبكة ويمكن أن تستغرق حوالي ثانية واحدة، حيث إذا كان لديك آلاف الخلايا التي تريد تحديثها، فقد يكون تحديثها واحدةً تلو الأخرى بطيئًا جدًا.
</p>

<h3 id="-5">
	عنونة الأعمدة والصفوف
</h3>

<p>
	تعمل عنونة الخلايا في جداول بيانات جوجل كما هو الحال في إكسل، ولكن الفرق الوحيد بينهما هو احتواء جداول بيانات جوجل على أعمدة وصفوف تستند إلى القيمة 1، أي أن العمود أو الصف الأول موجود في الفهرس 1 وليس في الفهرس 0 على عكس فهارس القائمة المستندة إلى القيمة 0 في لغة بايثون. يمكنك تحويل العنوان الذي تنسيقه سلسلة نصية <code>'A2'</code> إلى عنوانٍ تنسيقه مجموعة <code>(column, row)</code> (والعكس صحيح) باستخدام الدالة <code>convertAddress()‎</code>. تحوّل الدالتان <code>getColumnLetterOf()‎</code> و <code>getColumnNumberOf()‎</code> أيضًا عنوان العمود من الحروف إلى الأعداد وبالعكس. لندخِل مثلًا ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1619_39" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> ezsheets
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ezsheets</span><span class="pun">.</span><span class="pln">convertAddress</span><span class="pun">(</span><span class="str">'A2'</span><span class="pun">)</span><span class="pln"> </span><span class="com"># تحويل العناوين‫...</span><span class="pln">
</span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ezsheets</span><span class="pun">.</span><span class="pln">convertAddress</span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln"> </span><span class="com"># ‏… وتحويلها بالعكس مرة أخرى</span><span class="pln">
</span><span class="str">'A2'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ezsheets</span><span class="pun">.</span><span class="pln">getColumnLetterOf</span><span class="pun">(</span><span class="lit">2</span><span class="pun">)</span><span class="pln">
</span><span class="str">'B'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ezsheets</span><span class="pun">.</span><span class="pln">getColumnNumberOf</span><span class="pun">(</span><span class="str">'B'</span><span class="pun">)</span><span class="pln">
</span><span class="lit">2</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ezsheets</span><span class="pun">.</span><span class="pln">getColumnLetterOf</span><span class="pun">(</span><span class="lit">999</span><span class="pun">)</span><span class="pln">
</span><span class="str">'ALK'</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ezsheets</span><span class="pun">.</span><span class="pln">getColumnNumberOf</span><span class="pun">(</span><span class="str">'ZZZ'</span><span class="pun">)</span><span class="pln">
</span><span class="lit">18278</span></pre>

<p>
	تُعَد العناوين التي لها تنسيق السلسلة النصية <code>'A2'</code> ملائمةً لكتابة العناوين في شيفرتك المصدرية، وتكون العناوين التي لها تنسيق المجموعة <code>(column, row)</code> ملائمةً إذا أردتَ التكرار على مجالٍ من العناوين واحتجتَ صيغةً رقمية للعمود، لذا تُعَد الدوال <code>convertAddress()‎</code> و <code>getColumnLetterOf()‎</code> و <code>getColumnNumberOf()‎</code> مفيدةً عندما تريد التحويل بين هذين التنسيقين.
</p>

<h3 id="-6">
	قراءة وكتابة الأعمدة والصفوف بأكملها
</h3>

<p>
	قد تستغرق كتابة البيانات ضمن خلية واحدة في كل مرة وقتًا طويلًا كما ذكرنا سابقًا، ولكن تحتوي وحدة EZSheets على توابع خاصة بكائن <code>Sheet</code> لقراءة وكتابة الأعمدة والصفوف بأكملها في الوقت ذاته، حيث يقرأ التابعان <code>getColumn()‎</code> و <code>getRow()‎</code> من الأعمدة والصفوف ويكتب التابعان <code>updateColumn()‎</code> و <code>updateRow()‎</code> في الأعمدة والصفوف. تنشِئ هذه التوابع طلبات إلى خوادم جداول بيانات جوجل لتحديث جدول البيانات، لذا يجب أن تكون متصلًا بالإنترنت. سنرفع في مثالنا جدول بيانات أسعار <a href="https://academy.hsoub.com/marketing/core-concepts-of-marketing/%D8%AA%D8%B9%D8%B1%D9%8A%D9%81-%D8%A7%D9%84%D9%85%D9%86%D8%AA%D8%AC-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9%D9%87-r433/" rel="">المنتجات</a> <code>produceSales.xlsx</code> من المقال السابق إلى جداول بيانات جوجل، حيث تبدو الصفوف الثمانية الأولى كما في الشكل التالي:
</p>

<p style="text-align: center;">
	<img alt="06_produceSalesSpreadsheet (1).png" class="ipsImage ipsImage_thumbnailed" data-fileid="155634" data-ratio="52.33" data-unique="dueqvm3m8" width="688" src="https://academy.hsoub.com/uploads/monthly_2024_08/06_produceSalesSpreadsheet(1).png.e6f47ac7326f8354bf3973ade0444380.png">
</p>

<p>
	يمكنك رفع جدول البيانات <code>produceSales.xlsx</code> من خلال إدخال ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1619_41" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> ezsheets
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss </span><span class="pun">=</span><span class="pln"> ezsheets</span><span class="pun">.</span><span class="pln">upload</span><span class="pun">(</span><span class="str">'produceSales.xlsx'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> sheet </span><span class="pun">=</span><span class="pln"> ss</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> sheet</span><span class="pun">.</span><span class="pln">getRow</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln"> </span><span class="com"># الصف الأول هو الصف 1 وليس الصف 0</span><span class="pln">
</span><span class="pun">[</span><span class="str">'PRODUCE'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'COST PER KiloGram'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'KiloGrams SOLD'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'TOTAL'</span><span class="pun">,</span><span class="pln"> </span><span class="str">''</span><span class="pun">,</span><span class="pln"> </span><span class="str">''</span><span class="pun">]</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> sheet</span><span class="pun">.</span><span class="pln">getRow</span><span class="pun">(</span><span class="lit">2</span><span class="pun">)</span><span class="pln">
</span><span class="pun">[</span><span class="str">'Potatoes'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'0.86'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'21.6'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'18.58'</span><span class="pun">,</span><span class="pln"> </span><span class="str">''</span><span class="pun">,</span><span class="pln"> </span><span class="str">''</span><span class="pun">]</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> columnOne </span><span class="pun">=</span><span class="pln"> sheet</span><span class="pun">.</span><span class="pln">getColumn</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> sheet</span><span class="pun">.</span><span class="pln">getColumn</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
</span><span class="pun">[</span><span class="str">'PRODUCE'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Potatoes'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Okra'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Fava beans'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Watermelon'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Garlic'</span><span class="pun">,</span><span class="pln">
</span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> sheet</span><span class="pun">.</span><span class="pln">getColumn</span><span class="pun">(</span><span class="str">'A'</span><span class="pun">)</span><span class="pln"> </span><span class="com"># ‫النتيجة نفسها للتعليمة getColumn(1)‎</span><span class="pln">
</span><span class="pun">[</span><span class="str">'PRODUCE'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Potatoes'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Okra'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Fava beans'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Watermelon'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Garlic'</span><span class="pun">,</span><span class="pln">
</span><span class="pun">--</span><span class="pln">snip</span><span class="pun">--</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> sheet</span><span class="pun">.</span><span class="pln">getRow</span><span class="pun">(</span><span class="lit">3</span><span class="pun">)</span><span class="pln">
</span><span class="pun">[</span><span class="str">'Okra'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'2.26'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'38.6'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'87.24'</span><span class="pun">,</span><span class="pln"> </span><span class="str">''</span><span class="pun">,</span><span class="pln"> </span><span class="str">''</span><span class="pun">]</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> sheet</span><span class="pun">.</span><span class="pln">updateRow</span><span class="pun">(</span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="pun">[</span><span class="str">'Pumpkin'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'11.50'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'20'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'230'</span><span class="pun">])</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> sheet</span><span class="pun">.</span><span class="pln">getRow</span><span class="pun">(</span><span class="lit">3</span><span class="pun">)</span><span class="pln">
</span><span class="pun">[</span><span class="str">'Pumpkin'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'11.50'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'20'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'230'</span><span class="pun">,</span><span class="pln"> </span><span class="str">''</span><span class="pun">,</span><span class="pln"> </span><span class="str">''</span><span class="pun">]</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> columnOne </span><span class="pun">=</span><span class="pln"> sheet</span><span class="pun">.</span><span class="pln">getColumn</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> i</span><span class="pun">,</span><span class="pln"> value </span><span class="kwd">in</span><span class="pln"> enumerate</span><span class="pun">(</span><span class="pln">columnOne</span><span class="pun">):</span><span class="pln">
</span><span class="pun">...</span><span class="pln">     </span><span class="com"># اجعل قائمة بايثون تحتوي على سلاسل نصية بأحرف كبيرة‫:</span><span class="pln">
</span><span class="pun">...</span><span class="pln">     columnOne</span><span class="pun">[</span><span class="pln">i</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> value</span><span class="pun">.</span><span class="pln">upper</span><span class="pun">()</span><span class="pln">
</span><span class="pun">...</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> sheet</span><span class="pun">.</span><span class="pln">updateColumn</span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> columnOne</span><span class="pun">)</span><span class="pln"> </span><span class="com"># تحديث العمود بأكمله في طلب واحد</span></pre>

<p>
	تسترد الدالتان <code>getRow()‎</code> و <code>getColumn()‎</code> البيانات من جميع الخلايا الموجودة في صف أو عمود محدد بوصفها قائمةً من القيم، وتصبح الخلايا الفارغة قيمًا لسلاسل نصية فارغة في القائمة. يمكنك تمرير رقم أو حرف العمود إلى الدالة <code>getColumn()‎</code> لإخبارها باسترداد بيانات عمودٍ معين، حيث وضّحنا في المثال السابق أن التعلمتين <code>getColumn(1)‎</code> و <code>getColumn('A')‎</code> تعيدان القائمة نفسها.
</p>

<p>
	تكتب الدالتان <code>updateRow()‎</code> و <code>updateColumn()‎</code> فوق البيانات الموجودة في الصف أو العمود على التوالي باستخدام قائمة القيم المُمرّرة إليهما، فمثلًا احتوى الصف الثالث في المثال السابق على معلومات حول البامية Okra في البداية، لكن أدّى استدعاء الدالة <code>updateRow()‎</code> إلى وضع بيانات حول اليقطين Pumpkin مكانها، ثم استدعينا الدالة <code>sheet.getRow(3)‎</code> مرةً أخرى لعرض القيم الجديدة في الصف الثالث.
</p>

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

<p>
	يمكن الحصول على كافة الصفوف دفعةً واحدة من خلال استدعاء التابع <code>getRows()‎</code> لإعادة قائمةٍ بجميع القوائم، حيث تمثل كل قائمة من القوائم الداخلية الموجودة ضمن القائمة الخارجية صفًا واحدًا من الورقة. يمكنك تعديل هذه القيم الموجودة في <a href="https://academy.hsoub.com/programming/general/%D9%87%D9%8A%D8%A7%D9%83%D9%84-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-data-structures/" rel="">هيكل البيانات</a> لتغيير اسم المنتج Produce Name وعدد الكيلوجرامات المباعة Kilograms Sold والتكلفة الإجمالية Total لبعض الصفوف، ثم تمرّرها إلى التابع <code>updateRows()‎</code> من خلال إدخال ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1619_43" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> rows </span><span class="pun">=</span><span class="pln"> sheet</span><span class="pun">.</span><span class="pln">getRows</span><span class="pun">()</span><span class="pln"> </span><span class="com"># الحصول على جميع الصفوف في جدول البيانات</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> rows</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="com"># فحص القيم الموجودة في الصف الأول</span><span class="pln">
</span><span class="pun">[</span><span class="str">'PRODUCE'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'COST PER KiloGrams'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'KiloGrams SOLD'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'TOTAL'</span><span class="pun">,</span><span class="pln"> </span><span class="str">''</span><span class="pun">,</span><span class="pln"> </span><span class="str">''</span><span class="pun">]</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> rows</span><span class="pun">[</span><span class="lit">1</span><span class="pun">]</span><span class="pln">
</span><span class="pun">[</span><span class="str">'POTATOES'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'0.86'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'21.6'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'18.58'</span><span class="pun">,</span><span class="pln"> </span><span class="str">''</span><span class="pun">,</span><span class="pln"> </span><span class="str">''</span><span class="pun">]</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> rows</span><span class="pun">[</span><span class="lit">1</span><span class="pun">][</span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'PUMPKIN'</span><span class="pln"> </span><span class="com"># تغيير اسم المنتج</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> rows</span><span class="pun">[</span><span class="lit">1</span><span class="pun">]</span><span class="pln">
</span><span class="pun">[</span><span class="str">'PUMPKIN'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'0.86'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'21.6'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'18.58'</span><span class="pun">,</span><span class="pln"> </span><span class="str">''</span><span class="pun">,</span><span class="pln"> </span><span class="str">''</span><span class="pun">]</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> rows</span><span class="pun">[</span><span class="lit">10</span><span class="pun">]</span><span class="pln">
</span><span class="pun">[</span><span class="str">'OKRA'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'2.26'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'40'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'90.4'</span><span class="pun">,</span><span class="pln"> </span><span class="str">''</span><span class="pun">,</span><span class="pln"> </span><span class="str">''</span><span class="pun">]</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> rows</span><span class="pun">[</span><span class="lit">10</span><span class="pun">][</span><span class="lit">2</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'400'</span><span class="pln"> </span><span class="com"># تغيير عدد الكيلوجرامات المباعة</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> rows</span><span class="pun">[</span><span class="lit">10</span><span class="pun">][</span><span class="lit">3</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'904'</span><span class="pln"> </span><span class="com"># تغيير التكلفة الإجمالية</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> rows</span><span class="pun">[</span><span class="lit">10</span><span class="pun">]</span><span class="pln">
</span><span class="pun">[</span><span class="str">'OKRA'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'2.26'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'400'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'904'</span><span class="pun">,</span><span class="pln"> </span><span class="str">''</span><span class="pun">,</span><span class="pln"> </span><span class="str">''</span><span class="pun">]</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> sheet</span><span class="pun">.</span><span class="pln">updateRows</span><span class="pun">(</span><span class="pln">rows</span><span class="pun">)</span><span class="pln"> </span><span class="com"># تحديث جدول البيانات عبر الإنترنت بالتغييرات التي أجريناها</span></pre>

<p>
	يمكنك تحديث الورقة بأكملها في طلب واحد من خلال تمرير قائمةٍ من القوائم المُعادة من الدالة <code>getRows()‎</code> والمُعدَّلة بالتغييرات التي أجريناها على الصفين 1 و 10 إلى الدالة <code>updateRows()‎</code>.
</p>

<p>
	لاحظ أن الصفوف الموجودة في ورقة جداول بيانات جوجل تحتوي على سلاسل نصية فارغة في نهايتها، لأن الورقة التي رفعناها تحتوي على 6 أعمدة، ولدينا 4 أعمدة فقط من البيانات. يمكنك قراءة عدد الصفوف والأعمدة في الورقة باستخدام السمتين <code>rowCount</code> و <code>columnCount</code>، ثم يمكنك تغيير حجم الورقة من خلال ضبط هاتين القيمتين.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1619_45" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> sheet</span><span class="pun">.</span><span class="pln">rowCount        </span><span class="com"># عدد الصفوف في الورقة</span><span class="pln">
</span><span class="lit">23758</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> sheet</span><span class="pun">.</span><span class="pln">columnCount     </span><span class="com"># عدد الأعمدة في الورقة</span><span class="pln">
</span><span class="lit">6</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> sheet</span><span class="pun">.</span><span class="pln">columnCount </span><span class="pun">=</span><span class="pln"> </span><span class="lit">4</span><span class="pln"> </span><span class="com"># تغيير عدد الأعمدة إلى 4</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> sheet</span><span class="pun">.</span><span class="pln">columnCount     </span><span class="com"># يصبح الآن عدد الأعمدة في الورقة 4</span><span class="pln">
</span><span class="lit">4</span></pre>

<p>
	يجب أن تحذف التعليمات السابقة العمودين الخامس والسادس من جدول بيانات "produceSales" كما هو موضّح في الشكل التالي:
</p>

<p style="text-align: center;">
	<img alt="07_000021.png" class="ipsImage ipsImage_thumbnailed" data-fileid="155632" data-ratio="36.85" data-unique="9jb1e0yen" width="844" src="https://academy.hsoub.com/uploads/monthly_2024_08/07_000021.png.a6e979e37e6ea9fd6eff4a24c80b4261.png">
</p>

<p>
	الورقة قبل (على اليسار) وبعد (على اليمين) تغيير عدد الأعمدة إلى 4.
</p>

<p>
	يمكن أن تحتوي جداول بيانات جوجل على ما يصل إلى 10 ملايين خلية وفقًا <a href="https://support.google.com/drive/answer/37603?hl=ar&amp;ref_topic=7000756&amp;sjid=3298178835497375619-EU" rel="external nofollow">لمركز المساعدة في جوجل درايف</a>، ولكن يُفضَّل أن تجعل الأوراق بالحجم الذي تحتاجه فقط لتقليل الوقت الذي يستغرقه تعديل البيانات وتحديثها.
</p>

<h3 id="-7">
	إنشاء وحذف الأوراق
</h3>

<p>
	تبدأ جميع جداول بيانات جوجل بورقة واحدة اسمها "Sheet1"، ولكن يمكنك إضافة أوراق إضافية إلى نهاية قائمة الأوراق باستخدام التابع <code>createSheet()‎</code> الذي تمرّر إليه سلسلة نصية لاستخدامها كعنوان للورقة الجديدة، ويمكن للوسيط الثاني الاختياري الخاص بهذا التابع تحديد فهرس العدد الصحيح للورقة الجديدة. يمكنك إنشاء جدول بيانات ثم إضافة أوراق جديدة إليه من خلال إدخال ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1619_47" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> ezsheets
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss </span><span class="pun">=</span><span class="pln"> ezsheets</span><span class="pun">.</span><span class="pln">createSpreadsheet</span><span class="pun">(</span><span class="str">'Multiple Sheets'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">sheetTitles
</span><span class="pun">(</span><span class="str">'Sheet1'</span><span class="pun">,)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">createSheet</span><span class="pun">(</span><span class="str">'Spam'</span><span class="pun">)</span><span class="pln"> </span><span class="com"># إنشاء ورقة جديدة في نهاية قائمة الأوراق</span><span class="pln">
</span><span class="pun">&lt;</span><span class="typ">Sheet</span><span class="pln"> sheetId</span><span class="pun">=</span><span class="lit">2032744541</span><span class="pun">,</span><span class="pln"> title</span><span class="pun">=</span><span class="str">'Spam'</span><span class="pun">,</span><span class="pln"> rowCount</span><span class="pun">=</span><span class="lit">1000</span><span class="pun">,</span><span class="pln"> columnCount</span><span class="pun">=</span><span class="lit">26</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">createSheet</span><span class="pun">(</span><span class="str">'Eggs'</span><span class="pun">)</span><span class="pln"> </span><span class="com"># إنشاء ورقة جديدة أخرى</span><span class="pln">
</span><span class="pun">&lt;</span><span class="typ">Sheet</span><span class="pln"> sheetId</span><span class="pun">=</span><span class="lit">417452987</span><span class="pun">,</span><span class="pln"> title</span><span class="pun">=</span><span class="str">'Eggs'</span><span class="pun">,</span><span class="pln"> rowCount</span><span class="pun">=</span><span class="lit">1000</span><span class="pun">,</span><span class="pln"> columnCount</span><span class="pun">=</span><span class="lit">26</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">sheetTitles
</span><span class="pun">(</span><span class="str">'Sheet1'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Spam'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Eggs'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">createSheet</span><span class="pun">(</span><span class="str">'Meat'</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="com"># إنشاء ورقة عند الفهرس 0 في قائمة الأوراق</span><span class="pln">
</span><span class="pun">&lt;</span><span class="typ">Sheet</span><span class="pln"> sheetId</span><span class="pun">=</span><span class="lit">814694991</span><span class="pun">,</span><span class="pln"> title</span><span class="pun">=</span><span class="str">'Meat'</span><span class="pun">,</span><span class="pln"> rowCount</span><span class="pun">=</span><span class="lit">1000</span><span class="pun">,</span><span class="pln"> columnCount</span><span class="pun">=</span><span class="lit">26</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">sheetTitles
</span><span class="pun">(</span><span class="str">'Meat'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Sheet1'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Spam'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Eggs'</span><span class="pun">)</span></pre>

<p>
	تضيف التعليمات السابقة ثلاث أوراق جديدة إلى جدول البيانات هي: "Meat" و"Spam" و"Eggs" بالإضافة إلى الورقة الافتراضية "Sheet1". تُرتَّب الأوراق الموجودة في جدول البيانات، وتضاف الأوراق الجديدة إلى نهاية القائمة إن لم تمرِّر وسيطًا ثانيًا إلى الدالة <code>createSheet()‎</code>، حيث يحدّد هذا الوسيط فهرس الورقة. أنشأنا في المثال السابق الورقة التي عنوانها "Meat" في الفهرس 0، مما يجعل الورقة "Meat" هي الورقة الأولى في جدول البيانات وإزاحة الأوراق الثلاث الأخرى بمقدار موضعٍ واحد، ويشبه ذلك سلوك تابع القائمة <code>insert()‎</code>. يمكنك رؤية الأوراق الجديدة على التبويبات الموجودة أسفل الشاشة كما هو موضَّح في الشكل التالي:
</p>

<p style="text-align: center;">
	<img alt="08_000114.png" class="ipsImage ipsImage_thumbnailed" data-fileid="155639" data-ratio="53.09" data-unique="3k5k20xms" width="614" src="https://academy.hsoub.com/uploads/monthly_2024_08/08_000114.png.c910878d3cdc4af7188012dd4b67a3f5.png">
</p>

<p>
	جدول بيانات الأوراق المتعددة "Multiple Sheets" بعد إضافة أوراق "Spam" و"Eggs" و"Meat"
</p>

<p>
	يحذف التابع <code>delete()‎</code> الخاص بالكائن <code>Sheet</code> ورقةً من جدول البيانات، ولكن إذا أدرتَ الاحتفاظ بالورقة مع حذف البيانات الموجودة فيها، فاستدعِ التابع <code>clear()‎</code> لمسح جميع الخلايا وجعل هذه الورقة ورقةً فارغة. إذا لندخِل ما يلي في الصدفة التفاعلية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1619_49" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">sheetTitles
</span><span class="pun">(</span><span class="str">'Meat'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Sheet1'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Spam'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Eggs'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">delete</span><span class="pun">()</span><span class="pln">      </span><span class="com"># ‫حذف الورقة الموجودة في الفهرس 0 أي الورقة "Meat"</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">sheetTitles
</span><span class="pun">(</span><span class="str">'Sheet1'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Spam'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Eggs'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">[</span><span class="str">'Spam'</span><span class="pun">].</span><span class="pln">delete</span><span class="pun">()</span><span class="pln"> </span><span class="com"># ‫حذف الورقة "Spam"</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">sheetTitles
</span><span class="pun">(</span><span class="str">'Sheet1'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Eggs'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> sheet </span><span class="pun">=</span><span class="pln"> ss</span><span class="pun">[</span><span class="str">'Eggs'</span><span class="pun">]</span><span class="pln">  </span><span class="com"># إسناد الورقة‫ "Eggs" إلى متغير</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> sheet</span><span class="pun">.</span><span class="pln">delete</span><span class="pun">()</span><span class="pln">      </span><span class="com"># ‫حذف الورقة "Eggs"</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">sheetTitles
</span><span class="pun">(</span><span class="str">'Sheet1'</span><span class="pun">,)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">clear</span><span class="pun">()</span><span class="pln">       </span><span class="com"># مسح جميع الخلايا الموجودة في الورقة‫ "Sheet1"</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss</span><span class="pun">.</span><span class="pln">sheetTitles      </span><span class="com"># الورقة‫ "Sheet1" فارغة ولكنها لا تزال موجودة</span><span class="pln">
</span><span class="pun">(</span><span class="str">'Sheet1'</span><span class="pun">,)</span></pre>

<p>
	يكون حذف الأوراق حذفًا نهائيًا، إذ لا توجد طريقة لاستعادة البيانات، ولكن يمكنك إنشاء <a href="https://academy.hsoub.com/apps/general/%D8%A7%D9%84%D9%86%D8%B3%D8%AE-%D8%A7%D9%84%D8%A7%D8%AD%D8%AA%D9%8A%D8%A7%D8%B7%D9%8A-%D9%88%D8%AD%D9%81%D8%B8-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%81%D9%8A-%D8%A7%D9%84%D8%B9%D8%A7%D9%84%D9%85-%D8%A7%D9%84%D8%B1%D9%82%D9%85%D9%8A-r375/" rel="">نسخة احتياطية</a> من الأوراق من خلال نسخها إلى جدول بيانات آخر باستخدام التابع <code>copyTo()‎</code> كما سنوضّح في القسم التالي.
</p>

<h3 id="-8">
	نسخ الأوراق
</h3>

<p>
	يحتوي كل كائن <code>Spreadsheet</code> على قائمةٍ مرتبة من كائنات <code>Sheet</code> الموجودة ضمنه، حيث يمكنك استخدام هذه القائمة لإعادة ترتيب الأوراق (كما وضّحنا في القسم السابق) أو نسخها إلى جداول بيانات أخرى، إذ يمكن نسخ كائن <code>Sheet</code> إلى كائن <code>Spreadsheet</code> آخر من خلال استدعاء التابع <code>copyTo()‎</code> الذي نمرّر إليه كائن <code>Spreadsheet</code> الهدف كوسيط. لندخِل ما يلي في الصدفة التفاعلية لإنشاء جدولي بيانات ونسخ بيانات جدول البيانات الأول إلى الورقة الأخرى:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1619_51" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> ezsheets
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss1 </span><span class="pun">=</span><span class="pln"> ezsheets</span><span class="pun">.</span><span class="pln">createSpreadsheet</span><span class="pun">(</span><span class="str">'First Spreadsheet'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss2 </span><span class="pun">=</span><span class="pln"> ezsheets</span><span class="pun">.</span><span class="pln">createSpreadsheet</span><span class="pun">(</span><span class="str">'Second Spreadsheet'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss1</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln">
</span><span class="pun">&lt;</span><span class="typ">Sheet</span><span class="pln"> sheetId</span><span class="pun">=</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> title</span><span class="pun">=</span><span class="str">'Sheet1'</span><span class="pun">,</span><span class="pln"> rowCount</span><span class="pun">=</span><span class="lit">1000</span><span class="pun">,</span><span class="pln"> columnCount</span><span class="pun">=</span><span class="lit">26</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss1</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">updateRow</span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="pun">[</span><span class="str">'Some'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'data'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'in'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'the'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'first'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'row'</span><span class="pun">])</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss1</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">copyTo</span><span class="pun">(</span><span class="pln">ss2</span><span class="pun">)</span><span class="pln"> </span><span class="com"># نسخ الورقة‫ Sheet1 الخاصة بجدول البيانات ss1 إلى جدول البيانات ss2</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss2</span><span class="pun">.</span><span class="pln">sheetTitles    </span><span class="com"># ‫سيحتوي جدول البيانات ss2 على نسخة من الورقة Sheet1 الخاصة بجدول البيانات ss1 Sheet1</span><span class="pln">
</span><span class="pun">(</span><span class="str">'Sheet1'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'Copy of Sheet1'</span><span class="pun">)</span></pre>

<p>
	لاحظ تسمية الورقة المنسوخة بالاسم <code>Copy of Sheet1</code>، لأن جدول البيانات الهدف (<code>ss2</code> في المثال السابق) يحتوي مسبقًا على ورقة بالاسم <code>Sheet1</code>. تظهر الأوراق المنسوخة في نهاية قائمة أوراق جدول البيانات الهدف، ولكن يمكنك تغيير السمة <code>index</code> لإعادة ترتيبها في جدول البيانات الجديد.
</p>

<h2 id="quotas">
	التعامل مع الحصص Quotas في جداول بيانات جوجل
</h2>

<p>
	تُعَد جداول بيانات جوجل متاحةً عبر الإنترنت، لذا من السهل مشاركة الأوراق بين عدة مستخدمين يمكنهم جميعًا الوصول إلى الأوراق في وقتٍ واحد، ولكن سيؤدّي ذلك إلى أن تكون قراءة الأوراق وتحديثها أبطأ من قراءة وتحديث ملفات إكسل المخزَّنة محليًا على قرص حاسوبك الصلب. تفرض جداول بيانات جوجل أيضًا قيودًا على عدد عمليات القراءة والكتابة التي يمكنك إجراؤها.
</p>

<p>
	يُقيَّد مستخدمو جداول بيانات جوجل بإنشاء 250 جدول بيانات جديد يوميًا، ويمكن لحسابات جوجل المجانية إجراء 100 طلب قراءة و100 طلب كتابة في كل 100 ثانية وفقًا لإرشادات مطوري جوجل، إذ ستؤدي محاولة تجاوز هذه الحصة إلى رفع الاستثناء <code>googleapiclient.errors.HttpError</code> أو "Quota exceeded for quota group" الذي يمثّل تجاوز الحصة المتاحة، حيث تلتقط الوحدة EZSheets تلقائيًا هذا الاستثناء وتعيد محاولة الطلب. إذا حدث ذلك، فستستغرق استدعاءات الدوال لقراءة البيانات أو كتابتها عدة ثوانٍ أو حتى دقيقة أو دقيقتين قبل أن تعيد شيئًا ما، وإذا استمر الطلب في الفشل، وهو أمرٌ ممكن إذا أجرى سكربتٌ آخر يمتلك الاعتماديات نفسها طلباتٍ أيضًا، فستعيد الوحدة EZSheets رفعَ هذا الاستثناء.
</p>

<p>
	يؤدي ذلك إلى أنه قد تستغرق استدعاءات توابع الوحدة EZSheets عدة ثوانٍ قبل أن تعيد شيئًا ما. إذا أردتَ عرضَ حجم استخدامك لواجهة برمجة التطبيقات أو زيادةَ حصتك، فانتقل إلى صفحة <a href="https://console.developers.google.com/quotas/" rel="external nofollow">IAM &amp; Admin Quotas</a> للتعرف على كيفية الدفع مقابل زيادة حجم الاستخدام. إذا أردتَ التعامل مع استثناءات <code>HttpError</code> بنفسك، فيمكنك ضبط <code>ezsheets.IGNORE_QUOTA</code> على القيمة <code>True</code>، وسترفع توابع الوحدة EZSheets هذه الاستثناءات عندما تواجهها.
</p>

<h2 id="-10">
	مشاريع للتدريب
</h2>

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

<h3 id="googleforms">
	برنامج لتنزيل بيانات نماذج جوجل Google Forms
</h3>

<p>
	تتيح لك نماذج جوجل إنشاء نماذج بسيطة عبر الإنترنت تسهّل جمع المعلومات من الأشخاص، حيث تُخزَّن المعلومات التي يدخلها هؤلاء الأشخاص في النموذج ضمن جداول بيانات جوجل. جرّب كتابة برنامجٍ يمكنه تنزيل معلومات النموذج التي أرسلها المستخدمون تلقائيًا، لذا انتقل إلى <a href="https://docs.google.com/forms/" rel="external nofollow">نماذج جوجل</a> وأنشئ نموذجًا جديدًا، حيث سيكون هذا النموذج فارغًا، ثم أضِف الحقول إلى النموذج الذي يطلب من المستخدم اسمه وعنوان بريده الإلكتروني، ثم انقر على زر "إرسال Send" في الجزء العلوي الأيمن للحصول على رابط لنموذجك الجديد مثل الرابط <code><a href="https://goo.gl/forms/QZsq5sC2Qe4fYO592/%E2%80%8E" ipsnoembed="false" rel="external nofollow">https://goo.gl/forms/QZsq5sC2Qe4fYO592/‎</a></code>، وحاول إدخال بعض الأمثلة على الردود في هذا النموذج.
</p>

<p>
	انقر على الزر الأخضر "Create Spreadsheet" في تبويب "الردود Responses" في نموذجك لإنشاء جدول بيانات جوجل الذي سيحتوي على الردود التي يرسلها المستخدمون. يُفترَض أن تشاهد إجاباتك في الصفوف الأولى من جدول البيانات. اكتب بعد ذلك سكربت بايثون مع استخدام الوحدة EZSheets لجمع قائمة بعناوين البريد الإلكتروني في جدول البيانات.
</p>

<h3 id="-11">
	برنامج لتحويل جداول البيانات إلى تنسيقات أخرى
</h3>

<p>
	يمكنك استخدام جداول بيانات جوجل لتحويل ملف جدول بيانات إلى تنسيقات أخرى، لذا جرّب كتابة سكربت يمرر ملفًا مُرسَلًا إلى الدالة <code>upload()‎</code>.نزّل جدول البيانات بعد رفعه على جداول بيانات جوجل باستخدام الدوال <code>downloadAsExcel()‎</code> و <code>downloadAsODS()‎</code> وغيرها من الدوال المماثلة لإنشاء نسخة من جدول البيانات بتنسيقات أخرى.
</p>

<h3 id="-12">
	برنامج للعثور على الأخطاء في جدول البيانات
</h3>

<p>
	لنفترض أن لدينا جدول بيانات يحتوي على إجمالي عدد حبات الفاصولياء مرفوع على جداول بيانات جوجل، حيث يكون جدول البيانات قابلًا للعرض، ولكنه غير قابل للتحرير، ويمكنك <a href="https://docs.google.com/spreadsheets/d/1jDZEdvSIh4TmZxccyy0ZXrH-ELlrwq8_YYiZrEOB4jg/edit?usp=sharing/" rel="external nofollow">الاطلاع عليه في متصفحك</a> والحصول عليه باستخدام الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_1619_53" style=""><span class="pun">&gt;&gt;&gt;</span><span class="pln"> </span><span class="kwd">import</span><span class="pln"> ezsheets
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> ss </span><span class="pun">=</span><span class="pln"> ezsheets</span><span class="pun">.</span><span class="typ">Spreadsheet</span><span class="pun">(</span><span class="str">'1jDZEdvSIh4TmZxccyy0ZXrH-ELlrwq8_YYiZrEOB4jg'</span><span class="pun">)</span></pre>

<p>
	أعمدة الورقة الأولى في جدول البيانات هي عدد حبات الفاصولياء في الجرة "Beans per Jar" وعدد الجِرار "Jars" وعدد حبات الفاصولياء الكلي "Total Beans"، حيث ينتج العمود "Total Beans" من ضرب الأعداد الموجودة في العمودين "Beans per Jar" و "Jars"، ولكن يوجد خطأ في أحد الصفوف البالغ عددها 15000 صفًا في هذه الورقة. يُعَد ذلك عددًا كبيرًا جدًا من الصفوف التي لا يمكن التحقق منها يدويًا، ولكن يمكنك كتابة سكربت يتحقق من العمود "Total Beans".
</p>

<p>
	يمكنك الوصول إلى الخلايا الفردية في صف باستخدام <code>ss[0].getRow(rowNum)‎</code>، حيث <code>ss</code> هو كائن <code>Spreadsheet</code> و <code>rowNum</code> هو رقم الصف، وتذكّر أن أرقام الصفوف في جداول بيانات جوجل تبدأ من العدد 1 وليس من 0. ستكون قيم الخلايا سلاسلًا نصية، لذا يجب تحويلها إلى أعداد صحيحة حتى يتمكّن برنامجك من العمل معها. يُقيَّم التعبير <code>int(ss[0].getRow(2)[0]) * int(ss[0].getRow(2)[1]) == int(ss[0].getRow(2)[2])‎</code> على القيمة <code>True</code> إذا احتوى الصف على القيمة الإجمالية الصحيحة، لذا ضع هذا الشيفرة البرمجية في حلقة لتحديد الصف الموجود في الورقة الذي يحتوي على القيمة الإجمالية غير الصحيحة.
</p>

<h2 id="-13">
	الخلاصة
</h2>

<p>
	يُعَد تطبيق جداول بيانات جوجل تطبيقًا شائعًا لجداول البيانات عبر الإنترنت التي تعمل في متصفحك. يمكنك تنزيل جداول البيانات وإنشاؤها وقراءتها وتعديلها باستخدام الوحدة الخارجية EZSheets التي تمثّل جداول البيانات بوصفها كائنات <code>Spreadsheet</code> التي تحتوي على قائمة مرتبة من كائنات <code>Sheet</code>، وتحتوي كل ورقة على أعمدة وصفوف من البيانات التي يمكنك قراءتها وتحديثها بطرق متعددة.
</p>

<p>
	تسهّل جداول بيانات جوجل مشاركة البيانات وتعديلها بصورة جماعية، ولكن عيبها الرئيسي هو السرعة، إذ يجب عليك تحديث جداول البيانات باستخدام طلبات الويب، مما يؤدي إلى أن يستغرق التنفيذ بضع ثوانٍ، ولكن لن يؤثر هذا القيد على سكربتات بايثون التي تستخدم وحدة EZSheets بالنسبة لمعظم الأغراض. تحدّ جداول بيانات جوجل أيضًا من عدد المرات التي يمكنك فيها إجراء التغييرات.
</p>

<p>
	<strong>ملاحظة</strong>: يمكنك الحصول على التوثيق الكامل لميزات الوحدة EZSheet من <a href="https://ezsheets.readthedocs.io/" rel="external nofollow">موقعها الرسمي</a>.
</p>

<p>
	ترجمة -وبتصرُّف- للمقال <a href="https://automatetheboringstuff.com/2e/chapter14/" rel="external nofollow">Working with Google Sheets</a> لصاحبه Al Sweigart.
</p>

<h2 id="-14">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D9%81%D9%8A-%D9%85%D8%B3%D8%AA%D9%86%D8%AF%D8%A7%D8%AA-%D8%A5%D9%83%D8%B3%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-python-r2375/" rel="">الكتابة في مستندات إكسل باستخدام لغة بايثون Python</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D9%82%D8%B1%D8%A7%D8%A1%D8%A9-%D9%85%D8%B3%D8%AA%D9%86%D8%AF%D8%A7%D8%AA-%D8%AC%D8%AF%D8%A7%D9%88%D9%84-%D8%A5%D9%83%D8%B3%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-python-r2374/" rel="">قراءة مستندات جداول إكسل باستخدام لغة بايثون Python</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/apps/productivity/google-drive/google-docs/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%85%D8%B3%D8%AA%D9%86%D8%AF%D8%A7%D8%AA-%D8%AC%D9%88%D8%AC%D9%84-google-docs-r281/" rel="">مقدمة إلى تطبيق مستندات جوجل Google Docs</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2386</guid><pubDate>Fri, 02 Aug 2024 15:00:00 +0000</pubDate></item></channel></rss>
