مجالات الأسماء والنطاقات في بايثون

من موسوعة حسوب

مجال الأسماء هو رابط بين الأسماء والكائنات، وتستخدم معظم مجالات الأسماء في الوقت الحاضر كقواميس. ومن الأمثلة على مجالات الأسماء: مجموعة الأسماء الداخلية (وتتضمن الدوال مثل abs()‎، وأسماء الاستثناءات الداخلية)، والأسماء العامة (global) في وحدة معينة، والأسماء المحلية (local) في بنية دالّة ما. إضافة إلى ما سبق، يمكننا أن نعدّ مجموعة خاصيات (attributes) كائن معيّن مجالًا للأسماء أيضًا.

والمقصود بالخاصيات هنا كل ما يأتي بعد النقطة، فعلى سبيل المثال في التعبير z.real تعدّ real خاصية للكائن z. كذلك تعدّ الإشارات إلى الأسماء في الوحدات إشارات للخاصيات، ففي التعبير modname.funcname، تكون modname كائن وحدة وfuncname خاصّية له. 

إنّ أهمّ ما يجب معرفته عن مجالات الأسماء هو عدم وجود أي علاقة تربط بين الأسماء في مجالات الأسماء المختلفة، فعلى سبيل المثال، يمكن لوحدتين مختلفتين أن تعرّفا دالة باسم maximize دون حدوث أيّ تضارب، ولكن يجب إضافة اسم الوحدة قبل اسم الدالة عند استخدامها في الشيفرة البرمجية.

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

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

تكون الشيفرات البرمجية المنفّذة بواسطة الاستدعاء ذي المستوى العالي (top-level invocation) في مفسّر بايثون، سواء أكان مصدر هذه الشيفرات ملف مستقلًّا أو ضمن المفسّر نفسه، جزءًا من وحدة تدعى __main__؛ لذا تمتلك هذه الشيفرات مجال أسماء عام، وكذلك الأمر بالنسبة للأسماء الداخلية، إذ أنّها جزء من وحدة خاصة تحمل الاسم builtins.

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

تمتلك الاستدعاءات التعاودية (recursive invocations) مجال أسماء خاص بكل واحد منها.

النطاق scope

النطاق هو جزء من شيفرة بايثون يمكن فيه الوصول إلى مجال الأسماء بصورة مباشرة، والمقصود بذلك هو أنّ الإشارة غير المؤهّلة لاسم معيّن تدفع اللغة إلى البحث عن ذلك الاسم في مجال الأسماء.

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

  • النطاق الداخلي، والذي يجري فيه البحث أوّلًا، ويضمّ الأسماء المحلية
  • النطاقات الخاصّة بأي دالة محيطة، والتي يجري البحث فيها بدءًا من أقرب نطاق محيط، تضمّ أسماء غير محلّية ولكن غير عامّة أيضًا
  • يضمّ النطاق ما قبل الأخير الأسماء العامّة للوحدة الحالية.
  • النطاق الخارجي (يجري البحث فيه آخرًا) وهو مجال الأسماء الذي يضمّ الأسماء الداخلية

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

يمكن استخدام العبارة nonlocal لإعادة ربط المتغيّرات الموجودة خارج النطاق الداخلي، وفي حال عدم استخدام هذه العبارة فإنّ المتغيرات ستكون للقراءة فقط (read-only). وتؤدّي أي محاولة للكتابة على هذه المتغيرات إلى إنشاء متغيّر محلي جديد في النطاق الداخلي دون المساس بالمتغيّر الخارجي الذي يحمل الاسم نفسه.

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

إنّه لمن الضروري إدراك أنّ تحديد النطاقات يكون نصّيًا، بمعنى أنّ النطاق العام الخاصّ بدالة معيّنة ومعرّفة في وحدة معينة هو مجال الأسماء الخاص بتلك الوحدة بغض النظر عن مكان استدعاء الدالة أو الاختصار المستخدم لذلك. 

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

تختلف بايثون عن غيرها من اللغات في أنّه في حال غياب عبارة عامة فإنّ عمليات الإسناد إلى الأسماء تذهب إلى النطاق الداخلي، ويجب الانتباه إلى أن عمليات الإسناد لا تنسخ البيانات، وإنّما تربط الأسماء بالكائنات، وهذا الأمر ينطبق أيضًا على عملية الحذف، فعبارة del x تقطع الرابط بين x وبين مجال الأسماء المشار إليه عن طريق النطاق المحلي. 

في الواقع تستخدم جميع العمليات التي تقدّم أسماء جديدة النطاقَ المحلي، وعلى وجه الخصوص تعمل عبارات import وتعاريف الدوال على ربط الوحدات أو اسم الدالة في النطاق المحلي.

يمكن استخدام عبارة global للإشارة إلى أن المتغيّر موجود في النطاق العام وأن إعادة الربط يجب أن تتم في ذاك النطاق. كذلك يمكن استخدام العبارة nonlocal للإشارة إلى أن المتغير موجود في نطاق محيط وأنّ إعادة تعريفه يجب أن تكون في ذاك النطاق.

أمثلة على النطاقات ومجالات الأسماء

يوضح المثال التالي كيفية الإشارة إلى نطاقات ومجالات أسماء مختلفة، وكيف تؤثر عبارتا global و nonlocal على عملية ربط المتغيرات:

def scope_test():
    def do_local():
        spam = "local spam"
    def do_nonlocal():
        nonlocal spam
        spam = "nonlocal spam"
    def do_global():
        global spam
        spam = "global spam"
    spam = "test spam"
    do_local()
    print("After local assignment:", spam)
    do_nonlocal()
    print("After nonlocal assignment:", spam)
    do_global()
    print("After global assignment:", spam)
scope_test()
print("In global scope:", spam)

تعطي الشيفرة السابقة المخرجات التالية:

After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam

لاحظ كيف أنّ الإسناد المحلي (وهو الافتراضي) لم يغيّر ربط الدالة scope_test()‎ بالمتغيّر spam. أما عملية الإسناد المسبوقة بعبارة nonlocal فقد غيّرت ربط الدالة بالمتغير spam، أما عبارة global فقد غيّرت الربط على مستوى الوحدة ككل.

لاحظ كذلك أنّه لم يكن هناك أي ربط مسبق مع المتغير spam قبل عملية الإسناد العامّة.

مصادر

  • صفحة Classes في توثيق بايثون الرسمي.