English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
في هذا المقال، ستتعلم كيفية استخدام مولدات بايثون لإنشاء التكرارات بسهولة، وما هو الفرق بينها وبين المولدات العادية والوظائف العادية، ولماذا يجب استخدامها.
باستخدام Pythonبناءالمستدعياتهناك الكثير من التكاليف؛ يجب علينا تحقيق كلاً من __iter__() و __next__() كفئة، تتبع الحالة الداخلية، وإطلاق StopIteration عند عدم وجود قيمة للاسترجاع، وما إلى ذلك.
هذا طويل ومخالف للبديهية. يمكن أن يستخدم الإنتاجية في هذا السياق.
الإنتاجية في Python هي وسيلة بسيطة لإنشاء المستدعيات. يتم التعامل مع جميع التكاليف التي ذكرناها أعلاه تلقائيًا بواسطة إنتاجية Python.
بكلمات قليلة، الإنتاجية هي دالة تعود objekt (مستدعي)، يمكننا تمريرها بالتتابع (قيمة واحدة تلو الأخرى).
إن إنشاء الإنتاجية في Python بسيط جدًا. بقدر سهولة استخدام جملة yield بدلاً من تعبير return لتحديد وظيفة عادية.
إذا كانت تحتوي الدالة على جملة yield على الأقل (قد تحتوي أيضًا على تعبيرات yield أو return أخرى)، فإنها تصبح دالة إنتاجية. تعود yield و return بعض القيم.
الفرق يكمن في أن عند إكمال تعبير return لإنهاء الدالة، تؤجل جملة yield الدالة وتحفظ جميع حالاتها، وتستأنف التنفيذ في الاستدعاء التالي.
الفرق بين دوال الإنتاجية ودوال العادي.الفرق.
تتضمن دوال الإنتاجية جملة yield واحدة أو أكثر.
عند الاستدعاء، يعود لها objekt (مستدعي)، ولكن لا يبدأ التنفيذ فورًا.
تمثل طرق مثل __iter__() و __next__() تنفيذًا تلقائيًا. لذلك، يمكننا استخدام next() للاستدعاء على العناصر.
بمجرد أن ينتجت الدالة نتيجة، تؤجل الدالة وتنتقل السيطرة إلى المستدعي.
تتم تذكر المتغيرات المحلية وأحوالها بين الاستدعاءات المتتابعة.
آخيرًا، عند انتهاء الدالة، يتم إطلاق StopIteration تلقائيًا عند التحقق من الاستدعاء التالي.
هذا مثال يوضح جميع النقاط المذكورة أعلاه. لدينا دالة my_gen() مسماة بجملة yield.
# دالة توليد بسيطة def my_gen(): n = 1 print('هذا هو الطبع الأول') # تتضمن دالة التوليد جملة yield yield n n += 1 print('هذا هو الطبع الثاني') yield n n += 1 print('هذا هو الدور الأخير للطبع') yield n
توضيح التشغيل التفاعلي في المفسر كما يلي. يمكنك تشغيل هذه الأوامر في Python Shell لرؤية الناتج.
>># يعود لها objekt، ولكن لا يبدأ التنفيذ فورًا. >>a = my_gen() >># يمكننا استخدام next() للاستدعاء على هذه العناصر. >>> next(a) هذا هو الطبع الأول 1 >># بمجرد أن ينتجت الدالة نتيجة، تؤجل الدالة وتنتقل السيطرة إلى المستدعي. >># يتم تذكر المتغيرات المحلية وأحوالها بين الاستدعاءات المتتابعة. >>> next(a) هذا هو الطبع الثاني 2 >>> next(a) هذا هو الدور الأخير للطبع 3 >># آخيرًا، عند انتهاء عمل الدالة، يتم إطلاق StopIteration تلقائيًا عند التحقق من الاستدعاء التالي. >>> next(a) >>> next(a) Traceback (أحدث استدعاء أخر): ... >>> next(a) >>> next(a) Traceback (أحدث استدعاء أخر): ...
StopIterationمن المهم ملاحظة شيئًا مثيرًا للاهتمام في المثال السابق، حيث يتم تذكر المتغير بين كل استدعاء.القيم
مختلفة عن الدوال العادية، لا يتم تدمير المتغيرات المحلية عند إنتاج الدالة. بالإضافة إلى ذلك، يمكن تمرير مولدات الأجسام فقط مرة واحدة.
لإعادة بدء العملية، نحتاج إلى إنشاء مولد جديد باستخدام = my_gen() وما إلى ذلك.
ملاحظة:يجب الانتباه إلى نقطه أخرى أن يمكننا نقل المولد مباشرة إلىدورة forمعًا
ذلك لأن دورة for تقبل م迭代ًا، وتستخدم دالة next() لاستدعاءها. عند تفعيل StopIteration، ينتهي بشكل تلقائي.فهم كيفية تنفيذ دورة for في Python。
# دالة توليد بسيطة def my_gen(): n = 1 print('هذا هو الطبع الأول') # تتضمن دالة التوليد جملة yield yield n n += 1 print('هذا هو الطبع الثاني') yield n n += 1 print('هذا هو الدور الأخير للطبع') yield n # دورة for for item in my_gen(): print(item)
عند تشغيل البرنامج، يكون الإخراج كالتالي:
هذا هو الطبع الأول 1 هذا هو الطبع الثاني 2 هذا هو الدور الأخير للطبع 3
مثال الأعلى ليس له فائدة كبيرة، ندرسه فقط لفهم ما يحدث في الخلفية.
عادةً، يتم تحقيق دوال التوليد من خلال دورات تحتوي على شروط انتهاء مناسبة.
لنأخذ مثالًا على توليد إعادة ترتيب النص.
def rev_str(my_str): length = len(my_str) for i in range(length - 1, -1, -1): yield my_str[i] # دورة for لتحويل النص # إخراج: # o # l # l # e # h for char in rev_str("hello"): print(char)
في هذا المثال، نستخدم دالة range() لاستخدام دورة for للحصول على مؤشرات بالترتيب المعكوس.
يظهر أنه هذا دالة توليد لا تنطبق فقط على الأحرف، بل تنطبق أيضًا على أنواع أخرى من العناصر القابلة للتكرار، مثلlist،tupleالخ.
يمكن استخدام تعبيرات التوليد بسهولة لإنشاء مولدات بسيطة ديناميكية. إنه يجعل بناء المولدات سهلة.
إنشاء دالة lambdaالمجهولةالمثل
، تعبيرات الاستدلال تُنشئ وظائف استدلال مجهولة المصدر.تقارب تعبيرات الاستدلال من الناحية الجملية هوبايثونفيالجملة.
الفرق الرئيسي بين تفكير القائمة وتعبيرات الاستدلال هو أنه بينما ينتج تفكير القائمة القائمة الكاملة، ينتج تعبيرات الاستدلال عنصرًا واحدًا في كل مرة.
هم نوعاً ما كسولون، ويقومون بإنتاج العناصر فقط عند الحاجة. لهذا السبب، تعبيرات الاستدلال أكثر كفاءة في استهلاك الذاكرة من تفكير القائمة المتساوي.
# إعداد القائمة my_list = [1, 3, 6, 10] # استخدم تفكير القائمة لتحويل كل عنصر # خروج: [1, 9, 36, 100] [x**2 for x in my_list] # يمكن القيام بالمثل باستخدام تعبيرات الاستدلال # خروج: <generator object <genexpr> at 0x0000000002EBDAF8> (x**2 for x in my_list)
يمكننا رؤية أن تعبيرات الاستدلال لا تنتج النتيجة المطلوبة على الفور. بدلاً من ذلك، تعود بمثابة عنصر استدلال يحتوي على العناصر التي يتم إنتاجها حسب الطلب.
# إعداد القائمة my_list = [1, 3, 6, 10] a = (x**2 for x in my_list) # خروج: 1 print(next(a)) # خروج: 9 print(next(a)) # خروج: 36 print(next(a)) # خروج: 100 print(next(a)) # خروج: StopIteration next(a)
يمكن استخدام تعبيرات الاستدلال داخل الدوال. يمكن إزالة الدوائر عند استخدامها بهذه الطريقة.
>>> sum(x**2 for x in my_list) 146 >>> max(x**2 for x in my_list) 100
هناك بعض الأسباب التي تجعل الاستدلالات إصدارًا جذابًا.
على عكس العناصر المترادفة من كلاسيكيات الاستدلال، يمكن للاستدلالات إنتاجها بطريقة واضحة وبسيطة. إليك مثال على استخدام كلاسيكيات الاستدلال لإنشاء سلسلة من القوى المربعة لعدد 2.
class PowTwo: def __init__(self, max = 0): self.max = max def __iter__(self): self.n = 0 return self def __next__(self): if self.n > self.max: raise StopIteration result = 2 ** self.n self.n += 1 return result
هذا الكود طويل جدًا. الآن، قم بتنفيذ نفس العملية باستخدام وظيفة المحول.
def PowTwoGen(max = 0): n = 0 while n < max: yield 2 ** n n += 1
بسبب متابعة المحولات للتفاصيل تلقائيًا، فإنها بسيطة وسهلة التحقيق.
الوظائف العادية التي تعود بنسخة من السلسلة تخلق النسخة الكاملة في الذاكرة قبل العودة، مما يؤثر على الكفاءة إذا كانت عدد العناصر في السلسلة كبيرًا.
وتبدأ هذه السلسلة من المحولات في التفاعل مع الذاكرة بشكل جيد، لذا هي الخيار المفضل، لأنها تولد عنصرًا واحدًا في كل مرة.
المحولات هي وسيلة ممتازة لتمثيل تدفقات البيانات اللامتناهية. لا يمكن تخزين التدفقات اللامتناهية في الذاكرة، وبما أن المحولات تولد عنصرًا واحدًا في كل مرة، فإنها يمكن أن تمثل تدفقات البيانات اللامتناهية.
الآن يمكننا توليد جميع الأعداد الأزلية (على الأقل نظرياً).
def all_even(): n = 0 while True: yield n n += 2
يمكن استخدام المحولات لخطوط سلسلة من العمليات. من الأفضل توضيح ذلك بمعاينة.
إذن، لنفترض أن لدينا ملف سجلات لشركة سلسلة مطاعم مشهورة. يحتوي ملف السجلات على عمود (العمود الرابع)، والذي يتبع عدد البيتزا المباعة كل ساعة، ونريد أن نضيفها للحصول على إجمالي عدد البيتزا المباعة في الخمس سنوات الماضية.
إذن، لنفترض أن جميع المحتويات هي نصوص وليس هناك أرقام متاحة لتكون مسمىها "N / A". يمكن تنفيذ المحول كما يلي.
with open('sells.log') as file: pizza_col = (line[3] for line in file) per_hour = (int(x) for x in pizza_col if x != 'N/A') print("Total pizzas sold = ", sum(per_hour))
هذه السلسلة من العمليات فعالة للغاية وسهلة القراءة (نعم، رائعة جداً!).