← Назад к вопросам

Почему range возвращает генератор в Python?

1.0 Junior🔥 111 комментариев
#Асинхронность и многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Почему range возвращает генератор в Python?

Технически range — это не совсем генератор, а immutable sequence type (тип последовательности). Но он работает похоже на генератор: ленивое вычисление значений. Это важное различие и хорошая оптимизация.

1. Что такое range на самом деле

import collections.abc

print(type(range(10)))  # <class 'range'>
print(isinstance(range(10), collections.abc.Iterable))  # True
print(isinstance(range(10), collections.abc.Sequence))   # True
print(isinstance(range(10), collections.abc.Generator))  # False

# range — это именно Sequence, а не Generator

range — это специальный тип последовательности, а не генератор. Он итерируется лениво, но сохраняет некоторые свойства последовательностей.

2. Почему range работает лениво (по требованию)

Проблема в Python 2

В Python 2 range() возвращал полный список:

# Python 2
my_list = range(10)  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(type(my_list))  # <type 'list'>

Это было неэффективно:

# Python 2 — выделяет память для всех элементов
r = range(1000000)  # Создаёт список из 1 млн элементов в памяти!
print(len(r))  # 1000000

Для больших диапазонов это приводило к:

  • Огромному использованию памяти
  • Медленному созданию списка
  • Неэффективности при итерации

Решение в Python 3

# Python 3
my_range = range(10)  # Не создаёт список, а объект range
print(type(my_range))  # <class 'range'>
print(sys.getsizeof(my_range))  # Очень мало памяти!

import sys

# Сравнение памяти
small_list = list(range(10))
small_range = range(10)

print(f"Список: {sys.getsizeof(small_list)} байт")
print(f"Range: {sys.getsizeof(small_range)} байт")

big_list = list(range(1000000))  # Выделит много памяти
big_range = range(1000000)  # Выделит минимум памяти

print(f"\nБольшой список: {sys.getsizeof(big_list)} байт")
print(f"Большой range: {sys.getsizeof(big_range)} байт")

Вывод:

Список: 184 байт
Range: 48 байт

Большой список: 8000000+ байт
Большой range: 48 байт (одинаково!)

3. Как range вычисляет значения по требованию

class SimpleRange:
    """Упрощённая реализация range для понимания"""
    def __init__(self, start, stop=None, step=1):
        if stop is None:
            self.start = 0
            self.stop = start
        else:
            self.start = start
            self.stop = stop
        self.step = step
    
    def __iter__(self):
        current = self.start
        while current < self.stop:
            yield current
            current += self.step
    
    def __len__(self):
        return max(0, (self.stop - self.start + self.step - 1) // self.step)
    
    def __getitem__(self, index):
        if isinstance(index, slice):
            raise NotImplementedError
        if index < 0:
            index += len(self)
        if index < 0 or index >= len(self):
            raise IndexError('range index out of range')
        return self.start + index * self.step

# Использование
r = SimpleRange(1000000)
print(len(r))  # Вычисляется в O(1)
print(r[999999])  # Вычисляется в O(1) без итерации!

for num in r:
    if num > 5:
        break
    print(num)

4. Преимущества range перед генератором

range имеет преимущества генератора И последовательности

# ✓ Экономия памяти (как генератор)
r = range(1000000)
print(f"Память: {sys.getsizeof(r)} байт")

# ✓ Можно узнать длину (как последовательность)
print(len(r))  # 1000000

# ✓ Индексирование (как последовательность)
print(r[50000])  # Вычисляется мгновенно без итерации

# ✓ Проверка принадлежности (как последовательность)
print(500000 in r)  # True, O(1) операция

# ✓ Итерация (как генератор)
for i in r:
    process(i)

# ✓ Нарезка
subset = r[100:200:2]  # Тоже возвращает range, не список
print(type(subset))  # <class 'range'>
print(list(subset))  # [100, 102, 104, ..., 198]

Генератор не может делать индексирование и проверку принадлежности эффективно:

def my_generator():
    for i in range(1000000):
        yield i

gen = my_generator()

# ✗ Нельзя узнать длину
len(gen)  # TypeError: object of type generator has no len()

# ✗ Нельзя индексировать
gen[50000]  # TypeError: 'generator' object is not subscriptable

# ✗ Проверка принадлежности требует итерации
500000 in gen  # Проходит всю последовательность!

5. Когда использовать что

Используй range когда:

# Нужны числовые последовательности
for i in range(10):
    print(i)

# Нужны индексы
for i, item in enumerate(range(len(items))):
    print(i, items[i])

# Нужна большая последовательность
for i in range(1000000):
    process(i)

Используй генератор когда:

# Нужна произвольная последовательность
def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

# Нужна трансформация данных
def process_file(filename):
    with open(filename) as f:
        for line in f:
            yield line.strip()

# Нужна отложенная обработка
def lazy_squares(numbers):
    for n in numbers:
        yield n * n

6. Правда ли range — генератор?

import inspect

print(inspect.isgenerator(range(10)))      # False
print(inspect.isgenerator(x for x in range(10)))  # True
print(inspect.isgenerator(gen for gen in []))     # False

# range имеет свой тип
print(type(range(10)))  # <class 'range'>

# Но он итерируется как генератор
print(hasattr(range(10), '__iter__'))  # True
print(hasattr(range(10), '__next__'))  # False (это не генератор)

7. Производительность

import timeit

# range — быстро
print(timeit.timeit(lambda: 999999 in range(1000000), number=1000))
# Output: ~0.001 сек (O(1) операция)

# list — медленно
print(timeit.timeit(lambda: 999999 in list(range(1000000)), number=1))
# Output: ~1 сек (O(n) операция)

# Индексирование
print(timeit.timeit(lambda: range(1000000)[500000], number=1000))
# Output: ~0.001 сек (O(1))

print(timeit.timeit(lambda: list(range(1000000))[500000], number=1))
# Output: ~1 сек (включает создание списка)

Итоги

  1. range не генератор, а специальный immutable sequence type
  2. Вычисляет значения по требованию (лениво) — экономит память
  3. Имеет преимущества обоих миров: ленивость генератора + возможности последовательности
  4. В Python 2 был список, в Python 3 стал объектом диапазона
  5. Оптимизирован на уровне C — очень быстрый
  6. Используй для числовых последовательностей, генераторы для произвольных