بعد أن تعلمنا الدوال Functions في بايثون وشرحنا استخدام return داخل الدوال، نحتاج إلى فهم سؤال مهم: أين يمكن استخدام المتغير بعد إنشائه؟ هنا يأتي مفهوم Scope في بايثون.
الـScope هو نطاق الاسم داخل البرنامج. فقد يكون المتغير محليًا Local داخل دالة، أو عامًا Global على مستوى الملف، أو موجودًا في دالة خارجية محيطة. فهم هذه الفكرة يمنع أخطاء شائعة مثل NameError وUnboundLocalError.
في هذا الدرس من سلسلة أساسيات بايثون سنتعلم الفرق بين Local وGlobal Variables، قاعدة LEGB المبسطة، ومتى نستخدم global وnonlocal، مع أمثلة مناسبة للمبتدئين توضح القراءة والإسناد داخل الدوال وكيف تنتقل القيم بين النطاقات بطريقة صحيحة.
{getToc} $title={محتوى المقال}
{alertInfo} الفكرة الأساسية: مكان تعريف الاسم يحدد المكان الذي تستطيع بايثون الوصول إليه منه. ومع الدوال، الإسناد إلى اسم داخل الدالة يجعله محليًا غالبًا ما لم تستخدم global أو nonlocal.
ما معنى Scope في بايثون؟
كلمة Scope تعني النطاق الذي يكون فيه الاسم متاحًا للاستخدام. عندما تكتب متغيرًا، لا تبحث بايثون عنه في كل مكان عشوائيًا، بل تتبع قواعد محددة لتحديد أي قيمة تقصد.
لنبدأ بمثال بسيط:
def show_message():
message = "Hello"
print(message)
show_message()
المتغير message أُنشئ داخل الدالة، لذلك هو اسم محلي لهذه الدالة. يمكن استخدامه داخلها، لكن ليس من خارجها مباشرة.
قاعدة LEGB: أين تبحث بايثون عن الاسم؟
للدوال العادية، توجد قاعدة تعليمية مشهورة تسمى LEGB. وهي تصف ترتيب البحث عن الاسم تقريبًا من الأقرب إلى الأبعد:
| الحرف | النطاق | المعنى |
|---|---|---|
L |
Local | الأسماء المحلية داخل الدالة الحالية. |
E |
Enclosing | نطاق الدوال الخارجية المحيطة عند وجود دوال متداخلة. |
G |
Global | الأسماء المعرفة على مستوى الملف أو الموديول. |
B |
Built-in | الأسماء الجاهزة في بايثون مثل len وprint. |
مثلًا إذا كتبت print(name) داخل دالة، تبحث بايثون أولًا عن name محليًا، ثم في الدوال المحيطة، ثم في النطاق العام، ثم في الأسماء الجاهزة إذا كان الاسم من نوع Built-in.
{alertInfo} LEGB قاعدة مبسطة ممتازة لفهم Scope في الدوال. توجد تفاصيل أكثر تقدمًا في بعض السياقات مثل Classes وComprehensions، لكنها ليست ضرورية في هذه المرحلة.
ما هو Local Variable؟
المتغير المحلي Local Variable هو اسم يتم ربطه بقيمة داخل دالة. يكون متاحًا داخل هذه الدالة أثناء تنفيذها.
def greet():
name = "Ahmed"
print(name)
greet()
الناتج:
Ahmed
هنا name محلي داخل greet(). وجود متغير محلي يساعد الدالة على العمل ببياناتها دون تلويث بقية البرنامج بأسماء غير ضرورية.
ماذا يحدث إذا استخدمت Local Variable خارج الدالة؟
إذا حاولت الوصول إلى المتغير المحلي من خارج الدالة، فلن تجده بايثون في النطاق الخارجي:
def greet():
name = "Ahmed"
greet()
print(name)
سيظهر خطأ قريب من:
NameError: name 'name' is not defined
إذا واجهت هذا الخطأ، راجع أيضًا: حل خطأ NameError: name is not defined في بايثون.
كيف نستخدم قيمة محلية خارج الدالة؟
الحل الطبيعي غالبًا هو أن تعيد الدالة القيمة باستخدام return بدل محاولة الوصول إلى المتغير المحلي نفسه:
def get_name():
name = "Ahmed"
return name
student_name = get_name()
print(student_name)
الدالة أعادت القيمة، ثم خزنّاها في متغير جديد في النطاق الخارجي. هذه الطريقة أوضح من محاولة جعل كل شيء Global.
ما هو Global Variable؟
المتغير العام Global Variable في هذا السياق هو اسم يتم تعريفه على مستوى الملف، خارج الدوال. تستطيع الدوال قراءة هذا الاسم إذا لم تنشئ اسمًا محليًا يحجبه.
site_name = "بايثون العرب"
def show_site():
print(site_name)
show_site()
الناتج:
بايثون العرب
لاحظ أننا لم نكتب global site_name؛ القراءة فقط لا تحتاج إلى ذلك في هذا المثال.
ماذا يحدث إذا استخدمنا الاسم نفسه داخل الدالة؟
إذا أسندت قيمة إلى اسم داخل الدالة، فإن بايثون تتعامل معه محليًا افتراضيًا:
count = 0
def change_count():
count = 1
print(count)
change_count()
print(count)
الناتج:
1
0
القيمة 1 تخص المتغير المحلي داخل الدالة، بينما المتغير العام خارج الدالة بقي بقيمة 0. هذه الحالة تسمى أحيانًا Shadowing لأن الاسم المحلي يحجب الاسم الخارجي داخل نطاقه.
لماذا يظهر UnboundLocalError أحيانًا؟
من أكثر أخطاء Scope إرباكًا أن يكون المتغير موجودًا خارج الدالة، ومع ذلك يظهر UnboundLocalError. انظر:
count = 10
def increase():
print(count)
count = 20
increase()
وجود إسناد إلى count داخل الدالة يجعل الاسم محليًا في هذه الدالة. لذلك عندما تصل بايثون إلى print(count) تحاول قراءة المتغير المحلي قبل أن يحصل على قيمة.
لشرح هذا الخطأ بالتفصيل، راجع: حل خطأ UnboundLocalError local variable referenced before assignment في بايثون.
استخدام global لتعديل متغير عام
إذا كنت تريد فعلًا إعادة ربط اسم موجود في النطاق العام من داخل دالة، استخدم global:
count = 0
def increase():
global count
count += 1
increase()
print(count)
الناتج:
1
هنا تعني global count: لا تنشئ اسمًا محليًا جديدًا عند الإسناد؛ استخدم الاسم الموجود في نطاق الموديول.
{alertWarning} لا تستخدم global كحل تلقائي لكل مشكلة Scope. كلما أمكن، مرّر القيمة إلى الدالة وأعد النتيجة باستخدام return؛ فهذا يقلل الحالة المشتركة ويجعل الكود أسهل في الاختبار والتتبع.
الأفضل غالبًا: Parameters وreturn بدل global
def increase(number):
return number + 1
count = 0
count = increase(count)
print(count)
الناتج ما زال 1، لكن الدالة الآن تعتمد على المدخل الذي تستلمه وتعيد نتيجة واضحة، بدل تعديل حالة عامة مخفية.
ما هو Enclosing Scope؟
عند وجود دالة داخل دالة، يمكن للدالة الداخلية قراءة أسماء موجودة في الدالة الخارجية المحيطة بها. هذا هو نطاق Enclosing في قاعدة LEGB.
def outer():
message = "Hello from outer"
def inner():
print(message)
inner()
outer()
الدالة inner() لم تجد message محليًا، فوجدته في الدالة الخارجية outer().
استخدام nonlocal مع الدوال المتداخلة
إذا أردت من الدالة الداخلية تعديل اسم موجود في أقرب دالة خارجية محيطة، نستخدم nonlocal:
def create_counter():
count = 0
def increase():
nonlocal count
count += 1
return count
return increase
counter = create_counter()
print(counter())
print(counter())
الناتج:
1
2
nonlocal لا تشير إلى Global Variable؛ بل إلى اسم موجود في نطاق دالة خارجية محيطة. وهي نقطة متقدمة قليلًا، لكن معرفة الفرق بينها وبين global تمنع خلطًا شائعًا.
الفرق بين global وnonlocal
| الكلمة | تشير إلى | متى تستخدم؟ |
|---|---|---|
global |
اسم على مستوى الموديول أو الملف | عندما تريد إعادة إسناد قيمة إلى متغير عام من داخل دالة. |
nonlocal |
اسم في دالة خارجية محيطة | عندما تريد تعديل متغير في Enclosing Scope من دالة متداخلة. |
هل if وfor تنشئ Scope جديدًا؟
بالنسبة للاستخدامات العادية في بايثون، كتل if وfor لا تنشئ نطاق دالة جديدًا. إذا أنشأت متغيرًا داخلها على مستوى الملف أو داخل الدالة نفسها، فإنه يبقى ضمن ذلك النطاق المحيط.
if True:
message = "Hello"
print(message)
الناتج:
Hello
لذلك لا تتعامل مع المسافة البادئة وحدها على أنها Scope جديد. الدوال هي العنصر الأهم هنا للمبتدئ.
الفرق بين Local وGlobal Variables
| المقارنة | Local Variable | Global Variable |
|---|---|---|
| مكان التعريف المعتاد | داخل الدالة | على مستوى الملف خارج الدوال |
| إمكانية القراءة | داخل نطاقه | يمكن للدالة قراءته إذا لم يحجبه اسم محلي |
| التعديل من داخل الدالة | طبيعي داخل الدالة | إعادة الإسناد تحتاج global |
| الأفضل في تصميم الدوال | استخدمه قدر الإمكان | قلل الاعتماد عليه عندما يمكن تمرير القيم وإرجاع النتائج |
أخطاء Scope الشائعة عند المبتدئين
1. استخدام Local Variable خارج الدالة
إذا عرّفت الاسم داخل الدالة ثم استخدمته خارجها، قد تحصل على NameError. أعد القيمة باستخدام return إذا كنت تحتاجها خارج الدالة.
2. استخدام += مع متغير خارجي دون تهيئة محلية
visits = 0
def add_visit():
visits += 1
هذا يؤدي إلى UnboundLocalError لأن += يتضمن إسنادًا، فتتعامل بايثون مع visits كاسم محلي ما لم تستخدم global أو تعيد تصميم الدالة.
3. الإفراط في استخدام global
قد يعمل البرنامج، لكن يصبح أصعب في التتبع لأن عدة دوال تستطيع تغيير نفس الحالة. اجعل المدخلات والمخرجات واضحة قدر الإمكان.
4. تسمية متغير باسم Built-in
len = 10
بهذا أنت تحجب الاسم الجاهز len() داخل النطاق الحالي. من الأفضل تجنب أسماء مثل list وstr وsum وlen للمتغيرات.
مثال عملي يجمع Local وGlobal
language = "Python"
def show_info(student_name):
level = "Beginner"
print(student_name)
print(language)
print(level)
show_info("Ali")
في هذا المثال:
student_nameParameter محلي للدالة.levelمتغير محلي.languageمتغير عام تتم قراءته من داخل الدالة.
تمرين على Scope
توقع الناتج قبل تشغيل الكود:
name = "Ali"
def show_name():
name = "Omar"
print(name)
show_name()
print(name)
الناتج:
Omar
Ali
الاسم داخل الدالة محلي ولا يغيّر الاسم العام خارجها.
أسئلة شائعة عن Scope في بايثون
ما معنى Scope في بايثون؟
هو النطاق الذي يستطيع البرنامج الوصول إلى اسم متغير أو دالة منه.
ما الفرق بين Local وGlobal Variable؟
Local يُنشأ عادة داخل الدالة ويخص نطاقها، بينما Global يُعرّف على مستوى الملف ويمكن قراءته من الدوال ما لم يحجبه اسم محلي.
هل أحتاج global لقراءة متغير عام؟
لا. تحتاج global عندما تريد إعادة إسناد قيمة إلى الاسم العام من داخل الدالة، وليس لمجرد قراءته.
ما الفرق بين global وnonlocal؟
global تتعامل مع اسم في مستوى الموديول، بينما nonlocal تتعامل مع اسم في أقرب دالة خارجية محيطة.
لماذا يظهر UnboundLocalError رغم أن المتغير موجود خارج الدالة؟
لأن وجود إسناد إلى الاسم داخل الدالة يجعل بايثون تعتبره محليًا في ذلك النطاق. إذا تمت قراءته قبل حصوله على قيمة محلية يظهر الخطأ.
روابط مهمة من بايثون العرب
- المتغيرات في بايثون للمبتدئين
- الدوال Functions في بايثون
- شرح return داخل الدوال
- حل خطأ NameError: name is not defined
- حل خطأ UnboundLocalError في بايثون
- جميع دروس كورس أساسيات بايثون
مصدر رسمي للتوسع
للمزيد عن Scopes وNamespaces وglobal وnonlocal يمكنك الرجوع إلى:
Python Scopes and Namespaces Documentation.
خلاصة الدرس
تعلمنا أن Scope يحدد المكان الذي يمكن الوصول إلى الاسم منه، وأن Local Variable يكون ضمن نطاق الدالة، بينما يمكن قراءة Global Variable داخل الدالة إذا لم يحجبه اسم محلي.
كما تعلمنا قاعدة LEGB المبسطة، ولماذا قد يظهر UnboundLocalError، ومتى تستخدم global أو nonlocal. والأهم: في معظم الدوال، تمرير القيم عبر Parameters وإرجاع النتائج باستخدام return يجعل الكود أوضح من الاعتماد الزائد على المتغيرات العامة.
{alertSuccess} القاعدة العملية: قبل أن تستخدم أو تعدّل أي متغير داخل دالة، اسأل نفسك: هل هذا الاسم Local أم Enclosing أم Global؟ هذا السؤال وحده يحل معظم مشاكل Scope.