Generators — Python المتقدم
المولّدات (Generators)
كتابة مكرّر بصنف (كما في الدرس السابق) تحتاج __iter__ و __next__ وحفظ الحالة يدوياً. المولّد يعطيك نفس النتيجة بدالة عادية فيها yield.
def countdown(start):
while start > 0:
yield start
start -= 1
for n in countdown(3):
print(n) # 3 2 1كيف يعمل yield؟
- استدعاء دالة المولّد لا ينفذ جسمها؛ يرجع كائن مولّد (وهو iterator).
- عند كل next() يُنفَّذ الجسم حتى أول yield، فترجع القيمة ويتجمد المولّد محتفظاً بكل متغيراته.
- الطلب التالي يكمل من حيث توقف.
- عند انتهاء الدالة تُرمى StopIteration تلقائياً.
gen = countdown(2)
print(next(gen)) # 2
print(next(gen)) # 1return مقابل yield
return تُنهي الدالة وترجع قيمة واحدة. yield ترجع قيمة وتستمر لاحقاً، فتنتج سلسلة قيم.
توفير الذاكرة
def read_lines(path):
with open(path, encoding="utf-8") as f:
for line in f:
yield line.rstrip("\n")مهما كان حجم الملف، في الذاكرة سطر واحد فقط في كل لحظة.
خطوط المعالجة (Pipelines)
مولّدات صغيرة تُركّب كسلسلة، وكل منها يمرر القيم للتالي واحدة واحدة:
lines = read_lines("log.txt")
errors = (l for l in lines if "ERROR" in l)
messages = (l.split(":", 1)[1].strip() for l in errors)لا يُقرأ أي سطر حتى تبدأ بالمرور على messages.
تعابير المولّد
(x * x for x in range(10)) بأقواس عادية: مولّد بسطر واحد، مثالي داخل sum() و any().
yield from
لتمرير كل قيم مولّد آخر:
def flatten(nested):
for item in nested:
if isinstance(item, list):
yield from flatten(item)
else:
yield itemتسلسلات لا نهائية
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + bآمنة لأن القيم تُحسب عند الطلب فقط؛ خذ ما تحتاجه بـ itertools.islice.
متى تستخدم مولّداً؟
عندما تمر على البيانات مرة واحدة بالترتيب، أو البيانات كبيرة أو لا نهائية. إن احتجت الوصول بالفهرس أو المرور عدة مرات، استخدم قائمة.
تمرين
اكتب مولّداً chunks(items, size) يقسم قائمة إلى أجزاء بحجم size، ومولّداً primes() لا نهائياً، واطبع أول 10 أعداد أولية.
# Generator بسيط
def count_up_to(n):
i = 1
while i <= n:
yield i
i += 1
# استخدام
for num in count_up_to(5):
print(num) # 1, 2, 3, 4, 5
# أو
counter = count_up_to(5)
print(next(counter)) # 1
print(next(counter)) # 2
# فيبوناتشي لانهائي
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib = fibonacci()
for _ in range(10):
print(next(fib), end=" ")
# 0 1 1 2 3 5 8 13 21 34
# Generator Expression
squares = (x**2 for x in range(1, 6))
print(list(squares)) # [1, 4, 9, 16, 25]
# مقارنة الذاكرة
import sys
list_comp = [x**2 for x in range(10000)]
gen_exp = (x**2 for x in range(10000))
print(f"List: {sys.getsizeof(list_comp)} bytes") # ~87000
print(f"Generator: {sys.getsizeof(gen_exp)} bytes") # ~200
# قراءة ملف كبير
def read_large_file(file_path):
with open(file_path, 'r') as file:
for line in file:
yield line.strip()
# Pipeline مع generators
def read_numbers(filename):
for line in read_large_file(filename):
yield int(line)
def filter_even(numbers):
for num in numbers:
if num % 2 == 0:
yield num
def square(numbers):
for num in numbers:
yield num ** 2
# استخدام
# result = square(filter_even(read_numbers("numbers.txt")))اختبر فهمك