في
الأجزاء السابقة ، نظرنا في الشرائح ، ومجموعات التفريغ / التعبئة ، وبعض ميزات العمليات والأنواع المنطقية.
ذكرت
التعليقات إمكانية مضاعفة المجموعات في العدد:
a = [0] * 3 s = 'a' * 2 print(a, s)
يعرف مطور بايثون أكثر أو أقل خبرة أنه يفتقر إلى آلية
النسخ عند الكتابة a = [0] b = a b[0] = 1 print(a, b)
فماذا سيخرج الكود التالي؟
b = a * 2 b[0] = 2 print(a, b)
يعمل Python في هذه الحالة على مبدأ المفاجأة الأقل: في المتغير a لدينا وحدة واحدة ، أي أنه يمكن إعلان b وكيف
b = [1] * 2
سيكون السلوك في هذه الحالة هو نفسه:
b = a * 2 b[0] = 2 print(a, b)
ولكن ليس بهذه البساطة ، يقوم بايثون بنسخ محتويات القائمة عدة مرات كما تقوم بضربها وإنشاء قائمة جديدة. وفي القوائم يتم تخزين الارتباطات إلى القيم ، وإذا كان نسخ كل شيء على ما يرام بهذه الطريقة ، كل شيء على ما يرام ، ولكن مع تغيير تأثير عدم النسخ عند الكتابة يظهر.
row = [0] * 2 matrix = [row] * 2 print(matrix)
قائمة المولدات ورقم لمساعدتك في هذه الحالة.
يمكن إضافة القوائم وحتى زيادتها ، ويمكن أن يكون أي مكرر على اليمين:
a = [0] a += (1,) a += {2} a += "ab" a += {1: 2} print(a)
سؤال مع خدعة (للمقابلة): في الثعبان ، يتم تمرير المعلمات بالإشارة أو بالقيمة؟
def inc(a): a += 1 return a a = 5 print(inc(a)) print(a)
كما نرى ، فإن التغييرات في القيمة في نص الوظيفة لم تغير قيمة الكائن خارجها ، ومن هذا يمكننا أن نستنتج أن المعلمات تم تمريرها بالقيمة وكتابة التعليمات البرمجية التالية:
def appended(a): a += [1] return a a = [5] print(appended(a))
تحتوي لغات مثل 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)
ولكن ماذا لو قررنا تغيير المتغير خارج الدالة؟ في هذه الحالة ، سيساعدنا المُعدِّل العالمي في:
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())
في هذا المثال ، أنشأنا متغيرًا يمكن تغييره (ويتم الحصول على قيمته) فقط من خلال الطرق ، يمكنك استخدام آلية مماثلة في الفئات:
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)
وأخيرًا ، مثال على كيفية تعامل الثعبان بحرية مع المتغيرات والنطاقات:
e = 42 try: 1 / 0 except Exception as e: pass print(e)
بالمناسبة ، ربما يكون هذا هو المثال الوحيد حيث يكون الثعبان الثاني أفضل من الثالث ، لأنه ينتج:
... print(e)