النطاق والدوال المجهولة (Lambdas)
افهم نطاق المتغيرات واكتب دوالًا سطرية موجزة.
- فهم النطاق المحلي مقابل النطاق العام
- استخدام الكلمتين المفتاحيتين global و nonlocal
- كتابة دوال lambda للعمليات القصيرة
- تطبيق lambdas مع sorted() و map() و filter()
أين يسكن الاسم؟
المتغيرُ اسمٌ مربوطٌ بقيمة ، لكن أين يسري ذلك الربط هو النطاق. وترسم الرياضيات هذا أيضًا: في ، الحرفُ متغيّرٌ مؤقتٌ لا يعيش إلا داخل التعريف. وخارجه قد يعني شيئًا مختلفًا كلّيًّا. ترسم بايثون الجدرانَ نفسها حول أجسام الدوال: المتغيرُ المنشأُ داخل دالةٍ محلي ، يوجد داخل الجدران ولا يوجد في غيرها.
def my_func():
x = 10
print(x) # يعمل
my_func()
# print(x) # NameError: x is not definedأمّا المتغيراتُ المعرَّفةُ على مستوى الوحدة فمرئيةٌ في كل ما تحتها ، إنها عامة:
counter = 0
def increment():
global counter
counter += 1
increment()
print(counter) # 1مشكلةُ global أنها تترك دالةً تعيد كتابةَ العالم من الداخل. يتغير الربطُ حيث لم يخبرك شيءٌ في الاستدعاء أنه سيتغير. فضّل إرجاع القيم على التمدد نحو global ، فالدالةُ التي ترجع دالةٌ تستطيع اختبارها والاستدلال عليها بمعزلٍ.
النطاق المتداخل و nonlocal
قد تتكدس الدوال، ويمكن لدالةٍ داخليةٍ أن تقرأ متغيرًا خارجيًّا. أمّا إعادة تعيينه فتشترط كلمةَ nonlocal ، اعترافًا بأن الاسمَ يخصّ النطاقَ المحيط:
def make_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
counter = make_counter()
print(counter()) # 1
print(counter()) # 2يحمل increment الداخليُّ ذاكرتَه الخاصة: كلُّ استدعاءٍ يدفع count الملتقَط. هذا هو إغلاقٌ ، دالةٌ بجيبِ حالةٍ تجرّه وراءها بعد أن تنتهي دورتُها المحيطية.
Lambda: الدالة السطرية
للدالةِ التي تتسع في سطرٍ واحدٍ اختزالٌ. تنشئ lambda دالةً صغيرةً مجهولة ، صيغةً في هيئة تعبير:
add = lambda a, b: a + b
print(add(3, 5)) # 8إنها معادلةٌ لـ def التي تعرفها بالفعل:
def add(a, b):
return a + bالفرقُ فرقُ ثقل: تكتب def المراسمَ كلَّها لأيِّ شيءٍ فيه خطوات؛ وتبقى lambda سطريةً لتعبيرٍ واحدٍ، بلا توثيقٍ لاحقٍ وبلا return ، فالتعبيرُ بعد النقطتين هو قيمةُ العودة.
Lambdas مع دوال الرتبة الأعلى
تكسب الـ lambdas قوتَها حين تُسلَّم إلى دوالٍ تأخذ دالةً مدخلًا. الترتيبُ بالدرجة، وتعيينُ كل عنصرٍ، والاكتفاءُ بالعناصرِ التي تجتاز اختبارًا ، كلٌّ منها تدوينُ بناءِ المجموعاتِ في شيفرة:
students = [("Alice", 85), ("Bob", 92), ("Charlie", 78)]
# الترتيب بالدرجة (العنصر الثاني)
by_score = sorted(students, key=lambda s: s[1])
print(by_score) # [('Charlie', 78), ('Alice', 85), ('Bob', 92)]
# Map: تطبيق دالةٍ على كل عنصرٍ — $\{2x \mid x \in \text{nums}\}$
nums = [1, 2, 3, 4]
doubled = list(map(lambda x: x * 2, nums))
# [2, 4, 6, 8]
# Filter: الاحتفاظ بما يجتاز اختبارًا — $\{x \in \text{nums} \mid x \equiv 0 \pmod{2}\}$
evens = list(filter(lambda x: x % 2 == 0, nums))
# [2, 4]يحوّل map كلَّ عنصرٍ؛ ويمسك filter بالعناصر التي يقرّها مسندٌ؛ ويرتّب sorted وفق مفتاحٍ مختار. ثلاثُ عملياتٍ شائعةٍ على البيانات، كلٌّ يقبل دالةً صغيرةً مقبضَ تخصيص.
مثالٌ محلول: الدالةُ النظيفة
نصيحةُ الأنطاقِ ، تفضيلُ return على global ، لها هيئةٌ جاهزةٌ: سعرٌ والمعدلُ معاملٌ:
def price_with_tax(price, rate=0.2):
return round(price * (1 + rate), 2)
price_with_tax(10.0) # 12.0
price_with_tax(10.0, 0.08) # 10.8الدالةُ النظيفةُ لا تحتاجُ global: يصلُ المعدلُ معاملًا، والعالمُ الخارجيُّ باقٍ كما هو، وتُقرأ الصيغةُ ، . كلُّ ما يحدثُ يقعُ داخلَ الجدرانِ، ويعودُ الناتجُ عبر return.
أخطاء شائعة
- استخدام
globalحين يكفيreturn. يخفي الأثرَ الجانبيَّ ويقرن الدالةَ بمحيطها. - الإفراط في الـ lambdas. تعبيرٌ واحدٌ فقط؛ وما إن تحتاج lambda خطوتين حتى تحوّلها
def. - الخلط بين الأنطاق في الدوال المتداخلة. حين يُقرأ متغيرٌ، تتجه بايثون للخارج بحثًا عنه؛ وصيحتُا
nonlocalأوglobalتغيّران من يحقّ له الكتابة. اقرأ هذا المنطق قبل أن تفترض الربط. sortedدونkeyيرتبُ بالعنصرِ نفسه. تُرتبُ الأزواجُ معجميًّا بعنصرِها الأولِ أولًا؛ وللترتيبِ بالثاني، تكونُkeyإلزاميةً ،sorted(students, key=lambda s: s[1]).
🧩 تحديات
🧩 تحدٍّ ، فكّر أولًا ثم أظهر
رتّب الكلماتَ words = ["banana", "pie", "Washington", "a"] حسب الطول.
💡 الجواب: sorted(words, key=lambda w: len(w)) → ['a', 'pie', 'banana', 'Washington'] ، الدالةُ المفتاحيةُ ترفع كل كلمةٍ إلى العدد الذي يُقارَن.
🧩 تحدٍّ ، فكّر أولًا ثم أظهر
استخدم filter مع lambda للاحتفاظ بالكلماتِ الأطولِ من ثلاثة أحرفٍ من ["hi", "hello", "hey", "howdy", "yo"].
💡 الجواب: list(filter(lambda w: len(w) > 3, words)) → ['hello', 'howdy'] ، المسندُ هو شرطُ انتمائك، وfilter هو باني المجموعة.
🤔 أسئلة سقراطية
- لماذا تشترط بايثون
nonlocalبدلًا من ترك الدوال الداخلية تعيد تعيينَ متغيرٍ خارجيٍّ بحرية؟ أيَّ أخطاءٍ يمنع ذلك الاشتراط؟ map/filterمع lambda في مقابل فهمِ القائمة ، متى يكون كلٌّ أصدقَ إملاءً؟- لا تقبل lambda إلا تعبيرًا واحدًا. أيُّ قيدٍ يختبئ خلف تلك القاعدة؟
✅ فحص سريع
١. ماذا ترجع sorted(["banana", "pie", "a"], key=len)؟
٢. أيُّ كلمةٍ مفتاحيةٍ تتيح لدالةٍ داخليةٍ تعديلَ متغيرٍ خارجيٍّ؟