Scope في بايثون: شرح Local وGlobal Variables | أساسيات بايثون 15

شرح Scope في بايثون والفرق بين Local وGlobal Variables للمبتدئين

بعد أن تعلمنا الدوال Functions في بايثون وشرحنا استخدام return داخل الدوال، نحتاج إلى فهم سؤال مهم: أين يمكن استخدام المتغير بعد إنشائه؟ هنا يأتي مفهوم Scope في بايثون.

الـScope هو نطاق الاسم داخل البرنامج. فقد يكون المتغير محليًا Local داخل دالة، أو عامًا Global على مستوى الملف، أو موجودًا في دالة خارجية محيطة. فهم هذه الفكرة يمنع أخطاء شائعة مثل NameError وUnboundLocalError.

في هذا الدرس من سلسلة أساسيات بايثون سنتعلم الفرق بين Local وGlobal Variables، قاعدة LEGB المبسطة، ومتى نستخدم global وnonlocal، مع أمثلة مناسبة للمبتدئين توضح القراءة والإسناد داخل الدوال وكيف تنتقل القيم بين النطاقات بطريقة صحيحة.

{getToc} $title={محتوى المقال}

{alertInfo} الفكرة الأساسية: مكان تعريف الاسم يحدد المكان الذي تستطيع بايثون الوصول إليه منه. ومع الدوال، الإسناد إلى اسم داخل الدالة يجعله محليًا غالبًا ما لم تستخدم global أو nonlocal.
مثال Local Variable في بايثون داخل الدالة وعدم إمكانية الوصول إليه خارجها

ما معنى 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.

الفرق بين المتغير المحلي Local والمتغير العام 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 Variable داخل دالة بايثون واستخدام global عند تعديل قيمته

استخدام 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
الأفضل في تصميم الدوال استخدمه قدر الإمكان قلل الاعتماد عليه عندما يمكن تمرير القيم وإرجاع النتائج
ملخص الفرق بين Local وGlobal Variables وأخطاء Scope الشائعة في بايثون

أخطاء 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_name Parameter محلي للدالة.
  • 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 رغم أن المتغير موجود خارج الدالة؟

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

روابط مهمة من بايثون العرب

مصدر رسمي للتوسع

للمزيد عن 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.

إرسال تعليق

أحدث أقدم