محمد بغات
الأعضاء-
المساهمات
177 -
تاريخ الانضمام
-
تاريخ آخر زيارة
-
عدد الأيام التي تصدر بها
6
نوع المحتوى
ريادة الأعمال
البرمجة
التصميم
DevOps
التسويق والمبيعات
العمل الحر
البرامج والتطبيقات
آخر التحديثات
قصص نجاح
أسئلة وأجوبة
كتب
دورات
كل منشورات العضو محمد بغات
-
سنتحدث في هذه المقالة عن كيفية استخدام العبارتين GROUP BY و ORDER BY لأجل تجميع نتائج الاستعلامات في SQL وترتيبها. التجميع عبر GROUP BY يمكن تجميع نتائج استعلام SELECT حسب عمود واحد أو أكثر باستخدام عبارة GROUP BY والتي تُجمّع كل النتائج التي لها نفس القيمة في الأعمدة المُجمَّعة (grouped columns). وينتج عنها جدول من النتائج الجزئية، بدلًا من إرجاع نتيجة واحدة. يمكن استخدام GROUP BY مع دوال التجميع (aggregation functions) لتحديد كيفية تجميع الأعمدة باستخدام العبارة HAVING. مثال على استخدام GROUP BY تشبه GROUP BY عبارة for each المُستخدمة في الكثير من لغات البرمجة. إليك الاستعلام التالي: SELECT EmpID, SUM (MonthlySalary) FROM Employee GROUP BY EmpID في الشيفرة أعلاه، نريد الحصول على مجموع الحقل MonthlySalary لكل قيم EmpID، مثلًا، في الجدول التالي: +-----+----------------------+ |EmpID|MonthlySalary| +-----+----------------------+ |1 |200 | +-----+----------------------+ |2 |300 | +-----+----------------------+ سنحصل على النتيجة التالية: +-+---+ |1|200| +-+---+ |2|300| +-+---+ لا يبدو أنّ Sum تفعل أيّ شيء، وذلك لأنّ مجموع عدد ما يساوي العدد نفسه. إليك الآن الجدول التالي: +-----+---------------------+ |EmpID|MonthlySalary| +-----+---------------------+ |1 |200 | +-----+---------------------+ |1 |300 | +-----+---------------------+ |2 |300 | +-----+---------------------+ سنحصل بتطبيق الاستعلام نفسه على النتيجة التالية: +-+---+ |1|500| +-+---+ |2|300| +-+---+ ترشيح نتائج GROUP BY باستخدام عبارة HAVING ترشِّح عبارة HAVING نتائج GROUP BY. سنستخدم في الأمثلة التالية قاعدة البيانات Library المُعرّفة في الفصل الأول. أمثلة إعادة جميع المؤلفين الذين كتبوا أكثر من كتاب (مثال حي). SELECT a.Id, a.Name, COUNT(*) BooksWritten FROM BooksAuthors ba INNER JOIN Authors a ON a.id = ba.authorid GROUP BY a.Id, a.Name HAVING COUNT(*) > 1 -- HAVING BooksWritten > 1 يكافئ ; إعادة جميع الكتب التي أُلِّفت من قبل ثلاثة مؤلفين أو أكثر (مثال حي). SELECT b.Id, b.Title, COUNT(*) NumberOfAuthors FROM BooksAuthors ba INNER JOIN Books b ON b.id = ba.bookid GROUP BY b.Id, b.Title HAVING COUNT(*) > 3 -- HAVING NumberOfAuthors > 3 يكافئ ; استخدام GROUP BY لحساب عدد الصفوف لكل مدخل فريد في عمود معيّن لنفترض أننا نريد عدّ أو حساب مجاميع فرعية لقيمة معينة في عمود. إليك الجدول التالي: table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } Name GreatHouseAllegience Arya Stark Cercei Lannister Myrcella Lannister Yara Greyjoy Catelyn Stark Sansa Stark في حال عدم استخدام العبارة GROUP BY، ستعيد COUNT إجمالي عدد الصفوف: SELECT Count(*) Number_of_Westerosians FROM Westerosians سنحصل على الخرج الناتج: Number_of_Westerosians 6 في حال استخدام GROUP BY، يمكن عدّ المستخدمين لكل قيمة في عمود معين كما يوضح المثال التالي: SELECT GreatHouseAllegience House, Count(*) Number_of_Westerosians FROM Westerosians GROUP BY GreatHouseAllegience الخرج الناتج: House Number_of_Westerosians Stark 3 Greyjoy 1 Lannister 2 من الشائع الجمع بين العبارتين GROUP BY و ORDER BY لترتيب النتائج تصاعديا أو تنازليًا: SELECT GreatHouseAllegience House, Count(*) Number_of_Westerosians FROM Westerosians GROUP BY GreatHouseAllegience ORDER BY Number_of_Westerosians Desc الخرج الناتج: House Number_of_Westerosians Stark 3 Lannister 2 Greyjoy 1 تجميع ROLAP (استخراج البيانات) - ROLAP aggregation يوفر معيار SQL معاملين تجميعيَين (aggregate operators) إضافيين. تُستخدم القيمة ALL للكناية عن جميع القيم التي يمكن أن تأخذها السمة (attribute). المُعاملان هما: with data cube: يوفّر كل التوليفات الممكنة لسمات وسيط العبارة (argument attributes of the clause). with roll up: يوفر المجموع الناتج عن اعتبار السمات مُرتّبة من اليسار إلى اليمين مع مقارنتها بكيفية إدراجها في وسيط العبارة (argument of the clause). تدعم إصدارات SQL القياسية التالية هذه الميزات: 1999 - 2003 - 2006 - 2008 - 2011. أمثلة إليك الجدول التالي: Food Brand Total_amount Pasta Brand1 100 Pasta Brand2 250 Pizza Brand2 300 استخدام عبارة cube: select Food,Brand,Total_amount from Table group by Food,Brand,Total_amount with cube الخرج الناتج: Food Brand Total_amount Pasta Brand1 100 Pasta Brand2 250 Pasta ALL 350 Pizza Brand2 300 Pizza ALL 300 ALL Brand1 100 ALL Brand2 550 ALL ALL 650 استخدام roll up: select Food,Brand,Total_amount from Table group by Food,Brand,Total_amount with roll up الخرج الناتج: Food Brand Total_amount Pasta Brand1 100 Pasta Brand2 250 Pizza Brand2 300 Pasta ALL 350 Pizza ALL 300 ALL ALL 650 الترتيب عبر ORDER BY الترتيب حسب رقم العمود (بدلاً من اسمه) يمكنك استخدام رقم العمود (يبدأ العمود الموجود في أقصى اليسار من الرقم "1") للإشارة إلى العمود الذي يستند الترتيب إليه. إيجابيات: هذا الخيار مناسب في حال كانت هناك إمكانية لتغيير أسماء الأعمدة لاحقًا، لأنّه سيجنّبك كسر الشيفرة. سلبيات: سيُضعف استخدام رقم العمود بدل اسمه مقروئية الاستعلام (وازن مثلا بين ORDER BY Reputation و 'ORDER BY 14'.). يرتب الاستعلام التالي النتيجة حسب المعلومات الموجودة في العمود رقم 3 بدلاً من الاعتماد اسم العمود Reputation. SELECT DisplayName, JoinDate, Reputation FROM Users ORDER BY 3 الخرج الناتج: DisplayName JoinDate Reputation Community 2008-09-15 1 Jarrod Dixon 2008-10-03 11739 Geoff Dalgas 2008-10-03 12567 Joel Spolsky 2008-09-16 25784 Jeff Atwood 2008-09-16 37628 استخدام ORDER BY مع TOP لإعادة أعلى س صفًّا بناءً على قيمة العمود في هذا المثال، يمكنك استخدام GROUP BY و TOP لتحديد الصفوف المُعادة وترتيبها. لنفترض أنّك تريد الحصول على أفضل 5 مستخدمين من حيث السمعة في موقع متخصص في الأسئلة والأجوبة. بدون ORDER BY يعيد هذا الاستعلام أعلى 5 صفوف مرتبة حسب الإعداد الافتراضي، والذي هو Id في هذه الحالة، أي العمود الأول في الجدول (رغم أنّه لن يظهر في النتائج). SELECT TOP 5 DisplayName, Reputation FROM Users الخرج الناتج: DisplayName Reputation Community 1 Geoff Dalgas 12567 Jarrod Dixon 11739 Jeff Atwood 37628 Joel Spolsky 25784 استخدام ORDER BY سنستخدم ORDER BY في المثال التالي: SELECT TOP 5 DisplayName, Reputation FROM Users ORDER BY Reputation desc الخرج الناتج: DisplayName Reputation JonSkeet 865023 Darin Dimitrov 661741 BalusC 650237 Hans Passant 625870 Marc Gravell 601636 ملاحظات تستخدم بعض إصدارات SQL (مثل MySQL) العبارة LIMIT في نهاية SELECT ، بدلًا من استخدام TOP في البداية كما هو موضّح في المثال التالي: SELECT DisplayName, Reputation FROM Users ORDER BY Reputation DESC LIMIT 5 الترتيب المخصص لترتيب الجدول Employee حسب القسم department، يمكنك استخدام التعليمة ORDER BY Department. أمّا إن أردت ترتيبه ترتيبًا غير أبجدي، فيجب عليك تحويل قيم Department إلى قيم أخرى قابلة للترتيب؛ يمكن فعل ذلك باستخدام عبارة CASE: Name Department Hasan IT Yusuf HR Hillary HR Joe IT Merry HR Ken Accountant في المثال التالي: SELECT * FROM Employee ORDER BY CASE Department WHEN 'HR' THEN 1 WHEN 'Accountant' THEN 2 ELSE 3 END; سنحصل على الخرج: Name Department Yusuf HR Hillary HR Merry HR Ken Accountant Hasan IT Joe IT الترتيب بالكنى بسبب طريقة معالجة الاستعلامات المنطقية، يمكن ترتيب نتائج الاستعلامات حسب الكُنى (Order by Alias). SELECT DisplayName, JoinDate as jd, Reputation as rep FROM Users ORDER BY jd, rep كما يمكن استخدام الترتيب النسبي (relative order) للأعمدة -أي الترتيب حسب رقم العمود- في عبارة الاختيار select. سنعود إلى المثال أعلاه، ولكن بدلاً من استخدام الكُنية، سنستخدم الترتيب النسبي. SELECT DisplayName, JoinDate as jd, Reputation as rep FROM Users ORDER BY 2, 3 الترتيب حسب عدة أعمدة في المثال التالي: SELECT DisplayName, JoinDate, Reputation FROM Users ORDER BY JoinDate, Reputation سيكون الخرج: DisplayName JoinDate Reputation Community 2008-09-15 1 Jeff Atwood 2008-09-16 25784 Joel Spolsky 2008-09-16 37628 Jarrod Dixon 2008-10-03 11739 Geoff Dalgas 2008-10-03 12567 المُعاملان المنطِقيان AND و OR AND و OR معاملان منطقيان يُستخدمان لبناء الشروط المنطقية. إليك الجدول التالي: Name Age City Bob 10 Paris Mat 20 Berlin Mary 24 Prague في المثال التالي: select Name from table where Age>10 AND City='Prague' سنحصل على الخرج: Name Mary وفي هذا المثال: select Name from table where Age=10 OR City='Prague' سنحصل على الخرج: Name Bob Mary ترجمة -وبتصرّف- للفصول 7 و8 و9 من الكتاب SQL Notes for Professionals اقرأ أيضًا: المقال التالي: تنفيذ تعليمات شرطية عبر CASE في SQL المقال السابق: جلب الاستعلامات عبر SELECT في SQL النسخة العربية الكاملة لكتاب ملاحظات للعاملين بلغة SQL 1.0.0
-
تُستخدَم العبارة SELECT في معظم استعلامات SQL. وتتحكم في تحديد النتائج التي يجب أن يعيدها الاستعلام، وتُستخدم في العادة مع العبارة FROM، والتي تحدد الجزء (أو الأجزاء) من قاعدة البيانات المُستعلَم عنها. استخدام حرف البدل * لاختيار جميع الأعمدة إليك قاعدة البيانات التالية المؤلفة من الجدولين التاليين: جدول الموظفين Employees: table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } Id FName LName DeptId 1 James Smith 3 2 John Johnson 4 جدول الأقسام Departments: Id Name 1 Sales 2 Marketing 3 Finance 4 IT عبارة select بسيطة يمثل الرمز * حرفَ البدل، ويُستخدم لاختيار جميع الأعمدة المتاحة في الجدول. عند استخدامه بديلًا عن الأسماء الصريحة للأعمدة، فإنه يعيد جميع الأعمدة في جميع الجداول التي يحددها الاستعلام FROM. ينطبق هذا الأمر على جميع الجداول التي يصل إليها الاستعلام عبر عبارات JOIN. إليك الاستعلام التالي: SELECT * FROM Employees سيعيد الاستعلام أعلاه جميع الحقول من جميع صفوف جدول Employees: Id FName LName DeptId 1 James Smith 3 2 John Johnson 4 الصياغة النقطية Dot notation لاختيار كل القيم من جدول محدّد، يمكن تطبيق حرف البدل على الجدول باستخدام الصياغة النقطية. إليك الاستعلام التالي: SELECT Employees.*, Departments.Name FROM Employees JOIN Departments ON Departments.Id = Employees.DeptId سيعيد هذا المثال مجموعة بيانات تحتوي كافة الحقول الموجودة في الجدول Employee، متبوعةً بـالحقل Name من الجدول Departments: Id FName LName DeptId Name 1 James Smith 3 Finance 2 John Johnson 4 IT تنبيهات يوصى عمومًا بتجنّب استخدام * في شيفرة الإنتاج، إذ يمكن أن تتسبّب في مجموعة من المشاكل، منها: حمل الدخل والخرج الزائد (Excess IO)، والحمل الزائد على الشبكة، واستنزاف الذاكرة، وغيرها، وذلك بسبب أنّ محرك قاعدة البيانات سيقرأ بيانات غير مطلوبة. وينقلها إلى شيفرة الواجهة الأمامية. وقد يصبح الأمر أسوأ إن كانت هناك حقول كبيرة، مثل تلك المستخدمة لتخزين الملاحظات الطويلة أو الملفات المرفقة. زيادة الضغط على الدخل والخرج إذا احتاجت قاعدة البيانات إلى تخزين النتائج الداخلية على القرص كجزء من عملية معالجة استعلامات أكثر تعقيدًا من العبارة البسيطة SELECT <columns> FROM <table> معالجة زائدة (و / أو مزيد من عمليات الدخل والخرج IO) إذا كانت هناك أعمدة غير ضرورية من نوع: الأعمدة المحسوبة (computed columns) في قواعد البيانات التي تدعم هذا النوع من الأعمدة في حالة الاختيار من معرض (view)، فيشمل ذلك الأعمدة من جدول / معرض معيّن، والتي كان من الممكن أن يُحسّنها "مُحسِّن الاستعلام" (query optimiser) احتمال حدوث أخطاء غير متوقعة عند إضافة أعمدة إلى الجداول والمعارض لاحقًا، مما قد يؤدي إلى أسماء أعمدة غير واضحة. على سبيل المثال: SELECT * FROM orders JOIN people ON people.id = orders.personid ORDER BY displayname في حال إضافة عمود يُسمى displayname إلى جدول الطلبات - orders - قصد السماح للمستخدمين بتقديم طلباتهم تحت أسماء من اختيارهم ليسهل عليهم الرجوع إليها مستقبلًا، فسيظهر اسم العمود مرتين في المخرجات، ونتيجة لذلك قد لا تكون عبارة ORDER BY واضحة، وهو ما قد يتسبب في خطأ ("ambiguous column name" في إصدارات MS SQL Server الحديثة). في المثال أعلاه، قد يعرض التطبيق اسم الطلب مكان اسم الشخص بالخطأ، نتيجة أنّ العمود الجديد سيُعاد أولًا. دورة علوم الحاسوب دورة تدريبية متكاملة تضعك على بوابة الاحتراف في تعلم أساسيات البرمجة وعلوم الحاسوب اشترك الآن متى يمكنك استخدام حرف البدل *؟ يُفضل عمومًا تجنّب استخدام * في شيفرة الإنتاج، لكن لا مشكلة في استخدامها كاختصار عند تنفيذ الاستعلامات اليدوية في قاعدة البيانات عند العمل على النماذج الأولية. كما قد يفرض عليك تصميم التطبيق أحيانًا استخدام حرف البدل (في مثل هذه الظروف، يُفضل استخدام tablealias.* بدلًا من * حيثما أمكن ذلك). عند استخدام EXISTS، كما في: SELECT A.col1, A.Col2 FROM A WHERE EXISTS (SELECT * FROM B where A.ID = B.A_ID) فذلك لن يعيد أيّ بيانات من B. وبالتالي لن يكون الضمّ (join) ضروريًا، كما أنّ محرّك (engine) قاعدة البيانات يعلم أنه لن تُعاد أيّ قيمة من B، وبالتالي لن يتأثّر الأداء جرّاء استخدام *. من جهة أخرى، لا بأس في استخدام COUNT(*)، لأنها لا تُعيد أيًا من الأعمدة فعليًا، إذ تحتاج فقط إلى قراءة ومعالجة الأعمدة المستخدمة في التصفية. استخدام SELECT مع كُنى الأعمدة تُستخدم الأسماء المستعارة أو الكُنى - aliases - لاختصار أسماء الأعمدة أو جعلها ذات معنى. ويساعد ذلك على اختزال الشيفرة وجعلها أكثر مقروئية جرّاء تجنّب أسماء الجداول الطويلة وتمييز الأعمدة (على سبيل المثال، قد يكون هناك مُعرِّفان في الجدول، بيْد أنّ واحدًا منهما فقط سيستخدم في العبارة)، بالإضافة إلى تسهيل استخدام أسماء وصفية أطول في قاعدة بياناتك مع إبقاء الاستعلامات الجارية عليها مختصرة. علاوة على ذلك، قد تكون الكنى إجبارية في بعض الأحيان (على سبيل المثال في المعارض) من أجل تسمية المخرجات المحسوبة (computed outputs). جميع إصدارات SQL يمكن إنشاء الكنى في جميع إصدارات SQL باستخدام علامات الاقتباس المزدوجة ("). SELECT FName AS "First Name", MName AS "Middle Name", LName AS "Last Name" FROM Employees إصدارات خاصة من SQL يمكنك استخدام علامات الاقتباس المفردة (')، وعلامات الاقتباس المزدوجة (") والأقواس المربّعة ([]) لإنشاء كُنية في Microsoft SQL Server. SELECT FName AS "First Name", MName AS 'Middle Name', LName AS [Last Name] FROM Employees سينتج عن الشيفرة أعلاه الخرج: First Name Middle Name Last Name James John Smith John James Johnson Michael Marcus Williams ستعيد هذه العبارة عمودين FName و LName يحملان الاسم المحدّد (الكنية). وقد تمّ ذلك باستخدام العامل AS متبوعًا بالكنية، أو بكتابة الكنية مباشرةً بعد اسم العمود. ستكون للاستعلام التالي نفس النتيجة الواردة أعلاه. SELECT FName "First Name", MName "Middle Name", LName "Last Name" FROM Employees First Name Middle Name Last Name James John Smith John James Johnson Michael Marcus Williams كما تلاحظ، فإنّ النسخة الصريحة (أي استخدام العامل AS) أفضل، لأنّها أكثر مقروئية. إذا كانت الكنية مؤلفة من كلمة واحدة، ولم تكن كلمة محجوزة، فيمكن كتابتها بدون علامات الاقتباس المفردة أو المزدوجة أو الأقواس المربعة: SELECT FName AS FirstName, LName AS LastName FROM Employees FirstName LastName James Smith John Johnson Michael Williams هناك شكل إضافي متاح في MS SQL Server، وهو <alias> = <column-or-calculation>، إليك المثال التالي: SELECT FullName = FirstName + ' ' + LastName, Addr1 = FullStreetAddress, Addr2 = TownName FROM CustomerDetails والذي يكافئ: SELECT FirstName + ' ' + LastName As FullName FullStreetAddress As Addr1, TownName As Addr2 FROM CustomerDetails وسيؤدي كلاهما إلى النتيجة: FullName Addr1 Addr2 James Smith 123 AnyStreet TownVille John Johnson 668 MyRoad Anytown Michael Williams 999 High End Dr Williamsburgh يرى البعض أنّ استخدام = بدلاً من As أفضل من ناحية المقروئية، فيما يوصي آخرون بتجنّب استخدامها لأنّها ليست قياسية، وبالتالي لا تدعمها جميع قواعد البيانات، كما قد يتداخل استخدامها مع الاستخدامات الأخرى للمحرف =. جميع إصدارات SQL إن كنت بحاجة إلى استخدام الكلمات المحجوزة، فيمكنك استخدام الأقواس المربعة أو علامات الاقتباس لتهريب الكُنية (escape): SELECT FName as "SELECT", MName as "FROM", LName as "WHERE" FROM Employees إصدارات خاصة من SQL بالمثل، يمكنك تهريب الكلمات المفتاحية في MSSQL عبر عدة مقاربات: SELECT FName AS "SELECT", MName AS 'FROM', LName AS [WHERE] FROM Employees SELECT FROM WHERE James John Smith John James Johnson Michael Marcus Williams يمكن أيضًا استخدام كنى الأعمدة في أيّ من العبارات النهائية في نفس الاستعلام، من قبيل العبارة ORDER BY: SELECT FName AS FirstName, LName AS LastName FROM Employees ORDER BY LastName DESC بالمقابل، لا يجوز استخدام الشيفرة التالية لإنشاء كنية تساوي الكلمات المحجوزة (SELECT و FROM)، لأنّ ذلك سيتسبّب في العديد من الأخطاء في مرحلة التنفيذ. SELECT FName AS SELECT, LName AS FROM FROM Employees ORDER BY LastName DESC اختيار أعمدة فردية SELECT PhoneNumber, Email, PreferredContact FROM Customers ستعيد العبارة أعلاه الأعمدة PhoneNumber و Email و PreferredContact من جميع صفوف الجدول Customers. كا ستُعاد الأعمدة بالتسلسل الذي تظهر به في عبارة SELECT. وها هي النتيجة: PhoneNumber Email PreferredContact 3347927472 william.jones@example.com PHONE 2137921892 dmiller@example.net EMAIL NULL richard0123@example.com EMAIL إذا تمّ ربط عدة جداول معًا، فيمكنك اختيار الأعمدة من جداول معيّنة عن طريق وضع اسم الجدول قبل اسم العمود على النحو [table_name].[column_name] التالي: SELECT Customers.PhoneNumber, Customers.Email, Customers.PreferredContact, Orders.Id AS OrderId FROM Customers LEFT JOIN Orders ON Orders.CustomerId = Customers.Id تعني العبارة * AS OrderId أنّ الحقل Id من الجدول Orders سيُعاد كعمود يُسمى OrderId. راجع قسم الاختيار عبر الكنى أسفله لمزيد من المعلومات. لتجنب استخدام أسماء الجداول الطويلة، يمكنك تكنية الجدول، فهذا سيخفف من صعوبات كتابة أسماء الجداول الطويلة مع كل حقل تختاره في عمليات الضمّ (joins). وعند استخدام الضمّ الذاتي - self join - (أي ضم نسختين من الجدول نفسه)، فعليك تكنِية الجداول لتمييزها عن بعضها. يمكن كتابة كنية الجدول على النحو التالي: Customers c أو Customers AS c، إذ تتصرّف c هنا ككُنية لـ Customers، يمكننا الآن أن نختار مثلا الحقل Email عبر الصيغة: c.Email. SELECT c.PhoneNumber, c.Email, c.PreferredContact, o.Id AS OrderId FROM Customers c LEFT JOIN Orders o ON o.CustomerId = c.Id اختيار عدد معيّن من السجلات عرّف معيار SQL 2008 العبارة FETCH FIRST كطريقة لاختيار عدد السجلات المُعادة. SELECT Id, ProductName, UnitPrice, Package FROM Product ORDER BY UnitPrice DESC FETCH FIRST 10 ROWS ONLY لم يُدعم هذا المعيار إلا في الإصدارات الأخيرة من بعض أنظمة إدارة قواعد البيانات (RDMSs). فيما توفّر الأنظمة الأخرى صياغة غير قياسية. أيضًا يدعم Progress OpenEdge 11.x الصياغة FETCH FIRST <n> ROWS ONLY. بالإضافة إلى ذلك، تسمح إضافة العبارة OFFSET <m> ROWS قبل FETCH FIRST <n> ROWS ONLY بتخطي عدد من الصفوف قبل جلب الصفوف. SELECT Id, ProductName, UnitPrice, Package FROM Product ORDER BY UnitPrice DESC OFFSET 5 ROWS FETCH FIRST 10 ROWS ONLY الاستعلام التالي مدعوم في SQL Server و MS Access: SELECT TOP 10 Id, ProductName, UnitPrice, Package FROM Product ORDER BY UnitPrice DESC لفعل الشيء نفسه في MySQL أو PostgreSQL، يجب استخدام الكلمة المفتاحية LIMIT: SELECT Id, ProductName, UnitPrice, Package FROM Product ORDER BY UnitPrice DESC LIMIT 10 وفي Oracle، ينبغي استخدام ROWNUM: SELECT Id, ProductName, UnitPrice, Package FROM Product WHERE ROWNUM <= 10 ORDER BY UnitPrice DESC وينتج عن هذا 10 سجلات. Id ProductName UnitPrice Package 38 Côte de Blaye 263.50 12 - 75 cl bottles 29 Thüringer Rostbratwurst 123.79 50 bags x 30 sausgs. 9 Mishi Kobe Niku 97.00 18 - 500 g pkgs. 20 Sir Rodney's Marmalade 81.00 30 gift boxes 18 Carnarvon Tigers 62.50 16 kg pkg. 59 Raclette Courdavault 55.00 5 kg pkg. 51 Manjimup Dried Apples 53.00 50 - 300 g pkgs. 62 Tarte au sucre 49.30 48 pies 43 Ipoh Coffee 46.00 16 - 500 g tins 28 Rössle Sauerkraut 45.60 25 - 825 g cans الفروق بين الأنظمة جدير بالذكر أنّ الكلمة المفتاحية TOP في نظام Microsoft SQL تعمل بعد عبارة WHERE، وتعيد عددًا محددًا من النتائج في حال كانت تلك النتائج متوافرة في أيّ مكان من الجدول، بينما تعمل ROWNUM كجزء من عبارة WHERE، لذا إذا لم تتحقّق الشروط الأخرى في العدد المحدد من الصفوف في بداية الجدول، فلن تحصل على أيّ نتيجة، حتى لو كان من الممكن العثور على نتائج أخرى. الاختيار الشرطي يمكن استخدام العبارتين SELECT و WHERE معًا على النحو التالي: SELECT column1, column2, columnN FROM table_name WHERE [condition] يمكن أن يكون الشرط [condition] أيّ تعبير صالح في SQL يستخدم المعاملات المنطقية: > و < و = و >= و <= و LIKE و NOT و IN و BETWEEN وغيرها. تُعيد العبارة التالية جميع الأعمدة من الجدول "Cars" ذات الحالة "READY": SELECT * FROM Cars WHERE status = 'READY' الاختيار باستخدام CASE تُستخدم عبارة CASE لتطبيق عملية معينة على النتائج مباشرة. SELECT CASE WHEN Col1 < 50 THEN 'under' ELSE 'over' END threshold FROM TableName يمكن أيضًا سلسَلَة CASE على النحو التالي: SELECT CASE WHEN Col1 < 50 THEN 'under' WHEN Col1 > 50 AND Col1 <100 THEN 'between' ELSE 'over' END threshold FROM TableName يمكن أيضا استخدام عبارة CASE داخل أخرى: SELECT CASE WHEN Col1 < 50 THEN 'under' ELSE CASE WHEN Col1 > 50 AND Col1 <100 THEN Col1 ELSE 'over' END END threshold FROM TableName اختيار الأعمدة التي تحمل أسماء لكلمات مفتاحية محجوزة عندما يتطابق اسم العمود مع كلمة مفتاحية محجوزة، ينص معيار SQL على ضرورة أن تحُاط بعلامات اقتباس مزدوجة: SELECT "ORDER", ID FROM ORDERS لاحظ أنّ هذا يجعل اسم العمود حساسًا لحالة الأحرف. بعض نظم إدارة قواعد البيانات لديها طرق خاصة لاقتباس الأسماء. على سبيل المثال، يستخدم SQL Server أقواس مربعة: SELECT [Order], ID FROM ORDERS بينما تستخدم MySQL (و MariaDB) افتراضيا علامة اقتباس مائلة (backtick): SELECT `Order`, id FROM orders الاختيار باستخدام كُنى الجداول إليك المثال التالي: SELECT e.Fname, e.LName FROM Employees e أُعطِي جدول الموظفين Employees الكنية "e" مباشرةً بعد اسمه. يساعد ذلك في إزالة الغموض في حال احتوت العديد من الجداول حقولًا تحمل نفس الاسم، وكنت تحتاج إلى تحديد الجدول الذي تريد استخلاص البيانات منه. SELECT e.Fname, e.LName, m.Fname AS ManagerFirstName FROM Employees e JOIN Managers m ON e.ManagerId = m.Id لاحظ أنه بمجرد تعريف الكنية، فلن يكون بمقدورك استخدام اسم الجدول الأساسي بعد الآن، لهذا ستطرح الشيفرة التالية خطأً: SELECT e.Fname, Employees.LName, m.Fname AS ManagerFirstName FROM Employees e JOIN Managers m ON e.ManagerId = m.Id تجدر الإشارة إلى أنّ كنى الجداول - أو "متغيرات النطاق" (range variables) إن أردنا التقيد بالتسميات الرسمية - قُدِّمت في لغة SQL لحل مشكلة الأعمدة المكرّرة التي تنجم عن استخدام INNER JOIN. وقد صحّح معيار SQL 1992 هذه الثغرة من خلال إدخال NATURAL JOIN (مطبّقة حاليًا في mySQL و PostgreSQL و Oracle ولكن ليس في SQL Server بعدُ)، وقد ضمن ذلك ألا تحدث مشكلة الأسماء المكررة للأعمدة. في المثال أعلاه، تُضمّ الجداول في الأعمدة ذات الأسماء المختلفة (Id و ManagerId) ولكن ليس في الأعمدة التي تحمل الاسم نفسه (LName، FName)، هذا الأمر يتطلّب إعادة تسمية الأعمدة قبل عملية الضمّ: SELECT Fname, LName, ManagerFirstName FROM Employees NATURAL JOIN ( SELECT Id AS ManagerId, Fname AS ManagerFirstName FROM Managers ) m; رغم أنّه يجب الإعلان عن متغير الكنية / النطاق الخاص بالجدول المُعدَّل (أو ستطرح SQL خطأً)، إلا أنّه من غير المنطقي أبدًا استخدامه فعليًا في الاستعلامات. الاختيار بناءً على عدة شروط تُستخدم الكلمة المفتاحية AND لإضافة المزيد من الشروط إلى الاستعلام. Name Age Gender Sam 18 M John 21 M Bob 22 M Mary 23 F إليك المثال التالي: SELECT name FROM persons WHERE gender = 'M' AND age > 20; سينتج عن هذا: Name John Bob يمكن أيضًا استخدام المعامل المنطقي OR على النحو التالي: SELECT name FROM persons WHERE gender = 'M' OR age < 20; سينتج عن هذا: Name Sam John Bob يمكن دمج هذه الكلمات المفتاحية لبناء شروط أكثر تعقيدًا: SELECT name FROM persons WHERE (gender = 'M' AND age < 20) OR (gender = 'F' AND age > 20); سينتج عن هذا: Name Sam Mary الاختيار دون قفل الجدول في بعض الأحيان، عندما تُستخدم الجداول لأجل القراءة أساسًا (أو حصرًا)، فإنّ الفهرسة (indexing) لا تكون ضرورية، لذا لن تكون هناك حاجة إلى قفل (LOCK) الجدول أثناء الاختيار. هذه بعض الأمثلة على ذلك: SQL Server: SELECT * FROM TableName WITH (nolock) MySQL SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT * FROM TableName; SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; Oracle SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SELECT * FROM TableName; DB2 SELECT * FROM TableName WITH UR; العبارة UR كناية عن "قراءة غير ملتزم بها" (uncommitted read). في حال استخدامها على جدول أثناء تعديل أحد حقوله، فقد تحدث نتائج غير متوقعة. الاختيار باستخدام الدوال التجميعية aggregate functions حساب المتوسط تعيد الدالة التجميعية AVG() متوسط القيم المحددة. SELECT AVG(Salary) FROM Employees يمكن أيضًا استخدام الدوال مع عبارة where على النحو التالي: SELECT AVG(Salary) FROM Employees where DepartmentId = 1 ويمكن أيضًا استخدام الدوال مع العبارة GROUP BY. مثلًا، إذا تم تصنيف الموظف في عدة أقسام، وأردنا أن نحدّد متوسط الراتب في كل قسم، فيمكننا استخدام الاستعلام التالي. SELECT AVG(Salary) FROM Employees GROUP BY DepartmentId القيمة الصغرى تعيد الدالة التجميعية MIN() الحد الأدنى من القيم المحددة. SELECT MIN(Salary) FROM Employees القيمة الكبرى تعيد الدالة التجميعية MAX() الحد الأقصى للقيم المحددة. SELECT MAX(Salary) FROM Employees التعداد تعيد الدالة التجميعية COUNT() عدد القيم المحددة. SELECT Count(*) FROM Employees يمكن أيضًا دمجها مع العبارة where للحصول على عدد الصفوف التي تفي بشروط محددة. SELECT Count(*) FROM Employees where ManagerId IS NOT NULL يمكن أيضًا اختيار عمود معيّن للحصول على عدد القيم في ذلك العمود. لاحظ أنّ القيم المعدومة NULL لا تُحتسب. Select Count(ManagerId) from Employees يمكن أيضًا دمج Count مع الكلمة المفتاحية DISTINCT. Select Count(DISTINCT DepartmentId) from Employees التجميع تعيد الدالة التجميعية SUM() مجموع القيم المُختارة في جميع الصفوف. SELECT SUM(Salary) FROM Employees الاختيار من بين قيم معيّنة من عمود المثال التالي: SELECT * FROM Cars WHERE status IN ( 'Waiting', 'Working' ) يكافئ: SELECT * FROM Cars WHERE ( status = 'Waiting' OR status = 'Working' ) إذ أنّ value IN ( <value list> ) هي مجرّد اختصار لمعامل OR المنطقي. تطبيق الدوال التجميعية على مجموعات من الصفوف يحسب المثال التالي عدد الصفوف بناءً على قيمة محددة من العمود: SELECT category, COUNT(*) AS item_count FROM item GROUP BY category; ويحسب المثال التالي متوسط الدخل حسب القسم: SELECT department, AVG(income) FROM employees GROUP BY department; الشيء الأساسي هنا هو اختيار الأعمدة المحددة في عبارة GROUP BY حصرًا، أو استخدامها مع الدوال التجميعية. يمكن أيضًا استخدام العبارة WHERE مع GROUP BY، إلّا أنّ WHERE ستصفّي السجلات قبل تجميعها (grouping): SELECT department, AVG(income) FROM employees WHERE department <> 'ACCOUNTING' GROUP BY department; إن أردت تصفية النتائج بعد الانتهاء من التجميع، مثلًا إن أردت ألّا تعرض إلا الأقسام التي يتجاوز معدل دخلها 1000، سيكون عليك استخدام العبارة HAVING. SELECT department, AVG(income) FROM employees WHERE department <> 'ACCOUNTING' GROUP BY department HAVING avg(income) > 1000; الاختيار مع ترتيب النتائج SELECT * FROM Employees ORDER BY LName ستعيد هذه العبارة جميع الأعمدة من الجدول Employees. Id FName LName PhoneNumber 2 John Johnson 2468101214 1 James Smith 1234567890 3 Michael Williams 1357911131 SELECT * FROM Employees ORDER BY LName DESC أو SELECT * FROM Employees ORDER BY LName ASC تغيّر عبارة ASC اتجَاه الترتيب. يمكن أيضا إجراء الترتيب وفق عدّة أعمدة على النحو التالي: SELECT * FROM Employees ORDER BY LName ASC, FName ASC سيرتِّب هذا المثال النتائج حسب الحقل LName أولّا، أمّا بالنسبة للسجلات التي لها نفس قيمة الحقل LName، فسيرتِّبها حسب FName. سينتج عن هذا قوائم مشابهة للقوائم التي تجدها في دفتر الهاتف. إن أردت تجنّب إعادة كتابة اسم العمود في عبارة ORDER BY، يمكنك استخدام رقم العمود بدلاً من اسمه. انتبه إلى أنّ أرقام الأعمدة تبدأ من 1. SELECT Id, FName, LName, PhoneNumber FROM Employees ORDER BY 3 يمكن أيضًا تضمين عبارة CASE في عبارة ORDER BY. SELECT Id, FName, LName, PhoneNumber FROM Employees ORDER BY CASE WHEN LName='Jones' THEN 0 ELSE 1 END ASC سترتّب هذه الشيفرة النتائج بحيث تكون السجلات التي يساوي حقل LName خاصتها القيمة "Jones" في الأعلى. استخدام null لأجل الاختيار إليك المثال التالي: SELECT Name FROM Customers WHERE PhoneNumber IS NULL تختلف صياغة الاختيار باستخدام القيم المعدومة null عن الصياغة العادية، إذ أنّها لا تستخدم معامل التساوي =، وإنّما تستخدم IS NULL أو IS NOT NULL. اختيار قيم غير مكرّرة SELECT DISTINCT ContinentCode FROM Countries; سيعيد هذا الاستعلام جميع القيم الفريدة والمختلفة عن بعضها من العمود ContinentCode في الجدول Countries ContinentCode OC EU AS NA AF هذا مثال حي. اختيار الصفوف من عدة جداول إليك الشيفرة التالية: SELECT * FROM table1, table2 SELECT table1.column1, table1.column2, table2.column1 FROM table1, table2 تُسمّى هذه العملية الجداء المتقاطع (cross product) في SQL، وهي مكافئة للجداء المتقاطع في المجموعات. تعيد هذه العبارات أعمدة مُختارة من عدّة جداول في استعلام واحد، ولا توجد علاقة محددة بين الأعمدة المُعادة من كل جدول. ترجمة -وبتصرّف- للفصل Chapter 6: SELECT من الكتاب SQL Notes for Professionals اقرأ أيضًا: المقال التالي: التجميع والترتيب في SQL المقال السابق: مدخل إلى SQL النسخة العربية الكاملة لكتاب ملاحظات للعاملين بلغة SQL 1.0.0
-
لغة الاستعلامات الهيكلية SQL اختصار إلى Structured Query Language هي لغة برمجة متخصصة في إدارة قواعد البيانات العلائقية RDBMS اختصار إلى Relational database management system. كما تُستخدم اللغات المشتقة من SQL في أنظمة إدارة مجاري البيانات العلائقية RDSMS اختصار إلى Relational Data Stream Management Systems، أو في إدارة قواعد بيانات SQL الحصرية NoSQL. تتألف SQL من ثلاث لغات فرعية أساسية، وهي: لغة تعريف البيانات DDL: تُستعمل لإنشاء وتعديل بنية قاعدة البيانات. لغة معالجة البيانات DML: تستعمل لتنفيذ عمليات قراءة البيانات وإدراجها وتحديثها وحذفها. لغة التحكم في البيانات DCL: تُستعمل للتحكم في الوصول إلى البيانات المخزّنة في قاعدة البيانات. تتألف DML من أربع عمليات أساسية، وهي عمليات الإنشاء (Create) والقراءة (Read) والتحديث (Update) والحذف (Delete)، ويُطلق عليها اختصارًا CRUD، تُنفّذ هذه العمليات عبر التعليمات INSERT و SELECT و UPDATE و DELETE على التوالي. أُضيفت مؤخرًا تعليمة MERGE، والتي تنفّذ العمليات INSERT و UPDATE و DELETE معًا. تُقدَّم العديد من قواعد بيانات SQL على هيئة نُظم عميل / خادم (client/server systems). ويُطلق على هذه الخوادم مصطلح "خادم SQL". أنشأت ميكروسوفت قاعدة بيانات تسمى "SQL Server". ورغم أنّها تُعدّ من لهجات SQL، إلا أنّنا لن نتحدث عنها في هذه السلسلة، إن كنت تريد تعلّمها فيمكنك الرجوع إلى توثيقها. المعرفات identifiers يستعرض هذا القسم موضوع المعرّفات (identifiers)، وتشرح قواعد تسمية الجداول والأعمدة وباقي كائنات قاعدة البيانات. سنحاول أن تغطي الأمثلة الاختلافات بين تقديمات SQL المختلفة. المعرفات غير المقتبسة Unquoted identifiers يمكن أن تحتوي المعرّفات غير المُقتبسة على الحروف (a - z) والأرقام (0 - 9) والشرطة السفلية (_)، وفي جميع الأحوال، ينبغي أن تبدأ بحرف. اعتمادًا على تقديم SQL المُستخدم، و / أو إعدادات قاعدة البيانات، قد يجوز استخدام أحرف أخرى، وبعضها يمكن أن تُستخدم حرفًا أولًا للمعرّف. هذه بعض الأمثلة على الأحرف الجائزة: MS SQL: @ و $ و # وباقي محارف اليونيكود -Unicode - الأخرى، المصدر MySQL: $، المصدر Oracle: $ و # وبافي الأحرف من مجموعة أحرف قاعدة البيانات، المصدر PostgreSQL: $ وباقي أحرف اليونيكود الأخرى، المصدر المعرّفات غير المقتبسة غير حسّاسة لحالة الأحرف عمومًا. بيْد أنّ طريقة التعامل مع حالة الأحرف تختلف بحسب تقديم SQL، مثلًا: MS SQL: تحافظ على الحالة (Case-preserving)، إذ تُحدّد مسألة الحساسية لحالة الأحرف عبر مجموعة أحرف قاعدة البيانات (database character set)، لذا يمكن أن تكون حساسة لحالة الأحرف. MySQL: تحافظ على الحالة، وتعتمد الحساسية على إعدادات قاعدة البيانات ونظام الملفات الأساسي. Oracle: تُحوّل إلى أحرف كبيرة، ثم تُعامل كمعرفّات مقتبسة. PostgreSQL: تُحوّل إلى أحرف صغيرة، ثم تُعامل مثل المعرّفات المقتبسة. SQLite: تحافظ على الحالة. وعدم حساسيتها تقتصر على أحرف ASCII. أنواع البيانات Data Types DECIMAL و NUMERIC يمثل النوعان DECIMAL و NUMERIC أعدادّا عشرية ذات دقة ثابتة، وهما متكافئان وظيفيًا. ويُصاغان على النحو التالي: DECIMAL ( precision [ , scale] ) NUMERIC ( precision [ , scale] ) أمثلة: SELECT CAST(123 AS DECIMAL(5,2)) --returns 123.00 SELECT CAST(12345.12 AS NUMERIC(10,5)) --returns 12345.12000 FLOAT و REAL يمثل نوعًا الأعداد FLOAT و REAL الأعداد التقريبية، ويُستخدمان لتمثيل البيانات الرقمية ذات الفاصلة العائمة (floating point numeric data). SELECT CAST( PI() AS FLOAT) --returns 3.14159265358979 SELECT CAST( PI() AS REAL) --returns 3.141593 الأعداد الصحيحة Integers يمثل النوع Integers البيانات العددية الصحيحة. نوع البيانات النطاق مساحة التخزين bigint -2^63 (-9,223,372,036,854,775,808) إلى 2^63-1 (9,223,372,036,854,775,807) 8 بايتات int -2^31 (-2,147,483,648) إلى 2^31-1 (2,147,483,647) 4 بايتات smallint -2^15 (-32,768) إلى 2^15-1 (32,767) بايتان tinyint من 0 إلى 255 بايت واحد table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } MONEY و SMALLMONEY يمثل النوعان MONEY و SMALLMONEY البيانات التي تمثل القيم النقدية أو العملات. نوع البيانات النطاق مساحة التخزين money -922,337,203,685,477.5808 إلى 922,337,203,685,477.5807 8 بايتات smallmoney -214,748.3648 إلى 214,748.3647 4 بايتات BINARY و VARBINARY يمثل النوعان BINARY و VARBINARY البيانات الثنائية ذات الطول الثابت أو المتغير. ويُصاغان على النحو التالي: BINARY [ ( n_bytes ) ] VARBINARY [ ( n_bytes | max ) ] يمكن أن يكون n_bytes أي عدد محصور بين 1 إلى 8000 أثمون. وتشير قيمة max إلى أنّ الحد الأقصى لمساحة التخزين هو 2^31-1. أمثلة: SELECT CAST(12345 AS BINARY(10)) -- 0x00000000000000003039 SELECT CAST(12345 AS VARBINARY(10)) -- 0x00003039 CHAR و VARCHAR يمثل النوعان CHAR و VARCHAR البيانات النصية ذات الطول الثابت أو المتغير. ويُصاغان على النحو التالي: CHAR [ ( n_chars ) ] VARCHAR [ ( n_chars ) ] أمثلة: SELECT CAST('ABC' AS CHAR(10)) -- 'ABC ' (إزاحة بمسافات بيضاء إلى اليمين) SELECT CAST('ABC' AS VARCHAR(10)) -- 'ABC' (لا إزاحة) SELECT CAST('ABCDEFGHIJKLMNOPQRSTUVWXYZ' AS CHAR(10)) -- 'ABCDEFGHIJ' (تُقصّ إلى عشرة أحرف فقط) NCHAR و NVARCHAR يمثل النوعان NCHAR و NVARCHAR نصوص اليونيكود ذات الطول الثابت أو المتغير. ويُصاغان على النحو التالي: NCHAR [ ( n_chars ) ] NVARCHAR [ ( n_chars | MAX ) ] استخدم MAX لأجل السلاسل النصية الطويلة التي يمكن أن تتجاوز 8000 حرفًا. UNIQUEIDENTIFIER يمثل هذا النوع مُعرّفا كونيا فريدًا (Universally Unique IDentifier أو UUID) أو معرّفًا عامّا فريدًا (globally unique identifier أو GUID) مُخزّنا على 16 أثمونًا. DECLARE @GUID UNIQUEIDENTIFIER = NEWID(); SELECT @GUID -- 'E28B3BD9-9174-41A9-8508-899A78A33540' DECLARE @bad_GUID_string VARCHAR(100) = 'E28B3BD9-9174-41A9-8508-899A78A33540_foobarbaz' SELECT @bad_GUID_string, -- 'E28B3BD9-9174-41A9-8508-899A78A33540_foobarbaz' CONVERT(UNIQUEIDENTIFIER, @bad_GUID_string) -- 'E28B3BD9-9174-41A9-8508-899A78A33540' الكلمة المفتاحية NULL تمثل الكلمة المفتاحية NULL في SQL، وكذلك في لغات البرمجة الأخرى، القيمة المعدومة، أو "لا شيء". وتُستخدم عادة في SQL للإشارة إلى "عدم وجود قيمة". من المهم التمييز بينها وبين القيم الفارغة، مثل السلسلة النصية '' الفارغة أو الرقم 0، إذ لا يُعدّ أي منهما في الواقع معدومًا (NULL). من المهم أيضًا تجنّب إحاطة NULL بعلامات الاقتباس، على شاكلة 'NULL'، والتي يمكن استخدامها في الأعمدة التي تقبل القيم النصية، بيد أنّها لا تمثّل القيمة NULL، ويمكن أن تسبّب أخطاءً، وتفسد البيانات. ترشيح NULL في الاستعلامات تختلف صياغة ترشيح NULL (أي عدم وجود قيمة) في كتل WHERE عن ترشيح القيم الأخرى: SELECT * FROM Employees WHERE ManagerId IS NULL ; SELECT * FROM Employees WHERE ManagerId IS NOT NULL ; لاحظ أنّه لمّا لم تكن NULL مساوية لأيّ شيء آخر، ولا حتى لنفسها، فستُعيد عوامل الموازنة = NULL أو <> NULL (أو != NULL ) دائمًا القيمة المنطقية الخاصة UNKNOWN، والتي ترفضها WHERE. ترشِّح WHERE كل الصفوف التي يساوي شرطها القيمة FALSE أو UNKNOWN، ولا تحتفظ إلا بالصفوف ذات الشرط الصحيح (TRUE). الأعمدة المعدومة في الجداول عند إنشاء الجداول، يمكن جعل العمود قابلًا للإلغاء (nullable) أو غير قابل للإلغاء. CREATE TABLE MyTable ( MyCol1 INT NOT NULL, -- non-nullable MyCol2 INT NULL -- nullable ) ; افتراضيًا، تكون جميع الأعمدة قابلة للإلغاء (باستثناء تلك الموجودة في قيد المفتاح الأساسي - primary key constraint) ما لم نعيّن القيد عند القيمة NOT NULL صراحة. وسينتج خطأ عن محاولة تعيين NULL لعمود غير قابل للإلغاء. INSERT INTO MyTable (MyCol1, MyCol2) VALUES (1, NULL) ; -- صحيح INSERT INTO MyTable (MyCol1, MyCol2) VALUES (NULL, 2) ; لا يمكن إدراج القيمة NULL في العمود 'MyCol1' في الجدول 'MyTable' لأنّ العمود لا يسمح بالقيمة المعدومة، لذا ستفشل عملية الإدراج INSERT. إسناد NULL إلى حقل إسناد القيمة NULL إلى حقل يشبه إسناد أيّ قيمة أخرى: UPDATE Employees SET ManagerId = NULL WHERE Id = 4 إدراج الصفوف التي تحتوي حقولًا معدومة (NULL fields) على سبيل المثال، إدراج بيانات موظف بدون رقم هاتف، وبدون مدير في جدول الموظفين Employees: INSERT INTO Employees (Id, FName, LName, PhoneNumber, ManagerId, DepartmentId, Salary, HireDate) VALUES (5, 'Jane', 'Doe', NULL, NULL, 2, 800, '2016-07-22') ; أمثلة على قواعد البيانات والجداول إليك بعض الأمثلة التوضيحية عن قواعد البيانات. قاعدة بيانات متجر السيارات سوف نستعرض في المثال التالي قاعدة بيانات لمتجر يبيع السيارات، سنخزّن في القاعدة قوائم تضمّ الأقسام والموظفين والعملاء وسيارات العملاء. وسنستخدم المفاتيح الخارجية (foreign keys) لإنشاء علاقات بين مختلف الجداول. هذا تطبيق حي للمثال: العلاقات بين الجداول يضم كل قسم 0 موظف أو أكثر، ولكل موظف مدير واحد أو أكثر، وقد يكون لكل عميل 0 سيارة أو أكثر الجدول Departments: Id Name 1 HR 2 Sales 3 Tech لننشئ الجدول عبر SQL: CREATE TABLE Departments ( Id INT NOT NULL AUTO_INCREMENT, Name VARCHAR(25) NOT NULL, PRIMARY KEY(Id) ); INSERT INTO Departments ([Id], [Name]) VALUES (1, 'HR'), (2, 'Sales'), (3, 'Tech') ; الجدول Employees: Id FName LName PhoneNumber ManagerId DepartmentId Salary HireDate 1 James Smith 1234567890 NULL 1 1000 01-01-2002 2 John Johnson 2468101214 1 1 400 23-03-2005 3 Michael Williams 1357911131 1 2 600 12-05-2009 4 Johnathon Smith 1212121212 2 1 500 24-07-2016 لننشئ الجدول: CREATE TABLE Employees ( Id INT NOT NULL AUTO_INCREMENT, FName VARCHAR(35) NOT NULL, LName VARCHAR(35) NOT NULL, PhoneNumber VARCHAR(11), ManagerId INT, DepartmentId INT NOT NULL, Salary INT NOT NULL, HireDate DATETIME NOT NULL, PRIMARY KEY(Id), FOREIGN KEY (ManagerId) REFERENCES Employees(Id), FOREIGN KEY (DepartmentId) REFERENCES Departments(Id) ); INSERT INTO Employees ([Id], [FName], [LName], [PhoneNumber], [ManagerId], [DepartmentId], [Salary], [HireDate]) VALUES (1, 'James', 'Smith', 1234567890, NULL, 1, 1000, '01-01-2002'), (2, 'John', 'Johnson', 2468101214, '1', 1, 400, '23-03-2005'), (3, 'Michael', 'Williams', 1357911131, '1', 2, 600, '12-05-2009'), (4, 'Johnathon', 'Smith', 1212121212, '2', 1, 500, '24-07-2016') الجدول Customers: Id FName LName Email PhoneNumber PreferredContact 1 William Jones william.jones@example.com 3347927472 PHONE 2 David Miller dmiller@example.net 2137921892 EMAIL 3 Richard Davis richard0123@example.com NULL EMAIL لننشئ الجدول: CREATE TABLE Customers ( Id INT NOT NULL AUTO_INCREMENT, FName VARCHAR(35) NOT NULL, LName VARCHAR(35) NOT NULL, Email varchar(100) NOT NULL, PhoneNumber VARCHAR(11), PreferredContact VARCHAR(5) NOT NULL, PRIMARY KEY(Id) ); INSERT INTO Customers ([Id], [FName], [LName], [Email], [PhoneNumber], [PreferredContact]) VALUES (1, 'William', 'Jones', 'william.jones@example.com', '3347927472', 'PHONE'), (2, 'David', 'Miller', 'dmiller@example.net', '2137921892', 'EMAIL'), (3, 'Richard', 'Davis', 'richard0123@example.com', NULL, 'EMAIL') ; الجدول Cars: Id CustomerId EmployeeId Model Status Total Cost 1 1 2 Ford F-150 READY 230 2 1 2 Ford F-150 READY 200 3 2 1 Ford Mustang WAITING 100 4 3 3 Toyota Prius WORKING 1254 تعليمات SQL لإنشاء الجدول: CREATE TABLE Cars ( Id INT NOT NULL AUTO_INCREMENT, CustomerId INT NOT NULL, EmployeeId INT NOT NULL, Model varchar(50) NOT NULL, Status varchar(25) NOT NULL, TotalCost INT NOT NULL, PRIMARY KEY(Id), FOREIGN KEY (CustomerId) REFERENCES Customers(Id), FOREIGN KEY (EmployeeId) REFERENCES Employees(Id) ); INSERT INTO Cars ([Id], [CustomerId], [EmployeeId], [Model], [Status], [TotalCost]) VALUES ('1', '1', '2', 'Ford F-150', 'READY', '230'), ('2', '1', '2', 'Ford F-150', 'READY', '200'), ('3', '2', '1', 'Ford Mustang', 'WAITING', '100'), ('4', '3', '3', 'Toyota Prius', 'WORKING', '1254') ; قاعدة بيانات المكتبة سننشئ في هذا المثال قاعدة بيانات خاصة بمكتبة، ستحتوي القاعدة على جداول لتخزين المؤلفين والكتب والكتاب. هنا تجد مثالًا حيًّا للقاعدة. يُعرف جدولَا المؤلفين والكتب بالجداول الأساسية (base tables)، لأنهما يحتويان على تعريف العمود، وكذا البيانات الخاصة بالكيانات الفعلية في النموذج العلائقي (relational model). ويُعرف الجدول BookAuthors باسم جدول العلاقة (relationship table)، لأنّه يحدّد العلاقة بين جدول الكتب Books والمؤلفين Authors. العلاقات بين الجداول يمكن أن يكون لكل مؤلف كتاب واحد أو أكثر. كل كتاب يمكن أن يكون له مؤلف واحد أو أكثر الجدول Authors (عرض الجدول): Id Name Country 1 J.D. Salinger USA 2 F. Scott. Fitzgerald USA 3 Jane Austen UK 4 Scott Hanselman USA 5 Jason N. Gaylord USA 6 Pranav Rastogi India 7 Todd Miranda USA 8 Christian Wenz USA لننشئ الجدول الآن: CREATE TABLE Authors ( Id INT NOT NULL AUTO_INCREMENT, Name VARCHAR(70) NOT NULL, Country VARCHAR(100) NOT NULL, PRIMARY KEY(Id) ); INSERT INTO Authors (Name, Country) VALUES ('J.D. Salinger', 'USA'), ('F. Scott. Fitzgerald', 'USA'), ('Jane Austen', 'UK'), ('Scott Hanselman', 'USA'), ('Jason N. Gaylord', 'USA'), ('Pranav Rastogi', 'India'), ('Todd Miranda', 'USA'), ('Christian Wenz', 'USA') ; الجدول Books ( عرض الجدول): Id Title 1 The Catcher in the Rye 2 Nine Stories 3 Franny and Zooey 4 The Great Gatsby 5 Tender id the Night 6 Pride and Prejudice 7 Professional ASP.NET 4.5 in C# and VB عبارات SQL لإنشاء الجدول: CREATE TABLE Books ( Id INT NOT NULL AUTO_INCREMENT, Title VARCHAR(50) NOT NULL, PRIMARY KEY(Id) ); INSERT INTO Books (Id, Title) VALUES (1, 'The Catcher in the Rye'), (2, 'Nine Stories'), (3, 'Franny and Zooey'), (4, 'The Great Gatsby'), (5, 'Tender id the Night'), (6, 'Pride and Prejudice'), (7, 'Professional ASP.NET 4.5 in C# and VB') ; الجدول BooksAuthors (عرض الجدول): BookId AuthorId 1 1 2 1 3 1 4 2 5 2 6 3 7 4 7 5 7 6 7 7 7 8 تعليمات SQL لإنشاء الجدول: CREATE TABLE BooksAuthors ( AuthorId INT NOT NULL, BookId INT NOT NULL, FOREIGN KEY (AuthorId) REFERENCES Authors(Id), FOREIGN KEY (BookId) REFERENCES Books(Id) ); INSERT INTO BooksAuthors (BookId, AuthorId) VALUES (1, 1), (2, 1), (3, 1), (4, 2), (5, 2), (6, 3), (7, 4), (7, 5), (7, 6), (7, 7), (7, 8) ; الآن، إن أردت عرض جميع المؤلفين، فاكتب ما يلي (عرض المثال الحي): SELECT * FROM Authors; عرض جميع عناوين الكتب (عرض مثال حي): SELECT * FROM Books; عرض جميع الكتب ومؤلفيها (عرض مثال حي): SELECT ba.AuthorId, a.Name AuthorName, ba.BookId, b.Title BookTitle FROM BooksAuthors ba INNER JOIN Authors a ON a.id = ba.authorid INNER JOIN Books b ON b.id = ba.bookid ; جدول الدول سننشئ في هذا المثال جدولًا للبلدان. يُستخدم جدول البلدان في العديد من المجالات، وخاصة في التطبيقات المالية التي تشمل العملات وأسعار الصرف. هذا مثال حي. تطلب بعض البرمجيات الخاصة بتحليل الأسواق مثل بلومبرج ورويترز أن تعطيهم رمزًا مؤلفًا من حرفين أو ثلاث يمثل الدولة، إلى جانب رمز العملة. يحتوي الجدول التالي على عمود يحتوي رموز ISO المؤلفة من حرفين، وكذلك على عمود يحتوي رموز ISO3 المكونة من 3 أحرف، والتي تمثل الدول. الجدول Countries (عرض الجدول): Id ISO ISO3 ISONumeric CountryName Capital ContinentCode CurrencyCode 1 AU AUS 36 Australia Canberra OC AUD 2 DE DEU 276 Germany Berlin EU EUR 2 IN IND 356 India New Delhi AS INR 3 LA LAO 418 Laos Vientiane AS LAK 4 US USA 840 United States Washington NA USD 5 ZW ZWE 716 Zimbabwe Harare AF ZWL لننشئ جدول الدول في SQL: CREATE TABLE Countries ( Id INT NOT NULL AUTO_INCREMENT, ISO VARCHAR(2) NOT NULL, ISO3 VARCHAR(3) NOT NULL, ISONumeric INT NOT NULL, CountryName VARCHAR(64) NOT NULL, Capital VARCHAR(64) NOT NULL, ContinentCode VARCHAR(2) NOT NULL, CurrencyCode VARCHAR(3) NOT NULL, PRIMARY KEY(Id) ) ; INSERT INTO Countries (ISO, ISO3, ISONumeric, CountryName, Capital, ContinentCode, CurrencyCode) VALUES ('AU', 'AUS', 36, 'Australia', 'Canberra', 'OC', 'AUD'), ('DE', 'DEU', 276, 'Germany', 'Berlin', 'EU', 'EUR'), ('IN', 'IND', 356, 'India', 'New Delhi', 'AS', 'INR'), ('LA', 'LAO', 418, 'Laos', 'Vientiane', 'AS', 'LAK'), ('US', 'USA', 840, 'United States', 'Washington', 'NA', 'USD'), ('ZW', 'ZWE', 716, 'Zimbabwe', 'Harare', 'AF', 'ZWL') ; ترجمة -وبتصرّف- للفصول الخمسة الأولى من الكتاب SQL Notes for Professionals اقرأ أيضًا: المقال التالي: جلب الاستعلامات عبر SELECT في SQL النسخة العربية الكاملة لكتاب ملاحظات للعاملين بلغة SQL 1.0.0
-
تصفير البت الأيمن (Rightmost set bit) ملاحظة: المقصود بتصفير بت أي جعل قيمته 0، والمقصود بتعيين قيمة بت أي جعل قيمته 1. معالجة البتات وفق أسلوب لغة C template < typename T > T rightmostSetBitRemoved(T n) { // وما بعدها c++11 لأجل // static_assert(std::is_integral<T>::value && !std::is_signed<T>::value, "type should //be unsigned"); return n & (n - 1); } تفسير المثال إذا كان n يساوي الصفر، فسنحصل على 0 & 0xFF..FF والتي تساوي صفرًا. وإن كان غير ذلك فيمكن كتابة n على الصورة 0bxxxxxx10..00، ومن ثم سيُكتب n - 1 على الصورة 0bxxxxxx011..11. وهكذا، فإنّ n & (n - 1) ستساوي 0bxxxxxx000..00. ضبط كل البتات إلى القيمة 1 (Set all bits) معالجة البتات وفق أسلوب لغة C x = -1; // -1 == 1111 1111 ... 1111b (انظر هذه الصفحة الأجنبية للمزيد من التفاصيل). استخدام std::bitset std::bitset<10> x; x.set(); // إسناد القيمة 1 إلى كل البتات تبديل قيمة بت معالجة البتات وفق أسلوب لغة C يمكن تبديل قيمة البت باستخدام معامِل XOR الثنائي (^) . // عكس قيمتها الحالية x ستصبح قيمة البت. number ^= 1LL << x; استخدام std::bitset std::bitset<4> num(std::string("0100")); num.flip(2); // num = 0000 num.flip(0); // num = 0001 num.flip(); // يبدل جميع البتّات ،num = 1110 التحقق من قيمة البت معالجة البتات وفق أسلوب لغة C يمكن الحصول على قيمة بت معيّنة في التمثيل الثنائي لعدد ما عن طريق إزاحته إلى اليمين بمقدار x - إذ x تشير إلى عدد المرات-، ثم تنفيذ عملية AND الثنائية & عليه: (number >> x) & 1LL; // تساوي 1، وإلا فسَيساوي 0 x الناتج سيساوي 1 في حال كانت البت رقم يمكن تنفيذ عملية الإزاحة اليمينية (right-shift operation) إما كإزاحة حسابية مُؤشرَّة (arithmetic signed shift) أو كإزاحة منطقية غير مؤشرة (logical unsigned shift). إن كان number في التعبير number >> x ذا نوعٍ مُؤشَّر (signed type) وله قيمة سالبة، فإنّ القيمة الناتجة تحدد وفق التطبيق (implementation-defined). وإن أردنا الحصول على قيمة تلك البت مباشرة في موضعها فيمكننا إزاحة القناع لليسار: (number & (1LL << x)); الناتج سيساوي 1 >> x في حال كان البت رقم 1 يساوي 1 وإلا فسيساوي الصفر. يمكننا استخدامهما كشرط لأنّ جميع القيم المخالفة للصفر تُعدُّ صحيحة. استخدام std::bitset std::bitset<4> num(std::string("0010")); bool bit_val = num.test(1); // true تساوي bit_val قيمة عدّ البتات ذات القيمة 1 تزيد الحاجة لعد البتات الموجودة في سلسلة بتية (Bitstring) في علم التشفير وغيره، وقد دُرست تلك المشكلة على نطاق واسع، والطريقة البسيطة لعدها هي المرور على البتات فرادى: // n مراكمة العدد الإجمالي للوحدات في التمثيل الثنائي للعدد unsigned value = 1234; unsigned bits = 0; for (bits = 0; value; value >>= 1) bits += value & 1; بيْد أنّ هذه الطريقة أفضل (تعتمد على إزالة البت الأيمن ذا القيمة 1): // n تراكم إجمالي عدد البتّات في التمثيل الثنائي للعدد unsigned bits = 0; for (; value; ++bits) value &= value - 1; تُنفِّذ الشيفرة أعلاه عددًا من التكرارات يكافئ عدد البتات التي تأخذ القيمة 1، لذا فهي مناسبة إن كان في value عدد قليل من البتات غير الصفرية (non-zero bits). تم اقتراح هذه الطريقة للمرة الأولى من طرف بيتر فيجنيور (Peter Wegner) في عام CACM 3/322 - 1960، وقد لقيت قبولًا وتبنيًا كثيرًا منذ ظهورها في كتاب لغة البرمجة C الذي كتبه برايان كرنيجن ودينيس ريتشي. وتتطلب تلك الطريقة 12 عملية حسابية، إحداها عملية متعددة: unsigned popcount(std::uint64_t x) { const std::uint64_t m1 = 0x5555555555555555; // binary: 0101... const std::uint64_t m2 = 0x3333333333333333; // binary: 00110011.. const std::uint64_t m4 = 0x0f0f0f0f0f0f0f0f; // binary: 0000111100001111 // وضع تعداد كل بتّيْن في هاتين البتّيْن x -= (x >> 1) & m1; // وضع تعداد كل 4 بتات في تلك البتات الأربعة x = (x & m2) + ((x >> 2) & m2); // وضع تعداد كل 8 بتات في تلك البتات الثمانية x = (x + (x >> 4)) & m4; // ... + (x<<24) + (x<<16) + (x<<8) + x البتات الثمانية على يسار return (x * h01) >> 56; } هذا النوع من التطبيق لديه أفضل سلوك للحالة الأسوأ (Worst-Case) انظر Hamming weight لمزيد من التفاصيل. يحتوي العديد من وحدات المعالجة المركزية CPUs على تعليمات محددة (مثل popcnt في x86)، ويمكن للمصرّف إنشاء دالة مخصصة (غير قياسية) مضمنة، فمثلًا في مصرّف ++g نجد التعليمة التالية: int __builtin_popcount (unsigned x); التحقق مما إذا كان عدد صحيح ما هو أس للعدد 2 تُعد طريقة n & (n - 1) المُستعملة في مقالة إزالة بت التعيين اليمنى مفيدة لتحديد ما إذا كان عدد صحيح هو أس للرقم 2: bool power_of_2 = n && !(n & (n - 1)); لاحظ أنه بدون الجزء الأول من عملية التحقق (n &&)، فسيُعد العدد 0 أسًّا للرقم 2، وذلك خطأ. تعيين بت محدَّد معالجة البتات وفق أسلوب لغة C يمكن تعيين قيمة بت باستخدام عامل OR الثنائي (|). // x تعيين قيمة البت رقم number |= 1LL << x; استخدام std::bitset التعبير set(x) أو set (x, true) يُسنِد القيمة 1 إلى البت الموجودة في الموضع x. std::bitset<5> num(std::string("01100")); num.set(0); // num = 01101 num.set(2); // 01101 يساوي num لا يزال num.set(4,true); // 11110 يساوي الآن num تصفير بت محدَّد معالجة البتات وفق مقاربة لغة C يمكن تصفير (إعطاء القيمة 0) بت محدَّد باستخدام معامِل AND الثنائي (&). // x مسح البت الموجودة عند الموضع number &= ~(1LL << x); استخدام std::bitset تصفر التعليمة reset(x) أو set (x, false)) يمسح البت الموجودة في الموضع x. std::bitset<5> num(std::string("01100")); num.reset(2); // 01000 يساوي الآن num num.reset(0); // 01000 يساوي الآن num num.set(3,false); // 00000 يساوي الآن num تغيير قيمة البت رقم n إلى x معالجة البتات وفق أسلوب لغة C في المثال التالي، ستضبط قيمة البت رقم n إلى القيمة 1 في حال كانت قيمة x تساوي 1 وستُصفَّر قيمتها في حال كانت قيمة x يساوي الصفر: number ^= (-x ^ number) & (1LL << n); استخدام std::bitset تعين التعليمة set(n,val) يعين القيمة val للبتّة n. std::bitset<5> num(std::string("00100")); num.set(0,true); // 00101 يساوي الآن num num.set(2,false); // 00001 يساوي الآن num تطبيق على معالجة البتات: تغيير حالة الحروف أحد التطبيقات على معالجة البتات والتلاعب بها هو تحويل الحروف الأجنبية من الحالة الصغرى (Small) إلى الحالة الكبرى (Capital) أو العكس، عن طريق اختيار قناع (mask) و عملية ثنائية (bit operation) مناسبة. فمثلًا، يكون التمثيل الثنائي للحرف a هو 01(1)00001، وللحرف A هو 01(0)00001. لا يختلف التمثيلان السابقان إلا في البت الموجودة بين القوسين. لذا فإنّ تحويل الحرف a من الحالة الصغرى إلى الحالة الكبرى يكافئ تعيين البت الموجودة بين قوسين على القيمة 1 على النحو التالي: /**************************************** تكبير حرف صغير ======================================== a: 01100001 mask: 11011111 <-- (0xDF) 11(0)11111 :--------- a&mask: 01000001 <-- A الحرف *****************************************/ شيفرة تكبير الحرف A هي: #include <cstdio> int main() { char op1 = 'a'; // "a" الحرف int mask = 0xDF; // اختيار قناع مناسب printf("a (AND) mask = A\n"); printf("%c & 0xDF = %c\n", op1, op1 & mask); return 0; } النتيجة: $ g++ main.cpp -o test1 $ ./test1 a (AND) mask = A a & 0xDF = A حقول البتات تحزِّم حقولُ البتات (Bit fields) بُنيات C و C++ لتصغير حجمها، إذ تحدد عدد البتات المخصصة لأعضاء البنية، ثم يدمج المصرّف (co-mingle) البتّات. السيء في هذا هو أنك لن تستطيع تحديد عنوان حقل البتة الخاص بعضو معين من البنية لأن المصرّف يدمجهما معًا. كذلك فإن الدالة sizeof() غير مسموح بها. أحد الجوانب السلبية الأخرى، هو أنّ الدخول إلى حقول البتات بطيء بسبب الحاجة لاسترجاع الذاكرة ثم تطبيق العمليات الثنائية لاستخراج أو تعديل قيم الأعضاء. قد تزيد هذه العمليات أيضًا في حجم البرنامج التنفيذي (executable). إليك المثال التالي لتوضيح كيفية التصريح والاستخدام: struct FileAttributes { unsigned int ReadOnly: 1; unsigned int Hidden: 1; }; في هذا المثال، سيشغل كلّ حقل من هذين الحقلين بتّة واحدة في الذاكرة، وعلامة ذلك العدد 1 الموجود بعد أسماء المتغيّرات. يمكن أن يكون حقل البتات من أيّ نوع عددي صحيح (من 8-bit int وحتى 64-bit int). يُستحسن استخدام النوع unsigned، وإلا فقد تحصل على نتائج غير متوقعة. إذا كانت هناك حاجة إلى المزيد من البتات، فاستبدل "1" بعدد البتات المطلوب. مثلًا: struct Date { unsigned int Year: 13; // 2^13 = 8192 unsigned int Month: 4; // 2^4 = 16 unsigned int Day: 5; // 32 }; البنية بأكملها لن تستخدم إلا 22 بت فقط، وفي إعدادات التصريف العادية، سيكون ناتج sizeof الخاص بهذه البنية هو 4 بايتات. استخدام حقول البتات سهل جدّا، فما عليك إلا التصريح عن المتغيّر ثم استخدامه كبنية عادية. Date d; d.Year = 2016; d.Month = 7; d.Day = 22; std::cout << "Year:" << d.Year << std::endl << "Month:" << d.Month << std::endl << "Day:" << d.Day << std::endl; هذا الدرس جزء من سلسلة دروس عن C++. ترجمة -وبتصرّف- للفصل Chapter 6: Bit Manipulation من كتاب C++ Notes for Professionals اقرأ أيضا الدرس 5: العمليات البتية
-
تُنفَّذ العمليات البتية (أو الثنائية) على مستوى البت من البيانات وذلك باستعمال العوامل التالية: | - عامِل OR البِتِّي (Bitwise OR) إليك المثال التالي: int a = 5; // 0101b (0x05) int b = 12; // 1100b (0x0C) int c = a | b; // 1101b (0x0D) std::cout << "a = " << a << ", b = " << b << ", c = " << c << std::endl; يكون ناتج الشيفرة السابقة هو: a = 5, b = 12, c = 13 وسبب ذلك أن عامِل OR يعمل على مستوى البِتّْ (bit level)، ويستخدم جدول الصحة البوليني أو المنطقي (Boolean Truth Table) التالي: true OR true = true true OR false = true false OR false = false عندما يُطبق عامل OR على القيمتين البتّيّتين a (أي 0101) و b (أي 1100) ، سنحصل على القيمة البتّيّة 1101: int a = 0 1 0 1 int b = 1 1 0 0 | --------- int c = 1 1 0 1 لا يغير عامِل OR القيم الأصلية للمعامَلات إلا في حالة استخدام عامِل الإسناد البِتِّي المركَّب =| (Bitwise Assignment Compound Operator) لإسناد تغيير تلك القيم: int a = 5; // 0101b (0x05) a |= 12; // a = 0101b | 1101b ^ - عامل XOR البِتِّي (Bitwise XOR) إليك المثال التالي: int a = 5; // 0101b (0x05) int b = 9; // 1001b (0x09) int c = a ^ b; // 1100b (0x0C) std::cout << "a = " << a << ", b = " << b << ", c = " << c << std::endl; يكون ناتج الشيفرة السابقة: a = 5, b = 9, c = 12 وسبب ذلك أن عامِل XOR يعمل على مستوى البِتّْ (bit level)، ويستخدم جدول الصحة البولياني التالي: true OR true = false true OR false = true false OR false = false لاحظ أنه في عملية XOR، يكون لدينا true XOR true = false، على خلاف العمليتين AND و OR، إذ تحققان: true AND/OR true = true، وذلك هو وصف الطبيعة الحصرية لعملية XOR. عندما يُطبّق عامِل XOR على القيمتين البتّيّتين a (أي 0101) و b (أي 1001) ، سنحصل على القيمة البتّيّة 1101: int a = 0 1 0 1 int b = 1 0 0 1 ^ --------- int c = 1 1 0 0 لا يغير عامِل XOR القيم الأصلية للمعامَلات إلا في حالة استخدام عامِل الإسناد البِتِّي المركَّب =^ (Bitwise Assignment Compound Operator) لإسناد تغيير تلك القيم: int a = 5; // 0101b (0x05) a ^= 9; // a = 0101b ^ 1001b يمكن استخدام عامِل XOR بعدة طرق، وغالبًا ما يُستخدَم في عمليات البتات المُقنّعة (bit mask operations) لتشفير البيانات وضغطها. لاحظ أن المثال التالي توضيحي، ولا ينبغي أن يُستخدم في الشيفرة النهائية (هناك طرق أفضل لتحقيق نفس النتيجة، مثل std::swap()). تستطيع استخدام عملية XOR لمبادلة متغيرين: int a = 42; int b = 64; // XOR المبادلة عبر a ^= b; b ^= a; a ^= b; std::cout << "a = " << a << ", b = " << b << "\n"; لإنجاز هذا، تحتاج إلى التحقق من إمكانية استخدامه. void doXORSwap(int & a, int & b) { // ينبغي إضافة عبارة للتحقق من أنك لا تبادل المتغير مع نفسه // وإلا ستجعل القيمة صفرية if ( & a != & b) { // XOR مبادلة a ^= b; b ^= a; a ^= b; } } ورغم أنها تبدو طريقة مناسبة للعزل إلا أنها ليست مفيدة في شيفرة الإنتاج، ذلك أن xor ليس عملية منطقية أساسية، بل مزيج من عمليات أخرى: a^c=~(a&c)&(a|c). كذلك ، فإن المُصرفات التي صدرت بعد 2015 أتاحت إسناد قيم ثنائية إلى المتغيرات: int cn=0b0111; & - عامل AND البتّيّ إليك المثال التالي: int a = 6; // 0110b (0x06) int b = 10; // 1010b (0x0A) int c = a & b; // 0010b (0x02) std::cout << "a = " << a << ", b = " << b << ", c = " << c << std::endl; يكون ناتج الشيفرة السابقة: a = 6, b = 10, c = 2 وسبب ذلك أن عامل AND يعمل على مستوى البت، ويستخدم جدول الصحة البوليني التالي: TRUE AND TRUE = TRUE TRUE AND FALSE = FALSE FALSE AND FALSE = FALSE عندما يُطبق عامِل AND على القيمتين البتّيّتين a (أي 0110) و b (أي 1010) ، سنحصل على القيمة البتّيّة 0010: int a = 0 1 1 0 int b = 1 0 1 0 & --------- int c = 0 0 1 0 لا يغير عامِل AND قيمة المعامَل الأصلي إلا عند استخدام عامِل الإسناد البِتِّي المركب &=: int a = 5; // 0101b (0x05) a &= 10; // a = 0101b & 1010b << - عملية الإزاحة اليسارية (left shift) إليك المثال التالي: int a = 1; // 0001b int b = a << 1; // 0010b std::cout << "a = " << a << ", b = " << b << std::endl; يكون ناتج ذلك هو: a = 1, b = 2 وتفسير ذلك أن عامِل الإزاحة اليساري يزيح بتَّات القيمة الموجودة على الجانب الأيسر (a) بالعدد المحدد على اليمين (1)، مع ملأ البتات الأقل أهمية (least significant bits) بأصفار، فمثلًا في حال إزاحة قيمة العدد 5 (تمثيله الثنائي 0000 0101) إلى اليسار أربعة منازل (على سبيل المثال 5 <<4)، سوف نحصل على القيمة 80 (تمثيلها الثنائي 0101 0000). قد تلاحظ أنّ إزاحة قيمة إلى اليسار بمنزلة واحدة يكافئ ضرب تلك القيمة بالعدد 2، على سبيل المثال: int a = 7; while (a < 200) { std::cout << "a = " << a << std::endl; a <<= 1; } a = 7; while (a < 200) { std::cout << "a = " << a << std::endl; a *= 2; } ولكن تجدر الإشارة إلى أنّ عملية الإزاحة اليسارية سوف تزيح جميع البتات إلى اليسار، بما في ذلك بتّ الإشارة، انظر إلى المثال التالي: int a = 2147483647; // 0111 1111 1111 1111 1111 1111 1111 1111 int b = a << 1; // 1111 1111 1111 1111 1111 1111 1111 1110 std::cout << "a = " << a << ", b = " << b << std::endl; سيكون الناتج: a = 2147483647, b = -2 وفي حين أنّ بعض المُصرِّفات ستعيد نتائج قد تبدو متوقعة غير أنه يجب ملاحظة أن إزاحة عددٍ ذا إشارة (signed number) بحيث تتأثر بتة الإشارة (sign bit)، فستكون النتيجة غير محددة (undefined). كذلك في حال كان عدد البتات التي ترغب في إزاحتها عددًا سالبًا، أو كان أكبر من عدد البتات التي يمكن أن يحملها النوع الموجود على اليسار، انظر: int a = 1; int b = a << -1; // سلوك غير محدد char c = a << 20; // سلوك غير محدد لا تغير الإزاحة البتّيّة اليسارية قيمة المعامَلات إلا في حال استخدام عامِل الإسناد البِتِّي المركب <<=: int a = 5; // 0101b a <<= 1; // a = a << 1; >> - الإزاحة اليمينية (right shift) إليك المثال التالي: int a = 2; // 0010b int b = a >> 1; // 0001b std::cout << "a = " << a << ", b = " << b << std::endl; سيكون الناتج: a = 2, b = 1 وتفسير ذلك أن عامِل الإزاحة اليميني يزيح بتِّات القيمة الموجودة على الجانب الأيسر (a) بالعدد المحدد على اليمين (1)؛ وتجدر الإشارة إلى أنه رغم أنّ عملية الإزاحة اليمينية قياسية، فإنّ ما يحدث لبتات الإزاحة اليمينية في الأعداد السالبة ذات الإشارة (signed negative number) يختلف بحسب الاستخدام، ومن ثم لا يمكن ضمان كونها قابلة للنقل (portable)، انظر: int a = -2; int b = a >> 1; // على المصرّف b تعتمد قيمة كذلك سيكون ذلك السلوك غير محدد إن كان عدد البتات التي ترغب في إزاحتها سالبًا، على سبيل المثال: int a = 1; int b = a >> -1; // سلوك غير محدد لا يغيّر عامل الإزاحة اليميني قيمة المعامَل الأصلي إلا في حال استخدام عامِل الإسناد البِتِّي المركب >>=: int a = 2; // 0010b a >>= 1; // a = a >> 1; هذا الدرس جزء من سلسلة دروس عن C++. ترجمة -وبتصرّف- للفصل Chapter 5: Bit Operators من كتاب C++ Notes for Professionals اقرأ أيضا الدرس 6: معالجة البتات والتلاعب بها الدرس 4: حسابيّات الأعداد العشرية
-
يفترض كثير من المبرمجين خطأً أن الشيفرة التالية ستعمل كما هو مخطط لها: float total = 0; for (float a = 0; a != 2; a += 0.01f) { total += a; } إذ يفترض المبرمج المبتدئ أن تلك الشيفرة ستجمع كل الأعداد الموجودة في النطاق المحصور بين 0، 0.01، 0.02، 0.03، … ، 1.97، 1.98، 1.99، للحصول في النهاية على 199 كنتيجة، وهي الإجابة الصحيحة. لكن لا يحدث ذلك لسببين: لن ينتهي البرنامج أبدًا بهيئته الحالية، لأنّ a لن تساوي أبدًا القيمة 2، والحلقة التكرارية لن تنتهي. إذا عدّلنا شرط الحلقة التكرارية وجعلناه a < 2، فإنّ الحلقة التكرارية ستنتهي، لكن الإجمالي سيكون شيئًا غير 199 التي نريدها، وعلى الأغلب ستحصل على 201 على الأجهزة المتوافقة مع معيار IEEE754. سبب حدوث ذلك هو أنّ الأعداد العشرية (Floating Point Numbers) تمثِّل القيم بشكل تقريبي وحسب. كما في المثال التالي: double a = 0.1; double b = 0.2; double c = 0.3; if (a + b == c) // IEEE754 لن يُطبع هذا على الأجهزة المتوافقة مع std::cout << "This Computer is Magic!" << std::endl; else std::cout << "This Computer is pretty normal, all things considered." << std::endl; برغم أننا كمبرمجين نرى ثلاثة أعداد مكتوبة وفق النظام العشري، إلا أن المصرِّف (Compiler) -والحاسوب الذي يعمل عليه- لا يرى إلا أرقامًا ثنائية، لذا يجب أن تخزَّن تلك الأعداد في صيغ تقريبية غير دقيقة كما في حالة العدد 1/3 إذ يخزن على الصورة 0.3333333333 في النظام العشري، وذلك لأن 0.1 و 0.2 و 0.3 تتطلب قسمة تامة على 10، وهذا يسير في النظام العشري لكنه مستحيل في النظام الثنائي. // الأعداد العشرية في نظام 64-بت لديها 53 خانة رقمية، بما في ذلك الجزء الصحيح من العدد double a = 0011111110111001100110011001100110011001100110011001100110011010; // تمثيل تقريبي للعدد 0.1 double b = 0011111111001001100110011001100110011001100110011001100110011010; // تمثيل تقريبي للعدد 0.2 double c = 0011111111010011001100110011001100110011001100110011001100110011; // تمثيل تقريبي للعدد 0.3 double a + b = 0011111111010011001100110011001100110011001100110011001100110100; // 0.3 لاحظ أن هذا العدد لا يساوي هذا الدرس جزء من سلسلة دروس عن C++. ترجمة -وبتصرّف- للفصل Chapter 4: Floating Point Arithmetic من كتاب C++ Notes for Professionals اقرأ أيضًا الدرس 5: العمليات البتِّيّة (Bit Operators) الدرس 3: أسبقية العمليات
-
العمليات المنطقية العامل المنطقي && له الأسبقية على ||، وذلك يعني أن الأقواس توضع لتقييم ما سيتم تقييمه مع غيره، وتستخدم C++ تقييم الدارة القصيرة (short-circuit evaluation) في تقييم && و || من أجل تجنّب إجراء العمليات غير الضرورية. أيضًا، إذا أعاد الجانب الأيسر من || القيمة true، فلن تكون هناك حاجة إلى تقييم الجانب الأيمن. في المثال التالي، سنقيّم ثلاثة قيم منطقية بالعامِليْن || و && من أجل توضيح الأسبقية، لا تعني الأسبقية أن && ستقيَّم أولًا وإنما توضح أين ستوضع الأقواس: #include <iostream> #include <string> using namespace std; bool True(string id) { cout << "True" << id << endl; return true; } bool False(string id) { cout << "False" << id << endl; return false; } int main() { bool result; // حالة 1 result = False("A") || False("B") && False("C"); // eq. False("A") || (False("B") && False("C")) //FalseA //FalseB // حالة 2 result = True("A") || False("B") && False("C"); // eq. True("A") || (False("B") && False("C")) cout << result << " :=====================" << endl; //TrueA } في تقييم الدارة القصيرة، يجري تخطي C في الحالة الأولى، فبما أن A خطأ فيجب تقييم الجانب الأيمن من العامِل ||؛ تكون B خطأ أيضًا، لذا ليس هنالك حاجة إلى تقييم C مهما كان تقييمها سواءً خطأ False أم صحيح True. في الحالة الثانية، يجري تخطي B و C وفقًا لتقييم الدارة القصيرة، إذ قيمة A هي True صحيحة فلا داع لتقييم الجانب الأيمن من المعامل ||. إن كان للعامِل || أسبقية على && فإن التقييم المكافئ يكون: (True("A") || False("B")) && False("C") والذي سيطبع TrueA للجزء الأول الموضوع بين أقواس والذي سيتخطى B فقط آنذاك في تقييم الدارة القصيرة ولكن بوجود العامل && في الطرف الأيمن من الأقواس، ستكون النتيجة النهائية FalseC. بما أنّ الأقواس موضوعة بشكل مختلف، فإنّ الأجزاء التي ستُقيّم ستكون مختلفة ما يجعل النتيجة النهائية في هذه الحالة هي False، لأن تقييم C هو False. العاملان AND و OR لهما الأسبقية المعتادة في C++، أي أنّ AND تسبق OR دومًا. // يمكنك القيادة برخصة أجنبية لستِّين يوما bool can_drive = has_domestic_license || has_foreign_license && num_days <= 60; هذه الشيفرة مكافئة لما يلي: // يمكنك القيادة برخصة أجنبية لستّين يوما bool can_drive = has_domestic_license || (has_foreign_license && num_days <= 60); لا تؤدي إضافة الأقواس إلى تغيير السلوك، ولكنها تحسّن إمكانية القراءة، وترفع اللُّبس عن نية كاتب الشيفرة. العمليات الأحادية تُطبَّق العمليات الأحادية على الكائن الذي استُدعيت عليه وسميت أحادية لأن العملية تطبَّق على طرف واحد فقط باستعمال عامل أحادي (Unary Operator)، ولها أسبقية عالية أي تأخذ الأولوية دومًا ضمن المعاملات. عند استخدام العامِل الأحادي بعد الكائن -أي postfix-، فلن يُتَّخذ إجراء إلا بعد تقييم العملية بأكملها، وهذا مفيد في بعض العمليات الحسابية. إليك المثال التالي: int a = 1; ++a; // 2 a--; // 1 int minusa = -a; // -1 bool b = true; !b; // true a = 4; int c = a++/2; // 4 / 2 = 2 cout << a << endl; // 5 int d = ++a / 2; // 6 / 2 = 3 int arr[4] = {1,2,3,4}; إليك مثال آخر أكثر تتعقيدًا حول تطبيق عملية أحادية باستعمال العامل ++ على المؤشرات: int arr[4] = {1,2,3,4}; int * ptr1 = & arr[0]; int * ptr2 = ptr1++; std::cout << * ptr1++ << std::endl; // 2 // قبل زيادته arr[0] الحصول على قيمة العنصر int e = arr[0]++; std::cout << e << std::endl; // 1 std::cout << * ptr2 << std::endl; // 2 يشير المؤشر ptr1 إلى عنوان [arr[0 والتي تخزن القيمة 1 بينما يشير المؤشر ptr2 إلى المؤشر prt1 ثم يزيد من قيمة الأخير مقدار 1 ليشير بذلك إلى العنصر [arr[1. العمليات الحسابية العمليات الحسابية في C++ لها نفس الأسبقية التي لها في الرياضيات. فعمليتا الضرب والقسمة لهما تجميع يساري (left associativity) -بمعنى أنهما تُقيَّمان من اليسار إلى اليمين-، ولهما أسبقية أعلى من الجمع والطرح، واللتان لهما تجميع يساري كذلك. كما يمكننا أيضًا التحكم في أسبقية التعابير باستخدام الأقواس () كما نفعل في الرياضيات العادية. إليك المثال التالي: // 4 pi R^3 - 4 pi r^3 = حجم السطح الكروي double vol = 4.0 * pi * R * R * R / 3.0 - 4.0 * pi * r * r * r / 3.0; // الإضافة int a = 2 + 4 / 2; // = 2+(4/2) = 4 int b = (3 + 3) / 2; // = (3+3)/2 = 3 // مع الضرب int c = 3 + 4 / 2 * 6; // = 3+((4/2)*6) = 15 int d = 3 * (3 + 6) / 9; // = (3*(3+6))/9 = 3 // القسمة والباقي int g = 3 - 3 % 1; // = 3 % 1 = 0 3 - 0 = 3 int h = 3 - (3 % 1); // = 3 % 1 = 0 3 - 0 = 3 int i = 3 - 3 / 1 % 3; // = 3 / 1 = 3 3 % 3 = 0 3 - 0 = 3 int l = 3 - (3 / 1) % 3; // = 3 / 1 = 3 3 % 3 = 0 3 - 0 = 3 int m = 3 - (3 / (1 % 3)); // = 1 % 3 = 1 3 / 1 = 3 3 - 3 = 0 هذا الدرس جزء من سلسلة دروس عن C++. ترجمة -وبتصرّف- للفصل Chapter 3: operator precedence من كتاب C++ Notes for Professionals اقرأ أيضا الدرس 4: حسابيات الأعداد العشرية الدرس 2: القيم مصنَّفة النوع
-
القيم مصنَّفة النوع (Literals) هي عبارات تدل على ثابت تشير صياغته المحرفية إلى نوعه وقيمته، فمثلًا 42 هي قيمة مصنَّفة النوع بينما x ليست كذلك لأنك تحتاج إلى أن تنظر إلى تصريحها (declaration) لتعرف نوعها وتطالع الأسطر السابقة من الشيفرة لتعرف قيمتها. غير أن C++11 أتاحت للمستخدم إضافة قيم مصنَّفة النوع (User-Defined Literals)، وتُستخدم كاختصار لاستدعاءات الدوال. سنستَعرض في هذا الدرس بعض القيم مصنَّفة النوع في C++، بدءًا بالقيمة this، ثم نستعرض بقية القيم في الأقسام التالية. الكلمة المفتاحية this تشير الكلمة المفتاحية this داخل دالة تابعة (member function) إلى نسخة من الصنف التي استدعيّت الدالة عليها، ولا يمكن استخدامها -أي this- في دالة تابعة ساكنة (static member function). struct S { int x; S & operator = (const S & other) { x = other.x; // تعيد مرجعًا إلى الكائن الذي أسندَت إليه return *this; } }; يعتمد نوع this على خاصية التأهيل الخاص بالتابع -سواء التأهيل المتطاير volatile أو الثابت constant- (بالإنجليزية اختصارًا: cv-qualification). فمثلًا، إن كان التابع X::f ثابتًا (const)، فإنّ نوع this داخل f سيكون مؤشرًا ثابتًا إلى X (أي const X*)، لذلك لا يمكن استخدام this لتعديل الحقول غير الساكنة (non-static data members) من داخل دالة const تابعة. وبالمثل، يكتسب this التأهيل المتطاير (volatile qualification) من الدالة التي يظهر فيها. الإصدار ≥ C++11 يمكن أيضًا استخدام this في المُهيِّئ المعقوص (brace-or-equal-initializer) للحقول غير الساكنة. struct S; struct T { T(const S * s); // ... }; struct S { // ... T t { this }; }; this عبارة عن قيمة يمينيّة (rvalue)، أي أنّها تعبير لا يمكن أن يكون إلا في الطرف الأيمن من أيّ عملية إسناد (assignment)، لذلك لا يمكن إسناد قيمة لها. الأعداد الصحيحة قيمةً ونوعًا (Integer literal) تأخذ صياغة العدد الصحيح أحد الأشكال التالية: نوع عشري (decimal-literal): وهو عدد مؤلف من رقم عشري يخالف الصفر (1، 2، 3، 4، 5، 6، 7، 8، 9)، متبوعًا برقم عشريّ واحد أو أكثر (0، 1، 2، 3، 4، 5، 6، 7، 8، 9)، مثلًا: int d = 42; نوع ثماني (octal-literal): وهو عدد يبدأ بالرقم صفر (0) متبوعًا برقم ثُمانيّ (octal) واحد أو أكثر (0، 1، 2، 3، 4، 5، 6، 7)، مثلًا: int o = 052; نوع ست عشري (hex-literal): وهو تسلسل الأحرف 0x أو 0X متبوعًا برقم واحد أو أكثر من الأرقام الست عشرية (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, A, b, B, c, C, d, D, e, E, f, F)، مثلًا: int x = 0x2a; int X = 0X2A; نوع ثنائي (binary-literal): (منذ C ++ 14) هو تسلسل الأحرف 0b أو 0B متبوعًا برقم ثنائي واحد أو أكثر (0، 1)، مثلا: int b = 0b101010; الأعداد الصحيحة الملحوقة غير المؤشّرة (unsigned-suffix)، قد تحتوي على أحد أو كلا الأمرين التاليين -قد يظهرا بدون ترتيب محدد في حالة وجودهما معًا: الأعداد الملحوقة غير المُؤشرة (unsigned-suffix): هي أعداد ملحوقة بالحرف u أو U، مثلا: unsigned int u_1 = 42u; الأعداد الملحوقة الطويلة (long-suffix): هي أعداد ملحوقة بالحرف l أو L، أو الأعداد الملحوقة الطويلة المزدوجة (long-long-suffix)، وهي أعداد ملحوقة بالحرفين ll أو الحرفين LL (منذ C++11) تأخذ المتغيرات التالية نفس القيمة: unsigned long long l1 = 18446744073709550592ull; // C++11 unsigned long long l2 = 18'446'744'073'709'550'592llu; // C++14 unsigned long long l3 = 1844'6744'0737'0955'0592uLL; // C++14 unsigned long long l4 = 184467'440737'0'95505'92LLU; // C++14 ملاحظات الأحرف الموجودة في الأعداد الصحيحة غير حساسة للحالة (case-insensitive): فتمثل الصيغتان 0xDeAdBaBeU و 0XdeadBABEu نفس العدد (الاستثناء الوحيد هو الأعداد الملحوقة الطويلة المزدوجة، والتي يمكن أن تكون إما ll أو LL، ولكن ليس lL أو Ll). لا توجد صياغة لأعداد سالبة، فمثلًا تطبق تعابير مثل -1 عامل السالب الأحادي (unary minus operator) على القيمة المُمثّلة بالقيمة مصنفَّة النوع (Literal)، والتي قد تتضمن تحويلات ضمنية للنوع (implicit type conversions). في إصدارات C السابقة للإصدار C99 (ولكن ليس في C++)، يُسمح للقيم العشرية غير الملحوقة (unsuffixed decimal) من غير النوع long int أن تكون من النوع unsigned long int (عدد صحيح طويل عديم الإشارة). تتصرف جميع الثوابت الصحيحة المؤشَّرة (signed integer constants) عند استخدامها في التعبيرات الشرطية -مثل #if أو #elif- كما لو كانت من النوع std::intmax_t، فيما تتصرف الثوابت الصحيحة غير المؤشرة كما لو كانت من النوع std::uintmax_t. القيم المنطقية الكلمة المفتاحية true true هي كلمة مفتاحية تمثّل إحدى القيمتين المنطقيّتين المُمكنَتين للنوع bool. bool ok = true; if (!f()) { ok = false; goto end; } الكلمة المفتاحية false false هي كلمة مفتاحية تمثل إحدى القيمتين المنطقيّتين المُمكنتين للنوع bool. bool ok = true; if (!f()) { ok = false; goto end; } الكلمة المفتاحية nullptr الإصدار ≥ C++ 11 nullptr هي كلمة مفتاحية تمثل مؤشرًا ثابتًا فارغًا (null pointer constant)، ويمكن تحويلها إلى أيّ نوع من المؤشرات (Pointers) أو المؤشرات إلى الأعضاء (pointer-to-member)، وتعيد مؤشرًا فارغًا يشير إلى النوع الناتج. Widget* p = new Widget(); delete p; p = nullptr; // تفريغ المؤشر بعد الحذف لاحظ أنّ nullptr ليست مؤشرًا بحد ذاتها، وإنّما هي نوع أساسي معروف باسم std::nullptr_t. void f(int * p); template < class T > void g(T * p); void h(std::nullptr_t p); int main() { f(nullptr); // ok g(nullptr); // error h(nullptr); // ok } هذا الدرس جزء من سلسلة دروس عن C++. ترجمة -وبتصرّف- للفصل Chapter 2: Literals من كتاب C++ Notes for Professionals اقرأ أيضا الدرس 3: أسبقية العمليات الدرس 1: مدخل إلى C++
-
مرحبا بالعالم يطبع البرنامج التالي العبارة مرحبًا بالعالم! في مجرى الخرج القياسي (standard output stream): #include <iostream> int main() { std::cout << "مرحبا بالعالم!" << std::endl; } يمكنك رؤية التجربة الحية على Coliru. تحليل البرنامج لنحلل كل جزء من شيفرة البرنامج بالتفصيل: #include <iostream> هو موجّه معالجة مسبقة (preprocessor directive)، ويتضمن محتوى iostream، وهي ترويسة ملف C++ القياسي (standard C++ header file). الكلمة iostream هي ترويسة ملف مكتبة قياسية (standard library header file)، وتحتوي على تعريفات مُجريَا الدخل والخرج القياسيين (standard input and output streams). هذه التعريفات مُتضمنة في فضاء الاسم std، كما هو موضح أدناه. يوفر مُجريا الدخل/الخرج القياسيين (I/O) طريقة للبرامج يمكنه عبرها جلب مدخلات من مخرجات خاصة بنظام خارجي، والذي يكون طرفية في العادة. int main() { ... } تُعرِّف هذه الشيفرة دالة جديدة باسم main تستدعى عند تنفيذ البرنامج. يجب أن تكون هناك دالة main واحدة لكل برنامج C++، ويجب أن تعيد دائمًا عددًا من النوع int. يمثل int نوع القيمة التي تعيدها الدالة والتي تمثل رمز الخروج (exit code) الخاص بالدالة main. البرامج ذات رمز الخروج المساوي للقيمة 0 أو EXIT_SUCCESS تُعد ناجحة من قبل النظام الذي ينفّذ ذلك البرنامج. ترتبط كل رموز الخروج الأخرى بأخطاء تحددها هي. في حال عدم استخدام التعليمة return، ستعيد الدالة main (وبالتالي البرنامج نفسه) القيمة 0 افتراضيًّا. في هذا المثال لا نحتاج إلى كتابة ;return 0. جميع الدوال الأخرى، باستثناء تلك التي تُعيد النوع void، يجب أن تُعيد قيمة بشكل صريح من النوع الذي يُفترض أن تعيده، أو لا ينبغي أن تعيد أي قيمة على الإطلاق. std::cout << "مرحبا بالعالم!" << std::endl; تطبع هذه التعليمة السلسلة النصية "مرحبا بالعالم!" في مجرى الخرج القياسي، وتتألف من الأجزاء التالية: std: هي فضاء اسم، و :: هو عامل تحليل النطاق (scope resolution operator) الذي يسمح بالبحث عن الكائنات بأسمائها ضمن فضاء الاسم. هناك العديد من فضاءات الاسم. هنا نستخدم :: لإظهار أننا نريد استخدام cout من فضاء الاسم std. لمزيد من المعلومات، ارجع إلى المقال Scope Resolution Operator في توثيق ميكروسوفت. std::cout: هو كائن مجرى الخرج القياسي (standard output)، وهو مُعرّف في iostream، ويُطبع أيّ شيء يُمرَّر إليه في مجرى الخرج القياسي (stdout). >>: هي، في هذا السياق، عامل الإدراج (stream insertion operator)، ويُسمى كذلك لأنه يدرج كائنًا في كائن المجرى (stream object). تُعرِّف المكتبة القياسية العامل >> لإدراج بعض أنواع البيانات في مجاري الخرج. تدرج التعليمة stream << content المحتوى content في المجرى stream ثم تعيد المجرى نفسه بعد تحديثه. يسمح ذلك بإجراء الإدراجات المتسلسلة، مثلًا، تطبع التعليمة std::cout << "Foo" << " Bar"; السلسة النصية "Foo Bar" في سطر الأوامر. "مرحبا بالعالم!": هي سلسلة نصية حرفية (character string literal). عامل الإدراج الخاص بالسلاسل النصية مُعرّف في الملف iostream. std::endl: هو كائن خاص لمعالجة مجرى I/O، وهو مُعرّف في الملف iostream. إنّ إدراج معالج في مجرى ما يغير حالة ذلك المجرى. يقوم معالج المجرى std::endl بعملين: أولًا، يدرج محرف نهاية السطر (end-of-line character)، ثم ينقل البيانات (flushes) الموجودة في المخزن المؤقت (stream buffer) الخاص بالمجرى لجعل النص يظهر في سطر الأوامر. يضمن هذا أنّ البيانات المُدرجة في المجرى ستظهر بالفعل في وحدة التحكم. (تُخزنّ بيانات المجرى عادة في مخزن مؤقت، ثم تُنقَل على دفعات، إلا إذا أمرت بنقلها فوريًا.) هناك طريقة بديلة تُجنِّب نقل البيانات من المخزن المؤقت، وهي: std::cout << "Hello World!\n"; // سطرًا جديدًا \n يمثل الفاصلة المنقوطة ;: تُخطر الفاصلة المنقوطة (;) المُصرّف (compiler) بأنّ التعليمة البرمجية قد انتهت. تتطلب كل عبارات C++ وتعريفات الأصناف استخدام فاصلة منقوطة في النهاية. التعليقات التعليقات هي جزء من الشيفرة يتجاهلها مصرف C++. تُستخدم التعليقات لتوضيح بعض الجوانب التي قد تكون غامضة بخصوص تصميم أو طريقة عمل البرنامج. هناك نوعان من التعليقات في C++: التعليقات السطرية (Single-Line Comments) تجعل الشرطتان المائِلتان // النص الذي يليها وحتى بداية السطر الجديد تعليقًا: int main() { // هذا تعليق سطري int a; // هذا تعليق سطري كذلك int i; // هذا أيضا تعليق سطري } التعليقات الكُتلية (Block Comments) يُستخدَم المحرفان /* للإعلان عن بداية تعليق كتلي، فيما يُستخدَم المحرفان */ للإعلان عن نهاية التعليق. يُفسَّر النص الموجود بين العبارتين على أنه تعليق، حتى لو كان النص الموجود بينهما شيفرة C++ صالحة. يسمى هذا النوع من التعليقات عادة تعليقات "C-style"، لأنّ صياغة هذ النوع من التعليقات موروثة من سلف C++، أي اللغة C: int main() { /* * هذا تعليق كتلي */ int a; } يمكنك كتابة ما تشاء في التعليقات الكتلية لكن عندما يجد المصرّف رمز نهاية التعليق*/، فإنه ينهي التعليق الكتلي: int main() { /* تعليق كتلي يمتد * على عدة أسطر * وينتهي في السطر التالي */ int a; } يمكن أيضًا أن تبدأ التعليقاتُ الكتلية وتنتهي في سطر واحد. مثلا: void SomeFunction(/* الوسيط الأول */ int a, /* الوسيط الثاني */ int b); أهمية التعليقات كما هو الحال مع جميع لغات البرمجة، توفر التعليقات العديد من الفوائد، منها: توثيق ضمني للشيفرة البرمجية لتسهيل قراءتها وصيانتها شرح الغرض من الشيفرة البرمجية ودوالها توفير تفاصيل حول تاريخ الشيفرة أو المنطق وراءها وضع حقوق الطبع والنشر/التراخيص، أو ملاحظات حول المشروع، أو شكر خاص، أو التنويه بالمساهمين، وما إلى ذلك مباشرة في الشيفرة المصدرية (source code). من جهة أخرى، فإنّ للتعليقات جانبًا سلبيًا كذلك: يجب تعديلها لتعكس أي تغييرات في الشيفرة الإفراط في إدراج التعليقات قد يؤثر سلبًا على مقروئية الشيفرة. يمكن تقليل الحاجة إلى التعليقات عبر كتابة شيفرة واضحة ومُوثقة ذاتيًا (self-documenting). أحد الأمثلة على ذلك هو استخدام أسماء توضيحية للمُتغيرات والدوال والأنواع. وتوزيع المهام المترابطة منطقيًا في دوال منفصلة. تعليق الشيفرة واختبارها أثناء التطوير، يمكن أيضًا استخدام التعليقات لتعطيل أجزاء من الشيفرة بسرعة دون حذفها. غالبًا ما يكون هذا مفيدًا في مرحلة الاختبار أو التنقيح (debugging)، ولكن ينبغي محو تلك التعليقات بعد الانتهاء. يشار إلى هذا غالبًا باسم "تعليق الشيفرة" (commenting out). بالمثل، الاحتفاظ بالإصدارات القديمة من أجزاء من الشيفرة في التعليقات لجعلها متاحة للمراجعة قد يكون مزعجًا، لأنّ ذلك يُراكم الكثير من الشيفرة غير المستخدمة، ولا يضيف قيمة تذكر موازنةً بالاطلاع على تاريخ الشيفرة عبر نظام إصدارات (versioning system). عملية التصريف القياسية في C++ تُنتَج برامج C++ القابلة للتنفيذ عادة بواسطة المُصرّف (compiler)، وهو برنامج يترجم الشيفرة من لغة برمجة إلى شيفرة تنفيذية تفهمها الآلة وتُنفذ على الحاسوب من قبل المستخدم النهائي. استخدام المصرّف لتصريف الشيفرة يسمى عملية التصريف (compilation process). ورثت C++ آلية التصريف من سلفها، أي اللغة C. فيما يلي قائمة توضح الخطوات الرئيسية الأربع للتصريف في C++: ينسخ المعالج الأولي (preprocessor) للغة C++ محتويات كل ملفات الترويسات (header files) المتضمنة في ملف الشيفرة المصدري، ويولد «شيفرة عملية بدل» (macro code، ويعوّض الثوابت الرمزية (symbolic constants) المعرفّة باستخدام #define بقِيمهما. تُصرَّف الشيفرة المصدرية الموسعة التي أُنتِجت بواسطة المعالج الأولي لـ C++ إلى لغة التجميع (assembly language) المناسبة لنظام التشغيل. تُصرَّف الشيفرة المجمّعة التي أُنتِجت بواسطة المصرّف إلى تعليمات مُصرّفة (object code) مناسبة لنظام التشغيل. تُربَط التعليمات المُصرَّفة المُولّدة من قبل المجمّع (assembler) مع ملفات التعليمات المُصرَّفة (object code files) الخاصة بدوال المكتبات المستخدمة لإنتاج الملف القابل للتنفيذ. ملاحظة: أحيانًا تُربط بعض الشيفرات المُصرَّفة معًا، ولكن ليس لغرض إنشاء برنامج نهائي، إذ يمكن تحزيم (packaging) الشيفرة "المربوطة" لأجل استخدامها من قبل برامج أخرى. الحزم الناتجة هي ما يشير إليه مبرمجو C++ بالمكتبات (libraries). تدمج العديد من مصرّفات C++ أو تفكّك بعض مراحل عملية التصريف لتسهيل العملية، أو لأجل التحليل الإضافي. يستخدم مُبرمجو C++ أدوات مختلفة، لكنها جميعًا تتبع المقاربة أعلاه. الدوال الدالة هي كتلة من الشيفرة تحتوي سلسلة من العبارات. يمكن للدوال قبول وسائط (arguments) أو قيم، ويمكن أن تعيد قيمة واحدة، أو قد لا تعيد أي قيمة. لاستخدام دالة، يجب استدعاؤها مع تمرير وسائط إليها، ثم تعيد قيمة. لكل دالة بصمة نوعيّة (type signature)، والتي تمثل أنواع وسائطها، ونوع القيمة المعادة. الدوال مستوحاة من مفهومَي الإجراء (procedure) والدالة في الرياضيات. ملاحظات: دوال C++ هي إجراءات (procedures) بالأساس، ولا تتبع بدقة قواعد الدوال الرياضية وفق مفهوم علوم الرياضيات. تؤدي الدوال في العادة مهام محددة ويمكن استدعاؤها من أجزاء أخرى من البرنامج. كما يجب التصريح عن الدالة وتعرِيفها قبل استدعائها في البرنامج. قد تُخفى تعريفات الدوال الشائعة والمهمة في ملفات أخرى مُضمّنة، فذلك يسهِّل إعادة استخدامها في البرامج. وهذا أحد الاستخدامات الشائعة لملفات الترويسات (header files). التصريح عن دالة التصريح عن دالة (Function Declaration) هو عملية الإعلان عن وجود دالة، مع توضيح اسمها وبصمتها النوعيّة للمصرف. ويتبع الصياغة التالية: // الدالة التالية تقبل عددًا صحيحًا وتعيد عددًا صحيحًا int add2(int i); في المثال أعلاه، تخبر int add2(int i) المصرّف بالمعلومات التالية عن الدالة المُصرَّح عنها: نوع القيمة المعادة هو int. اسم الدالة هو add2. عدد وسائط الدالة هو 1: الوسيط الأول من النوع int. سيُشار إلى الوسيط الأول داخل الدالة بالاسم i. اسم الوسيط اختياري؛ إذ يمكن التصريح بالدالة كما يلي: int add2(int); // يمكن حذف اسم الوسيط وفقًا لقاعدة التعريف الواحد (one-definition rule)، لا يمكن التصريح بدالة ذات بصمة (signature) نوعيّة معينة أو تعريفها أكثر من مرة واحدة في كامل شيفرة C++ المرئية للمصرّف. بمعنى آخر، لا يمكن إعادة تعريف دالة ذات بصمة نوعية معيّنة إلا مرة واحدة فقط. وبالتالي، فالشيفرة التالية غير صالحة في C++ وسيطلق المصرف خطأ عند تنفيذها: // int هي دالة من النوع add2 المصرف سيعلم أن int add2(int i); // (int) -> int // لا يمكن إعادة تعريف نفس الدالة بنفس البصمة int add2(int j); // (int) -> int إذا لم تُعِد الدالة أي قيمة، فإنّ نوع القيمة المعادة سيُكتب void. إذا لم تأخذ الدالة أيّ معاملات، فينبغي أن تكون قائمة المعاملات فارغة. // الدالة التالية لا تأخذ أي وسائط، ولا تعيد أي قيمة void do_something(); لاحظ أنه ما يزال بمقدور الدالة do_something التأثير في المتغيرات التي يمكنها الوصول إليها. استدعاء الدالة يمكن استدعاء الدالة بعد التصريح عنها. على سبيل المثال، يستدعي البرنامج التالي الدالة add2 مع القيمة 2 داخل الدالة main: #include <iostream> int add2(int i); // add2 تعريف الدالة int main() { // في هذا الموضع add2(2) سيتم تقييم std::cout << add2(2) << "\n"; // وستُطبع النتيجة return 0; } بالنظر إلى الشيفرة السابقة، نجد أنَّه ما تزال الدالة add2 تحتاج إلى توفير طريقة تنفيذ لها بالإضافة إلى تعريفها (جسم الدالة)، رغم أن تنفيذ الدالة لا يظهر مباشرة في الشيفرة، إذ يمكن جلبه من ملف آخر بربط هذا الملف مع الشيفرة التي تستدعي الدالة. تمثل add2(2) صياغة استدعاء دالة. تعريف الدالة تعريف الدالة يشبه التصريح عنها، إلا أنه يحتوي أيضًا على الشيفرة الذي ستُنفّذ عند استدعاء الدالة، هذه الشيفرة تُسمى جسم الدالة (function body). فيما يلي مثال عن تعريف الدالة add2: // i القيمة التي ستُمرر إلى الدالة سيشار إليها بالاسم int add2(int i) { // بين القوسين يمثل داخل نطاق أو جسم الدالة int j = i + 2; return j; } زيادة تحميل الدوال يمكنك إنشاء عدة دوال تشترك في نفس الاسم، ولكن مع اختلاف المعاملات، أي تختلف بصمة الدالة فقط، وهذا ما يعرف «بزيادة تحميل دالة» (Function Overloading). // التنفيذ الأول int add2(int i) { int j = i + 2; return j; } // التنفيذ الثاني int add2(int i, int j) { int k = i + j + 2; return k; // } كلتا الدالتين تحملان الاسم add2، بيْد أنّ الدالة المُنفّذة تعتمد على عدد وأنواع المعاملات المُمررة في الاستدعاء. في معظم الحالات، يمكن لمصرّف C++ أن يحدد الدالة المراد استدعاؤها. لكن في بعض الحالات يجب ذكر النوع بوضوح. فسيتم تقييم الشيفرة المحتواة في التعريف الأول عند استدعاء الدالة نع معامل واحد بالشكل add2(1). وفي حال استدعاء الدالة مع معاملين بالشكل add2(1, 3)، فسيتم تقييم الشيفرة المتضمنة في التعريف الثاني بدلًا من التعريف الأول. المعاملات الافتراضية يمكن تحديد القيم الافتراضية لمعاملات الدالة (Default Parameters) في تعريفات الدوال فقط. // 7 هي b القيمة الافتراضية للمعامل int multiply(int a, int b = 7); // جسم الدالة int multiply(int a, int b) { return a * b; } في هذا المثال، يمكن استدعاء multiply() مع معامل واحد أو مُعاملين. في حال تمرير معامل واحدة فقط، فستكون القيمة الافتراضية للمعامل b هي 7 وسيتم ضرب القيمة الممررة تلك بالمعامل الافتراضي 7 آنذاك. يجب وضع المعاملات الافتراضية في الأخير على النحو التالي: // تعريف صحيح int multiply(int a = 10, int b = 20); // مُقدّمة int a تعريف غير صحيح لأن int multiply(int a = 10, int b); استدعاءات خاصة للدوال - العوامل توجد استدعاءات خاصة في C++ للدوال، وهي ذات صياغة مختلفة عن الصياغة التقليدية name_of_function(value1, value2, value3). المثال الأكثر شيوعا هي العوامل (operators). وهي تسلسلات أحرف خاصة تحوَّل إلى استدعاءات دوال من طرف المصرّف، من أمثلة ذلك، ! و + و - و * و % و << وغيرها. عادةً ما ترتبط هذه المحارف الخاصة باستخدامات غير برمجية، أو تُستخدم للتبسيط (على سبيل المثال، المحرف + يمثل عادةً مفهوم الإضافة). تعالج C++ هذه التسلسلات؛ وتحوّل كل عامل إلى استدعاء الدالة المقابلة. على سبيل المثال، التعبير التالي: 3+3 يكافئ استدعاء الدالة التالية: operator+(3, 3) تبدأ جميع أسماء دوال العوامل بـ operator. في لغة C، لا يمكن تعيين معان جديدة لأسماء دوال العوامل عبر كتابة تعريفات إضافية ذات بصمات نوعية مختلفة، في C++، هذا جائز. يشار إلى ربط تعريفات إضافية بنفس اسم الدالة باسم التحميل الزائد للعامل (operator overloading) في C++، وهو اصطلاح شائع نسبيًا، ولكنه غير عام في C++. التصريح عن الدوال وقواعد مرئيتها في C++، يجب التصريح عن الشيفرة أو تعريفها قبل استخدامها. على سبيل المثال، ينتج عن الشيفرة التالية خطأ في وقت التصريف: int main() { foo(2); // (*) } void foo(int x) {} سيطلق خطأ، لأن الدالة foo استُدعِيت في السطر (*) قبل أن تُعرّف، إذ لا ترى الدالة main هذا التعريف المتأخر للدالة foo بعد استدعائها ضمنها. هناك طريقتان لحل هذه المشكلة: إما التصريح عن foo() أو تعريفها قبل استخدامها في main(). هذا مثال على ذلك: // ووضع جسمها أولا foo التصريح عن الدالة void foo(int x) {} int main() { // الآن foo يمكن استدعاء الدالة foo(2); } من الممكن أيضًا التصريح المسبق (forward-declaration) عن الدالة عن طريق وضع تصريح عن قالبها (prototype declaration) قبل موضع استدعائها والذي يحدد نوع القيمة المعادة واسم الدالة وعدد وسائطها وأنواعها، ثم تعريف جسمها لاحقًا: // foo تصريح عن قالب لدالة باسم void foo(int); int main() { foo(2); // (*) } // ينبغي أن تطابق تصريح القالب أعلاه void foo(int x) { // هنا foo تعريف جسم } أصبحت الدالة foo مرئية، لذا يمكن استدعاؤها الآن ضمن الدالة main في السطر (*) رغم أنها لم تُعرّف بعد. يجب أن يحدد تصريح النموذج نوع القيمة المعادة (void)، واسم الدالة (foo)، وأنواع المتغيرات في قائمة الوسائط (int)، لكنّ أسماء الوسائط غير ضرورية. يمكن وضع تصريحات قوالب الدوال في ترويسة الملف: // foo.h الملف // تصريح عن قالب دالة void foo(int); ثم تقديم التعريف الكامل في موضع آخر: // foo.cpp --> foo.o // foo تضمين الملف الذي يحوي قالب الدالة #include "foo.h" // foo تعريف جسم الدالة void foo(int x) { } ثم، بمجرد تصريف الشيفرة، يمكن ربط كائن الملف (object file) المقابل foo.o بالكائن المصرّف حيث يتم استخدامه في مرحلة الربط، main.o: // main.cpp --> main.o // foo تصريح عن قالب الدالة #include "foo.h" int main() { foo(2); } // (*) استدعاء الدالة foo ممكن هنا، لأنه سبق التصريح عنها، وتعريف قالب ومتن الدالة foo مربوط عبر كائنات الملفات (object files). يحدث الخطأ "رمز خارجي غير محلول" (unresolved external symbol) إذا صادف المصرّف تصريحَ قالب واستدعاء لدالة، دون وجود لجسمها (body). قد يكون حل هذه الأخطاء معقدًا، لأنّ المصرّف لن يبلِّغ عن الخطأ حتى مرحلة الربط النهائية، ولن يعرف السطر الذي يجب الانتقال إليه في الشيفرة لإظهار الخطأ. المعالج الأولي المعالج الأولي (Preprocessor) هو جزء مهم من المصرّف إذ يقوم بتعديل الشيفرة المصدرية، وحذف بعض البتات، أو تغييرها، وإضافة أشياء أخرى. في الملفات المصدرية، يمكننا تضمين مُوجِّهات (directives) للمعالج الأولي. تخبر تلك الموجِّهات المعالج الأولي بتنفيذ إجراءات محددة. يبدأ الموجّه بالرمز # في سطر جديد مثل: #define ZERO 0 من أشهر مُوجهات المعالج الأولي، الموجهات التالية: #include <something> يأخذ هذا الموجه something ويُدرجه في ملفك حيث يظهر الموجه. مثلا، يبدأ برنامج "مرحبا بالعالم!" بالسطر التالي: #include <iostream> يضيف هذا السطر الدوال والكائنات التي تتيح لك استخدام المدخلات والمخرجات القياسية. لا تحتوي اللغة C (والتي تستخدم أيضًا المُعالج الأولي) نفس القدر من ترويسات الملفات كخَلَفها C++، كما أنه يمكنك في C++ استخدام جميع ترويسات C. المُوجِّه الثاني في الأهمية هو على الأرجح الموجّه التالي: #define something something_else يخبر هذا المُوجِّه المعالج الأولي بأنه يجب أن يبدِّل something مكان كل ظهور لـ something_else في الملف الحالي. يمكن أن يجعل هذا الموجّه بعض الأشياء تتصرف مثل دوال، لكنه مفهوم متقدم أشرنا إليه قبل قليل على أنه «شيفرة عملية بدل» (macro code)، ولن نتطرق إليه الآن. something_else ليس ضروريًا، ولكن في حال عدم إضافة something، سيُحذَف كل ظهور لـ something. هذا مفيد جدا، إذ يمكن استخدامه مع المُوجِّهات #if و #else و #ifdef. وفق الصياغة التالية: #if something == true // شيفرة #else // شيفرة أخرى #endif #ifdef thing_that_you_want_to_know_if_is_defined // شيفرة #endif تدرِج هذه الموجِّهات الشيفرة الموجودة في البت الصحيح، وتحذف الآخرين. يمكن استخدام هذا لتضمين أجزاء من الشيفرة حصرًا في أنظمة تشغيل معينة دون الحاجة إلى إعادة كتابة الشيفرة بالكامل. هذه المقالة جزء من سلسلة مقالات عن C++. ترجمة -وبتصرّف- للفصل Chapter 1: Getting started with C++ من الكتاب C++ Notes for Professionals اقرأ أيضًا الدرس 2: القيم مصنَّفة النوع
-
طُوِّرَت بايثون في أواخر الثمانينات، وأُطلقَت لأول مرة عام 1991. استُوحي اسمها من المجموعة الكوميدية البريطانية Monty Python، تُعدُّ بايثون خليفة للغة البرمجة ABC متعددة الأغراض. تضمنت بايثون في إصداراتها الأولى معالجة الاستثناءات، والدوال، والأصناف والوراثة. table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } سيرشدك هذا الدرس إلى أفضل آليات وممارسات ترحيل الشيفرات من بايثون 2 إلى بايثون 3، وما إن كان عليك جعل الشيفرة متوافقة مع كلا الإصدارين. خلفية عامة صدر الإصدار 2 من بايثون عام 2000، ليُدشِّن حقبةً جديدةً من التطوير تقوم على الشفافية والشمولية، إذ شمل هذا الإصدار العديد من الميزات البرمجية، واستمر في إضافة المزيد طوال مدة تطويره. يُعد إصدار بايثون 3 مستقبل بايثون، وهو إصدار اللغة قيد التطوير حاليًا، فجاء في أواخر عام 2008، ليعالج العديد من عيوب التصميم الداخلية ويُعدِّلها. بيْد أنَّ اعتماد بايثون 3 كان بطيئًا بسبب عدم توافقه مع بايثون 2. في خضم ذلك، جاء الإصدار بايثون 2.7 في عام 2010 ليكون آخر إصدارات بايثون 2.x وليُسِّهل على مستخدمي بايثون 2.x الانتقال إلى بايثون 3 من خلال توفير قدر من التوافق بين الاثنتين، فهذا هو الهدف الأساسي من إطلاقه. يمكنك معرفة المزيد حول إصدارات بايثون والاختيار من بينها من المقالة: اعتبارات عملية للاختيار ما بين بايثون 2 و بايثون 3. ابدأ ببايثون 2.7 للانتقال إلى بايثون 3، أو لدعم بايثون 2 وبايثون 3 معًا، يجب عليك التأكد من أنّ شيفرة بايثون 2 متوافقة تمامًا مع بايثون 2.7. يعمل العديد من المطورين حصريًا بشيفرات بايثون 2.7، أمَّا المبرمجون الذي يعملون بشيفرات أقدم، فعليهم أن يتأكدوا من أنّ الشيفرة تعمل جيدًا مع بايثون 2.7، وتتوافق معه. التأكُّد من توافق الشيفرة مع بايثون 2.7 أمرٌ بالغ الأهمية لأنه الإصدار الوحيد من بايثون 2 الذي ما يزال قيد الصيانة، وتُصحَّحُ ثغراته. فإذا كنت تعمل بإصدار سابق من بايثون 2، فستجد نفسك تتعامل مع مشكلات في شيفرة لم تعد مدعومة، ولم تعد ثغراتها تُصحَّح. هناك أيضًا بعض الأدوات التي تسِّهل ترحيل الشيفرة، مثل الحزمة Pylint التي تبحث عن الأخطاء البرمجية، لكن لا تدعمها إصدارات بايثون السابقة للإصدار 2.7. من المهم أن تضع في حسبانك أنَّه رغم أنَّ بايثون 2.7 ما زالت قيد الدعم والصيانة في الوقت الحالي، إلا أنَّها ستموت في النهاية. ستجد في PEP 373 تفاصيل الجدول الزمني لإصدار بايثون 2.7، وفي وقت كتابة هذا المقال، فإنّ أجل بايثون 2.7 حُدِّد في عام 2020 (يحتمل أن تكون قد ماتت وأنت تقرأ هذه السطور :-| ). الاختبار اختبار الشيفرة جزءٌ أساسيٌّ من عملية ترحيل شيفرة بايثون 2 إلى بايثون 3. فإذا كنت تعمل على أكثر من إصدار واحد من بايثون، فعليك أيضًا التحقُّق من أنَّ أدوات الاختبار التي تستخدمها تغطي جميع الإصدارات للتأكُّد من أنَّها تعمل كما هو متوقع. يمكنك إضافة حالات بايثون التفاعلية (interactive Python cases) إلى السلاسل النصية التوثيقية (docstrings) الخاصة بكافة الدوال والتوابع والأصناف والوحدات، ثم استخدام الوحدة doctest المضمنة للتحقق من أنها تعمل كما هو موضح، إذ يعدُّ ذلك جزءًا من عملية الاختبار. إلى جانب doctest، يمكنك استخدام الحزمة package.py لتتبع وحدة الاختبار. ستراقب هذه الأداة برنامجك وتحدد الأجزاء التي تُنفِّذها من الشيفرة، والأجزاء التي يمكن تنفيذها ولكن لم تُنفَّذ. يمكن أن تطبع Cover.py تقارير في سطر الأوامر، أو تنتج مستند HTML. تُستخدم عادةً لقياس فعالية الاختبارات، إذ توضح الأجزاء من الشيفرة التي اختُبِرت، والأجزاء التي لم تُختبَر. تَذكَّر أنه ليس عليك اختبار كل شيء، لكن تأكَّد من تغطية أيّ شيفرة غامضة أو غير عادية. للحصول على أفضل النتائج، يُنصح أن تشمل التغطية 80٪ من الشيفرة. تعرف على الاختلافات بين بايثون 2 و بايثون 3 سيمكّنك التعرّف على الاختلافات بين بايثون 2 و بايثون 3 من استخدام الميزات الجديدة المتاحة، أو التي ستكون متاحة في بايثون 3. تتطرق مقالتنا حول "اعتبارات عملية للاختيار ما بين بايثون 2 و بايثون 3" إلى بعض الاختلافات الرئيسية بين الإصدارين. يمكنك أيضًا مراجعة توثيق بايثون الرسمي لمزيد من التفاصيل. عند البدء في ترحيل الشيفرة، فهناك بعض التغييرات في الصياغة عليك تنفيذها فوريًا. print حلت الدالة print() في بايثون 3 مكان التعليمة print في بايثون 2: بايثون 2 بايثون 3 "مرحبا بالعالم!" print ("مرحبا بالعالم!")print exec تغيَّرت التعليمة exec في بايثون 2 وأصبحت دالةً تسمح بمتغيرات محلية (locals) وعامة (globals) صريحة في بايثون 3: بايثون 2 بايثون 3 exec code exec(code) exec code in globals exec(code, globals) exec code in (globals, locals) exec(code, globals, locals) / و // تُجرِي بايثون 2 القسمة التقريبية (floor division) بالعامل / ، بينما تخصص بايثون 3 العامل // لإجراء القسمة التقريبية: بايثون 2 بايثون 3 5 / 2 = 2 5 / 2 = 2.5 5 // 2 = 2 لاستخدام هذين المعاملين في بايثون 2، استورد division من الوحدة __future__: from __future__ import division اقرأ المزيد عن قسمة الأعداد الصحيحة من المقالة: اعتبارات عملية للاختيار ما بين بايثون 2 و بايثون 3. raise في بايثون 3، يتطلب إطلاق الاستثناءات ذات الوسائط استخدام الأقواس، كما لا يمكن استخدام السلاسل النصية كاستثناءات: بايثون 2 بايثون 3 raise Exception, args raise Exception raise Exception(args) raise Exception, args, traceback raise Exception(args).with_traceback(traceback) raise "Error" raise Exception("Error") except في بايثون 2، كان من الصعب إدراج الاستثناءات المُتعدِّدة، لكن ذلك تغيَّر في بايثون 3. لاحظ أنَّ as تُستخدَم صراحةً مع except في بايثون 3: بايثون 2 بايثون 3 except Exception, variable: except AnException as variable: except (OneException, TwoException) as variable: def في بايثون 2، يمكن للدوال أن تقبل سلاسل مثل الصفوف أو القوائم. أمَّا في بايثون 3، فقد أزيل هذا الأمر. بايثون 2 بايثون 3 def function(arg1, (x, y)): def function(arg1, x_y): x, y = x_y expr لم تعد صياغة علامة الاقتباس المائلة `` في بايثون 2 صالحة، واستخدم بدلًا عنها repr()أوstr.format()` في بايثون 3. بايثون 2 بايثون 3 `x = `355/113`` `x = repr(355/113):` تنسيق السلاسل النصية (String Formatting) لقد تغيرت صياغة تنسيق السلاسل النصية من بايثون 2 إلى بايثون 3. بايثون 2 بايثون 3 `"%d %s" % (i, s)` `"{} {}".format(i, s)` `"%d/%d=%f" % (355, 113, 355/113)` `"{:d}/{:d}={:f}".format(355, 113, 355/113)` تعلم كيفية استخدام تنسيقات السلاسل النصية في بايثون 3 من مقالة كيفية استخدام آلية تنسيق السلاسل النصية في بايثون 3. class ليست هناك حاجة لتمرير object في بايثون 3. بايثون 2 class MyClass(object): pass بايثون 3 class MyClass: pass في بايثون 3، تُضبَط الأصناف العليا (metaclasses) بالكلمة مفتاحية metaclass. بايثون 2: class MyClass: __metaclass__ = MyMeta class MyClass(MyBase): __metaclass__ = MyMeta بايثون 3: class MyClass(metaclass=type): pass class MyClass(MyBase, metaclass=MyMeta): pass تحديث الشيفرة هناك أدَاتان رئيّسيتان لتَحديث الشيفرة تلقائيًا إلى بايثون 3 مع الحفاظ على توافقيّتها مع بايثون 2 وهما: future و modernize. تختلف آليَتا عمل هاتين الأداتين، إذ تحاول future نقل أفضل ممارسات بايثون 3 إلى بايثون 2، في حين أنّ modernize تسعى إلى إنشاء شيفرات موحدة لبايثون تتوافق مع 2 و 3 وتستخدم الوحدة six لتحسين التوافقية. يمكن أن تساعدك هاتان الأداتان في إعادة كتابة الشيفرة وتحديد ورصد المشاكل المحتملة وتصحيحها. يمكنك تشغيل الأداة عبر مجموعة unittest لفحص الشيفرة والتحقق منها بصريًا، والتأكد من أنّ المراجعات التلقائية التي أُجريَت دقيقة. وبمجرد انتهاء الاختبارات، يمكنك تحويل الشيفرة. بعد هذا، ستحتاج على الأرجح إلى إجراء مراجعة يدوية، وخاصة استهداف الاختلافات بين بايثون 2 و 3 المذكورة في القسم أعلاه. إن أردت استخدام future، فعليك إضافة عبارة الاستيراد التالية في جميع وحدات بايثون 2.7: from __future__ import print_function, division, absolute_imports, unicode_literals رغم أن هذا لن يعفيك من إعادة كتابة الشيفرة، إلا أنه سيضمن لك أن تتماشى شيفرة بايثون 2 مع صياغة بايثون 3. أخيرًا، يمكنك استخدام الحزمة pylint لتحديد ورصد أي مشكلات محتملة أخرى في الشيفرة. تحتوي هذه الحزمة على مئات القواعد التي تغطي مجموعة واسعة من المشكلات التي قد تطرأ، بما فيها قواعد الدليل PEP 8، بالإضافة إلى أخطاء الاستخدام. قد تجد أنّ بعض أجزاء شيفرتك تربك pylint وأدوات الترحيل التلقائي الأخرى. حاول تبسيطها، أو استخدم unittest. التكامل المستمر (Continuous Integration) إذا أردت أن تجعل شفرتك متوافقة مع عدة إصدارات من بايثون، فستحتاج إلى تشغيل الإطار unittest باستمرار وفق مبدأ التكامل المستمر (وليس يدويًا)، أي أن تفعل ذلك أكبر عدد ممكن من المرات أثناء عملية التطوير. إذا كنت تستخدم الحزمة six لصيانة التوافقية بين بايثون 2 و 3، فستحتاج إلى استخدام عدة بيئات عمل لأجل الاختبار. إحدى حزم إدارة البيئة التي قد تكون مفيدة لك هي الحزمة tox، إذ ستفحص تثبيتات الحزمة مع مختلف إصدارات بايثون، وإجراء الاختبارات في كل بيئة من بيئات عملك، كما يمكن أن تكون بمثابة واجهة عمل للتكامل المستمر. خلاصة من المهم أن تعلم أنه مع ازدياد تركيز المطورين على بايثون 3، فستصبح اللغة أكثر دقةً وتماشيًا مع احتياجات المبرمجين، وسيضعف دعم بايثون 2.7. إذا قرَّرت أن تجعل شيفرتك متوافقة مع كل من بايثون 2 و بايثون 3، فقد تواجه صعوبة في ذلك لأنَّ بايثون 2 ستتلقى دعمًا أقل مع مرور الوقت. هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال How To Port Python 2 Code to Python 3 لصاحبته Lisa Tagliaferri اقرأ أيضًا المقالة السابقة: كيف تستخدم التسجيل Logging في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون كتاب البرمجة بلغة بايثون تعرف على أبرز مميزات لغة بايثون
-
التنقيح (debugging) هو جزء من عملية تطوير البرمجيات، ويروم البحث عن الأخطاء والمشاكل في الشيفرة، والتي تحول دون تنفيذ البرنامج تنفيذًا صحيحًا. الوحدة code هي إحدى الأدوات المفيدة التي يمكن استخدامها لمحاكاة المترجم (interpreter) التفاعلي، إذ توفر هذه الوحدة فرصةً لتجربة الشيفرة التي تكتبها. قبل أن تكمل هذا المقال، أنصحك بمطالعة المقال السابق عن منقح بايثون: كيف تستخدم منقح بايثون. فهم الوحدة code بدلاً من تفحص الشيفرة باستخدام منقح، يمكنك إضافة الوحدة code لوضع نقاط لإيقاف تنفيذ البرنامج، والدخول في الوضع التفاعلي لتفحص ومتابعة كيفية عمل الشيفرة. الوحدة code هي جزء من مكتبة بايثون القياسية. هذه الوحدة مفيدةٌ لأنَّها ستمكنك من استخدام مترجم دون التضحية بالتعقيد والاستدامة التي توفرها ملفات البرمجة. فيمكنك عبر استخدام الوحدة code تجنب استخدام الدالة print() في شيفرتك لأجل التنقيح، لأنها طريقة غير عملية. لاستخدام هذه الوحدة في تنقيح الأخطاء، يمكنك استخدام الدالة interact() الخاصة بالوحدة code، والتي توقف تنفيذ البرنامج عند استدعائها، وتوفر لك سطر أوامر تفاعلي حتى تتمكن من فحص الوضع الحالي لبرنامجك. تُكتَب هذه الدالة هكذا: code.interact(banner=None, readfunc=None, local=None, exitmsg=None) تُنفِّذ هذه الدالة حلقة اقرأ-قيِّم-اطبع (تختصر إلى REPL، أي Read–eval–print loop، وتنشئ نسخة من الصنف InteractiveConsole، والذي يحاكي سلوك مترجم بايثون التفاعلي. هذه هي المعاملات الاختيارية: banner: يمكن أن تعطيه سلسلة نصية لتعيين موضع إطلاق المترجم. readfunc: يمكن استخدامه مثل التابع InteractiveConsole.raw_input(). local: سيعينّ فضاء الأسماء (namespace) الافتراضي لحلقة المترجم (interpreter loop). exitmsg: يمكن إعطاؤه سلسلة نصية لتعيين موضع توقف المترجم. مثلًا، يمكن استخدام المعامل local بهذا الشكل: local=locals() لفضاء أسماء محلي. local=globals() لفضاء أسماء عام. local=dict(globals(), **locals()) لاستخدام كل من فضاء الأسماء العام، وفضاء الأسماء المحلي الحالي. المعامل exitmsg جديد، ولم يظهر حتى إصدار بايثون 3.6، لذلك إن كنت تستخدم إصدارًا أقدم، فحدّثه، أو لا تستخدم المعامل exitmsg. ضع الدالة interact() حيث تريد إطلاق المترجم التفاعلي في الشيفرة. كيفية استخدام الوحدة code لتوضيح كيفية استخدام الوحدة code، سنكتب بُريمجًا عن الحسابات المصرفية يسمى balances.py. سنعيّن المعامل المحلي عند القيمة locals() لجعل فضاء الأسماء محليًّا. # `code` استيراد الوحدة import code bal_a = 2324 bal_b = 0 bal_c = 409 bal_d = -2 account_balances = [bal_a, bal_b, bal_c, bal_d] def display_bal(): for balance in account_balances: if balance < 0: print("Account balance of {} is below 0; add funds now." .format(balance)) elif balance == 0: print("Account balance of {} is equal to 0; add funds soon." .format(balance)) else: print("Account balance of {} is above 0.".format(balance)) # لبدء المترجم بفضاء أسماء محلي interact() استخدام code.interact(local=locals()) display_bal() لقد استدعينا الدالة code.interact() مع المعامل local=locals() لاستخدام فضاء الأسماء المحلي كقيمة افتراضية داخل حلقة المترجم. لنُنفِّذ البرنامج أعلاه باستخدام الأمر python3 إذا لم نكن تعمل في بيئة افتراضية، أو الأمر python خلاف ذلك: python balances.py بمجرد تنفيذ البرنامج، سنحصل على المخرجات التالية: Python 3.5.2 (default, Nov 17 2016, 17:05:23) [GCC 5.4.0 20160609] on linux Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) >>> سيتم وضع المؤشر في نهاية السطر >>>، كما لو أنك في سطر الأوامر التفاعلي. من هنا، يمكنك استدعاء الدالة print() لطباعة المتغيرات والدوال وغير ذلك: >>> print(bal_c) 409 >>> print(account_balances) [2324, 0, 409, -2] >>> print(display_bal()) Account balance of 2324 is 0 or above. Account balance of 0 is equal to 0, add funds soon. Account balance of 409 is 0 or above. Account balance of -2 is below 0, add funds now. None >>> print(display_bal) <function display_bal at 0x104b80f28> >>> نرى أنه باستخدام فضاء الأسماء المحلي، يمكننا طباعة المتغيرات، واستدعاء الدالة. يُظهر الاستدعاء الأخير للدالة print() أنّ الدالة display_bal موجودة في ذاكرة الحاسوب. بعد أن تنتهي من العمل على المترجم، يمكنك الضغط على CTRL + D في الأنظمة المستندة إلى *نكس، أو CTRL + Z في أنظمة ويندوز لمغادرة سطر الأوامر ومتابعة تنفيذ البرنامج. إذا أردت الخروج من سطر الأوامر دون تنفيذ الجزء المتبقي من البرنامج، فاكتب quit()، وسيتوقف البرنامج. في المثال التالي، سنستخدم المُعاملين banner و exitmsg: # لبدء المترجم interact() استخدم الدالة code.interact(banner="Start", local=locals(), exitmsg="End") display_bal() عند تنفيذ البرنامج، ستحصل على المخرجات التالية: Start >>> يتيح لك استخدام المعامل banner تعيين عدة نقاط داخل شيفرتك، مع القدرة على تحديدها. على سبيل المثال، يمكن أن يكون لديك معامل banner يطبع السلسلة النصية "In for-loop" مع معامل exmsg يطبع "Out of for-loop"، وذلك حتى تعرف مكانك بالضبط في الشيفرة. من هنا، يمكننا استخدام المترجم كالمعتاد. بعد كتابة CTRL + D للخروج من المترجم، ستحصل على رسالة الخروج، وسيتم تنفيذ الدالة: End Account balance of 2324 is 0 or above. Account balance of 0 is equal to 0, add funds soon. Account balance of 409 is 0 or above. Account balance of -2 is below 0, add funds now. سيتم تنفيذ البرنامج بالكامل بعد الجلسة التفاعلية. بمجرد الانتهاء من استخدام الوحدة code لتنقيح الشيفرة، يجب عليك إزالة دوال الوحدة code وعبارة الاستيراد حتى يُنفَّذ البرنامج مثل المعتاد. خلاصة تُستخدَم الوحدة code لإطلاق سطر الأوامر التفاعلي لتفحُّص الشيفرة خطوةً بخطوة بقصد فهم سلوكها، وتعديل الشيفرة إن لزم الأمر. لقراءة المزيد حول هذا الموضوع، يمكنك مطالعة التوثيق الرسمي للوحدة code. هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال How To Debug Python with an Interactive Console لصاحبته Lisa Tagliaferri اقرأ أيضًا المقالة التالية: كيف تستخدم التسجيل Logging في بايثون 3 المقالة السابقة: كيف تستخدم منقح بايثون المرجع الشامل إلى تعلم لغة بايثون كتاب البرمجة بلغة بايثون
-
التعددية الشكلية (Polymorphism) هي القدرة على استخدام واجهة موحدة لعدة أشكال مختلفة، مثل أنواع البيانات أو الأصناف . هذا يسمح للدوال باستخدام كيانات من أنواع مختلفة. بالنسبة للبرامج الكائنية في بايثون، هذا يعني أنه يمكن استخدام كائن معين ينتمي إلى صنف مُعيَّن كما لو كان ينتمي إلى صنف مختلف. تسمح التعددية الشكلية بكتابة شيفرات مرنة ومجرّدَة وسهلة التوسيع والصيانة. سوف تتعلم في هذا الدرس كيفية تطبيق التعددية الشكلية على أصناف بايثون. ما هي التعددية الشكلية؟ التعددية الشكلية هي إحدى السمات الأساسية للأصناف في بايثون، وتُستخدَم عندما تكون هناك توابع لها نفس الأسماء في عدة أصناف، أو أصناف فرعية. يسمح ذلك للدوال باستخدام كائنات من أيٍّ من تلك الأصناف والعمل عليها دون الاكتراث لنوعها. يمكن تنفيذ التعددية الشكلية عبر [الوراثة](رابط المقالة السابقة)، أو باستخدام توابعِ الأصناف الفرعية، أو إعادة تعريفها (overriding). يستخدم بايثون نظام أنواع (typing) خاص، يسمى «نظام التحقق من الأنواع: البطة نموذجًا» (Duck Typing)، وهو حالة خاصة من أنظمة التحقق من الأنواع الديناميكية (Dynamic Typing). يستخدم هذا النظامُ التعدُّديةَ الشكلية، بما في ذلك الربط المتأخر، والإيفاد الديناميكي. يعتمد هذا النظام على «نموذج البطة» بناءً على اقتباسٍ للكاتب جيمس ويتكومب رايلي: خُصِّص هذا المفهوم من قبل مهندس الحاسوب الإيطالي أليكس مارتيلي (Alex Martelli) في رسالة إلى مجموعة comp.lang.python، يقوم نظام التحقق من الأنواع هذا الذي يعتمد البطة نموذجًا على تعريف الكائن من منظور ملاءمة الغرض الذي أُنشِئ لأجله. عند استخدام نظام أنواع عادي، فإنّ ملاءمة الكائن لغرض مُعيَّن يتحدد بنوع الكائن فقط، ولكن في نموذج البطة، يَتحدَّد ذلك بوجود التوابع والخاصيات الضرورية لذلك الغرض بدلًا من النوع الحقيقي للكائن. بمعنى آخر، إذا أردت أن تعرف إن كان الكائن بطةً أم لا، فعليك التحقق مما إذا كان ذلك الكائن يمشي مشي البطة، وصوته كصوت البطة، بدلًا من أن تسأل عما إذا كان الكائن بطةً. عندما تحتوي عدة أصناف أو أصناف فرعية على توابع لها نفس الأسماء، ولكن بسلوكيات مختلفة، نقول إنّ تلك الأصناف متعددة الأشكال (polymorphic) لأنها تستعمل واجهة موحدة يمكن استخدامها مع كيانات من أنواع مختلفة. يمكن للدوال تقييم ومعالجة هذه التوابع متعدِّدة الأشكال دون معرفة أصنافها. إنشاء أصناف متعددة الأشكال للاستفادة من التَعدُّدية الشكلية، سننشئ صنفين مختلفين لاستخدامهما مع كائنين مختلفين. يحتاج هذان الصنفان المختلفان واجهة موحدة يمكن استخدامها بطريقة تعدُّدية الشكل (polymorphically)، لذلك سنعرّف فيهما توابع مختلفة، ولكن لها نفس الاسم. سننشئ صنفًا باسم Shark وصنفُا آخر باسم Clownfish، وسيُعرِّف كل منهما التوابع swim() و swim_backwards() و skeleton(). class Shark(): def swim(self): print("القرش يسبح.") def swim_backwards(self): print("لا يمكن للقرش أن يسبح إلى الوراء، لكن يمكنه أن يغوص إلى الوراء.") def skeleton(self): print("هيكل القرش مصنوع من الغضروف.") class Clownfish(): def swim(self): print("سمكة المهرج تسبح.") def swim_backwards(self): print("يمكن لسمكة المهرج أن تسبح إلى الخلف.") def skeleton(self): print("هيكل سمكة المهرج مصنوع من العظام.") في الشيفرة أعلاه، لدى الصنفين Shark و Clownfish ثلاث توابع تحمل نفس الاسم بيْد أنّ وظائف تلك التوابع تختلف من صنف لآخر. دعنا نستنسخ (instantiate) من هذين الصنفين كائنين: ... sammy = Shark() sammy.skeleton() casey = Clownfish() casey.skeleton() عند تنفيذ البرنامج باستخدام الأمر python polymorphic_fish.py، يمكننا أن نرى أنّ كل كائن يتصرف كما هو متوقع: هيكل القرش مصنوع من الغضروف. هيكل سمكة المهرج مصنوع من العظام. الآن وقد أصبح لدينا كائنان يستخدمان نفس الواجهة، فبمقدورنا استخدام هذين الكائنين بنفس الطريقة بغض النظر عن نوعيهما. التعددية الشكلية في توابع الأصناف لإظهار كيف يمكن لبايثون استخدام الصنفين المختلفين اللذين عرّفناهما أعلاه بنفس الطريقة، سننشئ أولاً حلقة for، والتي ستمر على صف من الكائنات. ثم سنستدعي التوابع بغض النظر عن نوع الصنف الذي ينتمي إليه كل كائن. إلا أننا سنفترض أنّ تلك التوابع موجودة في كل تلك الأصناف. ... sammy = Shark() casey = Clownfish() for fish in (sammy, casey): fish.swim() fish.swim_backwards() fish.skeleton() لدينا كائنان، sammy من الصنف Shark، و casey من الصنف Clownfish. تمر حلقة for على هذين الكائنين، وتستدعي التوابع swim() و swim_backwards() و skeleton() على كل منها. عند تنفيذ البرنامج، سنحصل على المخرجات التالية: القرش يسبح. لا يمكن للقرش أن يسبح إلى الوراء، لكن يمكنه أن يغوص إلى الوراء. هيكل القرش مصنوع من الغضروف. سمكة المهرج تسبح. يمكن لسمكة المهرج أن تسبح إلى الخلف. هيكل سمكة المهرج مصنوع من العظام. مرت الحلقة for على الكائن sammy من الصنف Shark، ثم على الكائن casey المنتمي إلى الصنف Clownfish، لذلك نرى التوابع الخاصة بالصنف Shark قبل التوابع الخاصة بالصنف Clownfish. يدلُّ هذا على أنَّ بايثون تستخدم هذه التوابع دون أن تعرف أو تعبأ بتحديد نوع الصنف الخاص بالكائنات. وهذا مثال حي على استخدام التوابع بطريقة مُتعدِّدَة الأشكال. التعددية الشكلية في الدوال يمكننا أيضًا إنشاء دالة تقبل أيّ شيء، وهذا سيسمح باستخدام التعددية الشكلية. لننشئ دالة تسمى in_the_pacific()، والتي تأخذ كائنًا يمكننا تسميته fish. رغم أننا سنستخدم الاسم fish، إلا أنه يمكننا استدعاء أي كائن في هذه الدالة: … def in_the_pacific(fish): بعد ذلك، سنجعل الدالة تستخدم الكائن fish الذي مرّرناه إليها. وفي هذه الحالة، سنستدعي التابع swim() المعرّف في كل من الصنفين Shark و Clownfish: ... def in_the_pacific(fish): fish.swim() بعد ذلك، سننشئ نسخًا (instantiations) من الصنفين Shark و Clownfish لنمرّرهما بعد ذلك إلى نفس الدالة in_the_pacific(): ... def in_the_pacific(fish): fish.swim() sammy = Shark() casey = Clownfish() in_the_pacific(sammy) in_the_pacific(casey) عند تنفيذ البرنامج، سنحصل على المخرجات التالية: القرش يسبح. سمكة المهرج تسبح. رغم أننا مرّرنا كائنًا عشوائيًا (fish) إلى الدالة in_the_pacific() عند تعريفها، إلا أننا ما زلنا قادرين على استخدامها استخدامًا فعالًا، وتمرير نسخ من الصنفين Shark و Clownfish إليها. استدعى الكائنُ casey التابعَ swim() المُعرَّف في الصنف Clownfish، فيما استدعى الكائنُ sammy التابعَ swim() المُعرَّف في الصنف Shark. خلاصة تسمح التعدُّدية الشكلية باستخدام الكائنات بغض النظر عن نوعها، وهذا يوفر لبايثون مرونة كبيرة، وقابلية لتوسيع الشيفرة الكائنية. هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال How To Apply Polymorphism to Classes in Python 3 لصاحبته Lisa Tagliaferri اقرأ أيضًا المقالة التالية: كيف تستخدم منقح بايثون المقالة السابقة: وراثة الأصناف في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون كتاب البرمجة بلغة بايثون
-
تُسِّهل البرمجة الكائنية كتابة شيفرات قابلة لإعادة الاستخدام وتجنب التكرار في مشاريع التطوير. إحدى الآليات التي تحقق بها البرمجة الكائنية هذا الهدف هي مفهوم الوراثة (inheritance)، التي بفضلها يمكن لصنفٍ فرعي (subclass) استخدام الشيفرة الخاصة بصنف أساسي (base class، ويطلق عليه «صنف أب» أيضًا) موجود مسبقًا. سيستعرض هذا الدرس بعض الجوانب الرئيسية لمفهوم الوراثة في بايثون، بما في ذلك كيفية إنشاء الأصناف الأساسية (parent classes) والأصناف الفرعية (child classes)، وكيفية إعادة تعريف (override) التوابع والخاصيات، وكيفية استخدام التابع super()، وكيفية الاستفادة من الوراثة المتعددة (multiple inheritance). ما هي الوراثة؟ تقوم الوراثة على استخدام شيفرة صنف معين في صنف آخر أي يرث صنف يراد إنشاؤه شيفرة صنف آخر. يمكن تمثيل مفهوم الوراثة في البرمجة بالوراثة في علم الأحياء تمامًا، فالأبناء يرثون خاصيات معينة من آبائهم. ويمكن لطفل أن يرث طول والده أو لون عينيه بالإضافة إلى خاصيات أخرى جديدة خاصة فيه. كما يتشارك الأطفال نفس اسم العائلة الخاصة بآبائهم. ترث الأصناف الفرعية (subclasses، تُسمى أيضًا *الأصناف الأبناء [child classes]) التوابع والمتغيرات من *الأصناف الأساسية* (base classes، تسمى أيضًاالأصناف الآباء [parent classes]). مثلًا، قد يكون لدينا صنف أساسي يسمى Parent يحتوي متغيرات الأصناف last_name و height و eye_color، والتي سيرثها الصنف الابن Child. لمَّا كان الصنف الفرعي Child يرث الصنف الأساسي Parent، فبإمكانه إعادة استخدام شيفرة Parent، مما يسمح للمبرمج بكتابة شيفرة أوجز، وتقليل التكرار. الأصناف الأساسية تشكل الأصناف الأساسية أساسًا يمكن أن تستند إليه الأصناف الفرعية المُتفرِّعة منها، إذ تسمح الأصناف الأساسية بإنشاء أصناف فرعية عبر الوراثة دون الحاجة إلى كتابة نفس الشيفرة في كل مرة. يمكن تحويل أي صنف إلى صنف أساسي، إذ يمكن استخدامه لوحده، أو جعله قالبًا (نموذجًا). لنفترض أّنّ لدينا صنفًا أساسيًا باسم Bank_account، وصنفين فرعيين مُشتقين منه باسم Personal_account و Business_account. ستكون العديد من التوابع مشتركة بين الحسابات الشخصية (Personalaccount) والحسابات التجارية (Businessaccount)، مثل توابع سحب وإيداع الأموال، لذا يمكن أن تنتمي تلك التوابع إلى الصنف الأساسي Bank_account. سيكون للصنف Business_account توابع خاصة به، مثل تابع مخصص لعملية جمع سجلات ونماذج الأعمال، بالإضافة إلى متغير employee_identification_number موروث من الصنف الأب. وبالمثل، قد يحتوي الصنف Animal على التابعين eating() و sleeping()، وقد يتضمن الصنف الفرعي Snake تابعين إضافيين باسم hissing() و slithering() خاصين به. دعنا ننشئ صنفًا أساسيًا باسم Fish لاستخدامه لاحقًا أساسًا لأصناف فرعية تمثل أنواع الأسماك. سيكون لكل واحدة من تلك الأسماك أسماء أولى وأخيرة، بالإضافة إلى خصائص مميزة خاصة بها. سننشئ ملفًا جديدًا يسمى fish.py ونبدأ بالباني، والذي سنعرّف داخله متغيري الصنف first_name و last_name لكل كائنات الصنف Fish، أو أصنافه الفرعية. class Fish: def __init__(self, first_name, last_name="Fish"): self.first_name = first_name self.last_name = last_name القيمة الافتراضية للمتغير last_name هي السلسلة النصية "Fish"، لأننا نعلم أنّ معظم الأسماك سيكون هذا هو اسمها الأخير. لنُضف بعض التوابع الأخرى: class Fish: def __init__(self, first_name, last_name="Fish"): self.first_name = first_name self.last_name = last_name def swim(self): print("The fish is swimming.") def swim_backwards(self): print("The fish can swim backwards.") لقد أضفنا التابعين swim() و swim_backwards() إلى الصنف Fish حتى يتسنى لكل الأصناف الفرعية استخدام هذه التوابع. ما دام أنّ معظم الأسماك التي ننوي إنشاءها ستكون عظمية (أي أنّ لها هيكلا عظميًا) وليس غضروفية (أي أن لها هيكلًا غضروفيًا)، فيمكننا إضافة بعض الخاصيات الإضافية إلى التابع __init__(): class Fish: def __init__(self, first_name, last_name="Fish", skeleton="bone", eyelids=False): self.first_name = first_name self.last_name = last_name self.skeleton = skeleton self.eyelids = eyelids def swim(self): print("The fish is swimming.") def swim_backwards(self): print("The fish can swim backwards.") لا يختلف بناء الأصناف الأساسية عن بناء أي صنف آخر، إلا أننا نصممها لتستفيد منها الأصناف الفرعية المُعرّفة لاحقًا. الأصناف الفرعية الأصناف الفرعية هي أصناف ترث كل شيء من الصنف الأساسي. هذا يعني أنّ الأصناف الفرعية قادرة على الاستفادة من توابع ومتغيرات الصنف الأساسي. على سبيل المثال، سيتمكن الصنف الفرعي Goldfish المشتق من الصنف Fish من استخدام التابع swim() المُعرّف في Fish دون الحاجة إلى التصريح عنه. يمكننا النظر إلى الأصناف الفرعية على أنها أقسام من الصنف الأساسي. فإذا كان لدينا صنف فرعي يسمى Rhombus (معيّن)، وصنف أساسي يسمى Parallelogram (متوازي الأضلاع)، يمكننا القول أنّ المعين (Rhombus) هو متوازي أضلاع (Parallelogram). يبدو السطر الأول من الصنف الفرعي مختلفًا قليلًا عن الأصناف غير الفرعية، إذ يجب عليك تمرير الصنف الأساسي إلى الصنف الفرعي كمعامل: class Trout(Fish): الصنف Trout هو صنف فرعي من Fish. يدلنا على هذا الكلمةُ Fish المُدرجة بين قوسين. يمكننا إضافة توابع جديدة إلى الأصناف الفرعية، أو إعادة تعريف التوابع الخاصة بالصنف الأساسي، أو يمكننا ببساطة قبول التوابع الأساسية الافتراضية باستخدام الكلمة المفتاحية pass، وهو ما سنفعله في المثال التالي: ... class Trout(Fish): pass يمكننا الآن إنشاء كائن من الصنف Trout دون الحاجة إلى تعريف أي توابع إضافية. ... class Trout(Fish): pass terry = Trout("Terry") print(terry.first_name + " " + terry.last_name) print(terry.skeleton) print(terry.eyelids) terry.swim() terry.swim_backwards() لقد أنشأنا كائنًا باسم terry من الصنف Trout، والذي سيستخدم جميع توابع الصنف Fish وإن لم نعرّفها في الصنف الفرعي Trout. يكفي أن نمرر القيمة "Terry" إلى المتغير first_name، أما المتغيرات الأخرى فقد جرى تهيئتها سلفًا. عند تنفيذ البرنامج، سنحصل على المخرجات التالية: Terry Fish bone False The fish is swimming. The fish can swim backwards. لننشئ الآن صنفًا فرعيًا آخر يعرّف تابعًا خاصا به. سنسمي هذا الصنف Clownfish. سيسمح التابع الخاص به بالتعايش مع شقائق النعمان البحري: ... class Clownfish(Fish): def live_with_anemone(self): print("The clownfish is coexisting with sea anemone.") دعنا ننشئ الآن كائنًا آخر من الصنف Clownfish: ... casey = Clownfish("Casey") print(casey.first_name + " " + casey.last_name) casey.swim() casey.live_with_anemone() عند تنفيذ البرنامج، سنحصل على المخرجات التالية: Casey Fish The fish is swimming. The clownfish is coexisting with sea anemone. تُظهر المخرجات أنّ الكائن casey المستنسخ من الصنف Clownfish قادر على استخدام التابعين __init__() و swim() الخاصين بالصنف Fish، إضافة إلى التابع live_with_anemone() الخاص بالصنف الفرعي. إذا حاولنا استخدام التابع live_with_anemone() في الكائن Trout، فسوف يُطلق خطأ: terry.live_with_anemone() AttributeError: 'Trout' object has no attribute 'live_with_anemone' ذلك أنَّ التابع live_with_anemone() ينتمي إلى الصنف الفرعي Clownfish فقط، وليس إلى الصنف الأساسي Fish. ترث الأصناف الفرعية توابع الصنف الأساسي الذي اشتُقَّت منه، لذا يمكن لكل الأصناف الفرعية استخدام تلك التوابع. إعادة تعريف توابع الصنف الأساسي في المثال السابق عرّفنا الصنف الفرعي Trout الذي استخدم الكلمة المفتاحية pass ليرث جميع سلوكيات الصنف الأساسي Fish، وعرّفنا كذلك صنفًا آخر Clownfish يرث جميع سلوكيات الصنف الأساسي، ويُنشئ أيضًا تابعًا خاصًا به. قد نرغب في بعض الأحيان في استخدام بعض سلوكيات الصنف الأساسي، ولكن ليس كلها. يُطلَق على عملية تغيير توابع الصنف الأساسي «إعادة التعريف» (Overriding). عند إنشاء الأصناف الأساسية أو الفرعية، فلا بد أن تكون لك رؤية عامة لتصميم البرنامج حتى لا تعيد تعريف التوابع إلا عند الضرورة. سننشئ صنفًا فرعيًا Shark مشتقًا من الصنف الأساسي Fish، الذي سيمثل الأسماك العظمية بشكل أساسي، لذا يتعين علينا إجراء تعديلات على الصنف Shark المخصص في الأصل للأسماك الغضروفية. من منظور تصميم البرامج، إذا كانت لدينا أكثر من سمكة غير عظمية واحدة، فيُستحب أن ننشئ صنفًا خاصًا بكل نوع من هذين النوعين من الأسماك. تمتلك أسماك القرش، على عكس الأسماك العظمية، هياكل مصنوعة من الغضاريف بدلاً من العظام. كما أنّ لديها جفونًا، ولا تستطيع السباحة إلى الوراء، كما أنها قادرة على المناورة للخلف عن طريق الغوص. على ضوء هذه المعلومات، سنعيد تعريف الباني __init__() والتابع swim_backwards(). لا نحتاج إلى تعديل التابع swim() لأنّ أسماك القرش يمكنها السباحة. دعنا نلقي نظرة على هذا الصنف الفرعي: ... class Shark(Fish): def __init__(self, first_name, last_name="Shark", skeleton="cartilage", eyelids=True): self.first_name = first_name self.last_name = last_name self.skeleton = skeleton self.eyelids = eyelids def swim_backwards(self): print("The shark cannot swim backwards, but can sink backwards.") لقد أعدنا تعريف المعاملات التي تمت تهيئتها في التابع __init__()، فأخذ المتغير last_name القيمة "Shark"، كما أُسنِد إلى المتغير skeleton القيمة "cartilage"، فيما أُسنِدَت القيمة المنطقية True إلى المتغير eyelids. يمكن لجميع نُسخ الصنف إعادة تعريف هذه المعاملات. يطبع التابع swim_backwards() سلسلة نصية مختلفة عن تلك التي يطبعها في الصنف الأساسي Fish، لأنّ أسماك القرش غير قادرة على السباحة للخلف كما تفعل الأسماك العظمية. يمكننا الآن إنشاء نسخة من الصنف الفرعي Shark، والذي سيستخدم التابع swim() الخاص بالصنف الأساسي Fish: ... sammy = Shark("Sammy") print(sammy.first_name + " " + sammy.last_name) sammy.swim() sammy.swim_backwards() print(sammy.eyelids) print(sammy.skeleton) عند تنفيذ هذه الشيفرة، سنحصل على المخرجات التالية: Sammy Shark The fish is swimming. The shark cannot swim backwards, but can sink backwards. True cartilage لقد أعاد الصنف الفرعي Shark تعريف التابعين __init__() و swim_backwards() الخاصين بالصنف الأساسي Fish، وورث في نفس الوقت التابع swim() الخاص بالصنف الأساسي. الدالة super() يمكنك باستخدام الدالة super() الوصول إلى التوابع الموروثة التي أُعيدت كتابتها. عندما نستخدم الدالة super()، فإننا نستدعي التابع الخاص بالصنف الأساسي لاستخدامه في الصنف الفرعي. على سبيل المثال، قد نرغب في إعادة تعريف جانب من التابع الأساسي وإضافة وظائف معينة إليه، ثم بعد ذلك نستدعي التابع الأساسي لإنهاء بقية العمل. في برنامج خاص بتقييم الطلاب مثلًا، قد نرغب في تعريف صنف فرعي Weighted_grade يرث الصنف الأساسي Grade، ونعيد فيه تعريف التابع calculate_grade() الخاص بالصنف الأساسي من أجل تضمين شيفرة خاصة بحساب التقدير المرجّح (weighted grade)، مع الحفاظ على بقية وظائف الصنف الأساسي. عبر استدعاء التابع super()، سنكون قادرين على تحقيق ذلك. عادة ما يُستخدم التابع super() ضمن التابع __init__()، لأنّه المكان الذي ستحتاج فيه على الأرجح إلى إضافة بعض الوظائف الخاصة إلى الصنف الفرعي قبل إكمال التهيئة من الصنف الأساسي. لنضرب مثلًا لتوضيح ذلك، دعنا نعدّل الصنف الفرعي Trout. نظرًا لأنّ سمك السلمون المرقَّط من أسماك المياه العذبة، فلنضف متغيرًا اسمه water إلى التابع __init__()، ولنُعطه القيمة "freshwater"، ولكن مع الحفاظ على باقي متغيرات ومعاملات الصنف الأساسي: ... class Trout(Fish): def __init__(self, water = "freshwater"): self.water = water super().__init__(self) ... لقد أعدنا تعريف التابع __init__() في الصنف الفرعي Trout، وغيرنا سلوكه موازنةً بالتابع __init__() المُعرَّف سلفًا في الصنف الأساسي Fish. لاحظ أننا استدعينا التابع __init__() الخاص بالصنف Fish بشكل صريح ضمن التابع __init__() الخاص بالصنف Trout،. بعد إعادة تعريف التابع، لم نعد بحاجة إلى تمرير first_name كمعامل إلى Trout، وفي حال فعلنا ذلك، فسيؤدي ذلك إلى إعادة تعيين freshwater بدلاً من ذلك. سنُهيِّئ بعد ذلك الخاصية first_name عن طريق استدعاء المتغير في الكائن خاصتنا. الآن يمكننا استدعاء متغيرات الصنف الأساسي التي تمت تهيئتها، وكذلك استخدام المتغير الخاص بالصنف الفرعي: ... terry = Trout() # تهيئة الاسم الأول terry.first_name = "Terry" # super() الخاص بالصنف الأساسي عبر __init__() استخدام print(terry.first_name + " " + terry.last_name) print(terry.eyelids) # المعاد تعريفها في الصنف الفرعي __init__() استخدام print(terry.water) # الخاص بالصنف الأساسي swim() استخدام التابع terry.swim() سنحصل على المخرجات التالية: Terry Fish False freshwater The fish is swimming. تُظهر المخرجات أنّ الكائن terry المنسوخ من الصنف الفرعي Trout قادر على استخدام المتغير water الخاص بتابع الصنف الفرعي __init__()، إضافة إلى استدعاء المتغيرات first_name و last_name و eyelids الخاصة بالتابع __init__() المُعرَّف في الصنف الأساسي Fish. يسمح لنا التابع super() المُضمن في بايثون باستخدام توابع الصنف الأساسي حتى بعد إعادة تعريف تلك التوابع في الأصناف الفرعية. الوراثة المُتعدِّدة (Multiple Inheritance) المقصود بالوراثة المتعددة هي قدرة الصنف على أن يرث الخاصيات والتوابع من أكثر من صنف أساسي واحد. هذا من شأنه تقليل التكرار في البرامج، ولكنه يمكن أيضًا أن يُعقِّد العمل، لذلك يجب استخدام هذا المفهوم بحذر. لإظهار كيفية عمل الوراثة المتعددة، دعنا ننشئ صنفًا فرعيًا Coral_reef يرث من الصنفين Coral و Sea_anemone. يمكننا إنشاء تابع في كل صنف أساسي، ثم استخدام الكلمة المفتاحية pass في الصنف الفرعي Coral_reef: class Coral: def community(self): print("Coral lives in a community.") class Anemone: def protect_clownfish(self): print("The anemone is protecting the clownfish.") class CoralReef(Coral, Anemone): pass يحتوي الصنف Coral على تابع يسمى community()، والذي يطبع سطرًا واحدًا، بينما يحتوي الصنف Anemone على تابع يسمى protect_clownfish()، والذي يطبع سطرًا آخر. سنُمرِّر الصنفين كلاهما بين قوسين في تعريف الصنف CoralReef، ما يعني أنه سيرث الصنفين معًا. دعنا الآن ننشئ كائنًا من الصنف CoralReef: ... great_barrier = CoralReef() great_barrier.community() great_barrier.protect_clownfish() الكائن great_barrier مُشتقٌ الصنف CoralReef، ويمكنه استخدام التوابع من كلا الصنفين الأساسيين. عند تنفيذ البرنامج، سنحصل على المخرجات التالية: Coral lives in a community. The anemone is protecting the clownfish. تُظهِر المخرجات أنَّ التوابع من كلا الصنفين الأساسيين استُخدِما بفعالية في الصنف الفرعي. تسمح لنا الوراثة المُتعدِّدة بإعادة استخدام الشيفرات البرمجية المكتوبة في أكثر من صنف أساسي واحد. وإذا تم تعريف التابع نفسه في أكثر من صنف أساسي واحد، فإنّ الصنف الفرعي سيستخدم التابع الخاص بالصنف الأساسي الذي ظهر أولًا في قائمة الأصناف المُمرَّرة إليه عند تعريفه. رغم فوائدها الكثيرة وفعاليتها، إلا أنَّ عليك توخي الحذر في استخدام الوراثة المُتعدِّدة، حتى لا ينتهي بك الأمر بكتابة برامج مُعقَّدة وغير مفهومة للمبرمجين الآخرين. خلاصة تعلمنا في هذا الدرس كيفية إنشاء أصناف أساسية وفرعية، وكيفية إعادة تعريف توابع وخاصيات الأصناف الأساسية داخل الأصناف الفرعية باستخدام التابع super()، إضافة إلى مفهوم الوراثة المتعددة. الوراثة هي إحدى أهم ميزات البرمجة الكائنية التي تجعلها متوافقة مع مبدأ DRY (لا تكرر نفسك)، وهذا يحسن إنتاجية المبرمجين، ويساعدهم على تصميم برامج فعالة وواضحة. هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال Understanding Class Inheritance in Python 3 لصاحبته Lisa Tagliaferri اقرأ أيضًا المقالة التالية: كيفية تطبيق التعددية الشكلية (Polymorphism) على الأصناف المقالة السابقة: فهم متغيرات الأصناف والنسخ في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون كتاب البرمجة بلغة بايثون
- 1 تعليق
-
- 2
-
تسمح البرمجة الكائنية باستخدام متغيرات على مستوى الصنف، أو على مستوى النسخة (instance). المتغيرات هي رموز (symbols) تدل على قيمة تستخدمها في برنامجك. يشار إلى المتغيرات على مستوى الصنف باسم متغيرات الصنف (class variables)، في حين تسمى المتغيرات الموجودة على مستوى النسخة متغيرات النسخة (instance variables). إذا توقعت أن يكون المتغير متسقًا في جميع نسخ الصنف، أو عندما تود تهيئة المتغير، فالأفضل أن تُعرِّف ذلك المتغير على مستوى الصنف. أما إن كنت تعلم أن المتغير سيختلف من نسخة إلى أخرى، فالأفضل أن تعرفه على مستوى النسخة. يسعى أحد مبادئ تطوير البرمجيات هو مبدأ DRY (اختصارًا للعبارة don’t repeat yourself، والذي يعني لا تكرر نفسك) إلى الحد من التكرار في الشيفرة. أي تلتزم البرمجة الكائنية بمبدأ DRY على تقليل التكرار في الشيفرة. ستتعلم في هذا الدرس كيفية استخدام متغيرات الصنف والنسخة في البرمجة الكائنية في بايثون. متغيرات الصنف تٌعرَّف متغيرات الصنف داخل الصنف وخارج كل توابعه وعادةً ما توضع مباشرة أسفل ترويسة الصنف، وقبل الباني (constructor) والتوابع الأخرى. ولمَّا كانت مملوكة للصنف نفسه، فستُشارَك مع جميع نُسَخ ذلك الصنف. وبالتالي، سيكون لها نفس القيمة بغض النظر عن النسخة، إلا إن كنت ستستخدم متغير الصنف لتهيئة متغير معيَّن. متغير الصنف يبدو كما يلي: class Shark: animal_type = "fish" في الشيفرة أعلاه أحلنا القيمة "fish" إلى المتغير animal_type. يمكننا إنشاء نسخة من الصنف Shark (سنطلق عليها new_shark)، ونطبع المتغير باستخدام الصياغة النقطية (dot notation): class Shark: animal_type = "fish" new_shark = Shark() print(new_shark.animal_type) لننفذ البرنامج: python shark.py سيعيد البرنامج قيمة المتغير: fish دعنا نضيف مزيدًا من متغيرات الصنف، ونطبعها: class Shark: animal_type = "fish" location = "ocean" followers = 5 new_shark = Shark() print(new_shark.animal_type) print(new_shark.location) print(new_shark.followers) يمكن أن تتألف متغيرات الصنف من أي نوع من البيانات المتاحة في بايثون تمامًا مثل أي متغير آخر. استخدمنا في هذا البرنامج السلاسل النصية والأعداد الصحيحة. لننفذ البرنامج مرة أخرى باستخدام الأمر python shark.py ونرى المخرجات: fish ocean 5 يمكن للنسخة new_shark الوصول إلى جميع متغيرات الصنف وطباعتها عند تنفيذ البرنامج. تُنشَأ متغيرات الصنف عند إنشاء الصنف مباشرةً (وليس عند إنشاء نسخة منه) وتحتل موضعًا لها في الذاكرة ويمكن لأي كائن مُشتَق (نسخة) من الصنف نفسه أن يصل إليها ويقرأ قيمتها. متغيرات النسخة تختلف متغيرات النسخة عن متغيرات الصنف أن النسخة المشتقة من الصنف هي من تملكها وليس الصنف نفسه أي تكون على مستوى النسخة وسيُنشَأ متغير مستقل في الذاكرة عند إنشاء كل نسخة. هذا يعني أنّ متغيرات النسخة ستختلف من كائن إلى آخر. تُعرَّف متغيرات النسخة ضمن التوابع على خلاف متغيرات الصنف. في مثال الصنف Shark أدناه، عّرفنا متغيري النسخة name و age: class Shark: def __init__(self, name, age): self.name = name self.age = age عندما ننشئ كائنًا من الصنف Shark، سيتعيّن علينا تعريف هذه المتغيرات، عبر تمريرها كمعاملات ضمن الباني (constructor)، أو أي تابع آخر. class Shark: def __init__(self, name, age): self.name = name self.age = age new_shark = Shark("Sammy", 5) كما هو الحال مع متغيرات الأصناف، يمكننا بالمثل طباعة متغيرات النسخة: class Shark: def __init__(self, name, age): self.name = name self.age = age new_shark = Shark("Sammy", 5) print(new_shark.name) print(new_shark.age) عند تنفيذ البرنامج أعلاه باستخدام python shark.py، سنحصل على المخرجات التالية: Sammy 5 تتألف المخرجات التي حصلنا عليها من قيم المتغيرات التي هيّأناها لأجل الكائن new_shark. لننشئ كائنًا آخر من الصنف Shark يسمى stevie: class Shark: def __init__(self, name, age): self.name = name self.age = age new_shark = Shark("Sammy", 5) print(new_shark.name) print(new_shark.age) stevie = Shark("Stevie", 8) print(stevie.name) print(stevie.age) يمرِّر الكائن stevie المعاملات إلى الباني لتعيين قيم متغيرات النسخة الخاصة به. تسمح متغيرات النسخة، المملوكة لكائنات الصنف، لكل كائن أو نسخة أن تكون لها متغيرات خاصة بها ذات قيم مختلفة عن بعضها بعضًا. العمل مع متغيرات الصنف والنسخة معًا غالبًا ما تُستخدم متغيرات الصنف ومتغيرات النسخة في نفس الشيفرة، ويوضح المثال التالي يستخدم الصنف Shark الذي أنشأناه سابقًا هذا الأمر. تشرح التعليقات في البرنامج كل خطوة من خطوات العملية. class Shark: # متغيرات الصنف animal_type = "fish" location = "ocean" # name و age باني مع متغيري النسخة def __init__(self, name, age): self.name = name self.age = age # followers تابع مع متغير النسخة def set_followers(self, followers): print("This user has " + str(followers) + " followers") def main(): # الكائن الأول، إعداد متغيرات النسخة في الباني sammy = Shark("Sammy", 5) # name طباعة متغير النسخة print(sammy.name) # location طباعة متغير الصنف print(sammy.location) # الكائن الثاني stevie = Shark("Stevie", 8) # name طباعة متغير النسخة print(stevie.name) # followers لتمرير متغير النسخة set_followers استخدام التابع stevie.set_followers(77) # animal_type طباعة متغير الصنف print(stevie.animal_type) if __name__ == "__main__": main() عند تنفيذ البرنامج باستخدام python shark.py، سنحصل على المخرجات التالية: Sammy ocean Stevie This user has 77 followers fish خلاصة في البرمجة الكائنية، يشار إلى المتغيرات المُعرَّفة على مستوى الصنف بمتغيرات الصنف، في حين تسمى المتغيرات المُعرّفة على مستوى الكائن بمتغيرات النسخة. يتيح لنا هذا التمييز استخدام متغيرات ذات قيم واحدة بينها عبر متغيرات الصنف، أو استخدام متغيرات مختلفة لكل كائن على حدة عبر متغيرات النسخة. كما يضمن استخدام المتغيرات الخاصة بالصنف أو النسخة أن تكون الشيفرة متوافقة مع مبدأ DRY. هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال Understanding Class and Instance Variables in Python 3 لصاحبته Lisa Tagliaferri اقرأ أيضا المقالة التالية: وراثة الأصناف في بايثون 3 المقالة السابقة: كيفية إنشاء الأصناف وتعريف الكائنات في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون كتاب البرمجة بلغة بايثون
-
بايثون لغةٌ برمجة كائنية (object-oriented programming language). تركز البرمجة الكائنية (OOP) على كتابة شيفرات قابلة لإعادة الاستخدام، على عكس البرمجة الإجرائية (procedural programming) التي تركز على كتابة تعليمات صريحة ومتسلسلة. تتيح البرمجة الكائنية لمبرمجي بايثون كتابة شيفرات سهلة القراءة والصيانة، وهذا مفيد للغاية عند تطوير البرامج المُعقَّدة. التمييز بين الأصناف والكائنات أحدُ المفاهيم الأساسية في البرمجة الكائنية، ويوضح التعريفان التاليان الفرق بين المفهومين: الصنف - نموذج عام تُنسج على منواله كائنات يُنشِئها المبرمج. يُعرِّف الصنف مجموعةً من الخاصيات التي تميز أي كائن يُستنسَخ (instantiated) منه. الكائن - نسخةٌ (instance) من الصنف، فهو تجسيد عملي للصنف داخل البرنامج. تُستخدَم الأصناف لإنشاء أنماط، ثم تُستعمَل تلك الأنماط لإنشاء كائنات منها. ستتعلم في هذا الدرس كيفيَّة إنشاء الأصناف والكائنات، وتهيئة الخاصيات باستخدام تابعٍ بانٍ (constructor method)، والعمل على أكثر من كائن من نفس الصنف. الأصناف الأصناف هي نماذج عامة تُستخدم لإنشاء كائنات وسبق أن عرَّفناها آنفًا. تُنشَأ الأصناف باستخدام الكلمة المفتاحية class، بشكل مشابه [لتعريف الدوال](رابط المقالة 34) الذي يكون باستخدام الكلمة المفتاحية def. دعنا نعرّف صنفًا يسمى Shark، ونجعل له تابعين مرتبطين به، swim و be_awesome: class Shark: def swim(self): print("The shark is swimming.") def be_awesome(self): print("The shark is being awesome.") تُسمَّى مثل هذه الدوال «توابعًا» (methods) لأنهما معرفتان داخل الصنف Shark؛ أي أنهما دالتان تابعتان للصنف Shark. الوسيط الأول لهاتَين الدالتين هو self، وهو مرجع إلى الكائنات التي يتم بناؤها من هذا الصنف. للإشارة إلى نُسخ (أو كائنات) من الصنف، يوضع self دائمًا في البداية، لكن يمكن أن تكون معه وسائط أخرى. لا يؤدي تعريف الصنف Shark إلى إنشاء كائنات منه، وإنما يعرّف فقط النمط العام لتلك الكائنات، والتي يمكننا تعريفها لاحقًا. لذا، إذا نفّذت البرنامج أعلاه الآن، فلن يُعاد أي شيء. الكائنات الكائن هو نسخةٌ (instance) من صنف. ويمكن أن نأخذ الصنف Shark المُعرَّف أعلاه، ونستخدمه لإنشاء كائن يعدُّ نسخةً منه. سننشئ كائنًا Shark يسمى sammy: sammy = Shark() لقد أحلنا على الكائن sammy ناتج الباني Shark()، والذي يعيد نسخةً من الصنف. سنستخدم في الشيفرة التالية التابعين الخاصين بالكائن sammy: sammy = Shark() sammy.swim() sammy.be_awesome() يستخدم الكائن sammy التابعين swim() و be_awesome()، وقد استدعينَاهما باستعمال المعامل النقطي (.)، والذي يُستخدم للإشارة إلى خاصيات أو توابع الكائنات. في هذه الحالة، استدعينا تابعًا، لذلك استعملنا قوسين مثلما نفعل عند استدعاء دالة. الكلمة self هي معامل يُمرّر إلى توابع الصنف Shark، في المثال أعلاه، يمثّل self الكائن sammy. يتيح المعامل self للتوابع الوصول إلى خاصيات الكائن الذي استُدعيت معه. لاحظ أننا لم نمرر شيئًا داخل القوسين عند استدعاء التابع أعلاه، ذلك أنّ الكائن sammy يُمرّر تلقائيًا مع العامل النقطي. البرنامج التالي يوضح لنا الأمر: class Shark: def swim(self): print("The shark is swimming.") def be_awesome(self): print("The shark is being awesome.") def main(): sammy = Shark() sammy.swim() sammy.be_awesome() if __name__ == "__main__": main() لننفذ البرنامج لنرى ما سيحدث: python shark.py ستُطبع المخرجات التالية: The shark is swimming. The shark is being awesome. في الشيفرة أعلاه، استدعى الكائن sammy التابعين swim() و be_awesome() في الدالة الرئيسية main(). الباني يٌستخدم الباني (Constructor Method) لتهيئة البيانات الأولية، ويُنفَّذ لحظة إنشاء الكائن. في تعريف الصنف، يأخذ الباني الاسم __init__، وهو أول تابع يُعرّف في الصنف، ويبدو كما يلي: class Shark: def __init__(self): print("This is the constructor method.") إذا أضفت التابع __init__ إلى الصنف Shark في البرنامج أعلاه، فسيَطبع البرنامجُ المخرجات التالية: This is the constructor method. The shark is swimming. The shark is being awesome. يُنفَّذ الباني تلقائيًا، لذا يستخدمه مطورو بايثون لتهيئة أصنافهم. سنُعدِّل الباني أعلاه، ونجعله يستخدم متغيرًا اسمه name سيمثّل اسم الكائن. في الشيفرة التالية، سيكون المتغير name المعامل المُمرَّر إلى الباني، ونحيل قيمته إلى الخاصية self.name: class Shark: def __init__(self, name): self.name = name بعد ذلك، يمكننا تعديل السلاسل النصية في دوالنا للإشارة إلى اسم الصنف، على النحو التالي: class Shark: def __init__(self, name): self.name = name def swim(self): # الإشارة إلى الاسم print(self.name + " is swimming.") def be_awesome(self): # الإشارة إلى الاسم print(self.name + " is being awesome.") أخيرًا، يمكننا تعيين اسم الكائن sammy عند القيمة "Sammy" (أي قيمة الخاصية name) بتمريره إلى Shark() عند إنشائه: class Shark: def __init__(self, name): self.name = name def swim(self): print(self.name + " is swimming.") def be_awesome(self): print(self.name + " is being awesome.") def main(): # Shark تعيين اسم كائن sammy = Shark("Sammy") sammy.swim() sammy.be_awesome() if __name__ == "__main__": main() عرّفنا التابع __init__، والذي يقبل مُعاملين self و name (تذكر أن المعامل self يُمرر تلقائيا إلى التابع)، ثم عرّفنا متغيرًا فيه. عند تنفيذ البرنامج: python shark.py سنحصل على: Sammy is swimming. Sammy is being awesome. لقد طُبع الاسم الذي مرّرناه إلى الكائن. ونظرًا لأنّ الباني يُنفّذ تلقائيًا، فلست بحاجة إلى استدعائه بشكل صريح، فيكفي تمرير الوسائط بين القوسين التاليين لاسم الصنف عند إنشاء نسخة جديدة منه. إذا أردت إضافة معامل آخر، مثل age، فيمكن ذلك عبر تمريره إلى التابع __init__: class Shark: def __init__(self, name, age): self.name = name self.age = age عند إنشاء الكائن sammy، سنمرر عُمره أيضًا بالإضافة إلى اسمه: sammy = Shark("Sammy", 5) إذًا، تتيح البانيات تهيئة خاصيات الكائن لحظة إنشائه. العمل مع عدة كائنات تتيح لنا الأصناف إنشاء العديد من الكائنات المتماثلة التي تتبع نفس النمط. لتفهم ذلك بشكل أفضل، دعنا نضيف كائنًا آخر من الصنف Shark إلى برنامجنا: class Shark: def __init__(self, name): self.name = name def swim(self): print(self.name + " is swimming.") def be_awesome(self): print(self.name + " is being awesome.") def main(): sammy = Shark("Sammy") sammy.be_awesome() stevie = Shark("Stevie") stevie.swim() if __name__ == "__main__": main() لقد أنشأنا كائنًا ثانيًا من الصنف Shark يسمى stevie، ومرّرنا إليه الاسم "Stevie". في هذا المثال، استدعينا التابع be_awesome() مع الكائن sammy، والتابع swim() مع الكائن stevie. لننفذ البرنامج: python shark.py سنحصل على المخرجات التالية: Sammy is being awesome. Stevie is swimming. يبدو ظاهرًا في المخرجات أننا نستخدم كائنين مختلفين، الكائن sammy والكائن stevie، وكلاهما من الصنف Shark. تتيح لنا الأصناف إنشاء عدة كائنات تتبع كلها نفس النمط دون الحاجة إلى بناء كل واحد منها من البداية. خلاصة تطرَّقنا في هذا الدرس إلى عِدَّة مفاهيم، مثل إنشاء الأصناف، وإنشاء الكائنات، وتهيئة الخاصيات باستخدام البانيات، والعمل مع أكثر من كائن من نفس الصنف. تُعدُّ البرمجة الكائنية أحد المفاهيم الضرورية التي ينبغي أن يتعلمها كل مبرمجي بايثون، لأنها تساعد على كتابة شيفرات قابلة لإعادة الاستخدام، إذ أنَّ الكائنات التي تُنشَأ في برنامج ما يمكن استخدامها في برامج أخرى. كما أنّ البرامج الكائنية عادة ما تكون أوضح وأكثر مقروئية، خصوصًا في البرامج المعقدة التي تتطلب تخطيطًا دقيقًا، وهذا بدوره يسهل صيانة البرامج مستقبلًا. هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال How To Construct Classes and Define Objects in Python 3 لصاحبته Lisa Tagliaferri اقرأ أيضًا المقالة التالية: فهم متغيرات الأصناف والنسخ في بايثون 3 المقالة السابقة: كيفية استخدام args و *kwargs في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون كتاب البرمجة بلغة بايثون
- 1 تعليق
-
- 3
-
تعدُّ المعاملات في تعاريف الدوال كيانات مسماة تُحدِّد وسيطًا (argument) يمكن أن يُمرَّر إلى الدالة المُعرَّفة. أثناء البرمجة، قد لا تدرك جميع حالات الاستخدام الممكنة للشيفرة، لذا قد ترغب في توسيع خيارات المبرمجين المستقبليين الذين سيستخدمون الوحدة التي طورتها. سنتعلم في هذا الدرس كيفيَّة تمرير عدد متغير من الوسائط إلى دالة ما باستخدام الصياغتين *args و **kwargs. الوسيط *args في بايثون، يمكن استخدام الشكل أحادي النجمة *args كمعامل لتمرير قائمة غير محددة الطول من الوسائط غير المسماة (non-keyworded argument) إلى الدوال. تجدر الإشارة إلى أنّ النجمة (*) عنصر ضروري هنا، إذ رغم أنّ الكلمة args متعارف عليها بين المبرمجين، إلا أنها غير رسمية. لنلقِ نظرة على مثال لدالة تستخدم وسيطين: def multiply(x, y): print (x * y) عرّفنا في الشفرة أعلاه دالة تقبل وسيطين x و y، عندما نستدعي هذه الدالة، سنحتاج إلى تمرير عددين موافقين للوسيطين x و y. في هذا المثال، سنمرّر العدد الصحيح 5 إلى x، والعدد الصحيح 4 إلى y: def multiply(x, y): print (x * y) multiply(5, 4) عند تنفيذ الشفرة أعلاه: python lets_multiply.py سنحصل على المخرجات التالية: 20 ماذا لو قررنا لاحقًا أننا نود حساب ناتج ضرب ثلاثة أعداد بدلًا من عددين فقط؟ إذا حاولت تمرير عدد إضافي إلى الدالة، كما هو موضح أدناه: def multiply(x, y): print (x * y) multiply(5, 4, 3) فسيُطلَق الخطأ التالي: TypeError: multiply() takes 2 positional arguments but 3 were given إذا شككت أنك ستحتاج إلى استخدام المزيد من الوسائط لاحقًا، فالحل هو استخدام *args كمعامل. سنزيل المُعاملين x و y من الشفرة في المثال الأول، ونضع مكانهما *args: def multiply(*args): z = 1 for num in args: z *= num print(z) multiply(4, 5) multiply(10, 9) multiply(2, 3, 4) multiply(3, 5, 10, 6) عندما ننفّذ هذه الشفرة، سنحصل على ناتج استدعاءات الدالة أعلاه: 20 90 24 900 يمكننا باستخدام الوسيط *args تمرير أي عدد نحب من الوسائط عند استدعاء الدالة بالإضافة إلى كتابة شيفرة أكثر مرونة، وإنشاء دوال تقبل عددًا غير محدد مسبقًا من الوسائط غير المسماة. الوسيط **kwargs يُستخدَم الشكل ذو النجمتين **kwargs لتمرير قاموس متغير الطول من الوسائط المسماة إلى الدالة المعرّفة. مرة أخرى، النجمتان (**) ضروريتان، فمع أنّ استخدام الكلمة kwargs متعارف عليه لدى المبرمجين، إلا أنها غير رسمية. يمكن أن تأخذ **kwargs، كما هو شأن *args، أيّ عدد من الوسائط التي ترغب في تمريرها إلى الدالة بيْد أنّ **kwargs تختلف عن *args في أنها تستوجب تعيين أسماء المعاملات (keywords). في المثال التالي، ستطبع الدالة الوسيط **kwargs الممرر إليها. def print_kwargs(**kwargs): print(kwargs) سنستدعي الآن الدالة ونمرر إليها بعض الوسائط المسماة: def print_kwargs(**kwargs): print(kwargs) print_kwargs(kwargs_1="Shark", kwargs_2=4.5, kwargs_3=True) لننفذ البرنامج أعلاه: python print_kwargs.py سنحصل على المخرجات التالية: {'kwargs_3': True, 'kwargs_2': 4.5, 'kwargs_1': 'Shark'} اعتمادًا على إصدار بايثون 3 الذي تستخدمه، فقد لا يكون القاموس مرتبًا. في بايثون 3.6 وما بعده، ستحصل على أزواج قيمة-مفتاح (key-value) مرتبة، ولكن في الإصدارات السابقة، سيكون ترتيب الأزواج عشوائيًا. سيُنشَأ قاموس يسمى kwargs، والذي يمكننا التعامل معه مثل أي قاموس عادي داخل الدالة. لننشئ برنامجًا آخر لإظهار كيفية استخدام **kwargs. سننشئ دالة تطبع قاموسًا من الأسماء. أولاً، سنبدأ بقاموس يحتوي اسمين: def print_values(**kwargs): for key, value in kwargs.items(): print("The value of {} is {}".format(key, value)) print_values(my_name="Sammy", your_name="Casey") بعد تنفيذ البرنامج: python print_values.py سنحصل على مايلي: The value of your_name is Casey The value of my_name is Sammy قد لا تكون القواميس مرتَّبة، لذلك قد يظهر الاسم Casey أولاً، وقد يظهر ثانيًا. سنمرر الآن وسائط إضافية إلى الدالة لنرى كيف يمكن أن تقبل **kwargs أيّ عدد من الوسائط: def print_values(**kwargs): for key, value in kwargs.items(): print("The value of {} is {}".format(key, value)) print_values( name_1="Alex", name_2="Gray", name_3="Harper", name_4="Phoenix", name_5="Remy", name_6="Val" ) إذا نفّذنا البرنامج الآن، فسنحصل على المخرجات التالية، والتي قد تكون غير مرتبة: The value of name_2 is Gray The value of name_6 is Val The value of name_4 is Phoenix The value of name_5 is Remy The value of name_3 is Harper The value of name_1 is Alex يتيح لك استخدام **kwargs مرونةً كبيرةً في استخدام الوسائط المسماة. فعند استخدامها، لن نحتاج إلى معرفة مسبقة بعدد الوسائط التي ستمرر إلى الدالة. ترتيب الوسائط عند الخلط بين عدة أنواع من الوسائط داخل دالة، أو داخل استدعاء دالة، يجب أن تظهر الوسائط وفق الترتيب التالي: الوسائط العادية *args الوسائط المسمّاة **kwargs عمليًا، عند الجمع بين المعاملات العادية، والوسيطين *args و **kwargs، فينبغي أن تكون وفق الترتيب التالي: def example(arg_1, arg_2, *args, **kwargs): ... وعند الجمع بين المعاملات العادية والمعاملات المسماة و *args و **kwargs، ينبغي أن تكون وفق الترتيب التالي: def example2(arg_1, arg_2, *args, kw_1="shark", kw_2="blobfish", **kwargs): ... من المهم أن تأخذ في الحسبان ترتيب الوسائط عند إنشاء الدوال حتى لا تتسبب في إطلاق خطأٍ متعلقٍ بالصياغة. استخدام *args و **kwargs في استدعاءات الدوال يمكننا أيضًا استخدام *args و **kwargs لتمرير الوسائط إلى الدوال. أولاً، دعنا ننظر إلى مثال يستخدم *args: def some_args(arg_1, arg_2, arg_3): print("arg_1:", arg_1) print("arg_2:", arg_2) print("arg_3:", arg_3) args = ("Sammy", "Casey", "Alex") some_args(*args) في الدالة أعلاه، هناك ثلاثة معاملات، وهي arg_1 و arg_ و arg_3. ستطبع الدالة كل هذه الوسائط. بعد ذلك أنشأنا متغيرًا، وأَحلنا عليه عنصرًا تكراريًا (في هذه الحالة، صف)، ثم مرَّرنا ذلك المتغير إلى الدالة باستخدام الصياغة النجمية (asterisk syntax). عندما ننفّذ البرنامج باستخدام الأمر python some_args.py، سنحصل على المخرجات التالية: arg_1: Sammy arg_2: Casey arg_3: Alex يمكننا أيضًا تعديل البرنامج أعلاه، واستخدام قائمة. سندمج أيضًا *args مع وسيط مسمى: def some_args(arg_1, arg_2, arg_3): print("arg_1:", arg_1) print("arg_2:", arg_2) print("arg_3:", arg_3) my_list = [2, 3] some_args(1, *my_list) إذا نفذنا البرنامج أعلاه، فسنحصل على المخرجات التالية: arg_1: 1 arg_2: 2 arg_3: 3 وبالمثل، يمكن استخدام الوسائط المسماة **kwargs لاستدعاء دالة. سننشئ متغيرًا، ونسند إليه قاموسًا من 3 أزواج مفتاح-قيمة (سنستخدم kwargs هنا، ولكن يمكنك تسميته ما تشاء)، ثم نُمرِّره إلى دالة ذات 3 وسائط: def some_kwargs(kwarg_1, kwarg_2, kwarg_3): print("kwarg_1:", kwarg_1) print("kwarg_2:", kwarg_2) print("kwarg_3:", kwarg_3) kwargs = {"kwarg_1": "Val", "kwarg_2": "Harper", "kwarg_3": "Remy"} some_kwargs(**kwargs) عند تنفيذ البرنامج أعلاه باستخدام الأمر python some_kwargs.py، سنحصل على المخرجات التالية: kwarg_1: Val kwarg_2: Harper kwarg_3: Remy خلاصة يمكنك استخدام الصياغتين الخاصتين *args و **kwargs ضمن تَعاريف الدوال لتمرير أيّ عدد تشاء من الوسائط إليها. يُستحسن استخدام *args و **kwargs في المواقف التي تتوقع أن يظل فيها عدد المدخلات في قائمة الوسائط صغيرًا نسبيًا. وضع في ذهنك أن استخدام *args و **kwargs يحسِّن مقروئية الشيفرة، ويسهِّل على المبرمجين، ولكن ينبغي استخدامهما بحذر. هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال How To Use *args and **kwargs in Python 3 لصاحبته Lisa Tagliaferri اقرأ أيضًا المقالة التالية: كيفية إنشاء الأصناف وتعريف الكائنات في بايثون 3 المقالة السابقة: كيفية تعريف الدوال في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون كتاب البرمجة بلغة بايثون
-
الدالة (funtion) هي كتلة من التعليمات التي تنفِّذ إجراءً ما، ويمكن، بعد تعريفها، إعادة استخدامها في أكثر من موضع. تجعل الدوال الشيفرة تركيبية (modular)، مما يسمح باستخدام نفس الشفرة مرارًا وتكرارًا. تضم بايثون عددًا من الدوال المُضمّنة الشائعة، مثل: print(): تطبع كائنًا في الطرفية. int(): تحوّل أنواع البيانات النصية أو العددية إلى أعداد صحيحة. len(): تعيد طول كائن، وغيرها من الدوال. تتضمن أسماء الدوال الأقواس، وقد تتضمن معاملات أيضًا. في هذا الدرس، سنتعلم كيفية تعريف الدوال، وكيفية استخدامها في البرامج. تعريف الدالة لنبدأ بتحويل البرنامج "مرحبًا بالعالم!" إلى دالة. أنشئ ملفًا نصيًا جديدًا، وافتحه في محرر النصوص المفضل عندك، ثم استدع البرنامج hello.py. تُعرَّف الدالة باستخدام الكلمة المفتاحية def، متبوعة باسم من اختيارك، متبوعًا بقوسين يمكن أن يَحتويا المعاملات التي ستأخذها الدالة، ثم ينتهي التعريف بنقطتين. في هذه الحالة، سنعرّف دالة باسم hello(): def hello(): في الشفرة أعلاه، أعددنا السطر الأول من تعريف الدالة. بعد هذا، سنضيف سطرًا ثانيًا مُزاحًا بأربع مسافات بيضاء، وفيه سنكتب التعليمات التي ستنفّذها الدالة. في هذه الحالة، سنطبع العبارة مرحبا بالعالم في سطر الأوامر: def hello(): print("مرحبا بالعالم") لقد أتممنا تعريف دالتنا، غير أننا إن نَفَّذنا البرنامج الآن، فلن يحدث أيّ شيء، لأننا لم نستدع الدالة؛ لذلك، سنستدع الدالة عبر التعبير hello() خارج كتلة تعريف الدالة: def hello(): print("مرحبا بالعالم") hello() الآن، لننفّذ البرنامج: python hello.py يجب أن تحصل على المخرجات التالية: مرحبا بالعالم! بعض الدوال أكثر تعقيدًا بكثير من الدالة hello() التي عرّفناها أعلاه. على سبيل المثال، يمكننا استخدام for والتعليمات الشرطية، وغيرها داخل كتلة الدالة. على سبيل المثال، تستخدم الدالة المُعرّفة أدناه تعليمة شرطية للتحقق مما إذا كانت المدخلات الممرّرة إلى المتغير name تحتوي على حرف علة (vowel)، ثم تستخدم الحلقة for للمرور (iterate) على الحروف الموجودة في السلسلة النصية name. # names() تعريف الدالة def names(): # وإحالة المدخلات عليه name إعداد المتغير name = str(input('أدخل اسمك:')) # يحتوي حرف علة name التحقق من أن if set('aeiou').intersection(name.lower()): print('اسمك يحوي حرف علة') else: print('اسمك لا يحوي حرف علة') # name المرور على حروف for letter in name: print(letter) # استدعاء الدالة names() تستخدم الدالة names() التي عرّفناها أعلاه تعليمة شرطية، وحلقة for، وهذا توضيح لكيفية تنظيم الشفرة البرمجية ضمن تعريف الدالة. يمكننا أيضًا جعل التعليمة الشرطية والحلقة for دالتين منفصلتين. إنّ تعريف الدوال داخل البرامج يجعل الشفرة البرمجية تركيبية (modular)، وقابلة لإعادة الاستخدام، وذلك سيتيح لنا استدعاء نفس الدالة دون إعادة كتابة شيفرتها كل مرة. المعاملات حتى الآن، عرّفنا دالة ذات قوسين فارغين لا تأخذ أيّ وسائط (arguments)، سنتعلم في هذا القسم كيفية تعريف المعاملات (parameters) وتمرير البيانات إلى الدوال. المعامل (parameter) هو كيان مُسمًّى يوضع في تعريف الدالة، ويعرّف وسيطًا (arguments) يمكن أن تقبله الدالة عند استدعائها. دعنا ننشئ برنامجًا صغيرًا يأخذ 3 معاملات x و y و z. سننشئ دالة تجمع تلك المعاملات وفق عدة مجموعات ثم تطبع تلك حاصل جمعها. def add_numbers(x, y, z): a = x + y b = x + z c = y + z print(a, b, c) add_numbers(1, 2, 3) مرّرنا العدد 1 إلى المعامل x، و 2 إلى المعامل y، و 3 إلى المعامل z. تتوافق هذه القيم مع المعاملات المقابلة لها في ترتيب الظهور. يُجرِي البرنامج العمليات الحسابية على المعاملات على النحو التالي: a = 1 + 2 b = 1 + 3 c = 2 + 3 تطبع الدالة أيضًا a و b و c، وبناءً على العمليات الحسابية أعلاه، فإنّ قيمة a ستساوي العدد 3، و b ستساوي 4، و c ستساوي العدد 5. لننفّذ البرنامج: python add_numbers.py سنحصل على المخرجات التالية: 3 4 5 المعاملات هي وسائط يتم تعريفها عادة كمتغيرات ضمن تعريف الدالة. يمكن تعيين قيم إليها عند تنفيذ التابع بتمرير وسائط إلى الدالة. دورة تطوير التطبيقات باستخدام لغة Python احترف تطوير التطبيقات مع أكاديمية حسوب والتحق بسوق العمل فور انتهائك من الدورة اشترك الآن الوسائط المسمّاة تُستدعى المعاملات بحسب ترتيب ظهورها في تعريف الدالة، أما الوسائط المسماة (Keyword Arguments) فتُستخدَم بأسمائها في استدعاء الدالة. عند استخدام الوسائط المسمّاة، يمكنك استخدام المعاملات بأيّ ترتيب تريد، لأنّ مترجم بايثون سيستخدم الكلمات المفتاحية لمطابقة القيم مع المعاملات. سننشئ دالة تعرض معلومات الملف الشخصي للمستخدم، ونمرر إليها المُعامِلين username (سلسلة نصية)، و followers (عدد صحيح). # تعريف دالة ذات معاملات def profile_info(username, followers): print("Username: " + username) print("Followers: " + str(followers)) داخل تعريف الدالة، وضعنا username و followers بين قوسي الدالة profile_info() أثناء تعريفها. تطبع شفرة الدالة المعلومات الخاصة بالمستخدم على هيئة سلسلة نصية باستخدام المعاملين المُمرّرين. الآن، يمكننا استدعاء الدالة وتعيين المعاملات: def profile_info(username, followers): print("Username: " + username) print("Followers: " + str(followers)) # استدعاء الدالة مع تعيين المعاملات profile_info("sammyshark", 945) # استدعاء الدالة مع تمرير الوسائط المسماة إليها profile_info(username="AlexAnglerfish", followers=342) في الاستدعاء الأول للدالة، مرّرنا اسم المستخدم sammyshark، وعدد المتابعين 945 بالترتيب الوارد في تعريف الدالة. أمّا في الاستدعاء الثاني للدالة، فقد استخدمنا الوسائط المسمّاة، وقمنا بتعيين قيم للوسائط ويمكن عكس الترتيب إن شئنا. لننفذ البرنامج: python profile.py سنحصل على المخرجات التالية: Username: sammyshark Followers: 945 Username: AlexAnglerfish Followers: 342 سنحصل في المخرجات على أسماء المستخدمين، وأعداد المتابعين لكلا المستخدمين. يمكننا تغيير ترتيب المعاملات، كما في المثال التالي: def profile_info(username, followers): print("Username: " + username) print("Followers: " + str(followers)) # تغيير ترتيب المعاملات profile_info(followers=820, username="cameron-catfish") عند تنفيذ البرنامج أعلاه، سنحصل على المخرجات التالية: Username: cameron-catfish Followers: 820 يحافظ تعريف الدالة على نفس ترتيب العبارات في print()، لذلك يمكننا استخدام الوسائط المسمّاة بأيّ ترتيب نشاء. القيم الافتراضية للوسائط يمكننا إعطاء قيم افتراضية لواحد أو أكثر من المعاملات. في المثال أدناه، سنعطي للمعامل followers القيمة الافتراضية 1 لاستعمالها إن لم تُمرَّر هذه القيمة للدالة عند استدعائها: def profile_info(username, followers=1): print("Username: " + username) print("Followers: " + str(followers)) الآن، يمكننا استدعاء الدالة مع تعيين اسم المستخدم فقط، وسيُعيّن عدد المتابعين تلقائيًا ويأخذ القيمة 1. لكن يمكننا تغيير عدد المتابعين إن شئنا. def profile_info(username, followers=1): print("Username: " + username) print("Followers: " + str(followers)) profile_info(username="JOctopus") profile_info(username="sammyshark", followers=945) عندما ننفّذ البرنامج باستخدام الأمر python profile.py، سنحصل على المخرجات التالية: Username: JOctopus Followers: 1 Username: sammyshark Followers: 945 تمرير قيم إلى المعاملات الافتراضية سيتخطى القيمة الافتراضية المعطاة في تعريف الدالة. إعادة قيمة كما يمكن تمرير قيم إلى الدالة، فيمكن كذلك أن تنتج الدالة قيمة وتعيدها لم استدعاها. يمكن أن تنتج الدالة قيمة، ويكونُ ذلك عبر استخدام التعليمة return، هذه التعليمة اختيارية، وفي حال استخدامها، فستُنهِي الدالة مباشرةً عملها وتوقف تنفيذها، وتُمرَّر قيمة التعبير الذي يعقُبها إلى المُستدعي (caller). إذا لم يلي التعليمة return أي شيء، فستُعيد الدالة القيمةَ None. حتى الآن، استخدمنا الدالة print() بدلاً من return في دوالنا لطباعة شيء بدلًا من إعادته. لننشئ برنامجًا يعيد متغيرًا بدلًا من طباعته الآن. في ملف نصي جديد يسمى square.py، سننشئ برنامجًا يحسب مربع المعامل x، ويُحيل الناتج إلى المتغير y، ثم يعيده. سنطبع المتغير result، والذي يساوي ناتج تنفيذ الدالة square(3). def square(x): y = x ** 2 return y result = square(3) print(result) لننفّذ البرنامج: python square.py سنحصل على المخرجات التالية: 9 مخرجات البرنامج هي العدد الصحيح 9 الذي أعادته الدالة وهو ما نتوقعه لو طلبنا من بايثون حساب مربع العدد 3. لفهم كيفية عمل التعليمة return، يمكننا تعليق التعليمة return: def square(x): y = x ** 2 # return y result = square(3) print(result) الآن، لننفّذ البرنامج مرة أخرى: python square.py سنحصل على الناتج التالي: None بدون استخدام التعليمة return، لا يمكن للبرنامج إعادة أيّ قيمة، لذلك تُعاد القيمة الافتراضية None. إليك مثال آخر، في برنامج add_numbers.py أعلاه، سنستبدل بالتعليمة return الدالة print(). def add_numbers(x, y, z): a = x + y b = x + z c = y + z return a, b, c sums = add_numbers(1, 2, 3) print(sums) خارج الدالة، أحلنا إلى المتغير sums نتيجة استدعاء الدالة بالوسائط 1 و 2 و 3 كما فعلنا أعلاه ثم طبعنا قيمته. فلننفّذ البرنامج مرة أخرى: python add_numbers.py والناتج سيكون: (3, 4, 5) لقد حصلنا على الأعداد 3 و 4 و 5 وهي نفس المخرجات التي تلقيناها سابقًا عندما استخدمنا الدالة print() في الدالة. هذه المرة تمت إعادتها على هيئة صف لأنّ التعبير المرافق للتعليمة return يحتوي على فاصلة واحدة على الأقل. تُوقَف الدوال فورًا عندما تصل إلى التعليمة return، سواء أعادت قيمة، أم لم تُعِد. def loop_five(): for x in range(0, 25): print(x) if x == 5: # x == 5 إيقاف الدالة عند return print("This line will not execute.") loop_five() يؤدي استخدام التعليمة return داخل الحلقة for إلى إنهاء الدالة، وبالتالي لن يتم تنفيذ السطر الموجود خارج الحلقة. لو استخدمنا بدلًا من ذلك break، فسيُنفّذ السطر print() الأخير من المثال السابق. نعيد التذكير أنَّ التعليمة return تنهي عمل الدالة، وقد تعيد قيمة إذا أعقبها تعبير. استخدام main() دالةً رغم أنه يمكنك في بايثون استدعاء الدالة في أسفل البرنامج، وسيتم تنفيذها (كما فعلنا في الأمثلة أعلاه)، فإنّ العديد من لغات البرمجة (مثل C++ و Java) تتطلب الدالة main. إنّ تضمين دالة main()، وإن لم يكن إلزاميًا، يمكن أن يهيكل برامج بيثون بطريقة منطقية، بحيث تضع أهم مكونات البرنامج في دالة واحدة. كما يمكن أن يجعل البرنامج أكثر مقروئية للمبرمجين غير البايثونيِّين. سنبدأ بإضافة دالة main() إلى برنامج hello.py أعلاه. سنحتفظ بالدالة hello()، ثم نعرّف دالة main(): def hello(): print("مرحبا بالعالم") def main(): ضمن الدالة main()، سندرج الدالة print()، والتي ستعُلِمنا بأننا في الدالة main(). أيضًا سنستدعي الدالة hello() داخل main(): def hello(): print("مرحبا بالعالم") def main(): print("هذه هي الدالة الرئيسية") hello() أخيرًا، في أسفل البرنامج، سنستدعي الدالة main(): def hello(): print("مرحبا بالعالم") def main(): print("هذه هي الدالة الرئيسية.") hello() main() الآن يمكننا تنفيذ برنامجنا: python hello.py وسنحصل على المخرجات التالية: هذه هي الدالة الرئيسية. مرحبا بالعالم! لمّا استدعينا الدالة hello() داخل main()، ثم نفّذنا الدالة main() وحدها، فقد طُبع النص مرحبا بالعالم مرة واحدة فقط، وذلك عقب السلسلة النصية التي أخبرتنا بأننا في الدالة الرئيسية. سنعمل الآن مع دوال مُتعدِّدة، لذلك من المستحسن أن تراجع نطاقات المتغيرات في المقالة: كيفية استخدام المتغيرات في بايثون. إذا عرّفت متغيرًا داخل دالة، فلا يمكنك أن تستخدم ذلك المتغير إلا ضمن تلك الدالة. لذا، إن أردت استخدام متغير ما في عدة دوال، فقد يكون من الأفضل الإعلان عنه متغيرًا عامًا (global variable). في بايثون، يعدُّ '__main__' اسم النطاق الذي ستُنفَّذ فيه الشيفرة العليا (top-level code). عند تنفيذ برنامج من الدخل القياسي (standard input)، أو من سكربت، أو من سطر الأوامر، سيتم ضبط __name__ عند القيمة '__main__'. لهذا السبب، اصطلح مطورو بايثون على استخدام الصياغة التالية: if __name__ == '__main__': # الشفرة التي ستُنفّذ لو كان هذا هو البرنامج الرئيسي هذه الصياغة تتيح استخدام ملفات بايثون إما: برامج رئيسية، مع تنفيذ ما يلي التعليمة if، أو وحدات عادية، مع عدم تنفيذ ما يتبع التعليمة if. سيتم تنفيذ الشفرة غير المُتضمّنة في العبارة if __name__ == '__main__': عند التنفيذ. إذا كنت تستخدم ملف بايثون كوحدة، فسيتم أيضًا تنفيذ الشفرة البرمجية غير المُتضمّنة في هذه العبارة عند استيراد ذلك الملف. دعنا نوسع البرنامج names.py أعلاه، سننشئ ملفا جديدًا يسمى more_names.py. سنعلن في هذا البرنامج عن متغير عام، ونعدِّل الدالة names() الأصليَّة بشكل نقسِّم فيه التعليمات إلى دالّتين منفصلتين. ستتحقق الدالة الأولى has_vowel() مما إذا كانت السلسلة النصية name تحتوي على حرف علة (vowel). وتطبع الدالة الثانية print_letters() كل حرف من السلسلة النصية name. # الإعلان عن متغير عام لاستخدامه في جميع الدوال name = str(input('أدخل اسمك:')) # يحتوي حرف علة name تعريف دالة للتحقق من أن def has_vowel(): if set('aeiou').intersection(name.lower()): print('اسمك يحتوي حرف علة') else: print('اسمك لا يحتوي حرف علة') # name المرور على حروف def print_letters(): for letter in name: print(letter) بعد ذلك، دعنا نعرّف الدالة main() التي سَتستدعي كلا الدّالتين has_vowel() و print_letters(). # الإعلان عن متغير عام لاستخدامه في جميع الدوال name = str(input('أدخل اسمك:')) # يحتوي حرف علة name تعريف دالة للتحقق من أنّ def has_vowel(): if set('aeiou').intersection(name.lower()): print('اسمك يحتوي حرف علة') else: print('اسمك لا يحتوي حرف علة') # name المرور على حروف def print_letters(): for letter in name: print(letter) # التي ستستدعي بقية الدوال main تعريف الدالة def main(): has_vowel() print_letters() أخيرًا، سنضيف العبارة if __name__ == '__main__': في أسفل الملف. لقد وضعنا جميع الدوال التي نودّ تنفيذها في الدالة main()، لذا سنستدعي الدالة main() بعد العبارة if. # الإعلان عن متغير عام لاستخدامه في جميع الدوال name = str(input('أدخل اسمك:')) # يحتوي حرف علة name تعريف دالة للتحقق من أن def has_vowel(): if set('aeiou').intersection(name.lower()): print('اسمك يحتوي حرف علة') else: print('اسمك لا يحتوي حرف علة') # name المرور على حروف def print_letters(): for letter in name: print(letter) # التي ستستدعي بقية الدوال main تعريف الدالة def main(): has_vowel() print_letters() # main() تنفيذ الدالة if __name__ == '__main__': main() يمكننا الآن تنفيذ البرنامج: python more_names.py سيعرض هذا البرنامج نفس المخرجات التي عرضها البرنامج names.py، بيْد أنّ الشفرة هنا أكثر تنظيمًا، ويمكن استخدامها بطريقة تركيبية (modular). إذا لم ترغب في الإعلان عن الدالة main()، يمكنك بدلاً من ذلك إنهاء البرنامج كما يلي: ... if __name__ == '__main__': has_vowel() print_letters() يؤدي استخدام main() كدالة، واستخدام العبارة if __name__ == '__main__': إلى تنظيم الشيفرة البرمجية بطريقة منطقية، وجعلها أكثر مقروئية وتراكبية. خلاصة الدوال هي كتل من التعليمات البرمجية التي تُنفِّذ إجراءات معيّنة داخل البرنامج، كما تساعد على جعل الشفرة تركيبية، وقابلة لإعادة الاستخدام بالإضافة إلى أنها تنظمها وتسهل من قراءتها. لمعرفة المزيد حول كيفية جعل الشفرة تركيبية، يمكنك قراءة المقالة التالية: كيفية كتابة الوحدات في بايثون 3. هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال How To Define Functions in Python 3 لصاحبته Lisa Tagliaferri اقرأ أيضا المقالة التالية: كيفية استخدام args و *kwargs في بايثون 3 المقالة السابقة: كيفية استخدام تعابير break و continue و pass عند التعامل مع حلقات التكرار في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون كتاب البرمجة بلغة بايثون
-
لا تخلو لغة برمجة من التعليمات الشرطية التي تُنفَّذ بناءً على تحقق شرط معين، وهي تعليمات برمجية يمكنها التحكم في تنفيذ شفرات معينة بحسب تحقق شرط ما من عدمه في وقت التنفيذ. تُنفّذ تعليمات برامج بايثون من الأعلى إلى الأسفل، مع تنفيذ كل سطر بحسب ترتيبه. باستخدام التعليمات الشرطية، يمكن للبرامج التحقق من استيفاء شروط معينة، ومن ثم تنفيذ الشيفرة المقابلة. هذه بعض الأمثلة التي سنستخدم فيها التعليمات الشرطية: إن حصلت الطالبة على أكثر من 65٪ في الامتحان، فأعلن عن نجاحها؛ وإلا، فأعلن عن رسوبها إذا كان لديه مال في حسابه، فاحسب الفائدة. وإلا، فاحسب غرامة إن اشتروا 10 برتقالات أو أكثر، فاحسب خصمًا بمقدار 5٪؛ وإلا فلا تفعل تقيِّم الشيفرة الشرطية شروطًا، ثم تُنفِّذ شيفرةً بناءً على ما إذا تحققت تلك الشروط أم لا. ستتعلم في هذا الدرس كيفية كتابة التعليمات الشرطية في بايثون. التعليمة if سنبدأ بالتعليمة if، والتي تتحقق مما إذا تحقق شرط محدَّد أم لا، وفي حال تحقق الشرط، فستنفَّذ الشيفرة المقابلة له. لنبدأ بأمثلة عملية توضح ذلك. افتح ملفًا، واكتب الشيفرة التالية: grade = 70 if grade >= 65: print("درجة النجاح") أعطينا للمتغير grade القيمة 70. ثم استخدمنا التعليمة if لتقييم ما إذا كان المتغير grade أكبر من (>=) أو يساوي 65. وفي تلك الحالة، سيطبع البرنامج السلسلة النصية التالية: درجة النجاح. احفظ البرنامج بالاسم grade.py، ثم نفّذه في بيئة البرمجة المحلية من نافذة الطرفية باستخدام الأمر python grade.py. في هذه الحالة، الدرجة 70 تلبي الشرط، لأنّها أكبر من 65، لذلك ستحصل على المخرجات التالية عند تنفيذ البرنامج: درجة النجاح لنغيّر الآن نتيجة هذا البرنامج عبر تغيير قيمة المتغير grade إلى 60: grade = 60 if grade >= 65: print("درجة النجاح") بعد حفظ وتنفيذ الشيفرة، لن نحصل على أي مخرجات، لأنّ الشرط لم يتحقق، ولم نأمر البرنامج بتنفيذ تعليمة أخرى. كمثال آخر، دعنا نتحقق مما إذا كان رصيد الحساب المصرفي أقل من 0. لننشئ ملفا باسم account.py، ونكتب البرنامج التالي: balance = -5 if balance < 0: print("الحساب فارغ، أضف مبلغا الآن، أو ستحصل على غرامة.") عند تنفيذ البرنامج باستخدام python account.py، سنحصل على المخرجات التالية: الحساب فارغ، أضف مبلغًا الآن، أو ستحصل على غرامة. أعطينا للمتغير balance القيمة -5، وهي أقل من 0 في البرنامج السابق. ولمَّا كان الرصيد مستوفيًا لشرط التعليمة if (أي balance < 0)، فسنحصل على سلسلة نصية في المخرجات بمجرد حفظ الشيفرة وتنفيذها. مرة أخرى، لو غيرنا الرصيد إلى القيمة 0 أو إلى عدد موجب، فلن نحصل على أيّ مخرجات. دورة تطوير التطبيقات باستخدام لغة Python احترف تطوير التطبيقات مع أكاديمية حسوب والتحق بسوق العمل فور انتهائك من الدورة اشترك الآن التعليمة Else قد تريد من البرنامج أن يفعل شيئًا ما في حال عدم تحقق شرط التعليمة if. في المثال أعلاه، نريد طباعة مخرجات في حال النجاح والرسوب. ولفعل ذلك، سنضيف التعليمة else إلى شرط الدرجة أعلاه وفق الصياغة التالية: grade = 60 if grade >= 65: print("درجة النجاح") else: print("درجة الرسوب") قيمة المتغير grade تساوي 60، لذلك فشرط التعليمة if غير متحقق، وبالتالي فإنّ البرنامج لن يطبع درجة النجاح. تخبر التعليمة else البرنامجَ أنّ عليه طباعة السلسلة النصية "درجة الرسوب". عندما نحفظ البرنامج وننفّذه، سنحصل على المخرجات التالية: درجة الرسوب إذا عدّلنا البرنامج وأعطينا المتغيرَ grade القيمة 65 أو أعلى منها، فسنحصل بدلاً من ذلك على الناتج درجة النجاح. لإضافة التعليمة else إلى مثال الحساب المصرفي، سنعيد كتابة الشيفرة كما يلي: balance = 522 if balance < 0: print("الحساب فارغ، أضف مبلغا الآن، أو ستحصل على غرامة.") else: print("رصيدك أكبر من 0.") سنحصل على المخرجات التالية: رصيدك أكبر من 0. هنا، غيّرنا قيمة المتغير balance إلى عدد موجب لكي تُنفَّذ الشيفرة المقابلة للتعليمة else. إن أردت تنفيذ الشيفرة المقابلة للتعليمة if، غيِّر القيمة إلى عدد سالب. من خلال دمج العبارتين if و else، فأنت تنشئ تعليمة شرطية مزدوجة، والتي ستجعل الحاسوب ينفذ شيفرة برمجية معينة سواء تم استيفاء شرط if أم لا. التعليمة Else if حتى الآن، عملنا على تعليمات شرطية ثنائية، أي إن تحقق الشرط، فنفذ شيفرة ما، وإلا، فنفِّذ شيفرة أخرى فقط. لكن في بعض الحالات، قد تريد برنامجًا يتحقق من عدة حالات شرطية. ولأجل هذا، سوف نستخدم التعليمة Else if، والتي تُكتب في بايثون هكذا elif. تشبه التعليمة elif - أو Else if - التعليمة if، ومهمتها التحقق من شرط إضافي. في برنامج الحساب المصرفي، قد نرغب في الحصول على ثلاثة مخرجات مختلفة مقابلة لثلاث حالات مختلفة: الرصيد أقل من 0 الرصيد يساوي 0 الرصيد أعلى من 0 ستوضع التعليمة elif بين التعليمة if والتعليمة else كما يلي: . . . if balance < 0: print("الحساب فارغ، أضف مبلغا الآن، أو ستحصل على غرامة.") elif balance == 0: print("الرصيد يساوي 0، أضف مبلغًا قريبًا.") else: print("رصيدك أكبر من 0.") الآن، هناك ثلاثة مخرجات محتملة يمكن أن تُطبع عند تنفيذ البرنامج: إن كان المتغير balance يساوي 0، فسنحصل على المخرجات من التعليمة elif (أي الرصيد يساوي 0، أضف مبلغًا قريبًا.). إذا ضُبِط المتغير balance عند عدد موجب، فسوف نحصل على المخرجات من التعليمة else (أي رصيدك أكبر من 0.). إذا ضُبِط المتغير balance عند عدد سالب، فسنحصل على المخرجات من التعليمة if (أي الحساب فارغ، أضف مبلغا الآن، أو ستحصل على غرامة). ماذا لو أردنا أن نأخذ بالحسبان أكثر من ثلاثة احتمالات؟ يمكننا كتابة عدة تعليمات elif في الشيفرة البرمجية. لنُعِد كتابة البرنامج grade.py بحيث يقابل كل نطاق من الدرجات علامة محددة: 90 أو أعلى تكافئ العلامة أ 80-89 تعادل العلامة ب 70-79 تعادل العلامة ج 65-69 تعادل العلامة د 64 أو أقل تكافئ العلامة ه سنحتاج لتنفيذ هذه الشيفرة إلى تعليمة if واحد، وثلاث تعليمات elif، وتعليمة else تعالج جميع الحالات الأخرى. دعنا نعيد كتابة الشيفرة من المثال أعلاه لطباعة سلسلة نصية مقابلة لكل علامة. يمكننا الإبقاء على التعليمة else كما هي. . . . if grade >= 90: print("العلامة أ") elif grade >=80: print("العلامة ب") elif grade >=70: print("العلامة ج") elif grade >= 65: print("العلامة د") else: print("درجة الرسوب") تُنفّذ التعليمات elif بالترتيب. هذا البرنامج سيكمل الخطوات التالية: إذا كانت الدرجة أكبر من 90، فسيطبع البرنامجُ العلامة أ، وإذا كانت الدرجة أقل من 90، فسيمرّ البرنامج إلى التعليمة التالية … إذا كانت الدرجة أكبر من أو تساوي 80، فسيطبع البرنامجُ العلامة ب، إذا كانت الدرجة تساوي 79 أو أقل، فسيمرّ البرنامج إلى التعليمة التالية … إذا كانت الدرجة أكبر من أو تساوي 70، فسيطبعُ البرنامجُ العلامة ج، إذا كانت الدرجة تساوي 69 أو أقل، فسيمرّ البرنامج إلى التعليمة التالية … إذا كانت الدرجة أكبر من أو تساوي 65، فسيطبع البرنامجُ العلامة د، وإذا كانت الدرجة تساوي 64 أو أقل، فسيمرّ البرنامج إلى التعليمة التالية … سيطبع البرنامج درجة الرسوب، لأنه لم يتم استيفاء أيِّ من الشروط المذكورة أعلاه. تعليمات if المتشعبة بعد أن تتعود على التعليمات if و elif و else، يمكنك الانتقال إلى التعليمات الشرطية المتشعبة (nested conditional statements). يمكننا استخدام تعليمات if المتشعبة في الحالات التي نريد فيها التحقق من شرط ثانوي بعد التأكد من تحقق الشرط الرئيسي. لهذا، يمكننا حشر تعليمة if-else داخل تعليمة if-else أخرى. لنلقِ نظرة على صياغة if المتشعبة: if statement1: # الخارجية if تعليمة print("true") if nested_statement: # المتشعبة if تعليمة print("yes") else: # المتشعبة else تعليمة print("no") else: # الخارجية else تعليمة print("false") هناك عدة مخرجات محتملة لهذه الشيفرة: إذا كانت statement1 صحيحة، فسيتحقق البرنامج مما إذا كانت nested_statement صحيحة أيضًا. إذا كانت كلتا الحالتين صحيحتان، فسنحصل على المخرجات التالية: true yes ولكن إن كانت statement1 صحيحة، و nested_statement خاطئة، فسنحصل على المخرجات التالية: true no وإذا كانت statement1 خاطئة، فلن تُنفّذ تعليمة if-else المتشعبة على أيّ حال، لذلك ستُنفّذ التعليمة else وحدها، والمخرجات ستكون: false يمكن أيضًا استخدام عدة تعليمات if متشعبة في الشيفرة: if statement1: # الخارجية if print("hello world") if nested_statement1: # المتشعبة الأولى if print("yes") elif nested_statement2: # المتشعبة الأولى elif print("maybe") else: #المتشعبة الأولى else print("no") elif statement2: # الخارجية elif print("hello galaxy") if nested_statement3: # المتشعبة الثانية if print("yes") elif nested_statement4: # المتشعبة الثانية elif print("maybe") else: # المتشعبة الثانية else print("no") else: # الخارجية else statement("مرحبا") في الشيفرة البرمجية أعلاه، هناك تعليمات if و elif متشعبة داخل كل تعليمات if. هذا سيفسح المجال لمزيد من الخيارات في كل حالة. دعنا نلقي نظرة على مثال لتعليمات if متشعبة في البرنامج grade.py. يمكننا التحقق أولًا مما إذا كان الطالب قد حقق درجة النجاح (أكبر من أو تساوي 65٪)، ثم نحدد العلامة المقابلة للدرجة. إذا لم يحقق الطالب درجة النجاح، فلا داعي للبحث عن العلامة المقابلة للدرجة، وبدلًا من ذلك، يمكن أن نجعل البرنامج يطبع سلسلة نصية فيها إعلان عن رسوب الطالب. ستبدو الشيفرة المعدلة كما يلي: . . . if grade >= 65: print("درجة النجاح:") if grade >= 90: print("أ") elif grade >=80: print("ب") elif grade >=70: print("ج") elif grade >= 65: print("د") else: print("درجة الرسوب") إذا أعطينا للمتغير grade القيمة 92، فسيُستوفى الشرط الأول، وسيَطبع البرنامجُ درجة النجاح:. بعد ذلك، سيتحقق مما إذا كانت الدرجة أكبر من أو تساوي 90، وبما أنّ هذا الشرط مستوفًى أيضًا، فستُطبع أ. أما إذا أعطينا للمتغير grade القيمة 60، فلن يتم استيفاء الشرط الأول، لذلك سيتخطى البرنامج تعليمات if المتشعبة، وينتقل إلى التعليمة else، ويطبع درجة الرسوب. يمكننا بالطبع إضافة المزيد من الخيارات، واستخدام طبقة ثانية من تعليمات if المتشعبة. ربما نود إضافة الدرجات التفصيلية أ+ و أ و أ-. يمكننا القيام بذلك عن طريق التحقق أولًا من اجتياز درجة النجاح، ثم التحقق مما إذا كانت الدرجة تساوي 90 أو أعلى، ثم التحقق مما إذا كانت الدرجة تتجاوز 96، وفي تلك الحالة ستقابل العلامة أ+. إليك المثال التالي: . . . if grade >= 65: print("درجة النجاح:") if grade >= 90: if grade > 96: print("أ+") elif grade > 93 and grade <= 96: print("أ") elif grade >= 90: print("أ-") . . . في الشيفرة أعلاه، في حال تعيين المتغير grade عند القيمة 96، سيقوم البرنامج بما يلي: التحقق مما إذا كانت الدرجة أكبر من أو تساوي 65 (صحيح) طباعة درجة النجاح: التحقق مما إذا كانت الدرجة أكبر من أو تساوي 90 (صحيح) التحقق مما إذا كانت الدرجة أكبر من 96 (خطأ) التحقق مما إذا كانت الدرجة أكبر من 93، وأقل من أو تساوي 96 (صحيح) طباعة أ تجاوز التعليمات الشرطية المتشعبة وتنفيذ باقي الشيفرة ستكون مخرجات البرنامج في حال كانت الدرجة تساوي 96 كالتالي: درجة النجاح: أ تساعد تعليمات if المتشعبة على إضافة عدة مستويات من الشروط الفرعية إلى الشيفرة. خلاصة ستتحكم باستخدام التعليمات الشرطية، مثل التعليمة if، في مسار البرنامج أي تدفق تنفيذ الشيفرة. تطلب التعليمات الشرطية من البرنامج التحقق من استيفاء شرط معين من عدمه. وإذا تم استيفاء الشرط، فستُنفّذ شيفرة معينة، وإلا فسيستمر البرنامج وينتقل إلى الأسطر التالية. يمكنك الدمج بين التعليمات الشرطية والمعاملات المنطقية، بما فيها and و or، واستخدام التعليمات الشرطية مع الحلقات التكرارية. هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال How To Write Conditional Statements in Python 3 لصاحبته Lisa Tagliaferri اقرأ أيضًا المقالة التالية: كيفية إنشاء حلقات تكرار while في بايثون 3 المقالة السابقة: كيفية كتابة الوحدات في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون كتاب البرمجة بلغة بايثون
-
وحدات (modules) بايثون هي ملفات .py تحتوي شفرة بايثون، ويمكن التعامل مع أيّ ملف بايثون على أنه وحدة. تتوفر بعض الوحدات في مكتبة بايثون القياسية، والتي تُثبّت تلقائيًا مع بايثون. يمكن لبعضها الآخر أن يُثبّت عبر مدير الحزم pip. بالإضافة إلى ذلك، يمكنك إنشاء وحدات بايثون خاصة بك، لأنّ الوحدات هي مجرد ملفات .py. سيرشدك هذا الدرس إلى كيفية كتابة وحدات بايثون لاستخدامها في ملفات البرمجة الأخرى. كتابة الوحدات واستيرادها كتابة الوحدات مشابه لكتابة أي ملف بايثون آخر. يمكن أن تحتوي الوحدات على تعريفات الدوال والأصناف والمتغيرات التي يمكن استخدامها بعد ذلك في برامج بايثون الأخرى. سنشئ من بيئة البرمجة الحالية الخاصة ببايثون 3 أو بيئة البرمجة المستندة إلى الخادم ملفًّا باسم hello.py، والذي سنتستورده لاحقًا من ملف آخر. في البدء، سننشئ دالة تطبع العبارة مرحبا بالعالم!: # تعريف دالة def world(): print("مرحبا بالعالم!") إذا نفّذنا البرنامج في سطر الأوامر باستخدام python hello.py، فلن يحدث شيء، لأننا لم نطلب من البرنامج فعل أي شيء. لننشئ ملفًا ثانيًا في نفس المجلد (أي بجانب الملف السابق) باسم main_program.py حتى نتمكن من استيراد الوحدة التي أنشأناها للتو، ومن ثم استدعاء الدالة. يجب أن يكون هذا الملف في نفس المجلد حتى تعرف بايثون موضع الوحدة، لأنها ليست وحدة مُضمّنة. # hello استيراد الوحدة import hello # استدعاء الدالة hello.world() نظرًا لأننا استوردنا الوحدة، نحتاج إلى استدعاء الدالة من خلال التأشير إلى اسم الوحدة بالصياغة النقطية (dot notation). يمكننا بدلًا من ذلك استيراد دالة محدَّدة من الوحدة بالتعليمة from hello import world، واستدعاء تلك الدالة بالشكل world() كما تعلمنا ذلك من الدرس السابق. الآن، يمكننا تنفيذ البرنامج من سطر الأوامر: python main_program.py سنحصل على المخرجات التالية: مرحبا بالعالم! لنرى كيف يمكننا استخدام المتغيرات في الوحدات، دعنا نضيف تعريفًا لمتغير في الملف hello.py: # تعريف دالة def world(): print("مرحبا بالعالم!") # تعريف المتغير shark = "Sammy" بعد ذلك، سنستدعي المتغير داخل الدالة print() في الملف main_program.py: # hello استيراد الوحدة import hello # استدعاء الدالة hello.world() # طباعة المتغير print(hello.shark) بمجرد تنفيذ البرنامج، سنحصل على المخرجات التالية: مرحبا بالعالم! Sammy أخيرًا، دعنا نعرّف صنفًا في الملف hello.py. سننشئ الصنف Octopus، والذي يحتوي على الخاصيتين name و color، إضافة إلى دالة تطبع الخاصيات عند استدعائها. # تعريف الدالة def world(): print("مرحبا بالعالم!") # تعريف المتغير shark = "Sammy" # تعريف الصنف class Octopus: def __init__(self, name, color): self.color = color self.name = name def tell_me_about_the_octopus(self): print("This octopus is " + self.color + ".") print(self.name + " is the octopus's name.") سنضيف الآن الصنفَ إلى نهاية الملف main_program.py: # hello استيراد الوحدة import hello # استدعاء الدالة hello.world() # طباعة المتغير print(hello.shark) # استدعاء الصنف jesse = hello.Octopus("Jesse", "orange") jesse.tell_me_about_the_octopus() بمجرد استدعاء الصنف Octopus باستخدام hello.Octopus()، يمكننا الوصول إلى دوال وخاصيات الصنف من فضاء الأسماء الخاص بالملف main_program.py. يتيح لنا هذا كتابة jesse.tell_me_about_the_octopus() في السطر الأخير دون استدعاء hello. يمكننا أيضًا، على سبيل المثال، استدعاء إحدى خاصيات الصنف، مثل jesse.color، دون الرجوع إلى اسم الوحدة hello. سنحصل عند تنفيذ البرنامج على المخرجات التالية: مرحبا بالعالم! Sammy This octopus is orange. Jesse is the octopus's name. من المهم أن تضع في الحسبان أنه على الرغم من أنّ الوحدات غالبًا ما تضم تعريفات، إلا أنها يمكن أيضًا أن تقدم شفرات برمجية. لتوضيح هذا، دعنا نعيد كتابة الملف hello.py لنجعله يقدم دالة world(): # تعريف دالة def world(): print("مرحبا بالعالم!") # استدعاء الدالة داخل الوحدة world() لقد حذفنا أيضًا التعريفات الأخرى في الملف. الآن، في الملف main_program.py، سنحذف كل الأسطر باستثناء عبارة الاستيراد: # hello استيراد الوحدة import hello عند تنفيذ main_program.py، سنحصل على المخرجات التالية: مرحبا بالعالم! هذا لأنّ الوحدة hello قدمت الدالة world()، والتي مُرِّرت بعد ذلك إلى main_program.py لتُنفّذ مع السكربت main_program.py. الوحدة هي ملف بايثون مؤلف من تعريفات و شيفرات برمجية يمكن الاستفادة منها في ملفات بايثون الأخرى. الوصول إلى الوحدات من مجلد آخر قد تكون الوحدات مفيدة لأكثر من مشروع واحد، وفي هذه الحالة، لن يكون من الحكمة الاحتفاظ بالوحدة في مجلد مرتبط بمشروع خاص. إذا أردت استخدام وحدة من مجلد آخر غير المجلد الذي يحوي البرنامج الرئيسي، فأمامك عدة خيارات سنسردها فيما يلي. التعرف تلقائيًا على مسار الوحدة أحد الخيارات هو استدعاء مسار الوحدة من الملفات البرمجية التي تستخدم تلك الوحدة. يُعد هذا حلًّا مؤقتًا يمكن استخدامه أثناء عملية التطوير، لأنه لا يجعل الوحدة متاحة على مستوى النظام بأكمله. لإلحاق مسار وحدة بملف برمجي آخر، ستبدأ باستيراد الوحدة sys، إلى جانب الوحدات الأخرى التي ترغب في استخدامها في ملف البرنامج الرئيسي. تعد الوحدة sys جزءًا من مكتبة بايثون القياسية، وتوفر معاملات ودوال نظامية يمكنك استخدامها في برنامجك لتعيين مسار الوحدة التي ترغب في تقديمها. على سبيل المثال، لنقل أننا نقلنا الملف hello.py إلى المسار /usr/sammy/، بينما يوجد الملف main_program.py في مجلد آخر. في الملف main_program.py، ما يزال بإمكاننا استيراد الوحدة hello عن طريق استيراد الوحدة sys، ثم إضافة المسار /usr/sammy/ إلى المسارات التي يبحث بايثون فيها عن الملفات. import sys sys.path.append('/usr/sammy/') import hello ... إن عيّنت مسار الملف hello.py بشكل صحيح، فسيكون بمقدورك تنفيذ الملف main_program.py دون أيّ أخطاء، وستحصل على نفس المخرجات التي حصلنا عليها أعلاه عندما كان hello.py في نفس المجلد. إضافة الوحدة إلى مسار بايثون الخيار الثاني هو إضافة الوحدة إلى المسار الذي يبحث فيه بايثون عن الوحدات والحزم. هذا حل أفضل وأدوم، لأنه يجعل الوحدة متاحة على نطاق البيئة، أو على مستوى النظام. لمعرفة المسار الذي يبحث فيه بايثون، شغِّل مترجم (interpreter) بايثون من بيئة البرمجة خاصتك: python بعد ذلك، استورد الوحدة sys: import sys ثم اطلب من بايثون طباعة مسار النظام: print(sys.path) ستحصل على بعض المخرجات، وسيُطبع مسار نظام واحد على الأقل. إذا كنت تعمل في بيئة برمجة، فقد تتلقى العديد منها. سيكون عليك البحث عن المسارات الموجودة في البيئة التي تستخدمها حاليًا، ولكن قد ترغب أيضًا في إضافة الوحدة إلى مسار النظام الرئيسي لبايثون. النتيجة ستكون مشابهة لما يلي: '/usr/sammy/my_env/lib/python3.5/site-packages' يمكنك الآن نقل الملف hello.py إلى هذا المجلد. بعد ذلك، يمكنك استيراد الوحدة hello كالمعتاد: import hello ... عند تنفيذ البرنامج، يُفترض ألا يحدث أيّ خطأ. يضمن لك تعديل مسار الوحدة إمكانية الوصول إليها مهما كان المجلد الذي تعمل فيه. هذا مفيد خاصة في حال كنت تعمل على عدة مشاريع تشير إلى الوحدة نفسها. خلاصة إنّ كتابة وحدات بايثون لا يختلف عن كتابة أيّ ملف بايثون آخر. غطينا في هذه المقالة كيفية كتابة التعاريف في الوحدات، وكيفية استخدامها في ملف بايثون آخر، وعرضنا بعض الخيارات حول المواضِع التي يمكن أن تحفظ فيها الوحدة لتجعلها متاحة. يمكنك تعلم المزيد حول تثبيت الوحدات واستيرادها من الدرس السابق: [كيفية استيراد الوحدات في بايثون 3](). هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال How To Write Modules in Python 3 لصاحبته Lisa Tagliaferri اقرأ أيضًا المقالة التالية: كيفية كتابة العبارات الشرطية في بايثون 3 المقالة السابقة: كيفية استيراد الوحدات في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون كتاب البرمجة بلغة بايثون
-
توفر لغة بايثون مجموعة متنوعة من الدوال المضمّنة مثل: print(): تطبع التعابير المُمرَّرة إليها في مجرى الخرج abs(): تُعيد القيمة المطلقة للعدد int(): تحوِّل نوع بيانات إلى عدد صحيح len(): تُعيد طول تسلسل أو مجموعة هذه الدوال المضمّنة وغيرها مفيدة، لكنها محدودة، لهذا يستخدم المطورون الوحدات (modules) لتطوير برامج أكثر تعقيدًا. الوحدات (Modules) هي ملفات بايثون ذات امتداد .py، والتي تحوي شيفرات بايثون. يمكن التعامل مع أيّ ملف بايثون على أنه وحدة. مثلًا، إن كان هناك ملف بايثون يسمى hello.py، فسيكون اسم الوحدة المقابلة له hello، والذي يمكن استيراده في ملفات بايثون الأخرى، أو استخدامه في مترجم (interpreter) سطر أوامر بايثون. ستتعلم في المقالة التالية كيفية إنشاء الوحدات. يمكن للوحدات أن تعرِّف دوالًا وأصنافًا ومتغيرات يمكن الرجوع إليها من ملفات بايثون الأخرى، أو من مترجم سطر أوامر بايثون. في بايثون، يمكنك الوصول إلى الوحدات باستخدام العبارة import. عند فعل ذلك، ستُنفَّذ شيفرة الوحدة، مع الاحتفاظ بنطاقات (scopes) التعريفات حتى تكون متاحة في ملفك الحالي. عندما تستورد بايثون وحدةً باسم hello على سبيل المثال، فسيبحث المترجم أولًا عن وحدة مضمّنة باسم hello. فإن لم يجد، فسيبحث عن ملف يسمى hello.py في قائمة من المجلدات يحددها المتغير sys.path. سيرشدك هذا الدرس إلى كيفية البحث عن الوحدات وتثبيتها، واستيرادها، وإعادة تسميتها (aliasing). تثبيت الوحدات هناك عدد من الوحدات المضمنة في مكتبة بايثون القياسية التي تحتوي على العديد من الوحدات التي توفر الكثير من وظائف النظام، أو توفر حلولًا قياسية. مكتبة بايثون القياسية تأتي مع كل توزيعات بايثون. للتحقق من أنّ وحدات بايثون جاهزة للعمل، ادخل إلى بيئة برمجة بايثون 3 المحلية، أو بيئة البرمجة المستندة إلى الخادم، وشغّل مترجم بايثون في سطر الأوامر على النحو التالي: (my_env) sammy@ubuntu:~/environment$ python من داخل المترجم، يمكنك تنفيذ العبارة import مع اسم الوحدة للتأكد من أنّها جاهزة: import math لمّا كانت math وحدة مضمّنة، فينبغي أن يُكمل المترجم المهمة دون أي مشاكل، ثم يعود إلى المحث (prompt). هذا يعني أنك لست بحاجة إلى فعل أيّ شيء للبدء في استخدام الوحدة math. لننفِّذ الآن العبارة import مع وحدة قد لا تكون مُثبّتة عندك، مثل matplotlib، وهي مكتبة للرسم ثنائي الأبعاد: import matplotlib إذا لم تكن matplotlib مثبتة، فستتلقّى خطأً مثل هذا: ImportError: No module named 'matplotlib' يمكنك إيقاف مترجم بايثون بالضغط على CTRL + D، ثم تثبيت الوحدة matplotlib عبر pip بتنفيذ الأمر التالي: (my_env) sammy@ubuntu:~/environment$ pip install matplotlib بمجرد تثبيتها، يمكنك استيراد matplotlib من مترجم بايثون باستخدام import matplotlib، ولن يحدث أيّ خطأ. استيراد الوحدات للاستفادة من الدوال الموجودة في الوحدة، ستحتاج إلى استيراد الوحدة عبر التعليمة import. تتألف التعليمة import من الكلمة المفتاحية import معقوبة باسم الوحدة. يُصرَّح عن عملية استيراد الوحدات في أعلى ملفات بايثون، قبل الأسطر التوجيهية (shebang lines أي الأسطر التي تبدأ بـ #!)، أو التعليقات العامة. لذلك، سنستورد في ملف برنامج بايثون my_rand_int.py الوحدة random لتوليد أعداد عشوائية على النحو التالي: import random عندما نستورد وحدة، فإننا نجعلها متاحة في برنامجنا الحالي كفضاء أسماء (namespace) منفصل. هذا يعني أنه سيتعيّن علينا الرجوع إلى الدالة باستخدام الصياغة النقطية (dot notation) على النحو التالي [module].[function]. عمليًا، باستخدام مثال الوحدة random، ستبدو الشفرة كما يلي: random.randint(): تستدعي الدالة لإعادة عدد صحيح عشوائي، أو random.randrange(): تستدعي الدالة لإعادة عنصر عشوائي من نطاق محدد. دعنا ننشئ حلقة for لتوضيح كيفية استدعاء دالة من الوحدة random ضمن البرنامج my_rand_int.py: import random for i in range(10): print(random.randint(1, 25)) يستورد هذا البرنامج الصغير الوحدة random في السطر الأول، ثم ينتقل إلى الحلقة for التي ستمر على 10 عناصر. داخل الحلقة، سيطبع البرنامج عددًا صحيحًا عشوائيًا من المجال 1 إلى 25 (مشمول). يُمرّّر العددان الصحيحان 1 و 25 إلى random.randint() كمعاملين. عند تنفيذ البرنامج باستخدام الأمرpython my_rand_int.py، ستظهر 10 أعداد صحيحة عشوائية في المخرجات. نظرًا لأنّ هذه العناصر عشوائية، فستحصل على الأرجح على أعداد مختلفة في كل مرة تنفّذ فيها البرنامج، لكنها عمومًا ستبدو كما يلي: 6 9 1 14 3 22 10 1 15 9 الأعداد الصحيحة كلها محصورة بين 1 و 25. إذا كنت ترغب في استخدام دوال من أكثر من وحدة، يمكنك ذلك عن طريق إضافة عدة تعليمات استيراد: import random import math قد تصادف برامج تستورد عدة وحدات مفصولة بفواصل - مثل import random, math - ولكنّ هذا لا يتوافق مع دليل التنسيق PEP 8. للاستفادة من الوحدة الإضافية، يمكننا إضافة الثابت pi من الوحدة math إلى برنامجنا، وتقليل عدد الأعداد الصحيحة العشوائية المطبوعة: import random import math for i in range(5): print(random.randint(1, 25)) print(math.pi) الآن، عند تنفيذ البرنامج، سنحصل على مخرجات على الشكل التالي، مع تقريب للعدد pi في السطر الأخير: 18 10 7 13 10 3.141592653589793 تتيح لك التعليمة import استيراد وحدة واحدة أو أكثر إلى برامجك، وهذ يمكّنك من الاستفادة مما تحويها تلك الوحدات. استخدام الصياغة from ... import للإشارة إلى عناصر من وحدة مستوردة ضمن فضاء الأسماء، يمكنك استخدام التعليمة from ... import. عندما تستورد الوحدات بهذه الطريقة، سيكون بمقدورك الرجوع إلى الدوال بأسمائها فقط، بدلًا من استخدام الصياغة النقطية. في هذه الصياغة، يمكنك تحديد التعريفات التي تود الإشارة إليها مباشرة. في بعض البرامج، قد ترى العبارة * from ... import، إذ تشير العلامة * إلى جميع العناصر الموجودة في الوحدة، ولكنّ هذه الصياغة غير معتمدة في PEP 8. سنحاول في البداية استيراد دالة واحدة من الوحدة random، وهي randint(): from random import randint هنا، نستدعي أولًا الكلمة المفتاحية from، ثم random. بعد ذلك، نستخدم الكلمة المفتاحية import، ونستدعي الدالة المحددة التي نودّ استخدامها. الآن، عندما نرغب في استخدام هذه الدالة في برنامجنا، لن نستدعي الدالة وفق الصياغة النقطية، random.randint()، ولكن سنستدعيها باسمها مباشرةً، أي randint(): from random import randint for i in range(10): print(randint(1, 25)) عند تنفيذ البرنامج، ستتلقى مخرجات مشابهة لما تلقيته مسبقًا. يتيح لنا استخدام from ... import الرجوع إلى العناصر المعرّفة في الوحدة من فضاء الأسماء الخاص ببرنامجنا، مما يتيح لنا تجنب استخدام الصياغة النقطية الطويلة. الأسماء المستعارة في الوحدات يمكن تعديل أسماء الوحدات ودوالها داخل بايثون باستخدام الكلمة المفتاحية as. قد ترغب في تغيير اسم ما لأنك تستخدمه سلفًا في برنامجك، أو أنه مستخدم في وحدة أخرى مستوردة، أو قد ترغب في اختصار اسم طويل تستخدمه كثيرًا. يمكنك ذلك عبر الصياغة التالية: import [module] as [another_name] لنعدّل اسم الوحدة math في ملف البرنامج my_math.py. سنغيّر اسم الوحدة math إلى m من أجل اختصاره. سيبدو برنامجنا المعدل كالتالي: import math as m print(m.pi) print(m.e) سنشير داخل البرنامج إلى الثابت pi بالتعبير m.pi، بدلًا من math.pi. يشيع في بعض الوحدات استخدام أسماء مستعارة (aliases) محدَّدة. فمثلًا، يدعو التوثيق الرسمي للوحدة matplotlib.pyplot إلى استخدام الاسم المستعار plt: import matplotlib.pyplot as plt يسمح هذا للمبرمجين بإلحاق الكلمة القصيرة plt بأي دالة متاحة داخل الوحدة، كما هو الحال في plt.show(). خلاصة يسمح مفهوم الوحدات باستدعاء دوال غير مضمّنة في بايثون. فبعض الوحدات مُثبّتة كجزء من بايثون، وبعضها سنثبّتها عبر pip. يتيح لنا استخدام الوحدات توسيع برامجنا وتقويتها، لأنها تضع تحت تصرّفنا شفرات جاهزة للاستخدام. يمكننا أيضًا إنشاء وحدات خاصة بنا، لنستخدمها نحن، أو المبرمجون الآخرون وهذا ما سنتعرف عليه في المقال التالي. هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال How To Import Modules in Python 3 لصاحبته Lisa Tagliaferri. اقرأ أيضًا المقالة التالية: كيفية كتابة الوحدات في بايثون 3 المقالة السابقة: فهم القواميس في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون كتاب البرمجة بلغة بايثون
-
القاموس هو نوع مُضمَّن في بايثون. تربط القواميس مفاتيح بقيم على هيئة أزواج، وهذه الأزواج مفيدة لتخزين البيانات في بايثون. تستخدم القواميس عادةً لتخزين البيانات المترابطة، مثل المعلومات المرتبطة برقم تعريف، أو ملفات تعريف المستخدم، وتُنشأ باستخدام الأقواس المعقوصة {}. تبدو القواميس على الشكل التالي: sammy = {'username': 'sammy-shark', 'online': True, 'followers': 987} بالإضافة إلى القوسين المعقوصين، لاحظ وجود النقطتين الرأسيتين (:) في القاموس. الكلمات الموجودة على يسار النقطتين الرأسيتين هي المفاتيح التي قد تكون أيَّ نوع بيانات غير قابل للتغيير. المفاتيح في القاموس أعلاه هي: username online *followers المفاتيح في المثال أعلاه عبارة عن سلاسل نصية. تمثِّل الكلمات الموجودة على يمين النقطتين «القيم». يمكن أن تتألف القيم من أي نوع من البيانات. القيم في القاموس أعلاه هي: sammy-shark True *987 قيم القاموس أعلاه هي إمَّا سلاسل نصية أو قيم منطقية أو أعداد صحيحة. سنطبع الآن القاموس sammy: print(sammy) # {'username': 'sammy-shark', 'followers': 987, 'online': True} نلاحظ بالنظر إلى المخرجات تغير ترتيب الأزواج قيمة-مفتاح (key-value). في بايثون 3.5 وما قبله، إذ القواميس غير مرتبة. لكن ابتداءً من بايثون 3.6، صارت القواميس مرتبةً. بغض النظر عما إذا كان القاموس مرتبًا أم لا، ستظل الأزواج قيمة-مفتاح كما هي، وهذا سيمكّنك من الوصول إلى البيانات بناء على ترابطاتها. الوصول إلى عناصر القاموس يمكننا الوصول إلى قيم محدَّدة في القاموس بالرجوع إلى المفاتيح المرتبطة بها ويمكن أيضًا الاستعانة ببعض التوابع الجاهزة للوصول إلى القيم أو المفاتيح أو كليهما. الوصول إلى عناصر القاموس باستخدام المفاتيح إذا أردنا الحصول على اسم المستخدم في Sammy، فيمكننا ذلك عن طريق استدعاء sammy['username']. هذا مثال على ذلك: print(sammy['username']) # sammy-shark تتصرف القواميس كقواعد البيانات، فهي بدلًا من فهرسة العناصر بأعداد صحيحة، كما هو الحال في القوائم، فإنها تُفهرس العناصر (أو قيم القاموس) بمفاتيح، ويمكنك عبر تلك المفاتيح الحصول على القيم المقابلة لها. باستدعاء المفتاح username، سنحصل على القيمة المرتبطة به، وهي sammy-shark. وبالمِثل، يمكن استدعاء القيم الأخرى في القاموس sammy باستخدام نفس الصياغة: sammy['followers'] # 987 sammy['online'] # True استخدام التوابع للوصول إلى العناصر بالإضافة إلى استخدام المفاتيح للوصول إلى القيم، يمكننا أيضًا استخدام بعض التوابع المُضمّنة، مثل: dict.keys(): الحصول على المفاتيح dict.values(): الحصول على القيم dict.items(): الحصول على العناصر على هيئة قائمة من أزواج (key, value) لإعادة المفاتيح، نستخدم التابع dict.keys()، كما يوضح المثال التالي: print(sammy.keys()) # dict_keys(['followers', 'username', 'online']) تلقينا في المخرجات كائنَ عرض تكراري (iterable view object) من الصنف dict_keys يحوي المفاتيح ثم طُبِعت المفاتيح على هيئة قائمة. يمكن استخدام هذا التابع للاستعلام من القواميس. على سبيل المثال، يمكننا البحث عن المفاتيح المشتركة بين قاموسين: sammy = {'username': 'sammy-shark', 'online': True, 'followers': 987} jesse = {'username': 'JOctopus', 'online': False, 'points': 723} for common_key in sammy.keys() & jesse.keys(): print(sammy[common_key], jesse[common_key]) يحوي القاموسان sammy و jesse معلومات تعريف المستخدم. كما أنّ لهما مفاتيح مختلفة، لأنّ لدى Sammy ملف تعريف اجتماعي يضم مفتاحًا followers يمثل المتابعين على الشبكة الاجتماعية، أما Jesse فلها ملف تعريف للألعاب يضم مفتاحًا points يمثل النقاط. كلا القاموسين يشتركان في المفتاحين username و online، ويمكن العثور عليهما عند تنفيذ هذا البُريمج: # المخرجات sammy-shark JOctopus True False يمكننا بالتأكيد تحسين البرنامج لتسهيل قراءة المخرجات، ولكنّ الغرض هنا هو توضيح إمكانية استخدام dict.keys() لرصد المفاتيح المشتركة بين عدة قواميس. هذا مفيد بشكل خاص عند العمل على القواميس الكبيرة. وبالمثل، يمكننا استخدام التابع dict.values() للاستعلام عن القيم الموجودة في القاموس sammy على النحو التالي: sammy = {'username': 'sammy-shark', 'online': True, 'followers': 987} print(sammy.values()) # dict_values([True, 'sammy-shark', 987]) يُعيد كلا التابعان values() و keys() قوائم غير مرتبة تضم مفاتيح وقيم القاموس sammy على هيئة كائِني عرضٍ من الصنف dict_values و dict_keys على التوالي. إن أردت الحصول على الأزواج الموجودة في القاموس، فاستخدم التابع items(): print(sammy.items()) المخرجات ستكون: dict_items([('online', True), ('username', 'sammy-shark'), ('followers', 987)]) النتيجة المعادة ستكون على هيئة قائمة مكونة من أزواج (key, value) من الصنف dict_items. يمكننا التكرار (iterate) على القائمة المُعادة باستخدام الحلقة for. على سبيل المثال، يمكننا طباعة جميع مفاتيح وقيم القاموس المحدد، ثم جعلها أكثر مقروئية عبر إضافة سلسلة نصية توضيحية: for key, value in sammy.items(): print(key, 'is the key for the value', value) وسينتج لنا: online is the key for the value True followers is the key for the value 987 username is the key for the value sammy-shark كرَّرت الحلقة for على العناصر الموجودة في القاموس sammy، وطبعت المفاتيح والقيم سطرًا سطرًا، مع إضافة معلومات توضيحية. تعديل القواميس القواميس هي هياكل بيانات قابلة للتغيير (mutable)، أي يمكن تعديلها. في هذا القسم، سنتعلم كيفية إضافة عناصر إلى قاموس، وكيفية حذفها. إضافة وتغيير عناصر القاموس يمكنك إضافة أزواج قيمة-مفتاح إلى قاموس دون استخدام توابع أو دوال باستخدام الصياغة التالية: dict[key] = value في المثال التالي، سنضيف زوجًا مفتاح-قيمة إلى قاموس يُسمى usernames: usernames = {'Sammy': 'sammy-shark', 'Jamie': 'mantisshrimp54'} usernames['Drew'] = 'squidly' print(usernames) # {'Drew': 'squidly', 'Sammy': 'sammy-shark', 'Jamie': 'mantisshrimp54'} لاحظ أنّ القاموس قد تم تحديثه بالزوج 'Drew': 'squidly'. نظرًا لأنّ القواميس غير مرتبة، فيمكن أن يظهر الزوج المُضاف في أيّ مكان في مخرجات القاموس. إذا استخدمنا القاموس usernames لاحقًا، فسيظهر فيه الزوج المضاف حديثًا. يمكن استخدام هذه الصياغة لتعديل القيمة المرتبطة بمفتاح معيّن. في هذه الحالة، سنشير إلى مفتاح موجود سلفًا، ونمرر قيمة مختلفة إليه. سنعرِّف في المثال التالي قاموسًا باسم drew يمثِّل البيانات الخاصة بأحد المستخدمين على بعض الشبكات الاجتماعية. حصل هذا المستخدم على عدد من المتابعين الإضافيين اليوم، لذلك سنحدّث القيمة المرتبطة بالمفتاح followers ثم نستخدم التابع print() للتحقق من أنّ القاموس قد عُدِّل. drew = {'username': 'squidly', 'online': True, 'followers': 305} drew['followers'] = 342 print(drew) # {'username': 'squidly', 'followers': 342, 'online': True} في المخرجات نرى أنّ عدد المتابعين قد قفز من 305 إلى 342. يمكننا استخدام هذه الطريقة لإضافة أزواج قيمة-مفتاح إلى القواميس عبر مدخلات المستخدم. سنكتب بريمجًا سريعًا، usernames.py، يعمل من سطر الأوامر ويسمح للمستخدم بإضافة الأسماء وأسماء المستخدمين المرتبطة بها: # تعريف القاموس الأصلي usernames = {'Sammy': 'sammy-shark', 'Jamie': 'mantisshrimp54'} # while إعداد الحلقة التكرارية while True: # اطلب من المستخدم إدخال اسم print('Enter a name:') # name تعيين المدخلات إلى المتغير name = input() # تحقق مما إذا كان الاسم موجودًا في القاموس ثم اطبع الرد if name in usernames: print(usernames[name] + ' is the username of ' + name) # إذا لم يكن الاسم في القاموس else: # اطبع الرد print('I don\'t have ' + name + '\'s username, what is it?') # خذ اسم مستخدم جديد لربطه بذلك الاسم username = input() # name عين قيمة اسم المستخدم إلى المفتاح usernames[name] = username # اطبع ردًا يبيّن أنّ البيانات قد حُدّثت print('Data updated.') سننفِّذ البرنامج من سطر الأوامر: python usernames.py عندما ننفّذ البرنامج، سنحصل على مخرجات مشابهة لما يلي: Enter a name: Sammy sammy-shark is the username of Sammy Enter a name: Jesse I don't have Jesse's username, what is it? JOctopus Data updated. Enter a name: عند الانتهاء من اختبار البرنامج، اضغط على CTRL + C للخروج من البرنامج. يمكنك تخصيص حرف لإنهاء البرنامج (مثل الحرف q)، وجعل البرنامج يُنصت له عبر العبارات الشرطية. يوضح هذا المثال كيف يمكنك تعديل القواميس بشكل تفاعلي. في هذا البرنامج، بمجرد خروجك باستخدام CTRL + C، ستفقد جميع بياناتك، إلا إن خزّنت البيانات في ملف. يمكننا أيضًا إضافة عناصر إلى القواميس وتعديلها باستخدام التابع dict.update(). هذا التابع مختلف عن التابع append() الذي يُستخدم مع القوائم. سنضيف المفتاح followers في القاموس jesse أدناه، ونَمنحه قيمة عددية صحيحة بواسطة التابع jesse.update(). بعد ذلك، سنطبع القاموس المُحدّث. jesse = {'username': 'JOctopus', 'online': False, 'points': 723} jesse.update({'followers': 481}) print(jesse) # {'followers': 481, 'username': 'JOctopus', 'points': 723, 'online': False} من المخرجات، نتبيّن أننا نجحنا في إضافة الزوج followers: 481 إلى القاموس jesse. يمكننا أيضًا استخدام التابع dict.update() لتعديل زوج قيمة-مفتاح موجود سلفًا عن طريق استبدال قيمة مفتاح معيَّن. سنغيِّر القيمة المرتبطة بالمفتاح online في القاموس Sammy من True إلى False: sammy = {'username': 'sammy-shark', 'online': True, 'followers': 987} sammy.update({'online': False}) print(sammy) # {'username': 'sammy-shark', 'followers': 987, 'online': False} يغيّر السطر sammy.update({'online': False}) القيمة المرتبطة بالمفتاح 'online' من True إلى False. عند استدعاء التابع print() على القاموس، يمكنك أن ترى في المخرجات أنّ التحديث قد تمّ. لإضافة عناصر إلى القواميس أو تعديل القيم، يمكن إمّا استخدام الصياغة dict[key] = value، أو التابع dict.update(). حذف عناصر من القاموس كما يمكنك إضافة أزواج قيمة-مفتاح إلى القاموس، أو تغيير قيمه، يمكنك أيضًا حذف العناصر الموجودة في القاموس. لتزيل زوج قيمة-مفتاح من القاموس، استخدم الصياغة التالية: del dict[key] لنأخذ القاموس jesse الذي يمثل أحد المستخدمين، ولنفترض أنّ jesse لم تعد تستخدم المنصة لأجل ممارسة الألعاب، لذلك سنزيل العنصر المرتبط بالمفتاح points. بعد ذلك، سنطبع القاموس لتأكيد حذف العنصر: jesse = {'username': 'JOctopus', 'online': False, 'points': 723, 'followers': 481} del jesse['points'] print(jesse) # {'online': False, 'username': 'JOctopus', 'followers': 481} يزيل السطر del jesse ['points'] الزوج 'points': 723 من القاموس jesse. إذا أردت محو جميع عناصر القاموس، فيمكنك ذلك باستخدام التابع dict.clear(). سيَبقى هذا القاموس في الذاكرة، وهذا مفيد في حال احتجنا إلى استخدامه لاحقًا في البرنامج، بيْد أنه سيُفرِّغ جميع العناصر من القاموس. دعنا نزيل كل عناصر القاموس jesse: jesse = {'username': 'JOctopus', 'online': False, 'points': 723, 'followers': 481} jesse.clear() print(jesse) # {} تُظهِر المخرجات أنّ القاموسًا صار فارغًا الآن. إذا لم تعد بحاجة إلى القاموس، فاستخدم del للتخلص منه بالكامل: del jesse print(jesse) إذا نفّذت الأمر print() بعد حذف القاموس jesse، سوف تتلقى الخطأ التالي: NameError: name 'jesse' is not defined خلاصة ألقينا في هذه المقالة نظرة على القواميس في بايثون. تتألف القواميس من أزواج قيمة-مفتاح، وتوفر حلًّا ممتازًا لتخزين البيانات دون الحاجة إلى فهرستها. يتيح لنا ذلك استرداد القيم بناءً على معانيها وعلاقتها بأنواع البيانات الأخرى. يمكنك تعلم المزيد عن أنواع البيانات الأخرى من المقالة التالية: "فهم أنواع البيانات في بايثون 3". هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال Understanding Dictionaries in Python 3 لصاحبته Lisa Tagliaferri اقرأ أيضًا المقالة التالية: كيفية استيراد الوحدات في بايثون 3 المقالة السابقة: فهم نوع البيانات Tuples في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون كتاب البرمجة بلغة بايثون
- 1 تعليق
-
- 1
-
يتضمّن بايثون عددًا من هياكل البيانات، بما في ذلك القوائم. تمكّن هياكل البيانات من تنظيم البيانات وتخزينها عبر توابع خاصة مُضمّنة في بايثون. للاستفادة من محتويات هذه المقالة، ينبغي أن تكون على إلمام بأساسيات القوائم، وصياغتها وكيفية فهرستها. يمكنك تعلم ذلك من المقالة السابقة: القوائم في بايثون 3. سنتعرف على التوابع المُضمنة التي يمكن استخدامها مع القوائم. ونتعلم كيفية إضافة عناصر إلى القائمة وكيفية إزالتها، وتوسيع القوائم وترتيبها، وغير ذلك. القوائم أنواعٌ قابلةٌ للتغيير (mutable) على عكس السلاسل النصية التي لا يمكن تغييرها، فعندما تستخدم تابعًا على قائمة ما، ستؤثر في القائمة نفسها، وليس في نسخة منها. سنعمل في هذه المقالة على قائمة تمثل حوض سمك، إذ ستحوي القائمة أسماء أنواع الأسماك الموجودة في الحوض، وسنعدلها كلما أضفنا أسماكًا أو أزلناها من الحوض. التابع list.append() يضيف التابع list.append(x) عنصرًا (x) إلى نهاية القائمة. يُعرّف المثال التالي قائمةً تمثل الأسماك الموجودة في حوض السمك. fish = ['barracuda','cod','devil ray','eel'] تتألف هذه القائمة من 4 سلاسل نصية، وتتراوح فهارسها من 0 إلى 3. سنضيف سمكة جديدة إلى الحوض، ونود بالمقابل أن نضيف تلك السمكة إلى قائمتنا. سنمرّر السلسلة النصية flounder التي تمثل نوع السمكة الجديدة إلى التابع list.append()، ثم نطبع قائمتنا المعدلة لتأكيد إضافة العنصر. fish.append('flounder') print(fish) # ['barracuda', 'cod', 'devil ray', 'eel', 'flounder'] الآن، صارت لدينا قائمة من 5 عناصر، تنتهي بالعنصر الذي أضفناه للتو عبر التابع append(). التابع list.insert() يأخذ التابع list.insert (i,x) وسيطين: الأول i يمثِّل الفهرس الذي ترغب في إضافة العنصر عنده، و x يمثل العنصر نفسه. لقد أضفنا إلى حوض السمك سمكة جديدة من نوع anchovy. ربما لاحظت أنّ قائمة الأسماك مرتبة ترتيبًا أبجديًا حتى الآن. لهذا السبب لا نريد إفساد الترتيب، لذا لن نضيف السلسلة النصية anchovy إلى نهاية القائمة باستخدام الدالة list.append(). بدلًا من ذلك، سنستخدم التابع list.insert() لإضافة anchovy إلى بداية القائمة، أي عند الفهرس 0: fish.insert(0,'anchovy') print(fish) # ['anchovy', 'barracuda', 'cod', 'devil ray', 'eel', 'flounder'] في هذه الحالة، أضفنا العنصر إلى بداية القائمة. ستتقدم فهارس العناصر التالية خطوةً واحدةً إلى الأمام. لذلك، سيصبح العنصر barracuda عند الفهرس 1، والعنصر cod عند الفهرس 2، والعنصر flounder - الأخير - عند الفهرس 5. سنحضر الآن سمكة من نوع damselfish إلى الحوض، ونرغب في الحفاظ على الترتيب الأبجدي لعناصر القائمة أعلاه، لذلك سنضع هذا العنصر عند الفهرس 3: fish.insert(3,'damselfish'). التابع list.extend() إذا أردت أن توسّع قائمة بعناصر قائمة أخرى، فيمكنك استخدام التابع list.extend(L)، والذي يأخذ قائمة كمعامل. سنضع في الحوض أربعة أسماك جديدة. أنواع هذه الأسماك مجموعة معًا في القائمة more_fish: more_fish = ['goby','herring','ide','kissing gourami'] سنضيف الآن عناصر القائمة more_fish إلى قائمة الأسماك، ونطبع القائمة لنتأكد من أنّ عناصر القائمة الثانية قد ضُمّت إليها: fish.extend(more_fish) print(fish) ستطبع بايثون القائمة التالية: ['anchovy', 'barracuda', 'cod', 'devil ray', 'eel', 'flounder', 'goby', 'herring', 'ide', 'kissing gourami'] في هذه المرحلة، صارت القائمة fish تتألف من 10 عناصر. التابع list.remove() لإزالة عنصر من قائمة، استخدم التابع list.remove(x)، والذي يزيل أول عنصر من القائمة له القيمة المُمرّرة x. جاءت مجموعة من العلماء المحليين لزيارة الحوض. سيجرون أبحاثًا عن النوع kissing gourami، وطلبوا استعارة السمكة kissing gourami، لذلك نود إزالة العنصر kissing gourami من القائمة لنعكس هذا التغيير: fish.remove('kissing gourami') print(fish) والمخرجات ستكون: ['anchovy', 'barracuda', 'cod', 'devil ray', 'eel', 'flounder', 'goby', 'herring', 'ide'] بعد استخدام التابع list.remove()، لم يعد العنصر kissing gourami موجودًا في القائمة. في حال مرّرت عنصرًا x غير موجود في القائمة إلى التابع list.remove()، فسيُطلق الخطأ التالي: ValueError: list.remove(x): x not in list التابع list.remove() لن يزيل إلا أول عنصر تساوي قيمته قيمة العنصر المُمرّر إلى التابع، لذلك إن كانت لدينا سمكتان من النوع kissing gourami في الحوض، وأعرنا إحداهما فقط للعلماء، فإنّ التعبير fish.remove('kissing gourami') لن يمحو إلا العنصر الأول المطابق فقط. التابع list.pop() يعيد التابع list.pop () العنصر الموجود عند الفهرس المحدد من القائمة، ثم يزيل ذلك العنصر. تشير الأقواس المربعة حول i إلى أنّ هذا المعامل اختياري، لذا، إذا لم تحدد فهرسًا (كما في fish.pop())، فسيُعاد العنصر الأخير ثم يُزال. لقد أصبح حجم السمكة devil ray كبيرًا جدًا، ولم يعد الحوض يسعها، ولحسن الحظ أنّ هناك حوض سمك في بلدة مجاورة يمكنه استيعابها. سنستخدم التابع .pop()، ونمرر إليه العدد 3، الذي يساوي فهرس العنصر devil ray، بقصد إزالته من القائمة. بعد إعادة العنصر، سنتأكد من أننا أزلنا العنصر الصحيح. print(fish.pop(3)) # devil ray print(fish) # ['anchovy', 'barracuda', 'cod', 'eel', 'flounder', 'goby', 'herring', 'ide'] باستخدام التابع .pop() تمكّنا من إعادة وإزالة ray devil من قائمة الأسماك. إذا لم نمرر أيّ معامل إلى هذا التابع، ونفّذنا التعبير fish.pop()، فسيُعاد العنصر الأخير ide ثم يُزَال من القائمة. التابع list.index() يصعب في القوائم الكبيرة تحديد فهارس العناصر التي تحمل قيمة معينة. لأجل ذلك، يمكننا استخدام التابع list.index(x)، حيث يمثل الوسيطx قيمة العنصر المبحوث عنه، والذي نريد إعادة فهرسه. إذا كان هناك أكثر من عنصر واحد يحمل القيمة x، فسيُعَاد فهرس العنصر الأول. print(fish) # ['anchovy', 'barracuda', 'cod', 'eel', 'flounder', 'goby', 'herring', 'ide'] print(fish.index('herring')) # 6 سوف يُطلق خطأ في حال مرّرنا قيمة غير موجودة في القائمة إلى التابع .index(). التابع list.copy() أحيانُا نرغب في تعديل عناصر قائمةٍ والتجريب عليها، مع الحفاظ على القائمة الأصلية دون تغيير؛ يمكننا في هذه الحالة استخدام التابع list.copy() لإنشاء نسخة من القائمة الأصلية. في المثال التالي، سنمرّر القيمة المعادة من fish.copy() إلى المتغير fish_2، ثم نطبع قيمة fish_2 للتأكد من أنها تحتوي على نفس عناصر القائمة fish. fish_2 = fish.copy() print(fish_2) # ['anchovy', 'barracuda', 'cod', 'eel', 'flounder', 'goby', 'herring', 'ide'] في هذه المرحلة، القائمتان fish و fish_2 متساويتان. التابع list.reverse() يمكننا عكس ترتيب عناصر قائمة باستخدام التابع list.reverse(). في المثال التالي سنستخدم التابع .reverse() مع القائمة fish لعكس ترتيب عناصرها. fish.reverse() print(fish) # ['ide', 'herring', 'goby', 'flounder', 'eel', 'cod', 'barracuda', 'anchovy'] بعد استخدام التابع .reverse()، صارت القائمة تبدأ بالعنصر ide، والذي كان في نهاية القائمة من قبل، كما ستنتهي القائمة بالعنصر anchovy، والذي كان في بداية القائمة من قبل. التابع list.count() يعيد التابع list.count(x) عدد مرات ظهور القيمة x في القائمة. هذا التابع مفيد في حال كنا نعمل على قائمة طويلة بها الكثير من القيم المتطابقة. إذا كان حوض السمك كبيرًا، على سبيل المثال، وكانت عندنا عدة أسماك من النوع neon tetra، فيمكننا استخدام التابع .count() لتحديد العدد الإجمالي لأسماك هذا النوع. في هذا المثال سنحسب عدد مرات ظهور العنصر goby: print(fish.count('goby')) # 1 تظهر السلسلة النصية goby مرةً واحدةً فقط في القائمة، لذا سيُعيد التابع .count() العدد 1. يمكننا استخدام هذا التابع أيضًا مع قائمة مكوَّنة من أعداد صحيحة. المثال التالي يوضح ذلك. يتتبع المشرفون على الحوض أعمار الأسماك الموجودة فيه للتأكد من أنّ وجباتها الغذائية مناسبة لأعمارها. هذه القائمة الثانية المُسماة fish_ages تتوافق مع أنواع السمك في القائمة fish. نظرًا لأنّ الأسماك التي لا يتجاوز عمرها عامًا واحدًا لها احتياجات غذائية خاصة، فسنحسب عدد الأسماك التي عمرها عامًا واحدًا: fish_ages = [1,2,4,3,2,1,1,2] print(fish_ages.count(1)) # 3 يظهر العدد الصحيح 1 في القائمة fish_ages ثلاث مرات، لذلك يعيد التابع .count() العدد 3. التابع list.sort() يُستخدم التابع list.sort() لترتيب عناصر القائمة التي استُدعِي معها . سنستخدم قائمة الأعداد الصحيحة fish_ages لتجريب التابع .sort(): fish_ages.sort() print(fish_ages) # [1, 1, 1, 2, 2, 2, 3, 4] باستدعاء التابع .sort() مع القائمة fish_ages، ستُعاد قائمة الأعداد الصحيحة مرتبةً. التابع list.clear() بعد الانتهاء من العمل على قائمة ما، يمكنك إزالة جميع القيم الموجودة فيها باستخدام التابع list.clear(). قررت الحكومة المحلية الاستيلاء على حوض السمك الخاص بنا، وجعله مساحة عامة يستمتع بها سكان مدينتنا. نظرًا لأننا لم نعد نعمل على الحوض، فلم نعد بحاجة إلى الاحتفاظ بقائمة الأسماك، لذلك سنزيل عناصر القائمة fish: fish.clear() print(fish) # [] نرى في المخرجات أقواسًا معقوفة نتيجة استدعاء التابع .clear() على القائمة fish، وهذا تأكيد على أنّ القائمة أصبحت خالية من جميع العناصر. خلاصة لمَّا كانت القوائم تسلسلات قابلة للتغيير (mutable)، فإنّها هياكلُ بيانات مرنة ومفيدة للغاية. كما تتيح لنا توابع القوائم إجراء العديد من العمليات على القوائم بسهولة، إذ يمكننا استخدام التوابع لتعديل القوائم وترتيبها ومعالجتها بفعالية. هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال How To Use List Methods in Python 3 لصاحبته Lisa Tagliaferri اقرأ أيضًا المقالة التالية: فهم كيفية استعمال List Comprehensions في بايثون 3 المقالة السابقة: مدخل إلى القوائم في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون كتاب البرمجة بلغة بايثون
-
القائمة هي بنية بيانات في بايثون، وهي عبارة عن تسلسل مُرتّب من العناصر قابل للتغيير (mutable). table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } القوائم مناسبة لتجميع العناصر المترابطة، إذ تمكّنك من تجميع البيانات المتشابهة، أو التي تخدم غرضًا معيّنا معًا، وتكثيف الشيفرة البرمجية، وتنفيذ التوابع والعمليات على عدة قيم في وقت واحد. القوائم في بايثون وغيرها من هياكل البيانات المركبة تساعد على تمثيل المجموعات، مثل مجموعة الملفات في مجلد على حاسوبك، أو قوائم التشغيل، ورسائل البريد الإلكتروني، وغير ذلك. في المثال التالي، سننشئ قائمةً تحتوي على عناصر من نوع السلاسل النصية: sea_creatures = ['shark', 'cuttlefish', 'squid', 'mantis shrimp', 'anemone'] عندما نطبع القائمة، فإنّ المخرجات ستشبه القائمة التي أنشأناها: print(sea_creatures) # ['shark', 'cuttlefish', 'squid', 'mantis shrimp', 'anemone'] باعتبارها تسلسلًا مرتبًا من العناصر، يمكن استدعاء أيّ عنصر من القائمة عبر الفهرسة. القوائم هي بيانات مركبة تتألف من أجزاء أصغر، وتتميز بالمرونة، إذ يمكن إضافة عناصر إليها، وإزالتها، وتغييرها. عندما تحتاج إلى تخزين الكثير من القيم، أو التكرار (iterate) عليها، وتريد أن تكون قادرًا على تعديل تلك القيم بسهولة، فالقوائم هي خيارك الأفضل. سنتعرف في هذه المقالة على القوائم وتوابعها وكيفية استخدامها. فهرسة القوائم (Indexing Lists) كل عنصر في القائمة يقابله رقم يمثل فهرسَ ذلك العنصر، والذي هو عدد صحيح، فهرس العنصر الأول في القائمة هو 0. هذا تمثيل لفهارس القائمة sea_creatures: shark cuttlefish squid shrimp anemone 0 1 2 3 4 يبدأ العنصر الأول، أي السلسلة النصية shark، عند الفهرس 0، وتنتهي القائمة عند الفهرس 4 الذي يقابل العنصر anemone. نظرًا لأنّ كل عنصر في قوائم بايثون يقابله رقم فهرس، يمكننا الوصول إلى عناصر القوائم ومعالجتها كما نفعل مع أنواع البيانات المتسلسلة الأخرى. يمكننا الآن استدعاء عنصر من القائمة من خلال رقم فهرسه: print(sea_creatures[1]) # cuttlefish تتراوح أرقام الفهارس في هذه القائمة بين 0 و 4، كما هو موضح في الجدول أعلاه. المثال التالي يوضح ذلك: sea_creatures[0] = 'shark' sea_creatures[1] = 'cuttlefish' sea_creatures[2] = 'squid' sea_creatures[3] = 'mantis shrimp' sea_creatures[4] = 'anemone' إذا استدعينا القائمة sea_creatures برقم فهرس أكبر من 4، فسيكون الفهرس خارج النطاق، وسيُطلَق الخطأ IndexError: print(sea_creatures[18]) والمخرجات ستكون: IndexError: list index out of range يمكننا أيضًا الوصول إلى عناصر القائمة بفهارس سالبة، والتي تُحسَب من نهاية القائمة، بدءًا من -1. هذا مفيد في حال كانت لدينا قائمة طويلة، وأردنا تحديد عنصر في نهايته. بالنسبة لقائمة sea_creatures، تبدو الفهارس السالبة كما يلي: shark cuttlefish squid shrimp anemone -5 -4 -3 -2 -1 في المثال التالي، سنطبع العنصر squid باستخدام فهرس سالب: print(sea_creatures[-3]) # squid يمكننا ضم (concatenate) عنصر يحتوي سلسلة نصية مع سلسلة نصبة أخرى باستخدام العامل +: print('Sammy is a ' + sea_creatures[0]) # Sammy is a shark لقد ضممنا السلسلة النصية Sammy is a مع العنصر ذي الفهرس 0. يمكننا أيضًا استخدام العامل + لضمّ قائمتين أو أكثر معًا (انظر الفقرة أدناه): تساعدنا الفهارس على الوصول إلى أيّ عنصر من عناصر القائمة والعمل عليه. تعديل عناصر القائمة يمكننا استخدام الفهرسة لتغيير عناصر القائمة، عن طريق إسناد قيمة إلى عُنصر مُفهرس من القائمة. هذا يجعل القوائم أكثر مرونة، ويسهّل تعديل وتحديث عناصرها. إذا أردنا تغيير قيمة السلسلة النصية للعنصر الموجود عند الفهرس 1، من القيمة cuttlefish إلى octopus، فيمكننا القيام بذلك على النحو التالي: sea_creatures[1] = 'octopus' الآن، عندما نطبع sea_creatures، ستكون النتيجة: print(sea_creatures) # ['shark', 'octopus', 'squid', 'mantis shrimp', 'anemone'] يمكننا أيضًا تغيير قيمة عنصر باستخدام فهرس سالب: sea_creatures[-3] = 'blobfish' print(sea_creatures) # ['shark', 'octopus', 'blobfish', 'mantis shrimp', 'anemone'] الآن استبدلنا بالسلسلة squid السلسلة النصية blobfish الموجودة عند الفهرس السالب -3 (والذي يتوافق مع الفهرس الموجب 2). اقتطاع القوائم (Slicing Lists) يمكننا أيضا استدعاء عدة عناصر من القائمة. لنفترض أننا نرغب في طباعة العناصر الموجودة في وسط القائمة sea_creatures، يمكننا القيام بذلك عن طريق اقتطاع شريحة (جزء) من القائمة. يمكننا باستخدام الشرائح استدعاء عدة قيم عن طريق إنشاء مجال من الفهارس مفصولة بنقطتين [x: y]: print(sea_creatures[1:4]) # ['octopus', 'blobfish', 'mantis shrimp'] عند إنشاء شريحة، كما في [1: 4]، يبدأ الاقتطاع من العنصر ذي الفهرس الأول (مشمولًا)، وينتهي عند العنصر ذي الفهرس الثاني (غير مشمول)، لهذا طُبعَت في مثالنا أعلاه العناصر الموجودة في المواضع، 1، و 2، و 3. إذا أردنا تضمين أحد طرفي القائمة، فيمكننا حذف أحد الفهرسَين في التعبير [x: y]. على سبيل المثال، إذا أردنا طباعة العناصر الثلاثة الأولى من القائمة sea_creatures، يمكننا فعل ذلك عن طريق كتابة: print(sea_creatures[:3]) # ['shark', 'octopus', 'blobfish'] لقد تم طبع العناصر من بداية القائمة حتّى العنصرٍ ذي الفهرس 3. لتضمين جميع العناصر الموجودة في نهاية القائمة، سنعكس الصياغة: print(sea_creatures[2:]) # ['blobfish', 'mantis shrimp', 'anemone'] يمكننا أيضًا استخدام الفهارس السالبة عند اقتطاع القوائم، تمامًا كما هو الحال مع الفهارس الموجبة: print(sea_creatures[-4:-2]) # ['octopus', 'blobfish'] print(sea_creatures[-3:]) # ['blobfish', 'mantis shrimp', 'anemone'] هناك معامل آخر يمكننا استخدامه في الاقتطاع، ويشير إلى عدد العناصر التي يجب أن يتم القفز عليها (أو الخطوة) بعد استرداد العنصر الأول من القائمة. حتى الآن، لقد أغفلنا المعامل stride، وستعطيه بايثون القيمة الافتراضية 1، ما يعني أنه سيتم استرداد كل العناصر الموجودة بين الفهرسَين المحددين. ستكون لصياغة على الشكل التالي [x: y: z]، إذ يشير z إلى الخطوة (stride). في المثال التالي، سننشئ قائمة كبيرة، ثم نقتطعها، مع خطوة اقتطاع تساوي 2: numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] print(numbers[1:11:2]) # [1, 3, 5, 7, 9] سيطبع التعبير numbers[1: 11: 2] القيم ذات الفهارس المحصورة بين 1 (مشمولة) و 11 (غير مشمولة)، وسيقفز البرنامج بخطوتين كل مرة، ويطبع العناصر المقابلة. يمكننا حذف المُعاملين الأوّليين، واستخدام الخطوة وحدها كمعامل وفق الصياغة التالية: [::z]: print(numbers[::3]) # [0, 3, 6, 9, 12] عند طباعة القائمة numbers مع تعيين الخطوة عند القيمة 3، فلن تُطبع إلا العناصر التي فهارسها من مضاعفات 3: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 يجعل استخدام الفهارس الموجبة والسالبة ومعامل الخطوة في اقتطاع القوائم التحكم في القوائم ومعالجتها أسهل وأكثر مرونة. تعديل القوائم بالعوامل يمكن استخدام العوامل لإجراء تعديلات على القوائم. سننظر في استخدام العاملين + و * ومقابليهما المركبين += و *=. يمكن استخدام العامل + لضمّ (concatenate) قائمتين أو أكثر معًا: sea_creatures = ['shark', 'octopus', 'blobfish', 'mantis shrimp', 'anemone'] oceans = ['Pacific', 'Atlantic', 'Indian', 'Southern', 'Arctic'] print(sea_creatures + oceans) والمخرجات هي: ['shark', 'octopus', 'blobfish', 'mantis shrimp', 'anemone', 'Pacific', 'Atlantic', 'Indian', 'Southern', 'Arctic'] يمكن استخدام العامل + لإضافة عنصر (أو عدة عناصر) إلى نهاية القائمة. لكن تذكر أن تضع العنصر بين قوسين مربعين: sea_creatures = sea_creatures + ['yeti crab'] print (sea_creatures) # ['shark', 'octopus', 'blobfish', 'mantis shrimp', 'anemone', 'yeti crab'] يمكن استخدام العامل * لمضاعفة القوائم (multiply lists). ربما تحتاج إلى عمل نُسِخٍ لجميع الملفات الموجودة في مجلد على خادم، أو مشاركة قائمة أفلام مع الأصدقاء؛ ستحتاج في هذه الحالات إلى مضاعفة مجموعات البيانات. سنضاعف القائمة sea_creatures مرتين، والقائمة oceans ثلاث مرات: print(sea_creatures * 2) print(oceans * 3) والنتيجة ستكون: ['shark', 'octopus', 'blobfish', 'mantis shrimp', 'anemone', 'yeti crab', 'shark', 'octopus', 'blobfish', 'mantis shrimp', 'anemone', 'yeti crab'] ['Pacific', 'Atlantic', 'Indian', 'Southern', 'Arctic', 'Pacific', 'Atlantic', 'Indian', 'Southern', 'Arctic', 'Pacific', 'Atlantic', 'Indian', 'Southern', 'Arctic'] يمكننا باستخدام العامل * نسخ القوائم عدة مرات. يمكننا أيضًا استخدام الشكلين المركبين للعاملين + و * مع عامل الإسناد =. يمكن استخدام العاملين المركبين += و *= لملء القوائم بطريقة سريعة ومُؤتمتة. يمكنك استخدام هذين العاملين لملء القوائم بعناصر نائبة (placeholders) يمكنك تعديلها في وقت لاحق بالمدخلات المقدمة من المستخدم على سبيل المثال. في المثال التالي، سنضيف عنصرًا إلى القائمة sea_creatures. سيعمل هذا العنصر مثل عمل العنصر النائب، ونود إضافة هذا العنصر النائب عدة مرات. لفعل ذلك، سنستخدم += العامل مع الحلقة for. for x in range(1,4): sea_creatures += ['fish'] print(sea_creatures) والمخرجات ستكون: ['shark', 'octopus', 'blobfish', 'mantis shrimp', 'anemone', 'yeti crab', 'fish'] ['shark', 'octopus', 'blobfish', 'mantis shrimp', 'anemone', 'yeti crab', 'fish', 'fish'] ['shark', 'octopus', 'blobfish', 'mantis shrimp', 'anemone', 'yeti crab', 'fish', 'fish', 'fish'] سيُضاف لكل تكرار في الحلقة for عنصر fish إلى القائمة sea_creatures. العامل *= يتصرف بطريقة مماثلة: sharks = ['shark'] for x in range(1,4): sharks *= 2 print(sharks) الناتج سيكون: ['shark', 'shark'] ['shark', 'shark', 'shark', 'shark'] ['shark', 'shark', 'shark', 'shark', 'shark', 'shark', 'shark', 'shark'] إزالة عنصر من قائمة يمكن إزالة العناصر من القوائم باستخدام التعبير del. سيؤدي ذلك إلى حذف العنصر الموجود عند الفهرس المحدد. سنزيل من القائمة sea_creatures العنصر octopus. هذا العنصر موجود عند الفهرس 1. لإزالة هذا العنصر، سنستخدم العبارة del ثم نستدعي متغير القائمة وفهرس ذلك العنصر: sea_creatures =['shark', 'octopus', 'blobfish', 'mantis shrimp', 'anemone', 'yeti crab'] del sea_creatures[1] print(sea_creatures) # ['shark', 'blobfish', 'mantis shrimp', 'anemone', 'yeti crab'] الآن، العنصر الموجود عند الفهرس 1، أي السلسلة النصية octopus، لم يعد موجودًا في قائمتنا. يمكننا أيضًا تحديد مجال مع العبارة del. لنقل أننا نريد إزالة العناصر octopus، و blobfish و mantis shrimp معًا. يمكننا فعل ذلك على النحو التالي: sea_creatures =['shark', 'octopus', 'blobfish', 'mantis shrimp', 'anemone', 'yeti crab'] del sea_creatures[1:4] print(sea_creatures) # ['shark', 'anemone', 'yeti crab'] باستخدام مجال مع العبارة del، تمكنا من إزالة العناصر الموجودة بين الفهرسَين 1 (مشمول) و 4 (غير مشمول)، والقائمة أضحت مكوّنة من 3 عناصر فقط بعد إزالة 3 عناصر منها. بناء قوائم من قوائم أخرى موجودة يمكن أن تتضمّن عناصر القوائم قوائم أخرى، مع إدراج كل قائمة بين قوسين معقوفين داخل الأقواس المعقوفة الخارجية التابعة لقائمة الأصلية: sea_names = [['shark', 'octopus', 'squid', 'mantis shrimp'],['Sammy', 'Jesse', 'Drew', 'Jamie']] تسمى القوائم المُتضمّنة داخل قوائم أخرى بالقوائم المتشعبة (nested lists). للوصول إلى عنصر ضمن هذه القائمة، سيتعيّن علينا استخدام فهارس متعددة: print(sea_names[1][0]) # Sammy print(sea_names[0][0]) # shark فهرس القائمة الأولى يساوي 0، والقائمة الثانية فهرسُها 1. ضمن كل قائمة متشعبة داخلية، سيكون هناك فهارس منفصلة، والتي سنسميها فهارس ثانوية: sea_names[0][0] = 'shark' sea_names[0][1] = 'octopus' sea_names[0][2] = 'squid' sea_names[0][3] = 'mantis shrimp' sea_names[1][0] = 'Sammy' sea_names[1][1] = 'Jesse' sea_names[1][2] = 'Drew' sea_names[1][3] = 'Jamie' عند العمل مع قوائم مؤلّفة من قوائم، من المهم أن تعي أنك ستحتاج إلى استخدام أكثر من فهرس واحد للوصول إلى عناصر القوائم المتشعبة. خلاصة القوائم هي نوع بيانات مرن يمكن تعديله بسهولة خلال أطوار البرنامج. غطينا في هذه المقالة الميزات والخصائص الأساسية لقوائم، بما في ذلك الفهرسة والاقتطاع والتعديل والضّم. يمكنك تعلم المزيد عن القوائم من هاتين المقالتين: كيفية استخدام توابع القوائم في بايثون 3 فهم كيفية استعمال List Comprehensions في بايثون 3 هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال Understanding Lists in Python 3 لصاحبته Lisa Tagliaferri اقرأ أيضًا المقالة التالية: كيفية استخدام توابع القوائم في بايثون 3 المقالة السابقة: فهم العمليات المنطقية في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون كتاب البرمجة بلغة بايثون
-
هناك قيمتان فقط لنوع البيانات المنطقية، وهما True و False. تُستخدم الحسابات المنطقية في البرمجة لإجراء الموازنات، والتحكم في مسار البرنامج. table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } تمثِّل القيم المنطقية قيم الحقيقة (truth values) في علم المنطق في الرياضيات. وتٌكتب القيمتان True و False دائمًا بالحرفين الكبيرين T و F على التوالي، لأنهما قيمتان خاصتان في بايثون. في هذه المقالة، سنتعرف على العمليات المنطقية في بايثون، بما في ذلك الموازنة المنطقية، والعوامل المنطقية، وجداول الحقيقة. عوامل الموازنة في البرمجة، تُستخدم عوامل الموازنة للموازنة بين القيم، وتعيد إحدى القيمتين المنطقتين True و False. يوضح الجدول أدناه عوامل الموازنة المنطقية. العامل الشرح == تساوي != تخالف < أصغر من > أكبر من <= أصغر من أو تساوي >= أكبر من أو تساوي لفهم كيفية عمل هذه العوامل، سنستخدم المتغيرين التاليين: x = 5 y = 8 في هذا المثال، لمّا كان x يساوي 5، فهو أصغر من y ذي القيمة 8. باستخدام هذين المتغيرين والقيم المرتبطة بهما، سنجرّب العوامل من الجدول أعلاه. سنطلب من بايثون أن تطبع ناتج عملية الموازنة، إما True أو False. لتوضيح المثال أكثر، سنطبع سلسلة نصية لتوضيح ما جرى تقييمه. x = 5 y = 8 print("x == y:", x == y) print("x != y:", x != y) print("x < y:", x < y) print("x > y:", x > y) print("x <= y:", x <= y) print("x >= y:", x >= y) والمخرجات هي: x == y: False x != y: True x < y: True x > y: False x <= y: True x >= y: False باتباع المنطق الرياضي، في كل من التعبيرات المذكورة أعلاه، هذه نتيجة الموازنات: 5 (x) تساوي 8 (y)؟ خطأ 5 تخالف 8؟ صح 5 أصغر من 8؟ صح 5 أكبر من 8؟ خطأ 5 أصغر من أو يساوي 8؟ صح 5 ليس أصغر من أو يساوي 8؟ خطأ رغم أننا استخدمنا الأعداد الصحيحة هنا، إلا أنه بإمكاننا استبدال الأعداد العشرية بها. يمكن أيضًا استخدام السلاسل النصية مع العوامل المنطقية. وهي حساسة لحالة الأحرف، ما لم تستخدم تابعًا إضافيًا للسلاسل النصية. المثال التالي يوضح كيفية موازنة السلاسل النصية: Sammy = "Sammy" sammy = "sammy" print("Sammy == sammy: ", Sammy == sammy) # Sammy == sammy: False السلسلة "Sammy" أعلاه لا تساوي السلسلة النصية "sammy"، لأنهما ليستا متماثلتين تمامًا؛ فإحداهما تبدأ بحرف كبير S، والأخرى بحرف صغير s. ولكن لو أضفنا متغيرًا آخر قيمته "Sammy"، فستكونان متساويتين: Sammy = "Sammy" sammy = "sammy" also_Sammy = "Sammy" print("Sammy == sammy: ", Sammy == sammy) # Sammy == sammy: False print("Sammy == also_Sammy", Sammy == also_Sammy) # Sammy == also_Sammy: True يمكنك أيضًا استخدام عوامل الموازنة الأخرى، بما في ذلك > و < لموازنة سلسلتين نصيتين. ستوازن بايثون هذه السلاسل النصية بحسب الترتيب المعجمي في نظام محارف ASCII. يمكننا أيضًا تقييم القيم المنطقية باستخدام عوامل الموازنة: t = True f = False print("t != f: ", t != f) # t != f: True تبيّن الشيفرة البرمجية أعلاه أنّ True لا تساوي False. لاحظ الفرق بين العاملين = و ==. x = y # x إلى y إسناد قيمة x == y # متساويين x و y تتحقق مما إذا كان الأول =، هو عامل الإسناد (assignment operator)، والذي سيحدد قيمة أحد المتغيرين، ويجعلها مساوية لقيمة الآخر. الثاني ==، وهو عامل الموازنة الذي سيحدد ما إذا كانت القيمتان متساويتان. دورة تطوير التطبيقات باستخدام لغة Python احترف تطوير التطبيقات مع أكاديمية حسوب والتحق بسوق العمل فور انتهائك من الدورة اشترك الآن العوامل المنطقية هناك ثلاثة عوامل منطقية تُستخدم لموازنة القيم. وتعيد إما True أو False. هذه العوامل هي، and (و)، و or (أو)، و not (النفي)، وقد عرّفناها في الجدول أدناه. العامل الشرح الصياغة and إن كان كلا التعبيرين صحيحين True x and y or إن كان أحد التعبيرين على الأقل صحيحًا True x or y not إن كان التعبير خطأ True not x عادةً ما تُستخدم العوامل المنطقية لتقييم ما إذا كان تعبيران منطقيان صحيحان أم لا. على سبيل المثال، يمكن استخدامها لتحديد ما إذا كان الطالب قد نجح، وأنه مُسجل في الفصل، وإذا كانت كلتا الحالتان صحيحتان، فسيتم إدخال الطالب إلى النظام. مثال آخر هو تحديد ما إذا كان المستخدم عميلًا نشطًا لمتجر إلكتروني استنادًا إلى ما إذا كان لديه رصيد في المتجر، أو أنه اشترى خلال الأشهر الستة الماضية. لفهم كيفية عمل العوامل المنطقية، دعنا نقيّم التعبيرات الثلاث التالية: print((9 > 7) and (2 < 4)) # كلا التعبيرين صحيحان print((8 == 8) or (6 != 6)) # أحد التعبيرين صحيح print(not(3 <= 1)) # التعبير الأصلي خاطئ والمخرجات هي: True True True في الحالة الأولى، ((9> 7) و (2 <4))print، كلا التعبيرين 9> 7 و 2 <4 ينبغي أن يكونا صحيحين لأنّ العامل and مُستخدَم. في الحالة الثانية، print((8 == 😎 or (6 != 6))بما أنّ قيمة 8 == 8 تم تقييمها بـ True، فإنّ نتيجة تقييم 6 != 6لا تهم. لكن لو استخدمنا العامل and، لتمّ تقييم العبارة المنطقية بالقيمة False. في الحالة الثالثة، print(not(3 <= 1))، العامل not ينفي القيمة False التي تعيدها العملية المنطقية 3 <= 1. في المثال التالي سنستبدل بالأعداد العشرية أعدادًا صحيحة: print((-0.2 > 1.4) and (0.8 < 3.1)) # أحد التعبيرين خاطئ print((7.5 == 8.9) or (9.2 != 9.2)) # كلا التعبيرين خاطئان print(not(-5.7 <= 0.3)) # التعبير الأصلي صحيح في المثال أعلاه: and: يجب أن يكون واحد على الأقل من التعبيرين خاطئًا لتعيد القيمة False، or: يجب أن يكون كلا التعبيرين خاطئين لتعيد القيمة False، not: يجب أن يكون التعبير المرافق لها صحيحًا حتى تعيد القيمة False. إذا لم تكن النتائج أعلاه واضحة لك، فسنعرض بعض جداول الحقيقة أدناه لتفهم الأمر بشكل أفضل. يمكنك أيضًا كتابة عبارات مركبة باستخدام and, و or, و not: not((-0.2 > 1.4) and ((0.8 < 3.1) or (0.1 == 0.1))) التعبير الداخلي: (0.8 <3.1) أو (0.1 == 0.1). يعيد القيمة True، لأنّ كلا العبارتين الرياضيتين تعيدان True. الآن، نأخذ القيمة المُعادة True ونجمعها مع التعبير المنطقي التالي: (-0.2> 1.4) and (True). هذا المثال يعيد False، لأنّ العبارة -0.2> 1.4 تعيد القيمة False، و (False) and (True) تعيد False. أخيرًا، يعيد التعبير الخارجي: not (False) القيمة True، وبالتالي ستكون القيمة النهائية المعادة هي: True جداول الحقيقة (Truth Tables) المنطق مجال واسع، وسنكتفي في هذه المقالة ببعض الأفكار المهمة التي يمكن أن تساعدك على تحسين طريقة تفكيرك وخوارزمياتك. فيما يلي جداول الحقيقة لعامل الموازنة ==، والعوامل المنطقية and, و or, و not. من المفيد أن تحفظ كيفية عملها، فذلك سيجعلك أسرع في اتخاذ القرارات أثناء كتابة الشيفرات البرمجية. جدول الحقيقة الخاص بالعامل == x == y القيمة المُعادة True == True True True == False False False == True False False == False True جدول الحقيقة الخاص بالعامل AND x and y القيمة المُعادة True and True True True and False False False and True False False and False False جدول الحقيقة الخاص بالعامل OR x or y القيمة المُعادة True or True True True or False True False or True True False and False False جدول الحقيقة الخاص بالعامل NOT not x القيمة المُعادة not True False not False True تُستخدم جداول الحقيقة في المنطق الرياضياتي كثيرًا، وهي مفيدة، ويجب حفظها ووضعها في الحسبان عند إنشاء الخوارزميات البرمجية. استخدام العوامل المنطقية للتحكم في مسار البرنامج للتحكم في مسار ونتائج البرنامج عبر العبارات الشرطية (flow control statements)، يمكننا استخدام شرط (condition) متبوعًا بعبارة برمجية (clause). يتم تقييم الشرط بإحدى القيمتين True أو False، تلك القيمة تُستخدم لاتخاذ قرار في البرنامج. أما العبارة (clause) فهي الكتلة البرمجية التي تعقب الشرط وتحدِّد نتيجة البرنامج وما ينبغي فعله في حال تحقق الشرط. تُظهر الشيفرة أدناه مثالًا على عوامل الموازنة التي تعمل مع العبارات الشرطية للتحكم في مسار البرنامج: if grade >= 65: # شرط print("Passing grade") # بند else: print("Failing grade") سيحدد هذا البرنامج ما إذا كان الطالب سينجح أم يرسب. في حال كانت الدرجة التي حصل عليها الطالب تساوي 83 مثلًا، فتقييم العبارة الأولى سيكون True، وسيُطبَع النص "Passing grade. أما إن كانت درجة الطالب هي 59، فتقييم العبارة الأولى سيكون False، وبالتالي سينتقل البرنامج لتنفيذ العبارة المرتبطة بالتعبير else، أي سيطبع Failing grade. يمكن تقييم كل كائنات بايثون بإحدى القيمتين True أو False، لذلك يوصي الدليل PEP 8 بعدم موازنة كائن بإحدى القيمتين True أو False، لأنّ ذلك قد يؤدي إلى إعادة قيم منطقية غير متوقعة. على سبيل المثال، عليك تجنب استخدام مثل هذه العبارة sammy == True في برامجك. تساعد العوامل المنطقية على صياغة شروط يمكن استخدامها لتحديد النتيجة النهائية للبرنامج من خلال التحكم في مسار التنفيذ. خلاصة ألقينا في هذه المقالة نظرة على الموازنات والعوامل المنطقية، بالإضافة إلى جداول الحقيقة، وكيفية استخدام القيم المنطقية للتحكم في مسار البرنامج. هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال Understanding Boolean Logic in Python 3 لصاحبته Lisa Tagliaferri اقرأ أيضًا المقالة التالية: مدخل إلى القوائم المقالة السابقة: الدوال الرياضية المضمّنة في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون كتاب البرمجة بلغة بايثون
-
تتضمّن بايثون 3 العديد من الدوال التي يمكنك استخدامها بسهولة في أي برنامج. تتيح لك بعض تلك الدوال تحويل أنواع البيانات، والبعض الآخر خاص بنوع معين، مثل السلاسل النصية. سنستعرض في هذه المقالة بعض الدوال الرياضية المُضمّنة التي يمكن استخدامها مع أنواع البيانات العددية في بايثون 3. سنلقي نظرة على الدوال التالية: abs(): للحصول على القيمة المطلقة divmod(): للحصول على الحاصل والباقي في وقت واحد pow(): لرفع عدد لقوة معينة round(): لتقريب عدد بمنازل عشرية محددة sum(): لحساب مجموع العناصر في كائن قابلٍ للتكرار (iterable) فهم هذه التوابع سيمنحك مرونة أكبر في البرمجة، ويساعدك على اتخاذ قرارات مدروسة عند تحديد العوامل والدوال التي عليك استخدامها. سنتطرق إلى بعض هذه الدوال في هذه المقالة، مع تقديم بعض الأمثلة. القيمة المطلقة تعيد الدالة المضمّنة abs() القيمة المطلقة للعدد الذي يُمرر إليها. في الرياضيات، تشير القيمة المطلقة إلى العدد نفسه إن كانت القيمة موجبة، أو القيمة المعاكسة إن كانت القيمة سالبة. مثلًا، القيمة المطلقة للعدد 15 هي 15، والقيمة المطلقة للعدد -74 هي 74، والقيمة المطلقة للعدد 0 هي 0. القيمة المطلقة مفهوم مهم في الحساب والتحليل، كما أنّها مفيدة كذلك في المواقف اليومية، مثل حساب المسافة المقطوعة. على سبيل المثال، إذا كنت تحاول الوصول إلى مكان يبعد 58 ميلًا، ولكنك تجاوزت ذلك المكان، وسافرت 93 فرسخًا. فإن حسبت عدد الفراسخ التي ينبغي أن تقطعها الآن للوصول إلى الوجهة المقصودة، فسوف ينتهي بك المطاف بعدد سالب، لكن لا يمكنك السفر عددًا سالبًا من الفراسخ! سنستخدم الدالة abs() لحل هذه المشكلة: miles_from_origin = 58 # عدد الفراسخ التي تفصلنا عن الوجهة انطلاقًا من المُنطلق miles_travelled = 93 # الفراسخ المقطوعة من المُنطَلق إلى الوجهة # حساب عدد الفراسخ من الموقع الحالي miles_to_go = miles_from_origin - miles_travelled print(miles_to_go) # طباعة عدد الفراسخ المتبقية - عدد سالب print(abs(miles_to_go)) # حساب القيمة المطلقة للعدد السالب المخرجات ستكون: -35 35 لولا استخدام الدالة abs()، لحصلنا على عدد سالب، أي -35. ورغم أنّ miles_travelled أصغر من miles_from_origin، فإنّ الدالة abs() تحل إشكالية العدد السالب. عندما نمرّر لها عددًا سالبًا، ستعيد الدالة abs() عددًا موجبًا، لأنّ القيمة المطلقة دائمًا تعيد أعدادًا موجبة أو معدومة. في المثال التالي، سنمرر للدالة abs() عددًا موجبًا، وكذلك الصفر: print(abs(89.9)) # 89.9 print(abs(0)) # 0 العثور على الحاصل والباقي بدالة واحدة القسمة التحتية (floor division، التي تُعيد حاصل القسمة [quotient])، وقسمة الباقي (modulo division، التي تعيد باقي القسمة [remainder])، مرتبطان ارتباطًا وثيقًا، وقد يكون من المفيد استخدام دالة تجمع بين العمليتين معًا. تجمع الدالة المضمّنة divmod() بين العمليتين، إذ تعيد أولًا حاصل عملية القسمة التحتية، ثم الباقي. ينبغي تمرير عددين إلى الدالة divmod()، على النحو التالي: divmod(a,b) تكافئ هذه الدالة العمليتين التاليتين: a // b a & b لنفترض أننا كتبنا كتابًا يحتوي 80 ألف كلمة. يريد الناشر أن تحتوي كل صفحة من الكتاب ما بين 300 و 250 كلمة، ونود أن نعرف عدد الصفحات التي ستشكل الكتاب بحسب عدد كلمات الصفحة الذي اخترناه. باستخدام الدالة divmod()، يمكننا أن نعرف على الفور عدد الصفحات في الكتاب، وعدد الكلمات المتبقية التي ستُنقل إلى صفحة إضافية. words = 80000 # كم عدد الكلمات في كتابنا per_page_A = 300 # 300 عدد كلمات الصفحة هو :A الخيار per_page_B = 250 # عدد كلمات الصفحة هو 250 :B الخيار print(divmod(words,per_page_A)) # A حساب الخيار print(divmod(words,per_page_B)) # B حساب الخيار وسينتج عن هذه الشيفرة: (266, 200) (320, 0) في الخيار A، سنحصل على 266 صفحة مليئة بالكلمات، و 200 كلمة متبقية (ثلثا صفحة)، والمجموع هو 267 صفحة، وفي الخيار B، سنحصل على كتاب من 320 صفحة. إن أردنا الحفاظ على البيئة، فالخيار A قد يكون أفضل، ولكن إذا أردنا تصميمًا جذابًا، أو الكتابة بحجم خط كبير، فقد نختار الخيار "B". تقبل الدالة divmod() االأعداد الصحيحة والأعداد العشرية، في المثال التالي سنمرر عددًا عشريًّا إلى الدالة divmod(): a = 985.5 b = 115.25 print(divmod(a,b)) # (8.0, 63.5) في هذا المثال، العدد 8.0 هو حاصل القسمة التحتية للعدد 985.5 مقسومًا على 115.25، و 63.5 هو الباقي. يمكنك استخدام عامل القسمة التحتية // و عامل الباقي% للتحقق من نتيجة divmod(): print(a//b) # 8.0 print(a%b) # 63.5 القوة (Power) في بايثون، يمكنك استخدام عامل القوة ** (أو الأس) لرفع عدد إلى قوة معينة، أو يمكنك استخدام الدالة pow() المضمّنة التي تأخذ عددين وتجري العملية نفسها. لتوضيح كيفية عمل الدالة pow()، لنقل أننا نجري أبحاثًا على البكتيريا، ونريد أن نقدّر عدد البكتيريا التي سنحصل عليها في نهاية اليوم إذا بدأنا ببكتيريا واحدة. تتضاعف البكتيريا التي نعمل عليها في كل ساعة، لذلك النتيجة النهائية ستكون 2 قوة العدد الكلي لعدد الساعات التي مرت (24 في حالتنا). hours = 24 total_bacteria = pow(2,hours) print(total_bacteria) # 16777216 لقد مرّرنا عددين صحيحين للدالة pow()، والنتيجة التي حصلنا عليها، والتي تمثِّل عدد البكتيريا بحلول نهاية اليوم، هي أكثر من 16 مليون بكتيريا. في الرياضيات، نكتب "3 أسّ 3" بشكل عام على النحو التالي: 3³ والتي تكافئ 3 × 3 × 3، أي 27. ولحساب 3³ في بايثون، نكتب pow(3,3). تقبل الدالة pow() الأعداد الصحيحة والأعداد العشرية، وتوفّر بديلًا لعامل الأس **. تقريب الأعداد تقريب الأعداد (Rounding Numbers) ضروري عند العمل مع الأعداد العشرية التي تحتوي على الكثير من المنازل (الأجزاء) العشرية. تقبل الدالة المضمنة round() عددين: أحدها يمثِّل العدد المراد تقريبه والآخر يحدّد عدد المنازل العشرية المراد الإبقاء عليها (أي قيمة التقريب). سنستخدم هذه الدالة لتقريب عدد عشري له أكثر من 10 منازل عشرية والحصول على عدد بأربعة منازل عشرية فقط: i = 17.34989436516001 print(round(i,4)) # 17.3499 في المثال أعلاه، تم تقريب العدد 17.34989436516001 إلى 17.3499 لأننا حددنا عدد المنازل العشرية التي ينبغي الاقتصار عليها بأربعة. لاحظ أيضًا أنّ الدالة round() تقرّب الأعداد إلى الأعلى، لذا بدلًا من إعادة 17.3498، فقد أعادت 17.3499، لأنّ الرقم الذي يلي المنزل العشري 8 هو الرقم 9. وسيتم تقريب أي عدد متبوع بالعدد 5 أو أكبر إلى العدد الصحيح التالي. في الحياة اليومية، نُقرِّب الأعداد لأسباب كثيرة، وخاصة في التعاملات المالية؛ فلا يمكننا تقسيم فلس واحد بالتساوي بين عدة أصدقاء مثلًا. سنكتب في المثال التالي برنامجًا بسيطًا يمكنه حساب البقشيش. في هذا المثال سنحدد قيم المتغيرات، ولكن يمكنك إعادة كتابة البرنامج لجعل المستخدمين يدخلون القيم. في هذا المثال، ذهب 3 أصدقاء إلى مطعم، وأرادوا تقسيم الفاتورة، والتي تبلغ 87.93 دولارًا بالتساوي، بالإضافة إلى إكرامية (بقشيش) بنسبة 20٪. bill = 87.93 # إجمالي الفاتورة tip = 0.2 # ٪ 20 بقشيش split = 3 # عدد الناس الذين سيتشاركون الفاتورة total = bill + (bill * tip) # حساب الفاتورة الإجمالية each_pay = total / split # حساب ما ينبغي أن يدفعه كل شخص print(each_pay) # ما ينبغي أن يدفعه كل شخص قبل التقريب print(round(each_pay,2)) # تقريب العدد - لا يمكننا تقسيم الفلسات والمخرجات ستكون: 35.172000000000004 35.17 في هذا البرنامج، نطلب أولًا إخراج العدد بعد حساب إجمالي الفاتورة والإكراميات مقسومًا على 3، النتيجة ستكون عددًا يتضمّن الكثير من المنازل العشرية: 35.172000000000004. نظرًا لأنّ هذا العدد ليس له معنى كمبلغ مالي، فإننا نستخدم الدالة round()، ونقصر المنازل العشرية على 2، حتى نتمكن من توفير ناتج يمكن للأصدقاء الثلاثة أن يدفعوه: 35.17. إذا كنت تفضل التقريب إلى عدد بلا منازل عشرية، يمكنك تمرير 0 كمعامل ثان إلى الدالة round(): round(345.9874590348545304636,0) القيمة الناتجة ستكون 346.0. يمكنك أيضًا تمرير الأعداد الصحيحة إلى round() دون الخوف من تلقي خطأ، وهذا مفيد في حال تلقيت من المستخدم عددًا صحيحًا بدلًا من عدد عشري. وفي هذه الحالة، سيُعاد عدد صحيح. حساب المجموع تُستخدم الدالة sum() لحساب مجاميع أنواع البيانات العددية المركبة (numeric compound data types)، بما في ذلك القوائم، والصفوف، والقواميس. يمكننا تمرير قائمة إلى الدالة sum() لجمع كل عناصرها بالترتيب من اليسار إلى اليمين: some_floats = [1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9] print(sum(some_floats)) # 49.5 نفس النتيجة سنحصل عليها إن استخدمنا الصفوف والقواميس: print(sum((8,16,64,512))) # حساب مجموع الأعداد في الصف print(sum({-10: 'x', -20: 'y', -30: 'z'})) # حساب مجموع الأعداد في القاموس المخرجات: 60 -60 يمكن أن تأخذ الدالة sum() وسيطين، الوسيط الثاني سيُضاف إلى المجموع الناتج: some_floats = [1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9] print(sum(some_floats, 0.5)) # 50.0 print(sum({-10: 'x', -20: 'y', -30: 'z'},60)) # 0 القيمة الافتراضية للوسيط الثاني هي 0. خلاصة غطينا في هذه المقالة بعض التوابع المضمّنة التي يمكنك استخدامها مع أنواع البيانات العددية في بايثون. هذه المقالة جزء من سلسة مقالات حول تعلم البرمجة في بايثون 3. ترجمة -وبتصرّف- للمقال Built-in Python 3 Functions for Working with Numbers لصاحبته Lisa Tagliaferri اقرأ أيضًا المقالة التالية: فهم العمليات المنطقية في بايثون 3 المقالة السابقة: كيفية إجراء العمليات الحسابية في بايثون 3 المرجع الشامل إلى تعلم لغة بايثون كتاب البرمجة بلغة بايثون