سؤال

هل هناك سبب لتفضيل استخدام map() على قائمة الفهم أو العكس؟ هو إما منهم بشكل عام أكثر كفاءة أو تعتبر أكثر ثبات أكثر من الآخر؟

هل كانت مفيدة؟

المحلول

map قد يكون بشكل أسرع مجهري في بعض الحالات (عندما لا تصنع Lambda لهذا الغرض، ولكن باستخدام نفس الوظيفة في الخريطة و LARDCOMP). قائمة الفهم قد تكون أسرع في حالات أخرى وأكثر من ذلك (ليس كل) Pythonistas يعتبرونهم أكثر مباشرة وأكثر وضوحا.

مثال على ميزة السرعة الصغيرة للخريطة عند استخدام نفس الوظيفة بالضبط:

$ python -mtimeit -s'xs=range(10)' 'map(hex, xs)'
100000 loops, best of 3: 4.86 usec per loop
$ python -mtimeit -s'xs=range(10)' '[hex(x) for x in xs]'
100000 loops, best of 3: 5.58 usec per loop

مثال على كيفية عكس مقارنة الأداء بالكامل عندما تحتاج الخريطة إلى Lambda:

$ python -mtimeit -s'xs=range(10)' 'map(lambda x: x+2, xs)'
100000 loops, best of 3: 4.24 usec per loop
$ python -mtimeit -s'xs=range(10)' '[x+2 for x in xs]'
100000 loops, best of 3: 2.32 usec per loop

نصائح أخرى

حالات

  • حالة شائعة: دائما تقريبا، سوف ترغب في استخدام فهم قائمة في بيثون لأنه سيكون أكثر وضوحا ما تفعله للمبرمجين المبتدئين الذين يقرؤون الرمز الخاص بك. (لا ينطبق هذا على لغات أخرى، حيث قد تكون التعابير الأخرى قد تنطبق.) سيكون الأمر أكثر وضوحا ما تفعله لمبرمجي Python، لأن قائمة الفهم هي معيار De-Facto في Python للتكرار؛ هم انهم متوقع.
  • حالة أقل شائعة: ولكن إذا كنت لديك بالفعل وظيفة محددة, ، غالبا ما تكون معقولة للاستخدام map, على الرغم من أنها تعتبر "Unpythonic". علي سبيل المثال، map(sum, myLists) هو أكثر أناقة / مقتومة من [sum(x) for x in myLists]. وبعد تكسب أناقة عدم الاضطرار إلى تشكيل متغير وهمية (على سبيل المثال sum(x) for x... أو sum(_) for _... أو sum(readableName) for readableName...) التي لديك لكتابة مرتين، فقط للتكرار. نفس الحجة تحمل ل filter و reduce وأي شيء من itertools الوحدة النمطية: إذا كان لديك بالفعل وظيفة مفيدة، فيمكنك المضي قدما والقيام ببعض البرمجة الوظيفية. هذا يكسب قابلية القراءة في بعض المواقف، ويخسرها في غيرها (مثل مبرمج المبتدئين، وسيطات متعددة) ... ولكن قابلية قراءة التعليمات البرمجية تعتمد كبير على تعليقاتك على أي حال.
  • على الاغلب لا: قد ترغب في استخدام map تعمل كدالة مجردة نقية أثناء القيام برمجة وظيفية، حيث يمكنك رسم الخرائط map, ، أو الكريات map, ، أو الاستفادة من الحديث عن map كوظيفة. في Haskell على سبيل المثال، تدعى واجهة Functor fmap تعميم رسم الخرائط عبر أي بنية بيانات. هذا غير شائع جدا في Python لأن Python Grammar يجبرك على استخدام نمط المولد للحديث عن التكرار؛ لا يمكنك تعميمها بسهولة. (هذا هو في بعض الأحيان جيدة وأحيانا سيئة.) ربما يمكنك التوصل إلى أمثلة بيثون النادرة حيث map(f, *lists) هو شيء معقول القيام به. أقرب مثال يمكنني التوصل إليه سيكون sumEach = partial(map,sum), ، وهو بطانة واحدة تعادل تقريبا:

def sumEach(myLists):
    return [sum(_) for _ in myLists]
  • فقط باستخدام أ for-حلقه: يمكنك أيضا بالطبع فقط استخدم لحلقة حلقة. على الرغم من أنه ليس أنيقا من وجهة نظر برمجة وظيفية، إلا أن المتغيرات غير المحلية في بعض الأحيان تجعل التعليمات البرمجية أكثر وضوحا بلغات البرمجة الحتمية مثل Python، لأن الأشخاص يستخدمون الناس في قراءة الكود بهذه الطريقة. بالنسبة للحلقات أيضا، عموما، والأكثر كفاءة عندما تقوم فقط بعملية معقدة لا تقوم ببناء قائمة مثل قائمة الأمتعة والخريطة محسنة (مثل تلخيص، أو صنع شجرة، وما إلى ذلك) - على الأقل فعالة من حيث الذاكرة (ليس بالضرورة من حيث الوقت، حيث أتوقع في أسوأ الأحوال عامل مستمر، باستثناء بعض الفوارق النادرة من جمع القمامة المرضية).

"الثدي"

أنا أكره كلمة "Pythonic" لأنني لا أجد أن بيثونيك أنيق دائما في عيني. مع ذلك، map و filter وظائف مماثلة (مثل مفيدة جدا itertools الوحدة النمطية) ربما تعتبر Unpythonic من حيث النمط.

الكسل

من حيث الكفاءة، مثل بنيات البرمجة الوظيفية، الخريطة يمكن أن تكون كسول, ، وفي الواقع كسول في بيثون. هذا يعني أنه يمكنك القيام بذلك (في Python3.) لن ينفد جهاز الكمبيوتر الخاص بك من الذاكرة وفقدان جميع بياناتك غير المحفوظة:

>>> map(str, range(10**100))
<map object at 0x2201d50>

حاول القيام بذلك مع فهم قائمة:

>>> [str(n) for n in range(10**100)]
# DO NOT TRY THIS AT HOME OR YOU WILL BE SAD #

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

>>> (str(n) for n in range(10**100))
<generator object <genexpr> at 0xacbdef>

يمكنك التفكير أساسا في [...] بناء الجملة كما يمر في تعبير المولد إلى منشئ القائمة، مثل list(x for x in range(5)).

مثال على مثال موجز

from operator import neg
print({x:x**2 for x in map(neg,range(5))})

print({x:x**2 for x in [-y for y in range(5)]})

print({x:x**2 for x in (-y for y in range(5))})

قائمة الفهم غير كسول، لذلك قد تتطلب المزيد من الذاكرة (إلا إذا كنت تستخدم فهم المولدات). الأقواس المربعة [...] غالبا ما تجعل الأمور واضحة، خاصة عندما تكون في فوضى من الأقواس. من ناحية أخرى، في بعض الأحيان ينتهي بك الأمر إلى الغرف مثل الكتابة [x for x in.... وبعد طالما كنت تبقي متغيرات للماء القصيرة قصيرة، فإن قائمة الفهم عادة ما تكون أكثر وضوحا إذا لم تفعل المسافة البادئة رمزك. ولكن يمكنك دائما المسافة البادئة رمزك.

print(
    {x:x**2 for x in (-y for y in range(5))}
)

أو كسر الأشياء:

rangeNeg5 = (-y for y in range(5))
print(
    {x:x**2 for x in rangeNeg5}
)

مقارنة الكفاءة لبيدثون 3

map الآن كسول:

% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=map(f,xs)'
1000000 loops, best of 3: 0.336 usec per loop            ^^^^^^^^^

لذلك إذا لم تستخدم جميع بياناتك، أو لا تعرف في وقت مبكر مقدار البيانات التي تحتاجها، map في Python3 (وتعبيرات المولد في Python2 أو Python3) سوف تتجنب حساب قيمها حتى اللحظة الأخيرة اللازمة. عادة ما يكون هذا عادة ما يفوق أي نفقات من استخدام map. وبعد الجانب السلبي هو أن هذا محدود جدا في بيثون بدلا من اللغات الأكثر وظيفية: يمكنك فقط الحصول على هذه الفائدة إذا كنت تصل إلى بياناتك اليسرى "بالترتيب"، لأن تعبيرات مولد الثعبان لا يمكن فقط تقييم النظام x[0], x[1], x[2], ....

ومع ذلك دعونا نقول أن لدينا وظيفة مسبقا f نحن نرغب في map, ونحن نتجاهل كسل map من خلال إجبار التقييم على الفور list(...). وبعد نحصل على بعض النتائج المثيرة للاهتمام للغاية:

% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=list(map(f,xs))'                                                                                                                                                
10000 loops, best of 3: 165/124/135 usec per loop        ^^^^^^^^^^^^^^^
                    for list(<map object>)

% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=[f(x) for x in xs]'                                                                                                                                      
10000 loops, best of 3: 181/118/123 usec per loop        ^^^^^^^^^^^^^^^^^^
                    for list(<generator>), probably optimized

% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=list(f(x) for x in xs)'                                                                                                                                    
1000 loops, best of 3: 215/150/150 usec per loop         ^^^^^^^^^^^^^^^^^^^^^^
                    for list(<generator>)

في النتائج في النموذج AAA / BBB / CCC حيث تم تنفيذ A مع محطة عمل Intel Circa-2010 مع Python 3.؟.؟ مع أجهزة مختلفة للغاية. يبدو أن النتيجة هي أن المخططات وقائمة الفهم قابلة للمقارنة في الأداء، والتي تتأثر بشدة بالعوامل العشوائية الأخرى. الشيء الوحيد الذي يمكننا أن نقوله يبدو أنه، من الغريب، بينما نتوقع قائمة الفهم [...] لأداء أفضل من تعبيرات المولد (...), map هو أيضا أكثر كفاءة أن تعبيرات المولدات (على افتراض أن جميع القيم يتم تقييمها / المستخدمة).

من المهم أن ندرك أن هذه الاختبارات تفترض وظيفة بسيطة للغاية (وظيفة الهوية)؛ ومع ذلك، هذا جيد لأنه إذا كانت الوظيفة معقدة، فإن الأداء العلوي سيكون ضئيلا مقارنة بالعوامل الأخرى في البرنامج. (قد لا يزال من المثير للاهتمام اختبار الأشياء البسيطة الأخرى مثل f=lambda x:x+x)

إذا كنت ماهرة في قراءة جملة بيثون، فيمكنك استخدام dis الوحدة لمعرفة ما إذا كان هذا هو في الواقع ما يجري وراء الكواليس:

>>> listComp = compile('[f(x) for x in xs]', 'listComp', 'eval')
>>> dis.dis(listComp)
  1           0 LOAD_CONST               0 (<code object <listcomp> at 0x2511a48, file "listComp", line 1>) 
              3 MAKE_FUNCTION            0 
              6 LOAD_NAME                0 (xs) 
              9 GET_ITER             
             10 CALL_FUNCTION            1 
             13 RETURN_VALUE         
>>> listComp.co_consts
(<code object <listcomp> at 0x2511a48, file "listComp", line 1>,)
>>> dis.dis(listComp.co_consts[0])
  1           0 BUILD_LIST               0 
              3 LOAD_FAST                0 (.0) 
        >>    6 FOR_ITER                18 (to 27) 
              9 STORE_FAST               1 (x) 
             12 LOAD_GLOBAL              0 (f) 
             15 LOAD_FAST                1 (x) 
             18 CALL_FUNCTION            1 
             21 LIST_APPEND              2 
             24 JUMP_ABSOLUTE            6 
        >>   27 RETURN_VALUE

 

>>> listComp2 = compile('list(f(x) for x in xs)', 'listComp2', 'eval')
>>> dis.dis(listComp2)
  1           0 LOAD_NAME                0 (list) 
              3 LOAD_CONST               0 (<code object <genexpr> at 0x255bc68, file "listComp2", line 1>) 
              6 MAKE_FUNCTION            0 
              9 LOAD_NAME                1 (xs) 
             12 GET_ITER             
             13 CALL_FUNCTION            1 
             16 CALL_FUNCTION            1 
             19 RETURN_VALUE         
>>> listComp2.co_consts
(<code object <genexpr> at 0x255bc68, file "listComp2", line 1>,)
>>> dis.dis(listComp2.co_consts[0])
  1           0 LOAD_FAST                0 (.0) 
        >>    3 FOR_ITER                17 (to 23) 
              6 STORE_FAST               1 (x) 
              9 LOAD_GLOBAL              0 (f) 
             12 LOAD_FAST                1 (x) 
             15 CALL_FUNCTION            1 
             18 YIELD_VALUE          
             19 POP_TOP              
             20 JUMP_ABSOLUTE            3 
        >>   23 LOAD_CONST               0 (None) 
             26 RETURN_VALUE

 

>>> evalledMap = compile('list(map(f,xs))', 'evalledMap', 'eval')
>>> dis.dis(evalledMap)
  1           0 LOAD_NAME                0 (list) 
              3 LOAD_NAME                1 (map) 
              6 LOAD_NAME                2 (f) 
              9 LOAD_NAME                3 (xs) 
             12 CALL_FUNCTION            2 
             15 CALL_FUNCTION            1 
             18 RETURN_VALUE 

يبدو أنه من الأفضل استخدامه [...] بناء جملة من list(...). وبعد للأسف map الفئة مبهمة بعض الشيء للتفكيك، ولكن يمكننا أن نجري مستحقا مع اختبار السرعة لدينا.

بيثون 2: يجب عليك استخدام map و filter بدلا من قائمة الفهم.

أ هدف السبب في أنه يجب عليك تفضيلها على الرغم من أنهم ليسوا "بيثوني" هو:
أنها تتطلب وظائف / لامبدااس كحجج، والتي تقديم نطاق جديد.

لقد عدت بهذا أكثر من مرة:

for x, y in somePoints:
    # (several lines of code here)
    squared = [x ** 2 for x in numbers]
    # Oops, x was silently overwritten!

ولكن إذا قلت بدلا من ذلك:

for x, y in somePoints:
    # (several lines of code here)
    squared = map(lambda x: x ** 2, numbers)

ثم كل شيء سيكون بخير.

قد أقول أنني كنت سخيفة لاستخدام نفس الاسم المتغير في نفس النطاق.

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

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

خاتمة:

يستخدم map و filter. وبعد أنها تمنع الأخطاء ذات الصلة بالنطاق الصلب.

ملاحظة جانبية:

لا تنسى النظر في استخدام imap و ifilter (في itertools) إذا كانوا مناسبة لموقفك!

فعلا، map وقائمة الفهم تتصرف بشكل مختلف تماما في لغة بيثون 3. إلقاء نظرة على برنامج Python 3 التالي:

def square(x):
    return x*x
squares = map(square, [1, 2, 3])
print(list(squares))
print(list(squares))

قد تتوقع ذلك طباعة الخط [1، 4، 9] مرتين، ولكن بدلا من ذلك يطبع [1، 4، 9] تليها []. أول مرة تنظر فيها squares يبدو أن تتصرف بمثابة تسلسل من ثلاثة عناصر، ولكن المرة الثانية كواحد فارغ.

في بيثون 2 اللغة map إرجاع قائمة قديمة عادي، تماما مثل قائمة الفهم القيام به في اللغتين. القصير هو أن قيمة العودة لل map في بيثون 3 (و imap في بيثون 2) ليست قائمة - إنه ميكروري!

يتم استهلاك العناصر عند التكرار فوق جهاز كمتكر معكوم على عكس عندما تتكرر في قائمة. هذا هو السبب squares يبدو فارغا في الماضي print(list(squares)) خط.

كي تختصر:

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

أجد قائمة الفهم أكثر تعبيرا عن ما أحاول القيام به map - كلاهما يحصل عليه، لكن السابق يحفظ الحمل العقلي للمحاولة فهم ما يمكن أن يكون معقد lambda التعبير.

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

هنا حالة واحدة ممكن:

map(lambda op1,op2: op1*op2, list1, list2)

عكس:

[op1*op2 for op1,op2 in zip(list1,list2)]

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

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

لذلك منذ بيثون 3، map() هو جهاز كمتكر، يجب أن تضع في اعتبارك ما تحتاجه: كمير أو list هدف.

كما alexmartelli بالفعل المذكورة, map() أسرع من الفهم القائمة فقط إذا كنت لا تستخدم lambda وظيفة.

سأقدم لك بعض مقارنات الوقت.

بيثون 3.5.2 و Cpython
لقد استخدمت كوكب المشتري دفتر وخاصة %timeit المدمج في الأمر السحري
قياسات: S == 1000 MS == 1000 * 1000 μs = 1000 * 1000 * 1000 NS

اقامة:

x_list = [(i, i+1, i+2, i*2, i-9) for i in range(1000)]
i_list = list(range(1000))

وظيفة مدمجة:

%timeit map(sum, x_list)  # creating iterator object
# Output: The slowest run took 9.91 times longer than the fastest. 
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 277 ns per loop

%timeit list(map(sum, x_list))  # creating list with map
# Output: 1000 loops, best of 3: 214 µs per loop

%timeit [sum(x) for x in x_list]  # creating list with list comprehension
# Output: 1000 loops, best of 3: 290 µs per loop

lambda وظيفة:

%timeit map(lambda i: i+1, i_list)
# Output: The slowest run took 8.64 times longer than the fastest. 
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 325 ns per loop

%timeit list(map(lambda i: i+1, i_list))
# Output: 1000 loops, best of 3: 183 µs per loop

%timeit [i+1 for i in i_list]
# Output: 10000 loops, best of 3: 84.2 µs per loop

هناك أيضا شيء مثل التعبير المولد، انظر PEP-0289.. وبعد لذلك اعتقدت أنه سيكون من المفيد إضافته إلى المقارنة

%timeit (sum(i) for i in x_list)
# Output: The slowest run took 6.66 times longer than the fastest. 
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 495 ns per loop

%timeit list((sum(x) for x in x_list))
# Output: 1000 loops, best of 3: 319 µs per loop

%timeit (i+1 for i in i_list)
# Output: The slowest run took 6.83 times longer than the fastest. 
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 506 ns per loop

%timeit list((i+1 for i in i_list))
# Output: 10000 loops, best of 3: 125 µs per loop

انت تحتاج list هدف:

استخدام فهم القائمة إذا كانت وظيفة مخصصة، واستخدام list(map()) إذا كانت هناك وظيفة مدمجة

لا تحتاج list كائن، تحتاج فقط قابلة للتوجيهية:

دائما يستخدم map()!

أعتبر أن الطريقة الأكثر ثباتا هي استخدام فهم قائمة بدلا من map و filter. وبعد والسبب هو أن قائمة الفهمية أكثر وضوحا من map و filter.

In [1]: odd_cubes = [x ** 3 for x in range(10) if x % 2 == 1] # using a list comprehension

In [2]: odd_cubes_alt = list(map(lambda x: x ** 3, filter(lambda x: x % 2 == 1, range(10)))) # using map and filter

In [3]: odd_cubes == odd_cubes_alt
Out[3]: True

كما ترى، لا يتطلب الفهم إضافي lambda التعبيرات كما map الاحتياجات. علاوة على ذلك، يسمح الفهم أيضا بالتصفية بسهولة، في حين map يستوجب filter للسماح بالتصفية.

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

# map_lambda
list(map(lambda x: x.add(), vals))

# map_operator
from operator import methodcaller
list(map(methodcaller("add"), vals))

# map_comprehension
[x.add() for x in vals]

نظرت إلى القوائم (تخزينها في المتغير vals) كل من أعداد صحيحة (بيثون int) وأرقام النقطة العائمة (بيثون float) لزيادة أحجام قائمة. الطبلة الدمية التالية DummyNum يعتبر:

class DummyNum(object):
    """Dummy class"""
    __slots__ = 'n',

    def __init__(self, n):
        self.n = n

    def add(self):
        self.n += 5

على وجه التحديد، add طريقة. ال __slots__ السمة هي تحسين بسيط في Python لتحديد الذاكرة الإجمالية التي تحتاجها الطبقة (السمات)، تقليل حجم الذاكرة. وهنا المؤامرات الناتجة.

Performance of mapping Python object methods

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

يزور هذا pastebin بالنسبة للمصدر المستخدم لتوليد المؤامرة والبيانات.

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