ملتقى الواحــة
مجالات التسمية (namespaces) في بيثون - نسخة قابلة للطباعة

+- ملتقى الواحــة (https://forums.wahaproject.org)
+-- قسم : واحــة لينكس (https://forums.wahaproject.org/forum-3.html)
+--- قسم : البرمجة (https://forums.wahaproject.org/forum-9.html)
+--- الموضوع : مجالات التسمية (namespaces) في بيثون (/thread-112.html)



مجالات التسمية (namespaces) في بيثون - محمد - 08-06-2018

مجالات التسمية (namespaces) في بيثون

مجال التسمية    =   namespace
نطاق المتغير    =   scope
محلي            =   local
شامل            =   global
وسيط - معطى     =   argument - parameter
فئة             =   class  
دالة            =   function
كتلة            =   block
تعليمة          =   instruction
تعبير           =   expression
سمة             =   attribute
معلمة           =   parameter
مرجع            =   reference
الدوال المتداخلة=   nested functions
لعل مفهوم 'نطاق المتغير' قد اعترضك سابقا، او ربما لاحظت أن متغيرين يحملان نفس الاسم متواجدان في سكربت واحد، أحدهما داخل الوحدة و الآخر داخل دالة مثلا.
نجد في بيثون كما في غالب لغات البرمجة، إن لم يكن كلها، قواعد تحدد نطاق المتغير. المقصود متى وكيف يمكن الوصول الى المتغير؟ ما هي المتغيرات الممكن استخدامها داخل دالة قمنا بتعريفها؟ هل ما قمنا بتمريره اليها كوسيط؟ هل يمكننا اضافة متغيرات داخل الدالة مع امكانية الوصول اليها من خارجها؟
كود :
a = 3
def my_var():
   print("a = {}".format(a))

my_var()                        # a = 3
print(a)                        # 3
- الاستنتاج الاول: يمكننا الوصول الى متغير قمنا بتعريفه خارج الدالة رغم أننا لم نمرره كوسيط. لنحاول تعديله من داخلها:
كود :
a = 3
def my_var():
   a = 5
   print("a = {}".format(a))

my_var()                        # a = 5
print(a)                        # 3
- هنا نستنتج أمرين: الاول هو أننا لا نستطيع تنفيذ تعديل مباشر (أي عبر التعيين بواسطة =) لقيمة متغير وقع تعريفه خارج نطاق الدالة. الاستنتاج الثاني هو أن محاولة تعديل المتغير a داخل الدالة أنتج في الواقع متغيرا آخر بنفس الاسم وما من طريقة مباشرة للوصول اليه من خارج الدالة. سنرى أن تعديل متغيرات معرفة خارج النطاق المحلي ممكن في بعض الاحيان لكن ليس مباشرة (عبر list.append مثلا)

## المتغيرات المحلية (local variables)
يقوم بيثون بالبحث عن مرجع المتغير a في النطاق المحلي للدالة أولا. هذا النطاق يحتوي على الوسائط التي ممرناها للدالة إن وجدت، والمتغيرات التي قمنا بتعريفها داخل الدالة. في المثال الاول أعلاه، اكتشف بيثون أنه لا وجود لمتغير باسم a في النطاق المحلي للدالة، عندها مر للبحث عن a داخل نطاق محلي أشمل حيث تم تعريف الدالة، وهنا وجد a واستخدمها.
في المثال الثاني، وجد بيثون المتغير a في النطاق المحلي للدالة فاستخدمها (a = 5) ، ثم لما طلبنا طباعة a مباشرة، بدأ البحث عنها في النطاق المحلي دائما، لكنه هنا النطاق الذي فيه تعريف الدالة فوجد أن a = 3 .
سنقدم  مزيدا من الامثلة لفهم نطاق المتغيرات أكثر:
كود :
def my_var():
   x = 10
   print("x = {}".format(x))
my_var()                        # x = 10
print(x)                        # Exception: NameError
يظهر لنا هذا المثال بشكل جلي أن المتغير x لم يعد بالامكان الوصول اليه بالخروج من الدالة التي تم تعريفه فيها.
كود :
def my_var(i):
   try:
       print("before ...")
       print("y = {}".format(y))
   except NameError:
       print("'y' is not yet defined.")
   y = i
   print("After...")
   print("y = {}".format(y))

my_var(20)
print(y)                        # Exception: NameError
هذا المثال يبين أن بيثون يقرأ السكربت تعليمة بعد الاخرى: هو في اول الدالة لا يعلم بوجود متغير باسم y رغم أننا مررنا له قيمة كوسيط، لذلك يطلق استثناء. بعد الخروج من الدالة حاولنا طباعة قيمة y لكن بيثون لا يجد المتغير، لأنه معرف في نطاق الدالة وبخروجنا منها تم استرجاع ذلك النطاق من الذاكرة ولم يعد له وجود (لم يعد بالامكان الوصول اليه).
## تعميم
تكون المتغيرات معزولة تماما داخل كتلة تعليمات أو داخل وحدة عبر ما يسمى 'مجال التسمية' (namespace). في بيثون مجال التسمية يجمع عددا من المتغيرات تنتمي الى كائن ما: الوحدة والدالة والفئة ومثيل الفئة كل منها تقوم بتعريف مجال تسمية خاص بها. هذا المفهوم موجود في لغات أخرى لكن بطريقة مختلفة وعادة تتطلب مجهودا اضافيا من المبرمج لعزل المتغيرات داخل مجالات تسمية. في بيثون مجرد انشائنا لوحدة جديدة يوفر مجال تسمية خاص بها وبالتالي تكون المتغيرات معزولة بمجرد كتابتنا لأول سطر كود. لنر كيف تقوم الوحدات بعزل المتغيرات عبر مجالات التسمية. سنقوم بانشاء وحدة باسم spam.py ونعرف داخلها متغيرا ودالة:
كود :
x = 1                   # 2
def f():                # 2 - 3
   print(x)            # 2
ووحدة ثانية باسم eggs.py نستورد داخلها الوحدة spam ونعرف متغيرا ودالة كما يلي:
كود :
import spam             # 2
x = 2                   # 4
def f():                # 5 - 3
   print(x)            # 4
f()                     # 5
spam.f()                # 6
print(spam.x)           # 7
بعد حفظ الوحدتين ننتقل الى المجلد ونفتح طرفية ونكتب:
كود :
python3 eggs.py         # 1
سنحلل ما حدث في ذاكرة الحاسب: خذ قلما وورقة وارسم مخططا للنقاط التالية حتى يتيسر عليك فهمها.
قام النظام بتخصيص مساحة من الذاكرة للكائنات ومساحة اخرى لمجالات التسمية. ما يهمنا هو المساحة المخصصة لمجالات التسمية:
# 1- داخل مساحة الذاكرة المخصصة لمجالات التسمية تم تخصيص مجال تسمية للوحدة eggs
# 2- عند قراءة سطر import spam خصص مجال تسمية خاص بالوحدة spam وضع فيه تعريف x و f (لكن دون تخصيص مجال تسمية للدالة f)
# 3- بالنسبة للدوال سيتم انشاء مجال تسمية خاص بكل منها عند استدعائها ثم يتم استرجاعه عند الخروج من الدالة.
# 4- عند قراءة السطر x = 2 قام النظام بعريف المتغير x داخل المجال المخصص لـeggs وربطه بالقيمة 2 في مساحة الذاكرة المخصصة للكائنات
# 5- عند استدعاء الدالة f قام النظام بتخصيص مجال تسمية لها. داخل الدالة بحث النظام عن x في المجال المحلي للدالة لكنه لم يجده، فبحث في المجال المحلي للوحدة eggs فوجده وطبع لنا القيمة، ثم، عند الخروج من الدالة، استرجع النظام مجال التسمية الذي كان قد خصصه للدالة.
# 6- عند السطر spam.f قام النظام بتخصيص مجال تسمية للدالة داخل المجال المخصص لـspam واتبع نفس المراحل، حيث بحث عن x في مجال التسمية الخاص بـf ولما لم يجده بحث عنه في المجال الأشمل اي المجال المخصص للوحدة spam وهناك وجده فطبع القيمة (1)، وعند الخروج من الدالة استرجع مساحة الذاكرة التي خصصها لها.
# 7- أخيرا، عند سطر طباعة spam.x بحث النظام عن x في المجال المخصص لـspam
اذا اردنا ان نلخص كل ما حدث ونعممه يمكننا ان نقول ان بيثون يبدأ البحث دائما في أقرب نطاق محلي للتعليمة ثم يتوسع تدريجيا في البحث، لذلك لا يشكل تسمية كائنات بنفس الاسم في مجالات تسمية مختلفة مشكلة بالنسبة للنظام (لكن قد يسبب مشاكل وصعوبات بالنسبة للمبرمج).
من المهم أيضا أن نتذكر أننا لا نستطيع تعديل قيمة متغير (التعيين مباشرة بواسطة =) من خارج النطاق الذي تم تعريف المتغير فيه. أما بالنسبة للكائنات التي لها وظائف تسمح بتعديلها، فذلك ممكن عبر تلك الوظائف: ذلك أننا عندما نمرر كائنا كوسيط لدالة فنحن انما نمرر مرجع للكائن نفسه وليس لقيمته. بالنسبة لمن لديهم بعض المعرفة بلغات برمجة اخرى كالسي++ او الجافا، تلك اللغات تمرر افتراضيا قيمة المتغير وليس المرجع او المؤشر.
كود :
t = []                  # قلنا أن الدالة تستطيع أن 'تقرأ' متغيرا معرفا خارجها
def my_var():           # لكن بالنسبة للكائنات القابلة للتعديل كالقوائم مثلا
   t.extend([5, 6, 7]) # يمكننا تعديل قائمة عبر وظائفها

print(t)

## المتغيرات العمومية (global variables)
وفر بيثون طريقة لتعديل متغير داخل دالة إذا كان معرفا خارج نطاق الدالة. هذه الطريقة تسمى المتغيرات العمومية
كود :
def global_var():
   global i
   i += 100

i = 50
global_var()
print(i)
ينصح بشدة بتجنب استخدام الكلمة المفتاحية global نظرا لما قد تسببه من تأثيرات جانبية. في بيثون3 يوجد كلمة مفتاحية أخرى تعدل متغيرا معرفا في الدوال المتداخلة nonlocal.
كود :
i = 50
def outsidef():
   i = 20
   def insidef():
       nonlocal i
       i += 100
       print("inside i = ", i)
   insidef()
   print("outside i =", i)


outsidef()
print("global i =", i)
## خاتمة
قد يبدو مفهوم مجالات التسمية معقدا في البداية، وأعترف أن شرحه نظريا أصعب من تجربته عمليا، لذلك أدعو من وجد صعوبات في فهم هذا الموضوع أن يجرب أمثلة أخرى عبر البحث عن python namespace في الانترنت مثلا.