يتطلب هذا المقال مستوى: مبتدئ
ليه القائمة الافتراضية في بايثون بتفضل تتذكر القيم القديمة؟
لو كتبت دالة وحطيت قيمة افتراضية cart=[]، وبعد شوية لقيت القائمة بتتراكم فيها قيم من استدعاءات قديمة خالص، المشكلة مش في كودك ولا في بايثون. ده أشهر فخ في اللغة، وفهمه هيوفر عليك باجات بتظهر في الإنتاج بس مبتظهرش وانت بتجرب.
المشكلة باختصار
القيمة الافتراضية القابلة للتغيير (list أو dict أو set) بتتشارك بين كل استدعاءات الدالة. يعني نفس القائمة الواحدة بتفضل موجودة من أول لما عرّفت الدالة، وكل استدعاء بيضيف عليها بدل ما يبدأ من فاضي.
مثال بسيط الأول
تخيل إنك صاحب محل، وقلت لنفسك: كل زبون يدخل هديله كيس فاضي يحط فيه مشترياته. بس بالغلط بتديله نفس الكيس اللي استعمله الزبون اللي قبله. الزبون التاني هيلاقي في كيسه حاجات مش بتاعته، والتالت كمان. مفيش كيس جديد كل مرة، ده كيس واحد بيتنقل من إيد لإيد.
ده بالظبط اللي بيحصل مع القائمة الافتراضية. الكيس هو القائمة، والزبون هو الاستدعاء. لأن الكيس واحد مشترك، الحاجات بتتراكم فيه.
الشرح العلمي: القيمة الافتراضية بتتقيّم مرة واحدة
في بايثون، التعبير اللي بتحطه كقيمة افتراضية بيتنفّذ مرة واحدة بس، وقت تعريف الدالة، مش وقت كل استدعاء. النتيجة بتتخزن جوا كائن الدالة نفسه في السمة __defaults__. فلو القيمة دي كائن قابل للتغيير زي []، كل الاستدعاءات بتشير لنفس الكائن، وأي append بيفضل محفوظ.
def add_item(item, cart=[]):
cart.append(item)
return cart
print(add_item("تفاحة")) # ['تفاحة']
print(add_item("موزة")) # ['تفاحة', 'موزة'] ← مفاجأة، مش ['موزة']
print(add_item("خوخة")) # ['تفاحة', 'موزة', 'خوخة']
# الدليل: القائمة متخزنة جوا الدالة نفسها
print(add_item.__defaults__) # (['تفاحة', 'موزة', 'خوخة'],)
لاحظ إن الاستدعاء التاني بدأ من ['تفاحة'] مش من قائمة فاضية. ده لأن cart هي نفس الكائن في كل مرة.
سيناريو واقعي بالأرقام
افترض عندك دالة build_response(item, log=[]) في خدمة ويب بتخدم 500 طلب في الدقيقة. مع كل طلب بتعمل log.append(...). لأن الـ log افتراضي ومشترك، بعد ساعة واحدة هيبقى فيه حوالي 30 ألف عنصر متراكم من كل المستخدمين مع بعض، وممكن بيانات مستخدم تتسرب لرد مستخدم تاني. الذاكرة بتكبر خطيًا وميتنضّفش أبدًا، وده بالظبط نوع الباج اللي بيعدي من الاختبارات وبيقع في الإنتاج.
الحل: استخدم None كقيمة افتراضية
القيمة None ثابتة (immutable)، فآمنة كقيمة افتراضية. بعدها تعمل قائمة جديدة جوا الدالة لو المستخدم مبعتش حاجة.