__len__ используется при итерации в циклах for по объектам, когда Python пытается оптимизировать итерацию, особенно для контейнерных типов данных.  Если у объекта определен __len__, Python может знать размер контейнера заранее и оптимизировать процесс итерации, например, для предварительного выделения памяти или для более эффективного доступа к элементам по индексу.  Однако, наличие __len__ не является абсолютно необходимым для итерации, так как можно использовать итераторы, которые не требуют знания длины.
Метод __len__ играет важную роль, хотя и не прямую, при итерации по объектам в Python, особенно в контексте оптимизации и предварительного выделения памяти.
Непрямое использование:
__len__: Когда Python сталкивается с итерацией (например, в цикле for или при использовании list(), tuple() и т.п.), он сначала пытается использовать протокол итератора (наличие методов __iter__ и __next__).  Если объект не поддерживает этот протокол и является последовательностью, он попытается итерироваться по нему как по списку или кортежу.  То есть обратиться к элементам по индексу.__len__, Python может предварительно выделить память для структуры данных (например, списка), которая будет создана в результате итерации. Например, при создании списка из итерируемого объекта с известной длиной, Python может сразу выделить память нужного размера. Это предотвращает многократное перераспределение памяти, что повышает эффективность.Пример:
Рассмотрим создание списка из объекта, поддерживающего __len__:
  class MyIterable:
      def __init__(self, data):
          self.data = data
      def __len__(self):
          return len(self.data)
      def __getitem__(self, index):
          return self.data[index]
  my_iterable = MyIterable([1, 2, 3, 4, 5])
  my_list = list(my_iterable) # Здесь может быть оптимизация благодаря __len__
  print(my_list)  # Вывод: [1, 2, 3, 4, 5]
  В этом примере, когда вызывается list(my_iterable), Python может использовать __len__, чтобы узнать размер my_iterable и предварительно выделить память для списка my_list.
Важно отметить: Если объект реализует протокол итератора (__iter__ и __next__), метод __len__ не используется напрямую для управления итерацией. Итерация полностью контролируется методами __iter__ и __next__.  В этом случае, если __len__ определен, он может быть использован для других целей, но не для самой итерации.
Вывод:
Хотя __len__ напрямую не управляет механизмом итерации (этим занимаются __iter__ и __next__ или __getitem__), он может использоваться для оптимизации и предварительного выделения памяти при создании новых коллекций из итерируемых объектов. Это особенно полезно для повышения производительности, когда размер результирующей коллекции известен заранее.