Срез с отрицательным числом
Почему spisok[:n] тихо возвращает не то, когда n оказалось меньше нуля.
Ловушка, которая попалась в трёх темах подряд — «Включения», «Файлы» и «CSV и JSON» — и каждый раз выглядела невинно.
В чём дело
Срез spisok[:n] берёт первые n элементов. Это верно, пока n
не меньше нуля.
spisok = ["а", "б", "в", "г", "д"]
spisok[:2] # ['а', 'б'] — первые два
spisok[:0] # [] — ни одного
spisok[:-2] # ['а', 'б', 'в'] — ВСЁ, КРОМЕ последних двух
Отрицательное число в срезе означает отсчёт с конца. Поэтому там, где вы ожидали пустой список, приходит почти весь.
Почему это опасно
Программа не падает. Ответ выглядит осмысленным: список, в нём элементы, порядок правильный. Понять, что их на два больше нужного, можно только зная, сколько должно было быть.
Типичный случай: число берётся из настройки, из ввода, из расчёта — и однажды оказывается отрицательным.
def pervye(spisok, skolko):
return spisok[:skolko] # при skolko = -1 вернёт почти всё
pervye(["а", "б", "в"], -1) # ['а', 'б'] вместо []
Что делать
Проверять до среза, а не после:
def pervye(spisok, skolko):
if skolko <= 0:
return []
return spisok[:skolko]
Ноль проверять вместе с отрицательными не обязательно — [:0] и так
даёт пустой список, — но проверка <= 0 читается как одно правило,
а не как два разных случая.
То же самое в других местах
Ловушка не только в срезах.
collections.deque(maxlen=n) при отрицательном n выбросит ошибку —
это как раз хорошо, беда видна сразу.
range(n) при отрицательном просто пуст, и это безопасно.
random.sample(spisok, n) выбросит ошибку.
Срез — единственный из этого ряда, кто молчит и возвращает правдоподобную неправду. Поэтому и стоит помнить именно про него.
Как проверить себя
Прогоните свою функцию на трёх значениях: обычном, нуле и минус единице. Если при минус единице приходит непустой ответ — почти наверняка ошибка.