# إزاي تضلل زمايلك المطورين ويبقى عندك قواميس بايثون بمفاتيح مش فريدة! ## افهم إزاي كائنات بايثون بتتصرف كمفاتيح في القواميس **تخيل معايا**: صاحبك المطور فتح الكود بتاعك ولقى القاموس (dictionary) ده: ```python {'Apple': 1, 'Apple': 2, 'Apple': 3} ``` **ردة فعله**: 😱 "**إيه ده؟! إزاي فيه 3 مفاتيح بنفس الاسم؟!**" **المفروض**: في بايثون، مفاتيح القواميس (dictionary keys) **لازم تكون فريدة** (unique)! **السؤال**: **إزاي عملت كده؟!** 🤔 **ده موضوع المقال** - بس **الأول** لازم نفهم إزاي القواميس ومفاتيحها بتشتغل من جوا! ## إيه هي القواميس (Dictionaries) أصلاً؟ **في بايثون**: القاموس = **مجموعة أزواج** من (مفتاح → قيمة) **مثال عملي** (قاعدة بيانات مبسطة): ```python users = { 'ahmed@example.com': {'name': 'أحمد', 'age': 28}, 'sarah@example.com': {'name': 'سارة', 'age': 25}, 'khaled@example.com': {'name': 'خالد', 'age': 32} } ``` **الميزة الكبيرة**: البحث **سريع جداً!** - لو عايز بيانات أحمد → `users['ahmed@example.com']` - **فوراً!** - مش محتاج تدور في كل العناصر واحد واحد **يعني إيه "سريع"؟** - القائمة العادية (list): لو فيها **مليون عنصر**، ممكن تدور في كلهم! ⏱️ **بطيء** - القاموس (dict): حتى لو فيه **مليون عنصر**، البحث **فوري تقريباً!** ⚡ **سريع** **السر**: القواميس بتستخدم **Hash Tables** - تقنية ذكية بتخلي البحث سريع! ## القاعدة الأولى: المفاتيح لازم تكون "قابلة للهاش" (Hashable) **يعني إيه Hashable؟** **دالة الـ Hash**: - بتاخد **أي قيمة** (نص، رقم، object) - بترجع **رقم ثابت** (hash value) - **نفس القيمة** → **نفس الـ hash** دايماً **مثال**: ```python hash('Apple') # النتيجة: رقم ثابت مثلاً -5693923834089884903 hash('Apple') # نفس الرقم دايماً! hash('Orange') # رقم مختلف ``` **الفكرة**: بايثون بتستخدم الـ hash عشان تلاقي المفتاح بسرعة - زي **فهرس الكتاب**! ### نجرب نعمل Class خاص بينا **تخيل**: عايزين نعمل class اسمه `Fruit` ونستخدم objects منه كمفاتيح قاموس! ```python class Fruit: def __hash__(self): return 1 # كل الفواكه ليها نفس الـ hash! d = { Fruit(): 1, Fruit(): 2 } # لما نطبع d: # {<__main__.Fruit at 0x10873eb10>: 1, <__main__.Fruit at 0x10873d9d0>: 2} ``` **لاحظ**: رغم إن الـ **hash نفس الحاجة** (كله 1)، القاموس عنده **مفتاحين منفصلين!** **ليه؟** 🤔 ### التصادم (Hash Collision): لما رقمين مختلفين يطلعوا نفس الـ Hash! **القاعدة المهمة** في دوال الـ Hash: > **نفس القيمة** → **نفس الـ hash** دايماً ✅ > لكن **نفس الـ hash** ← **مش معناه نفس القيمة!** ❌ **يعني إيه؟** **تخيل**: دالة hash ساذجة بتجمع أحرف الكلمة: - `"ABC"` → A=1, B=2, C=3 → **المجموع = 6** - `"BAC"` → B=2, A=1, C=3 → **المجموع = 6** (نفس الـ hash!) - لكن `"ABC"` **مش** `"BAC"` - **كلمات مختلفة!** **الحالة دي** اسمها **Hash Collision** (التصادم)! **عشان كده**: بايثون **مش بتكتفي بالـ hash** - بتتأكد كمان إن الـ objects **متساويين** ولا لأ! ## القاعدة التانية: المفاتيح لازم تكون فريدة (Unique) **طب نجرب نخلي الـ objects متساوية**: ```python class Fruit: def __hash__(self): return 1 # نفس الـ hash def __eq__(self, other): return True # كل الفواكه متساوية! d = { Fruit(): 1, Fruit(): 2 } # لما نطبع d: # {<__main__.Fruit at 0x10873ed50>: 2} ``` **تادا! 🎉** دلوقتي القاموس شايف إن الاتنين **نفس المفتاح!** - الـ hash **متساوي** ✅ - الـ objects **متساوية** ✅ - النتيجة: **المفتاح التاني بيستبدل الأول!** ### نتحكم في المساواة برمجياً **طبعاً** مش كل الفواكه لازم تكون متساوية - نقدر **نقرر** بناءً على الاسم: ```python class Fruit: def __init__(self, name): self.name = name def __hash__(self): return hash(self.name) # الـ hash بناءً على الاسم def __eq__(self, other): return self.name == other.name # متساويين لو نفس الاسم d = { Fruit("Apple"): 1, Fruit("Orange"): 2, Fruit("Apple"): 3 # دي هتستبدل الأولى! } # النتيجة: # {<__main__.Fruit at 0x10873cb90>: 3, <__main__.Fruit at 0x108247ad0>: 2} ``` **منطقي!** Apple التانية **استبدلت** الأولى، لكن Orange **لسه موجودة**! ## المشكلة: شكل المفاتيح وحش! 😖 **الـ output** بيطلع كده: ``` {<__main__.Fruit at 0x10873cb90>: 3, ...} ``` **مش عايزين** الـ `<__main__.Fruit at ...` القبيحة دي! **الحل**: نستخدم الـ `__repr__()` method! ```python class Fruit: def __init__(self, name): self.name = name def __hash__(self): return hash(self.name) def __eq__(self, other): return self.name == other.name def __repr__(self): return f"'{self.name}'" # كده هيطلع حلو! d = { Fruit("Apple"): 1, Fruit("Orange"): 2, Fruit("Apple"): 3 } # دلوقتي النتيجة: # {'Apple': 3, 'Orange': 2} ``` **أحلى! 😍** دلوقتي القاموس **شكله حلو** ومفهوم! ## حل اللغز: إزاي نعمل قاموس بمفاتيح "مش فريدة"؟ 😈 **دلوقتي جاهزين** نحل اللغز اللي في البداية! **الفكرة الشريرة**: - نخلي الـ **hashes مختلفة** → عشان بايثون تشوفهم **مفاتيح منفصلة** - بس الـ **`__repr__()` يطلع نفس الحاجة** → عشان **يبان** إنهم نفس المفتاح! **الكود**: ```python class Fruit: def __init__(self, name): self.name = name def __hash__(self): return hash(self.name) # كل فاكهة ليها hash مختلف def __eq__(self, other): return self.name == other.name def __repr__(self): return "'Apple'" # لكن كلهم بيطلعوا "Apple" في العرض! d = { Fruit("Apple"): 1, Fruit("Orange"): 2, # hash مختلف! Fruit("Banana"): 3 # hash مختلف! } # النتيجة المضللة: print(d) # {'Apple': 1, 'Apple': 2, 'Apple': 3} ``` **🎭 الخدعة اشتغلت!** القاموس **شكله** فيه 3 مفاتيح بنفس الاسم! ## ليه ده شرير؟ ⚠️ **المشكلة**: أي حد يشوف الكود ده **هيتضلل!** ### المشكلة الأولى: مش هيقدر يوصل للقيم! ```python d['Apple'] # ❌ KeyError! ``` **ليه؟** لأن `'Apple'` (string) **مش** `Fruit('Apple')` (object) - **الـ hash مختلف!** ### المشكلة التانية: المفاتيح بتبان زي بعض بس مش زي بعض! ```python list(d.keys()) # ['Apple', 'Apple', 'Apple'] # شكلهم زي بعض # لكن لو جربت: key1, key2, key3 = d.keys() key1 == key2 # False! - مش متساويين فعلاً! ``` **يعني**: الـ 3 "Apple" **مش نفس الحاجة** - بس **بيبانوا** إنهم نفس الحاجة! **ده **تضليل** خطير! 🚨** ## ليه بايثون عايزة المفاتيح Immutable (غير قابلة للتغيير)؟ **تخيل معايا** السيناريو الكارثي ده: ```python class Fruit: def __init__(self, name): self.name = name def __hash__(self): return hash(self.name) def __eq__(self, other): return self.name == other.name def __repr__(self): return self.name apple = Fruit("Apple") orange = Fruit("Orange") d = { apple: 1, orange: 2 } print(d) # {Apple: 1, Orange: 2} ✅ تمام ``` **دلوقتي نغير اسم Apple**: ```python apple.name = "Banana" # 😱 غيرنا الاسم! print(d) # {Banana: 1, Orange: 2} # الشكل اتغير # طب نحاول نوصل للقيمة: d[apple] # ❌ KeyError: Banana ``` **إيه اللي حصل؟!** 1. لما **أضفنا** `apple` للقاموس → الـ hash كان بناءً على `"Apple"` 2. بايثون **خزنت** المفتاح في مكان معين بناءً على الـ hash ده 3. لما **غيرنا** الاسم لـ `"Banana"` → الـ **hash اتغير!** 4. لما **بندور** على `apple` → بايثون بتدور في **مكان تاني** (hash جديد) 5. **ما بتلاقيش** المفتاح → **KeyError!** **يعني**: المفتاح **موجود** في القاموس، بس بايثون **مش لاقياه** لأن الـ hash اتغير! **عشان كده**: بايثون بتشجع على استخدام **مفاتيح immutable** زي: - ✅ Strings: `"Apple"` - ✅ Numbers: `42`, `3.14` - ✅ Tuples: `(1, 2, 3)` - ❌ Lists: `[1, 2, 3]` - قابلة للتعديل! - ❌ Dictionaries: `{'a': 1}` - قابلة للتعديل! ## الخلاصة: فهمنا إيه النهارده؟ **رحلة اليوم** علمتنا: ### 1. إزاي القواميس بتشتغل من جوا - **Hash Function**: بتحول المفتاح لرقم عشان البحث يبقى سريع - **Hash Collision**: ممكن مفاتيح مختلفة يكون ليها نفس الـ hash - **Equality Check**: بايثون بتتأكد من الـ hash **والمساواة** مع بعض ### 2. الـ Magic Methods المهمة - **`__hash__()`**: بتحدد الـ hash value للـ object - **`__eq__()`**: بتحدد المساواة بين objects - **`__repr__()`**: بتحدد شكل الـ object لما يتطبع ### 3. الخدعة الشريرة 😈 **ممكن** نخلي مفاتيح مختلفة **تبان** إنها نفس الحاجة: - نخلي الـ **hash مختلف** (عشان بايثون تشوفهم منفصلين) - نخلي الـ **`__repr__()` نفس الحاجة** (عشان يبانوا متشابهين) **لكن**: ده **تضليل خطير** - **ممنوع استخدامه في كود حقيقي!** ⚠️ ### 4. ليه المفاتيح لازم تكون Immutable - لو **غيرت** المفتاح بعد إضافته → الـ **hash بيتغير** - بايثون **مش هتلاقي** المفتاح تاني (بتدور في المكان الغلط!) - **النتيجة**: KeyError و bugs صعبة الاكتشاف ### 5. Default Behavior في بايثون **لو ما عرفتش** `__hash__()` و `__eq__()`: - بايثون بتديك **تطبيق افتراضي** - كل object **مختلف** عن أي object تاني - بس الـ object **يساوي نفسه** بس **المصدر**: [وثائق بايثون الرسمية](https://docs.python.org/3/reference/datamodel.html#object.__hash__) ## الدرس المستفاد 🎓 **المقال ده لأغراض تعليمية بس!** **الهدف**: - ✅ نفهم **إزاي بايثون بتشتغل** من جوا - ✅ نعرف **ليه** بايثون بتفرض قواعد معينة - ✅ نتعلم من **ثني القواعد** - بس **ما نكسرهاش** في كود حقيقي! **زي ما بنقول**: "اعرف القواعد كويس عشان تعرف **امتى** و**إزاي** تخالفها - لو **فعلاً** محتاج!" **بس في الكود الحقيقي**: استخدم القواميس بطريقة عادية - **مفاتيح فريدة، immutable، وواضحة!** ✅ --- **ملاحظة أخيرة**: أنا **بحب** ألعب بحدود اللغة وأشوف إزاي الحاجات بتشتغل لما "أثنيها" شوية - **وأظن إنت كمان!** 😄 **بس** دايماً **افتكر**: الكود اللي بتكتبه **هيقراه حد تاني** - **ما تضللوش!** 😉 --- طارق عمرو، 12 يناير 2023