أنشأ راندال مونرو Randall Munroe في مدونة الكاريكاتير XKCD مخططًا تقنيًا لصاروخ "زحل الخامس" تحت عنوان "Up Goer Five" مُستخدمًا فقط الكلمات الإنجليزية الألف الأشهر، إذ بسّط المصطلحات التقنية إلى جمل يستطيع أي طفل فهمها، إلا أن هذا الأمر يسلط الضوء أيضًا على تفسير عدم إمكانية شرح أي شيء باستخدام مصطلحات بسيطة؛ فالشرح القائل "شيء يساعد الناس على الهرب بسرعة كبيرة في حال وجود مشكلة واشتعال كل شيء ما يجعلهم يقررون عدم الذهاب إلى الفضاء" أسهل فهمًا للمتلقي العادي من عبارة "بدء نظام الهروب Launch Escape System". الشرح الأول مبالغ بإطالته ليستخدمه مهندسو الإدارة الوطنية للملاحة الجوية والفضاء NASA في عملهم اليومي، بل أنهم حتى قد يفضلون استخدام الاختصار LES.
قد تسبب مصطلحات الحوسبة الحيرة والقلق للمبرمجين المبتدئين، إلا أنها أمر لا بد من تعلمه، إذ تختلف العديد من المصطلحات في بايثون ومجال التطوير البرمجي عمومًا اختلافات دقيقة، لدرجة أن المطورين المتمرسين قد يخلطون بينها دون اكتراث. قد يختلف التعريف التقني لهذه المصطلحات من لغة برمجة لأخرى، إلا أن هذا المقال يغطي المصطلحات المتعلقة بلغة بايثون تحديدًا، إذ ستحصل فيه على فهم واسع لمفاهيم لغة البرمجة الكامنة في الكواليس حتى لو لم يكن عميقًا.
تعاريف
ما أن يبلغ عدد المبرمجين في نفس المكان اثنين، حتى تصبح احتمالية انطلاق نقاش حول دلالات اللغة 100%، فاللغة سهلة والبشر هم سادات الكلمات وليس العكس. قد يستخدم بعض المطورين المصطلحات بطريقة مختلفة قليلًا، ومع ذلك يبقى التعرف على هذه المصطلحات أمرًا مفيدًا. سنستعرض في هذا المقال قسمًا من هذه المصطلحات وكيفية مقارنتها ببعضها بعضًا، وفي حال رغبتك بالحصول على قائمة مرتبة أبجديًا بالمصطلحات، فيمكنك الاعتماد على تلك الرسمية الخاصة ببايثون وصولًا إلى التعريفات الأساسية.
مما لا شك فيه أن بعض المبرمجين سيقرؤون التعريفات الواردة في هذا المقال طارحين حالاتٍ خاصة أو استثناءات وهي في الواقع غير محدودة، فلا يمكن عد هذا المقال دليلًا إرشاديًا كاملًا ونهائيًا، بل إنه يهدف إلى تزويدك بالتعريفات حتى لو لم تكن شاملة تمامًا، فكما هو الحال مع كل ميادين البرمجة، يوجد دائمًا المزيد لتعلمه.
لغة بايثون ومفسر بايثون
تنطوي كلمة "بايثون" على معانٍ عديدة، إذ سُميّت لغة البرمجة بايثون نسبةً إلى الفرقة الكوميدية البريطانية Monty Python، وليس نسبةً إلى الثعبان كما توحي الترجمة الحرفية، رغم كون مصادر بايثون التعليمية وتوثيقاتها تشير إلى كلًا من فرقة Monty Python والثعبان مثل مرجع لأصل التسمية، وكذلك الأمر من حيث الشق البرمجي، إذ تحمل كلمة بايثون معنيين أيضًا.
يمكن أن نقول "تُشغّل بايثون برنامجًا" أو "ستعرض بايثون استثناءً"، والمقصود هنا هو مفسر بايثون -وهو البرنامج الفعلي المسؤول عن قراءة النصوص ضمن الملفات ذات اللاحقة "py." مُنفذًا التعليمات الواردة فيها؛ فعندما نقول "مفسر بايثون"، نقصد غالبًا "CPython"، وهو المفسر المُعتمد من قبل مؤسسة بايثون للبرمجيات Python Software Foundation. إذًا، CPython هو تنفيذ للغة بايثون، بمعنى أنه برمجية أُنشئت لتحقق مواصفات محددة، ولكن يوجد برمجيات غيرها؛ ففي حين أن المفسر CPython مكتوب بلغة البرمجة سي C، المفسر Jython
مكتوبٌ بلغة جافا Java بغية تشغيل نصوص بايثون البرمجية القابلة للتشغيل المتبادل مع برامج جافا؛ أما PyPy
فهو مترجم ديناميكي just-in-time compiler لبايثون والذي يترجم البرامج لدى تنفيذها، فهو مكتوبٌ بلغة بايثون.
تُشغّل كل من هذه التنفيذات الشبفرات المصدرية المكتوبة بلغة البرمجة بايثون، وهي المقصودة لدى قولنا: "هذا برنامج بايثون" أو "أتعلم بايثون".ومن الناحية المثالية فإن أي مفسر بايثون قادر على تشغيل أي شيفرة مصدرية مكتوبة بلغة بايثون، أما في الواقع العملي فتوجد بعض حالات عدم التوافق البسيطة والاختلافات ما بين المفسرات، ولعل سبب تسمية CPython بالمفسر المرجعي للغة بايثون هو أنه في حال وجود اختلافات في كيفية تفسير شيفرة بايثون ما بين CPython وغيره من المفسرات، سيكون ما فسره CPython هو الصحيح والمُتفق عليه.
كنس المهملات Garbage Collection
كان على المبرمج سابقًا في لغات البرمجة القديمة توجيه البرنامج لتخصيص أو إلغاء تخصيص أو تحرير الذاكرة لبنى المعطيات حسب الحاجة، وقد كانت عملية تخصيص الذاكرة اليدوية هذه مصدرًا للعديد من الأخطاء، مثل تسريبات الذاكرة memory leaks (التي تحدث عند نسيان المبرمج لتحرير الذاكرة) أو أخطاء التحرير المزدوج للذاكرة double-free bugs (إذ يحرر المبرمج نفس الجزء من الذاكرة مرتين، متسببًا في تلف البيانات).
تمنلك بايثون ميزة تجميع وكنس المهملات لتجنب هذه الأخطاء، والتي تعد أحد أشكال الإدارة التلقائية للذاكرة والتي تتعقّب التوقيت المناسب لتخصيص وتحرير الذاكرة نيابةً عن المبرمج. من الممكن النظر إلى تجميع المهملات مثل عملية إعادة تدوير للذاكرة، لكونها تتيح الذاكرة للبيانات الجديدة. فعلى سبيل المثال، لنكتب ما يلي في الصدفة التفاعلية:
>>> def someFunction(): ... print('someFunction() called.') ... spam = ['cat', 'dog', 'moose'] ... >>> someFunction() someFunction() called.
تخصص بايثون لدى استدعاء الدالة ()someFunction
الذاكرة للقائمة ['cat', 'dog', 'moose']
. وبالتالي ما من حاجة لمعرفة المبرمج عدد البايتات من الذاكرة المتوجب طلبها لأن بايثون تدير هذا الأمر نلقائيًا، إذ سيحرر مُجمّع المهملات المتغيرات المحلية بمجرد إعادة الدالة المُستدعاة، ما يجعل الذاكرة متاحةً لبيانات جديدة. إذًا، يجعل مفهوم تجميع المهملات من البرمجة أمرًا أسهل وأقل عرضة للأخطاء.
القيم المجردة Literals
القيمة المجردة هي نص ضمن الشيفرة المصدرية ذات قيمة ثابتة كما هي مكتوبة. ففي المثال التالي 42 هو قيمة صحيحة مجردة و 'Zophie'
هي سلسلة نصية مجردة.:
>>> age = 42 + len('Zophie')
يمكن فهم القيم المجردة مثل قيم تظهر كما هي بحرفيتها مثل نص ضمن الشيفرة المصدرية، ولا يمكن إلا لأنواع البيانات المُعرّفة أصلًا في بايثون امتلاك قيم مجردة في الشيفرة المصدرية، وبالتالي ليس المتغير age
قيمة مجردة. يبيّن الجدول 1 التالي بعض الأمثلة عن القيم المجردة في بايثون.
القيمة المجردة | نوع البيانات |
---|---|
42 | عدد صحيح |
3.14 | عدد عشري |
1.4886191506362924e+36 | عدد عشري |
"""!Howdy""" | سلسلة نصية |
'r'Green\Blue | سلسلة نصية |
[] | قائمة |
{'name': 'Zophie'} | قاموس |
'b'\x41 | بايتات |
True | بولياني |
None | نوع فارغ NoneType |
قد يجادل بعض المعترضين في أن بعضًا من القيم لا يمثّل قيمًا مجردة استنادًا إلى التوثيق الرسمي للغة بايثون، فمن الناحية التقنية، 5-
ليست قيمة مجردة في بايثون لأن اللغة تعرّف رمز السالب (-) مثل عامل على القيمة المجردة 5، كما تُعد القيم True
و False
و None
كلمات مفتاحية في بايثون وليست قيمًا مجردة، في حين أن []
و{}
تسمى مُخرجات أو ذرات atoms اعتمادًا على جزء التوثيق الرسمي الذي تبحث ضمنه. بغض النظر عن ذلك، القيمة المجردة هي مصطلح شائع يستخدمه محترفو البرمجيات للدلالة على كل الأمثلة السابقة.
الكلمات المفتاحية Keywords
تمتلك كل لغة برمجة الكلمات المفتاحية الخاصة بها؛ وكلمات بايثون المفتاحية هي مجموعة من الأسماء المحجوزة للاستخدام مثل جزء من بناء اللغة والتي لا يمكن استخدامها أسماءً للمتغيرات (أي المعرّفات). على سبيل المثال، لا يمكنك تسمية أحد المتغيرات بالاسم while
لأنها كلمة محجوزة للاستخدام في حلقات while
التكرارية، وفيما يلي الكلمات المفتاحية المحجوزة في بايثون وفقًا للإصدار 3.9 منها.
and | continue | finally | is | raise |
---|---|---|---|---|
as | def | for | lambda | return |
assert | del | from | None | True |
async | elif | global | nonlocal | try |
await | else | if | not | while |
break | except | import | or | with |
class | False | in | pass | yield |
نلاحظ أن كلمات بايثون المفتاحية هي دومًا باللغة الإنجليزية وغير متاحة بأي لغات أخرى. فعلى سبيل المثال، في الدالة التالية المعرفات مكتوبة باللغة الاسبانية، في حين بقيت الكلمتين def
و return
المفتاحيتين في بايثون بالإنجليزية:
def agregarDosNúmeros(primerNúmero, segundoNúmero): return primerNúmero + segundoNúmero
تهيمن اللغة الإنجليزية لسوء الحظ على مجال البرمجة بالنسبة للتعداد البالغ 6.5 مليار من غير الناطقين بها.
الكائنات Objects والقيم Values والنسخ Instances والهويات Identities
يمثّل الكائن جزءًا من البيانات سواء كانت عددًا أو بعضًا من النصوص أو هيكل بيانات أكثر تعقيدًا، مثل القائمة، أو القاموس. ويمكن تخزين الكائنات ضمن متغيرات وتمريرها مثل وسطاء عند استدعاء الدوال واستخدامها مثل قيمة معادة عن استدعاء الدوال.
تمتلك جميع الكائنات قيمةً وهوية ونوع بيانات، والقيمة هي البيانات التي يمثلها الكائن، مثل العدد الصحيح 42، أو السلسلة النصية 'hello'
. يستخدم بعض المبرمجين في الواقع مصطلح القيمة بمثابة مرادف لمصطلح الكائن، لا سيما لأنواع البيانات البسيطة مثل الأعداد الصحيحة أو السلاسل النصية، رغم أن هذا الأمر قد يسبب الإزعاج.
يُنشَأ الكائن بهوية، وهي رقم صحيح فريد من الممكن الإطلاع عليه باستدعاء الدالة ()id
. على سبيل المثال، لنكتب ما يلي في الصدفة التفاعلية:
>>> spam = ['cat', 'dog', 'moose'] >>> id(spam) 33805656
يُخزّن المتغير spam
كائنًا من نوع البيانات "قائمة"، قيمته تساوي ['cat', 'dog', 'moose']
أما هويته فهي 33805656
، رغم كون العدد الصحيح ID هذا يتغير في كل مرة يُنفّذ فيها البرنامج، وبالتالي ستكون القيمة على حاسوبك مختلفة عن هذه المعروضة هنا، ولكن بمجرد إنشاء الكائن، لن تتغير قيمة هويته طالما أن البرنامج قيد التشغيل، ورغم أن كلًا من هوية ونوع بيانات الكائن لا يتغيران أثناء تشغيل البرنامج، إلا أن قيمته قد تتغير، كما في المثال التالي:
>>> spam.append('snake') >>> spam ['cat', 'dog', 'moose', 'snake'] >>> id(spam) 33805656
وبذلك أصبحت القائمة تتضمّن أيضًا العنصر 'snake'
، ولكن وكما هو مبين من نتيجة استدعاء الدالة (id(spam
أن هويتها لم تتغير وبقيت نفسها، ولكن ماذا لو كتبنا الشيفرات التالية:
>>> spam = [1, 2, 3] >>> id(spam) 33838544
استُبدلت القيمة في المتغير spam
بكائن قائمة جديد وبهوية جديدة قيمتها 33838544
بدلًا عن 33805656
، إذ يختلف المعرف spam
عن مفهوم الهوية من حيث أن عدّة معرفات قد تتبع لكائن واحد، كما في المثال التالي الذي أسندنا فيه متغيرين لنفس القاموس:
>>> spam = {'name': 'Zophie'} >>> id(spam) 33861824 >>> eggs = spam >>> id(eggs) 33861824
ومنه نجد أن هوية كل من المعرفين spam
و eggs
نفسها وتساوي 33861824
، لأنهما يتبعان لنفس كائن القاموس. الآن وبتغيير قيمة spam
في الصدفة التفاعلية:
>>> spam = {'name': 'Zophie'} >>> eggs = spam 1 >>> spam['name'] = 'Al' >>> spam {'name': 'Al'} >>> eggs 2 {'name': 'Al'}
ومنه نجد أن التغييرات على المتغير spam
في السطر 1 قد ظهرت أيضًا على المتغير eggs
في السطر 2، وسبب ذلك أن كلاهما يتبع لنفس الكائن.
مفهوم المتغيرات المجرد: الصندوق BOX مقابل العنوان LABEL
تستخدم الكثير من الكتب التمهيدية مصطلح "الصناديق" بمثابة مفهوم مجرد للمتغيرات، ويُعد الأمر بسيطًا جدًا، فمن السهل فهم المتغيرات على أنها صناديق تُخزَّن فيها القيم كما في الشكل 1، إلا أن هذا المفهوم يتهاوى لدى التفكير من وجهة النظر المرجعية، فمثلًا المتغيرين spam
و eggs
السابقين لا يخزنان قاموسين منفصلين (نسختين من نفس القاموس) بل يخزنان مرجعًا في ذاكرة الحاسوب لنفس القاموس.
شكل 1: تفيد العديد من المراجع بأنه من الممكن فهم المتغيرات على أنها صناديق تحتوي على قيم
المتغيرات في بايثون من الناحية التقنية هي مراجع وليست حاويات containers للقيم، وذلك بغض النظر عن نوع البيانات التي تحتويها. رغم بساطة مفهوم الصندوق إلا أنه منقوص، فبدلًا من فهم المتغيرات على أنها صناديق، يُفضّل فهمها مثل عناوين للكائنات في الذاكرة، والشكل 2 التالي يبين العناوين لأمثلة المتغيرين spam
و eggs
السابقين.
شكل 2: من الممكن أيضًا فهم المتغيرات مثل عناوين للقيم
قد تشير عدة متغيرات إلى نفس الكائن، لذلك قد يُخزَّن الكائن الواحد في عدة متغيرات، ولكن لا يمكن للصناديق المختلفة أن تخزّن نفس الكائن الوحيد، ما يجعل فهم المتغيرات مثل عناوين أسهل وأقرب للفهم.
قد تكون معرضًا لوقوع أخطاء في شيفرتك ما لم تدرك أن عامل الإسناد =
ينسخ دومًا مرجع الكائن وليس الكائن نفسه، وذلك بظنك أنك تُنشئ نسختين متطابقتين من الكائن ولكن في الواقع أنت تنسخ مرجع الكائن الأصلي. لحسن الحظ أن هذه المسألة لا تتعلق بالقيم الثابتة immutable من أعداد صحيحة وسلاسل نصية وصفوف، وذلك لسبب سنشرحه لاحقًا في هذا الكتاب ضمن فقرة "متغيرات وثوابت".
يمكن استخدام العامل is
لمقارنة فيما إذا كان لكائنين نفس الهوية، وبالمقابل يمكن استخدام العامل ==
للتحقق فقط من كون قيمتي الكائنين متطابقتين، وبالتالي التعبير x is y
مكافئ لنظيره (id(x) == id(y
. لنكتب ما يلي في الصدفة التفاعلية لنلاحظ الفرق:
>>> spam = {'name': 'Zophie'} 1 >>> eggs = spam >>> spam is eggs True >>> spam == eggs True 2 >>> fish = {'name': 'Zophie'} >>> spam == fish True >>> spam is fish False
إذ يشير المتغيران spam
وeggs
إلى نفس كائن القاموس (في السطر 1)، وبالتالي فهما متساويان بالقيمة والهوية.، بينما يشير المتغير fish
إلى كائن قاموس آخر (في السطر 2)، رغم كونه يتضمن بيانات مطابقة لتلك الموجودة في المتغيرين spam
و eggs
. يعني تطابق البيانات أن للمتغير fish
نفس القيمة كما في المتغيرين spam
و eggs
، إلا أنهما كائنان مختلفان بهويتين مختلفتين.
العناصر Items
ندعو في بايثون الكائن الموجود ضمن كائن حاوية مثل قائمة أو قاموس بالعنصر، فعلى سبيل المثال، السلسة النصية ضمن القائمة ['dog', 'cat', 'moose']
هي كائنات ولكننا ندعوها أيضًا بالعناصر.
الثابت Immutable والمتغير Mutable
كما لاحظنا سابقًا، تمتلك كل الكائنات في بايثون قيمة ونوع بيانات وهوية، ويمكن منها تغيير القيمة فقط، وبما أننا نستطيع تغيير قيمة الكائن فهو كائن متغيّر؛ أما إذا كانت قيمته غير متغيرة فندعوه بالكائن الثابت. يعرض الجدول 2 قائمةً ببعض أنواع البيانات الثابتة والمتغيرة في بايثون.
جدول 2: بعض أنواع البيانات الثابتة والمتغيرة في بايثون
أنواع البيانات الثابتة | أنواع البيانات المتغيرة |
---|---|
عدد صحيح | قائمة |
عدد عشري | قاموس |
قيمة بوليانية | مجموعات |
سلسلة نصية | مصفوفة بايت Bytearray |
مجموعة ثابتة Frozen set | مصفوفة |
بايتات | |
صف Tuple |
فلو أعدت الكتابة في متغير ما، قد يبدو لك أنك قد غيرت قيمة الكائن الخاص به، كما في المثال التالي في الصدفة التفاعلية:
>>> spam = 'hello' >>> spam 'hello' >>> spam = 'goodbye' >>> spam 'goodbye'
لكننا لم نغيّر هنا قيمة الكائن 'hello'
من 'hello'
إلى 'goodbye'
، فهما كائنان منفصلان، وكل ما فعلناه في الشيفرة السابقة هو مجرّد جعل المتغير spam
يشير إلى الكائن 'goodbye'
بدلًا من 'hello'
، ويمكن التحقق من صحة أنهما فعلًا كائنين مستقلين باستخدام الدالة ()id
للحصول على هوية كل منهما:
>>> spam = 'hello' >>> id(spam) 40718944 >>> spam = 'goodbye' >>> id(spam) 40719224
ومنه نجد أنّ لهذين الكائنين النصيين هويتان مختلفتان (40718944 و 40719224) نظرًا لكونهما كائنين مستقلين، بينما يمكن تغيير قيمة المتغيرات التي تشير إلى كائنات متغيرة، مثل القوائم والصفوف مع الحفاظ على نفس الهوية. دعنا نكتب على سبيل المثال الشيفرات التالية في الصدفة التفاعلية:
>>> spam = ['cat', 'dog'] >>> id(spam) 33805576 1 >>> spam.append('moose') 2 >>> spam[0] = 'snake' >>> spam ['snake', 'dog', 'moose'] >>> id(spam) 33805576
تعمل كلًا من الدالة ()append
(في السطر 1) وعملية الإسناد للعنصر (في السطر 2) على تعديل قيمة القائمة في مكانها (من أجل نفس الكائن بنفس الهوية)؛ فقد تغيرت قيمة القائمة، إلا أن هويتها قد بقيت كما هي 33805576؛ إما في حال استخدام العامل +
لبناء تسلسل القائمة، فإننا ننشئ كائنًا جديدًا (ذو هوية جديدة) بديلًا عن ذلك السابق على النحو التالي:
>>> spam = spam + ['rat'] >>> spam ['snake', 'dog', 'moose', 'rat'] >>> id(spam) 33840064
ومنه نجد أن بناء تسلسل القائمة وفق الطريق السابقة (باستخدام العامل +
) قد أنشأ قائمة جديدة بهوية جديدة، وبمجرد حدوث ذلك يُحرّر مُجمّع المهملات القائمة القديمة من الذاكرة. لمعرفة التوابع والعمليات التي تعدل قيم الكائنات مع الحفاظ على هويتها وتلك التي تغيرها، عليك الرجوع إلى توثيق بايثون، ولكن يمكنك أخذ القاعدة التالية بالحسبان: إذا رأيت قيمةً مجردةً ضمن الشيفرة المصدرية، مثل ['rat']
في المثال السابق، فعندها سينشئ بايثون كائنًا جديدًا بهوية جديدة غالبًا، في حين أن التوابع المُستدعاة مع استخدام الكائن بمثابة وسيط لها، مثل التابع ()append
، فغالبًا ما تعدّل قيمة الكائن مُحافظةً على هويته.
في حال التعامل مع أنواع البيانات الثابتة مثل الأعداد الصحيحة والسلاسل النصية والصفوف، ستكون عمليات الإسناد هي الأبسط. لنكتب على سبيل المثال ما يلي في الصدفة التفاعلية:
>>> fish = 'Goodbye' >>> id(fish) 33827584 1 >>> fish = 'Hello' >>> id(fish) 33863820 2 >>> fish = fish + ', world!' >>> fish 'Hello, world!' >>> id(fish) 33870056 3 >>> fish[0] = 'J' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'str' object does not support item assignment
السلاسل النصية ثابتة، بمعني أنه من غير الممكن تغيير قيمة السلسلة نفسها، ففي حين يبدو أننا قد غيرنا قيمة السلسة النصية في المتغير fish
من القيمة 'Goodbye'
إلى القيمة 'Hello'
(في السطر رقم 1)، إلا أنّه في الواقع قد غيّرنا قيمته إلى كائن سلسلة نصية جديدة ذو هوية جديدة؛ وعلى نحوٍ مشابه، ينشئ التعبير البرمجي باستخدام بناء تسلسل القائمة كائن قائمة جديد (كما في السطر 2) ذو هوية جديدة. لا يُسمَح في الإصدار 3 من بايثون تغيير السلاسل النصية باستخدام عمليات الإسناد دون تغيير هوية الكائن.
تُعرّف قيمة الصف وفقًا للكائنات التي يحتويها وترتيب هذه الكائنات؛ فالصفوف هي تسلسلات من الكائنات الثابتة التي تحصر القيم في أقواس، ما يعني أنه من غير الممكن تغيير العناصر الموجودة في الصف، كما في المثال التالي:
>>> eggs = ('cat', 'dog', [2, 4, 6]) >>> id(eggs) 39560896 >>> id(eggs[2]) 40654152 >>> eggs[2] = eggs[2] + [8, 10] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment
أما في حال تواجد قائمة متغيرة ضمن الصف الثابت، فستبقى متغيّرة دون تغيير هويتها، كما في المثال التالي:
>>> eggs[2].append(8) >>> eggs[2].append(10) >>> eggs ('cat', 'dog', [2, 4, 6, 8, 10]) >>> id(eggs) 39560896 >>> id(eggs[2]) 40654152
ورغم كون هذه الحالة خاصة ومبهمة قليلًا، إلا أنه من الضروري أخذها بالحسبان، فلا يزال الصف يشير إلى نفس الكائنات كما هو موضح في الشكل 3، ولكن إذا احتوى الصف على كائن متغيّر وفي حال تغيرت قيمة هذا الكائن -بمعنى أن هذا الكائن قد تغير- تتغيّر قيمة الصف أيضًا.
يعد كل محترفي بايثون الصف ثابتًا، ويعتمد أمر تسمية بعض الصفوف بأنها متغيرة على تعريفك وفهمك لها.
الشكل 3: رغم كون مجموعة الكائنات ضمن الصف ثابتة، إلا أن المتغيرات نفسها قادرة على التغير.
الفهارس Indexes والمفاتيح Keys والقيم المعماة Hashes
قوائم وقواميس بايثون هي قيم يمكن أن تحتوي على قيم أخرى متعددة، ويمكن للوصول إلى هذه القيم استخدام عامل الفهرس المكون من زوج من الأقواس المعقوفة وعدد صحيح يسمى الفهرس لتحديد القيمة المراد الوصول إليها، ولنبيّن كيفية استخدام الفهرس مع القائمة، سنكتب ما يلي في الصدفة التفاعلية:
>>> spam = ['cat', 'dog', 'moose'] >>> spam[0] 'cat' >>> spam[-2] 'dog'
في المثال السابق، الرقم 0 هو فهرس، إذ أن الفهرس الأول هو 0 وليس 1، إذ تستخدم بايثون ومعظم لغات البرمجة الصفر بدايةً للفهارس، أما اللغات التي تعتمد 1 بدايةً للفهارس فهي نادرة وأشهرها لغتي Lua و R، كما أن بايثون تدعم الفهارس السالبة، إذ يشير مثلًا "1-" إلى العنصر الأخير في القائمة، في حين يشير "2-" إلى العنصر ما قبل الأخير، وهكذا. يمكن فهم الفهرس السالب بالشكل [spam[–n
مكافئًا لاستخدام [spam[len(spam) – n
.
ملاحظة: قال عالم الحاسوب والمغني وكاتب الأغاني ستان كيلي-بوتل Stan Kelly-Bootle مرة مازحًا: أيجب أن تبدأ فهارس المصفوفات من الصفر أو الواحد؟ كان اقتراحي بمثابة حلٍ وسطي أن تبدأ من 0.5 ولكنه رفض دون سبب وجيه على ما أعتقد.
يمكن استخدام عامل الفهرس على السلسلة المجردة كما هي، رغم كون الأقواس المعقوصة الخاصة بالفهرس قد تبدو غريبة وغير ضرورية في هذه الحالة، كما في المثال:
>>> ['cat', 'dog', 'moose'][2] 'moose'
: كما يمكن استخدام الفهارس على قيم غير القوائم، مثل السلاسل النصية بغية الحصول على محارف معينة من السلسلة، على النحو التالي:
>>> 'Hello, world'[0] 'H'
أما قواميس بايثون، فتُرتّب على هيئة أزواج "مفتاح-قيمة"، على النحو التالي:
>>> spam = {'name': 'Zophie'} >>> spam['name'] 'Zophie'
تنحصر فهارس القوائم بكونها أعداد صحيحة، لكن فهارس قواميس بايثون هي مفاتيح، يمكن أن تكون أي قيمة قابلة للتعمية؛ فالقيمة المعماة هي عدد صحيح شبيه بالبصمة الخاصة بالقيمة، إذ أن القيمة المعماة الخاصة بكائن ما لا تتغير طوال فترة وجود الكائن، وللكائنات المتماثلة في قيمها، قيم معماة متماثلة أيضًا. في مثالنا السابق، السلسلة النصية 'name'
هي مفتاح القيمة 'Zophie'
. وتعيد الدالة ()hash
القيمة المعماة للكائنات القابلة للتعمية. الكائنات الثابتة مثل الأعداد الصحيحة والأعداد العشرية والسلاسل النصية والصفوف قابلة للتعمية، في حين أن القوائم وغيرها من الكائنات المتغيرة غير قابلة للتعمية. لنكتب ما يلي في الصدفة التفاعلية:
>>> hash('hello') -1734230105925061914 >>> hash(42) 42 >>> hash(3.14) 322818021289917443 >>> hash((1, 2, 3)) 2528502973977326415 >>> hash([1, 2, 3]) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unhashable type: 'list'
تُستخدم المفاتيح المعماة لإيجاد العناصر المخزنة في القواميس وغيرها من هياكل البيانات، ولهذا لا يمكن استخدام قائمة متغيرة مثل مفتاح لقاموس:
>>> d = {} >>> d[[1, 2, 3]] = 'some value' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unhashable type: 'list'
تختلف القيمة المعماة عن الهوية، فلو كان لدينا كائنان مختلفان يتضمنان نفس القيمة، سيكون لكل منهما هويته الخاصة، أما القيمة المعماة فستكون نفسها لكليهما. لنكتب على سبيل المثال ما يلي في الصدفة التفاعلية:
>>> a = ('cat', 'dog', 'moose') >>> b = ('cat', 'dog', 'moose') >>> id(a), id(b) (37111992, 37112136) 1 >>> id(a) == id(b) False >>> hash(a), hash(b) (-3478972040190420094, -3478972040190420094) 2 >>> hash(a) == hash(b) True
يمتلك الصفان المُشار إليهما بواسطة a
و b
هويتين مختلفتين كما هو موضح في السطر 1 من الشيفرة السابقة، ولكن كونهما يحتويان على قيمتين متطابقتين فالقيم المعماة لهما متساوية كما هو موضح في السطر 2. يكون الصف قابلًا للتعمية في حال احتوائه على عناصر قابلة للتعمية، وبما أنه لا يمكن استخدام سوى عناصر قابلة للتعمية مثل مفاتيح للقواميس، فمن غير الممكن استخدام صفٍ يحتوي على قائمة غير قابلة للتعمية مثل مفتاح. لنكتب ما يلي ضمن الصدفة التفاعلية:
>>> tuple1 = ('cat', 'dog') >>> tuple2 = ('cat', ['apple', 'orange']) >>> spam = {} 1 >>> spam[tuple1] = 'a value' 2 >>> spam[tuple2] = 'another value' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unhashable type: 'list'
نلاحظ أن الصف tuple1
قابل للتعمية، في حين أن الصف tuple2
يتضمن قائمة فهو غير قابل للتعمية مثلها.
أنواع الحاويات Containers والسلاسل المفهرسة بأرقام Sequences والمفهرسة بمفاتيح Mapping والمجموعات set
تمتلك كلًا من الكلمات: حاوية وسلسلة مفهرسة بأرقام وسلسلة مفهرسة بمفاتيح معانٍ في بايثون قد لا تنطبق بالضرورة على غيرها من لغات البرمجة؛ فالحاوية في بايثون هي كائن من أي نوع بيانات، والتي قد تتضمن العديد من الكائنات الأخرى. تمثّل كلًا من القوائم والقواميس أنواعًا شائعة من الحاويات المستخدمة في بايثون.
السلسلة المفهرسة بأرقام هي كائن يحتوي على أي من أنواع بيانات الحاويات، ذو قيم مرتبة يمكن الوصول إليها اعتمادًا على فهارس من أعداد صحيحة. السلاسل النصية والصفوف والقوائم والمتغيرات من النوع byte
هي أنواع بيانات من نوع السلاسل المفهرسة بأرقام، إذ يمكن الوصول إلى القيم في هذا النوع من الكائنات باستخدام عامل الفهرسة (القوس المعقوف [
و ]
)، كما يمكن تمرير الفهرس إلى الدالة ()len
. يُقصد بكلمة "مرتبة" وجود ما يمكن تسميته القيمة الأولى والثانية وهكذا، فعلى سبيل المثال، لا يمكن أن تكون قيمتا القائمتين التاليتين متساويتين لأنهما مرتبتان على نحوٍ مختلف:
>>> [1, 2, 3] == [3, 2, 1] False
السلسلة المفهرسة بمفاتيح mapping هي كائن يحتوي على أي من أنواع بيانات الحاويات الذي يستخدم المفاتيح عوضًا عن الفهرس. يمكن لهذا النوع من السلاسل أن يكون مرتبًا أو غير مرتبًا؛ فالقواميس في الإصدار 3.4 من بايثون والإصدارات الأقدم غير مرتبة لعدم وجود ما يسمى أول أو آخر زوج "مفتاح-قيمة" في القاموس، كما في المثال التالي:
>>> spam = {'a': 1, 'b': 2, 'c': 3, 'd': 4} # This is run from CPython 3.5. >>> list(spam.keys()) ['a', 'c', 'd', 'b'] >>> spam['e'] = 5 >>> list(spam.keys()) ['e', 'a', 'c', 'd', 'b']
لا توجد ضمانات للحصول على العناصر من القواميس بترتيب متسق في الإصدارات القديمة من بايثون، ونتيجةً للطبيعة غير الترتيبية للقواميس، سيكون لقاموسين مجردين يحتويان على نفس أزواج "مفتاح-قيمة" ولكن بترتيب مختلف قيمتان متساويتان:
>>> {'a': 1, 'b': 2, 'c': 3} == {'c': 3, 'a': 1, 'b': 2} True
ولكن بدءًا من الإصدار 3.6 من المفسر CPython
أصبحت القواميس تحتفظ بترتيب إدخال أزواج "مفتاح قيمة"، كما في المثال التالي:
>>> spam = {'a': 1, 'b': 2, 'c': 3, 'd': 4} # This is run from CPython 3.6. >>> list(spam) ['a', 'b', 'c', 'd'] >>> spam['e'] = 5 >>> list(spam) ['a', 'b', 'c', 'd', 'e']
وتعد هذه إحدى الميزات الموجودة في الإصدار 3.6 من المفسّر CPython وليست موجودة في كافة مفسرات الإصدار 3.6 من بايثون، بينما تدعم كافة مفسرات الإصدار 3.7 من بايثون القواميس الترتيبية، والتي أصبحت معيارية في لغة بايثون في الإصدار 3.7. إلا أن حقيقة كون قاموس ما ترتيبي لا يعني أنه من الممكن الوصول إلى عناصره باستخدام فهارس من أعداد صحيحة، فمثلًا لا يُقيّم التعبير [spam[0
على أنه العنصر الأول من القاموس الترتيبي (فيما عدا حالة كون مفتاح العنصر الأول من القاموس هو فعلًا 0). تعد القواميس الترتيبية أيضًا متطابقة إذا كانت تحتوي على نفس أزواج "مفتاح-قيمة"، حتى ولو كانت هذه الأزواج بترتيب مختلف في كل قاموس.
تتضمّن الوحدة collections
العديد من السلاسل المفهرسة بمفاتيح الأخرى بما يتضمن OrderedDict
(والتي تمثّل القواميس التي تحافظ على الترتيب دائمًا) و ChainMap
(المُستخددمة لتجميع عدة قواميس معًا) و Counter
(التي تُخزّن العناصر مثل مفاتيح للقواميس وعددها على أنها قيم فيه) و UserDict
، وجميعها موصوفة ضمن توثيق بايثون.
التوابع السحرية
التوابع السحرية والتي تسمّى Dunder methods أو magic methods هي توابع خاصة في بايثون تبدأ أسماء كل منها وتنتهي بشرطتين سفليتين، وتُستخدم هذه التوابع لزيادة تحميل العامل. يأتي مصطلح Dunder اختصارًا للعبارة double underscore أي "شرطتين سفليتين". لعل أشهر التوابع السحرية هو ()__int__
والتي تقرأ بالشكل "dunder init dunder" أي "شرطتان سفليتان int شرطتان سفليتان" أو فقط "int"، والذي يعمل على تهيئة الكائنات. تتضمن بايثون عشراتٍ من التوابع السحرية.
الوحدات والحزم
الوحدة module هي برنامج بايثون تكون برامج بايثون الأخرى قادرةً على استيراده وبالتالي استخدام شيفرته. تسمّى مجموعة الوحدات الموجودة افتراضيًا في بايثون متجمعةً باسم مكتبة بايثون المعيارية، كما من الممكن إنشاء وحدات خاصة بك. إذا حفظت برنامجًا ما باسم "spam.py" على سبيل المثال، فمن الممكن تشغيل أمر الاستيراد import spam.py
للوصول إلى الدوال والأصناف والمتغيرات عالية المستوى للبرنامج spam.py.
الحزمة Package هي مجموعة من الوحدات التي نُشكّلها عبر إنشاء ملف باسم "init.py" ضمن المجلد المطلوب، إذ نستخدم اسم هذا المجلد اسمًا للحزمة، ويمكن أن تحتوي الحزم على وحدات متعددة (وهي ملفات ذات امتداد "py.") أو حتى حزم أخرى (أي مجلدات أخرى تحتوي على ملفات "init.py").
الكائنات من الدرجة الأولى First-Class Objects والقابلة للاستدعاء Callables
لا تقتصر الأشياء القابلة للاستدعاء في بايثون على الدوال والتوابع، بل أي كائن يتضمّن عامل الاستدعاء -أي رمز القوسين الهلاليين ()- هو كائن قابل للاستدعاء. على سبيل المثال، لو كان لدينا التعبير ()def hello
، يمكن فهم هذه الشيفرة على أنها متغير يدعى hello
مُتضمنًا كائن دالة، إذ سيستدعي استخدام عامل الاستدعاء لهذا المتغير الدالة في المتغير بالشكل: ()hello
.
الأصناف classes هي من مفاهيم لغات البرمجة كائنية التوجه، ويمثّل الصنف مثالًا على كائن قابل للاستدعاء دون أن يكون دالة أو تابع، فعلى سبيل المثال، يمكن استدعاء صنف التاريخ date
الموجود في الوحدة datatime
باستخدام عامل الاستدعاء كما في الشيفرة (datetime.date(2020, 1, 1
، وبمجرد استدعاء كائن الصنف، تعمل الشيفرة الموجودة ضمن التابع ()__int__
من الصنف.
تعد الدوال من كائنات الدرجة الأولى في بايثون، ما يعني أنه يمكن تخزينها ضمن متغيرات وتمريرها مثل وسطاء عند استدعاء الدوال الأخرى واستخدامها مثل قيم مُعادة للدوال الأخرى أو تنفيذ كل ما يمكن تنفيذه مع أي كائن آخر، وبالتالي يمكن فهم التعليمة def
الخاصة بتعريف الدوال على أنها عملية إسناد لكائن الدالة إلى متغير ما، إذ يمكن مثلًا إنشاء دالة ()spam
لتُستدعى لاحقًا على النحو التالي:
>>> def spam(): ... print('Spam! Spam! Spam!') ... >>> spam() Spam! Spam! Spam!
كما يمكن إسناد كائن الدالة ()spam
إلى متغيراتٍ أخرى، وبمجرد استدعاء المتغير الذي أسندنا الدالة إليه، ينفذ بايثون هذه الدالة:
>>> eggs = spam >>> eggs() Spam! Spam! Spam!
ويُدعى هذا بالأسماء المستعارة aliases، وهي أسماء جديدة مختلفة لدوال موجودة أصلًا، ونلجأ إلى هذه الطريقة عند الحاجة إلى إعادة تسمية دالة ما مع موجود أجزاء كبيرة من الشيفرة تستخدم الاسم القديم، ما يتطلب جهدًا كبيرًا لتعديل الاسم في كامل الشيفرة.
ولعل الاستخدام الأكثر شيوعًا للدوال من الدرجة الأولى هو إمكانية تمرير الدوال مثل وسطاء لدوال أخرى، إذ يمكن مثلًا تعريف دالة باسم ()callTwicw
لنمرر لها أي دالة نريد استدعائها مرتين، على النحو التالي:
>>> def callTwice(func): ... func() ... func() ... >>> callTwice(spam) Spam! Spam! Spam! Spam! Spam! Spam!
كما يمكن الاكتفاء باستدعاء الدالة ()spam
مرتين بكتابتها مرتين في الشيفرة المصدرية، إلا أنه يمكن تمرير التابع المراد استدعائه مرتين إلى الدالة ()callTwicw
عوضًا عن كتابة اسم الدالة مرتين في الشيفرة المصدرية.
الخلاصة
من السهل أن تمارس البرمجة لسنوات مع بقاء بعض المصطلحات البرمجية غير مألوفة بالنسبة لك، إلا أن معظم التطبيقات البرمجية قد أنشئت من قبل فريق كامل من المطورين وليس من قبل أفراد، لذا تُعد القدرة على التواصل الواضح أمرًا أساسيًا عند العمل ضمن فريق.
وضحنا في هذا المقال أن برامج بايثون مكونة من معرفات ومتغيرات وسلاسل مجردة وكلمات مفتاحية وكائنات، وأن كل كائنات بايثون تمتلك قيمةً ونوع بيانات وهوية، ورغم كون كل كائن يمتلك نوع بيانات، إلا أنه يوجد مجموعة واسعة من فئات الأنواع، مثل الحاويات والسلاسل المفهرسة بأرقام وتلك المفهرسة بمفاتيح والمجموعات والمُعرف منها مسبقًا أو من قبل المستخدم.
ستجد دومًا أن استخدام المصطلحات يختلف من لغة لأخرى وحتى من مبرمج لآخر، وستغدو معتادًا أكثر على المصطلحات مع الخبرة والبحوثات المتكررة على الإنترنت.
ترجمة -وبتصرف- لجزء من الفصل السابع "المصطلحات البرمجية" من كتاب Beyond the Basic Stuff with Python لصاحبه Al Sweigart.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.