Сравнение выражения генератора с другим из «Изучение Python.. от Марка Лутца»

#python #python-3.x #generator

Вопрос:

Читая «Изучение Python Марка Лутца», я наткнулся на главу о функции генератора и выражении. Все было хорошо, но я застрял за этой чертой:

«Например, выражения генератора часто эквивалентны вызовам 3.X map….»

 list(map(abs,(-1,-2,3,4))
list(abs(x) for x in (-1,-2,3,4))
 

Я знаю, что происходит в двух строках выше. Но я не понимаю, как решить, какой метод лучше? Ниже приведен еще один пример:

 list(map(lambda x: x*2, (1,2,3,4))
list(x*2 for x in (1,2,3,4))
 

Какой из них лучше в данном случае?

Далее текст звучит так «..Однако, как и понимание списка, выражения генератора могут быть похожи на код, когда применяемая операция не является вызовом функции.»

Вот, может кто-нибудь, пожалуйста, объяснить мне значение этой строки?

Комментарии:

1. «похоже» или «проще»? Предложение имеет смысл, если слово «проще» — если операция не является функцией, использование map заставляет вас обернуть ее в a lambda , чтобы иметь возможность передать ее в качестве аргумента, что делает код немного сложнее, чем эквивалентный генератор.

2. Эти примеры предназначены для иллюстрации того , что там, где операция является вызовом abs , map версия короче. Если операция представляет собой умножение ( «не вызов функции» ), map версия длиннее. Что лучше — это вопрос мнения.

3. На самом деле, я не мог понять смысла «… когда операция не является ВЫЗОВОМ ФУНКЦИИ»

4. abs(x) является вызовом функции, x*2 не является.

Ответ №1:

Какой из них лучше в данном случае?

Исторически понимание списка было введено в качестве более краткой альтернативы использованию map или filter , PEP 202 — Понимание списка Обоснование этой функции

Понимание списков обеспечивает более краткий способ создания списков в ситуациях, когда map() filter() в настоящее время будут использоваться и и/или вложенные циклы.

Отказ от ответственности: map() и filter() здесь должно быть понято Python-2.x НЕ в том смысле Python-3.x , в каком этот документ датирован июлем 2000 года.

Ответ №2:

Но я не понимаю, как решить, какой метод лучше?

Лучше, конечно, несколько субъективно. Некоторые люди предпочитают компактность map() . Другие предпочитают ясность выражения генератора, потому for что оно выскакивает и дает понять, что есть итерация.

С точки зрения производительности, map() как правило, быстрее, потому что большая часть работы выполняется внутри интерпретатора (в случае CPython это означает скомпилированный код на C) и не требует выполнения большого количества интерпретируемого байт-кода. Примечание: это в основном относится к вашей первой паре примеров. Использование lambda , очевидно, означает выполнение интерпретируемого кода.

В качестве краткого примера производительности:

 % python3 -m timeit "list(map(abs, (-1, -2, 3, 4)))"
500000 loops, best of 5: 587 nsec per loop

% python3 -m timeit "list(abs(x) for x in (-1, -2, 3, 4))"
500000 loops, best of 5: 870 nsec per loop
 

Генераторное выражение занимает на 50% больше времени, чем map() .

Вы можете видеть в разборке операторов, которая map() требует менее интерпретируемого кода:

 >>> dis.dis("list(map(abs, (-1, -2, 3, 4)))")
  1           0 LOAD_NAME                0 (list)
              2 LOAD_NAME                1 (map)
              4 LOAD_NAME                2 (abs)
              6 LOAD_CONST               0 ((-1, -2, 3, 4))
              8 CALL_FUNCTION            2
             10 CALL_FUNCTION            1
             12 RETURN_VALUE
 

по сравнению с выражением генератора:

 >>> dis.dis("list(abs(x) for x in (-1, -2, 3, 4))")
  1           0 LOAD_NAME                0 (list)
              2 LOAD_CONST               0 (<code object <genexpr> at 0x6fffffe0df50, file "<dis>", line 1>)
              4 LOAD_CONST               1 ('<genexpr>')
              6 MAKE_FUNCTION            0
              8 LOAD_CONST               2 ((-1, -2, 3, 4))
             10 GET_ITER
             12 CALL_FUNCTION            1
             14 CALL_FUNCTION            1
             16 RETURN_VALUE

Disassembly of <code object <genexpr> at 0x6fffffe0df50, file "<dis>", line 1>:
  1           0 LOAD_FAST                0 (.0)
        >>    2 FOR_ITER                14 (to 18)
              4 STORE_FAST               1 (x)
              6 LOAD_GLOBAL              0 (abs)
              8 LOAD_FAST                1 (x)
             10 CALL_FUNCTION            1
             12 YIELD_VALUE
             14 POP_TOP
             16 JUMP_ABSOLUTE            2
        >>   18 LOAD_CONST               0 (None)
             20 RETURN_VALUE