تعلم البرمجة مع Python


الدرس: اكتشف نطاق المتغيرات والمراجع


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

هذا الفصل ليس ضروريًا ، لكنني بطبيعة الحال لا أكتبه للمتعة: يمكنك الاستمرار في تعلم بايثون دون أن تعرف بالضبط كيف تلعب بايثون مع المراجع ، ولكن قد يكون من المفيد أن تعرف.

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

نطاق المتغيرات



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

في وظائفنا ، ما هي المتغيرات التي يمكن الوصول إليها؟


لا يمكنك تغيير الفريق الفائز: دعنا ننتقل إلى الأمثلة الآن.


>>> a = 5
>>> def print_a():
...     """ aالوظيفة المسؤولة عن عرض المتغير.
...     لا يتم تمرير هذا المتغير a كمعامل دالة.
...     نفترض أنه تم إنشاؤه خارج الوظيفة ، نريد أن نرى
...     إذا كان يمكن الوصول إليه من جسم الوظيفة """
...     
...     print("La variable a = {0}.".format(a))
... 
>>> print_a()
La variable a = 5.
>>> a = 8
>>> print_a()
La variable a = 8.
>>>
 
مفاجأة! أو ربما لا ...

لا يتم تمرير المتغير a كمعامل للدالة print_a . ومع ذلك ، تجده Python ، طالما تم تعريفه قبل استدعاء الوظيفة.

هذا هو المكان الذي تأتي فيه المساحات المختلفة.

مساحة محلية


في وظيفتك ، عندما تشير إلى متغير a، يتحقق Python من المساحة المحلية للوظيفة. تحتوي هذه المساحة على المعلمات التي يتم تمريرها إلى الوظيفة والمتغيرات المحددة في جسمها. تعلم بايثون أن المتغير a غير موجود في المساحة المحلية للدالة. في هذه الحالة ، سيبحث في المساحة المحلية التي تم فيها استدعاء الوظيفة. وهناك ، يجد المتغير a، وبالتالي يمكنه عرضه.

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

نطاق متغيراتنا


دعونا نرى بعض الحالات الملموسة. سأشرحها كما يجب ، لا تقلق.

ماذا يحدث للمتغيرات المحددة في جسم الوظيفة؟

دعنا نرى مثالًا جديدًا:


def set_var(nouvelle_valeur):
    """ وظيفة تسمح لنا باختبار نطاق المتغيرات
    المحددة في وظائفنا الجسم """
    
    # نحاول عرض المتغير var ، إن وجد
    try:
        print("قبل الإسناد ، المتغير لدينا هو {0}.".format(var))
    except NameError:
        print("المتغير غير موجود بعد.")
    var = nouvelle_valeur
    print("بعد التخصيص ، المتغير لدينا هو {0}.".format(var))
 
والآن دعنا نستخدم وظيفتنا:

>>> set_var(5)
المتغير غير موجود بعد.
بعد الإسناد ، المتغير لدينا هو 5.
>>> var
Traceback (most recent call last):
  File "", line 1, in 
NameError: name 'var' is not defined
>>>
 
أشعر أن بعض التفسيرات ضرورية:
  • أثناء استدعائنا لـ set_var،  لم تتمكن بايثون من العثور على المتغير var الخاص بنا : هذا أمر طبيعي ، لم نقم بتعريفه بعد ، لا في جسم وظيفتنا ولا في جسم برنامجنا. تعين Python القيمة 5 للمتغير var، وتعرضها ، وتخرج.
  • عند ترك الوظيفة ، نحاول عرض المتغير var... لكن بايثون لا يمكنها العثور عليه! في الواقع: تم تعريفه في جسم الوظيفة (وبالتالي في مساحته المحلية) وفي نهاية تنفيذ الوظيفة ، يتم تدمير المساحة ... وبالتالي المتغير var المحدد في جسم الوظيفة ، موجود فقط في هذا الجسد ثم يتم تدميره.
لدى Python قاعدة وصول محددة للمتغيرات خارج المساحة المحلية: يمكننا قراءتها ، ولكن لا يمكننا تعديلها. لهذا السبب ، في وظيفتنا print_a، تمكنا من عرض متغير لم يتم تضمينه في المساحة المحلية للدالة. من ناحية أخرى ، لا يمكن تعديل قيمة متغير خارجي للمساحة المحلية ، عن طريق التخصيص على الأقل. إذا قمت بذلك في جسم وظيفتك var = nouvelle_valeur، فلن تقوم بتعديل متغير خارج الجسم بأي شكل من الأشكال .

في الواقع ، عندما تعثر Python على عبارة تخصيص ، على سبيل المثال var = nouvelle_valeur، فإنها ستغير قيمة المتغير في المساحة المحلية للدالة. وتذكر أن هذه المساحة المحلية تتلف بعد استدعاء الوظيفة.

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

يبدو الأمر سخيفًا جدًا في البداية ... لكن لا نفاد صبر. سأضع هذا في منظور سريع جدا.

وظيفة تعديل الكائنات


آمل أن تتذكر ، كل شيء في بايثون هو كائن . عندما تمرر المعلمات إلى وظيفتك ، فهذه هي الكائنات التي يتم تمريرها. وليست قيم الأشياء ، بل الأشياء نفسها ، هذا مهم للغاية.

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


>>> def ajouter(liste, valeur_a_ajouter):
...     """ تُدرج هذه الوظيفة القيمة المراد إضافتها في نهاية القائمة """
...     liste.append(valeur_a_ajouter)
... 
>>> ma_liste=['a', 'e', 'i']
>>> ajouter(ma_liste, 'o')
>>> ma_liste
['a', 'e', 'i', 'o']
>>>
 
إنها تعمل ! نقوم بتمرير كائن النوع الخاص بنا كمعلمات list مع القيمة التي يجب إضافتها. وتستدعي الدالة طريقة append الكائن. هذه المرة ، عندما تركنا الوظيفة ، تم بالفعل تعديل كائننا.

لا ارى لماذا. هل قلت أن الوظيفة لا يمكنها تعيين قيم جديدة للمعلمات؟

إطلاقا. لكن هذه هي الدقة الصغيرة في القصة: نحن لا نغير قيمة المعلمة على الإطلاق ، نحن فقط نسمي طريقة الكائن. وهذا يغير كل شيء. إذا شعرت بالارتباك ، تذكر أنه في جسم الوظيفة ، إذا قمت بذلك parameter = new_value، فسيتم تغيير المعلمة فقط في جسم الوظيفة. بينما إذا قمت بذلك parameter.methode_to_modify(…)، فسيتم بالفعل تغيير الكائن الموجود خلف المعلمة.

يمكنك أيضًا تعديل سمات كائن ، على سبيل المثال تغيير مربع في القائمة أو في قاموس: ستكون هذه التغييرات أيضًا فعالة بعد استدعاء الوظيفة.

وما المراجع في كل هذا؟


تحدثت عن المراجع ، ووعدت بتخصيص قسم لها ؛ إنه الآن بعد أن نتحدث عنها!

سأقوم بالتخطيط طوعًا: المتغيرات التي نستخدمها منذ بداية هذه الدورة في الواقع تخفي الإشارات إلى الأشياء.

بشكل ملموس ، قدمت متغيرات مثل هذا: اسم تعريف يشير إلى قيمة. على سبيل المثال ، المتغير المسمى لدينا a له قيمة (لنقل 0).

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

هذا يعني أن متغيرين يمكن أن يشيران إلى نفس الكائن.

باه ... بالطبع ، لا شيء يمنع عمل متغيرين بنفس القيمة.

لا لا ، أنا لا أتحدث عن القيم هنا ولكن عن الأشياء. دعنا نرى مثالا ، سوف تفهم:


>>> ma_liste1 = [1, 2, 3]
>>> ma_liste2 = ma_liste1
>>> ma_liste2.append(4)
>>> print(ma_liste2)
[1, 2, 3, 4]
>>> print(ma_liste1)
[1, 2, 3, 4]
>>>
 
نقوم بإنشاء قائمة في المتغير ma_liste1 . في السطر 2 ، نسند ma_liste1 إلى المتغير ma_liste2 . قد تعتقد أن ma_liste2 هذه نسخة من ma_liste1 . ومع ذلك ، عندما نضيف 4 إلى ma_liste2 ، ma_liste1 يتغير أيضًا.

ونحن نقول ان ma_liste1 و ma_liste2 تتضمن إشارة إلى نفس الكائن: إذا كنا تعديل كائن من أحد المتغيرين، فإن التغيير سيكون مرئيا من المتغيرين.

أه ... أحاول أن أفعل الشيء نفسه مع المتغيرات التي تحتوي على أعداد صحيحة وهي لا تعمل.

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

ماذا لو كنت أرغب في تعديل إحدى القوائم دون لمس الأخرى؟

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


>>> ma_liste1 = [1, 2, 3]
>>> ma_liste2 = list(ma_liste1) #  my_list1هذا يعادل نسخ محتوى  
>>> ma_liste2.append(4)
>>> print(ma_liste2)
[1, 2, 3, 4]
>>> print(ma_liste1)
[1, 2, 3]
>>>
 
في السطر 2 ، طلبنا من Python إنشاء كائن جديد بناءً على ma_liste1 . نتيجة لذلك ، لم يعد المتغيرين يحتويان على نفس المرجع: بل يعدلان كائنات مختلفة. يمكنك استخدام معظم المنشئات (هذا هو الاسم الذي نطلقه list لإنشاء قائمة على سبيل المثال) لهذا الغرض. بالنسبة إلى القواميس ، استخدم المُنشئ dict عن طريق تمرير قاموس تم إنشاؤه بالفعل كمعامل وسيكون لديك في المقابل قاموس ، مشابه للقاموس الذي تم تمريره كمعامل ، ولكنه مشابه في المحتوى. في الواقع ، إنها نسخة من الكائن ، لا أكثر ولا أقل.

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


>>> ma_liste1 = [1, 2]
>>> ma_liste2 = [1, 2]
>>> ma_liste1 == ma_liste2 # نقارن محتوى القوائم
True
>>> ma_liste1 is ma_liste2 # نقارن مرجعهم
False
>>>
 
لا يسعني إلا أن أشجعك على تجربة أشياء مختلفة. جولة صغيرة في جانب المتغيرات العالمية؟

المتغيرات العالمية (global)



هناك طريقة لتعديل المتغيرات خارجها في دالة. يتم استخدام المتغيرات العالمية لهذا الغرض .

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

مبدأ المتغيرات العالمية


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

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

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

لكن نظرية كافية ، دعنا نرى مثالا.

استخدام المتغيرات العالمية بشكل ملموس


للإعلان لـ Python ، في جسم الوظيفة ، أن المتغير الذي سيتم استخدامه يجب اعتباره عالميًا ، نستخدم الكلمة الأساسية global . يتم وضعها عمومًا بعد تعريف الوظيفة ، أسفل علامة التبويب مباشرة docstring، وهذا يجعل من الممكن العثور بسرعة على المتغيرات العامة دون المرور بكل التعليمات البرمجية (إنها اصطلاح بسيط). أحدهما يحدد خلف هذه الكلمة الأساسية اسم المتغير الذي سيتم اعتباره عالميًا:


>>> i = 4 
>>> def inc_i():
...     """   بمقدار 1 i الوظيفة المسؤولة عن زيادة  """
...     global i #  خارج المساحة المحلية للوظيفة i  عن Python تبحث 
...     i += 1
... 
>>> i
4
>>> inc_i()
>>> i
5
>>>
 
إذا لم تخبر Python بأنه i يجب اعتبارها عالمية ، فلن تتمكن من تغيير قيمتها بالفعل ، كما رأينا أعلاه. من خلال التحديد global i، تسمح Python بوصول القراءة والكتابة إلى هذا المتغير ، مما يعني أنه يمكنك تغيير قيمته عن طريق التعيين.

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

باختصار


  • المتغيرات المحلية التي تم تحديدها قبل استدعاء الوظيفة يمكن الوصول إليها ، من جسم الوظيفة ، في وضع القراءة فقط.
  • سيتم حذف المتغير المحلي المحدد في دالة بعد تنفيذ هذه الوظيفة.
  • ومع ذلك ، يمكننا استدعاء سمات وأساليب الكائن لتعديله بشكل دائم.
  • يتم تعريف المتغيرات العامة باستخدام الكلمة الأساسية global متبوعة باسم المتغير الذي تم إنشاؤه مسبقًا.
  • يمكن تعديل المتغيرات العامة من داخل جسم الوظيفة (استخدم بحذر).