Обновить
17
0
Михаил Евграфов@MishaPogrommist

Пользователь

Отправить сообщение

Спасибо за отзыв! Был бы рад услышать, какие моменты показались сложными для понимания и какие части недостаточно подробно расписаны?

Да, вы правы, это решение действительно проще фабрики и как раз полагается на способ работы со значениями по умолчанию. Я как-то упустил этот момент, спасибо!

Хм, мне казалось, это правильный перевод на русский. А какую бы вы посоветовали формулировку?

В русскоязычной литературе натыкался только на вариант списковые, словарные, множественные включения. Это не мой прямой перевод.

И вот еще одна интересная ссылка на StackOverlow, на этот раз уже прямо по теме нашей дискуссии, про списковые включения.

Я понимаю, что вы хотите сказать. И я с вами не согласен. Вот как выглядит байткод создания списка с помощью генератора:

>>> import dis
>>> comand = "list(i for i in range(10))"
>>> dis.dis(comand)
  0           0 RESUME                   0

  1           2 PUSH_NULL
              4 LOAD_NAME                0 (list)
              6 LOAD_CONST               0 (<code object <genexpr> at 0x00000207B510F590, file "<dis>", line 1>)
              8 MAKE_FUNCTION            0
             10 PUSH_NULL
             12 LOAD_NAME                1 (range)
             14 LOAD_CONST               1 (10)
             16 PRECALL                  1
             20 CALL                     1
             30 GET_ITER
             32 PRECALL                  0
             36 CALL                     0
             46 PRECALL                  1
             50 CALL                     1
             60 RETURN_VALUE

Здесь для создания списка мы используем вызов list , а в качестве аргумента передаем генератор, созданный с помощью генераторного выражения, которое реализовано функцией genexpr . На всякий случай приведу байт код genexpr еще раз:

Disassembly of <code object <genexpr> at 0x00000207B510F590, file "<dis>", line 1>:
  1           0 RETURN_GENERATOR
              2 POP_TOP
              4 RESUME                   0
              6 LOAD_FAST                0 (.0)
        >>    8 FOR_ITER                 6 (to 22)
             10 STORE_FAST               1 (i)
             12 LOAD_FAST                1 (i)
             14 YIELD_VALUE
             16 RESUME                   1
             18 POP_TOP
             20 JUMP_BACKWARD            7 (to 8)
        >>   22 LOAD_CONST               0 (None)
             24 RETURN_VALUE

Здесь мы создаем генератор, а при каждом вызове мы будем делать YIELD_VALUE.

Теперь еще раз посмотрим на код создания с помощью спискового включения:

>>> comand = "[i for i in range(10)]"
>>> dis.dis(comand)
  0           0 RESUME                   0

  1           2 LOAD_CONST               0 (<code object <listcomp> at 0x00000207B4F95210, file "<dis>", line 1>)
              4 MAKE_FUNCTION            0
              6 PUSH_NULL
              8 LOAD_NAME                0 (range)
             10 LOAD_CONST               1 (10)
             12 PRECALL                  1
             16 CALL                     1
             26 GET_ITER
             28 PRECALL                  0
             32 CALL                     0
             42 RETURN_VALUE

Здесь и вызовов меньше, и list не используется, и нет никакого genexpr . Зато вместо него появляется listcomp . Как выглядит listcomp ? На всякий случай дублирую:

Disassembly of <code object <listcomp> at 0x00000207B4F95210, file "<dis>", line 1>:
  1           0 RESUME                   0
              2 BUILD_LIST               0
              4 LOAD_FAST                0 (.0)
        >>    6 FOR_ITER                 4 (to 16)
              8 STORE_FAST               1 (i)
             10 LOAD_FAST                1 (i)
             12 LIST_APPEND              2
             14 JUMP_BACKWARD            5 (to 6)
        >>   16 RETURN_VALUE

Здесь мы сразу создаем список (инструкция по адресу 2 BUILD_LIST), а потом на каждой итерации выполняем команду LIST_APPEND . Т.е. мы так же добавляем элементы в конец списка, как и при использовании цикла и явном вызове append . Вот ссылка на документацию к этой команду для версии 3.11. В итоге, мы в обоих вариантах (цикл и включение) сначала создаем список, а потом изменяем его, закидывая туда элементы. Разница лишь в том, что включениям для этого требуется меньше операций.

По поводу ссылок. Вот неплохой ответ на StackOverflow, правда, про словари, но сути не меняет.

Спасибо! Интересное замечание. Нужно будет обязательно проверить.

Не соглашусь. Все же с точки зрения байткода списковое включение и генераторное выражение - это два разных объекта. Они работают по одной и той же схеме, но используют немного разные операции.

Чтобы в этом убедиться, достаточно дизассемблировать код генераторного выражения:

Disassembly of <code object <genexpr> at 0x000001B8E67AF590, file "<dis>", line 1>:
  1           0 RETURN_GENERATOR
              2 POP_TOP
              4 RESUME                   0
              6 LOAD_FAST                0 (.0)
        >>    8 FOR_ITER                 6 (to 22)
             10 STORE_FAST               1 (i)
             12 LOAD_FAST                1 (i)
             14 YIELD_VALUE
             16 RESUME                   1
             18 POP_TOP
             20 JUMP_BACKWARD            7 (to 8)
        >>   22 LOAD_CONST               0 (None)
             24 RETURN_VALUE

Похоже - да. То же самое - нет. В вашем примере ассерт проходит потому что вы из генератора явно сконструировали список.

Спасибо! Не натыкался на эту стать статью, когда искал похожие материалы.

Да, все верно. Тут я сплоховал, что не конкретизировал. Называю это включениями, потому что рассматривал включения в целом (listcomp, dictcomp, setcomp). Спасибо за замечание.

Информация

В рейтинге
Не участвует
Откуда
Россия
Зарегистрирован
Активность

Специализация

Backend Developer, Application Developer
Junior
Git
Python
Docker
REST
OOP
Algorithms and data structures
Software development
Object-oriented design
SQL
Fastapi