it-swarm.asia

قائمة الفهم مقابل lambda + filter

لقد صادفت أن أجد نفسي بحاجة إلى تصفية أساسية: لدي قائمة ويجب أن أقوم بترشيحها بواسطة سمة من العناصر.

بدا الرمز الخاص بي هكذا:

my_list = [x for x in my_list if x.attribute == value]

ولكن بعد ذلك فكرت ، أليس من الأفضل أن تكتب مثل هذا؟

my_list = filter(lambda x: x.attribute == value, my_list)

إنه أكثر قابلية للقراءة ، وإذا لزم الأمر للأداء ، يمكن إخراج lambda للحصول على شيء ما.

السؤال هو: هل هناك أي تحذيرات في استخدام الطريقة الثانية؟ أي اختلاف في الأداء؟ هل أفتقد Pythonic Way ™ تمامًا وهل ينبغي أن أقوم بذلك بطريقة أخرى (مثل استخدام itemgetter بدلاً من lambda)؟

726
Agos

إنه لأمر غريب كم يختلف الجمال بالنسبة لأشخاص مختلفين. أجد أن فهم القائمة أكثر وضوحًا من filter + lambda ، لكن استخدم أيهما تجده أسهل. ومع ذلك ، لا تتوقف عن إعطاء أسماء المتغيرات الخاصة بك المستخدمة بالفعل للوظائف الإضافية ، وهذا أمر محير. [ السؤال المستخدم في الأصل list كاسم متغير ولكن تم تحديثه استجابةً لهذه الإجابة. ]

هناك شيئان قد يؤديان إلى إبطاء استخدامك لـ filter.

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

الحمل الآخر الذي قد يتم تطبيقه هو أن lambda يتم فرضها للوصول إلى متغير نطاق (value). هذا هو أبطأ من الوصول إلى متغير محلي وفي بيثون 2.x فإن فهم القائمة يصل فقط إلى المتغيرات المحلية. إذا كنت تستخدم Python 3.x ، فسيتم تشغيل فهم القائمة في وظيفة منفصلة ، وبالتالي سيتم الوصول إلى value من خلال الإغلاق ولن ينطبق هذا الاختلاف.

الخيار الآخر الذي يجب مراعاته هو استخدام مولد بدلاً من فهم القائمة:

def filterbyvalue(seq, value):
   for el in seq:
       if el.attribute==value: yield el

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

499
Duncan

هذه قضية دينية إلى حد ما في بيثون. على الرغم مننظر Guido في إزالة map و filter و reduce من Python 3، كان هناك ما يكفي من ردود الفعل العنيفة التي في النهاية تم نقل reduce فقط من الإعدادات المدمجة إلى functools.reduce .

أنا شخصياً أجد أن قائمة الفهم أسهل في القراءة. إنه أكثر وضوحًا ما يحدث من التعبير [i for i in list if i.attribute == value] لأن كل السلوك موجود على السطح وليس داخل وظيفة المرشح.

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

أيضًا منذBDFLمطلوب filter انتقل من اللغة ، ومن المؤكد أن هذا يجعل قائمة فهم الكلمات تلقائيًا أكثر Pythonic ؛-)

205
Tendayi Mawushe

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

حالة الاستخدام المتكرر للغاية هي سحب قيم بعض X القابل للتكرار مع مراعاة القيمة P (x):

[x for x in X if P(x)]

لكن في بعض الأحيان تريد تطبيق بعض الوظائف على القيم أولاً:

[f(x) for x in X if P(f(x))]


كمثال محدد ، خذ بعين الاعتبار

primes_cubed = [x*x*x for x in range(1000) if prime(x)]

أعتقد أن هذا يبدو أفضل قليلاً من استخدام filter. ولكن الآن النظر

prime_cubes = [x*x*x for x in range(1000) if prime(x*x*x)]

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

prime_cubes = filter(prime, [x*x*x for x in range(1000)])
64
I. J. Kennedy

على الرغم من أن filter قد يكون "الطريقة الأسرع" ، فإن "الطريقة Pythonic" لن تكون مهتمة بمثل هذه الأشياء ما لم يكن الأداء حرجًا للغاية (وفي هذه الحالة لن تستخدم Python!).

28
Umang

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

lst_a = range(25) #arbitrary list
lst_b = [num for num in lst_a if num % 2 == 0]
lst_c = filter(lambda num: num % 2 == 0, lst_a)

تحتوي القائمتان b و c على نفس القيم ، وتم إكمالهما في نفس الوقت تقريبًا حيث كان filter () مكافئًا [x لـ x في y إذا z]. ومع ذلك ، في 3 ، هذا الرمز نفسه سيترك قائمة ج تحتوي على كائن عامل تصفية ، وليس قائمة تمت تصفيتها. لإنتاج نفس القيم في 3:

lst_a = range(25) #arbitrary list
lst_b = [num for num in lst_a if num % 2 == 0]
lst_c = list(filter(lambda num: num %2 == 0, lst_a))

المشكلة هي أن هذه القائمة () تأخذ تكرارا لأنها حجة ، وتقوم بإنشاء قائمة جديدة من تلك الوسيطة. والنتيجة هي أن استخدام عامل التصفية بهذه الطريقة في python 3 يستغرق ما يصل إلى ضعفي طول الطريقة [x لـ x في y if z] لأنك يجب أن تتكرر على الإخراج من filter () وكذلك القائمة الأصلية.

15
Jim50

الفارق المهم هو أن فهم القائمة سيعود list بينما يقوم عامل التصفية بإرجاع filter ، والذي لا يمكنك معالجته مثل list (أي: استدعاء len عليه ، والذي لا يعمل مع إرجاع filter).

أخذني التعلم الذاتي الخاص بي إلى قضية مماثلة.

إذا قيل ذلك ، إذا كانت هناك طريقة للحصول على list الناتج من filter ، تمامًا كما تفعل في .NET عندما تقوم بـ lst.Where(i => i.something()).ToList() ، فأنا أشعر بالفضول لمعرفة ذلك.

تحرير: هذا هو الحال بالنسبة لبيثون 3 ، وليس 2 (انظر المناقشة في التعليقات).

10
Adeynack

أجد الطريقة الثانية أكثر قابلية للقراءة. يخبرك بالضبط ما هو المقصود: تصفية القائمة.
PS: لا تستخدم "قائمة" كاسم متغير

9
unbeli

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

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

return [item.other_attribute for item in my_list if item.attribute==value]

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

7
thiruvenkadam

عموما filter يكون أسرع قليلاً في حالة استخدام دالة مدمجة.

أتوقع أن يكون فهم القائمة أسرع قليلاً في قضيتك

6
John La Rooy

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

في هذه الحالة ، أقرأ ملفًا ، وأتجاهل الأسطر الفارغة ، وعلق الأسطر ، وأي شيء بعد التعليق على السطر:

# Throw out blank lines and comments
with open('file.txt', 'r') as lines:        
    # From the inside out:
    #    [s.partition('#')[0].strip() for s in lines]... Throws out comments
    #   filter(lambda x: x!= '', [s.part... Filters out blank lines
    #  y for y in filter... Converts filter object to list
    file_contents = [y for y in filter(lambda x: x != '', [s.partition('#')[0].strip() for s in lines])]
5
rharder

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

cursor.execute("SELECT * FROM TABLE1;")
data_from_db = cursor.fetchall()
processed_data = filter(lambda s: 'abc' in s.field1 or s.StartTime >= start_date_time, data_from_db) 

إذا كنت تستخدم قائمة الفهم هنا ، فستتلقى الخطأ:

TypeError: type unhashable: 'list'

5
C.W.praen

استغرق الأمر بعض الوقت للتعرف على higher order functionsfilterوmap. لذلك اعتدت عليهم وأحببت فعلاً اسم filter لأنه كان من الواضح أنه يفلتر عن طريق الحفاظ على كل ما هو صدق وشعرت بالبرودة لأنني أعرف بعض مصطلحات functional programming.

ثم قرأت هذا المقطع (Fluent Python Book):

لا تزال وظائف الخريطة والتصفية مضمنة في Python 3 ، ولكن منذ إدخال فهم القائمة وضغط المولد السابق ، فإنها ليست مهمة. تقوم listcomp أو genexp بعمل الخريطة والمرشحات مجتمعة ، ولكن تكون أكثر قابلية للقراءة.

والآن أعتقد ، لماذا تهتم بمفهوم filter/map إذا استطعت تحقيقه باستخدام تعبيرات منتشرة بالفعل على نطاق واسع مثل فهم القائمة. علاوة على ذلك ، maps و filters نوع من الوظائف. في هذه الحالة ، أفضل استخدام Anonymous functions lambdas.

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

from timeit import Timer

timeMap = Timer(lambda: list(map(lambda x: x*x, range(10**7))))
print(timeMap.timeit(number=100))

timeListComp = Timer(lambda:[(lambda x: x*x) for x in range(10**7)])
print(timeListComp.timeit(number=100))

#Map:                 166.95695265199174
#List Comprehension   177.97208347299602
3
user1767754

الغريب في بيثون 3 ، أرى عامل تصفية أداء أسرع من فهم القائمة.

اعتقدت دائما أن فهم القائمة سيكون أكثر أداء. شيء من هذا القبيل: [اسم للاسم في brand_names_db إذا لم يكن الاسم لا شيء] فإن كود الشفرة الذي تم إنشاؤه أفضل قليلاً.

>>> def f1(seq):
...     return list(filter(None, seq))
>>> def f2(seq):
...     return [i for i in seq if i is not None]
>>> disassemble(f1.__code__)
2         0 LOAD_GLOBAL              0 (list)
          2 LOAD_GLOBAL              1 (filter)
          4 LOAD_CONST               0 (None)
          6 LOAD_FAST                0 (seq)
          8 CALL_FUNCTION            2
         10 CALL_FUNCTION            1
         12 RETURN_VALUE
>>> disassemble(f2.__code__)
2           0 LOAD_CONST               1 (<code object <listcomp> at 0x10cfcaa50, file "<stdin>", line 2>)
          2 LOAD_CONST               2 ('f2.<locals>.<listcomp>')
          4 MAKE_FUNCTION            0
          6 LOAD_FAST                0 (seq)
          8 GET_ITER
         10 CALL_FUNCTION            1
         12 RETURN_VALUE

لكنها في الواقع أبطأ:

   >>> timeit(stmt="f1(range(1000))", setup="from __main__ import f1,f2")
   21.177661532000116
   >>> timeit(stmt="f2(range(1000))", setup="from __main__ import f1,f2")
   42.233950221000214
0
Rod Senra