المولّدات في بايثون

المولّدات في بايثون

تشرح هذه المقالة المولّدات في بايثون۔

YouTube Video

المولّدات في بايثون

نظرة عامة

المولّدات في بايثون نوع من التكرار، وهي ميزة قوية لأداء المعالجة التكرارية بكفاءة۔ تمكنك من كتابة شيفرة موفرة للذاكرة عند التعامل مع كميات كبيرة من البيانات۔

ما هو المولّد؟

المولّد في بايثون هو دالة خاصة تنتج قيمة واحدة في كل مرة، ويتم تعريفها باستخدام الكلمة المفتاحية yield۔ ميزتها أنها توقف التنفيذ مع الاحتفاظ بحالتها ويمكنها الاستئناف لاحقاً۔

أساسيات yield

yield هي كلمة مفتاحية تعيد قيمة وتوقف تنفيذ الدالة مؤقتًا في الوقت نفسه۔

 1def simple_generator():
 2    yield 1
 3    yield 2
 4    yield 3
 5
 6gen = simple_generator()
 7
 8print(next(gen))  # 1
 9print(next(gen))  # 2
10print(next(gen))  # 3
  • عند استدعائها، تعيد هذه الدالة كائن مولّد يُخرج القيم واحدة تلو الأخرى۔
  • إذا قمت باستدعاء next() عندما لا توجد قيمة تالية، سيحدث خطأ StopIteration

next() و StopIteration

 1def simple_generator():
 2    yield 1
 3    yield 2
 4    yield 3
 5
 6gen = simple_generator()
 7
 8try:
 9    while True:
10        value = next(gen)
11        print(value)
12except StopIteration:
13    print("Finished")
  • من خلال التعامل الصريح مع خطأ StopIteration بهذه الطريقة، يمكنك اكتشاف متى ينتهي المولّد.۔

send(value)

استدعاء send(value) يستأنف تشغيل المولد ويرسل القيمة value إلى موضع تعبير yield۔ يمكن استقبال القيمة المرسلة على جانب المولد كـ قيمة إرجاع لتعبير yield۔ في الاستدعاء الأول، لا يمكنك إرسال أي شيء غير None باستخدام send(value)، لذا يجب عليك استخدام next() أو send(None)۔

 1def gen():
 2    x = yield 1
 3    print(f"x = {x}")
 4    y = yield 2
 5    print(f"y = {y}")
 6
 7g = gen()
 8print(next(g))       # -> 1 (value from yield 1)
 9print(g.send(10))    # -> x = 10, 2 (value from yield 2)
10print(g.send(20))    # -> y = 20, StopIteration occurs
  • مع send(10)، يصبح تعبير yield في المولد تعبيرًا يُرجع 10، ويتم إسناد القيمة 10 إلى المتغير x۔

throw()

استدعاء throw يستأنف المولد ويثير استثناءً عند موضع توقف تعبير yield۔ يمكنك معالجة الاستثناء داخل المولد لمتابعة المعالجة۔ إذا لم يتم التقاط الاستثناء، فسوف ينتشر إلى الخارج وينتهي المولد۔

 1def gen():
 2    try:
 3        yield 1
 4    except ValueError as e:
 5        print(f"Caught: {e}")
 6        yield "recovered"
 7
 8g = gen()
 9print(next(g))   # -> 1
10print(g.throw(ValueError("boom")))  # -> Caught: boom, "recovered"
  • في هذا الرمز، يتم استدعاء throw لإدخال استثناء في المولد۔ على جانب المولد، يتم معالجة الاستثناء ويتم إرجاع القيمة recovered۔

close()

استدعاء close() ينهي المولد۔ داخل المولد، يمكنك تنفيذ عمليات التنظيف باستخدام العبارة finally۔ استدعاء next() أو send() بعد استدعاء close() يؤدي إلى رفع خطأ StopIteration۔

1def gen():
2    try:
3        yield 1
4    finally:
5        print("Cleaning up...")
6
7g = gen()
8print(next(g))  # -> 1
9g.close()       # -> Cleaning up...
  • يوضح هذا الكود أن استدعاء close() ينهي المولد ويؤدي إلى تشغيل عملية التنظيف في العبارة finally۔

yield from

yield from عبارة عن بناء جملة يُستخدم لتفويض العمل إلى مولد فرعي۔ إنها طريقة بسيطة لاستدعاء مولد آخر داخل مولد وتمرير جميع قيمه إلى النطاق الخارجي۔

1def sub_gen():
2    yield 1
3    yield 2
4
5def main_gen():
6    yield from sub_gen()
7    yield 3
8
9print(list(main_gen()))  # -> [1, 2, 3]
  • يفوض هذا الكود جميع القيم من المولد الفرعي إلى المولد الخارجي باستخدام yield from، ثم يُصدر القيمة 3۔

العلاقة مع الكائنات المكررة (المكررات)

المولّدات تقوم داخلياً بتنفيذ __iter__() و __next__()، مما يجعلها نوعًا من المكررات۔ لذلك فهي متوافقة تمامًا مع العمليات القابلة للتكرار مثل حلقات for۔

دمج مع حلقات for

في بايثون، حلقة for تستخدم داخليًا next() لاسترجاع القيم تلقائيًا۔

1def simple_generator():
2    yield 1
3    yield 2
4    yield 3
5
6for value in simple_generator():
7    print(value)

وبهذه الطريقة، يتم التعامل مع StopIteration تلقائيًا أيضًا۔

إنشاء مولّدات لانهائية

1def count_up(start=0):
2    while True:
3        yield start
4        start += 1
5
6counter = count_up()
7print(next(counter))  # 0
8print(next(counter))  # 1

من الممكن إنشاء حلقات لانهائية، ولكن يجب الحذر عند استخدامها۔

تعبيرات المولّدات

تعبيرات المولّدات، التي تكتب باستخدام الأقواس، تسمح لك بتعريف مولّدات بصياغة مشابهة لقوائم الفهم۔

1# List comprehension (generates the entire list at once)
2squares_list = [x**2 for x in range(5)]
3print(squares_list)
4
5# Generator expression
6squares_gen = (x**2 for x in range(5))
7for square in squares_gen:
8    print(square)

على عكس قوائم الفهم، فإنها لا تحمل جميع العناصر في الذاكرة دفعة واحدة، مما يجعلها أكثر كفاءة في استخدام الذاكرة۔

التعامل مع الأخطاء في المولّدات

قد تحدث استثناءات داخل المولّد۔ في هذه الحالات، تستخدم try-except كما في كود بايثون العادي۔

 1def safe_divide_generator(numbers, divisor):
 2    """Yields results of dividing numbers by a given divisor safely."""
 3    for number in numbers:
 4        try:
 5            yield number / divisor  # Attempt to divide and yield result.
 6        except ZeroDivisionError:
 7            yield float('inf')  # Return infinity if division by zero occurs.
 8
 9# Example usage
10numbers = [10, 20, 30]
11gen = safe_divide_generator(numbers, 0)  # Create generator with divisor as 0.
12for value in gen:
13    print(value)  # Output: inf, inf, inf

في هذا المثال، يتم التعامل الصحيح مع الخطأ في حالة وقوع قسمة على صفر۔

تتبع المكدس للمولّد

إذا حدث استثناء داخل المولّد، سيتم إطلاقه عند استئناف عمل المولّد.۔

 1def error_generator():
 2    """A generator that yields values and raises an error."""
 3    yield 1
 4    raise ValueError("An error occurred")  # Raise a ValueError intentionally.
 5    yield 2
 6
 7gen = error_generator()
 8print(next(gen))       # Output: 1 (first value yielded)
 9try:
10    print(next(gen))   # Attempt to get the next value, which raises an error
11except ValueError as e:
12    print(e)           # Output: An error occurred (exception message is printed)
  • يُرجع هذا المولّد القيمة 1 أولًا.۔ يتم التقاط الخطأ الذي يتم إطلاقه عند الاستئناف وعرضه كرسالة خطأ.۔

أمثلة على استخدام المولّدات

قراءة ملف سطرًا بسطر (مناسب للملفات الكبيرة)

1def read_large_file(filepath):
2    with open(filepath, 'r') as f:
3        for line in f:
4            yield line.strip()
  • تقرأ هذه الدالة ملفًا نصيًا سطرًا بسطر باستخدام مكرّر، وتزيل المسافات البيضاء من كل سطر، وتعيده كمولّد، مما يتيح معالجة الملفات الكبيرة باستخدام ذاكرة منخفضة.۔

مولّد لسلسلة فيبوناتشي

1def fibonacci(limit):
2    a, b = 0, 1
3    while a < limit:
4        yield a
5        a, b = b, a + b
6
7for n in fibonacci(100):
8    print(n)
  • يستخدم هذا الكود مولّدًا لتوليد أرقام فيبوناتشي بشكل متسلسل أقل من الحد الأعلى ويعرضها باستخدام حلقة for

حالات الاستخدام

يمكن أيضًا استخدام المولدات في السيناريوهات التالية۔

  • المعالجة المتتابعة لملفات CSV أو ملفات السجلات الكبيرة
  • تجزئة صفحات API
  • معالجة بيانات البث المباشر (مثل كافكا، أو أجهزة إنترنت الأشياء)

الملخص

المفهوم النقطة الرئيسية
yield توقف وتنفيذ الدالة مؤقتًا وتعيد قيمة
دالة المولّد دالة تحتوي على yield وتعيد مكرراً عند استدعائها
المميزات موفرة للذاكرة ومثالية لمعالجة مجموعات البيانات الكبيرة
تعبير مولّد يسمح بصياغة مختصرة مثل (x for x in iterable)

باستخدام المولّدات، يمكنك معالجة مجموعات البيانات الكبيرة بكفاءة مع توفير الذاكرة والحفاظ على شفرة برمجية مختصرة۔

يمكنك متابعة المقالة أعلاه باستخدام Visual Studio Code على قناتنا على YouTube.۔ يرجى التحقق من القناة على YouTube أيضًا.۔

YouTube Video