Оптимизирует ли Python дублирование выражений в списках? - PullRequest
0 голосов
/ 06 апреля 2020

Рассмотрим следующее понимание списка:

[s.strip() for s in ['a', 'b ', ' ', ' c'] if s.strip()]

Будет ли s.strip() вычисляться дважды или Python оптимизирует такие выражения внутренне и будет вычислять повторяющиеся выражения только один раз? Я знаю, что Python не является скомпилированным языком, но такая простая оптимизация может быть даже выведена из AST.

Заранее спасибо.

1 Ответ

1 голос
/ 10 апреля 2020

Вызывается ли 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']))
...