هل يمكنك حل هذه المشاكل الثلاث (الخادعة) البسيطة في Python؟

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







جرب هذه المشاكل الثلاث ثم تحقق من الإجابات في نهاية المقال.



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



المهمة الأولى



هناك عدة متغيرات:



x = 1
y = 2
l = [x, y]
x += 5

a = [1]
b = [2]
s = [a, b]
a.append(5)


ما سيتم عرضه عند الطباعة lو s؟



المهمة الثانية



حدد وظيفة بسيطة:



def f(x, s=set()):
    s.add(x)
    print(s)


ماذا يحدث إذا اتصلت:



>>f(7)
>>f(6, {4, 5})
>>f(2)


المهمة الثالثة



دعونا نحدد وظيفتين بسيطتين:



def f():
    l = [1]
    def inner(x):
        l.append(x)
        return l
    return inner

def g():
    y = 1
    def inner(x):
        y += x
        return y
    return inner


ما الذي نحصل عليه بعد تنفيذ هذه الأوامر؟



>>f_inner = f()
>>print(f_inner(2))

>>g_inner = g()
>>print(g_inner(2))


ما مدى ثقتك في إجاباتك؟ لنتحقق من حالتك.



حل المشكلة الأولى



>>print(l)
[1, 2]

>>print(s)
[[1, 5], [2]]


لماذا تتفاعل القائمة الثانية مع تغيير العنصر الأول a.append(5)، بينما تتجاهل القائمة الأولى نفس التغيير تمامًا x+=5؟



حل المشكلة الثانية



دعونا نرى ما سيحدث:



>>f(7)
{7}

>>f(6, {4, 5})
{4, 5, 6}

>>f(2)
{2, 7}


انتظر ، ألا يجب أن تكون النتيجة الأخيرة {2}؟



حل المشكلة الثالثة



ستكون النتيجة هكذا:



>>f_inner = f()
>>print(f_inner(2))
[1, 2]

>>g_inner = g()
>>print(g_inner(2))
UnboundLocalError: local variable ‘y’ referenced before assignment


لماذا g_inner(2)لم تفعل 3؟ لماذا f()تتذكر الوظيفة الداخلية النطاق الخارجي ، لكن الوظيفة الداخلية g()لا تتذكر ؟ إنها متطابقة تقريبًا!



تفسير



ماذا لو أخبرتك أن كل هذه السلوكيات الغريبة لها علاقة بالفرق بين الأشياء القابلة للتغيير والثابتة في Python؟



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



شرح المهمة الأولى



x = 1
y = 2
l = [x, y]
x += 5

a = [1]
b = [2]
s = [a, b]
a.append(5)

>>print(l)
[1, 2]

>>print(s)
[[1, 5], [2]]


نظرًا لأنه غير xقابل للتغيير ، فإن العملية x+=5لا تغير الكائن الأصلي ، ولكنها تنشئ كائنًا جديدًا. لكن العنصر الأول في القائمة لا يزال يشير إلى الكائن الأصلي ، لذلك لا تتغير قيمته.



لان كائن قابل للتغيير ، ثم يقوم الأمر a.append(5)بتعديل الكائن الأصلي (بدلاً من إنشاء كائن جديد) ، والقائمة s"ترى" التغييرات.



شرح المهمة الثانية



def f(x, s=set()):
    s.add(x)
    print(s)

>>f(7)
{7}

>>f(6, {4, 5})
{4, 5, 6}

>>f(2)
{2, 7}


كل شيء واضح مع أول نتيجتين: 7تضاف القيمة الأولى إلى المجموعة الفارغة في البداية وتتضح {7}؛ ثم 6تضاف القيمة إلى المجموعة {4, 5}ويتم الحصول عليها {4, 5, 6}.



ثم تبدأ الشذوذ. 2لا تتم إضافة القيمة إلى المجموعة الفارغة ، ولكن إلى {7}. لماذا ا؟ يتم sحساب القيمة الأولية للمعلمة الاختيارية مرة واحدة فقط: في المكالمة الأولى ، سيتم تهيئة s كمجموعة فارغة. وبما أنها قابلة للتغيير ، f(7)سيتم تغييرها في مكانها بعد استدعائها . f(6, {4, 5})لن تؤثر المكالمة الثانية على المعلمة الافتراضية: يتم استبدالها بمجموعة {4, 5}، أي أنها {4, 5}متغير مختلف. f(2)تستخدم المكالمة الثالثة نفس المتغيرsالتي تم استخدامها أثناء المكالمة الأولى ، ولكن لم يتم إعادة تهيئة المجموعة كمجموعة فارغة ، ولكن بدلاً من ذلك تم أخذ قيمتها السابقة {7}.



لذلك ، يجب ألا تستخدم الوسائط القابلة للتغيير كوسيطة افتراضية. في هذه الحالة ، يجب تغيير الوظيفة:



def f(x, s=None):
    if s is None:
        s = set()
    s.add(x)
    print(s)


شرح المهمة الثالثة



def f():
   l = [1]
   def inner(x):
       l.append(x)
       return l
   return inner

def g():
   y = 1
   def inner(x):
       y += x
       return y
   return inner

>>f_inner = f()
>>print(f_inner(2))
[1, 2]

>>g_inner = g()
>>print(g_inner(2))
UnboundLocalError: local variable ‘y’ referenced before assignment


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



لماذا يحدث هذا؟ عندما ننفذ l.append(x)، يتغير الكائن القابل للتغيير عندما يتم تعريف الوظيفة. لكن المتغير lلا يزال يشير إلى العنوان القديم في الذاكرة. ومع ذلك ، تؤدي محاولة تغيير متغير غير قابل للتغيير في الوظيفة الثانية y += xإلى حقيقة أن y تبدأ في الإشارة إلى عنوان آخر في الذاكرة: سيتم نسيان y الأصلي ، مما سيؤدي إلى خطأ UnboundLocalError.



خاتمة



الفرق بين الأشياء القابلة للتغيير والثابتة في Python مهم جدًا. تجنب السلوك الغريب الموصوف في هذه المقالة. خصوصا:



  • .
  • - .



All Articles