Как получить отрицательную длину len()≤0 на Python
Рассказываем, возможно ли получить негативное значение из встроенной функции len() в Python, как это работает и зачем это нужно.
Возможно ли получить негативное значение из встроенной функции len()
в Python?
Встроенная функция len()
возвращает длину (количество элементов) объекта. Интуитивно понятно, что количество элементов в коллекции не может быть отрицательным. Оно должно быть равно 0 или больше.
Однако, я задумался над этим вопросом когда встретил следующий рабочий Python код:
Условие пропускается, если коллекция не пустая. Если же она пустая, то мы попадаем в if блок. На первый взгляд это выглядит как незначительная ошибка из-за невнимательности. Но все же давайте исследуем ситуации когда len()
может вернуть отрицательное значение.
Встроенная функция len()
Прежде всего, давайте взглянем на документацию:
Return the length (the number of items) of an object. The argument may be a sequence (such as a string, bytes, tuple, list, or range) or a collection (such as a dictionary, set, or frozen set).
Несколько примеров использования len()
с последовательностями и коллекциями:
Для тех кто хочет глубже погрузиться в реализацию может обратиться к исходникам.
Специальный метод __len__
Встроенная функция len()
работает как фасад к дандер методу __len__()
объекта (см. док). Вы можете явно вызвать его для встроенных типов, таких как последовательности или коллекции:
Но если вы попытаетесь использовать len()
с кастомным классом без определения __len__
, то это не сработает, и вы получите исключение:
Было вызвано исключение TypeError, поскольку специальный метод __len__
не был определен, и len()
не может быть использован с экземпляром этого класса.
Возврат отрицательного значения
Итак, чтобы проверить первоначальную гипотезу — может ли len()
вернуть отрицательное значение, нам нужно создать пользовательский тип и определить специальный метод __len__
, возвращающий такое значение.
Сначала сэмулируем пользовательский тип контейнера и определим __len__()
, возвращающий небольшое отрицательное целое число:
В результате это приводит к ValueError, что соответствует задокументированному поведению, хотя ничто не мешает нам явно вызвать __len__
.
Как насчет возврата большого отрицательного числа?
Мы получаем одно и то же значение ошибки независимо от того, насколько отрицательным является значение.
Однако, согласно баг-трекеру и pull request раньше наблюдалась ошибка переполнения OverflowError. Вы можете проверить это на предыдущих версиях python (например, Python 3.6.15).
Предыдущая реализация, возвращающая OverflowError, имела ограничения на длину в реализации CPython, которая все еще работает для больших положительных чисел для Python 3.11.2:
CPython implementation detail: In CPython, the length is required to be at most sys.maxsize. If the length is larger than sys.maxsize some features (such as len()) may raise OverflowError.
Итак, мы не можем заставить len()
возвращать отрицательное значение, потому что выходные данные __len__
тщательно валидируются. Вот почему невозможно перейти в условие:
Этот код не детектируются как недоступный ни mypy, ни PyCharm. Возможно, имеет смысл пометить его как недоступный, чтобы привлечь к нему внимание.
Поиска метода __len__
В качестве продвинутого примера давайте рассмотрим поиск метода __len__
и обратимся к соответствующему параграфу о специальных методах:
For custom classes, implicit invocations of special methods are only guaranteed to work correctly if defined on an object’s type, not in the object’s instance dictionary.
Это объяснение нелегко уловить сразу. В нем говорится о 2 случаях определения __len__
:
- определение в словаре экземпляра объекта
- определение в типе объекта (работает корректно)
Мы рассмотрим оба случая с примерами для пояснения. Сначала создайте пользовательский тип без __len__
и назначьте его в качестве атрибута экземпляру объекта:
Можно увидеть, что неявный вызов __len__
через len()
вызывает исключение, поскольку __len__
добавляется в качестве атрибута экземпляра вне класса. Тем не менее, если мы определим метод __len__
внутри определения типа, он будет работать корректно:
Таким образом, неявный вызов специального метода __len__
не использует метод, определенный в словаре экземпляра объекта. Если вы собираетесь определить методы, поместите их в область видимости классов.
Мы пришли к выводу, что не можем получить отрицательное значение из встроенной функции len()
, поскольку значение возвращаемое __len__
в этом случае валидируется. Если __len__
пользовательского типа возвращает отрицательное значение, это приведет к исключению при его неявном вызове. Таким образом, подобные условные выражения не представляют никакой практической ценности:
Не стоит забывать, что ошибки имеют тенденцию накапливаться вместе. Когда вы где-то сталкиваетесь с незначительной проблемой, существует высокая вероятность того, что вы обнаружите в близлежащем коде более серьезные проблемы. Дважды перепроверяйте смежный код, чтобы убедиться, что все работает должным образом.
***- Ссылка на оригинальную статью
- Linkedin_profile