هل سبق لك التعامل مع مجموعات بيانات ضخمة لدرجة أن ذاكرة الكمبيوتر لديك لا تستطيع التعامل معها؟ هل تساءلت يومًا ما إذا كان من الممكن أن تكون هناك طريقة لمقاطعة دالة في منتصفها قبل استئنافها؟ هنا يأتي دور مولدات بايثون.
مولدات بايثون هي وسيلة لإنشاء متكررات مخصصة. قبل أن نستمر، إذا لم تكن على دراية بمصطلحات بايثون، فراجع مقالاتنا حول مصطلحات بايثون للمبتدئين. وإذا لم تكن مرتاحًا للعمليات على هياكل البيانات في بايثون، فقد ترغب في تجربة دورة الخوارزميات المضمنة في بايثون.
يمكنك تكرار كائنات المولد كما تفعل مع القائمة. ولكن على عكس القوائم، لا تخزن المولدات محتوياتها في الذاكرة. ومن الحالات الشائعة الاستخدام عندما يتعين عليك التعامل مع ملفات أكبر من قدرة ذاكرة جهازك على التعامل معها، على سبيل المثال مجموعة بيانات كبيرة. ستؤدي محاولة فتح مثل هذا الملف إلى حدوث MemoryError
.
من خلال استخدام مولد بايثون، يمكنك تجنب مثل هذه المشكلة. ولكن انتظر! كيف يمكنك تعريف مولدات بايثون؟
كيفية تعريف المولدات في بايثون
مولد بايثون يشبه إلى حد كبير دالة بايثون العادية، ولكننا ننهيها بـ yield
بدلاً من كلمة return
. دعنا نكتب مثالاً سريعًا باستخدام حلقة for
.
def regular_function(x):
for i in range(x):
return i*5
بمجرد تنفيذ regular_function(10)
، ستعيد هذه الدالة العادية القيمة 0، لأن التنفيذ يتوقف بعد التكرار الأول.
ومع ذلك، دعونا نكتبها بشكل مختلف قليلا:
def generator(x):
for i in range(x):
yield i*5
تشير كلمة yield
إلى أننا بدأنا تشغيل كائن مولد؛ وهي موجودة هنا للتحكم في تدفق مولد بايثون. عندما يصل إليها البرنامج، يتم إيقاف تنفيذ الدالة مؤقتًا، ويتم إرجاع القيمة من yield
.
في هذه المرحلة، يتم حفظ حالة الدالة وتستأنف الدالة تنفيذها كلما استدعيت إحدى طرق المولد. توقف عبارة return
الدالة تمامًا.
لذلك عندما ننفذ ..
generator(10)
.. نحصل على:
<generator object generator at 0x00000262F8EBB190>
بعد ذلك، نقوم بإنشاء كائن المولد كـ g
:
>>> g = generator(10)
لتنفيذ المولد في بايثون، نحتاج إلى استخدام التابع next()
. في المثال التالي، نقوم بتضمين عبارات الطباعة للحصول على بعض المخرجات:
>>> print(next(g))
0
>>> print(next(g))
5
>>> print(next(g))
10
>>> print(next(g))
15
على الرغم من أن التابع next()
خاصة بالمولدات في ايثون، إلا أنها ليست الطريقة الوحيدة لإيقاف حلقة for
مؤقتًا أو إنهائها.
هناك طريقة أخرى لتعريف المولد في بايثون وهي استخدام فهم المولد. على غرار فهم القائمة، يمكن تعريف فهم المولد على النحو التالي:
gen_comp = (i*5 for i in range(10))
بالمقارنة مع فهم القائمة، فإن فهم المولدات يتميز بعدم بناء الكائن بالكامل وحفظه في الذاكرة قبل التكرار. دعنا نقارن بين تكوين المولد وفهم القائمة:
list_comp = [i*5 for i in range(100000)]
gen_comp = (i*5 for i in range(10000))
تبدو هذه التعبيرات متشابهة جدًا؛ والاختلاف الوحيد هو الأقواس والأقواس الفاصلة. ومع ذلك، فهي في الواقع مختلفة جدًا. دعنا نلقي نظرة على حجمها:
>>> import sys
>>> list_comp
>>> print('list comprehension:', sys.getsizeof(list_comp), 'bytes')
list comprehension: 87616 bytes
>>> gen_comp
>>> print('generator comprehension:', sys.getsizeof(gen_comp), 'bytes')
generator comprehension: 112 bytes
في هذه الحالة، يكون كائن القائمة أكبر بحوالي 782 مرة من كائن المولد. لذلك، إذا كانت الذاكرة مشكلة، فمن الأفضل استخدام مولد بايثون.
أخيرًا وليس آخرًا، لا يوجد فرق بين المولد العادي وفهم المولد باستثناء بناء الجملة. الفرق الوحيد هو أن فهم المولد عبارة عن سطر واحد.
إذا كنت بحاجة إلى تعريف حلقة لا نهائية لسبب ما، فستحتاج إلى استخدام مولد بايثون. وبينما يمكن أن تكون تسلسلتك لا نهائية، فإن ذاكرة الكمبيوتر الخاصة بك ليست كذلك بالتأكيد.
def infinity():
n = 0
while True:
yield n*n
n += 13
نقوم بتهيئة متغير n ونبدأ حلقة لا نهائية. ستقوم الكلمة المفتاحية yield
بالتقاط الحالة الأولية وتقليد عمل range()
; وأخيرًا، نقوم بزيادة n بمقدار 13. سيستمر هذا البرنامج في حلقة for
حتى نوقفها يدويًا.
في حالتنا، من خلال استدعاء next()
، يمكننا تكرار العملية يدويًا بشكل متكرر، وهو ما يساعد في اختبار المولد للتأكد من أنه ينتج الناتج المتوقع. هل يعني هذا أن المولد يمكنه الاستمرار إلى ما لا نهاية؟
كيفية إنهاء المولدات في بايثون
أولاً، يمكن أن تتوقف بشكل طبيعي. بعبارة أخرى، بمجرد تقييم جميع القيم، ستتوقف التكرارات، وستخرج حلقة for
.
إذا كنت تستخدم next()
، فسوف تحصل على استثناء StopIteration
صريح.
طريقة أخرى لإنهاء مولد بايثون هي استخدام التابع close()
، على النحو التالي:
>>> def generator(x):
... for i in range(x):
... yield i*5
>>> g = generator(10)
>>> print(next(g))
0
>>> print(next(g))
5
>>> print(g.close())
None
سيؤدي التابع close()
إلى إخراج GeneratorExit
عند قيمة العائد وإيقاف تنفيذ المولد. يمكن أن يكون ذلك مفيدًا للتحكم في تدفق المولد اللانهائي.
أفكار ختامية حول المولدات في بايثون
في هذه المقالة، تعلمنا عن المولدات في بايثون. واكتشفنا كيف يمكن أن تكون مفيدة في التعامل مع العمليات الحسابية التي تستهلك قدرًا كبيرًا من الذاكرة وكيف يمكنها أن توفر لنا مزيدًا من المرونة فيما يتعلق بدوالنا (على سبيل المثال عند اختبار الناتج).
أشجعك على استكشاف أمثلة هذه المقالة بشكل أكبر والاطلاع على وثائق بايثون للحصول على مزيد من المعلومات. يمكن لدورات Python Basics أيضًا مساعدة المبرمجين الجدد في اكتساب خبرة عملية في البرمجة. لا يلزم وجود معرفة سابقة بتكنولوجيا المعلومات.
اكتشاف المزيد من بايثون العربي
اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.