Чому Python дозволяє індекси зрізів поза послідовністю для послідовностей?


76

Тож я просто натрапив на те, що мені здається дивною особливістю Python, і хотів трохи пояснити щодо цього.

Наступна маніпуляція з масивом має сенс:

p = [1,2,3]
p[3:] = [4] 
p = [1,2,3,4]

Думаю, насправді це просто додавання цього значення до кінця, правильно?
Чому я можу це зробити?

p[20:22] = [5,6]
p = [1,2,3,4,5,6]

І тим більше це:

p[20:100] = [7,8]
p = [1,2,3,4,5,6,7,8]

Це просто здається неправильною логікою. Здається, це повинно викликати помилку!

Будь-яке пояснення?
-Це просто дивно, що робить Python?
-Чи є в цьому мета?
-Або я про це думаю не так?


2
В інших мовах я завжди в кінцевому підсумку пишу подібні речі повсюди: if i > sequence.length(): return sequence.slice(0, sequence.length()) else sequence.slice(0, n)це точно те саме, що просто використання sequence[:n]в Python економить вам оператор if і 2 дзвінки length.
Бакуріу

4
До речі. Ви можете розглядати зрізи як "набори". Так p[20:22]само є послідовність усіх елементів з індексами від 20 до 22. Порожній набір є дійсним набором. Це зовсім інше, ніж p[20]твердження, яке стверджує про існування елемента з індексом 20. Отже, різниця в перевірці діапазону між пошуком елемента та зрізом відображає два різні значення.
Бакуріу

2
Я думаю, що це більш широке питання про те, чому додавання послідовностей у зрізи послідовностей різної довжини дозволено в Python і які його переваги. В іншому питанні взагалі не розглядається присвоєння частини цього питання. Це просто говорить про нарізку.
Akaisteph7,

Відповіді:


79

Частина запитання щодо індексів поза межами асортименту

Логіка зрізу автоматично відсікає індекси на довжину послідовності.

Дозволяючи індексам зрізів розширювати минулі кінцеві точки, це було зроблено для зручності. Було б боляче, коли б потрібно було перевіряти діапазон кожного виразу, а потім регулювати обмеження вручну, тому Python робить це за вас.

Розглянемо варіант використання бажання відображати не більше перших 50 символів текстового повідомлення.

Простий спосіб (що робить Python зараз):

preview = msg[:50]

Або складний спосіб (перевірте ліміт самостійно):

n = len(msg)
preview = msg[:50] if n > 50 else msg

Вручну реалізувати цю логіку для коригування кінцевих точок було б легко забути, легко було б помилитися (оновлення 50 в двох місцях), було б багатослівним і було б повільним. Python переміщує цю логіку до своїх внутрішніх елементів, де вона є стислим, автоматичним, швидким і правильним. Це одна з причин, чому я люблю Python :-)

Частина запитання щодо невідповідності довжини призначень від довжини вводу

Оператор також хотів знати обгрунтування дозволу на призначення, наприклад, p[20:100] = [7,8]коли ціль призначення має іншу довжину (80), ніж довжина даних заміщення (2).

Найпростіше побачити мотивацію за аналогією зі струнами. Розглянемо "five little monkeys".replace("little", "humongous"). Зверніть увагу, що ціль "мало" має лише шість букв, а "гордо" - дев'ять. Ми можемо зробити те ж саме зі списками:

>>> s = list("five little monkeys")
>>> i = s.index('l')
>>> n = len('little')
>>> s[i : i+n ] = list("humongous")
>>> ''.join(s)
'five humongous monkeys'

Все це зводиться до зручності.

До введення методів copy () та clear () це були популярні ідіоми:

s[:] = []           # clear a list
t = u[:]            # copy a list

Навіть зараз ми використовуємо це для оновлення списків під час фільтрації:

s[:] = [x for x in s if not math.isnan(x)]   # filter-out NaN values

Сподіваюся, ці практичні приклади дають хороший погляд на те, чому нарізка працює так, як це робиться.


2
"Навіть зараз ми використовуємо це для оновлення списків під час фільтрації [приклад використання s[:]] " - Чи могли б ви розширити інформацію про те, чому ви використовували б s[:] =там, а не просто s =? Я ніколи не бачив, щоб хтось використовував s[:] =у контексті такий рядок, як те, що ви там писали. Хороша відповідь інакше!
Quuxplusone

10
@Quuxplusone: фрагмент призначення мутує список вже посилається s; використання s = повторного прив'язки s для посилання на новий список. Якщо до списку можна дістатися за допомогою декількох імен, і ви хочете, щоб мутація була видимою для всіх імен, вам потрібно призначити зріз. Крім того, якби sбуло глобальним, перепризначення sвимагало б globalдекларації, але призначення зрізу мало б подібний ефект навіть без globalзаяви.
Даніель Прайден,

24

У документації є ваша відповідь:

s[i:j]: фрагмент sвід iдо j(примітка (4))

(4) Фрагмент sвід iдо jвизначається як послідовність елементів з kтаким індексом , що i <= k < j. Якщо iабо jбільше ніж len(s), використовуйтеlen(s) . Якщо iпропущено або None, використовуйте 0. Якщо j пропущено або None, використовуйте len(s). Якщо iбільше або дорівнює j, зріз порожній.

ДокументаціяIndexError підтверджує цю поведінку:

виняток IndexError

Підвищується, коли індекс послідовності виходить за межі діапазону. ( Індекси зрізів мовчки скорочуються, щоб потрапити в дозволений діапазон; якщо індекс не є цілим числом, TypeErrorпіднімається.)

По суті, такі речі p[20:100]зменшуються до p[len(p):len(p]. p[len(p):len(p]є порожнім фрагментом в кінці списку, і присвоєння йому списку змінить кінець списку, щоб містити вказаний список. Таким чином, це працює як додавання / розширення вихідного списку.

Ця поведінка подібна до того, що відбувається, коли ви призначаєте список порожньому фрагменту в будь-якому місці вихідного списку. Наприклад:

In [1]: p = [1, 2, 3, 4]

In [2]: p[2:2] = [42, 42, 42]

In [3]: p
Out[3]: [1, 2, 42, 42, 42, 3, 4]

3
Я не думаю, що OP запитує, як працює нарізка, він просить обґрунтування вибору дизайну.
Примуса

2
@Primusa - Я вважаю, що вони запитують обох . Це пояснює як , що корисно знати, оскільки пояснює, чому поведінка не порушена. Чому , ймовірно , похований в надрах одного з де - то списки розсилки.
gddc

Хороша відповідь, але це не пояснює, чому нові цифри додаються до кінця списку.
Атіраг

1
@Atirag Я додав невелику розмитку про це для повноти.
iz_

1
Індексація @Atirag сильно відрізняється від нарізки; індексація завжди відноситься до значень.
iz_
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.