متى لا يكون الوقت المناسب لاستخدام مولدات بايثون؟
-
05-07-2019 - |
سؤال
وهذا هو بالأحرى عكس ذلك ما الذي يمكنك استخدام وظائف مولد بايثون؟:مولدات بايثون، وتعبيرات المولدات، و itertools
الوحدة هي بعض ميزاتي المفضلة في لغة بايثون هذه الأيام.إنها مفيدة بشكل خاص عند إعداد سلاسل العمليات لتنفيذها على كومة كبيرة من البيانات - غالبًا ما أستخدمها عند معالجة ملفات DSV.
إذن متى لا الوقت المناسب لاستخدام المولد، أو تعبير المولد، أو itertools
وظيفة؟
- متى يجب أن أفضّل
zip()
زيادةitertools.izip()
, ، أو range()
زيادةxrange()
, ، أو[x for x in foo]
زيادة(x for x in foo)
?
من الواضح أننا نحتاج في النهاية إلى "تحويل" المولد إلى بيانات فعلية، عادةً عن طريق إنشاء قائمة أو تكرارها باستخدام حلقة غير المولد.في بعض الأحيان نحتاج فقط إلى معرفة الطول.هذا ليس ما أطلبه.
نحن نستخدم المولدات حتى لا نقوم بتعيين قوائم جديدة في الذاكرة للبيانات المؤقتة.وهذا منطقي بشكل خاص بالنسبة لمجموعات البيانات الكبيرة.هل هذا منطقي بالنسبة لمجموعات البيانات الصغيرة أيضًا؟هل هناك مقايضة ملحوظة بين الذاكرة ووحدة المعالجة المركزية؟
أنا مهتم بشكل خاص بما إذا كان أي شخص قد قام ببعض التوصيف في هذا الشأن، في ضوء المناقشة المفتوحة حول هذا الموضوع أداء فهم القائمة مقابلالخريطة () والتصفية (). (رابط بديل)
المحلول
استخدم القائمة بدلاً من المولد عندما:
1) تحتاج إلى الوصول إلى البيانات عديد مرات (أيتخزين النتائج مؤقتًا بدلاً من إعادة حسابها):
for i in outer: # used once, okay to be a generator or return a list
for j in inner: # used multiple times, reusing a list is better
...
2) تحتاج دخول عشوائي (أو أي وصول بخلاف الترتيب التسلسلي الأمامي):
for i in reversed(data): ... # generators aren't reversible
s[i], s[j] = s[j], s[i] # generators aren't indexable
3) تحتاج إلى ينضم السلاسل (التي تتطلب تمريرين عبر البيانات):
s = ''.join(data) # lists are faster than generators in this use case
4) أنت تستخدم PyPy والتي في بعض الأحيان لا يمكنها تحسين كود المولد بقدر ما تستطيع من خلال استدعاءات الوظائف العادية ومعالجة القائمة.
نصائح أخرى
بشكل عام، لا تستخدم المولد عندما تحتاج إلى عمليات قائمة، مثل len() وreverse() وما إلى ذلك.
قد تكون هناك أيضًا أوقات لا ترغب فيها في التقييم البطيء (على سبيل المثال.لإجراء جميع العمليات الحسابية مقدمًا حتى تتمكن من تحرير أحد الموارد).في هذه الحالة، قد يكون تعبير القائمة أفضل.
الملف الشخصي، الملف الشخصي، الملف الشخصي.
يعد إنشاء ملف تعريف للكود الخاص بك هو الطريقة الوحيدة لمعرفة ما إذا كان ما تفعله له أي تأثير على الإطلاق.
معظم استخدامات xrange والمولدات وما إلى ذلك تتجاوز الحجم الثابت ومجموعات البيانات الصغيرة.فقط عندما تصل إلى مجموعات بيانات كبيرة، يحدث فرقًا حقيقيًا.النطاق () مقابل.يتعلق الأمر xrange() في الغالب بجعل التعليمات البرمجية تبدو قبيحة بعض الشيء، وعدم خسارة أي شيء، وربما اكتساب شيء ما.
الملف الشخصي، الملف الشخصي، الملف الشخصي.
كما ذكرت، "هذا منطقي بشكل خاص بالنسبة لمجموعات البيانات الكبيرة"، أعتقد أن هذا يجيب على سؤالك.
إذا لم تتمكن من الوصول إلى أي مستوى من الأداء، فلا يزال بإمكانك الالتزام بالقوائم والوظائف القياسية.ثم عندما تواجه مشكلات في الأداء، قم بإجراء التبديل.
كما ذكر @u0b34a0f6ae في التعليقات، فإن استخدام المولدات في البداية يمكن أن يسهل عليك التوسع في مجموعات بيانات أكبر.
فيما يتعلق بالأداء:في حالة استخدام psyco، يمكن أن تكون القوائم أسرع قليلاً من المولدات.في المثال أدناه، تكون القوائم أسرع بنسبة 50% تقريبًا عند استخدام psyco.full()
import psyco
import time
import cStringIO
def time_func(func):
"""The amount of time it requires func to run"""
start = time.clock()
func()
return time.clock() - start
def fizzbuzz(num):
"""That algorithm we all know and love"""
if not num % 3 and not num % 5:
return "%d fizz buzz" % num
elif not num % 3:
return "%d fizz" % num
elif not num % 5:
return "%d buzz" % num
return None
def with_list(num):
"""Try getting fizzbuzz with a list comprehension and range"""
out = cStringIO.StringIO()
for fibby in [fizzbuzz(x) for x in range(1, num) if fizzbuzz(x)]:
print >> out, fibby
return out.getvalue()
def with_genx(num):
"""Try getting fizzbuzz with generator expression and xrange"""
out = cStringIO.StringIO()
for fibby in (fizzbuzz(x) for x in xrange(1, num) if fizzbuzz(x)):
print >> out, fibby
return out.getvalue()
def main():
"""
Test speed of generator expressions versus list comprehensions,
with and without psyco.
"""
#our variables
nums = [10000, 100000]
funcs = [with_list, with_genx]
# try without psyco 1st
print "without psyco"
for num in nums:
print " number:", num
for func in funcs:
print func.__name__, time_func(lambda : func(num)), "seconds"
print
# now with psyco
print "with psyco"
psyco.full()
for num in nums:
print " number:", num
for func in funcs:
print func.__name__, time_func(lambda : func(num)), "seconds"
print
if __name__ == "__main__":
main()
نتائج:
without psyco
number: 10000
with_list 0.0519102208309 seconds
with_genx 0.0535933367509 seconds
number: 100000
with_list 0.542204280744 seconds
with_genx 0.557837353115 seconds
with psyco
number: 10000
with_list 0.0286369007033 seconds
with_genx 0.0513424889137 seconds
number: 100000
with_list 0.335414877839 seconds
with_genx 0.580363490491 seconds
فيما يتعلق بالأداء، لا أستطيع التفكير في أي وقت قد ترغب فيه في استخدام قائمة فوق المولد.
لم أجد أبدًا موقفًا قد تعيق فيه المولدات ما تحاول القيام به.ومع ذلك، هناك الكثير من الحالات التي لن يساعدك فيها استخدام المولدات أكثر من عدم استخدامها.
على سبيل المثال:
sorted(xrange(5))
لا يقدم أي تحسن على:
sorted(range(5))
يجب أن تفضل فهم القائمة إذا كنت بحاجة إلى الاحتفاظ بالقيم الموجودة لشيء آخر لاحقًا وكان حجم مجموعتك ليس كبيرًا جدًا.
على سبيل المثال:أنت تقوم بإنشاء قائمة ستقوم بتكرارها عدة مرات لاحقًا في برنامجك.
إلى حد ما، يمكنك التفكير في المولدات كبديل للتكرار (الحلقات) مقابل التكرار (الحلقات).قائمة الفهم كنوع من تهيئة بنية البيانات.إذا كنت تريد الاحتفاظ ببنية البيانات، فاستخدم فهم القائمة.