Страницы

Поиск по вопросам

понедельник, 10 февраля 2020 г.

Почему не возникает ошибка при s[0:len(s)]?

#python #language_lawyer


s = "Worldoftanks"


Почему print(s[0:len(s)]) будет работать?

len(s) вернет 12. Тогда получается s[0:12], а это ошибка: обращение к несуществующему
индексу.
    


Ответы

Ответ 1



Для начала стоит отметить, что все верно. Из документации ( раздел Sequences): a[i:j] вернет все элементы с индексом k таким, что i <= k < j. Обратите внимание на то, что j строго больше. Послединий индекс "Worldoftanks" - 11 (нумерация с нуля) и срез [0:len(obj)] вернет все элементы с индексами 0 1 2 ... 10 11 равенство 11 < 12 выполнено. Однако, есть другой момент: Срезы представляют собой отдельные объекты, которые возвращает функция slice. В определении сказано, что при взятии среза нормальным способом ([start:stop:step]) объекты этого типа используются неявно. Когда необходимо будет вернуть результат среза, будут вычислены допустимые границы (границы будут проверены за вас, да), чтобы не произошло выхода за пределы. За это отвечает метод indices (getIndices в CPython). Можно немного переписать взятие индекса вот так: slic = slice(0, 20) print("Тип объекта из функции slice:", type(slic)) my_string = "Hello world" print("Корректные индексы:", slic.indices(len(my_string))) print("Результат среза", my_string[slic]) >>> Тип объекта из функции slice: >>> Корректные индексы: (0, 11, 1) >>> Результат среза Hello world Как видно, никаких ошибок, ибо indices вернул корректные границы.

Ответ 2



s[0:12] - это ж с нулевой по 11ю; первый индекс - это откужа начинается то, что нам надо, второй - откуда начинается то, что нам НЕ надо; както так в доках поедлагается запоминать, чтобы не путаться ))) UPD: вообще, синтаксис слайсов (они же - "срезы") НЕ вызывает ошибок при выходе за пределы массива/строки, в отличие от обращения к единичному элементу. ну т.е. s[100] вызовет ошибку, а s[100:101] - уже не вызовет, как и s[100:], ну и все такое. В доках об этом явно сказано: docs.python.org/2/tutorial/introduction.html , ищем строчку "However, out of range slice indexes are handled gracefully when used for slicing"

Ответ 3



Почему print(s[0:len(s)]) будет работать? Правая граница среза так же как и для range(len(s)) в Питоне не включается†: >>> [*range(4)] [0, 1, 2, 3] Видно что правая граница 4 исключена. Аналогично, print(s[0:len(s)]) печатает все символы с допустимыми индексами от 0 до len(s) - 1 включительно, то есть в диапазоне 0 <= i < len(s). E.W. Dijkstra объясняет (в 1982) почему правую границу не следует включать. Фактически для строк (str тип) и других стандартных последовательностей в Питоне можно использовать в срезах значения вне допустимых индексов, к примеру: 'a'[-42:1000] не выбрасывает ошибку, так как s[i:j] формально: (4) The slice of s from i to j is defined as the sequence of items with index k such that i <= k < j. If i or j is greater than len(s), use len(s). If i is omitted or None, use 0. If j is omitted or None, use len(s). If i is greater than or equal to j, the slice is empty. (3) If i or j is negative, the index is relative to the end of sequence s: len(s) + i or len(s) + j is substituted. But note that -0 is still 0. Если индексы отрицательны, то к ним прибавляется len(s) и затем s[i:j] равнозначно‡: s[max(0, min(i, len(s))) : max(0, min(j, len(s)))] то есть i,j принудительно к диапазону [0, len(s)] (оба конца включительно) приводятся. Неуказанные границы или None заменяются на 0 и len(s) соответственно: s[0:len(s)] == s[:] == s. Если в итоге i >= j, то пустой срез создаётся. Иллюстрация из вводного руководства: +---+---+---+---+---+---+ | P | y | t | h | o | n | +---+---+---+---+---+---+ 0 1 2 3 4 5 6 -6 -5 -4 -3 -2 -1 "Python"[3:5] == "ho"—визуально достаточно выбрать буквы ('h' и 'o'), которые между указанными индексами (3 и 5) находятся. s[i:j] действует как type(s).__getitem__(s, slice(i, j)). Пример реализации: class FakeSeq: ... def calc_item(self, i): ... def __getitem__(self, item): if isinstance(item, slice): indices = item.indices(len(self)) return FakeSeq([self.calc_item(i) for i in range(*indices)]) else: return self.calc_item(i) slice.indices() метод используется, чтобы помочь реализовать необходимое поведение для среза. К примеру, "a"[-42:1000]: >>> slice(-42, 1000).indices(len("a")) (0, 1, 1) # start, stop, step >>> list(range(*_)) [0] # indices >>> 'a'[-42:1000] == 'a'[0] True Фактически в CPython для строк вызывается похожая функция PySlice_GetIndicesEx(). В общем случае, типы передаваемые в slice() неограничены и их интерпретация зависит от конкретного типа: class Slicable(object): def __getitem__(self, given): if isinstance(given, slice): # do your handling for a slice object: print(given.start, given.stop, given.step) else: # Do your handling for a plain index print(given) s = Slicable() s[1] s[1:2] s[1:2:3] s[1:2:3,4] s[()::1j,] s[()::1j,None] Результат 1 1 2 None 1 2 3 (slice(1, 2, 3), 4) (slice((), None, 1j),) (slice((), None, 1j), None) Реальный пример: кортежи, ... и 1j индексы используются в numpy. †Что значит * (звёздочка) и ** двойная звёздочка в Питоне? ‡Часть об обнулении отрицательных индексов я не нашёл где явно задокументирована (кроме упомянутой цитаты из вводного руководства (неформально): "out of range slice indexes are handled gracefully when used for slicing") и из новостей для Питон 2.3 (древняя версия): "[slice.]indices() handles omitted and out-of-bounds indices in a manner consistent with regular slices (and this innocuous phrase hides a welter of confusing details!"выделение моё Выделенная часть переводится как: «эта безобидная фраза скрывает кучу сбивающих с толку деталей»

Комментариев нет:

Отправить комментарий