Делает ли секция `a` (например,` a [1:] == a [: - 1] `) создавать копии` a`?

Друг мой показал мне следующий код Python:

a[1:] == a[:-1]

Возвращает True, если все элементы в a идентичны.

Я утверждал, что код трудно понять с первого взгляда, и более того - он неэффективен в использовании памяти, потому что для сравнения будут созданы две копии a.

Я использовал Python dis, чтобы узнать, что происходит за капотом в a[1:]==a[:-1]:

>>> def stanga_compare(a):
...     return a[1:]==a[:-1]
...
>>> a=range(10)
>>> stanga_compare(a)
False
>>> a=[0 for i in range(10)]
>>> stanga_compare(a)
True

>>> dis.dis(stanga_compare)
  2           0 LOAD_FAST                0 (a)
              3 LOAD_CONST               1 (1)
              6 SLICE+1
              7 LOAD_FAST                0 (a)
             10 LOAD_CONST               2 (-1)
             13 SLICE+2
             14 COMPARE_OP               2 (==)
             17 RETURN_VALUE

Он сводится к двум командам среза - SLICE+1 и SLICE+2. Документация неясна относительно того, действительно ли эти коды операций создают новую копию a или просто ссылку на нее.

  • Выполняет ли команды SLICE a?
  • Отвечает ли ответ между реализациями Python (Cython, Jython)?

Обновление

Этот фрагмент явно непонятен и запутан, и я не буду его использовать в реальных код. Мой интерес носит чисто технический характер: копирует ли список список и зависит ли ответ в разных обстоятельствах.

Ответ 1

Документация неясно, потому что нарезка разных объектов делает разные вещи. В случае списка нарезка делает копию (неглубокую) 1. Обратите внимание, что это функция списков python, не зависящая от реализации python. В случае других объектов (например, массивов numpy) он может не создавать копию.

Если вам нужен лучший способ проверить, что все элементы в списке одинаковы, я бы, вероятно, рекомендовал:

 all(lst[0] == item for item in lst)

С точки зрения производительности, ваш код друга может фактически превзойти его для небольших списков, так как оптимизация списка разреза. Но это ИМХО гораздо проще сказать, что происходит, и имеет возможность "короткого замыкания", как только он находит несоответствие.

1 Фактическая функция для просмотра - list_subscript, но для большинства случаев она просто вызывает list_slice

Ответ 2

Если a - это список или кортеж или строка, len(a) - n и n > 0, то каждый срез создает (на уровне C) новый массив длиной n-1. На уровне C все объекты в CPython реализованы как указатели, поэтому в этих новых массивах содержатся указатели n-1, скопированные из a (ну, не для строк), строковое представление более экономно.

Но, как сказал @mgilson, какая режущая способность возвращается к типу a. Некоторые типы могут возвращать компактный дескриптор вместо копирования чего-либо. И тип может даже реализовать нарезку таким образом, чтобы показанный код не работал так, как предполагалось.

Но у вас действительно был список в виду: -)

Ответ 3

Да, при list объектах Python создает мелкие копии при разрезе, однако цикл выполняется в C (для cpython), и по этой причине будет намного быстрее, чем все, что вы можете написать в Python для этого. Цикл 2 раза в C для мелкой копии и повторение цикла для сравнения будет быстрее, чем просто цикл в Python один раз.

Помните, что cpython довольно часто достаточно быстро, но этот код Python все еще примерно в 100 раз медленнее кода C. Поэтому оставить cpython, делая петли для вас, лучше, если вы хотите немного скорости. Обратите внимание, что даже такие вещи, как c = a + b в Python, означают выполнение большого количества логики (включая ветки и распределения памяти).

С другой стороны, если для вашего кода такая микро-оптимизация фундаментальная, то, вероятно, Python не подходит для проблемы, с которой вы сражаетесь, и вы должны рассмотреть другие варианты (например, написать маленькое расширение С++, сопряженное с глотком, используя Cython, PyPy...).

Конечно, этот код не читается для неподготовленного глаза, и если список длинный и часто не, то all(y == x[0] for y in x) будет быстрее из-за короткого замыкания (даже если цикл находится в Python, и для каждого элемента выполняется дополнительная операция подстроки).

Показатели удобочитаемости. Много.

ИЗМЕНИТЬ

Еще одна интересная опция для циклического цикла кода C над элементами:

x and x.count(x[0]) == len(x)

Это не обеспечивает короткое замыкание, но на моем компьютере примерно в 75 раз быстрее, чем решение на основе all для списка из 1000 элементов, все из которых равны и примерно в 6 раз быстрее, чем x[1:] == x[:-1].

Я нахожу это также более читаемым, чем x[1:] == x[:-1], но это, вероятно, вопрос вкуса.

Ответ 4

Для ванильных списков нарезка создает копию. Вместо этого вы можете предотвратить копирование с помощью итерации:

import itertools
a1 = iter(a)
a2 = iter(a)
a2.next() # start a2 iterator one removed
all_are_identical = all((i1 == i2 for i1, i2 in itertools.izip(a1, a2)))

Строка (i1 == i2 for i1, i2 in itertools.izip(a1, a2)) создает генератор, который будет возвращать, будет ли каждый элемент из a равным следующему, по одному за раз, до all. Результаты оцениваются один за другим, а не сначала помещаются в список, поэтому вы сохраняете память за счет некоторой производительности.