Внутренняя реализация функции `len()` в Python: как это работает?

Внутренняя реализация функции `len()` в Python: как это работает?


Внутренняя реализация функции len() в Python: как это работает?

Функция len() в Python — один из самых часто используемых инструментов. Она возвращает количество элементов в объекте: длину строки, списка, словаря и других коллекций. Но как она работает под капотом? Почему для одних объектов она выполняется мгновенно, а для других может вызвать ошибку? В этой статье разберемся с внутренней реализацией len() и её особенностями.

Основной механизм: метод __len__

В основе функции len() лежит магический метод __len__(), который должен быть реализован в объекте. Когда вы вызываете len(obj), интерпретатор Python автоматически обращается к obj.__len__(). Это часть общего протокола Python, позволяющего объектам определять свою “длину”.

Пример простого класса с реализацией __len__:

class MyCollection:
    def __init__(self, items):
        self.items = items
    
    def __len__(self):
        return len(self.items)

my_obj = MyCollection([1, 2, 3])
print(len(my_obj))  # Вывод: 3

Если метод __len__ не определен, вызов len() приводит к ошибке TypeError:

class NoLength:
    pass

obj = NoLength()
print(len(obj))  # TypeError: object of type 'NoLength' has no len()

Оптимизация для встроенных типов

Для стандартных типов данных (списки, кортежи, строки и т.д.) функция len() работает за константное время O(1). Это связано с тем, их длина хранится в памяти как отдельное поле структуры данных. Например:

  • Списки (list): Длина хранится в поле ob_size структуры PyListObject (в реализации CPython). При создании списка его длина сразу фиксируется и обновляется при изменениях.
  • Строки (str): Длина строки предвычислена и хранится в заголовке объекта.

Благодаря этому len() не требует пересчета элементов — значение просто считывается из памяти.

Почему len() — это функция, а не метод?

Это дизайнерское решение создателей Python. Аргументы в пользу такого подхода:

  1. Единообразие: Функция len() работает для всех типов данных, а не является методом конкретного класса.
  2. Читаемость: len(obj) интуитивно понятнее, чем obj.len(), особенно для людей, знакомых с другими языками.
  3. Принцип “единственного способа”: В Python есть философия, что должен существовать один очевидный способ выполнить задачу. Функция len() универсальна и предсказуема.

Ограничения и исключения

  1. Возвращаемое значение: Метод __len__() должен возвращать целое число >= 0. В противном случае возникнет ошибка:

    class InvalidLength:
        def __len__(self):
            return -10
    
    print(len(InvalidLength()))  # ValueError: __len__() should return >= 0
  2. Итерируемые объекты без __len__: Для объектов, которые не реализуют __len__, но являются итерируемыми (например, генераторы), длина может быть определена только путем полного обхода элементов, что неэффективно:

    gen = (x for x in range(5))
    print(len(gen))  # TypeError: object of type 'generator' has no len()

Примеры реализации в CPython

Рассмотрим фрагмент исходного кода CPython (реализация для списка):

static Py_ssize_t list_length(PyListObject *a) {
    return a->ob_size;
}

Здесь ob_size — поле, хранящее длину списка. Функция list_length просто возвращает его значение, что и делает len() молниеносно быстрой.

Сравнение с другими языками

  • JavaScript: Использует свойство array.length.
  • Java: array.length для массивов, collection.size() для коллекций.
  • Python: Универсальная функция len() для всех типов, что упрощает запоминание.

Заключение

Функция len() в Python — это не просто “счетчик элементов”. Её работа основана на методе __len__(), который может быть кастомизирован для пользовательских классов. Для встроенных типов длина вычисляется мгновенно благодаря оптимизациям на уровне C. Понимание этого механизма помогает писать более эффективный код и правильно проектировать собственные классы.