Вызывается ли s.strip()
дважды?
Если вы используете CPython, да. Вы можете попробовать dis
модуль для проверки байт-кода.
def f():
return [s.strip() for s in ['a', 'b ', ' ', ' c'] if s.strip()]
С Python 3.7 вы получите байт-код функции и понимание списка:
>>> import dis
>>> dis.dis(f)
2 0 LOAD_CONST 1 (<code object <listcomp> at 0x7f6ddc8ce8a0, file "temp.py", line 2>)
2 LOAD_CONST 2 ('f.<locals>.<listcomp>')
4 MAKE_FUNCTION 0
6 LOAD_CONST 3 (('a', 'b ', ' ', ' c'))
8 GET_ITER
10 CALL_FUNCTION 1
12 RETURN_VALUE
Disassembly of <code object <listcomp> at 0x7f6ddc8ce8a0, file "temp.py", line 2>:
2 0 BUILD_LIST 0
2 LOAD_FAST 0 (.0)
>> 4 FOR_ITER 20 (to 26)
6 STORE_FAST 1 (s)
8 LOAD_FAST 1 (s)
10 LOAD_METHOD 0 (strip)
12 CALL_METHOD 0
14 POP_JUMP_IF_FALSE 4
16 LOAD_FAST 1 (s)
18 LOAD_METHOD 0 (strip)
20 CALL_METHOD 0
22 LIST_APPEND 2
24 JUMP_ABSOLUTE 4
>> 26 RETURN_VALUE
(При Python <= 3.6 вам нужно написать <code>dis.dis(f.__code__.co_consts[1]), чтобы получить байт-код понимания списка.)
Как видите, метод strip
вызывается дважды ( строки 10-12 и 18-20).
Почему Cpython не выполняет эту оптимизацию?
такая простая оптимизация может быть даже выведена из AST.
Почему вы ожидаете, что s.strip()
будет вычислен один раз? Потому что вы знаете, что эта функция pure и особенно если s == t
, то s.strip() == t.strip()
. Но, насколько мне известно, такого понятия нет в Python. Это означает, что интерпретатор не может сказать, что результат будет таким же.
Небольшой пример с не чистой функцией (да, это ужасно):
>>> i=0
>>> def mystrip(s):
... global i
... i+=1
... return s.strip() if i%2==0 else s
...
One call, mystrip
возвращает извлеченную строку, другая возвращает строку:
>>> mystrip('a ')
'a '
>>> mystrip('a ')
'a'
Отсюда следующий результат:
>>> [mystrip(s) for s in ['a', 'b ', ' ', ' c'] if mystrip(s)]
['a', 'b', '', 'c']
if
аргументы не удаляются (следовательно, True
, поскольку во всей строке есть хотя бы один символ), но возвращаемые значения удаляются.
Ручная оптимизация
Если вы хотите, чтобы s.strip()
был оценен один раз, вы должны написать :
>>> [t for t in (s.strip() for s in ['a', 'b ', ' ', ' c']) if t]
(выражение в скобках является генератором.)
Другие версии (см. Также комментарий @bruno desthuilliers под нашим вопросом):
>>> [t for t in map(str.strip, ['a', 'b ', ' ', ' c']) if t]
>>> list(filter(None, (s.strip() for s in ['a', 'b ', ' ', ' c'])))
>>> filter(None, map(str.strip, ['a', 'b ', ' ', ' c']))