#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
заставляет вас обернуть ее в alambda
, чтобы иметь возможность передать ее в качестве аргумента, что делает код немного сложнее, чем эквивалентный генератор.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