Делает ли секция `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
. Результаты оцениваются один за другим, а не сначала помещаются в список, поэтому вы сохраняете память за счет некоторой производительности.