08-06-2018, 10:31 PM
مجالات التسمية (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
## المتغيرات المحلية (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
كود :
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
## تعميم
تكون المتغيرات معزولة تماما داخل كتلة تعليمات أو داخل وحدة عبر ما يسمى 'مجال التسمية' (namespace). في بيثون مجال التسمية يجمع عددا من المتغيرات تنتمي الى كائن ما: الوحدة والدالة والفئة ومثيل الفئة كل منها تقوم بتعريف مجال تسمية خاص بها. هذا المفهوم موجود في لغات أخرى لكن بطريقة مختلفة وعادة تتطلب مجهودا اضافيا من المبرمج لعزل المتغيرات داخل مجالات تسمية. في بيثون مجرد انشائنا لوحدة جديدة يوفر مجال تسمية خاص بها وبالتالي تكون المتغيرات معزولة بمجرد كتابتنا لأول سطر كود. لنر كيف تقوم الوحدات بعزل المتغيرات عبر مجالات التسمية. سنقوم بانشاء وحدة باسم spam.py ونعرف داخلها متغيرا ودالة:
كود :
x = 1 # 2
def f(): # 2 - 3
print(x) # 2
كود :
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)
كود :
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 في الانترنت مثلا.