Большинство pythonic и/или исполнительский способ назначить единое значение срезу?

Я хочу присвоить отдельное значение части списка. Есть ли лучшее решение для этого, чем одно из следующего?

Может быть, самый результативный, но как-то уродливый:

>>> l=[0,1,2,3,4,5]
>>> for i in range(2,len(l)): l[i] = None

>>> l
[0, 1, None, None, None, None]

Краткая (но я не знаю, понимает ли Python, что перегруппировка элементов списка не требуется):

>>> l=[0,1,2,3,4,5]
>>> l[2:] = [None]*(len(l)-2)
>>> l
[0, 1, None, None, None, None]

То же предостережение, как указано выше:

>>> l=[0,1,2,3,4,5]
>>> l[2:] = [None for _ in range(len(l)-2)]
>>> l
[0, 1, None, None, None, None]

Не уверен, что использование библиотеки для такой тривиальной задачи разумно:

>>> import itertools
>>> l=[0,1,2,3,4,5]
>>> l[2:] = itertools.repeat(None,len(l)-2)
>>> l
[0, 1, None, None, None, None]

Проблема, которую я вижу с назначением срезу (по сравнению с циклом for), заключается в том, что Python, возможно, пытается подготовиться к изменению длины "l". В конце концов, изменение списка путем вставки более короткого/более длинного фрагмента включает в себя копирование всех элементов (то есть всех ссылок) списка AFAIK. Если Python делает это и в моем случае (хотя это необязательно), операция становится O (n) вместо O (1) (при условии, что я всегда меняю только несколько элементов).

Ответ 1

Я думаю, что в Python нет прямой возможности для этого. Мне нравится ваш второй подход, но имейте в виду, что есть компромисс между пространством и временем. Это очень хорошее чтение, рекомендованное @user4815162342: Шаблоны Python - Анекдот оптимизации.

Во всяком случае, если это операция, которую вы в конечном итоге выполняете в своем коде, я думаю, что ваш лучший вариант - обернуть ее внутри вспомогательной функции:

def setvalues(lst, index=0, value=None):
    for i in range(index, len(lst)):
        lst[i] = value

>>>l=[1,2,3,4,5]
>>>setvalues(l,index=2)
>>>l
>>>[1, 2, None, None, None]

Это имеет некоторые преимущества:

  • Код реорганизуется внутри функции, поэтому его легко изменить, если вы передумали, как выполнить действие.
  • У вас может быть несколько функций, которые выполняют одну и ту же цель и, следовательно, могут измерять их производительность.
  • Вы можете написать для них тесты.
  • Каждое другое преимущество, которое вы можете получить при рефакторинге:)

Поскольку IMHO там нет прямого будущего Python для этого действия, это лучшее обходное решение, которое я могу себе представить.

Надеюсь, это поможет!

Ответ 2

Сроки:

python -mtimeit "l=[0,1,2,3,4,5]" "for i in range(2,len(l)):" "    l[i] = None"
1000000 loops, best of 3: 0.669 usec per loop

python -mtimeit "l=[0,1,2,3,4,5]" "l[2:] = [None]*(len(l)-2)"
1000000 loops, best of 3: 0.419 usec per loop

python -mtimeit "l=[0,1,2,3,4,5]" "l[2:] = [None for _ in range(len(l)-2)]"
1000000 loops, best of 3: 0.655 usec per loop

python -mtimeit "l=[0,1,2,3,4,5]" "l[2:] = itertools.repeat(None,len(l)-2)"
1000000 loops, best of 3: 0.997 usec per loop

Похоже, что l[2:] = [None]*(len(l)-2) - лучший из предоставленных вами параметров (для области, с которой вы имеете дело).

Примечание:

Имейте в виду, что результаты будут варьироваться в зависимости от версии Python, операционной системы, других запущенных в данный момент программ и, прежде всего, - размера списка и подлежащего замене среза. Вероятнее всего, для более крупных областей последняя опция (с использованием itertools.repeat) будет наиболее эффективной, будучи легко читаемой (питоновой) и эффективной (производительности).

Ответ 3

Все ваши решения Pythonic и примерно одинаково читаемы. Если вы действительно заботитесь о производительности и считаете, что это важно в этом случае, используйте модуль timeit, чтобы проверить их.

Сказав это, я ожидал бы, что первое решение почти наверняка не является наиболее эффективным, потому что оно выполняет итерации над элементами списка в Python. Кроме того, Python не оптимизирует список списка, созданный с правой стороны назначения, но создание списка происходит очень быстро, и в большинстве случаев небольшой временный список вообще не влияет на выполнение. Лично для краткого списка я бы пошел со своим вторым решением, и для более длинного списка я бы пошел с itertools.repeat().

Обратите внимание, что itertools на самом деле не считается "библиотекой", он поставляется с Python и так часто используется, что он по существу является частью языка.

Ответ 4

Я бы предложил что-то вроде этого:

>>> l = [0, 1, 2, 3, 4, 5]
>>> n = [None] * len(l)
>>> l[2:] = n[2:]
>>> l
[0, 1, None, None, None, None]

Это выглядит красиво: нет явных циклов, нет "если", никаких сравнений! За счет сложности 2N! (или нет?)

Ответ 5

Быть более питоническим и быть более совершенным - это цели, которые иногда могут столкнуться. Поэтому вы в основном задаете два вопроса. Если вам действительно нужна производительность: измерьте ее и возьмите быстрее. Во всех остальных случаях просто переходите к тому, что наиболее читаемо, другими словами, что является наиболее Pythonic (что наиболее читаемо/знакомо другим программистам на Python).

Лично я думаю, что ваше второе решение вполне читаемо:

>>> l=[0,1,2,3,4,5]
>>> l[2:] = [None]*(len(l)-2)
>>> l
[0, 1, None, None, None, None]

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

Ответ 6

EDIT. Теперь редактируем исходный список.

Вы забыли это, (ИМО, этот текст более читабельен)

>>> l = [i if i < 2 else None for i in range(6)]
>>> l
[0, 1, None, None, None, None]

Если сохранение необходимо,

>>> l = range(6)
>>> l
[0, 1, 2, 3, 4, 5]
>>> l[:] = [l[i] if i < 2 else None
...              for i in range(len(l))]
>>> l
[0, 1, None, None, None, None]

С учетом этого производительность примерно в 2,5 раза медленнее, чем тот, который был введен Inbar в качестве самого быстрого метода.