python filter - قائمة الفهم مقابل امدا+فلتر



lambda function (13)

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

بدا شفرتي كالتالي:

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

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

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

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

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


Answers

نظرًا لأن أي اختلاف في السرعة لا بد أن يكون قليلًا ، فإن استخدام الفلاتر أو فهم المكونات يرجع إلى مسألة ذوق. بشكل عام ، أميل إلى استخدام الفهم (الذي يبدو أنه موافق على معظم الإجابات الأخرى هنا) ، ولكن هناك حالة واحدة أفضل فيها 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 وفقًا للقيمة المحسوبة بعد. إلى جانب مسألة حساب المكعب مرتين (تخيل حساب أكثر تكلفة) ، هناك مسألة كتابة التعبير مرتين ، منتهكة الجمالية DRY . في هذه الحالة سأكون عرضة للاستخدام

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

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

جلبتني تعلّمي الذاتي إلى بعض القضايا المماثلة.

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

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


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

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

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

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


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


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


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

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


إحدى مزايا إصدار lambda هي أنه يمكنك التقاط متغيرات إضافية إذا كانت حالتك تعتمد عليها:

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

لان

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

سيتم إرجاع name 'value' is not defined لأنه لا يمكن الوصول إلى قيمة المتغير في الشرط.

كلا الإصدارين تعمل بشكل جيد مع الاختيار ضد varaibale.


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

هناك شيئان قد يبطئان استخدامك filter .

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

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

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

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

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


بلدي اتخاذها

def filter_list(list, key, value, limit=None):
    return [i for i in list if i[key] == value][:limit]

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

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

لا تزال وظائف الخريطة والمرشحات مدمجة في بايثون 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

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

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

# 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])]

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

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: نوع قابل للإزالة: 'list'


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

class slist(list):
    @property
    def length(self):
        return len(self)

يمكنك استخدامه كما يلي:

>>> l = slist(range(10))
>>> l.length
10
>>> print l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

وبشكل أساسي ، تتطابق تمامًا مع كائن قائمة ، مع ميزة إضافية تتمثل في امتلاك خاصية ذات length OOP.

كما هو الحال دائما، قد تختلف المسافة المقطوعة.





python list functional-programming filter lambda