← Назад к вопросам
Почему 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 сек (включает создание списка)
Итоги
- range не генератор, а специальный immutable sequence type
- Вычисляет значения по требованию (лениво) — экономит память
- Имеет преимущества обоих миров: ленивость генератора + возможности последовательности
- В Python 2 был список, в Python 3 стал объектом диапазона
- Оптимизирован на уровне C — очень быстрый
- Используй для числовых последовательностей, генераторы для произвольных