سؤال

متى يجب عليك استخدام تعبيرات المولد ومتى يجب عليك استخدام فهم القائمة في بايثون؟

# Generator expression
(x*2 for x in range(256))

# List comprehension
[x*2 for x in range(256)]
هل كانت مفيدة؟

المحلول

إجابة جون جيدة (تلك القائمة تكون أفضل عندما تريد تكرار شيء ما عدة مرات).ومع ذلك، تجدر الإشارة أيضًا إلى أنه يجب عليك استخدام القائمة إذا كنت تريد استخدام أي من طرق القائمة.على سبيل المثال، لن تعمل التعليمات البرمجية التالية:

def gen():
    return (something for something in get_some_stuff())

print gen()[:2]     # generators don't support indexing or slicing
print [5,6] + gen() # generators can't be added to lists

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

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

نصائح أخرى

التكرار على تعبير المولد أو ال قائمة الفهم سوف تفعل نفس الشيء.ومع ذلك، قائمة الفهم سيقوم بإنشاء القائمة بأكملها في الذاكرة أولاً بينما تعبير المولد سيقوم بإنشاء العناصر بسرعة، حتى تتمكن من استخدامها لتسلسلات كبيرة جدًا (وأيضًا لا نهائية!).

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

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

تخيل أن لديك ملف سجل بسعة 2 تيرابايت يسمى "hugefile.txt"، وتريد المحتوى والطول لجميع الأسطر التي تبدأ بالكلمة "ENTRY".

لذا حاول البدء بكتابة قائمة الفهم:

logfile = open("hugefile.txt","r")
entry_lines = [(line,len(line)) for line in logfile if line.startswith("ENTRY")]

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

لذلك يمكننا بدلاً من ذلك استخدام المولد لتطبيق "مرشح" على المحتوى الخاص بنا.لا تتم قراءة أي بيانات فعليًا حتى نبدأ في تكرار النتيجة.

logfile = open("hugefile.txt","r")
entry_lines = ((line,len(line)) for line in logfile if line.startswith("ENTRY"))

ولم تتم قراءة حتى سطر واحد من ملفنا حتى الآن.في الواقع، لنفترض أننا نريد تصفية نتائجنا بشكل أكبر:

long_entries = ((line,length) for (line,length) in entry_lines if length > 80)

لم تتم قراءة أي شيء حتى الآن، لكننا حددنا الآن مولدين سيعملان على بياناتنا كما نرغب.

لنكتب خطوطنا التي تمت تصفيتها إلى ملف آخر:

outfile = open("filtered.txt","a")
for entry,length in long_entries:
    outfile.write(entry)

الآن نقرأ ملف الإدخال.كخاصتنا for تستمر الحلقة في طلب خطوط إضافية، و long_entries يتطلب المولد خطوطًا من entry_lines المولد، ويعيد فقط تلك التي يزيد طولها عن 80 حرفًا.وبدورها ال entry_lines خطوط طلبات المولد (التي تمت تصفيتها كما هو محدد) من logfile المكرر، والذي بدوره يقرأ الملف.

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

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

على سبيل المثال:

sum(x*2 for x in xrange(256))

dict( ((k, some_func(k) for k in some_list_of_keys) )

الميزة هنا هي أن القائمة لم يتم إنشاؤها بالكامل، وبالتالي يتم استخدام القليل من الذاكرة (ويجب أيضًا أن تكون أسرع)

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

على سبيل المثال:

reversed( [x*2 for x in xrange(256)] )

عند إنشاء مولد من كائن قابل للتغيير (مثل القائمة)، انتبه إلى أنه سيتم تقييم المولد بناءً على حالة القائمة في وقت استخدام المولد، وليس في وقت إنشاء المولد:

>>> mylist = ["a", "b", "c"]
>>> gen = (elem + "1" for elem in mylist)
>>> mylist.clear()
>>> for x in gen: print (x)
# nothing

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

أنا أستخدم وحدة Hadoop اللحم المفروم.أعتقد أن هذا مثال رائع يجب مراعاته:

import mincemeat

def mapfn(k,v):
    for w in v:
        yield 'sum',w
        #yield 'count',1


def reducefn(k,v): 
    r1=sum(v)
    r2=len(v)
    print r2
    m=r1/r2
    std=0
    for i in range(r2):
       std+=pow(abs(v[i]-m),2)  
    res=pow((std/r2),0.5)
    return r1,r2,res

هنا يحصل المولد على أرقام من ملف نصي (يصل حجمه إلى 15 جيجابايت) ويطبق عمليات حسابية بسيطة على تلك الأرقام باستخدام تقنية تقليل الخريطة الخاصة بـ Hadoop.إذا لم أستخدم دالة العائد، ولكن بدلاً من ذلك استخدمت فهم القائمة، لكان الأمر قد استغرق وقتًا أطول بكثير لحساب المجاميع والمتوسط ​​(ناهيك عن تعقيد المساحة).

يعد Hadoop مثالًا رائعًا لاستخدام جميع مزايا المولدات.

في بعض الأحيان يمكنك الابتعاد عن قمزة وظيفة من com.itertools, ، فهو يُرجع عدة مكررات لنفس المولد والتي يمكن استخدامها بشكل مستقل.

ماذا عن استخدام [(exp for x in iter)] للاستفادة من كليهما.الأداء من خلال فهم المولد بالإضافة إلى طرق القائمة

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top