التكرارات رائعة وكيفية جعلها

صورة لجون ماتيتشوك على Unsplash

المشكلة

أثناء التعلم في Make School ، رأيت زملائي يكتبون وظائف تنشئ قوائم بالعناصر.

s = 'baacabcaab
ع = 'أ'
def find_char (سلسلة ، حرف):
  المؤشرات = قائمة ()
  بالنسبة إلى الفهرس ، str_char في التعداد (السلسلة):
    إذا str_char == character:
      indices.append (رقم قياسي)
  مؤشرات العودة
طباعة (find_char (s، p)) # [1، 2، 4، 7، 8]

يعمل هذا التطبيق ، لكنه يطرح بعض المشاكل:

  • ماذا لو كنا نريد النتيجة الأولى فقط؟ هل سنحتاج إلى عمل وظيفة جديدة تمامًا؟
  • ماذا لو كان كل ما نقوم به هو حلقة فوق النتيجة مرة واحدة ، هل نحن بحاجة إلى تخزين كل عنصر في الذاكرة؟

التكرارات هي الحل الأمثل لهذه المشاكل. إنها تعمل مثل "قوائم كسول" في ذلك بدلاً من إرجاع قائمة بكل قيمة تنتجها وترجع كل عنصر واحد في كل مرة.

التكرار إرجاع القيم بتكاسل. حفظ الذاكرة.

لذلك دعونا نتعمق في التعلم عنهم!

المدمج في التكرارات

التكرارات التي غالبًا ما تكون تعداد () و zip (). كل من هذه القيم بتكاسل بإرجاع التالي () معهم.

النطاق () ، مع ذلك ، ليس متكرراً ، ولكنه "متكرر كسول". - شرح

يمكننا تحويل النطاق () إلى مكرر باستخدام iter () ، لذلك سنفعل ذلك من أجل أمثلة من أجل التعلم.

my_iter = iter (النطاق (10))
طباعة (التالي (my_iter)) # 0
طباعة (التالي (my_iter)) # 1

عند كل مكالمة من التالي () نحصل على القيمة التالية في مجموعتنا ؛ من المنطقي الصحيح؟ إذا كنت ترغب في تحويل التكرار إلى قائمة ، فأنت فقط منشئها.

my_iter = iter (النطاق (10))
طباعة (قائمة (my_iter)) # [0 ، 1 ، 2 ، 3 ، 4 ، 5 ، 6 ، 7 ، 8 ، 9]

إذا قمنا بتقليد هذا السلوك ، فسنبدأ في فهم المزيد حول كيفية عمل التكرارات.

my_iter = iter (النطاق (10))
my_list = قائمة ()
محاولة:
  احيانا صحيح:
    my_list.append (بجانب (my_iter))
باستثناء StopIteration:
  البشري
طباعة (قائمتي) # [0 ، 1 ، 2 ، 3 ، 4 ، 5 ، 6 ، 7 ، 8 ، 9]

يمكنك أن ترى أننا نحتاج إلى لفه في بيان جرب الصيد. ذلك لأن التكرارات ترفع StopIteration عندما يتم استنفادها.

لذلك إذا اتصلنا بعد ذلك على مكرر النطاق المنضب ، فسنواجه هذا الخطأ.

التالي (my_iter) # الزيادات: StopIteration

صنع مكرر

دعنا نحاول صنع مكرر يتصرف مثل النطاق مع وسيطة التوقف فقط باستخدام ثلاثة أنواع شائعة من التكرارات: الفئات ووظائف المولد (العائد) وتعبيرات المولد

صف دراسي

كانت الطريقة القديمة لإنشاء مكرر من خلال فئة محددة بوضوح. لكي يكون الكائن مكررًا ، يجب تطبيق __iter __ () لإرجاع نفسه و __next __ () لإرجاع القيمة التالية.

فئة my_range:
  _current = -1
  def __init __ (self، stop):
    self._stop = توقف
  def __iter __ (self):
    عودة النفس
  def __next __ (self):
    self._current + = 1
    إذا self._current> = self._stop:
      رفع StopIteration
    عودة الذات
ص = my_range (10)
طباعة (قائمة (ص)) # [0 ، 1 ، 2 ، 3 ، 4 ، 5 ، 6 ، 7 ، 8 ، 9]

لم يكن ذلك صعبًا للغاية ، ولكن لسوء الحظ ، يتعين علينا تتبع المتغيرات بين مكالمات next (). أنا شخصياً لا أحب لوحة الغلاية أو أغير طريقة تفكيري في الحلقات لأنه ليس حلاً متدرجًا ، لذلك أفضل المولدات

الفائدة الرئيسية هي أنه يمكننا إضافة وظائف إضافية تقوم بتعديل متغيراتها الداخلية مثل _stop أو إنشاء متكررات جديدة.

يمتلك مكررو الصف الدراسي الجانب السلبي الذي يحتاجون إليه ، لكن يمكنهم الحصول على وظائف إضافية تقوم بتعديل الحالة.

مولدات كهرباء

قدمت PEP 255 "مولدات بسيطة" باستخدام الكلمة الرئيسية ذات العائد.

اليوم ، المولدات الكهربائية عبارة عن مكررة يسهل تصنيعها أكثر من نظيراتها في الفصل.

مولد وظيفة

وظائف المولد هي ما تمت مناقشته في النهاية في برنامج PEP وهي نوع التكرار المفضل لدي ، لذلك دعونا نبدأ بذلك.

def my_range (توقف):
  الفهرس = 0
  بينما الفهرس <توقف:
    مؤشر العائد
    مؤشر + 1
ص = my_range (10)
طباعة (قائمة (ص)) # [0 ، 1 ، 2 ، 3 ، 4 ، 5 ، 6 ، 7 ، 8 ، 9]

هل ترى كم هي جميلة تلك الأسطر الـ 4 من الكود؟ إنه أقصر قليلاً من تنفيذ قائمتنا لتتصدره!

يعمل المولِّد على التكرارات التي تحتوي على قدر أقل من الطبقات المرجانية من الفئات ذات التدفق المنطقي العادي.

يعمل المولد تلقائيًا على "إيقاف" التنفيذ وإرجاع القيمة المحددة مع كل مكالمة من التالي (). هذا يعني أنه لا يتم تشغيل أي كود حتى أول مكالمة تالية ().

هذا يعني أن التدفق يشبه هذا:

  1. يسمى التالي () ،
  2. يتم تنفيذ التعليمات البرمجية حتى بيان العائد التالي.
  3. يتم إرجاع القيمة على يمين العائد.
  4. تم ايقاف التنفيذ
  5. كرر 1-5 لكل مكالمة تالية () حتى يتم ضرب السطر الأخير من التعليمات البرمجية.
  6. يتم رفع StopIteration.

تسمح وظائف المولد أيضًا لك باستخدام العائد من الكلمة الأساسية التي يستدعيها المستقبل التالي () إلى مكرر آخر حتى يتم استنفاد التكرار المذكور.

def yielded_range ():
  العائد من my_range (10)
طباعة (قائمة (yielded_range ())) # [0 ، 1 ، 2 ، 3 ، 4 ، 5 ، 6 ، 7 ، 8 ، 9]

لم يكن هذا مثالًا معقدًا بشكل خاص. ولكن يمكنك القيام بذلك بشكل متكرر!

def my_range_recursive (توقف ، الحالي = 0):
  إذا كان الحالي> = توقف:
    إرجاع
  العائد الحالي
  العائد من my_range_recursive (توقف ، حالي + 1)
ص = my_range_recursive (10)
طباعة (قائمة (ص)) # [0 ، 1 ، 2 ، 3 ، 4 ، 5 ، 6 ، 7 ، 8 ، 9]

مولد التعبير

تتيح تعبيرات المولدات إمكانية إنشاء برامج تكرار كخطوط أحادية وتكون جيدة عندما لا نحتاج إلى إعطائها وظائف خارجية. لسوء الحظ ، لا يمكننا إجراء my_range آخر باستخدام تعبير ، لكن يمكننا العمل على استخدام عناصر مثل آخر وظيفة my_range لدينا.

my_doubled_range_10 = (x * 2 لـ x في my_range (10))
طباعة (قائمة (my_doubled_range_10)) # 0 ، 2 ، 4 ، 6 ، 8 ، 10 ، 12 ، 14 ، 16 ، 18]

الشيء الرائع في هذا الأمر هو أنه يقوم بما يلي:

  1. تسأل القائمة my_doubled_range_10 عن قيمتها التالية.
  2. my_doubled_range_10 يسأل my_range عن قيمتها التالية.
  3. إرجاع my_doubled_range_10 قيمة my_range مضروبة في 2.
  4. قائمة إلحاق القيمة لنفسها.
  5. كرر 1-5 حتى يثير my_doubled_range_10 برنامج StopIteration الذي يحدث عندما يحدث my_range.
  6. يتم إرجاع القائمة التي تحتوي على كل قيمة تم إرجاعها بواسطة my_doubled_range.

يمكننا حتى القيام بالتصفية باستخدام تعبيرات المولد!

my_even_range_10 = (x لـ x في my_range (10) إذا x٪ 2 == 0)
طباعة (قائمة (my_even_range_10)) # [0 ، 2 ، 4 ، 6 ، 8]

هذا يشبه إلى حد كبير السابق باستثناء my_even_range_10 فقط بإرجاع القيم التي تتطابق مع الشرط المحدد ، لذلك فقط القيم بين في النطاق [0 ، 10).

خلال كل هذا ، نقوم فقط بإنشاء قائمة لأننا أخبرناها بذلك.

الفائدة

مصدر

نظرًا لأن المولدات متكرّرة ، فإن التكرارات متكررة ، ويعيد التكرار القيم بتسلل. هذا يعني أنه باستخدام هذه المعرفة ، يمكننا إنشاء كائنات من شأنها أن تعطينا كائنات فقط عندما نطلبها ومع ذلك نود أن نحبها.

هذا يعني أنه يمكننا نقل المولدات الكهربائية إلى وظائف تقلل من بعضها البعض.

طباعة (مجموع (my_range (10))) # 45

يؤدي حساب المبلغ بهذه الطريقة إلى تجنب إنشاء قائمة عندما يكون كل ما نقوم به هو إضافتها معًا ثم التخلص منها.

يمكننا إعادة كتابة المثال الأول ليكون أفضل بكثير باستخدام وظيفة المولد!

s = 'baacabcaab
ع = 'أ'
def find_char (سلسلة ، حرف):
  بالنسبة إلى الفهرس ، str_char في التعداد (السلسلة):
    إذا str_char == character:
      مؤشر العائد
طباعة (قائمة (find_char (s، p))) # [1 ، 2 ، 4 ، 7 ، 8]

الآن على الفور قد لا تكون هناك فائدة واضحة ، ولكن دعنا نذهب إلى سؤالي الأول: "ماذا لو كنا نريد النتيجة الأولى فقط ؛ هل سنحتاج إلى عمل وظيفة جديدة تمامًا؟ "

باستخدام وظيفة المولد ، لا نحتاج إلى إعادة كتابة نفس المنطق.
طباعة (التالي (find_char (s، p))) # 1

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

خاتمة

إذا كنت تنشئ وظيفة على الإطلاق ، فإن القيم تتراكم في قائمة مثل هذه.

def foo (bar):
  القيم = []
  بالنسبة لـ x in bar:
    # بعض المنطق
    values.append (خ)
  القيم المرجعة

فكر في إعادته إلى أداة تكرار مع فئة أو وظيفة منشئ أو تعبير منشئ مثل هذا:

def foo (bar):
  بالنسبة لـ x in bar:
    # بعض المنطق
    العائد س

الموارد والمصادر

ممثلي المخاطر

  • مولدات كهرباء
  • تعبيرات المولدات PEP
  • العائد من PEP

مقالات وخيوط

  • المكررات
  • إيتابل مقابل إيتاتور
  • وثائق المولدات
  • التكرارات مقابل المولدات
  • مولد التعبير مقابل وظيفة
  • مولدات تراجعية

تعريفات

  • متوقعة
  • مكرر
  • مولد كهرباء
  • مولد التكرار
  • مولد التعبير

نشر في الأصل على https://blog.dacio.dev/2019/05/03/python-iterators-and-generators/.