مثيرة للاهتمام وفائدة الثعبان. الجزء 3

في الأجزاء السابقة ، نظرنا في الشرائح ، ومجموعات التفريغ / التعبئة ، وبعض ميزات العمليات والأنواع المنطقية.

ذكرت التعليقات إمكانية مضاعفة المجموعات في العدد:

a = [0] * 3 s = 'a' * 2 print(a, s) # -> [0, 0, 0], 'aa' 

يعرف مطور بايثون أكثر أو أقل خبرة أنه يفتقر إلى آلية النسخ عند الكتابة

 a = [0] b = a b[0] = 1 print(a, b) # -> [1], [1] 

فماذا سيخرج الكود التالي؟

 b = a * 2 b[0] = 2 print(a, b) 

يعمل Python في هذه الحالة على مبدأ المفاجأة الأقل: في المتغير a لدينا وحدة واحدة ، أي أنه يمكن إعلان b وكيف

 b = [1] * 2 

سيكون السلوك في هذه الحالة هو نفسه:

 b = a * 2 b[0] = 2 print(a, b) # -> [1], [2, 1] 

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

 row = [0] * 2 matrix = [row] * 2 print(matrix) # -> [[0, 0], [0, 0]] matrix[0][0] = 1 print(matrix) # -> [[1, 0], [1, 0]] 

قائمة المولدات ورقم لمساعدتك في هذه الحالة.

يمكن إضافة القوائم وحتى زيادتها ، ويمكن أن يكون أي مكرر على اليمين:

 a = [0] a += (1,) a += {2} a += "ab" a += {1: 2} print(a) # -> [0, 1, 2, 'a', 'b', 1] ,     #       

سؤال مع خدعة (للمقابلة): في الثعبان ، يتم تمرير المعلمات بالإشارة أو بالقيمة؟

 def inc(a): a += 1 return a a = 5 print(inc(a)) print(a) # -> 5 

كما نرى ، فإن التغييرات في القيمة في نص الوظيفة لم تغير قيمة الكائن خارجها ، ومن هذا يمكننا أن نستنتج أن المعلمات تم تمريرها بالقيمة وكتابة التعليمات البرمجية التالية:

 def appended(a): a += [1] return a a = [5] print(appended(a)) # -> [5, 1] print(a) # -> [5, 1] 


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

في python ، تخلوا عن آلية مماثلة ، والاستبدال هو آلية التخصيص لاسم المتغير بالكائن ، على سبيل المثال ، عند إنشاء متغير:
 var = "john" 


يقوم المترجم بإنشاء الكائن "john" و "name" var ، ثم يربط الكائن بالاسم المحدد.
عند استدعاء دالة ، لا يتم إنشاء كائنات جديدة ؛ بدلاً من ذلك ، يتم إنشاء اسم في نطاقها مرتبط بكائن موجود.
لكن الثعبان له أنواع متغيرة وغير قابلة للتغيير. الثاني ، على سبيل المثال ، يتضمن أرقامًا: أثناء العمليات الحسابية ، لا تتغير الكائنات الموجودة ، ولكن يتم إنشاء كائن جديد ، ثم يتم ربط الاسم الموجود به. إذا لم يتم ، بعد ذلك ، ربط اسم واحد بالكائن القديم ، فسيتم حذفه باستخدام آلية حساب الارتباط.
إذا كان الاسم مرتبطًا بمتغير من نوع متغير ، فعندئذٍ تتغير ذاكرة الكائن أثناء العمليات ؛ وبالتالي ، فإن جميع الأسماء المرتبطة بمنطقة الذاكرة هذه "ترى" التغييرات.

يمكنك أن تقرأ عنها في الوثائق الموصوفة بمزيد من التفصيل هنا .

مثال آخر:

 a = [1, 2, 3, 4, 5] def rev(l): l.reverse() return l l = a print(a, l) # -> [1, 2, 3, 4, 5], [1, 2, 3, 4, 5] l = rev(l) print(a, l) # -> [5, 4, 3, 2, 1], [5, 4, 3, 2, 1] 

ولكن ماذا لو قررنا تغيير المتغير خارج الدالة؟ في هذه الحالة ، سيساعدنا المُعدِّل العالمي في:

 def change(): global a a += 1 a = 5 change() print(a) 

ملحوظة: لا تفعل ذلك (لا ، بجدية ، لا تستخدم المتغيرات العالمية في برامجك ، وخاصة في برامجك). من الأفضل إرجاع بضع قيم من الوظيفة:
 def func(a, b): return a + 1, b + 1 


ومع ذلك ، في python ، هناك نطاق آخر وكلمة رئيسية مقابلة:

 def private(value=None): def getter(): return value def setter(v): nonlocal value value = v return getter, setter vget, vset = private(42) print(vget()) # -> 42 vset(0) print(vget()) # -> 0 

في هذا المثال ، أنشأنا متغيرًا يمكن تغييره (ويتم الحصول على قيمته) فقط من خلال الطرق ، يمكنك استخدام آلية مماثلة في الفئات:

 def private(value=None): def getter(): return value def setter(v): nonlocal value value = v return getter, setter class Person: def __init__(self, name): self.getid, self.setid = private(name) adam = Person("adam") print(adam.getid()) print(adam.setid("john")) print(adam.getid()) print(dir(adam)) 

ولكن ربما من الأفضل أن نقتصر على خصائص أو تعريف __getattr__ ، __setattr__ .

يمكنك حتى تحديد __delattr__ .

ميزة أخرى لبايثون هي وجود طريقتين للحصول على السمة: __getattr__ و __getattribute__ .

ما الفرق بينهما؟ يتم استدعاء الأول فقط إذا لم يتم العثور على السمة في الفئة ، والثاني غير مشروط. إذا تم الإعلان عن كليهما في الفصل ، فسيتم استدعاء __getattr__ فقط إذا تم استدعاؤه بشكل صريح في __getattribute__ أو إذا أثار __getattribute__ AttributeError.

 class Person(): def __getattr__(self, item): print("__getattr__") if item == "name": return "john" raise AttributeError def __getattribute__(self, item): print("__getattribute__") raise AttributeError person = Person() print(person.name) # -> __getattribute__ # -> __getattr__ # -> john 

وأخيرًا ، مثال على كيفية تعامل الثعبان بحرية مع المتغيرات والنطاقات:

  e = 42 try: 1 / 0 except Exception as e: pass print(e) # -> NameError: name 'e' is not defined 

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

  ... print(e) # -> float division by zero 

Source: https://habr.com/ru/post/ar422951/


All Articles