إبراهيم البحيصي

الأعضاء
  • المساهمات

    35
  • تاريخ الانضمام

  • تاريخ آخر زيارة

  • Days Won

    2

السُّمعة بالموقع

13 جيدة

المعلومات الشخصية

  • النبذة الشخصية مبرمج, وكاتب تقني, لدي معرفة جيدة في أنظمة قواعد البيانات مثل Oracle و MySql. محب للبايثون.
    "بايثونات" مدونتي وأكتب عليها من فترة لأخرى.
  • الموقع

2 متابعين

  1. المصفوفة في الجافا سكريبت تتكون من قائمة عناصر، وتتيح الجافا سكريبت للمبرمجين العديد من الوظائف التي تعمل على المصفوفات. الوظائف التي تقوم بالتعديل على المصفوفة الأصلية تُعرف بتوابع التعديل (Mutator Methods)، والوظائف التي تُعيد قيمة جديدة أو شكل آخر من المصفوفة تُعرف بتوابع الوصول (Accessor Methods). يوجد نوع ثالث من التوابع يُسمى بتوابع التكرار (Iteration Methods) والتي سنتناولها بالشرح في هذا المقال. تُتيح توابع التكرار العمل على كل عنصر في المصفوفة كلٌ على حدة، وترتبط هذه التوابع بشكل أساسي بحلقات التكرار. للاستفادة أكثر من هذا الدرس، يجب أن يكون لديك معرفة مُسبقة بكيفية إنشاء المصفوفات، فهرستها، التعديل عليها والمرور على عناصرها بواسطة حلقة التكرار. تستطيع مراجعة درس فهم المصفوفات في الجافا سكريبت لأخذ فكرة عما سبق. ما سنتناوله في هذا الدرس يشمل شرح استخدام وظائف التكرار لتنفيذ حلقة التكرار على المصفوفات وإجراء عمليات على كل عنصر في المصفوفة، توضيح كيفية ترشيح نتائج العمل على المصفوفة، تلخيص عناصر المصفوفة في قيمة واحدة والبحث عن قيم وفهارس معينة. فهم الدوال السهمية (Arrow Functions) العديد من الأمثلة في هذا المقال ستستخدم صيغة الدوال السهمية في الجافا سكريبت والتي يتم تمثيلها بواسطة علامة المساواة ثم علامة الأكبر من <=. الدالة في الجافا سكريبت عبارة عن كتلة من الشيفرة البرمجية يتم تنفيذها وإعادة استخدامها أكثر من مرة، وعادةً ما تُكتب وفق الصيغة التالية: var example = function() { // code to execute } example(); النسخة الأخيرة من الجافا السكريبت -حتى وقت كتابة هذا الدرس- تسمح باستخدام الدوال السهمية والتي تُكتب وفق الصيغة التالية: var example = () => { // code to execute } example(); في الدوال السهمية تُكتب الأقواس لاستقبال معاملات الدالة. في حالة كان لدينا مُعامل واحد فقط فنستطيع حينها التخلي عن كتابة الأقواس كما في الصيغة التالية: var example = parameter1 => { // code to execute } سنعتمد في شرح هذا الدرس على الدوال السهمية، وللاطلاع أكثر على هذا الموضوع، تستطيع الذهاب الى هذا المرجع . الوظيفة ()forEach تقوم الوظيفة ()forEach باستدعاء دالة ليتم تنفيذها على كل عنصر في مصفوفة معينة. فمثلًا، نفترض وجود المصفوفة fish بالعناصر التالية: let fish = [ "piranha", "barracuda", "cod", "eel" ]; نستطيع استخدام الوظيفة ()forEach لطباعة كل عنصر في المصفوفة fish على الطرفية: // Print out each item in the array fish.forEach(individualFish => { console.log(individualFish); }) Output piranha barracuda cod eel يوجد طريقة أخرى لعمل ما سبق وذلك باستخدام جملة التكرار for بالإضافة لاستخدام الخاصية length: // Loop through the length of the array for (let i = 0; i < fish.length; i++) { console.log(fish[i]); } استخدام جملة for السابقة في طباعة كل عنصر على حدة يعطينا نفس النتيجة عند استخدام ()forEach، ولكن استخدام الطريقة ()forEach يُعتبر خيارًا مختصرًا ومباشرًا وأفضل لمثل هذه الحالات. الوظيفة ()map تقوم الوظيفة ()map ببناء مصفوفة ناتجة عن استدعاء دالة على عناصر مصفوفة أخرى. نستطيع طباعة عناصر مصفوفة عنصر-عنصر كمثال توضيحي على كيفية استخدام وظيفة ()map ولكن على العكس من الوظيفة ()forEach، لابد من حفظ ناتج الوظيفة في متغير جديد. let fish = [ "piranha", "barracuda", "cod", "eel" ]; // Print out each item in the array let printFish = fish.map(individualFish => { console.log(individualFish); }); printFish; Output piranha barracuda cod eel مثال أخر على استخدام الوظيفة ()map، حيث نستطيع أيضًا أن نُغير قيمة كل عنصر في مصفوفة وحفظ العناصر الجديدة الناتجة عن هذا التغيير في مصفوفة أخرى. فمثلًا، يمكننا إضافة الحرف s الى نهاية كل عنصر في المصفوفة fish لجعل الأسماء أسماء جمع. // Pluralize all items in the fish array let pluralFish = fish.map(individualFish => { return `${individualFish}s`; }); pluralFish; Output [ 'piranhas', 'barracudas', 'cods', 'eels' ] المصفوفة الأصلية fish لم تتغير، ولكن المصفوفة الجديدة pluralFish أصبحت الان تحتوي على النسخة المُعدلة من المصفوفة الأصلية. الوظيفة ()filter تقوم الوظيفة ()filter بإنشاء مصفوفة جديدة تحتوي على عناصر من المصفوفة الأصلية تتوافق مع شرط معين. الهدف الرئيسي من هذه الوظيفة هو إجراء عملية الترشيح، فمثلًا، نستطيع أن نحصل من الوظيفة ()filter على مصفوفة جديدة تحتوي على العناصر التي تبدأ بحرف مُعين، وذلك بالاستفادة من خاصية فهرسة النصوص التي تُمكننا من الحصول على الحرف الأول لكل عنصر. let seaCreatures = [ "shark", "whale", "squid", "starfish", "narwhal" ]; // Filter all creatures that start with "s" into a new list let filteredList = seaCreatures.filter(creature => { return creature[0] === "s"; }); filteredList; Output [ 'shark', 'squid', 'starfish' ] في المثال السابق اختبرنا جميع العناصر التي تبدأ بالحرف s وحفظنا النتيجة في مصفوفة جيدة باسم filteredList. الوظيفة ()reduce تقوم الوظيفة ()reduce باختصار مصفوفة في قيمة واحدة، وعادةً يُرى هذا الاستخدام مع المصفوفات التي تحتوي على أرقام، مثل الحصول على مجموع عناصر مصفوفة: let numbers = [ 42, 23, 16, 15, 4, 8 ]; // Get the sum of all numerical values let sum = numbers.reduce((a, b) => { return a + b; }); sum; Output 108 تُستخدم الوظيفة ()reduce أيضًا مع النصوص وأنواع البيانات الأخرى، وتكون القيمة المُرجعة من الوظيفة إما رقم أو نص أو أي نوع بيانات أخر. الوظيفة ()find تُعيد الوظيفة ()find أول عنصر من مصفوفة يتوافق مع شرط مُعين. لتوضيح ذلك، سوف نُنشئ مصفوفة تحتوي على أسماء مخلوقات بحرية، وسنستخدم الوظيفة ()find في إيجاد المخلوقات البحرية ذات النوع الرخوي مثل الأخطبوط (من الرخويات). let seaCreatures = [ "whale", "octopus", "shark", "cuttlefish", "flounder" ]; // Check if a given value is a cephalopod const isCephalopod = cephalopod => { return [ "cuttlefish", "octopus" ].includes(cephalopod); } seaCreatures.find(isCephalopod); Output Octopus بما أن “octopus” هو العنصر الأول في المصفوفة seaCreatures الذي ينطبق عليه الشرط، فإنه هو القيمة الأولى التي تم إرجاعها. نستفيد من الوظيفة ()find في عملنا مع المصفوفات التي تحتوي على قيمة عديدة. الوظيفة ()findIndex تُعيد الوظيفة ()findIndex فهرس أول عنصر من مصفوفة يتوافق مع شرط مُعين. سنستخدم نفس المثال السابق في وظيفة ()find لإيجاد فهرس أول عنصر من المصفوفة من النوع الرخوي. let seaCreatures = [ "whale", "octopus", "shark", "cuttlefish", "flounder" ]; باستخدام المصفوفة isCephalopod مع الوظيفة ()findIndex سنحصل على رقم الفهرس للعنصر بدلا من قيمته. // Check if a given value is a cephalopod const isCephalopod = cephalopod => { return [ "cuttlefish", "octopus" ].includes(cephalopod); } seaCreatures.findIndex(isCephalopod); Output 1 العنصر “octopus” هو العنصر الأول من المصفوفة الذي ينطبق عليه الشرط وموقعه في الفهرس رقم 1 وهي القيمة التي حصلنا عليها. في حالة لم ينطبق الشرط على أية عنصر في المصفوفة، فإن القيمة المرجعة هي -1. const isThereAnEel = eel => { return [ "eel" ].includes(eel); } seaCreatures.findIndex Output -1 خاتمة في هذا الدرس قمنا بمراجعة أغلب وظائف توابع التكرار في الجافا سكريبت. وظائف التكرار تعمل على كل عنصر في المصفوفة كلٌ على حدة وغالبًا ما يتم تطبيق دالة جديدة معها. تم شرح كيفية المرور على المصفوفة وعناصرها من خلال حلقة تكرار، تغيير قيمة العنصر في المصفوفة، ترشيح المصفوفة، اختصار المصفوفة في قيمة واحدة والبحث في المصفوفة باستخدام قيم العناصر او فهارسها. لأخذ فكرة عن المصفوفات تستطيع الاطلاع على درس فهم المصفوفات في الجافا سكريبت . ترجمة -وبتصرّف- للمقال How To Use Array Methods in JavaScript: Iteration Methods لصاحبته Tania Rascia حقوق الصورة البارزة محفوظة لـ Freepik
  2. المصفوفة في جافا سكريبت هي نوع من أنواع البيانات المتاحة فيها وتتكون من قائمة من العناصر. تحتوي الجافا سكريبت على العديد من الوظائف المُرفقة والتي تعمل على المصفوفات. الوظائف التي تقوم بالتعديل على المصفوفة الأصلية تُعرف بتوابع التعديل (Mutator Methods)، والوظائف التي تُعيد قيمة جديدة أو شكلًا آخر من المصفوفة تُعرف بتوابع الوصول (Accessor Methods). في هذا المقال سنتحدث عن توابع الوصول. للاستفادة أكثر من هذا الدرس، يجب أن يكون لديك معرفة مسبقة بكيفية إنشاء المصفوفات، فهرستها، التعديل عليها والمرور على عناصرها بواسطة حلقة التكرار. تستطيع مراجعة درس فهم المصفوفات في الجافا سكريبت لأخذ فكرة عما سبق. هذا الدرس يوضح كيفية دمج المصفوفات ببعضها، تحويل المصفوفات الى نصوص، نسخ أجزاء من مصفوفة وحفظها كمصفوفة جديدة بالإضافة لتحديد فهارس المصفوفة. الوظيفة ()concat تقوم الوظيفة ()concat بدمج مصفوفتين أو أكثر ببعضها البعض لينتج لدينا مصفوفة جديدة. في المثال التالي نقوم بإنشاء مصفوفتين تحتويان على أنواع الأسماك الصدفية ومن ثم نقوم بدمجهما في مصفوفة واحدة. // Create arrays of monovalves and bivalves let monovalves = [ "abalone", "conch" ]; let bivalves = [ "oyster", "mussel", "clam" ]; // Concatenate them together into shellfish variable let shellfish = monovalves.concat(bivalves); بمجرد استدعائنا للمصفوفة الجديدة، سنرى أنها تحتوي على العناصر الموجودة في كلا المصفوفتين السابقتين. shellfish; Output [ 'abalone', 'conch', 'oyster', 'mussel', 'clam' ] نستطيع تمرير أكثر من مُعطى للوظيفة ()concate لتسمح لنا بكفاءة وبخطوة واحدة أن ندمج مجموعة مصفوفات ببعضها البعض. الوظيفة ()join تقوم الوظيفة ()join بتحويل جميع عناصر مصفوفة معينة إلى قيمة نصية. في المثال التالي، تقوم الوظيفة بعملية تحويل عناصر المصفوفة fish الى قيمة نصية. لاحظ أنه بدون أن نُمرر أي معطى للوظيفة ()join سيتم فصل عناصر المصفوفة عن بعضها بواسطة فاصلة عادية وذلك في القيمة النصية الناتجة. let fish = [ "piranha", "barracuda", "koi", "eel" ]; // Join the elements of an array into a string let fishString = fish.join(); fishString; Output 'piranha,barracuda,koi,eel' اذا أردنا أن يتم فصل العناصر عن بعضها البعض بواسطة مسافة أو أي فاصل اخر، فعلينا أن نضيف الفاصل الذي نرغب به كمُعطى للوظيفة ()join. // Join the elements of an array into a string let fishString = fish.join(', '); fishString; Output 'piranha, barracuda, koi, eel' في المثال السابق، مررنا الفاصل ‘, ’ الذي يحتوي على فاصلة عادية ومسافة وذلك لفصل عناصر المصفوفة ولكي نحصل على نص مقروء بطريقة أفضل. في حالة قمنا بتمرير فاصل فارغ للوظيفة ()join، فإن الفاصلة التلقائية سيتم ازالتها بالكامل. الوظيفة ()slice نستخدم الوظيفة ()slice لنسخ أجزاء من مصفوفة إلى مصفوفة أخرى جديدة. let fish = [ "piranha", "barracuda", "koi", "eel" ]; لنفترض أننا نربد نسخ أخر عنصرين في المصفوفة fish الى مصفوفة جديدة، فإننا سنبدأ بالنسخ من الفهرس 2 الذي يمثل موقع العنصر “koi” وسينتهي النسخ عند رقم الفهرس الذي يلي فهرس أخر عنصر نريد نسخه. بما أن فهرس أخر عنصر “eel” هو 3، فإننا سنمرر للمعطى الثاني القيمة 4. // Slice a new array from 2 to 5 let fishWithShortNames = fish.slice(2, 4); fishWithShortNames; Output [ 'koi', 'eel' ] في هذه الحالة الخاصة، وبسبب أن العنصر “eel” هو أخر عنصر في المصفوفة، فإن المُعطى الأخير في الوظيفة ()slice غير ضروري، حيث أن النسخ سيبدأ من فهرس البداية وينتهي لنهاية المصفوفة عند عدم تمرير المعطى الثاني. // Slice a new array from 2 to the end of the array let fishWithShortNames = fish.slice(2); fishWithShortNames; Output [ 'koi', 'eel' ] انتبه ألا يختلط عليك الأمر مع وظيفة توابع التعديل المسماة ()splice والتي تقوم بإضافة أو حذف العناصر من المصفوفة الأصلية. الوظيفة ()indexOf تُعيد هذه الوظيفة رقم الفهرس لأول وجود لعنصر معين في مصفوفة، وتظهر فائدة هذه الوظيفة بشكل واضح في المصفوفات التي تحتوي قيم عديدة ومتكررة. في المثال التالي، يتكرر وجود العنصر”barracuda” مرتين في المصفوفة fish. let fish = [ "piranha", "barracuda", "koi", "barracuda" ]; باستخدام الوظيفة ()indexOf نستطيع الحصول على موقع العنصر الأول من القيمة “barracuda”. // Find the first instance of an element fish.indexOf("barracuda"); Output 1 إذا قمنا بتمرير قيمة غير موجودة في المصفوفة فإن نتيجة الوظيفة ستكون القيمة الخاصة -1. fish.indexOf("shark"); Output -1 الوظيفة ()lastIndexOf تُعيد هذه الوظيفة رقم الفهرس للوجود الأخير لعنصر معين في مصفوفة. نستطيع اختبار هذه الوظيفة على نفس المثال السابق الذي يحتوي على تكرار القيمة “barracuda” مرتين. let fish = [ "piranha", "barracuda", "koi", "barracuda" ]; // Find the last instance of an element fish.lastIndexOf("barracuda"); Output 3 الوظيفة ()lastIndexOf تبدأ بالبحث عن العنصر ابتداءً من نهاية المصفوفة وبذلك فهي فعليًا تعيد فهرس أول عنصر تجده أثناء عملية البحث. خاتمة من خلال هذا الدرس قمنا بمراجعة وظائف توابع الوصول المرفقة مع مصفوفات الجافا سكريبت. توابع الوصول تقوم بإنشاء نسخة جديدة من المصفوفة بعكس توابع التعديل التي تقوم بتغيير المصفوفة الأصلية. تعلمنا كيفية دمج مصفوفتين أو أكثر، تحويل عناصر المصفوفة الى مجموعة قيم نصية مُجزأة بفاصلة عادية بالإضافة الى كيفية نسخ أجزاء من المصفوفة الى مصفوفة جديدة. وفي النهاية تعلمنا كيفية الحصول على فهرس أول وجود لعنصر أو أخر وجود له في مصفوفة معينة. لمراجعة أساسيات المصفوفات، راجع درس فهم المصفوفات في الجافا سكريبت. ترجمة -وبتصرّف- للمقال How To Use Array Methods in JavaScript Mutator Methods لصاحبته Tania Rascia
  3. مقدمة تُعتبر خدمات تخزين البيانات المرنة والقابلة للتوسع حسب الحاجة، متطلب أساسي لأغلب التطبيقات والخدمات التي يتم تطويرها بالأدوات والتقنيات الحديثة. بغض النظر عن تخزين كميات قليلة أو كثيرة من الصور، الفيديوهات والنصوص الضخمة فإن مُطوري التطبيقات يحتاجون حلًا لتخزين واسترجاع المحتوى الخاص بالمستخدم، سجلات عمله، نسخه الاحتياطية وغيره من الأمور الأخرى. في ظل الأنظمة المنشورة المعقدة، وفي ظل الحاويات المختلفة والبيئات سريعة التغير والزوال، فإن زمن حفظ الملفات ببساطة في وحدة تخزين على خادوم واحد قد انتهى، حيث قام مزودو الخدمات السحابية بتطوير خدمات لتلبية حاجات التخزين في الأنظمة والتطبيقات الحديثة، وغالبًا تندرج خدمات التخزين السحابية تحت نوعين هما التخزين الكائني (Object Storage) والتخزين الكتلي (Block Storage). سنتناول هنا مميزات وسلبيات كل نوع. ما هو التخزين الكتلي؟ تعتبر خدمات التخزين الكتلي بسيطة ومألوفة نوعًا ما، حيث تُقدم هذه الخدمات خدمة التخزين العادية –كما في القرص الصلب– ولكن من خلال الشبكة. يقدم مزودو الخدمات السحابية الأدوات اللازمة التي تُتيح الحصول على جهاز تخزين كتلي بأي حجم وربطه بالآلة الافتراضية الخاصة بك، ومن هنا تستطيع التعامل مع الجهاز كقرص عادي، حيث تستطيع تهيئته، حفظ الملفات عليه، ربط أكثر من قرص بنظام RAID أو حتى إعداد قاعدة بيانات عليه للكتابة مباشرةً على الجهاز الكتلي. بالإضافة لما سبق، فإن أجهزة التخزين المرتبطة بشبكة غالبًا ما تتميز عن الأقراص الصلبة العادية بما يلي: تستطيع أخذ نسخة حية بسهولة للجهاز للأغراض الاحتياطية. من الممكن إعادة تغيير حجم جهاز التخزين الكتلي وتلبية الاحتياجات المتراكمة. تستطيع فك وربط الجهاز بين الآلات الوهمية بسهولة. ما ذكرناه من مميزات هي خصائص مرنة من الممكن أن تكون مفيدة غالبًا لأي تطبيق. فيما يلي نسرد بعضًا من مميزات وعيوب هذه التقنية. مميزات التخزين الكتلي: التخزين الكتلي شائع ومألوف، حيث أن المستخدمين والبرمجيات يفهمونه ويستطيعون التعامل مع ملفاته وأنظمته بشكل واسع. الأجهزة الكتلية مدعومة جيدًا. كل لغات البرمجة تستطيع قراءة وكتابة الملفات منها. صلاحيات وأذونات ملفات النظام في الأجهزة الكتلية مألوفة ومفهومة بشكل جيد. أجهزة التخزين الكتلي لديها وقت استجابة منخفض في عمليات الإدخال والإخراج، مما يجعلها مناسبة لقواعد البيانات. عيوب التخزين الكتلي: التخزين مرتبط بخادم واحد في نفس الوقت. تحتوي كتلة التخزين وملفات النظام على بيانات وصفية (metadata) قليلة عن المعلومات التي تُخزنها مثل تاريخ الإنشاء، المستخدم الذي تعود له البيانات وحجم هذه البيانات. أي معلومات إضافية عما تُخزنه وحدة التخزين الكتلي يجب أن يتم التعامل معها على مستوى التطبيق أو قاعدة البيانات مما يؤدي إلى زيادة الأعباء على المطور لمتابعة هذه المعلومات. يجب عليك أن تدفع مقابل حجم التخزين الذي حجزته حتى لو لم تقم بالاستفادة منه أو استخدامه. تستطيع أن تصل لخدمة التخزين الكتلي عبر خادومٍ واحد يعمل فقط. التخزين الكتلي يحتاج إلى اعدادات تنصيب وعمل أكثر (اختيار ملفات النظام، الأذونات، الإصدارات، النسخ الاحتياطية...وغيرها) مقارنة بالتخزين الكائني. بسبب سرعة عمليات الإدخال والإخراج في التخزين الكتلي ، فإنه يُعتبر خيارًا أفضل لتخزين البيانات في قواعد البيانات العادية. بالإضافة لذلك، فإن العديد من الأنظمة القديمة تتطلب وجود ملفات نظام عادية للتخزين مما يجعل التخزين الكتلي هو الحل في هذه الحالة. إذا لم يتوفر لدى مزود الخدمات السحابية خدمة التخزين الكتلي، تستطيع أن تقوم بتشغيل الخدمة الخاصة بك بنفسك باستخدام OpenStack Cinder، Ceph، أو خدمة iSCSI المُضمنة مع العديد من أجهزة NAS. ما هو التخزين الكائني؟ في عالم الحوسبة السحابية الحديث، يُعرف التخزين الكائني بأنه تخزين واسترجاع البيانات غير المُرتبة وفق بنية معينة (Unstructured) عبر استخدام الواجهة البرمجية ل HTTP. فبدلًا من تقسيم الملفات إلى كُتل متعددة وحفظها باستخدام نظام الملفات على وحدة التخزين، يتم التعامل مع الملفات ككائنات وتُحفظ على الشبكة. قد تكون هذه الكائنات عبارة عن صورة، سجلات، ملفات HTML أو أية كائنات ثنائية كبيرة بذاتها. تكون هذه الكائنات غير مرتبة وفق بنية معينة لأنها لا تحتاج لاتباع مُخطط أو تنسيق محدد. أصبح التخزين الكائني معروفًا بسبب قدرته الكبيرة على تبسيط تجربة المطور في بناء وتطوير خدمات التخزين، حيث تم تطوير مكتبات تدعم بناء الواجهات البرمجية (التي تحتوي طلبات (HTTP في أغلب لغات البرمجة. حفظ ملف ثنائي كبير أصبح أمرًا سهلًا باستخدام طلب HTTP PUT، وكذلك استرجاع الملف وبياناته الوصفية يتم عبر طلب GET عادي. علاوة على ذلك، فأغلب خدمات التخزين الكائني تستطيع مشاركة هذه الملفات مع المستخدمين الآخرين دون الحاجة لإعدادات خاصة على الخادم المُستضيف. بالإضافة لما سبق، لن تَدفع مقابل خدمة التخزين الكائني إلا مقابل المساحة التي تَستخدمها (بعض الخدمات تُدفع مقابل كل طلب HTTP أو حسب استهلاك النقل عبر النطاق) وهذا الأمر يُعد نعمةً لبعض المطورين الذين يحصلون على خدمة استضافة وتخزين عالمية مقابل تكلفة تتلاءم مع حجم الاستخدام. التخزين الكائني ليس الحل الجيد لكل الحالات. لنلقي نظرة على مميزات وعيوب هذه النوع. مميزات التخزين الكائني: واجهة برمجية بسيطة عبر HTTP تدعم أغلب المستخدمين بغض النظر عن نظام التشغيل المستخدم أو لغة البرمجة. تكلفة التخزين تُحدد مقابل ما يتم استخدامه من مساحة وليس ما يتم حجزه. لن تحتاج لحجز خادوم كامل للحصول على خدمة التخزين الكائني لملفاتك الساكنة. بعض مزودي خدمة التخزين الكائني تُقدم خاصية الدمج CDN المُضمنة، والتي تتيح تخبئة ملفاتك حتى يكون تنزيلها أسرع للمستخدمين. خاصية الأَصْدَرَة (Versioning) التي تتيح لك استرجاع النسخ القديمة من ملفاتك واستعادتها بعد التعديل عليها بشكل خاطئ. تتكيف خدمات التخزين الكائني مع احتياجات الاستخدام بدءًا من الاستخدام البسيط وحتى حالات الاستخدام المكثف دون أن يحتاج المطور لزيادة المصادر أو إعادة الهيكلة لمعالجة الحمل المطلوب. باستخدام خدمات التخزين الكائني لن تحتاج لمتابعة وإدارة وحدات التخزين أو أنظمة RAID لأنها من مسئولية مزود الخدمة. تستطيع حفظ بيانات وصفية بجانب البيانات الأصلية وذلك يُبسط لك هيكلة التطبيق الذي تعمل عليه. عيوب التخزين الكائني: لا تستطيع أن تستضيف قاعدة بيانات عادية على خدمة التخزين الكائني بسبب وقت الاستجابة العالي واللازم لقاعدة البيانات. باستخدام التخزين الكائني لا تستطيع التعديل على جزء من الكائن الذي تحفظه، حيث عليك قراءة أو كتابة الكائن كله في العملية الواحدة وهذا له آثار مترتبة على الأداء. فمثلًا، باستخدام نظام الملفات تستطيع بسهولة أن تُضيف سطر في نهاية ملف سجلات، ولكن في نظام التخزين الكائني يجب عليك قراءة الملف كاملا ثم التعديل عليه ثم إعادة كتابته وحفظه مرة أخرى، وهذا الأمر يجعل من التخزين الكائني خيارًا غير مثالي في حفظ البيانات التي تتغير بشكل مستمر. لا تستطع نُظم التشغيل تعيين كائن كمحرك أقراص بشكل سهل. يوجد بعض العملاء والمحولات التي تساعد في تنفيذ هذا الأمر، ولكن بشكل عام، استخدام الكائن وتصفحه ليس متاحًا بسهولة كما هو متاح لدى التنقل خلال المجلدات في نظام الملفات. بسبب هذه الخصائص، التخزين الكائني مفيد في استضافة الملفات الساكنة، حفظ محتوى المستخدم المتمثل مثلًا في الصور ولقطات الفيديو، حفظ ملفات النسخ الاحتياطي والسجلات. يوجد بعض حلول التخزين الكائني التي تتيح لك حفظ ملفاتك وبياناتك دون القلق على كيفية إدارة الأقراص أو خيارات التوسع. تستطيع مثلا أن تُجرب Minio، وهو خادوم مشهور مُطَوَّرْ باستخدام لغة Go ويقدم خدمة التخزين الكائني، أو تستطيع تجربة Ceph أو OpenStack Swift. خاتمة اختيار طريقة التخزين قد يكون قرارًا معقدًا للمطورين. ناقشنا مزايا وعيوب كلًا من التخزين الكتلي والتخزين الكائني. على الأرجح أن أي تطبيق معقد وذو كفاءة سيحتاج لاستخدام كلا النوعين في التخزين لتلبية كافة الاحتياجات اللازمة للعمل. ترجمة -وبتصرّف- للمقال Object Storage vs. Block Storage Services لصاحبه Brian Boucheron
  4. تتكون المصفوفة في الجافا سكريبت من قائمة عناصر، وتحتوي الجافا سكريبت على العديد من الوظائف المرفقة والتي تعمل على المصفوفات. الوظائف التي تقوم بالتعديل عل المصفوفة الأصلية تُعرف بتوابع التعديل (Mutator Methods)، والوظائف التي تعيد قيمة جديدة أو شكل آخر تُعرف بتوابع الوصول (Accessor Methods). للاستفادة أكثر من هذا المقال، يجب أن يكون لديك معرفة مسبقة بكيفية إنشاء المصفوفات، فهرستها، التعديل عليها والمرور على عناصرها بواسطة حلقة التكرار. تستطيع مراجعة درس فهم المصفوفات في الجافا سكريبت لأخذ فكرة عما سبق. المصفوفات تتشابه مع النصوص من حيث أن كلاهما يتكون من مجموعة من العناصر التي يمكن الوصول لها عبر رقم الفهرس. على الرغم من ذلك، من الأهمية أن نتذكر أن النصوص ثابتة وغير قابلة للتعديل (Immutable). ولكن على الجانب الاّخر، فإن المصفوفات غير ثابتة (Mutable)، ونستطيع التعديل عليها بشكل مباشر. في هذا المقال سوف نشرح عمليات إضافة وإزالة العناصر من المصفوفة، عكس المصفوفة، استبدال المصفوفة والتعديل على عناصرها. الوظيفة ()isArray قبل البدء بشرح توابع التعديل، سنتعرض للوظيفة ()isArray والتي تقوم بفحص هل الكائن عبارة عن مصفوفة أم لا. في حالة كان الكائن الذي نقوم بفحصه من نوع مصفوفة، فإن الوظيفة ستعيد القيمة المنطقية true، وفي حالة لم يكن مصفوفة، فإن الوظيفة ستعيد القيمة المنطقية false. let fish = [ "piranha", "barracuda", "koi", "eel" ]; // Test if fish variable is an array Array.isArray(fish); Output True الوظيفة ()isArray مفيدة بسبب أن العملية typeof التي تُستخدم عادة في فحص هذه الحالات ستعيد لنا القيمة object عند استخدامها مع المصفوفة، ومن المهم في بعض الأحيان معرفة هل الكائن مصفوفة أم لا. لاحظ أن الوظيفة ()isArray تُكتب بطريقة مختلفة عن أغلب وظائف المصفوفات، حيث يتم تمرير الكائن الذي يمثل المصفوفة كمُعطى للوظيفة. بعد أن تعرفنا على الوظيفة التي تساعدنا في فحص هل الكائن الذي نتعامل معه مصفوفة أم لا، سنبدأ بشرح وظائف توابع التعديل. الوظيفة ()pop أول وظيفة سنتناولها في الشرح هي وظيفة ()pop، التي تُزيل العنصر الأخير من نهاية مصفوفة. لنفترض أنه لدينا مصفوفة باسم fish تحتوي على أنواع بعض الأسماك: let fish = [ "piranha", "barracuda", "koi", "eel" ]; عند استدعاء الوظيفة ()pop سيتم إزالة اخر عنصر في المصفوفة، والذي سيكون العنصر الذي يساوي القيمة النصية “eel” : // Use pop method to remove an item from the end of an array fish.pop(); نقوم بطباعة محتوى المصفوفة لنتأكد من أنها أصبحت لا تحتوي القيمة المحذوفة: fish; Output [ 'piranha', 'barracuda', 'koi' ] بذلك نكون قد أزلنا العنصر “eel” من المصفوفة بنجاح. الوظيفة ()pop لا تأخذ أي معاملات أخرى. الوظيفة ()shift الوظيفة ()shift هي وظيفة أخرى من توابع التعديل وتقوم بإزالة العنصر الأول من المصفوفة. لنفترض وجود المصفوفة fish بالعناصر التالية: let fish = [ "piranha", "barracuda", "koi", "eel" ]; نستخدم الوظيفة ()shift لإزالة العنصر الأول “piranha” والموجود في الفهرس رقم 0، وكذلك سيتم إزاحة كافة عناصر المصفوفة بمقدار فهرس واحد للأسفل. // Use shift method to remove an item from the beginning of an array fish.shift(); fish; Output [ 'barracuda', 'koi', 'eel' ] في المثال السابق، تم إزالة العنصر الأول وإجراء عملية الإزاحة لكافة العناصر الباقية، لهذا السبب، وبشكل عام، فإنه يُفضل أن يتم استخدام الوظيفة ()pop قدر المستطاع لإزالة العناصر من المصفوفة لعدم وجود عملية إزاحة العناصر عند استخدامها. الوظيفة ()push الوظيفة ()push تعمل على إضافة عنصر جديد أو عدة عناصر جديدة في نهاية المصفوفة. let fish = [ "piranha", "barracuda", "koi", "eel" ]; لإضافة عنصر جديد، نمرر العنصر للوظيفة كمُعامل: // Use push method to add an item to the end of an array fish.push("swordfish"); fish; Output [ 'piranha', 'barracuda', 'koi', 'eel', 'swordfish' ] من الممكن أيضًا أن نضيف أكثر من عنصر دفعة واحدة لنهاية المصفوفة بالشكل التالي: fish.push("swordfish", "dragonfish") الوظيفة unshift() لإضافة عنصر جديد أو عدة عناصر في بداية المصفوفة، نستخدم الوظيفة ()unshift. let fish = [ "piranha", "barracuda", "koi", "eel" ]; // Use unshift method to add an item to the beginning of an array fish.unshift("shark"); fish; Output [ 'shark', 'piranha', 'barracuda', 'koi', 'eel' ] في المثال السابق، العنصر shark”” تم اضافته في الفهرس رقم 0، مع إزاحة كافة العناصر للإمام. نستطيع إضافة عدة عناصر دفعة واحدة بنفس الطريقة التي استخدمناها مع الوظيفة ()shift وذلك بتمرير العناصر بحيث تكون مفصولة عن بعضها بفاصلة عادية. الوظيفتان ()pop و ()push تؤثران على نهاية المصفوفة، والوظيفتان ()shift و ()unshift تؤثران على بداية المصفوفة. يوجد طريقة سهلة لتذكر هذه المعلومة وهي معرفة أن الوظيفتان ()shift و ()unshift تقومان بعمل إزاحة للعناصر في المصفوفة. الوظيفة ()splice تقوم الوظيفة ()splice بإضافة أو إزالة عنصر من أي موقع في المصفوفة. الوظيفة تقوم إما بالإضافة أو الحذف كلٌ على حدة، أو الإضافة والحذف بشكل متزامن. تأخذ الوظيفة ()splice ثلاث معاملات، الأول هو الفهرس الذي سنبدأ من عنده العملية، الثاني هو عدد العناصر التي نريد حذفها، الثالث هو العنصر الذي نريد اضافته (اختياري). splice(index number, number of items to remove, items to add) الأمثلة التالية توضح كيفية استخدام الوظيفة ()splice في إضافة وحذف العناصر في المصفوفة. الإضافة باستخدام ()splice إذا قمنا بإعداد المعامل الثاني في الوظيفة ()splice ليأخذ القيمة 0، فإنه لن يتم حذف أي عنصر. بهذه الطريقة، نستطيع أن نضيف عنصر لمصفوفة في أي موقع فيها، وهذه الطريقة تُعتبر أفضل وأقوى من استخدام الوظيفتين ()push و ()unshift واللتان تقومان بالإضافة في نهاية المصفوفة أو بدايتها فقط. let fish = [ "piranha", "barracuda", "koi", "eel" ]; // Splice a new item number into index position 1 fish.splice(1, 0, "manta ray"); fish; Output [ 'piranha', 'manta ray', 'barracuda', 'koi', 'eel' ] النص الجديد “manta ray” تم إضافته للمصفوفة في الفهرس رقم 1. الإزالة باستخدام ()splice إذا تجاهلنا المُعامل الثالث (الاختياري) في الوظيفة ()splice، نستطيع ببساطة إزالة أي عنصر في المصفوفة ومن أي موقع فيها: let fish = [ "piranha", "barracuda", "koi", "eel" ]; // Remove two items, starting at index position 1 fish.splice(1, 2); fish; Output [ 'piranha', 'eel' ] في المثال السابق، قمنا بحذف عنصرين من المصفوفة ابتداء من الفهرس 1. إذا لم نقم بإدخال المعامل الثاني الذي يُمثل عدد العناصر التي نريد حذفها، سيتم حذف جميع عناصر المصفوفة ابتداءً من الفهرس 1 وحتى نهاية المصفوفة. الإضافة والإزالة باستخدام ()splice باستخدام كافة المعاملات للوظيفة ()splice، نستطيع إجراء كلتا عمليتي الإضافة والحذف في نفس الوقت. لتوضيح هذا، لنقم بحذف نفس العناصر السابقة في المثال قبل السابق واضافة العنصر الجديد في الفهرس 1: let fish = [ "piranha", "barracuda", "koi", "eel" ]; // Remove two items and add one fish.splice(1, 2, "manta ray"); fish; Output [ 'piranha', 'manta ray', 'eel' ] الوظيفة ()splice تُعتبر وظيفة قوية في إجراء التعديلات على أي مصفوفة. انتبه أن لا يكون لديك لبس بين الوظيفة ()splice والوظيفة ()slice التي تُعتبر وظيفة من توابع الوصول وتقوم بإنشاء نسخة من جزء من مصفوفة. الوظيفة ()reverse تقوم هذه الوظيفة بعكس ترتيب العناصر في المصفوفة. let fish = [ "piranha", "barracuda", "koi", "eel" ]; باستخدام الوظيفة ()reverse، فإن العنصر الأخير سيكون الأول، والعنصر الأول سيكون اّخر العناصر، ولا تأخذ هذه الوظيفة أي معاملات. // Reverse the fish array fish.reverse(); fish; Output [ 'eel', 'koi', 'barracuda', 'piranha' ] الوظيفة ()fill تقوم الوظيفة ()fill باستبدال جميع عناصر المصفوفة بقيمة ثابتة. let fish = [ "piranha", "barracuda", "koi", "eel" ]; لنفترض وجود المصفوفة fish ولتحتوي على 4 عناصر، باستخدام الوظيفة ()fill سنستبدل جميع العناصر الأربعة بقيمة واحدة: // Replace all values in the array with "shark" fish.fill("shark"); fish; Output [ 'shark', 'shark', 'shark', 'shark' ] نستطيع أن نمرر للوظيفة ()fill معاملين إضافيين وكلاهما اختياري، ويمثلان فهرس البداية الذي نبدأ من عنده عملية الاستبدال وفهرس النهاية الذي ننهي عملية الاستبدال قبله. fish.fill("shark", 1) // > [ 'piranha', 'shark', 'shark', 'shark' ] fish.fill("shark", 1, 3); // > [ 'piranha', 'shark', 'shark', 'eel' ] الوظيفة ()sort الوظيفة ()sort تقوم بترتيب عناصر المصفوفة بناءً على الحرف الأول في العنصر. في حالة وجود أكثر من عنصر بنفس الحرف الأول، يتم الترتيب بناءً على الحرف الثاني وهكذا. بشكل تلقائي تقوم الوظيفة بالترتيب الأبجدي لمصفوفة نصية والتي يكون جميع عناصرها إما uppercase أو lowercase. let fish = [ "piranha", "barracuda", "koi", "eel" ]; // Sort items in array fish.sort(); fish; Output [ 'barracuda', 'eel', 'koi', 'piranha' ] بما أن الوظيفة ()sort تعتمد على الترميز النصي للحرف الأول، فإنها ستُرتب العناصر التي تكون حالتها uppercase قبل العناصر التي حالتها lowercase. انظر للمثال التالي: let fish = [ "piranha", "barracuda", "Koi", "eel" ]; fish.sort(); fish; Output [ 'Koi', 'barracuda', 'eel', 'piranha' ] الأرقام تأتي قبل الأحرف بغض النظر عن حالتها في ترتيب المصفوفة. انظر للمثال التالي: let fish = [ "piranha", "barracuda", "Koi", "1 eel" ]; fish.sort(); Output [ '1 eel', 'Koi', 'barracuda', 'piranha' ] لن تقوم الوظيفة بترتيب مصفوفة أعداد حسب قيمة الأعداد فيها (الأصغر ثم الأكبر)، ولكنها ستقوم بفحص الرقم الأول في كل عدد ويتم الترتيب بناءً عليه. let numbers = [ 42, 23, 16, 15, 4, 8 ]; numbers.sort(); Output [ 15, 16, 23, 4, 42, 8 ] لترتيب الأعداد في مصفوفة أعداد بطريقة سليمة وحسب قيمة الأعداد (من الأصغر إلى الأكبر)، نستطيع إنشاء وظيفة “مقارنة” ونمررها كمعامل للوظيفة ()sort: // Function to sort numbers by size const sortNumerically = (a, b) => { return a - b; } numbers.sort(sortNumerically); Output [ 4, 8, 15, 16, 23, 42 ] خاتمة في هذا الدرس، قمنا باستعراض أغلب وظائف توابع التعديل في لغة الجافا سكريبت. الوظائف من هذا النوع تقوم بالتعديل على المصفوفة الأصلية التي تستخدمها، على العكس من وظائف توابع الوصول، وتعلمنا خلال هذا الدرس كيفية إضافة وإزالة العناصر من المصفوفة في بدايتها أو نهايتها، بالإضافة لكيفية ترتيب المصفوفة، عكسها واستبدال عناصرها. لمراجعة أساسيات المصفوفات، راجع درس فهم المصفوفات في الجافا سكريبت. ترجمة -وبتصرّف- للمقال How To Use Array Methods in JavaScript Mutator Methods لصاحبه Tania Rascia حقوق الصورة البارزة محفوظة لـ Freepik
  5. مقدمة تُعتبر المصفوفة في الجافا سكريبت كائن عمومي (global) الغرض منه هو تخزين البيانات، وتحتوي المصفوفة إما على مجموعة من العناصر بنوع بيانات واحد أو أكثر، وقد تكون فارغة. نستخدم الفهارس العددية التي تبدأ من القيمة 0 للوصول إلى عناصر المصفوفة. المصفوفات مفيدة جدًا بما أنها تُخزن عدة قيم في متغير واحد، وهذا الأمر يقلل وينظم الشيفرة البرمجية التي نكتبها ويجعلها أكثر ملائمة للقراءة والصيانة. تستطيع المصفوفة أن تحتوي على أي نوع بيانات، ابتداءً من الأرقام ومرورا بالنصوص والكائنات وغيرها من أنواع البيانات. لتوضيح كيف من الممكن أن تكون المصفوفات مهمة، لنفترض أننا نريد أن نحفظ أسماء المحيطات في متغيرات عدة، بحيث يكون لكل محيط المتغير الخاص به: oceans.js // Assign the five oceans to five variables const ocean1 = "Pacific"; const ocean2 = "Atlantic"; const ocean3 = "Indian"; const ocean4 = "Arctic"; const ocean5 = "Antarctic"; هذه الطريقة مُضجرة جدا، وتُصبح أكثر صعوبة بشكل متسارع في المتابعة والصيانة. باستخدام المصفوفات، نستطيع تبسيط الأمر. oceans.js // Assign the five oceans let oceans = [ "Pacific", "Atlantic", "Indian", "Arctic", "Antarctic", ]; بدلًا من استخدام خمسة متغيرات منفصلة، نستطيع الان أن يكون لدينا متغير واحد يحتوي على جميع العناصر الخمسة. لإنشاء المصفوفة، نستخدم الأقواس المربعة [ ] كما هو واضح في الشيفرة البرمجية السابقة، وللوصول إلى عنصر معين في المصفوفة، نستخدم الفهرس مع المصفوفة بالطريقة التالية: // Print out the first item of the oceans array oceans[0]; Output Pacific في هذا الدرس سنتعلم كيفية بناء المصفوفة، وكيفية الوصول إلى عناصرها، والاضافة إليها وتعديلها والحذف منها، كما سنتعلم كيفية المرور خلال عناصرها باستخدام حلقة التكرار. إنشاء مصفوفة يوجد طريقتان لإنشاء المصفوفة في جافا سكريبت: التعريف اللفظي باستخدام الأقواس المعكوفة. التعريف بواسطة الباني (constructor) باستخدام كلمة new. لنوضح كيفية إنشاء مصفوفة تحتوي على أنواع سمك القرش، وذلك باستخدام التعريف اللفظي بواسطة الأقواس المربعة: sharks.js // Initialize array of shark species with array literal let sharks = [ "Hammerhead", "Great White", "Tiger", ]; الان نُعرف نفس المصفوفة باستخدام الباني وذلك بواسطة الجملة new Array() : sharks.js // Initialize array of shark species with array constructor let sharks = new Array( "Hammerhead", "Great White", "Tiger", ); كلا الطريقتين سوف يُنشئ لنا المصفوفة، ولكن طريقة التعريف اللفظي هي المشهورة والأكثر تفضيلا بما أن التعريف باستخدام الباني قد يؤدي إلى نتائج غير مستقرة وغير متوقعة وعليك الانتباه في حال صادفتك تلك الطريقة في التعريف أو في حال استخدامك لها. نستطيع طباعة محتويات المصفوفة بكتابة المتغير الخاص بها مباشرة: // Print out the entire sharks array sharks; Output [ 'Hammerhead', 'Great White', 'Tiger' ] تُستخدم المصفوفات عادة في تجميع العناصر أو القوائم من نفس نوع البيانات، ولكن من الناحية التقنية، فإن المصفوفات تستطيع أن تحتوي على عناصر من أنواع مختلفة بالإضافة إلى إمكانية أن تحتوي على مصفوفات أخرى: // Initialize array of mixed datatypes let mixedData = [ "String", null, 7, [ "another", "array", ], ]; بعد أن تعلمنا كيفية إنشاء المصفوفة، نستطيع الان التعامل معا بأكثر من طريقة، ولكننا في البداية نحتاج الى فهم كيفية فهرسة المصفوفات (Arrays Indexing). ملاحظة: قد تجد اخر عنصر في المصفوفة ينتهي بفاصلة وأحيانا قد لا تجد هذه الفاصلة. تُعرف هذه الفاصلة بالفاصلة التابعة (Trailing comma)، ومن الشائع ان تكون غير موجودة، ولكن بشكل عام أصبح من الأفضل أن يتم استخدامها في الشيفرة البرمجية بسبب أنها تجعل الاختلافات بين الإصدارات (في عملية إدارة الإصدارات Versions Control) أكثر وضوحا وتسهل من إضافة وإزالة عناصر المصفوفة دون أخطاء. لاحظ أن الفاصلة التابعة غير مسموح بها في ملفات JSON. فهرسة المصفوفات إذا تعاملت مسبقاً مع النصوص والفهرسة في الجافا سكريبت، ستكون مُلمًا بمفهوم فهرسة المصفوفات، حيث أن النص يُعتبر شبيهًا بالمصفوفة. لا تحتوي المصفوفات على عناصر مزدوجة على شكل اسم/قيمة، وبدلا من ذلك، فإن المصفوفات تُفهرس بقيم عددية تبدأ من القيمة 0. المثال التالي ينشئ مصفوفة باسم seaCreatures: seacreatures.js let seaCreatures = [ "octopus", "squid", "shark", "seahorse", "starfish", ]; الجدول التالي يُفصل كيف يتم فهرسة كل عنصر في المصفوفة بقيمة عددية ابتداءً من 0: octopus squid shark seahorse starfish 0 1 2 3 4 العنصر الأول في المصفوفة هو octopus ومُفهرس في الموقع 0 من المصفوفة، والعنصر الأخير هو starfish ومُفهرس في الموقع 4. تبدأ الفهرسة من 0، وهذا يتضارب مع طبيعتنا الفطرية ببدء العد من القيمة 1، لذلك نحتاج لأخذ الاحتياط وأن نتذكر هذه النقطة دائما حتى تصبح طبيعية. نستطيع أن نحصل على عدد العناصر في المصفوفة باستخدام الخاصية length: seaCreatures.length; Output 5 على الرغم من أن الفهارس الخاصة بالمصفوفة seaCreatuers تبدأ من 0 إلى 4، فإن الخاصية length سوف تُرجع العدد الفعلي للعناصر الموجودة في المصفوفة. إذا أردنا معرفة رقم الفهرس لعنصر معين في المصفوفة، وليكن مثلا seahorse، نستطيع أن نستخدم لذلك الوظيفة indexOf() : seaCreatures.indexOf("seahorse"); Output 3 إذا لم تحتوي المصفوفة على العنصر الذي نريده، فلن نحصل على رقم فهرس لعنصر غير موجود، وفي هذه الحالة، فإن الوظيفة سترجع لنا القيمة -1 كما في المثال التالي: seaCreatures.indexOf("cuttlefish"); Output -1 بواسطة أرقام الفهارس المرتبطة بعناصر المصفوفة، فإنه لدينا القدرة على الوصول لكل عنصر بشكل منفرد بهدف العمل على هذا العنصر والتعامل معه. الوصول لعناصر المصفوفة يتم الوصول لعنصر في مصفوفة جافا سكريبت بواسطة الإشارة لرقم الفهرس للعنصر بين قوسين معكوفين: seaCreatures[1]; Output squid نعلم أن الرقم 0 سيعيد لنا دائما العنصر الأول في المصفوفة. كذلك نستطيع إيجاد العنصر الأخير في المصفوفة بواسطة إجراء عملية طرح قيمة 1 من قيمة الخاصية length للمصفوفة، والإشارة لناتج هذه العملية كرقم فهرس للعنصر الأخير كما هو موضح في المثال التالي: const lastIndex = seaCreatures.length - 1; seaCreatures[lastIndex]; Output starfish محاولة الوصول لعنصر غير موجود سيعيد لنا undefined: seaCreatures[10]; Output undefined للوصول لعنصر مصفوفة متداخلة (مصفوفة داخل مصفوفة)، فعلينا إضافة فهرس اخر يعود للمصفوفة الداخلية: let nestedArray = [ [ "salmon", "halibut", ], [ "coral", "reef", ] ]; nestedArray[1][0]; Output coral في المثال السابق، قمنا بالوصول للعنصر coral بالإشارة لرقم الفهرس الذي يحتوي المصفوفة الداخلية وهو 1، ثم أشرنا للفهرس الذي يحتوي على العنصر في المصفوفة الداخلية وهو 0. إضافة عنصر لمصفوفة في المتغير seaCreatuers يوجد لدينا 5 عناصر بأرقام فهراس تبدأ من 0 الى 4. إذا أردنا أن نُضيف عنصر جديد لهذه المصفوفة، فيمكننا أن نقوم بذلك بإعطاء قيمة للفهرس التالي الذي يلي اخر فهرس: seaCreatures[5] = "whale"; seaCreatures; Output [ 'octopus', 'squid', 'shark', 'seahorse', 'starfish', 'whale' ] إذا قمنا بإضافة عنصر وتجاهلنا قيمة الفهرس التالي ووضعنا بدلا منه فهرس بقيمة 7 مثلا، فإن ذلك يؤدي لإضافة عنصر غير مُعرف (undefined) للمصفوفة كما في المثال التالي: seaCreatures[7] = "pufferfish"; seaCreatures; Output [ 'octopus', 'squid', 'shark', 'seahorse', 'starfish', 'whale', , 'pufferfish' ] هذه المشكلة نستطيع تجنبها باستخدام الوظيفة push() والتي تقوم بإضافة العنصر الجديد في نهاية المصفوفة: // Append lobster to the end of the seaCreatures array seaCreatures.push("lobster"); seaCreatures; Output [ 'octopus', 'squid', 'shark', 'seahorse', 'starfish', , 'whale', 'pufferfish', 'lobster' ] على العكس تماما من الوظيفة push()، فإن الوظيفة unshift() تقوم بإضافة العنصر في بداية المصفوفة: // Append dragonfish to the beginning of the seaCreatures array seaCreatures.unshift("dragonfish"); seaCreatures; Output [ 'dragonfish', 'octopus', 'squid', 'shark', 'seahorse', 'starfish', 'whale', , 'pufferfish', 'lobster' ] باستخدام الوظيفتين السابقتين، ستكون لديك المقدرة على إضافة عناصر جديدة للمصفوفة إما في بدايتها، أو نهايتها. إزالة عنصر من مصفوفة لإزالة عنصر معين من مصفوفة، نستخدم الوظيفة splice(). في المصفوفة seaCreatuers قمنا بإضافة عنصر غير مُعرف وليس له قيمة، ولإزالته نقوم بالتالي: seaCreatures.splice(7, 1); seaCreatures; Output [ 'dragonfish', 'octopus', 'squid', 'shark', 'seahorse', 'starfish', 'whale', 'pufferfish', 'lobster' ] في الوظيفة splice()، المُعامل الأول يشير لرقم الفهرس الذي سنبدأ بالإزالة من عنده (في هذه الحالة 7)، والمُعامل الثاني يشير لعدد العناصر التي نرغب بإزالتها (في حالتنا سيكون 1 حيث أننا نرغب بإزالة عنصر واحد). الوظيفة splice() ستؤثر على المتغير الأصلي. لذلك، إذا أردنا أن نحافظ على المصفوفة الأصلية دون تغيير، نستخدم الوظيفة slice() ونعطي القيمة الناتجة عنها لمتغير جديد. let newArray = slice(7, 1); الوظيفة pop() ستزيل العنصر الأخير من المصفوفة: // Remove the last item from the seaCreatures array seaCreatures.pop(); seaCreatures; Output [ 'dragonfish', 'octopus', 'squid', 'shark', 'seahorse', 'starfish', 'whale', 'pufferfish' ] العنصر lobster أُزيل من المصفوفة لأنه العنصر الأخير، ولإزالة العنصر الأول في المصفوفة، نستخدم الوظيفة shift(): // Remove the first item from the seaCreatures array seaCreatures.shift(); seaCreatures; Output [ 'octopus', 'squid', 'shark', 'seahorse', 'starfish', 'whale', 'pufferfish' ] باستخدام الوظيفتين pop() و shift() نستطيع إزالة العناصر من بداية المصفوفة أو نهايتها. يُفضل استخدام الوظيفة pop() قدر الإمكان، حيث أن باقي العناصر في المصفوفة تبقى في مواقعها دون تغيير. تعديل العناصر في المصفوفة نستطيع تغيير أي قيمة عنصر في المصفوفة وذلك بإعطاء القيمة الجديدة للعنصر باستخدام عملية المساواة، كما نفعل تماما عند التعامل مع المتغيرات العادية: // Assign manatee to the first item in the seaCreatures array seaCreatures[0] = "manatee"; seaCreatures; Output [ 'manatee', 'squid', 'shark', 'seahorse', 'starfish', 'whale', 'pufferfish' ] طريقة أخرى لتغيير قيمة العنصر باستخدام الوظيفة splice() وذلك بإضافة مُعامل جديد. فمثلا، إذا أردنا تغيير قيمة العنصر seahorse والذي يقع في الفهرس 3، نستطيع إزالته وإضافة قيمة جديدة بدلا منه: // Replace seahorse with sea lion using splice method seaCreatures.splice(3, 1, "sea lion"); seaCreatures(); Output [ 'manatee', 'squid', 'shark', 'sea lion', 'starfish', 'whale', 'pufferfish' ] في المثال السابق، قمنا بإزالة العنصر seahorse من المصفوفة، ووضعنا بدلا منه القيمة sea lion في نفس الفهرس 3. حلقة التكرار خلال المصفوفة نستطيع المرور على عناصر المصفوفة من خلال حلقة تكرار for وذلك بالاستفادة من خاصية length. في المثال التالي، نُنشئ مصفوفة باسم shellfish ونطبع رقم كل فهرس فيها بالإضافة إلى قيمة العنصر المرتبط بالفهرس: // Create an array of shellfish species let shellfish = [ "oyster", "shrimp", "clam", "mussel", ]; // Loop through the length of the array for (let i = 0; i < shellfish.length; i++) { console.log(i, shellfish[i]); } Output 0 'oyster' 1 'shrimp' 2 'clam' 3 'mussel' نستطيع أيضا استخدام حلقة التكرار for…of وهي خاصية جديدة في الجافا سكريبت: // Create an array of aquatic mammals let mammals = [ "dolphin", "whale", "manatee", ]; // Loop through each mammal for (let mammal of mammals) { console.log(mammal); } Output dolphin whale manatee حلقة التكرار for…of لا تقوم باستخدام رقم الفهرس للعناصر في المصفوفة، ولكنها بشكل عام طريقة أبسط وأكثر اختصارا للمرور على المصفوفة من خلال حلقة التكرار. استخدام حلقات التكرار مفيد بشكل كبير في طباعة قيم عناصر المصفوفة وهو يشبه عرض العناصر من قاعدة بيانات خلال موقع الكتروني. خاتمة تُعتبر المصفوفات جزء أساسي ومهم في برمجة الجافا سكريبت. في هذا الدرس تعلمنا كيفية إنشاء المصفوفة، وكيفية فهرستها، وتعلمنا إجراء بعض العمليات المهمة على المصفوفات مثل إزالة العناصر والتعديل عليها. وكذلك تعلمنا طريقتين للمرور على عناصر المصفوفة من خلال حلقات التكرار والتي تهدف لإجراء عمليات على عناصر المصفوفة مثل طباعة محتوياتها وطباعة أرقام الفهارس. ترجمة -وبتصرّف- للمقال Understanding Arrays in JavaScript لصاحبه Tania Rascia حقوق الصورة البارزة محفوظة لـ Freepik
  6. تعتمد صفحات الويب على HTML التي تُحدد محتوى الصفحة.تعدّ CSS لغة منفصلة عن HTML ودورها هو تحديد الشكل والمظهر الخاص بصفحة الويب. الشفرة الخاصّة بـ CSS عبارة عن قواعد ساكنة. كل قاعدة تأخذ مُحدِدًا Selector أو أكثر، وتُرجع قيم لمجموعة من الخصائص الشكلية. تُطبَّق هذه الخصائص بعد ذلك على عناصر صفحة الويب المشار إليها بواسطة المحددات. ملاحظة: لتعلم CSS بطريقة صحيحة، ولأن مخرجات هذه اللغة عبارة عن نتائج مرئية، يجب عليك ممارسة كل ما تتعلمه وننصحك بتطبيقه على موقع dabblet. الهدف الرئيسي من هذا المقال التركيز على كيفية الكتابة الصحيحة مع بعض النصائح. سنتناول في هذا المقال العناوين التالية: التعليقات. المحددات. الخصائص. طريقة استخدام CSS في الصفحة. الأولوية أم التتالي. استعلامات الميديا. التوافقية. التعليقات توجد طريقة واحدة لكتابة التعليقات في ملف CSS وهي كتابة التعليقات بين الرمزين /* */. /* التعليقات تُتكتب هنا ولا يوجد نمط لكتابة التعليق في سطر واحد سوى هذه الطريقة */ المحددات يُستخدم المحدد في استهداف عنصر في صفحة، ويُكتَب بالطريقة التالية: selector { property: value; /* خصائص أخرى*/ } لنفترض وجود العنصر التالي من نوع div في صحة ويب: <div class='class1 class2' id='anID' attr='value' otherAttr='en-us foo bar' /> تستطيع تطبيق قاعدة CSS على هذا العنصر باستخدام أحد أسماء الأصناف Classes التي ينتمي إليها في الصفحة: .class1 { } أو باستخدام جميع أسماء الأصناف المطبَّقة عليه: .class1.class2 { } أو باستخدام نوع العنصر: div { } أو باستخدام الرقم الخاص بالعنصر: #anID { } نستطيع تحدد العنصر من الصفحة في حال وجود صفة Attribute باسم معين: [attr] { font-size:smaller; } أو في حالة وجود صفة بقيمة معينة: [attr='value'] { font-size:smaller; } في حالة وجود صفة معينة في عنصر، ونريد تطبيق قاعدة على هذا العنصر بشرط وجود قيمة تبدأ منها الصفة نستخدم الطريقة التالية: [attr^='val'] { font-size:smaller; } أما إن كان الشرط يتعلّق بقيمة معينة تنتهي بها الصفة: [attr$='ue'] { font-size:smaller; } نستطيع تحديد العنصر في حالة احتواء الصفة على قيمة معينة ضمن قائمة قيم منفصلة عن بعضها بمسافة فارغة (يوافق الشرط أدناه العناصر التي لديها صفة otherAttr تساوي “foo” أو “foo bar” أو “foo bar far” …إلخ): [otherAttr~='foo'] { } أو تحديد العنصر في حالة احتواء الصفة على قيمة معينة ضمن قائمة قيم منفصلة عن بعضها برمز – كما في المثال التالي: [otherAttr|='en'] { font-size:smaller; } نستطيع جمع أكثر من مُحدد ببعضها للحصول على مُحدد مُركز كما في المثال التالي: div.some-class[attr$='ue'] { } من الممكن أيضا أن تقوم بتحديد عنصر يكون تابعا (ابن) لعنصر آخر: div.some-parent > .class-name { } في المثال السابق، يكون العنصر الابن على مستوى واحد أسفل من العنصر الأب. تستطيع أن تُحدد عنصر من سلالة عنصرآأخر (الأب)، وهذا يعني من أي مستوى أسفل من مستوى العنصر الأب (ليس شرطا أن يكون مستوى واحد أقل): div.some-parent .class-name { } يختلف المُحدد التالي عن المُحدد السابق لوجود مسافة فاصلة بين الأسماء: div.some-parent.class-name { } نستطيع تحديد العنصر بناءً على عنصر آخر مجاور له باستخدام الطريقة التالية: .i-am-just-before + .this-element { } أو بناءً على أي عنصر يسبق العنصر الذي نريده: .i-am-any-element-before ~ .this-element { } توجد بعض المُحددات تسمى الأصناف الزائفة pseudo classes تطبَّق على العنصر عندما يكون بحالة محددة، فمثلا، نستطيع تحديد عنصر عندما يمر عليه المؤشر: selector:hover { } أو رابط تمت زيارته: selector:visited { } أو لم تتم زيارته: selected:link { } أو عنصر في حالة التركيز: selected:focus { } لتحديد أول عنصر تابع لعنصر: selector:first-child {} لتحديد آخر عنصر تابع لعنصر: selector:last-child {} نستطيع تنسيق أجزاء محددة من العنصر باستخدام العناصر الزائدة Pseudo elements، فمثلا، نستخدم before لإضافة محتوى قبل محتوى عنصر معين: selector::before {} وafter لإضافة محتوى بعد محتوى عنصر معين: selector::after {} في أماكن معينة، يُستخدم رمز * كحرف “بدل” لاختيار كافة العناصر. * { } /* كل العناصر */ .parent * { } /* كل التابعين */ .parent > * { } /* كل الأبناء */ الخصائص selector { وحدات الطول إما مطلقة أو نسبية. الوحدات النسبية: width: 50%; /* نسبة من عرض العنصر الأب */ font-size: 2em; /* مضاعفة حجم الخط الخاص بالعنصر نفسه*/ font-size: 2rem; /* مضاعفة حجم الخط حسب حجم الخط الخاص بالعنصر الأب */ font-size: 2vw; /* مضاعفة حجم الخط بالنسبة ل 1% من عرض المساحة المرئية للمستخدم*/ font-size: 2vh; /* من الارتفاع*/ font-size: 2vmin; /* مضاعفة حجم الخط لأصغر قيمة من الارتفاع أو العرض*/ font-size: 2vmax; /* لاكبر قيمة */ القيم المطلقة: width: 200px; /* بكسل */ font-size: 20pt; /* نقطة */ width: 5cm; /* سنتميتر */ min-width: 50mm; /* مليميتر */ max-width: 5in; /* إنش */ الألوان: color: #F6E; /* صيغة سداسية قصيرة */ color: #FF66EE; /* صيغة سداسية طويلة */ color: tomato; /* حسب الاسم */ color: rgb(255, 255, 255); /* كقيم rgb */ color: rgb(10%, 20%, 50%); /* كنسبة rgb */ color: rgba(255, 0, 0, 0.3); /* كقيم rgba */ color: transparent; /* الشفافية صفر*/ color: hsl(0, 100%, 50%); /* كنسب hsl */ color: hsla(0, 100%, 50%, 0.3); /* كنسب hsla */ الحدود: border-width:5px; border-style:solid; border-color:red; border: 5px solid red; /* اختصار القواعد السابقة في قاعدة واحدة */ border-radius:20px; /* خاصية ابتداء من css3 */ الصور: background-image: url(/img-path/img.jpg); الخطوط: font-family: Arial; إذا كان اسم الخط به مسافات فيجب وضع الاسم بين علامتي تنصيص: font-family: "Courier New"; في نسرُد لائحة من الخطوط وإن لم يجد المتصفح الخط، يستخدم نوع الخط التالي: font-family: "Courier New", Trebuchet, Arial, sans-serif; } طريقة استخدام CSS في الصفحة توجد ثلاث طرق لإجراء عملية تنسيق صفحة الويب وهي كالتالي: 1- تضمين اسم الملف بالامتداد .css داخل العنصر head في بداية صفحة html كالتالي (وهي الطريقة المُوصى بها): <link rel='stylesheet' type='text/css' href='path/to/style.css'> 2- كتابة قواعد css مباشرة في ملف الصفحة: <style> a { color: purple; } </style> 3- تنسيق العنصر بطريقة مباشرة: <div style="border: 1px solid red;"> </div> الأولوية أم التتالي العنصر الواحد في صفحة الويب قد يكون مُستهدفا (محددا) من قبل مجموعة متعددة من المحددات، وقد يكون هناك تعديل أو تحديد لقيمة خاصية تابعة لهذا العنصر من قبل أكثر من مُحدد. في مثل هذه الحالات، فإن قاعدة واحدة سيكون لها الأولوية في التطبيق على هذا العنصر. القواعد التي تمتلك محددات مُفصلة لها أولوية على المحددات ذات التفاصيل الأقل، والقواعد التي تأتي في النهاية تقوم بالتعديل على القواعد التي قبلها (وهذا يعني أنه في حالة تضمين ملفي css وكلاهما يقومان بتحديد عنصر والتعديل على خصائصه، فإن ترتيب ربط الملفات يحكم تنسيق العنصر حسب قاعدة الترتيب المذكورة والتي تُسمى التتالي أو التتابع). استعلامات الوسائط Media queris استعلامات الوسائط هي خاصية بدأت من CSS 3 وتسمح لك بتحديد متى تُطبَّق قواعد CSS، مثلا عند الطباعة، أو عند كثافة وأبعاد شاشة معينين. أمثلة: هذه قاعدة CSS تُطبَّق على كل الأجهزة. h1 { font-size: 2em; color: white; background-color: black; } يعدّل استعلام الوسيط التالي القاعدة السابقة عند الطباعة: @media print { h1 { color: black; background-color: white; } } يجعل استعلام الوسيط التالي حجم الخط أكبر في شاشة بعرض 480 بكسل على الأقل: @media screen and (min-width: 480px) { h1 { font-size: 3em; font-weight: normal; } } تحتوي استعلامات الوسائط على الخصائص التالية: width, height, device-width, device-height, orientation, aspect-ratio, device-aspect-ratio, color,color-index, monochrome, resolution, scan, grid. من الممكن أن يسبق أغلب الخصائص السابقة خاصيتي min- و max-. خاصية الدقة Resolution غير مدعومة على الأجهزة القديمة، وبدلا من ذلك استخدم خاصية device-pixel-ratio. تحاول كثير من أجهزة الجوال والأجهزة اللوحية عرض الصفحة كما لو كانت على سطح المكتب العادي إلا إذا قمت بإضافة الصفة viewport بالشكل التالي: <head> <meta name="viewport" content="width=device-width; initial-scale=1.0"> </head> التوافقية أغلب المميزات والخصائص الموجودة في CSS2 وكثيرا من CSS3 موجودة في كل المتصفحات والأجهزة، ولكن ينصح من باب الممارسة الأفضل أن يتم الفحص قبل استخدام الخاصية الجديدة. ترجمة – بتصرّف – للمقال Learn X in Y minutes Where X=CSS.
  7. وضع Netscape’s Brendan Eich أسس جافا سكريبت سنة 1995. وكان الغرض منها أن تكون لغة ترميز سهلة خاصة بالمواقع ومكملة للجافا في تطبيقات الويب المعقدة، ولكن سهولة دمج جافا سكريبت والدعم الذاتي لها مع المتصفحات جعلها أكثر شيوعًا من لغة الجافا الأصلية في واجهات الويب. لا يقتصر استخدام جافا سكريبت مقتصرة على المتصفحات، ف Node.js مشروع قائم بذاته ويقدم إمكانية بناء تطبيقات إنترنت قائمة بذاتها. صيغة الشفرة البرمجية الخاصة بجافا سكريبت شبيهة بطريقة كتابة لغة C، فإذا كنت قد تعاملت مع لغة البرمجة C قبل ذلك أو جافا، ستكون الكثير من الأساسيات مألوفة لك. على الرغم من ذلك، وعلى الرغم من سهولة الاسم، إلا أن النموذج الكائني في جافا سكريبت مختلف تماماً عن الموجود في الجافا. سنتناول في هذا المقال المواضيع التالية: التعليقات. الأرقام، النصوص والعمليات. المتغيرات، المصفوفات والكائنات. جمل التحكم والمنطق. الدوال، نطاق الوصول و Closures. المشيّدات Constructors والنماذج الأولية Prototypes التعليقات لكتابة تعليق من سطر واحد نبدأ السطر بعلامتي / كما في السطر التالي: // Single-line comments start with two slashes. لكتابة تعليق من أكثر من سطر، نستخدم /* و */ في إحاطة الأسطر التي نريدها كما في الأسطر التالية: /* Multiline comments start with slash-star، and end with star-slash */ تنتهي الجمل في جافا سكريبت بفاصلة منقوطة، ولكن هذا الأمر غير ضروري، حيث يتم إضافة الفاصلة المنقوطة تلقائيا عند وجود سطر جديد وعدم وجود الفاصلة، وهذا الأمر مستثنى في بعض الحالات: doStuff(); بدون فاصلة: doStuff() سنعتمد في هذا الدرس استخدام الفاصلة المنقوطة. الأرقام، النصوص والعمليات تحتوي جافا سكريبت على نوع رقمي واحد (64-bit IEEE 754 double). الأرقام من نوع Double (الأعداد الحقيقة) تحتوي على 52 بت من الأساس العشري، بما يكفي لتخزين الأعداد الصحيحة Integers حتى 9✕10¹⁵ بدقة. 3; // = 3 1.5; // = 1.5 بعض العمليات الحسابية الأساسية: 1 + 1; // = 2 0.1 + 0.2; // = 0.30000000000000004 8 - 1; // = 7 10 * 2; // = 20 35 / 5; // = 7 5 / 2; // = 2.5 10 % 2; // = 0 30 % 4; // = 2 18.5 % 7; // = 4.5 العمليات الثنائية متاحة أيضا، فعند إجراءك لعملية ثنائية، فإن الأعداد العشرية Float يتم تحويله إلى أعداد طبيعية Int حتى 32 بت: 1 << 2; // = 4 ترتيب العمليات يتم بواسطة استخدام الأقواس: (1 + 3) * 2; // = 8 توجد ثلاثة قيم أرقام غير حقيقية كالتالي: Infinity; // ناتجة عن قسمة رقم موجب على صفر -Infinity; // ناتجة عن قسمة رقم سالب على صفر NaN; //تشير إلى قيمة "غير رقم" القيم المنطقية: true; false; يتم استخدام علامة التنصيص المنفردة أو المزدوجة لبناء النصوص: 'abc'; "Hello، world"; لعكس القيمة نستخدم علامة التعجب: !true; // = false !false; // = true لفحص المساواة: 1 === 1; // = true 2 === 1; // = false لفحص عدم المساواة: 1 !== 1; // = false 2 !== 1; // = true عمليات المقارنة: 1 < 10; // = true 1 > 10; // = false 2 <= 2; // = true 2 >= 2; // = true دمج النصوص يتم بواسطة عملية + : "Hello " + "world!"; // = "Hello world!" وعملية الدمج + لا تعمل فقط مع النصوص، بل مع الأرقام أيضا والتراكيب مثل المصفوفات: "1، 2، " + 3; // = "1، 2، 3" "Hello " + ["world"، "!"] // = "Hello world،!" من الممكن مقارنة النصوص: "a" < "b"; // = true لإجراء عملية فحص المساواة باعتبار تحويل أنواع البيانات (في حالة اختلافها) نستخدم عملية = مرتين: "5" == 5; // = true null == undefined; // = true في حالة استخدام = ثلاثة مرات، لا تتم عملية التحويل: "5" === 5; // = false null === undefined; // = false لابد من الانتباه من التحويل التلقائي للنوع تجنبا لبعض الحالات غير المرغوبة: 13 + !0; // 14 "13" + !0; // '13true' نستخدم charAt للوصول لمحرف Character معين في النصوص بتعيين مكانها في سلسلة المحارف: "This is a string".charAt(0); // = 'T' أو نستخدم substring للحصول على أجزاء أكبر من النصوص: "Hello world".substring(0, 5); // = "Hello" length تعتبر خاصية، لذلك لا تستخدم الأقواس في النهاية: "Hello".length; // = 5 نستخدم null للإشارة للفارغ أو غير الموجود، بينما نستخدم undefined للإشارة لقيمة غير منشأة أو غير موجودة حاليا (مثل تعريف متغير وعدم إعطائه قيمة). القيم false، null، undefined، NaN، 0 ،”” كلها قيم خاطئة (تستخدم كقيمة منطقية خطأ) والباقي صحيح. المتغيرات، المصفوفات، والكائنات يتم تعريف المتغيرات باستخدام كلمة var. جافا سكريبت ديناميكية النوع، حيث لا يجب عليك تحديد نوع المتغير عند تعريفه، ولإعطاء قيمة للمتغير نستخدم = كما في المثال التالي: var someVar = 5; عند عدم استخدام كلمة var لن يظهر لك خطأ، ولكن في هذه الحالة فإن المتغير يكون مستواه على نطاق الوصول العام Global scope ولن يكون على المستوى الذي تم تعريفه فقط. someOtherVar = 10; المتغيرات التي لا تأخذ قيمة عند تعريفها تكون بقيمة undefined تلقائيا: var someThirdVar; // = undefined لتعريف أكثر من متغير في نفس السطر نفصل بين المتغيرات بفاصلة عادية: var someFourthVar = 2، someFifthVar = 4; نستطيع اختصار كتابة العمليات الحسابية بالشكل التالي: someVar += 5; // هذا يعادل someVar = someVar + 5; someVar is 10 now someVar *= 10; // someVar = 100 someVar++; // someVar = 101 someVar--; // back = 100 المصفوفات عبارة عن قائمة من القيم المرتبة من أي نوع: var myArray = ["Hello"، 45، true]; نستطيع الوصول لمحتويات المصفوفة باستخدام الأقواس المعكوفة والفهرس. فهرس المصفوفات يبدأ من صفر: myArray[1]; // = 45 المصفوفات غير ثابتة وذات حجم متغير: myArray.push("World"); myArray.length; // = 4 للتعديل أو الإضافة في موقع معين في المصفوفة: myArray[3] = "Hello"; لتعريف قاموس (Hash): var myObj = {key1: "Hello"، key2: "World"}; المفاتيح في القاموس عبارة عن نص، أو مُعرف صحيح، والقيم تأخذ أي نوع: var myObj = {myKey: "myValue"، "my other key": 4}; للوصول إلى قيمة باستخدام مفتاح والأقواس المعكوفة: myObj["my other key"]; // = 4 أو باستخدام صيغة النقطة والمُعِرف الذي يمثل المفتاح: myObj.myKey; // = "myValue" الكائنات في جافا سكريبت غير ثابتة وقابلة للتعديل: myObj.myThirdKey = true; إذا حاولت الوصول لقيمة غير موجودة في القاموس، ستكون النتيجة المرجعة undefined: myObj.myFourthKey; // = undefined جمل التحكم والمنطق جملة if: var count = 1; if (count == 3){ // ستُنفَّذ هذه الشفرةإذا كانت قيمة المتغير تساوي 3 } else if (count == 4){ // ستُنفَّذ هذه الشفرةإذا كانت قيمة المتغير تساوي 4 } else { // ستُنفَّذ هذه الشفرة في حالة عدم تحقق أي شرط سابق } جملة while: while (true){ // جملة تكرار غير منتهية } جملة do تشبه جملة while إلا أنها تُكرَّر مرة واحدة على الأقل: var input; do { input = getInput(); } while (!isValid(input)) جملة For تشبه الموجودة في لغة سي وجافا: for (var i = 0; i < 5; i++){ // ستُنفَّذ هذه الشفرة خمس مرات } توقف جملة التكرار باستخدام break مع تحديد اسم جملة التكرار التي نريد وقفها: outer: for (var i = 0; i < 10; i++) { for (var j = 0; j < 10; j++) { if (i == 5 && j ==5) { break outer; } } } تسمح لنا جملة for/in بالمرور على خصائص ومحتويات الكائن. في المثال التالي نقوم بالمرور على محتوى قاموس وحفظ النتيجة في متغير: var description = ""; var person = {fname:"Paul"، lname:"Ken"، age:18}; for (var x in person){ description += person[x] + " "; } // description = 'Paul Ken 18 ' العملية المنطقية and تُمَثَلْ ب && والعملية or تُمَثَلْ ب ||: if (house.size == "big" && house.colour == "blue"){ house.contains = "bear"; } if (colour == "red" || colour == "blue"){ // colour is either red or blue } نستفيد من && و || في تحديد القيم التلقائية كما في المثال التالي: var name = otherName || "default"; جملة switch تفحص المساواة باستخدام ===، استخدم break بعد كل حالة فحص وإلا سيتم تنفيذ حالة case الصحيحة التالية أيضا: grade = 'B'; switch (grade) { case 'A': console.log("Great job"); break; case 'B': console.log("OK job"); break; case 'C': console.log("You can do better"); break; default: console.log("Oy vey"); break; } الدوال، نطاق الوصول وClosures تُعرَّف الدوال في جافا سكريبت باستخدام كلمة function: function myFunction(thing){ return thing.toUpperCase(); } myFunction("foo"); // = "FOO" لابد الانتباه أن تضع القيمة المرجعة في نفس السطر الموجودة به كلمة return، إذا لم يكن كذلك، ستكون النتيجة المرجعة undefined بسبب الإضافة التلقائية للفاصلة المنقوطة عند كل سطر جديد (وقد نوهنا لهذه النقطة في البداية). function myFunction(){ return // الفاصلة المنقوطة مضافة تلقائيا هنا {thisIsAn: 'object literal'} } myFunction(); // = undefined يُتعامل مع الدوال في جافا سكريبت بوصفها كائنات، وهذا يعني أنك تستطيع تمرير الدالة معاملا لدالة أخرى، أو قيمة لمتغير. تُستخدَم الدالة myFunction في معالجة حدث في المثال التالي، حيث سيتم تنفيذها بعد فترة زمنية محددة: function myFunction(){ } // ينتُج عن السطر التالي تنفيذ الدالة أعلاه بعد 5 ثوان setTimeout(myFunction، 5000); ملاحظة: الدالة setTimeout ليست جزءًا من جافا سكريبت، ولكنها مقدمة من قبل المتصفحات و Node.js. وظيفة setInterval أيضا مقدمة من قبل المتصفحات. function myFunction(){ } setInterval(myFunction، 5000); ليس من الشرط تحديد اسم الدالة، ونستطيع كتابة الدالة دون اسم في المكان الذي يتم تمرير قيمتها المُرجعة فيه بالطريقة التالية: setTimeout(function(){ }, 5000); تمتلك جافا سكريبت نطاقاً وظيفياً، حيث لكل دالة نطاقها الخاص، بينما الكتل الأخرى لا تشاركها هذا النطاق. if (true){ var i = 5; } i; // = 5 إن كتبنا الشفرة في المثال السابق داخل دالة فإن قيمة المتغير i تساوي 5 على عكس ما تتوقعه في النطاق الكتلي، بمعنى أن المتغيرات مُشاهدَة ونستطيع الوصول إليها على مستوى الدالة بغض النظر عن مكان تعريفها داخل هذه الدالة. وهذا يشير إلى نمط متعارف عليه يمنع المتغيرات المؤقتة من الظهور في نطاق الوصول العام. وللتوضيح على ما سبق، في المثال التالي، يبقى المتغير temporary داخل نطاق الدالة المُعرف فيها، أما المتغيرات في النطاق العام مثل permanent فنستطيع الوصول إليه باستخدام الكائن العام والمسمى في كل المتصفحات ب window وتكون صيغة الوصول للمتغير هكذا window.permanent. الكائن ذو النطاق العام يختلف اسمه في البيئات التي لا علاقة لها بالمتصفحات مثل Node.js. (function(){ var temporary = 5; window.permanent = 10; })(); temporary; // raises ReferenceError permanent; // = 10 من أقوى خصائص لغة جافا سكريبت وجود ما يسمى بclosures ، حيث إذا كانت دالة مُعرفة داخل دالة أخرى، فإن الدالة الداخلية تمتلك الوصول لكافة المتغيرات الخاصة بالدالة الخارجية حتى بعد خروجها وانتهائها. في المثال التالي، فإن استدعاء الدالة setTimeout سيتم تنفيذها مباشرة بعد استدعاء الدالة الخارجية sayHelloInFiveSeconds والتي ستنتهي مباشرة. ومن ثم سوف يبدأ العد حتى 5 ثوان لاستدعاء الوظيفة الداخلية، وعند انتهاء المدة، وعلى الرغم من خروج وانتهاء الدالة الخارجية، إلا أنه سيتم تنفيذ الداخلية بنجاح وسيتم الوصول للمتغير prompt دون مشاكل. function sayHelloInFiveSeconds(name){ var prompt = "Hello، " + name + "!"; function inner(){ alert(prompt); } setTimeout(inner، 5000); } // سيتم طباعة "مرحبا أدم" بعد 5 ثواني sayHelloInFiveSeconds("Adam"); المشيّدات Constructors والنماذج الأولية Prototypes يمكن للكائنات أن تحتوي على دوال، كما في المثال التالي: var myObj = { myFunc: function(){ return "Hello world!"; } }; myObj.myFunc(); // = "Hello world!" عندما يتم استدعاء دوال معرَّفة في كائن، فإن هذه الدوال تستطيع الوصول للكائن التي عُرِّفت فيه باستخدام كلمة this كما في المثال التالي: myObj = { myString: "Hello world!"، myFunc: function(){ return this.myString; } }; myObj.myFunc(); // = "Hello world!" الدالة myFunc لا تعمل إذا لم يتم استدعاؤها في سياق الكائن الذي تتصل به، لاحظ في المثال التالي: var myFunc = myObj.myFunc; myFunc(); // = undefined نستطيع ربط دالة بكائن والوصول لمتغيرات هذا الكائن بواسطة this على الرغم من أن هذه الدالة لم تُعرَّف مع تعريف بالكائن. var myOtherFunc = function(){ return this.myString.toUpperCase(); } myObj.myOtherFunc = myOtherFunc; myObj.myOtherFunc(); // = "HELLO WORLD!" نستطيع أيضا تحديد سياق الدالة لتنفيذها من خلاله وذلك عن طريق استدعاء الوظيفة باستخدام call او apply. var anotherFunc = function(s){ return this.myString + s; } anotherFunc.call(myObj، " And Hello Moon!"); // = "Hello World! And Hello Moon!" استخدمنا في المثال السابق الدالة call. تؤدّي الدالة apply نفس الغرض ولكننا نمرر لها مصفوفة معاملات، وهذا يفيدنا في حالة التعامل مع دالة تقبل مجموعة من المعاملات. anotherFunc.apply(myObj، [" And Hello Sun!"]); // = "Hello World! And Hello Sun!" Math.min(42، 6، 27); // = 6 Math.min([42، 6، 27]); // = NaN (uh-oh!) Math.min.apply(Math، [42، 6، 27]); // = 6 لاحظ أننا عند استخدام apply و call قمنا بتمرير السياق الذي نريده من خلال myObj. إذا أردنا أن نُثبت السياق الذي نريد تنفيذ الدالة من خلاله، فإننا نستخدم bind عوضا عن ذلك. var boundFunc = anotherFunc.bind(myObj); boundFunc(" And Hello Saturn!"); // = "Hello World! And Hello Saturn!" نستطيع استخدام bind لتطبيق دالة جزئيا، انظر المثال التالي: var product = function(a، b){ return a * b; } var doubler = product.bind(this، 2); doubler(8); // = 16 عند استدعاء دالة بواسطة الكلمة new فإن كائناً جديداً يتم إنشاؤه وسوف يكون متاحا للدالة بواسطة كلمة this. الدوال التي صُممت للاستدعاء بهذه الطريقة تسمى المشيّدات constructors. var MyConstructor = function(){ this.myNumber = 5; } myNewObj = new MyConstructor(); // = {myNumber: 5} myNewObj.myNumber; // = 5 على خلاف لغات البرمجة الكائنية الأخرى، جافا سكريبت لا تحتوي على مفهوم العيّنة Instance أو “الكائن المتولد من الفئة عند التشغيل”. تُقدم جافا سكريبت المفاهيم الكائنية مثل التوليد والوراثة من خلال مفهوم واحد يسمى النموذج الأولي Prototype. كل كائن في الجافا سكريبت يحتوي على نموذج أولي. عندما تقوم بمحاولة استخدام لخاصية غير موجودة في كائن معين، فإن مفسر جافا سكريبت سوف ينظر في النموذج الأولي للكائن. تجعلك بعض تطبيقات الجافا سكريبت تصل لنموذج الكائن بواسطة الخاصية “proto“. على الرغم من أن هذه الطريقة مفيدة في شرح مفهوم النموذج الأولي، إلا أنها ليست الطريقة المعيارية لذلك، وسوف نشرح الطريقة الصحيحة لهذا الأمر لاحقا. var myObj = { myString: "Hello world!" }; var myPrototype = { meaningOfLife: 42، myFunc: function(){ return this.myString.toLowerCase() } }; myObj.__proto__ = myPrototype; myObj.meaningOfLife; // = 42 myObj.myFunc(); // = "hello world!" في حال لم تكن الخاصية موجودة في النموذج الأولي، فإن المفسر يبحث في نموذج النموذج وهكذا. myPrototype.__proto__ = { myBoolean: true }; myObj.myBoolean; // = true لا يوجد نُسَخْ عند استخدام النموذج، حيث إن كل كائن يقوم بالتأشير للنموذج الخاص به، وهذا يعني أن أي تغيير على النموذج سوف ينعكس في كل مكان آخر. myPrototype.meaningOfLife = 43; myObj.meaningOfLife; // = 43 جملة for/in تسمح بالمرور على خصائص كائن، مرورا بسلسلة النماذج الأولية حتى الوصول إلى نموذج فارغ. for (var x in myObj){ console.log(myObj[x]); } ///prints: // Hello world! // 42 // [Function: myFunc] للمرور على خصائص الكائن دون النموذج، نستخدم وظيفة hasOwnProperty كما في المثال التالي: for (var x in myObj){ if (myObj.hasOwnProperty(x)){ console.log(myObj[x]); } } ///prints: // Hello world! كما ذكرنا سابقا، فإن استخدام “proto” في تعريف نموذج كائن هي طريقة غير معيارية، ولا يوجد طريقة لتغيير نموذج أولي لكائن موجود. على الرغم من ذلك، توجد طريقتان لإنشاء كائن مع نموذج مُعطى. الأولى هي استخدام Object.create: var myObj = Object.create(myPrototype); myObj.meaningOfLife; // = 43 الطريقة الثانية – مضمونة أكثر - باستخدام المشيّدات. تمتلك المشيّدات خاصية تسمى prototype تُحدَّد عند إنشاء كائن جدي باستخدام كلمة new، المثال التالي يشرح هذا الأمر: MyConstructor.prototype = { myNumber: 5، getMyNumber: function(){ return this.myNumber; } }; var myNewObj2 = new MyConstructor(); myNewObj2.getMyNumber(); // = 5 myNewObj2.myNumber = 6 myNewObj2.getMyNumber(); // = 6 توجد لدى أنواع البيانات مثل النصوص والأرقام مشيّدات تقوم بإنشاء كائنات تعادل الكائنات المنشأة بطريقة عادية. عدا أنها ليست متماثلة تماما! var myNumber = 12; var myNumberObj = new Number(12); myNumber == myNumberObj; // = true typeof myNumber; // = 'number' typeof myNumberObj; // = 'object' myNumber === myNumberObj; // = false if (0){ //لن تُنفَّذ هذه الشفرة لأن قيمة الصفر خاطئة } if (new Number(0)){ //سوف تُنفَّذ هذه الشفرة لأن الرقم في الشرط عبارة عن كائن وليس نوع رقم، والكائنات دائما ذات قيمة منطقية صحيحة } الكائنات المغلفة أو العادية تتشارك في النموذج الأولي الخاص بنوعها، فمثلا، نستطيع إضافة خاصية على النموذج الخاص بنوع string بهدف الحصول على الحرف الأول من النص، كما في المثال التالي: String.prototype.firstCharacter = function(){ return this.charAt(0); } "abc".firstCharacter(); // = "a" تُستخدَم الخاصية السابقة غالباً في ما يُعرَف بالملْء المتعدّد Polyfilling والتي تُطَبِقْ مميزات أحدث من جافا سكريبت في مجموعة قديمة من نُسخ جافا سكريبت بهدف استخدام هذه المميزات الحديثة في بيئات قديمة مثل المتصفحات المنتهية تاريخا. ملاحظة: تنفيذ Object.create قد يكون غير متاح في بعض التطبيقات، ولكننا نستطيع استخدام الملْء المتعدّد لتعويض هذا الغياب كالتالي: if (Object.create === undefined){ //في حالة كانت موجودة لا تعدل عليها Object.create = function(proto){ // أنشئ مشيّدًا مؤقتا باستخدام النموذج الأولي المناسب var Constructor = function(){}; Constructor.prototype = proto; return new Constructor(); } } ترجمة – بتصرّف – للمقال Learn X in Y minutes Where X=javascript.
  8. تأتي وحدة التسجيل (logging) مبدئيا توزيعة بايثون المعيارية وتقدم حلا لمتابعة الأحداث التي تحصُل أثناء عمل البرمجية. تستطيع إضافة استدعاءات التسجيل للشيفرة البرمجية الخاصة بك للإشارة لما تحقق من أحداث. تسمح لك وحدة التسجيل بإعداد كل من التسجيلات التشخيصية التي تسجل الأحداث المقترنة بعمليات التطبيق، بالإضافة إلى تسجيلات التدقيق التي تسجل الأحداث الخاصة بحركات المستخدم بهدف تحليلها. وحدة التسجيل هذه مختصة في حفظ السجلات في ملفات. لماذا نستخدم وحدة التسجيل؟ تحافظ وحدة التسجيل على سجل الأحداث التي تحدث خلال عمل البرنامج، مما يجعل من رؤية مخرجات البرنامج المتعلقة بأحداثه أمراً متاحاً. قد يكون استخدام الأمرprint أمرا مألوفا لك خلال الشيفرة البرمجية لفحص الأحداث. يقدم الأمر print طريقة بدائية لإجراء عملية التنقيح الخاصة بحل المشاكل خلال عمل البرمجية. بينما يعد تضمين تعليمات print خلال الشيفرة البرمجية طريقة لمعرفة مسار تنفيذ البرنامج والحالة الحالية له، إلا أن هذه الطريقة اُثبت أنها أقل قدرة على الصيانة من استخدام وحدة التسجيل في بايثون، وذلك للأسباب التالية: باستخدام تعليمات print يصبح من الصعب التفرقة بين مخرجات البرنامج الطبيعية وبين مخرجات التنقيح لتشابههما. عندما تنتشر تعليمات print خلال الشيفرة البرمجية، فإنه لا توجد طريقة سهلة لتعطيل التعليمات الخاصة بالتنقيح. من الصعب إزالة كافة تعليمات print عندما تنتهي من عملية التنقيح. لا توجد سجلات تشخيصية للأحداث. من الجيد البدء بالتعود على استخدام وحدة التسجيل المعيارية في بايثون خلال كتابة الشيفرة البرمجية بما أنها طريقة تتلاءم أكثر مع التطبيقات التي يكبر حجمها عن سكريبتات بايثون بسيطة، وكذلك بما أنها تقدم طريقة أفضل للتنقيح. طباعة رسائل التنقيح في وحدة التحكم إذا كنت متعودا على استخدام تعليمات print لرؤية ما يحدث في برنامجك خلال العمل، فمن المحتمل مثلا أنك تعودت على رؤية برنامج يُعرف صنفًاClass وينشئ منه عناصر كما في المثال التالي: class Pizza(): def __init__(self, name, price): self.name = name self.price = price print("Pizza created: {} (${})".format(self.name, self.price)) def make(self, quantity=1): print("Made {} {} pizza(s)".format(quantity, self.name)) def eat(self, quantity=1): print("Ate {} pizza(s)".format(quantity, self.name)) pizza_01 = Pizza("artichoke", 15) pizza_01.make() pizza_01.eat() pizza_02 = Pizza("margherita", 12) pizza_02.make(2) pizza_02.eat() توجد في الشيفرة السابقة الدالة__init__ التي تستخدم لتعريف خصائصname وprice للصنفPizza. كما تحتوي على الدالتين make لصنع البيتزا، وeat لأكلها وتأخذان المعطى quantity ذا القيمة الافتراضية 1. لنشغل البرنامج: >> python pizza.py وسنحصل على المخرج التالي: Output Pizza created: artichoke ($15) Made 1 artichoke pizza(s) Ate 1 pizza(s) Pizza created: margherita ($12) Made 2 margherita pizza(s) Ate 1 pizza(s) تسمح لنا تعليمات print برؤية أن البرنامج يعمل، ولكننا نستطيع أن نستخدم وحدة التسجيل لذات الغرض بدلا من ذلك. لنقم بإزالة تعليمات print من الشيفرة البرمجية، ونستورد الوحدة باستخدام الأمر import logging: import logging class Pizza(): def __init__(self, name, value): self.name = name self.value = value ... المستوى التلقائي للتسجيل في وحدة التسجيل هو مستوى التحذير (WARNING)، وهو مستوى فوق مستوى التنقيح (DEBUG). بما أننا سنستخدم الوحدة بغرض التنقيح في هذا المثال، سنحتاج الى تعديل إعدادات التسجيل لتصبح بمستوى التنقيح logging.DEBUG بحيث تعود معلومات التنقيح لنا من خلال لوحة التحكم. ونقوم بإعداد ذلك بإضافة ما يلي بعد تعليماتة الاستيراد: import logging logging.basicConfig(level=logging.DEBUG) class Pizza(): ... هذا المستوى المتمثل بlogging.DEBUG يشير لقيد رقمي قيمته 10. سنستبدل الآن جميع تعليمات print بتعليمات logging.debug()، (logging.DEBUG ثابت بينما logging.debug() دالة). نستطيع أن نمرر لهذه الدالة نفس المدخلات النصية لتعليماتة print كما هو موجود بالأسفل: import logging logging.basicConfig(level=logging.DEBUG) class Pizza(): def __init__(self, name, price): self.name = name self.price = price logging.debug("Pizza created: {} (${})".format(self.name, self.price)) def make(self, quantity=1): logging.debug("Made {} {} pizza(s)".format(quantity, self.name)) def eat(self, quantity=1): logging.debug("Ate {} pizza(s)".format(quantity, self.name)) pizza_01 = Pizza("artichoke", 15) pizza_01.make() pizza_01.eat() pizza_02 = Pizza("margherita", 12) pizza_02.make(2) pizza_02.eat() لهذا الحد، نستطيع تشغيل البرنامج عبر تنفيذ الأمر python pizza.py وسنحصل على المخرج التالي: Output DEBUG:root:Pizza created: artichoke ($15) DEBUG:root:Made 1 artichoke pizza(s) DEBUG:root:Ate 1 pizza(s) DEBUG:root:Pizza created: margherita ($12) DEBUG:root:Made 2 margherita pizza(s) DEBUG:root:Ate 1 pizza(s) لاحظ أن مستوى التسجيل في المخرج السابق هو DEBUG بالإضافة لكلمة root والتي تشير لمستوى المُسجل (logger) الذي يتم استخدامه. يعني ما سبق أن وحدة التسجيل logging من الممكن أن يتم استخدامها لإعداد أكثر من مُسجل بأسماء مختلفة. فمثلا، نستطيع إنشاء مسجلين باسمين مختلفين ومخرجات مختلفة كما هو موضح بالأسفل: logger1 = logging.getLogger("module_1") logger2 = logging.getLogger("module_2") logger1.debug("Module 1 debugger") logger2.debug("Module 2 debugger") Output DEBUG:module_1:Module 1 debugger DEBUG:module_2:Module 2 debugger بعد أن أصبحت لدينا المعرفة اللازمة لكيفية استخدام الوحدة logging لطباعة الرسائل على وحدة التحكم، دعونا نكمل شرح الوحدة ونتعرف على كيفية استخدام الوحدة في طباعة الرسائل إلى ملف خارجي. التسجيل في ملف الغرض الأساسي للتسجيل هو حفظ البيانات في ملف وليس إظهار معلومات التسجيل على وحدة التحكم. يتيح لك التسجيل في ملف حفظ بيانات التسجيل مع مرور الوقت واستخدامها في عملية التحليل والمتابعة ولتحديد ما تحتاجه من تغيير على الشيفرة البرمجية. لجعل عملية التسجيل تحفظ التسجيلات في ملف، علينا أن نعدّل logging.basicConfig() بحيث تحتوي على معطى لاسم الملف (filename)، وليكن مثلا test.log: import logging logging.basicConfig(filename="test.log", level=logging.DEBUG) class Pizza(): def __init__(self, name, price): self.name = name self.price = price logging.debug("Pizza created: {} (${})".format(self.name, self.price)) def make(self, quantity=1): logging.debug("Made {} {} pizza(s)".format(quantity, self.name)) def eat(self, quantity=1): logging.debug("Ate {} pizza(s)".format(quantity, self.name)) pizza_01 = Pizza("artichoke", 15) pizza_01.make() pizza_01.eat() pizza_02 = Pizza("margherita", 12) pizza_02.make(2) pizza_02.eat() الشيفرة البرمجية هنا هي نفسها الموجودة سابقا عدا أننا أضفنا اسم الملف الذي سنقوم بحفظ التسجيلات فيه. بمجرد تشغيلنا للشيفرة السابقة، سنجد في نفس المسار الملف test.log. لنفتحه باستخدام محرر النصوص nano (أو أي محرر نصوص من اختيارك): $ nano test.log وسيكون محتويات الملف كالتالي: DEBUG:root:Pizza created: artichoke ($15) DEBUG:root:Made 1 artichoke pizza(s) DEBUG:root:Ate 1 pizza(s) DEBUG:root:Pizza created: margherita ($12) DEBUG:root:Made 2 margherita pizza(s) DEBUG:root:Ate 1 pizza(s) المخرج السابق هو نفسه الذي حصلنا عليه في القسم السابق من المقال، غير أنه الآن في ملف باسم test.log وليس على الطرفية. لنغلق المحرر، ونجر بعض التعديلات التالية على المتغيرين pizza_01 و pizza_02: ... # Modify the parameters of the pizza_01 object pizza_01 = Pizza("Sicilian", 18) pizza_01.make(5) pizza_01.eat(4) # Modify the parameters of the pizza_02 object pizza_02 = Pizza("quattro formaggi", 16) pizza_02.make(2) pizza_02.eat(2) عند تنفيذ الشيفرة بعد حفظ التعديلات، ستُضاف التسجيلات الجديدة للملف وسيكون محتواه كالتالي: DEBUG:root:Pizza created: artichoke ($15) DEBUG:root:Made 1 artichoke pizza(s) DEBUG:root:Ate 1 pizza(s) DEBUG:root:Pizza created: margherita ($12) DEBUG:root:Made 2 margherita pizza(s) DEBUG:root:Ate 1 pizza(s) DEBUG:root:Pizza created: Sicilian ($18) DEBUG:root:Made 5 Sicilian pizza(s) DEBUG:root:Ate 4 pizza(s) DEBUG:root:Pizza created: quattro formaggi ($16) DEBUG:root:Made 2 quattro formaggi pizza(s) DEBUG:root:Ate 2 pizza(s) تُعد البيانات الموجودة في الملف مفيدة، ولكننا نستطيع جعلها أكثر إعلاماً بإضافة بعض الإعدادات. بشكل أساسي، فإننا نريد أن نجعل السجلات مفصلة أكثر بإضافة الوقت الذي أنشئ السجل فيه. نستطيع إضافة المعطى المسمى format ونضيف له النص %(asctime)s الذي يشير للوقت، كذلك، للإبقاء على ظهور مستوى التسجيل في السجلات، لابد أن نضيف النص %(levelname)s بالإضافة للسجل نفسه %(message)s. لابد من الفصل بين كل خيار في المعطى format بالعلامة : كما هو موضح بالأسفل: import logging logging.basicConfig( filename="test.log", level=logging.DEBUG, format="%(asctime)s:%(levelname)s:%(message)s" ) ....... عندما ننفذ الشيفرة السابقة، سنحصل على تسجيلات جديدة في ملف test.log تتضمن الوقت الذي أنشئ فيه التسجيل بالإضافة لمستوى التسجيل ورسالة التسجيل: Output DEBUG:root:Pizza created: Sicilian ($18) DEBUG:root:Made 5 Sicilian pizza(s) DEBUG:root:Ate 4 pizza(s) DEBUG:root:Pizza created: quattro formaggi ($16) DEBUG:root:Made 2 quattro formaggi pizza(s) DEBUG:root:Ate 2 pizza(s) 2017-05-01 16:28:54,593:DEBUG:Pizza created: Sicilian ($18) 2017-05-01 16:28:54,593:DEBUG:Made 5 Sicilian pizza(s) 2017-05-01 16:28:54,593:DEBUG:Ate 4 pizza(s) 2017-05-01 16:28:54,593:DEBUG:Pizza created: quattro formaggi ($16) 2017-05-01 16:28:54,593:DEBUG:Made 2 quattro formaggi pizza(s) 2017-05-01 16:28:54,593:DEBUG:Ate 2 pizza(s) تبعا لاحتياجاتك، من الممكن أن تضيف إعدادات أخرى للمسجل بحيث تجعل التسجيلات التي يتم حفظها في الملف مرتبطة بك نوعا ما. التنقيح بواسطة التسجيل في ملفات خارجية يتيح لك فهما شاملا لبرنامج بايثون مع مرور الوقت، معطيا الفرصة لحل المشاكل التي تظهر وتغيير ما تحتاج تغييره في الشيفرة البرمجية استنادا لما لديك من بيانات تسجيلات تاريخية وأحداث وحركات تمت خلال عمل البرنامج. جدول بمستويات التسجيل تستطيع نسب مستوى الأهمية للحدث الذي يتم تسجيله بواسطة المُسجل وذلك بإضافة مستوى الخطورة (Severity Level). مستويات الخطورة موضحة في الجدول الذي بالأسفل. تتمثل مستويات التسجيل تقنيا بأرقام (ثوابت)، بفرق قيمة بين كل مستوى ب 10، تبدأ من المستوىNOTEST ذي القيمة 0. تستطيع أن تُعرف مستويات خاصة بك مرتبطة بالمستويات المعرفة مسبقا. إذا عَرَّفْتَ مستوى بنفس القيمة الرقمية، فإنك تستبدل اسم المستوى المرتبط بتلك القيمة. المستوى القيمة الرقمية الدالة الاستخدام CRITICAL 50 ()logging.critical اظهار الأخطاء الخطيرة، البرنامج قد لا يستمر بالعمل ERROR 40 ()logging.error إظهار مشكلة خطيرة WARNING 30 ()logging.warning الإشارة لحدث غير متوقع حصل أو قد يصحل INFO 20 ()logging.info الإشارة أن الحدث حصل كما هو متوقع DEBUG 10 ()logging.debug فحص المشاكل، وإظهار معلومات تفصيلية خاتمة الوحدة logging هي وحدة ضمن التوزيعة المعيارية لبايثون، وتقدم حلا لمتابعة الأحداث التي تحدث خلال عمل البرمجية مع إمكانية تسجيل هذه الأحداث في ملفات خارجية أو إظهارها على الطرفية. وهذا يتيح لك فهما شاملا لبرنامج بايثون مع مرور الوقت. ترجمة – بتصرّف – للمقال How To Use Logging in Python 3 لصاحبته Lisa Tagliaferri.
  9. نعني بالتنقيح Debugging في مجال تطوير البرمجيات، عملية إيجاد وحل المشاكل التي تمنع البرمجية من العمل على نحو سليم. يُقدم مُنقح بايثون بيئة تنقيح لبرامج بايثون، ويدعم إعداد نقاط الفصل (Breakpoints)، التدرج خلال الشيفرة البرمجية (Stepping) سطرًا بسطر والعديد من المزايا. التفاعل مع مُنقح بايثون يأتي مُنقح بايثون مرفقًا مع توزيعة بايثون المعيارية على هيئة وحدة بايثون باسم pdb، ومن الممكن أن يُستخدم في الشيفرة البرمجية كعنصر من الصنف Pdb، ولمزيد من المعلومات تستطيع قراءة التوثيق الرسمي للوحدة pdb. سوف نبدأ العمل باستخدام برنامج صغير يحتوي على متغيرين ودالة تحتوي على حلقة تكرار بالإضافة لسطر استدعاء الدالة من خلال التركيبة if __name__ == '__main__':. num_list = [500, 600, 700] alpha_list = ['x', 'y', 'z'] def nested_loop(): for number in num_list: print(number) for letter in alpha_list: print(letter) if __name__ == '__main__': nested_loop() نستطيع الآن تشغيل البرنامج السابق من خلال مُنقح بايثون في الطرفية باستخدام الأمر التالي: python -m pdb looping.py يستورد الخيار –m في الأمر أي وحدة بايثون وتشغيلها كبرنامج. في هذه الحالة نقوم باستيراد الوحدة pdb وتمريرها كما هو مكتوب في الأمر. عند تنفيذ الأمر السابق سوف تحصل على المُخرج التالي: > /Users/sammy/looping.py(1)<module>() -> num_list = [500, 600, 700] (Pdb) يحتوي السطر الأول في المُخرج على اسم الملف الحالي الذي نقوم بتنقيحه – بالمسار الكامل -، ومن ثم يوجد رقم سطر الشيفرة التي يقف عليها مؤشر المُنقح (في هذه الحالة 1، ولكن في حالة وجود تعليق أو سطر غير تنفيذي سيكون الرقم أكبر من ذلك). السطر الثاني عبارة عن سطر الشيفرة الذي يتم تنفيذه. يُقدم مُنقح بايثون وحدة تحكم (Console) تفاعلية لإجراء عملية التنقيح وتستطيع استخدام أمر المساعدةhelp للتعرف على الأوامر المتاحة في المُنقح، وأيضا من الممكن أن تستخدم صيغة المساعدة مع الأمرhelp command للتعرف أكثر على تفاصيل أمر معين. سيعيد المُنقح عمله مرة أخرى تلقائيا عندما يصل لنهاية البرنامج. إذا أردت الخروج من وحدة تحكم المُنقح، أدخل الأمرquit أوexit. إذا أردت إعادة تشغيل عملية التنقيح مرة أخرى وفي أي مكان من البرنامج، أدخل الأمرrun. استخدام المُنقح للتدرج خلال البرنامج عند العمل على تنقيح البرامج باستخدام مُنقح بايثون، فإنك غالبا ستستخدم أوامر step، list وnext للمرور على الشيفرة البرمجية. خلال هذا الجزء من المقال سنتناول هذه الأوامر. من خلال نافذة الأوامر، نستطيع إدخال الأمرlist للحصول على السياق المحيط للسطر الحالي. فمثلا، من السطر الأول في البرنامج looping.py الذي عرضناه في الأعلى — num_list = [500, 600, 700] — سيكون تنفيذ الأمرlist معه بالشكل التالي: (Pdb) list 1 -> num_list = [500, 600, 700] 2 alpha_list = ['x', 'y', 'z'] 3 4 5 def nested_loop(): 6 for number in num_list: 7 print(number) 8 for letter in alpha_list: 9 print(letter) 10 11 if __name__ == '__main__': (Pdb) السطر الحالي يشار إليه بالعلامة -> في بدايته وهو في حالتنا هذه السطر الأول من البرنامج. بحكم أن البرنامج الذي نستخدمه هنا صغير نسبيا، فإننا تقريبا نحصل على كافة الأسطر في البرنامج عند استخدامالأمر list. بدون تزويد معطيات مع الأمر list، نحصل من استخدام الأمر على 11 سطراً من الشيفرة البرمجية محيطة بالسطر الحالي في المُنقح، ولكننا نستطيع تحديد الأسطر التي نريد عرضها بالشكل التالي: (Pdb) list 3, 7 3 4 5 def nested_loop(): 6 for number in num_list: 7 print(number) (Pdb) في الأمر السابق قمنا بعرض الأسطر 3-7 باستخدام الأمر list 3,7. للمرور خلال البرنامج سطرا بسطر، نستخدم الأمرstep أو next. (Pdb) step > /Users/sammy/looping.py(2)<module>() -> alpha_list = ['x', 'y', 'z'] (Pdb) (Pdb) next > /Users/sammy/looping.py(2)<module>() -> alpha_list = ['x', 'y', 'z'] (Pdb) الفرق بين الأمرstep والأمرnext أن الأمرstep سوف يتوقف مع استدعاء دالة، بينما الأمر next يُنفذ الدالة عند استدعائها. يتجلى الفرق بين الأمرين عند التعامل مع الدوال. في مثالنا المستخدم، سيقوم الأمرstep بالتعداد خلال حلقات التكرار بمجرد الدخول في استدعاء الدالة nested_loop() بحيث يظهر تماما ما تقوم به حلقة التكرار من طباعة رقم ومن ثم الدخول في حلقة تكرار طباعة الحروف المقترنة بالرقم ومن ثم العودة لطباعة رقم جديد وهكذا. (Pdb) step > /Users/sammy/looping.py(5)<module>() -> def nested_loop(): (Pdb) step > /Users/sammy/looping.py(11)<module>() -> if __name__ == '__main__': (Pdb) step > /Users/sammy/looping.py(12)<module>() -> nested_loop() (Pdb) step --Call-- > /Users/sammy/looping.py(5)nested_loop() -> def nested_loop(): (Pdb) step > /Users/sammy/looping.py(6)nested_loop() -> for number in num_list: (Pdb) step > /Users/sammy/looping.py(7)nested_loop() -> print(number) (Pdb) step 500 > /Users/sammy/looping.py(8)nested_loop() -> for letter in alpha_list: (Pdb) step > /Users/sammy/looping.py(9)nested_loop() -> print(letter) (Pdb) step x > /Users/sammy/looping.py(8)nested_loop() -> for letter in alpha_list: (Pdb) step > /Users/sammy/looping.py(9)nested_loop() -> print(letter) (Pdb) step y > /Users/sammy/looping.py(8)nested_loop() -> for letter in alpha_list: (Pdb) الأمرnext، بدلا من ذلك، سيستدعي الدالة دون الدخول في خطوات تنفيذها خطوة-بخطوة. لنخرج من المُنقح ونعد تشغيله مرة أخرى: python -m pdb looping.py الآن لنستخدم الأمرnext: (Pdb) next > /Users/sammy/looping.py(5)<module>() -> def nested_loop(): (Pdb) next > /Users/sammy/looping.py(11)<module>() -> if __name__ == '__main__': (Pdb) next > /Users/sammy/looping.py(12)<module>() -> nested_loop() (Pdb) next 500 x y z 600 x y z 700 x y z --Return-- > /Users/sammy/looping.py(12)<module>()->None -> nested_loop() (Pdb) أثناء عملك في تنقيح الشيفرة البرمجية الخاصة بك، قد تريد فحص قيمة متغير، وهو ما تستطيع تنفيذه باستخدام الأمرpp والذي يقوم بطباعة القيمة باستخدام وحدة pprint: (Pdb) pp num_list [500, 600, 700] (Pdb) أغلب الأوامر في مُنقح pdb لديها اختصارات، فمثلا، الأمرstep له اختصار هوs ولأمرnext يوجدn. يسرد أمر المساعدة help كافة الاختصارات لكل الأوامر. بالإضافة لذلك، تستطيع استدعاء الأمر الأخير بالضغط على زر الإدخال (Enter) في لوحة المفاتيح. نقاط الفصل من المؤكد أنك ستعمل على برامج أكبر من المثال المستخدم هنا، لذلك، غالبا ستحتاج أن تتوقف عند وظيفة معينة أو سطر محدد أثناء تنقيحك للشيفرة البرمجية، وذلك بدلا من المرور على كافة أسطر البرنامج. ستتمكن باستخدام الأمرbreak لإعداد نقاط الفصل من تشغيل البرنامج حتى نقطة معينة مسبقا فيه تـسمى نقطة فصل Break point. عندما تضيف نقطة فصل، سيعطيها المُنقح رقما خاصا بها. الأرقام المُعطاة لنقاط الفصل تكون أرقاماً متعاقبة وتبدأ من 1، وتستطيع الإشارة لنقاط الفصل باستخدام أرقامها أثناء عملك في المُنقح. تُضاف نقاط الفصل في أسطر معينة باستخدام الصيغة التالية <program_file>:<line_number> كما هو موضح بالأسفل: (Pdb) break looping.py:5 Breakpoint 1 at /Users/sammy/looping.py:5 (Pdb) اكتب الأمرclear ومن ثم y لإزالة نقاط الفصل الحالية. تستطيع بعد ذلك إضافة نقطة فصل في مكان تعريف الدالة: (Pdb) break looping.nested_loop Breakpoint 1 at /Users/sammy/looping.py:5 (Pdb) تستطيع كذلك إضافة شرط لنقطة الفصل: (Pdb) break looping.py:7, number > 500 Breakpoint 1 at /Users/sammy/looping.py:7 (Pdb) لو أدخلنا الآن الأمرcontinue، فإن البرنامج سيتوقف عندما تكون قيمة المتغيرnumber أكبر من 500. (Pdb) continue 500 x y z > /Users/sammy/looping.py(7)nested_loop() -> print(number) (Pdb) للحصول على قائمة بنقاط الفصل المُعدة حاليا للعمل، استخدم الأمرbreak فقط (دون معطيات) وسوف تحصل على معلومات نقاط الفصل التي قمت بإعدادها. (Pdb) break Num Type Disp Enb Where 1 breakpoint keep yes at /Users/sammy/looping.py:7 stop only if number > 500 breakpoint already hit 2 times (Pdb) نستطيع تعطيل نقطة فصل من البرنامج باستخدام الأمرdisable ومن ثم ندخل رقم نقطة الفصل. في هذه الجلسة (المثال المشروح) قمنا بإضافة نقطة فصل أخرى ومن ثم عطلنا النقطة الأولى: Pdb) break looping.py:11 Breakpoint 2 at /Users/sammy/looping.py:11 (Pdb) disable 1 Disabled breakpoint 1 at /Users/sammy/looping.py:7 (Pdb) break Num Type Disp Enb Where 1 breakpoint keep no at /Users/sammy/looping.py:7 stop only if number > 500 breakpoint already hit 2 times 2 breakpoint keep yes at /Users/sammy/looping.py:11 (Pdb) لتفعيل نقطة الفصل نستخدم الأمرenable، ولإزالة نقطة الفصل نستخدم الأمرclear: (Pdb) enable 1 Enabled breakpoint 1 at /Users/sammy/looping.py:7 (Pdb) clear 2 Deleted breakpoint 2 at /Users/sammy/looping.py:11 (Pdb) تقدّم لك نقاط الفصل في pdb قدرة كبيرة في التحكم، فمثلا، من الإضافات أنك تستطيع تجاهل نقطة الفصل خلال الدورة الحالية من البرنامج باستخدام الأمرignore أو تنفيذ حدث معين عند الوصول لنقطة فصل معينة باستخدام الأمرcommand. تستطيع كذلك إضافة نقاط فصل مؤقتة بواسطة الأمرtbreak، بحيث يقوم المُنقح بحذفها تلقائيا عند الوصول إليها وتنفيذها للمرة الأولى (لإضافة نقطة فصل مؤقتة في السطر رقم 3، ندخل الأمرtbreak 3 ). تضمين المُنقح في الشيفرة البرمجية تستطيع تشغيل جلسة تنقيح في الشيفرة البرمجية مباشرة وذلك باستيراد الوحدة pdb إضافة الدالة pdb.set_trace قبل السطر الذي تريد أن تبدأ الجلسة من عنده. في مثالنا المستخدم خلال هذا المقال، سوف نقوم باستيراد الوحدة pdb واضافة الدالة المذكورة قبل البدء بحلقة التكرار الداخلية في الدالة nested_loop: # Import pdb module import pdb num_list = [500, 600, 700] alpha_list = ['x', 'y', 'z'] def nested_loop(): for number in num_list: print(number) # Trigger debugger at this line pdb.set_trace() for letter in alpha_list: print(letter) if __name__ == '__main__': nested_loop() بإضافة المُنقح في الشيفرة البرمجية الخاصة بك، فأنت لست بحاجة لتشغيل الشيفرة بطريقة معينة أو أن تتذكر إعدادات نقاط الفصل، وستتمكن من تشغيل البرنامج بطريقة عادية وتفعيل المُنقح من خلال التنفيذ. تعديل مسار عمل البرنامج يتيح لك مُنقح بايثون تغيير مسار التنفيذ (Execution Flow) خلال زمن التنفيذ باستخدام الأمرjump، وهذا يعني أنك تستطيع أن تقفز إلى الأمام خلال البرنامج لمنع بعض الشيفرة البرمجية من التنفيذ أو العودة لتنفيذ جزء من الشيفرة مرة أخرى. سوف نعمل على شرح هذه النقطة باستخدام برنامج صغير يقوم بإنشاء متغير من نوع قائمة (List) من الأحرف النصية موجودة ضمن المتغير النصي sammy = "sammy" : def print_sammy(): sammy_list = [] sammy = "sammy" for letter in sammy: sammy_list.append(letter) print(sammy_list) if __name__ == "__main__": print_sammy() إذا قمنا بتشغيل البرنامج بطريقة عادية باستخدام python letters_list.py فإننا سوف نحصل على النتيجة التالية: Output ['s'] ['s', 'a'] ['s', 'a', 'm'] ['s', 'a', 'm', 'm'] ['s', 'a', 'm', 'm', 'y'] لنستعرض كيفية استخدام مُنقح بايثون في تغيير مسار عمل البرنامج السابق بحيث نقوم بالقفز قُدما خلال الشيفرة البرمجية للبرنامج أثناء التشغيل بعد الدورة الأولى من حلقة التكرار: python -m pdb letter_list.py > /Users/sammy/letter_list.py(1)<module>() -> def print_sammy(): (Pdb) list 1 -> def print_sammy(): 2 sammy_list = [] 3 sammy = "sammy" 4 for letter in sammy: 5 sammy_list.append(letter) 6 print(sammy_list) 7 8 if __name__ == "__main__": 9 print_sammy() 10 11 (Pdb) break 5 Breakpoint 1 at /Users/sammy/letter_list.py:5 (Pdb) continue > /Users/sammy/letter_list.py(5)print_sammy() -> sammy_list.append(letter) (Pdb) pp letter 's' (Pdb) continue ['s'] > /Users/sammy/letter_list.py(5)print_sammy() -> sammy_list.append(letter) (Pdb) jump 6 > /Users/sammy/letter_list.py(6)print_sammy() -> print(sammy_list) (Pdb) pp letter 'a' (Pdb) disable 1 Disabled breakpoint 1 at /Users/sammy/letter_list.py:5 (Pdb) continue ['s'] ['s', 'm'] ['s', 'm', 'm'] ['s', 'm', 'm', 'y'] قمنا خلال جلسة التنقيح السابقة بوضع نقطة فصل عند السطر رقم 5 لمنع الشيفرة البرمجية من الاستمرار، ومن ثم قمنا بطباعة بعض الأحرف والاستمرار من خلال الأمرcontinue لإظهار ماذا يحدث. ثم استخدمنا الأمرjump لتجاهل السطر 6. عند هذه النقطة، المتغيرletter يساوي القيمة a ولكننا تجاهلنا السطر الذي يقوم بإضافة هذه القيمة للقائمة. بعد ذلك قمنا بتعطيل نقطة الفصل وتركنا البرنامج يستمر في التنفيذ. في النهاية، فإن الحرفa لم يُضف للقائمة sammy_list. لنعد تشغيل جلسة التنقيح، ونستخدم المُنقح في العودة للخلف خلال التشغيل بهدف إعادة تنفيذ جملة الإضافة للقائمة sammy_list والتي تم تنفيذها خلال التكرار الأول من حلقة التكرار: > /Users/sammy/letter_list.py(1)<module>() -> def print_sammy(): (Pdb) list 1 -> def print_sammy(): 2 sammy_list = [] 3 sammy = "sammy" 4 for letter in sammy: 5 sammy_list.append(letter) 6 print(sammy_list) 7 8 if __name__ == "__main__": 9 print_sammy() 10 11 (Pdb) break 6 Breakpoint 1 at /Users/sammy/letter_list.py:6 (Pdb) continue > /Users/sammy/letter_list.py(6)print_sammy() -> print(sammy_list) (Pdb) pp letter 's' (Pdb) jump 5 > /Users/sammy/letter_list.py(5)print_sammy() -> sammy_list.append(letter) (Pdb) continue > /Users/sammy/letter_list.py(6)print_sammy() -> print(sammy_list) (Pdb) pp letter 's' (Pdb) disable 1 Disabled breakpoint 1 at /Users/sammy/letter_list.py:6 (Pdb) continue ['s', 's'] ['s', 's', 'a'] ['s', 's', 'a', 'm'] ['s', 's', 'a', 'm', 'm'] ['s', 's', 'a', 'm', 'm', 'y'] خلال جلسة التنقيح السابقة، قمنا بإضافة نقطة فصل عند السطر 6، وقمنا بالعودة للخلف للسطر 5 بعد الاستمرار.طبعنا ما تحتويه القائمة خلال التنفيذ وذلك لكي نظهر أن الحرف s قد أٌضيف مرتين. وبعد ذلك قمنا بتعطيل نقطة الفصل وجعلنا البرنامج يستمر في التنفيذ. النتيجة الظاهرة توضح أن الحرف s أضيف مرتين في بداية القائمة sammy_list. يمنع المنقّح بعض استخدامات الأمرjump ، وخاصة عندما يُعدَّل مسار البرنامج للأمام أو الخلفمن خلال جمل لم تُعرَّف. فمثلا، لا تستطيع القفز إلى دوال قبل أن تعرّف المعطيات الخاصة بها، وكذلك لا تستطيع الدخول في وسط جملة try:except أو الخروج من كتلة الشيفرة البرمجية لـ finally. ملخص ما نستطيع ذكره عن الأمرjump، أنك من خلال استخدامه في مُنقح بايثون تستطيع تغيير مسار التنفيذ خلال التنقيح لمعرفة هل هذا التغيير مفيد ضمن ظروف ومعطيات محددة أم لا، وكذلك يساعدك في فهم أصل أو مكان الخلل والمشاكل التي تظهر خلال تنفيذ البرنامج. جدول أوامر المنقح pdb هنا نلخص لكم أوامر المُنقح pdb مع شرح بسيط عما يقدمه كل أمر لكي يساعدك على التذكر خلال تنقيح البرامج على بايثون: الأمر الاختصار العمل help h يقدم قائمة بالأوامر المتاحة أو شرحا لأمر معين jump j تحديد السطر القادم للتنفيذ list l طباعة السياق (ما حول السطر الحالي) للتعرف أكثر على محيط التنفيذ الحالي next n الاستمرار في التنفيذ حتى السطر القادم الذي تصل فيه الدالة الحالية أو تعود step s تنفيذ السطر الحالي والتوقف عند أول فرصة متاحة pp pp طباعة متغير أو تعبير معين quit أو exit q الخروج من البرنامج return r الاستمرار في التنفيذ حتى تعود الدالة الحالية تستطيع أن تقرأ أكثر عن الأوامر وكيفية العمل مع المنقح من خلال الاطلاع على التوثيق الرسمي في بايثون. خاتمة يعد تنقيح البرمجيات خطوة مهمة في أي مشروع لبناء وتطوير برمجية. يقدم مُنقح بايثون بيئة تفاعلية تستطيع الاستفادة منها في تنقيح أي برنامج بايثون. الأوامر المتاحة خلال المنقح تتيح لك وقف البرنامج الخاص بك، والاطلاع على قيم متغيراته خلال التنفيذ وتعديلها إذا رغبت، وكذلك التحكم في مسارات التنفيذ وغيره من الخصائص التي تساعدك في فهم ما يقوم به برنامجك بشكل كامل ولكي تضع يدك على المشاكل والقضايا التي تظهر خلال التنفيذ. ترجمة – بتصرّف – للمقال How To Use the Python Debugger لصاحبته Lisa Tagliaferri.
  10. هذا يعود لطريقتك في التعلم. من الممكن ان تعتمد على فيديوهات تشرح استخدام المكتبة ولكن لن تحصل على كل شيئ. إذا أردت معرفة تفاصيل المكتبة يُحبذ أن تقوم بمراجعة التوثيق الخاص بها. تحياتي العطرة.
  11. تعرفنا في الدرس السابق على كيفية دمج النصوص واستيفائها وكذلك كيفية حفظها في متغيرات، وسنتابع في هذ الدرس التعرّف على القيم الحرفية للنصوص ويفية تكرارها، بالإضافة إلى كيفية إضافة أسطر جديدة ضمن النصوص الطويلة. القيم الحرفية للنصوص لاحظ أن النصوص التي كتبناها في الشيفرة البرمجية أحيطت بعلامتي تنصيص، وأن النصوص التي طُبعت كانت دون علامات التنصيص. لابد من التفرقة بين الأمرين، فالقيم الحرفية للنص هي التي تُكتب في الشيفرة البرمجية، بينما القيم النصية هي التي نراها في المُخرج ولا تحتوي على علامات التنصيص. القيمة الحرفية تكون بالشكل التالي: "Sammy the Shark" والقيمة النصية: Sammy the Shark. في أغلب الحالات لن تحتاج للقلق حول هذا الأمر إلا إذا أردت طباعة حروف خاصة مثل علامة التنصيص في النصوص التي تريدها. تهريب علامات التنصيص في النصوص تُستخدم علامات التنصيص للإشارة الى النصوص في الشيفرة البرمجية، وعليك أن تقوم ببعض العمل الإضافي إذا أردت تضمين علامات التنصيص في النص الذي تريده. لو حاولنا إضافة علامة التنصيص المنفردة في نص بالشكل التالي: 'This isn't what I wanted.' ستقوم علامة التنصيص في كلمة isn't بإنهاء النص كما تلاحظه من لون النص، ونتيجة لذلك، سيقوم مُفسر الروبي بقراءة بقية النص كشيفرة برمجية وينتج عن ذلك خطأ. وستقع في نفس المشكلة إذا قمت باستخدام علامة التنصيص المزدوجة. يوجد لدينا بعض الخيارات للتغلب على هذه المشكلة. الخيار الأول هو أنك تستطيع أن تستخدم علامة التنصيص المعاكسة للتي تم استخدامها في تعريف النص، فلو اردت تضمين علامة التنصيص المزدوجة في نصك عليك استخدم علامة التنصيص المنفردة في تعريف النص والعكس صحيح. الخيار الثاني هو تهريب علامة التنصيص (Escaping Quotes)، أو استخدام طريقة بناء مختلفة عن لغة الروبي في بناء النصوص. سنتكلم عن كل طريقة فيما يلي. الخيار الأول: استخدام علامة التنصيص البديلة الخيار الأسهل لتخطي هذه المشاكل هو إحاطة النص بعلامتي تنصيص منفردتين عندما تريد إضافة علامة تنصيص مزدوجة في نصك، وإحاطة النص بعلامتي تنصيص مزدوجتين عندما تريد إضافة علامة تنصيص منفردة. فبدلًا من استخدام علامتي تنصيص منفردتين في احاطة النص التالي: 'This isn't what I wanted.' نستخدم علامتي تنصيص مزدوجتين كما يلي: "This isn't what I wanted." وبدلًا من استخدام علامتي تنصيص مزدوجتين في إحاطة النص التالي: "Sammy says, "Hello!"" نستخدم علامتي تنصيص منفردتين كما يلي: 'Sammy says, "Hello!"' استخدام علامة التنصيص البديلة يحل لنا أغلب الحالات وليس جميعها. فمثلا كلتا علامات التنصيص لن تنفع في طباعة النص التالي: "Sammy says, "I'm a happy shark!"" في النص السابق، علامة التنصيص المزدوجة قبل I’m هي التي تسبب المشكلة، حيث أنها تُنهي النص الأول قبلها، ومن ثم تقوم الروبي باستقبال نص جديد بعد علامة التنصيص المنفردة في I’m والتي لا يوجد لها علامة تنصيص منفردة أخرى لإغلاقها مما يسبب ظهور خطأ. استخدام علامة التنصيص المنفردة في احاطة النص يسبب نفس المشكلة: 'Sammy says, "I'm a happy shark!"' ولكن هذه المرة فإن علامة التنصيص المنفردة هي التي تُغلق النص. استخدام علامة التنصيص البديلة يجعل الشيفرة البرمجية لديك غير مستقرة ومُحيرة في فهم النص في بعض الحالات. الخيار الثاني لحل المشكلة هو استخدام طريقة تهريب الحروف. الخيار الثاني: تهريب الحروف في النصوص الخط المائل العكسي () الذي يُعرف عادةً برمز الهروب في النصوص، هذا الخط المائل يمنع مُفسر الروبي من تفسير الحرف الذي يليه في النصوص حرفيًا. سنحل مشكلة النص التالي الذي كنا نُحاول طباعته والمُحاط بعلامتي تنصيص مزدوجتين وبداخله علامة تنصيص من نفس النوع وعلامة تنصيص منفردة: "Sammy says, "I'm a happy shark!"" لننشئ ملف باسم quoting.rb ونُضيف إليه الشيفرة التالية: quoting.rb print "Sammy says, "I'm a happy shark!"" عند تشغيل البرنامج سنحصل على الخطأ التالي: Output quoting.rb:1: syntax error, unexpected tCONSTANT, expecting end-of-input print "Sammy says, "I'm a happy shark!"" لحل هذه المشكلة نستخدم الخط المائل العكسي. نقوم بتعديل الملف ليصبح كالتالي: quoting.rb print "Sammy says, \"I'm a happy shark!\"" وعند تشغيل البرنامج سنحصل على النتيجة التالية: Sammy says, "I'm a happy shark!" لاحظ أننا لسنا بحاجة لتهريب علامة التنصيص المنفردة في وسط النص لأنها لا تسبب تعارضًا مع علامة التنصيص التي تُحيط النص. نحتاج فقط لتهريب علامة التنصيص التي تُسبب عدم مقدرة الروبي على تفسير النص. نستطيع استخدام طريقة أخرى لحل المشكلة دون تهريب علامات التنصيص عبر استخدام الخيار الثالث التالي. الخيار الثالث: استخدام طريقة مختلفة في بناء النصوص حتى الان استخدمنا علامات التنصيص لتعريف النصوص. تستطيع من خلال الروبي تعريف النصوص باستخدام علامات تختلف عن علامات التنصيص، بحيث يتم تعريف مُحدد (Delimiter) أو حرف لإحاطة النصوص عند تعريفها، ويتم ذلك بعد علامة النسبة المئوية كالتالي: %$Sammy says, "I'm a happy shark!"$ لاحظ أننا استخدمنا علامة الدولار بعد علامة النسبة المئوية، وهذا يعني أن المُحدد هنا هو علامة الدولار، والنص الذي نريده يقع بين علامتي الدولار. يُكتب النص السابق باستخدام علامتي التنصيص المزدوجة كالتالي: "Sammy says, \"I'm a happy shark!\"" إذا أردنا استخدام نفس علامة المُحدد في النص، فعلينا في هذه الحالة تهريبه باستخدام الخط المائل العكسي. لتجنب ذلك، تستطيع استخدام الأقواس المُزهرة { } أو المُربعة [ ] كمحدد. الأقواس المُزهرة مُتعارف عليها أكثر: %{Sammy says, "I'm a happy shark!"} وجميعها يدعم استيفاء النصوص في حالة احتياجك لهذه الخاصية: droplets = 5 print %{Sammy says, "I just created #{droplets} droplets!"} سترى في العديد من برامج الروبي كل من %Q{ } و %q{ } لتعريف النصوص. العلامة%Q{ } تعمل تمامًا مثل علامة التنصيص المزدوجة، مما يعني أنك لست بحاجة لتهريب علامة التنصيص المزدوجة مع النصوص وستستطيع استخدام خاصية استيفاء النصوص أيضًا: droplets = 5 print %Q{Sammy says, "I just created #{droplets} droplets!"} العلامة %q{} تعمل تمامًا مثل علامة التنصيص المنفردة مع النصوص: %q{Sammy says, "I'm a happy shark!"} قد ترى %q و %Q مع أقواس مُربعة بدلا من أقواس مُزهرة. كما تلاحظ، يوجد العديد من الطرق لبناء النصوص في الروبي. إذا قمت باختيار إحدى الطرق في الشيفرة البرمجية فكن ثابتًا ومستقرًا عليها. ستجد أن %Q{ } و %{ } هما الأكثر استخداما. الان بعد أن تعلمنا كيفية التعامل مع الحروف والرموز الخاصة، سنتكلم عن النصوص الطويلة والأسطر الجديدة. النصوص الطويلة والأسطر الجديدة قد تحتاج في بعض الأحيان لإضافة سطر جديد. تستطيع استخدام \n أو \r لإضافة سطر جديد في النص الذي تريده: output = "This is\na string\nwith newlines" puts output ستكون نتيجة الشيفرة السابقة كالتالي: Output This is a string with newlines من ناحية عملية، الشيفرة السابقة ستجعلنا نطبع أسطر متعددة في المُخرج. على الرغم من ذلك، اذا كان السطر الذي نريد طباعته طويل نوعًا ما في الشيفرة البرمجية، سيكون من الصعب التعامل معه. يوجد بعض الحلول لذلك. الأول، تستطيع استخدام علامة الدمج لجعل النص في عدة أسطر في الشيفرة البرمجية: output = "This is a\n" + "longer string\n" + "with newlines." puts output يقوم هذا الأمر بدمج النصوص الثلاثة وطباعتها بنفس الشكل الذي تم سابقا. تستطيع أيضًا اضافة توقيفات الأسطر (Lines Breaks) بشكل مباشر في النص: output = "This is a longer string with newlines" puts output أو من خلال الطريقة التالية: output = %{This is a longer string with newlines} puts output في المثالين السابقين لم نحتاج لوضع رمز السطر الجديد (\n). وهذا يعني أن النص يتم طباعته كما يُكتب، وبذلك اذا احتوى النص على مسافات فارغة أو إزاحات فسيتم طباعتها كما هي، وسيكون ناتج المثال السابق كما يلي: Output This is a longer string with newlines لمنع ذلك وببساطة نقوم بإزالة الإزاحات من بداية كل سطر: output = %{This is a longer string with newlines } نستطيع أيضًا تعريف نصوص بأسطر متعددة باستخدام heredoc او here document وهو مصطلح يُستخدم لتعريف نص في البرامج، ويتم كتابته كالتالي: output = <<-END This is a longer string with newlines END <<-END و END تعني بداية ونهاية النص. Heredocs في الروبي تحتفظ بالمسافات الفارغة مما يعني أنه في حالة وجود إزاحات في النص المُعرف سيتم طباعته كما هو. أنظر للشيفرة التالية: output = <<-END This is a longer string with newlines END نسخة الروبي 2.3 وأعلى تقدم ما يسمى بـ “squiggly heredoc” والتي تٌزيل الإزاحات بشكل تلقائي من النص، ويتم ذلك باستبدال <<- بالعلامة <<~ : output = <<~END This is a longer string with newlines and the code is indented but the output is not. END والذي يعطينا النتيجة التالية: Output This is a longer string with newlines and the code is indented but the output is not. وهذا يُتيح لك أن تكون شيفرتك البرمجية مرتبة ومُزاحة بشكل جميل. خاصية Heredocs تدعم استيفاء النصوص. كما ترى، يوجد العديد من الطرق لمعالجة الأسطر الجديدة والنصوص المتعددة في الروبي، وسوف تقابل هذه الطرق كلما تعاملت مع مشاريع روبي أكثر حيث ستجد لكل مشروع طريقته الخاصة في الكتابة. في شيفرتك البرمجية، أختر الطريقة المناسبة لك وكن ثابتًا عليها. تكرار النصوص في بعض الأحيان قد تحتاج إلى تكرار نص معين أكثر من مرة في الروبي. تستطيع فعل ذلك باستخدام عملية التكرار التي يرمز لها بعلامة . كما في عملية الدمج +، فإن عملية التكرار لها استخدام اخر مع الأرقام حيث تقوم بتنفيذ عملية الضرب. عند استخدام العملية * مع رقم ونص فهذا يعني عملية تكرار للنص بعدد الرقم المستخدم. الشيفرة التالية تُستخدم لطباعة كلمة Sammy تسع مرات: print "Sammy" * 9 وسيكون الناتج بالشكل التالي: Output SammySammySammySammySammySammySammySammySammy هذا الأمر يتيح لك تنفيذ بعض الحركات الفنية باستخدام النصوص. لننشئ ملف باسم banner.rb ونضيف إليه الشيفرة التالية: puts "=" * 15 puts "| Hello World |" puts "=" * 15 هل تتخيل ماذا سيكون الناتج قبل تنفيذ الشيفرة؟ سيكون الناتج بالشكل التالي: Output =============== | Hello World | =============== هذا مثال صغير لكيفية استخدام الحاسوب لتكرار تنفيذ مهام معينة. خاتمة تعلمنا في هذين الدرسين كيفية التعامل مع النصوص في لغة البرمجة روبي. قمنا بإنشاء نصوص جديدة، دمجنا النصوص مع بعضها البعض، تعلمنا عدة طرق للتعامل مع الأسطر، التنصيص والفاصلة العلوية. بالإضافة لذلك، تعلمنا خاصية استيفاء النصوص التي تمكننا من استخدام النصوص والمتغيرات مع بعضها البعض وفي النهاية تعلمنا كيفية تكرار النصوص.
  12. تساعدك الواجهة البرمجية (API) الخاصة بموقع التواصل الاجتماعي تويتر في إدارة الحسابات الموجودة فيه، كما وتسمح لك بالتنقيب عما يحتويه من بيانات. هذا الأمر يفيدك – مثلاً - في عملية ترويج هوية المؤسسة أو المنظمة التي تعمل فيها، وكذلك يُعدّ ممتعًا ومسليًا للمستخدمين الأفراد وهواة البرمجة. في هذا الدرس سوف نشرح لكم الخطوات اللازمة لإنشاء تطبيق توتير، وبعد ذلك سنبني سكربت بايثون من خلال استخدام مكتبة Tweepy للاستفادة من التطبيق و نشر تغريدات من خلاله. المتطلبات قبل البدء، تأكد من وجود المتطلبات التالية: وجود حساب توتير مرتبط برقم هاتف محمول صحيح، وتستطيع إعداد ذلك بالذهاب إلى خيار الهاتف المحمول في قائمة الإعدادات والخصوصية. وجود بيئة بايثون مثبتة على جهاز العمل. الخطوة الأولى: إنشاء تطبيق تويتر لنبدأ في بناء تطبيق تويتر والحصول على مفاتيح ورموز الوصول الخاصة بالواجهة البرمجية للتطبيق. هذه الرموز هي التي تسمح لك بالاستيثاق من أي تطبيق تطوّره لكي يعمل مع تويتر. كما ذكرنا في المتطلبات، فأنت تحتاج لرقم هاتف محمول لكي تستطيع بناء التطبيق. افتح المتصفح وزر هذا الرابط وسجّل الدخول للصفحة باستخدام بيانات حسابك. اضغط بمجرد ولوجك على الزر المعنون ب (Create New App). سوف تُوجَّه إلى صفحة بناء التطبيق التالية: أدخل في هذه الصفحة الحقول المطلوبة كما في المثال التالي: الاسم: AcademyHsoubTest الوصف: Academy Hsoub Application الموقع: https://my.example.placeholder اقرأ اتفاقية مطور تويتر. إذا كنت موافقاً عليها، اضغطعلى خيار الموافقة واضغط على الزر المعنون ب Create your Twitter application والموجود في أسفل الصفحة، وسوف تتلقى صفحة تأكيد على ذلك. ستُوجَّه بعد أن نجاح إنشاء التطبيق إلى صفحة التطبيق التالية، والتي تُقدم لك بعض المعلومات العامة عن التطبيق. الخطوة الثانية: تعديل مستوى الإذن للتطبيق وتوليد رموز الوصول الخاصة به من صفحة التفاصيل (Details Page) السابقة، اذهب لخيار الأذونات (Permissions) وذلك للتأكد من أننا نمتلك مستوى الوصول المطلوب لتوليد مفاتيح التطبيق. يمتلك التطبيق تلقائيا أذونات القراءة والكتابة. إذا لم يكن كذلك، عدّل التطبيق وتأكد من أن خيار القراءة والكتابة هو المحدد من قائمة الأذونات. هذا الأمر يسمح للتطبيق بالنشر على حساب تويتر بالنيابة عنك. اذهب بعد التأكد من أذونات التطبيق التي تسمح له بالنشر إلى خيار المفاتيح ورموز الوصول (Keys and Access Tokens). ستُنقَل إلى صفحة تعرض لك مفتاح المستهلك (Consumer Key) والرمز السري للمستهلك (Consumer Secret)، وكذلك تُمكنك من توليد رمز الوصول ورمز الوصول السري. هذه الرموز والمفاتيح هي التي ستقوم بعملية الاستيثاق لتطبيقك مع تويتر. اضغط على الزر المعنون ب (Create my access token) لتوليد رمز الوصول ورمز الوصول السري. أصبح لديك الآن رمز الوصول ورمز الوصول السري. الخطوة الثالثة: تثبيت مكتبة Tweepy تستطيع أن تستخدم مجموعة متنوعة من لغات البرمجة للتخاطب مع الواجهة البرمجية لتويتر. سنختبر التطبيق الذي أنشأناه عن طريق تشغيل سكربت بايثون ينشُر نصًّا معيّنًا على حساب تويترللمستخدم. مكتبة Tweepy مفتوحة المصدر وسهلة الاستخدام وتسمح للمشاريع المكتوبة بلغة البايثون بالوصول للواجهة البرمجية لتويتر بكل سهولة. سنستخدم أداة pip لتثبيت مكتبة tweepy. أنشئ مجلدًا خاصًّا للمشروع باسم twitter. تأكد قبل تثبيت المكتبة أن أداة pip مُحدثة: >> pip install --upgrade pip بعد أن تُحدَّث الأداة بنجاح، نثبّت مكتبة Tweepy: >> pip install tweepy بعد تثبيت المكتبة تستطيع البدء بكتابة البرنامج. الخطوة الرابعة: بناء البرنامج الذي يتخاطب مع الواجهة البرمجية لتويتر أصبحنا، بعد أن بنينا التطبيق وولّدنا الرموز والمفاتيح اللازمة للوصول للتطبيق، على مشارف بناء البرنامج الذي سينشر بالنيابة عنك. أنشئ باستخدام محرر النصوص المفضل لديك ملف بايثون باسم helloworld.py في داخل المجلد twitter الذي أنشأناه في الخطوة السابقة. في بداية الملف، نحتاج أن نستورد المكتبة باستخدام جملة import: import tweepy سننشئ متغيرات لكل مفتاح ورمز ولّدناه. استبدل ما بين علامات التنصيص في المتغيرات التالية بالمفاتيح والرموز التي تم توليدها في تطبيقك الخاص. import tweepy consumer_key = 'your_consumer_key' consumer_secret = 'your_consumer_secret' access_token = 'your_access_token' access_token_secret = 'your_access_token_secret' ننشئ بعدها عنصرًا من الصنف OAuthHandler الموجود في مكتبة Tweepy وسنمرر لهذا العنصر المفاتيح والرموز الموجودة لدينا. يعمل هذا العنصر من خلال بروتوكول HTTP يعطي التصريح اللازم للأجهزة، الواجهات البرمجية، الخوادم والتطبيقات، ويعدّ هذا الصنف صنفا معياريا يقدّم ألية وصول آمن وذي تفويض كامل. نعدّ كذلك رموز الوصول وندمجها مع الواجهة البرمجية المنشأة من تعريف عنصر من نوع API. import tweepy consumer_key = 'your_consumer_key' consumer_secret = 'your_consumer_secret' access_token = 'your_access_token' access_token_secret = 'your_access_token_secret' auth = tweepy.OAuthHandler(consumer_key, consumer_secret) auth.set_access_token(access_token, access_token_secret) api = tweepy.API(auth) نحدّد في نهاية البرنامج النص الذي سننشره. عرف متغيرًا باسم tweet ومرر له النص الذي تريد نشره ومرّر هذا المتغير للدالة api.update_status. import tweepy # Create variables for each key, secret, token consumer_key = 'your_consumer_key' consumer_secret = 'your_consumer_secret' access_token = 'your_access_token' access_token_secret = 'your_access_token_secret' # Set up OAuth and integrate with API auth = tweepy.OAuthHandler(consumer_key, consumer_secret) auth.set_access_token(access_token, access_token_secret) api = tweepy.API(auth) # Write a tweet to push to our Twitter account tweet = 'أكاديمية حسوب، أهلا بالعالم' api.update_status(status=tweet) تستطيع الان حفظ الملف وتشغيله: >> python helloworld.py بعد انتهاء تنفيذ البرنامج، قم بمراجعة الحساب توتير الخاص بك. نشرت التغريدة على الخط الزمني لحسابك بنجاح وبذلك تكون قد هيّأت تطبيق تويتر واستخدمته من خلال مكتبة Tweepy! خاتمة باتباعك للشرح في هذا المقال، أنت أصبحت قادرا على تهيئة تطبيق تويتر وربطه بحسابك الخاص على الموقع. بمجرد بناء التطبيق وتوليد المفاتيح والرموز اللازمة، قمنا باستخدام التطبيق والرموز للاستيثاق من برنامج بايثون باستخدام المكتبة المفتوحة المصدر Tweepy. إذا لم تكن مبرمج بايثون، فإنه يوجد العديد من لغات البرمجة والمكتبات التي من الممكن استخدامها مع الواجهة البرمجية لتويتر. يحتوي الموقع الخاص بمطوري تويتر العديد من هذه المكتبات التي تدعم التخاطب مع الواجهة البرمجية لتويتر. ترجمة - بتصرّف - للمقال How To Create a Twitter App لصاحبته Lisa Tagliaferri. حقوق الصورة البارزة محفوظة لـ Freepik
  13. مقدمة النص عبارة عن سلسلة تحتوي على حرف واحد أو أكثر وقد تحتوي على حروف أبجدية، أرقام ورموز. النصوص في لغة البرمجة روبي عبارة عن كائنات (Objects)، وعلى عكس لغات البرمجة الأخرى، فإن النصوص قابلة للتغيير (Mutable) والتي تعني أننا نستطيع تعديل النصوص بدلًا من إنشاء نصوص جديدة. ستستخدم النصوص في أغلب البرامج التي تكتبها، وتتيح لك النصوص عرض ما تريده للمستخدمين والتواصل معهم عبر نقل المعلومات لهم عبر الشاشة. في الحقيقة، إن النص الذي تقوم بقراءته الأن يتكون من مجموعة من النصوص المعروضة على الشاشة من خلال المتصفح. النصوص أحد أهم الأساسيات في البرمجة. في هذا الدرس، سوف تتعلم كيفية التعامل مع النصوص بلغة الروبي. سوف تُنشئ النصوص، تعرضها على الشاشة، تحفظها في متغيرات، تدمج النصوص ببعضها وستتعلم في الدرس التالي كيفية التعامل مع الرموز الخاصة مثل السطر الجديد، الفواصل وعلامات التنصيص المزدوجة. إنشاء وطباعة النصوص تُحاط النصوص بعلامتي تنصيص مزدوجة ” أو علامتي تنصيص منفردة ’ في لغة الروبي، لذلك لإنشاء نص، لابد أن تحيط مجموعة الحروف بأحد علامتي التنصيص: 'This is a string in single quotes.' "This is a string in double quotes." تستطيع أن تختار أي نوع من علامات التنصيص، وفي أغلب الحالات لا يهم أيهما تختار ما دمت ثابتًا على أحدهما. على الرغم من ذلك، فإن استخدام علامتي التنصيص المزدوجة يتيح لك إجراء ما يسمى استيفاء النص (String Interpolation) والذي سوف تتعلمه في هذا المقال. لعرض نص في برنامجك، تستطيع استخدام الوظيفة print والتي تعرض النص كما تم كتابته: print "Let's print out this string." قم بإنشاء برنامج روبي وأعطه الإسم print.rb ومن خلال محرر النصوص الخاص بك قم باستخدام الوظيفة print ثلاث مرات لطباعة نص: print.rb print 'This is the first string.' print 'This is the second string.' print 'This is the third string.' قم بحفظ الملف وتشغيله باستخدام الأمر التالي: ruby print.rb وسوف تحصل على النتيجة التالية: Output This is the first string.This is the second string.This is the third string. في الناتج الذي سبق، بدلًا من طباعة النصوص الثلاثة كلٌ في سطره، تم طباعة جميع النصوص في سطر واحد. إذا أردت أن يُطبع كل نص في سطر، يجب عليك أن تُضيف هذا السطر بنفسك وستتعلم ذلك تحت عنوان النصوص الطويلة والأسطر الجديدة، أو أن تستخدم الوظيفة put بدلًا من الوظيفة print. قم بتعديل البرنامج ومن ثم احفظ التعديل وفقا لما يلي: print.rb puts 'This is the first string.' puts 'This is the second string.' puts 'This is the third string.' الان قم بتشغيل البرنامج مرة أخرى وستحصل على النتيجة التالية: Output This is the first string. This is the second string. This is the third string. الوظيفة puts تطبع النص الذي تحدده لها وتطبع أيضا سطر جديد مع نهاية النص المُدخل. حفظ النصوص في متغيرات المتغيرات عبارة عن مؤشر لمكان في ذاكرة الحاسوب وتُستخدم لحفظ البيانات والرجوع إليها لاحقا. لحفظ نص في متغير، نُعرف اسم المتغير ونعطيه قيمة النص الذي نريده: my_string = 'This is my string' لاسترجاع القيمة النصية نستخدم اسم المتغير: print my_string لتجربة ذلك، نُنشئ ملف جديد باسم string_variables.rb باستخدام محرر النصوص ونضيف الشيفرة التالية: string_variables.rb my_name = "Sammy the Shark" my_age = "none of your business" puts my_name puts my_age يقوم البرنامج بتعرف مُتغيرين my_name و my_age وكل متغير منهما تم إعطاءه قيمة نصية. بعد ذلك نقوم باستخدام الوظيفة put لطباعة كل منهما في سطر. نحفظ الملف ونقوم بتشغيله باستخدام الأمر التالي: ruby string_variables.rb ونحصل على النتيجة التالية: Output Sammy the Shark none of your business بإعطاء المتغيرات القيم النصية تستطيع تفادى إعادة كتابة النص نفسه كلَ مرة تحتاجه فيها مما يجعل العمل مع النصوص أسهل في برامجك. في العنوان التالي من المقال سنتحدث عن دمج النصوص ببعضها وإنشاء نصوص جديدة منها. دمج النصوص الدمج يعني ربط نصين أو أكثر ببعضهم لإنشاء نص جديد، ويتم باستخدام عملية الدمج التي يُشار لها بالرمز + ، ويُستخدم نفس الرمز في عملية جمع الأرقام خلال العمليات الرياضية. لدمج النصين “sammy” و “shark” نكتب ما يلي: "sammy" + "shark" وسوف نحصل على النتيجة التالية: Output sammyshark الدمج يقوم بربط النصوص من النهاية للنهاية منتجًا نصًا جديدًا. إذا أردت أن يكون هناك فراغ بين النصين فعليك أن تُضيف هذا الفراغ لأحد النصوص: "sammy " + "shark" من الأغلب أنك لن تكتب شيفرة برمجية بهذا الشكل، ولكنك ستحتاج إلى استعمال النصوص مع المتغيرات وهنا يأتي دور الدمج. انظر المثال التالي: color = "Blue" print "My favorite color is " + color الشيفرة السابقة سينتج عنها النص My favorite color is blue. لاحظ أننا تركنا فراغا بعد الكلمة is بحيث يكون الناتج يحتوي على فراغ بين اخر كلمتين. تستطيع دمج نصوص عديدة بنفس الطريقة. أنشئ ملف باسم concatenation.rb وأضف فيه الشيفرة التالية: concatenation.rb my_name = "Sammy the Shark" my_age = "none of your business" puts "My name is " + my_name + " and my age is " + my_age + "." في الشيفرة السابقة قمنا بتعريف متغيرين my_name و my_age وكل منهما له قيمته النصية وطبعنا عدة نصوص ومتغيرات باستخدام عملية الدمج. عند تشغيل الشيفرة السابقة ستحصل على النتيجة التالية: Output My name is Sammy the Shark and my age is none of your business. عند استخدام عملية الدمج مع أكثر من نص فإن النتيجة هي نص جديد. هذا النص الجديد نستطيع أن نحفظه في متغير لإعادة استخدامه مرة أخرى: concatenation.rb my_name = "Sammy the Shark" my_age = "none of your business" # assign concatenated string to variable output = "My name is " + my_name + " and my age is " + my_age + "." # Print the output. puts output في مثال صغير مثل هذا لن تحتاج للمتغير output غالبًا، ولكن في برامج أكبر، من المحتمل أنك ستنشئ نص باستخدام عملية الدمج وتحفظه في متغير خاص وتستخدم هذا المتغير في أماكن متعددة. من الجيد أيضًا أن تتعود على فصل معالجة البيانات –مثل دمج النصوص والعمليات الرياضية– عن المخرجات كلما زاد حجم برنامجك، وكلما زاد حجم البرامج لديك أكثر ستحتاج لفصل العمليات المنطقية عن عمليات الإخراج في ملفات منفصلة ليسهل عليك إدارتها. تأكد من عدم استخدام عملية الدمج + بين نوعي بيانات مختلفين، فمثلًا، لن تستطيع دمج نص مع رقم بطريقة مباشرة. لتتعرف أكثر على هذه الحالة، أنشئ ملف باسم string_and_integers.rb وأضف له الشيفرة التالية: strings_and_integers.rb my_name = "Sammy the Shark" my_number = 27 print my_name + my_number لاحظ أن المتغير my_number يحمل القيمة العددية الصحيحة 27. بعد تشغيل البرنامج ستحصل على الخطأ التالي: Output strings_and_ints.rb:4:in `+': no implicit conversion of Integer into String (TypeError) from strings_and_ints.rb:4:in `<main>' الخطأ السابق يعني أننا لا نستطيع دمج متغير يحمل قيمة رقمية مع متغير آخر يحمل قيمة نصية. في نسخة الروبي 2.3 وما قبل، سيظهر لك رسالة الخطأ التالية: strings_and_ints.rb:4:in `+': no implicit conversion of Fixnum into String (TypeError) from strings_and_ints.rb:4:in `<main>' النوع Fixnum تم استبداله بالنوع Integer في نُسخ الروبي الجديدة. نستطيع تعديل البرنامج ليصبح المتغير my_number يحمل القيمة النصية “27” أو نقوم بتحويل القيمة الرقمية إلى نصية بالطريقة التالية: strings_and_integers.rb my_name = "Sammy the Shark" my_number = 27 print my_name + my_number.to_s الوظيفة .to_s تقوم بتحويل الرقم إلى نص. هذه الطريقة أفضل لأنها تُحافظ على نوع المتغير my_number كما هو دون تغيير، حيث قد نحتاج أن نتعامل معه كرقم في أماكن أخرى من البرنامج. قم بتشغيل البرنامج مرة أخرى وستحصل على النتيجة التالية: Sammy the Shark27. دمج الأرقام مع النصوص عملية ستتكرر معك دائما عند تعاملك مع العملات، أرقام الهواتف، الرموز البريدية وغيرها من البيانات التي تحتوي على أرقام، وعند حاجتك لعرض هذه البيانات على الشاشة كنصوص. الدمج عملية قوية ولكن لديها بعض القيود. فمثلا، إذا أزلت عملية الدمج + بشكل غير مقصود سينتج لديك خطأ، وكذلك لدمج متغير يحتوي على نوع رقم فستحتاج لتحويله لنص أولًا. تُقدم الروبي طريقة أخرى لدمج المتغيرات مع النصوص وتُسمى هذه الطريقة بـ استيفاء النصوص والتي تعالج المشكلتين السابقتين. استيفاء النصوص عند دمج النصوص والمتغيرات يكون المُخرج صعبًا في القراءة والتنقيح. طريقة استيفاء النصوص تحل لك ذلك وتسمح بتضمين العبارات في النصوص المحاطة بعلامتي تنصيص مزدوجتين. فبدلًا من كتابة ما يلي: "My name is " + my_name + "!" تستطيع كتابته بالطريقة التالية: "My name is #{my_name}!" بدلًا من استخدام العملية +، نستطيع تضمين المتغير الذي نريده باستخدام الرموز #{ } والتي تجعل روبي تقوم بتفسير التعبير وتُضمن النص الناتج عنه في النص الأصلي. البرنامج التالي يقوم بنفس عمل البرنامج السابق ولكن بطريقة استيفاء النص: interpolation.rb my_name = "Sammy the Shark" my_age = "none of your business" output = "My name is #{my_name} and my age is #{my_age}." puts output نستطيع استخدام طريقة استيفاء النص في دمج نص مع رقم بطريقة تلقائية. لنقم بتعديل البرنامج strings_and_integers.rb ليصبح كالتالي: strings_and_integers.rb my_name = "Sammy the Shark" my_number = 27 # use interpolation instead of concatenation print "My name is #{my_name} and my favorite number is #{my_number}." ستقوم الروبي بتحويل المتغير my_number إلى نص بشكل تلقائي وعند تشغيل البرنامج ستحصل على النتيجة التالية: Output My name is Sammy the Shark and my favorite number is 27. استيفاء النصوص طريقة قوية ومناسبة وهي مفضلة في دمج النصوص والمتغيرات. خاتمة تعلمنا في هذا الدرس كيفية التعامل مع النصوص في لغة البرمجة روبي. وسنتابع في الدرس التالي ماهية القيم الحرفية للنصوص وكيفية تكرارها بالإضافة إلى كيفية إضافة أسطر جديدة ضمن النصوص الطويلة. ترجمة -وبتصرّف- للمقال How to Work with Strings in Ruby لصاحبه Brian Hogan
  14. في بدايات التسعينات، قام Guido van Rossum بإنشاء لغة البايثون. تُعتبر البايثون من أشهر لغات البرمجة حاليا، ولها حضور واسع في العديد من المجالات التطبيقية والعلمية، وتتميز بسهولة شفرتها البرمجية وسرعة تعلمها مع متانة وقوة تضاهي اللغات الأخرى. سوف نتناول في هذا المقال المواضيع التالية: التعليقات. أنواع البيانات. المتغيرات والتراكيب. جمل التحكم. الدوال Functions. الوحدات. الفئات. ملاحظة: إصدار البايثون 3 هو المعتمد في شرح هذا المقال، وناتج العمليات والأوامر في هذا المقال سيتم كتابتها بعد الرمز # =>. التعليقات تبدأ التعليقات ذات السطر الواحد برمز #، أما التعليقات التي تحتوي أكثر من سطر فتجب إحاطتها بثلاث علامات تنصيص (منفردة أو مزدوجة) في البداية والنهاية. أنظر المثال التالي: # Single line comments start with a number symbol. """ Multiline strings can be written using three "s, and are often used as documentation. """ أنواع البيانات والعمليات الأرقام: 3 # => 3 العمليات الرياضية: 1 + 1 # => 2 8 - 1 # => 7 10 * 2 # => 20 35 / 5 # => 7.0 يوجد نوعان من القسمة في بايثون 3، الأولى تُسمى القسمة بعدد فاصل عائم “floating point division” ونَستخدم رمز القسمة المعروف / ، وناتج العملية هو دائما عدد حقيقي من النوع float: 10.0 / 3 # => 3.3333333333333335 أما النوع الثاني من القسمة فيُسمى القسمة الصحيحة “integer division” ونَستخدم الرمز // لهذا النوع، ويكون ناتج العملية دون الفاصلة والأرقام التي بعدها: 5 // 3 # => 1 5.0 // 3.0 # => 1.0 # يعمل هذا النوع من القسمة على الأعداد الحقيقية أيضا -5 // 3 # => -2 -5.0 // 3.0 # => -2.0 عملية باقي القسمة: 7 % 3 # => 1 عملية الأس: 2**3 # => 8 قاعدة أولوية العمليات حسب الأقواس: (1 + 3) * 2 # => 8 القيم المنطقية (لاحظ الحرف الكبير في البداية): True False عكس القيمة المنطقية باستخدام not: not True # => False not False # => True العمليات المنطقية (العمليات المنطقية حساسة لحالة الأحرف): True and False # => False False or True # => True القيمة المنطقية False تساوي الرقم 0، والقيمة المنطقية True تساوي الرقم 1: 0 and 2 # => 0 -5 or 0 # => -5 0 == False # => True 2 == True # => False 1 == True # => True -5 != False != True #=> True عملية فحص المساواة باستخدام ==: 1 == 1 # => True 2 == 1 # => False فحص عدم المساواة: 1 != 1 # => False 2 != 1 # => True المقارنات: 1 < 10 # => True 1 > 10 # => False 2 <= 2 # => True 2 >= 2 # => True 1 < 2 < 3 # => True 2 < 3 < 2 # => False تفحص عملية is إذا كان متغيران يشيران لنفس الكائن أم لا، ولكن العملية == تفحص إذا كانا بنفس القيمة أم لا: a = [1, 2, 3, 4] b = a b is a # => True b == a # => True b = [1, 2, 3, 4] b is a # => False b == a # => True تُنشَأ النصوص باستخدام علامات التنصيص المزدوجة أو الفردية: "This is a string." 'This is also a string.' تستطيع جمع النصوص ببعضها، ولكن حاول تجنب هذه الطريقة: "Hello " + "world!" # => "Hello world!" تستطيع دمج النصوص ببعضها دون استخدام + : "Hello " "world!" # => "Hello world!" من الممكن التعامل مع النص وكأنه مصفوفة من الحروف: "This is a string"[0] # => 'T' للحصول على طول نص نستخدم الدالة المضمنة len : len("This is a string") # => 16 تستطيع استخدام الدالة format لإجراء عملية التنسيق على النص: "{} can be {}".format("Strings", "interpolated") # => "Strings can be interpolated" تستطيع عند استخدام الدالة format ترقيم المدخلات حسب ترتيبها واستخدامها في تنسيق النص أكثر من مرة: "{0} be nimble, {0} be quick, {0} jump over the {1}".format("Jack", "candle stick") # => "Jack be nimble, Jack be quick, Jack jump over the candle stick" أو باستخدام طريقة تسمية المدخلات: "{name} wants to eat {food}".format(name="Bob", food="lasagna") # => "Bob wants to eat lasagna" تستطيع في البايثون 3 استخدام الطريقة القديمة في بايثون 2 لعمل تنسيق للنصوص: "%s can be %s the %s way" % ("Strings", "interpolated", "old") # => "Strings can be interpolated the old way" None عبارة عن كائن: None # => None لا تستخدم فحص المساواة باستخدام رمز == للمقارنة مع None واستخدم عملية الفحص is بدلا منها: "etc" is None # => False None is None # => True None والرقم 0 والمتغيرات الفارغة من الأنواع strings، lists، dict، وtuples جميعها تُرادف القيمة المنطقية False، أما باقي القيم فهي True: # All other values are True bool(0) # => False bool("") # => False bool([]) # => False bool({}) # => False bool(()) # => False المتغيرات والتراكيب: تتوفّردالة خاصة للطباعة (الإخراج على الطرفية) وتسمى print: print("I'm Python. Nice to meet you!") # => I'm Python. Nice to meet you! يُطبَع سطر جديد تلقائيا عند استخدام الدالة print. تستطيع استخدام المعطى end لتغيير هذا الأمر وتحديد النص الذي تريده بدلا من السطر الجديد: print("Hello, World", end="!") # => Hello, World! للحصول على مدخلات من الطرفية نستخدم الدالة input: input_string_var = input("Enter some data: ") # Returns the data as a string ملاحظة/ في النسخ القديمة من البايثون، كانت الدالة input باسم raw_input. لا يوجد في البايثون تعريفات، ولكن يوجد إعطاء قيم مباشرة. الطريقة المتعارف عليها في تسمية المتغيرات هي الأحرف الصغيرة مع التسطير السفلي: some_var = 5 some_var # => 5 محاولة استخدام متغير لم يأخذ قيمة مسبقاً ينتج عنه خطأ، راجع كيفية معالجة الأخطاء تحت عنوان جمل التحكم. some_unknown_var # ينتُج خطأ من الصنف NameError تشبه القوائم المصفوفات في اللغات الأخرى: li = [] other_li = [4, 5, 6] نستخدم append لإضافة عناصر في نهاية القائمة: li.append(1) # li is now [1] li.append(2) # li is now [1, 2] li.append(4) # li is now [1, 2, 4] li.append(3) # li is now [1, 2, 4, 3] نستخدم الدالةpop لحذف العناصر من آخر القائمة. ترجع التعليمة أدناه القيمة 3 وتصبح مكونات القائمة [1, 2, 4]: li.pop() # => 3 and li is now [1, 2, 4] تعود القائمة إلى حالتها السابقة لتنفيذ الدالة pop بعد تنفيذ الدالة append على النحو التالي: li.append(3) # li is now [1, 2, 4, 3] again. تستطيع التعامل مع القائمة مثل المصفوفة من حيث الوصول لعناصرها: li[0] # => 1 li[-1] # => 3 في حال استخدام فهرس خارج حدود القائمة سينتج خطأ من نوع IndexError: li[4] # Raises an IndexError تستطيع استخدام مجال للحصول على جزء أكبر من القائمة بحيث نحدد فهرس البداية وفهرس النهاية. li[1:3] # => [2, 4] ملاحظة: فهرس النهاية غير مشمول في القيمة المرجعة، حيث يعدّ النمط المستخدم هو نمط نطاق مغلق-مفتوح. في حال عدم استخدام فهرس النهاية: li[2:] # => [4, 3] في حال عدم استخدام فهرس البداية: li[:3] # => [1, 2, 4] اختيار عنصر كل خطوتين ابتداء من العنصر الأول في القائمة: li[::2] # =>[1, 4] إرجاع كامل المصفوفة بطريقة عكسية: li[::-1] # => [3, 4, 2, 1] القاعدة العامة للاستعلامات السابقة في القوائم هي كالتالي(البداية start، النهاية end والخطوة step): # li[start:end:step] نسخ عميق (Deep Copy): li2 = li[:] # => li2 = [1, 2, 4, 3] عندما نفحص المساواة باستخدام عملية is كالتالي: (li2 is li) ستكون النتيجة False. لحذف عنصر من القائمة: del li[2] # li is now [1, 2, 3] لحذف أول عنصر في القائمة يساوي القيمة المدخلة في الدالة remove: li.remove(2) # li is now [1, 3] li.remove(2) # ValueError لأن القيمة غير موجودة إضافة عنصر في مكان معين في القائمة: li.insert(1, 2) # li is now [1, 2, 3] again الحصول على فهرس أول عنصر في القائمة يساوي القيمة المعطاة: li.index(2) # => 1 li.index(4) # ValueError لأن القيمة غير موجودة لإضافة قائمة لقائمة وإرجاع النتيجة كقائمة جديدة: li + other_li # => [1, 2, 3, 4, 5, 6] لتمديد قائمة وإضافة قائمة إليها: li.extend(other_li) # Now li is [1, 2, 3, 4, 5, 6] لفحص وجود قيمة في القائمة: 1 in li # => True للحصول على حجم القائمة (عدد العناصر التي بها): len(li) # => 6 نوع البيانات Tuple تشبه القائمة ولكنها غير قابلة للتعديل (ثابتة-immutable): tup = (1, 2, 3) tup[0] # => 1 tup[0] = 3 # Raises a TypeError لاحظ أنه في حالة وجود عنصر واحد في tuple لابد من وضع فاصلة عادية بعد العنصر، أما في حالة وجود أكثر من عنصر فتصبح الفاصلة إضافية: type((1)) # => <class 'int'> type((1,)) # => <class 'tuple'> type(()) # => <class 'tuple'> تستطيع تنفيذ أغلب عمليات القوائم على النوع Tuple: len(tup) # => 3 tup + (4, 5, 6) # => (1, 2, 3, 4, 5, 6) tup[:2] # => (1, 2) 2 in tup # => True تستطيع تفريغ (unpacking) محتويات Tuples وكذلك القوائم في متغيرات كما في الأمثلة التالية: a, b, c = (1, 2, 3) # a = 1, b = 2, c = 3 a, *b, c = (1, 2, 3, 4) # a = 1, b = [2, 3], c = 4 عند عدم استخدام الأقواس فإن نوع البيانات التلقائي الذي سيتم استخدامه هو Tuple: d, e, f = 4, 5, 6 تبديل قيم المتغيرات بطريقة سهلة: e, d = d, e # d = 5, e = 4 القواميس عبارة عن مؤشرات (مُخططات) من المفاتيح للقيم (كل مفتاح يؤشر على قيمة خاصة به). تعريف قاموس فارغ: empty_dict = {} تعريف قاموس بقيم مسبقة: filled_dict = {"one": 1, "two": 2, "three": 3} لاحظ أن المفاتيح في القواميس لابد أن يكون نوع بياناتها ثابتا (immutable) وذلك لضمان الحصول على مفتاح ثابت (لا تتغير قيمته). أنواع البيانات الثابتة والتي من الممكن استخدامها هي int , float, string, tuple. invalid_dict = {[1,2,3]: "123"} # => Raises a TypeError: unhashable type: 'list' valid_dict = {(1,2,3):[1,2,3]} # Values can be of any type, however. يمكن للقيم – عكس المفاتيح – أن تكون من أي نوع. للبحث عن قيم نستخدم الأقواس المعكوفة: filled_dict["one"] # => 1 للحصول على مفاتيح قاموس على شكل قائمة (الترتيب في القواميس غير ثابت): list(filled_dict.keys()) # => ["three", "two", "one"] للحصول على قيم قاموس على شكل قائمة: list(filled_dict.values()) # => [3, 2, 1] للتأكد من وجود مفتاح قاموس معين: "one" in filled_dict # => True 1 in filled_dict # => False في حالة استخدام مفتاح غير موجود للبحث في قاموس، فإن ذلك ينتج خطأ: filled_dict["four"] # KeyError استخدم الدالة get لتجنب الخطأ السابق: filled_dict.get("one") # => 1 filled_dict.get("four") # => None تدعم الدالة get إعادة قيمة تلقائية في حالة عدم وجود المفتاح: filled_dict.get("one", 4) # => 1 filled_dict.get("four", 4) # => 4 تضيف الدالة setdefault المفتاح المُمرر إلى القاموس في حالة عدم وجوده. تضيف التعليمة التالية مفتاحا باسم five وتعطيه قيمة 5، أما التعليمة الثانية فلا تحدت تغييرا على القاموس. filled_dict.setdefault("five", 5) # filled_dict["five"] is set to 5 filled_dict.setdefault("five", 6) # filled_dict["five"] is still 5 للإضافة إلى القاموس: filled_dict.update({"four":4}) # => {"one": 1, "two": 2, "three": 3, "four": 4} filled_dict["four"] = 4 # طريقة أخرى حذف المفتاح من القاموس: del filled_dict["one"] # Removes the key "one" from filled dict بعض طرق التفريغ في القواميس: {'a': 1, **{'b': 2}} # => {'a': 1, 'b': 2} {'a': 1, **{'a': 2}} # => {'a': 2} المجموعات: empty_set = set() some_set = {1, 1, 2, 2, 3, 4} # some_set is now {1, 2, 3, 4} نوع البيانات الخاص بعناصر المجموعات لابد أن يكون ثابتا: invalid_set = {[1], 1} # => Raises a TypeError: unhashable type: 'list' valid_set = {(1,), 1} للإضافة إلى المجموعة: filled_set.add(5) # filled_set is now {1, 2, 3, 4, 5} إجراء عملية التقاطع بين مجموعتين: other_set = {3, 4, 5, 6} filled_set & other_set # => {3, 4, 5} إجراء عملية الاتحاد بين مجموعتين: filled_set | other_set # => {1, 2, 3, 4, 5, 6} إجراء عملية الطرح بين مجموعتين: {1, 2, 3, 4} - {2, 3, 5} # => {1, 4} لإجراء عملية فرق التماثل بين مجموعتين: {1, 2, 3, 4} ^ {2, 3, 5} # => {1, 4, 5} لفحص إذا كانت المجموعة على الشمال هي مجموعة تحتوي المجموعة على اليمين أم لا: {1, 2} >= {1, 2, 3} # => False عكس المثال السابق: {1, 2} <= {1, 2, 3} # => True فحص وجود قيمة في مجموعة: 2 in filled_set # => True 10 in filled_set # => False جمل التحكم some_var = 5 جملة if: if some_var > 10: print("قيمة المتغيّر أكبر تماما من 10") elif some_var < 10: # هذه الجملة اختيارية print("قيمة المتغيّر أصغر من 10") else: # هذه الجملة اختيارية print("قيمة المتغيّر تساوي 10") جملة for: for animal in ["dog", "cat", "mouse"]: print("{} is a mammal".format(animal)) لاحظ استخدام الدالة format في جملة for السابقة. يمكن أيضا تطبيق الجملة على مجال عددي range: for i in range(4): print(i) for i in range(4, 8): print(i) for i in range(4, 8, 2): print(i) جملة while: x = 0 while x < 4: print(x) x += 1 # اختصارا ل x = x + 1 معالجة الأخطاء باستخدام try/except (استخدم raise لتوليد الخطأ): try: raise IndexError("This is an index error") except IndexError as e: pass except (TypeError, NameError): pass else: print("All good!") finally: print("We can clean up resources here") ملاحظات حول معالجة الأخطاء: Pass تعني عدم وجود عملية للتنفيذ. تستطيع سرد أكثر من نوع خطأ في جملة except. تستطيع استخدام جملة else مع try/except اختياريا (تنفذ في حالة كانت الشفرة البرمجية في try لم تُصدر أي خطأ). نستخدم جملة finally لتنفيذ شفرة برمجية بعد try/except بغض النظر عن وجود أخطاء أم لا، وعادةً يُعاد تحرير المصادر المستخدمة. بدلا من استخدام جملة finally لإعادة تحرير المصادر المستخدمة، تستطيع استخدام جملة with: with open("myfile.txt") as f: for line in f: print(line) تُقدم البايثون كائنًا متُعددًا (Iterable) وهو كائن مجرد (عام) يُتعامل معه مثل sequence. فمثلا الكائن المُرجع من الدالة range هو كائن مُتعدد: filled_dict = {"one": 1, "two": 2, "three": 3} our_iterable = filled_dict.keys() print(our_iterable) # => dict_keys(['one', 'two', 'three']). تستطيع المرور على عناصر الكائن المتعدد والتعامل معها: for i in our_iterable: print(i) # Prints one, two, three على الرغم من خاصية الكائن المتعدد، إلا أنه لا تستطيع استخدام الفهرس معه: our_iterable[1] # Raises a TypeError تستطيع الحصول من خلال الكائن المُتعدد على كائن iterator منه بحيث تستطيع المرور على عناصره: our_iterator = iter(our_iterable) يحتفظ الكائن iterator بحالته كلما تم استخدامه، فمثلا، باستخدام وظيفة next تستطيع الحصول على العنصر التالي في هذا الكائن: next(our_iterator) # => "one" next(our_iterator) # => "two" next(our_iterator) # => "three" بعد الحصول على كافة عناصر iterator فإن استخدام الدالة next سيعيد خطأ: next(our_iterator) # Raises StopIteration تستطيع الحصول على كافة عناصر iterator دفعة واحدة على شكل قائمة وذلك باستخدام الدالة list : list(filled_dict.keys()) # => Returns ["one", "two", "three"] الدوال نستخدم الكلمة def في تعريف الدالة، ونستخدم كلمة return في إرجاع النتيجة: def add(x, y): print("x is {} and y is {}".format(x, y)) return x + y تطبع الدالة السابقة قيمتيْ المعامليْن المُمرّرين لها وتعيد ناتج جمعهما: add(5, 6) # => prints out "x is 5 and y is 6" and returns 11 يمكن أيضا استدعاء الدالة بذكر أسماء المعاملات (شرط الترتيب غير مطلوب هنا للمعاملات): add(y=6, x=5) تستطيع تعريف دالة باستقبال عددًا غير محدد من المعاملات: def varargs(*args): return args varargs(1, 2, 3) # => (1, 2, 3) من الممكن استخدام المعاملات المُسماة لاستقبال عدد غير محدد من المعاملات أيضا: def keyword_args(**kwargs): return kwargs keyword_args(big="foot", loch="ness") # => {"big": "foot", "loch": "ness"} كما نستطيع دمج الطريقتين في نفس الدالة: def all_the_args(*args, **kwargs): print(args) print(kwargs) all_the_args(1, 2, a=3, b=4) # => (1, 2) {"a": 3, "b": 4} توجد طريقة أخرى لاستدعاء الدوال باستخدام args/kwargs وذلك عندما تكون المعطيات من النوع tuple أو قاموس: args = (1, 2, 3, 4) kwargs = {"a": 3, "b": 4} all_the_args(*args) # equivalent to foo(1, 2, 3, 4) all_the_args(**kwargs) # equivalent to foo(a=3, b=4) all_the_args(*args, **kwargs) # equivalent to foo(1, 2, 3, 4, a=3, b=4) يمكن أيضا إرجاع نتيجة من قيم متعددة على شكل tuple: def swap(x, y): return y, x x = 1 y = 2 x, y = swap(x, y) # => x = 2, y = 1 يختلف المتغيّر في نطاق scope الدالة عن المتغيّرات العامة Global: x = 5 def set_x(num): x = num # => 43 print(x) # => 43 تُستخدَم الكلمة المفتاحية global لتعريف متغيّر عام من داخل الدالة: def set_global_x(num): global x print(x) # => 5 x = num # هذا المتغير يمثل المتغير على النطاق العام وقيمته الان 6 print(x) # => 6 set_x(43) set_global_x(6) تعدّ الدوال في بايثون كائنات من الفئة الأولى: def create_adder(x): def adder(y): return x + y return adder add_10 = create_adder(10) add_10(3) # => 13 كما يمكنك تعريف دوال غير مسمّاة Anonymous functions: (lambda x: x > 2)(3) # => True (lambda x, y: x ** 2 + y ** 2)(2, 1) # => 5 ويمكنك تمرير الدالة معاملا لدالة أخرى: list(map(add_10, [1, 2, 3])) # => [11, 12, 13] list(map(max, [1, 2, 3], [4, 2, 1])) # => [4, 2, 3] list(filter(lambda x: x > 5, [3, 4, 5, 6, 7])) # => [6, 7] تستطيع استخدام مبدأ “تفهيم القائمة” للحصول على نفس نتيجة الدوال map و filter: [add_10(i) for i in [1, 2, 3]] # => [11, 12, 13] [x for x in [3, 4, 5, 6, 7] if x > 5] # => [6, 7] تستطيع استخدام مبدأ “تفيهم القاموس” و “تفهيم المجموعة” كذلك: {x for x in 'abcddeef' if x not in 'abc'} # => {'d', 'e', 'f'} {x: x**2 for x in range(5)} # => {0: 0, 1: 1, 2: 4, 3: 9, 4: 16} الوحدات Modules الوحدات في بايثون عبارة عن ملفات بايثون عادية. تستطيع أن تكتب الوحدة الخاصة بك وتستوردها في الشفرة البرمجة الخاصة بمشروعك. اسم الوحدة سيكون نفس اسم الملف الذي أنشأته لهذا الغرض. تُستورَد الوحدات بالطريقة التالية: import math print(math.sqrt(16)) # => 4.0 تستطيع الحصول على دوال محددة من الوحدات: from math import ceil, floor print(ceil(3.7)) # => 4.0 print(floor(3.7)) # => 3.0 تستطيع استيراد جميع الدوالّ من الوحدة دفعة واحدة ولكن هذا الأمر غير منصوح به: from math import * تستطيع اختصار أسماء الوحدات عند استيرادها: import math as m math.sqrt(16) == m.sqrt(16) # => True تُستخدَم الدالة المضمنة dir لمعرفة مكان ملف الوحدة. import math dir(math) إذا كان لديك ملف بايثون باسم math في نفس المجلد الذي يوجد به ملف العمل الخاص بك، فإن الملف math هو الذي سيُحمَّل ويُستورد بدلا من الوحدة التلقائية المضمنة في البايثون باسم math ذلك لأن الأولوية في حال تشابه الأسماء هي للملفات في مجلد العمل المحلي أو الحالي. الأصناف Classes نستخدم كلمة class لتعريف صنف: class Human: لتعريف خاصية للصنف (هذه الخاصية تكون مُشاركة بين كل العناصر المتولدة من هذا الصنف): species = "H. sapiens" init هو المشيّدات Constructor الأساسي ويُستدعى عند توليد عنصر من الصنف. التسطير السفلي المكرر مرتين قبل كلمة init وبعدها يدل على أن هذا الكائن أو الخاصية يستخدمه بايثون ولا يجب علينا استخدامها مباشرة. def __init__(self, name): # إعطاء قيمة المعطى للخاصية الموجودة في الصنف self.name = name # قيمة مبدئية self._age = 0 الدالة say هي تابع عيّنة Instance method، أي أن لكل كائن نسخة خاصة به منها. تأخذ هذه التوابع أن self في أول معامل يُمرّر لها: def say(self, msg): print ("{name}: {message}".format(name=self.name, message=msg)) def sing(self): return 'yo... yo... microphone check... one two... one two...' يمكن أيضا تعريف تابع متشارك بين كل كائنات الصنف: @classmethod def get_species(cls): return cls.species نستطيع كذلك تعريف تابع ساكن يُستدعى دون الحاجة لإنشاء كائن من الصنف: @staticmethod def grunt(): return "*grunt*" يحوّل التعليمة property@ دالة إلى خاصيّة للقراءة فقط لها نفس اسم الدالة، لتؤدّي بالتالي وظيفة المسترجعات Getters. @property def age(self): return self._age يمكننا جعل الخاصية قابلة للتعيين لتصبح الدالة تعمل معدّلا Setter: @age.setter def age(self, age): self._age = age كما يمكننا السماح بحذفها: @age.deleter def age(self): del self._age يقوم مُفسر البايثون بتنفيذ كافة اشيفرة البرمجية في ملف الوحدة الذي يقرأه، ومن خلال الخاصية name نتأكد من أن كتلة الشفرة البرمجية التي في جملة الشرط ستُنفَّذ في حال كانت الوحدة هي البرنامج الرئيسي المُنفذ: if __name__ == '__main__': i = Human(name="Ian") i.say("hi") # "Ian: hi" j = Human("Joel") j.say("hello") # "Joel: hello" # استدعاء دالة الفئة i.say(i.get_species()) # "Ian: H. sapiens" # تغيير الخاصية المشتركة Human.species = "H. neanderthalensis" i.say(i.get_species()) # => "Ian: H. neanderthalensis" j.say(j.get_species()) # => "Joel: H. neanderthalensis" # استدعاء الدالة الساكنة print(Human.grunt()) # => "*grunt*" لا تستطيع استدعاء الدالة الساكنة من خلال العنصر المتولد i لأن استدعاءها بهذه الطريقة سيضيف self كمعامل لها مما سينتج عنه خطأ: print(i.grunt()) # => TypeError: grunt() takes 0 positional arguments but 1 was given i.age = 42 i.say(i.age) # => "Ian: 42" j.say(j.age) # => "Joel: 0" del i.age # i.age # => this would raise an AttributeError ترجمة -وبتصرف- للمقال Learn X in Y minutes Where X=Python3
  15. أُنشئت لغة البرمجة Go لإنجاز العمل بسهولة، وهي ليست ضمن الاتجاهات الحديثة في علم الحاسوب، ولكنها أحدث وسيلة برمجية لحل المشاكل في الواقع بسرعة. تمتلك لغة Go مفاهيم مُشابهة للغات البرمجة الإجبارية Imperative Languages بالإضافة لثبات أنواع البيانات Static typing، وتعدّ كذلك سريعة في البرمجة Compilation وسريعة في التشغيل والتنفيذ، ومتوافقة مع المعالجات ذات الأنوية المتعددة بهدف الاستفادة منها بسهولة، كما أنها تمتلك الكثير من المزايا التي تساعد في البرمجة للأنظمة الكبيرة والمعقَّدة. تتمتع لغة Go بمكتبة معيارية عظيمة ومجتمع برمجي متحمس ونشط. في هذا المقال، سوف نشرح أساسيات لغة Go بطريقة سهلة وبسيطة، ونُعرج على بعض المفاهيم المهمة. الشفرة البرمجية الموجودة في هذا المقال مترابطة، ولكننا قسّمناها إلى أجزاء ووضعنا عناوين لهذه الأجزاء، كما توجد الكثير من التعليقات المباشرة على الشفرة البرمجية. المواضيع الأساسية التي يغطيها هذا المقال كالتالي: كتابة التعليقات. المكتبات واستيرادها. الدوال. أنواع البيانات. القيم الراجعة المسماة. المتغيرات والذاكرة. جمل التحكم. توليد الدوال. التنفيذ المؤجل. الواجهات. المُدخلات المتعددة. معالجة الأخطاء. التنفيذ المتزامن. الويب كتابة التعليقات لكتابة تعليق من سطر واحد // single line comment لكتابة تعليق بأكثر من سطر /* Multi- line comment */ المكتبات واستيرادها يبدأ كل ملف مصدري بالكلمة المفتاحية packag. تُستخدَم الكلمة المفتاحية main لتعريف الملف كملف تشغيلي وليس مكتبة. package main لاستيراد حزمة مكتبية في الملف نستخدم التعليمة Import بالطريقة التالية: import ( "fmt" // حزمة في المكتبة المعيارية للغة "io/ioutil" // تطبق دوال إدخال وإخراج m "math" //نستخدم الحرف m لاختصار اسم مكتبة الدوال الرياضية "net/http" // خادوم ويب "os" // دوال على مستوى نظام التشغيل مثل التعامل مع الملفات "strconv" // تحويلات نصية ) الدوال Functions تُعرَّف الدوال باستخدام كلمة func متبوعة باسم الدالة. تعدّ الدالة main خاصة، وهي المدخل للملف التنفيذي للبرنامج (لغة Go تستخدم الأقواس المزخرفة {} لتحديد الأجزاء/الكتل البرمجية). func main() { // لإخراج نص على وحدة الإخراج (العرض) الرئيسية stdout نستخدم الدالة Println الموجودة في مكتبة fmt fmt.Println("Hello world!") // استدعاء دالة من نفس الحزمة الحالية beyondHello() } تحتاج الدوال لأقواس تستقبل المعاملات Parameters، وحتى في عدم وجود معاملات فإن الأقواس مطلوبة. func beyondHello() { // تعريف متغير (لا بد من تعريف المتغير قبل استخدامه) var x int // إعطاء قيمة للمتغير x = 3 // التعريف القصير باستخدام := ويشمل تعريف المتغير, وتحديد نوعه وإعطاءه قيمة y := 4 // دالة ترجع قيمتين منفصلتين sum, prod := learnMultiple(x, y) // طباعة وإخراج بشكل بسيط ومباشر fmt.Println("sum:", sum, "prod:", prod) learnTypes() } يمكن أن توجد في تعريف الدوال معاملات وقيم مرجعة متعددة، فمثلا تأخذ الدالة learnMultiple أدناه معاملين x و y وترجع قيمتين sum و prod من نوع عدد صحيح Int. func learnMultiple(x, y int) (sum, prod int) { // نفصل بين القيم المُرجعة بفاصلة عادية return x + y, x * y } أنواع البيانات Data Types func learnTypes() { //التعريفات القصيرة عادة تؤدي الغرض المطلوب // تعريف متغير نصي باستخدام علامة التنصيص المزدوجة str := "Learn Go!" // تعريف متغير نصي باستخدام علامة التنصيص المنفردة s2 := `A "raw" string literal can include line breaks.` // تعريف متغير من نوع rune وهو عبارة عن مسمى آخر لنوع int32 ويحتوي المتغير من هذا النوع على يونيكود g := 'Σ' // تعريف عدد عشري Float f := 3.14195 // تعريف عدد مركب (عقدي)Complex c := 3 + 4i // تعريف المتغيرات باستخدام var var u uint = 7 //عدد طبيعي (صحيح موجب) var pi float32 = 22. / 7 //عدد عشري من 32 بت // طريقة التحويل باستخدام التعريف القصير (byte تعتبر مسمى اخر لنوع uint8) n := byte('\n') // المصفوفات لها حجم محدد وثابت في وقت الترجمة // تعريف مصفوفة من نوع int بحجم 4 عناصر وبقيمة أولية تساوي صفر var a4 [4]int // تعريف مصفوفة بحجم 3 عناصر بالقيم 3 و 1 و 5 a3 := [...]int{3, 1, 5} يقدّم Go نوع بيانات يُسمّى الشرائح Slices. الشرائح (Slices) لها حجم ديناميكي. المصفوفات والشرائح لها مميزات ولكن حالات الاستخدام للشرائح شائعة أكثر. تعرّف التعليمة التالية شريحة من النوع int // لاحظ الفرق بين تعريف المصفوفة والشريحة، حيث عند تعريف الشريحة لا يوجد رقم يحدد حجمها s3 := []int{4, 5, 9} //تعريف شريحة من نوع int بأربعة عناصر بقيم صفرية s4 := make([]int, 4) // تعريف فقط، ولا يوجد تحديد var d2 [][]float64 // طريقة تحويل النوع من نص لشريحة bs := []byte("a slice") بحكم طبيعة الشرائح الديناميكية، فإنه من الممكن إضافة عناصر جديدة للشريحة وذلك يتم باستخدام الدالة المضمنة append. نمرر أولا الشريحة التي نريد الإضافة عليها ومن ثم العناصر التي نريد إضافتها، أنظر للمثال بالأسفل. s := []int{1, 2, 3} s = append(s, 4, 5, 6) // ستُطبَع شريحة بالمحتويات التالية [1 2 3 4 5 6] fmt.Println(s) لإضافة شريحة إلى شريحة أخرى نمرر الشريحتين للدالة بدلا من تمرير عناصر منفردة، ونتبع الشريحة الثانية بثلاث نقاط كما في المثال التالي. s = append(s, []int{7, 8, 9}...) // سيتم طباعة شريحة بالمحتويات التالية [1 2 3 4 5 6 7 8 9] fmt.Println(s) تعرّف التعليمة التالية متغيرين p وq ليكونا مؤشّريْن Pointers على متغيّرين من نوع int يحويان قيمتين مُرجعتيْن من الدالة learnMemory: p, q := learnMemory() عندما تسبق النجمة مؤشرا فإن ذلك يعني قيمة المتغير الذي يحيل إليه المؤشر، أي في المثال التالي قيمتا المتغيريْن اللذيْن ترجعهما الدالة learnMemory: fmt.Println(*p, *q) الخرائط Maps في Go هي مصفوفات ترابطية يمكن التعديل عليها ديناميكيا وهي تشابه نوع القاموس او الهاش في اللغات الأخرى. /* هنا نعرف خريطة يكون مفتاحها من نوع نصي، وقيم العناصر رقمية. */ m := map[string]int{"three": 3, "four": 4} m["one"] = 1 تعدّ لغة Go المتغيرات غير المستخدمة خطأ. التسطير السفلي بالطريقة التالية يجعلك تستخدم المتغير ولكن تتجاهل قيمته في نفس الوقت: _, _, _, _, _, _, _, _, _, _ = str, s2, g, f, u, pi, n, a3, s4, bs تُستخدَم هذه الطريقة عادة لتجاهل قيمة راجعة من دالة، فمثلا تستطيع تجاهل رقم الخطأ الراجع من دالة إنشاء الملف os.Create والذي يفيد بأن الملف موجود مسبقا، وتفترض دائما أن الملف شيُنشَأ: file, _ := os.Create("output.txt") fmt.Fprint(file, "بالمناسبة، هذه هي دالة الكتابة في ملف") file.Close() fmt.Println(s, c, a4, s3, d2, m) learnFlowControl() } القيم الراجعة المسماة Named return values خلافا للغات الأخرى، من الممكن أن تكون للدوال قيم راجعة مسماة. حيث يتم إعطاء اسم للقيمة الراجعة من الدالة وذلك في سطر تعريف الدالة، وهذه الميزة تتيح الرجوع بسهولة من أي نقطة في الدالة بالإضافة لاستخدام كلمة return فقط دون ذكر شيء بعدها: func learnNamedReturns(x, y int) (z int) { z = x * y //هنا كتبنا كلمة return فقط وضمنيا نعني إعادة قيمة المتغير z return } ملاحظة: تعتمد لغة Go كثيرا على جمع الموارد غير المستخدمة Garbage collection. توجد في Go مؤشّرات لكن بدون إجراء عمليات حسابية عليها (تستطيع الخطأ في استخدام مؤشر فارغ ولكن لا تستطيع الزيادة على المؤشر). المتغيرات والذاكرة المتغيّران p وq أدناه هما مؤشّران على النوع int ويمثّلان قيمتين راجعتين في الدالة. يكون المؤشّران عند تعريفهما فارغين؛ إلا أن استخدام الدالة المُضمّنة new يجعل قيمة المتغيّر العددي الذي يحيل إليه المؤشّرp مساوية للصفر، وبالتالي يأخذ حيزا من الذاكرة؛ أي أن المؤشر p لم يعد فارغا. func learnMemory() (p, q *int) { p = new(int) // تعريف شريحة من 20 عنصر كوحدة واحدة في الذاكرة s := make([]int, 20) // إعطاء قيمة لأحد العناصر s[3] = 7 // تعريف متغير جديد محلي على مستوى الدالة r := -2 // إرجاع قيمتين من الدالة هما عبارة عن عناوين الذاكرة للمتغيرات s و r على الترتيب. return &s[3], &r } func expensiveComputation() float64 { return m.Exp(10) } جمل التحكم تتطلّب الجمل الشرطية وجود أقواس مزخرفة ولا تتطلب وجود أقواس هلالية. func learnFlowControl() { if true { fmt.Println("told ya") } if false { // Pout. } else { // Gloat. } نستخدم جملة switch في حال حاجتنا لكتابة أكثر من جملة شرطية متتابعة. x := 42.0 switch x { case 0: case 1: case 42: case 43: default: } كما الجملة الشرطية، فإن جملة for لا تأخذ أقواس هلالية.المتغيرات المعرفة في جملة for تكون مرئية على مستوى الجملة. for x := 0; x < 3; x++ { fmt.Println("iteration", x) } جملة for هي جملة التكرار الوحيدة في لغة Go ولها شكل آخر بالطريقة التالية: for { // تكرار لا نهائي // نستطيع استخدام break لوقف التكرار break // نستطيع استخدام continue للذهاب للتكرار القادم continue } تستطيع استخدام range للمرور على عناصر مصفوفة، شريحة، نص، خريطة أو قناة Channel تـعيد range قيمة واحدة عند استخدام قناة، وقيمتين عند استخدام شريحة أو مصفوفة أو نص أو خريطة. // مثال: for key, value := range map[string]int{"one": 1, "two": 2, "three": 3} { // نطبع قيمة كل عنصر في الخريطة fmt.Printf("key=%s, value=%d\n", key, value) } استخدم علامة التسطير السفلي مقابل القيمة الراجعة إذا كنت تريد الحصول على القيمة فقط، كالتالي: for _, name := range []string{"Bob", "Bill", "Joe"} { fmt.Printf("Hello, %s\n", name) } نستطيع استخدام التعريف القصير مع الجملة الشرطية بحيث يُعرف متغير ومن ثم يُفحَص في جملة الشرط. نعرّف في ما يلي متغيرًا y ونقوم بإعطائه قيمة ومن ثم نقوم بوضع شرط الجملة بحيث يتم فصلهما ب فاصلة منقوطة. if y := expensiveComputation(); y > x { x = y } نستطيع تعريف دوال وهمية anonymous مباشرة في الشفرة البرمجية” xBig := func() bool { // عرّفنا المتغيّر x التالي قبل جملة switch السابقة return x > 10000 } x = 99999 // ترجع الدالة xBig الآن القيمة true fmt.Println("xBig:", xBig()) x = 1.3e3 // بعد تعديل قيمة x إلى 1.3e3 التي تساوي 1300 (أي أكبر من 1000) فإن الدالة xBig ترجع false fmt.Println("xBig:", xBig()) بالإضافة لما سبق، فإنه من الممكن تعريف الدالة الوهمية واستدعائها في نفس السطروتمريرها في معطى لدالة أخرى بشرط أن يتم استدعاؤها مباشرة وأن يكون نوع النتيجة متوافقا مع ما هو متوقع في معطى الدالة. fmt.Println("Add + double two numbers: ", func(a, b int) int { return (a + b) * 2 }(10, 2)) goto love love: learnFunctionFactory() // دالة ترجع دالة learnDefer() // التأجيل learnInterfaces() // التعامل مع الواجهات } توليد الدوال نستطيع التعامل مع الدوال ككائنات منفصلة، فمثلا من الممكن أن نقوم بإنشاء دالة وتكون القيمة الراجعة منها دالة أخرى. func learnFunctionFactory() { تعدّ الطريقتان التاليتان في طباعة الجملة متماثلتين، إلا أن الطريقة الثانية أوضح ومقروءة أكثر وهي الشائعة. fmt.Println(sentenceFactory("summer")("A beautiful", "day!")) d := sentenceFactory("summer") fmt.Println(d("A beautiful", "day!")) fmt.Println(d("A lazy", "afternoon!")) } المزخرفات Decorators موجودة في بعض لغات البرمجة، وموجودة بنفس المفهوم في لغة Go بحيث نستطيع تمرير معطيات إلى الدوال. func sentenceFactory(mystring string) func(before, after string) string { return func(before, after string) string { return fmt.Sprintf("%s %s %s", before, mystring, after) } } التنفيذ المؤجل نستطيع استخدام خاصية التأجيل في الدوال بحيث ننفّذ إجراءً قبل إعادة القيمة المرجعة، وفي حال كتابة أكثر من إجراء، فإن تنفيذ هذه الإجراءات يكون بطريقة عكسية، كما في وظيفة learnDefer: func learnDefer() (ok bool) { // تُنفَّذ التعليمات المؤجلة قبل أن ترجع الوظيفة النتيجة. defer fmt.Println("deferred statements execute in reverse (LIFO) order.") defer fmt.Println("\nThis line is being printed first because") // يُستخدَم تأجيل التنفيذ عادة لإغلاق ملف بعد فتحه. return true } الواجهات Interfaces نعرّف في ما يلي دالة باسم Stringer تحتوي على دالة واحدة باسم String ؛ ثم نعرّف هيكلا Struct من خانتين نوع int باسم x وy. type Stringer interface { String() string } type pair struct { x, y int } نعرّف في ما يلي دالة String على النوع pair، ليصبح pair تطبيقا Implementation للواجهة Stringer. يُسمى المتغيّرp أدناه بالمُستقبل. لاحظ كيفية الوصول لحقول الهيكل pair وذلك باستخدام اسم الهيكل متبوعا بنقطة ثم اسم الحقل. func (p pair) String() string { return fmt.Sprintf("(%d, %d)", p.x, p.y) } تُستخدَم الأقواس الهلالية لإنشاء عنصر من الهياكل Structs. نستخدم التعريف القصير (باستخدام := ) في المثال أدناه لإنشاء متغير باسم p وتحديد نوعه بالهيكل pair . func learnInterfaces() { p := pair{3, 4} // نستدعي الدالة String الخاصة بالنوع pair fmt.Println(p.String()) // نُعرف متغيرًا باسم i من نوع الواجهة المعرفة سابقا Stringer var i Stringer // هذه المساواة صحيحة، لأن pair تُطبق Stringer i = p /* نستدعي الدالة String الخاصة بالمتغير i من نوع Stringer ونحصُل على نفس النتيجة السابقة */ fmt.Println(i.String()) /* عند تمرير المتغيرات السابقة مباشرةإلى دوال الحزمة fmt الخاصة بالطباعة والإخراج، فإن هذه الدوال تستدعي الدالة String لطباعة التمثيل الخاص بالمتغير. */ // يعطي السطران التاليان نفس النتيجة السابقة للطباعة fmt.Println(p) fmt.Println(i) learnVariadicParams("great", "learning", "here!") } المدخلات المتعددة من الممكن أن نمرر معطيات متغيرة العدد للدوال. func learnVariadicParams(myStrings ...interface{}) { /* تمرّ جملة التكرار التالية على عناصر المعطيات المدخلة للدالة. التسطير السفلي هنا نعني به تجاهل المؤشر الخاص بالعنصر الذي نمر عليه. */ for _, param := range myStrings { fmt.Println("param:", param) } /* هنا نمرّر مدخلات الدالة ذات العدد المتغير كمعامل لدالة أخرى (للدالة Sprintln) */ fmt.Println("params:", fmt.Sprintln(myStrings...)) learnErrorHandling() } معالجة الأخطاء Errors Handling تُستخدَم الكلمة المفتاحية “ok,” لمعرفة صحة عبارة من عدمها. في حال حدوث خطأ فيمكننا استخدام err لمعرفة تفاصيل أكثر عن الخطأ. func learnErrorHandling() { m := map[int]string{3: "three", 4: "four"} if x, ok := m[1]; !ok { // ok هنا ستكون false لأن رقم 1 غير موجود في الخريطة m fmt.Println("no one there") } else { // x ستكون القيمة الموجودة في map fmt.Print(x) } /* هنا نحاول أن نقوم بعمل تحويل لقيمة نصية إلى عدد مما سينتج عنه خطأ, ونقوم بطباعة تفاصيل الخطأ في حالة أن err ليست nil */ if _, err := strconv.Atoi("non-int"); err != nil { fmt.Println(err) } learnConcurrency() } // المعطى c هنا من نوع قناة، وهو كائن لتأمين الاتصالات المتزامنة func inc(i int, c chan int) { // عندما يظهر عنصر من نوع قناة على الشمال، فإن العملية <- تعني إرسال c <- i + 1 } التنفيذ المتزامن Concurrency نستخدم الدالة السابقة لعمل إضافة عددية على بعض الأرقام بالتزامن. نستخدم make كما فعلنا في بداية المقال لإنشاء متغير دون تحديد قيمة له. func learnConcurrency() { // هنا نقوم بإنشاء متغير من نوع قناة وباسم c c := make(chan int) /* نبدأ بإنشاء ثلاثة دوال متزامنة للغة Go. الأعداد سيتم الزيادة عليهابالتزامن (وبالتوازي في حال كان الجهاز مُهيئاً لذلك). */ // كافة الإرسالات ستتجه لنفس القناة // كلمة go هنا تعني بدء دالة أو وظيفة جديدة go inc(0, c) go inc(10, c) go inc(-805, c) // ثم نقوم بعمل ثلاثة قراءات من نفس القناة وطباعة النتائج. /* لاحظ أنه لا يوجد تحديد ترتيب لوصول القراءات من القناة، ولاحظ أيضا أنه عند ظهور القناة على يمين العملية <- فهذا يعني أننا نقوم بقراءة واستقبال من القناة. */ fmt.Println(<-c, <-c, <-c) // قناة جديدة تحتوي على نص cs := make(chan string) // قناة تحتوي على قنوات نصية ccs := make(chan chan string) // إرسال قيمة 84 إلى القناة c go func() { c <- 84 }() // إرسال كلمة wordy للقناة cs go func() { cs <- "wordy" }() /* جملة Select تشبه جملة switch ولكنها في كل حالة تحتوي على عملية خاصة بقناة جاهزة للتواصل معها. */ select { // القيمة المُستلمة من القناة من الممكن أن تُحفظ في متغير. case i := <-c: fmt.Printf("it's a %T", i) case <-cs: fmt.Println("it's a string") // قناة فارغة ولكنها جاهزة للتواصل case <-ccs: fmt.Println("didn't happen.") } // برمجة الويب learnWebProgramming() } الويب نستطيع بدء خادوم ويب باستخدام دالة واحدة من حزمة http. نمرّر في المعامل الأول للدالة ListenAndServe عنوان TCP للاستماع له، والمعامل الثاني واجهة عبارة عن معالج http. func learnWebProgramming() { go func() { err := http.ListenAndServe(":8080", pair{}) // نطبع الأخطاء في حال وجودها. fmt.Println(err) }() requestServer() } // اجعل pair معالج http وذلك بواسطة تطبيق دالته الوحيدة المسماة ServeHTTP func (p pair) ServeHTTP(w http.ResponseWriter, r *http.Request) { // تتبع الدالة Write للحزمة http.ResponseWriter ونستخدمها لإرجاع رد لطلب http w.Write([]byte("You learned Go in Y minutes!")) } func requestServer() { resp, err := http.Get("http://localhost:8080") fmt.Println(err) defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) fmt.Printf("\nWebserver said: `%s`", string(body)) } ترجمة – بتصرّف – للمقال Learn X in Y minutes Where X=Go.