Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как работают генераторы в Python
Генераторы — это мощная и элегантная функция Python, которые позволяют создавать итераторы более удобным способом. Они являются основой для работы с большими объёмами данных, так как позволяют работать с данными лениво (по мере необходимости), а не загружать всё в память сразу.
Что такое генератор?
Генератор — это функция, которая содержит одно или несколько выражений yield. Когда такая функция вызывается, она возвращает генератор-объект, а не результат функции.
def simple_generator():
yield 1
yield 2
yield 3
gen = simple_generator()
print(gen) # <generator object simple_generator at 0x...>
print(type(gen)) # <class 'generator'>
Как работает генератор?
Шаг за шагом:
- Функция-генератор не выполняется немедленно
- Вызов функции возвращает объект генератора
- При первом вызове
next()функция выполняется до первогоyield yieldвозвращает значение и приостанавливает функцию- При следующем вызове
next()функция продолжает выполнение послеyield - Когда функция завершается, выбрасывается
StopIteration
def counter_gen():
print("Starting generator")
yield 1
print("Continuing after 1")
yield 2
print("Continuing after 2")
yield 3
print("Done")
gen = counter_gen()
print(next(gen)) # Starting generator, returns 1
print(next(gen)) # Continuing after 1, returns 2
print(next(gen)) # Continuing after 2, returns 3
print(next(gen)) # Done, raises StopIteration
Использование в циклах
Часто генераторы используются в циклах for, которые автоматически вызывают next() и обрабатывают StopIteration:
def squares(n):
for i in range(n):
yield i * i
for square in squares(5):
print(square) # 0, 1, 4, 9, 16
Преимущества генераторов
1. Ленивое вычисление (Lazy Evaluation) Генератор вычисляет значения только когда они нужны:
# Плохо: создаёт список в памяти
def create_list(n):
result = []
for i in range(n):
result.append(i * 2)
return result
big_list = create_list(1000000) # Загружается вся память
# Хорошо: генератор
def create_gen(n):
for i in range(n):
yield i * 2
gen = create_gen(1000000) # Ничего не вычисляется!
for value in gen:
print(value) # Вычисляется по одному значению
2. Экономия памяти
# Для обработки 1 млн записей из БД
def fetch_records(db, query):
cursor = db.execute(query)
for row in cursor:
yield row # Одна запись в памяти, не всех млн
for record in fetch_records(db, "SELECT * FROM big_table"):
process(record)
3. Чистый и читаемый код
def fibonacci(n):
a, b = 0, 1
for _ in range(n):
yield a
a, b = b, a + b
for num in fibonacci(10):
print(num)
Практические примеры для Data Engineer
1. Чтение больших файлов
def read_large_csv(filepath, chunk_size=1000):
with open(filepath) as f:
chunk = []
for line in f:
chunk.append(line.strip().split(','))
if len(chunk) == chunk_size:
yield chunk
chunk = []
if chunk:
yield chunk
for batch in read_large_csv('huge_file.csv'):
process_batch(batch) # Обрабатываем батч за батчем
2. Обработка потока данных
def process_stream(data_stream):
for data in data_stream:
if data['valid']:
transformed = transform(data)
yield transformed
for clean_data in process_stream(incoming_data):
save_to_db(clean_data)
3. Бесконечные последовательности
def infinite_counter():
i = 0
while True:
yield i
i += 1
counter = infinite_counter()
for _ in range(5):
print(next(counter)) # 0, 1, 2, 3, 4
Выражения-генераторы (Generator Expressions)
Аналогично list comprehension, но возвращает генератор:
# List comprehension (загружает в память)
squares_list = [x**2 for x in range(1000000)]
# Generator expression (лениво)
squares_gen = (x**2 for x in range(1000000))
for square in squares_gen:
print(square) # Вычисляется по одному
Send и Throw
Генераторы могут получать значения через send() и обрабатывать исключения через throw():
def echo_gen():
while True:
value = yield
print(f"Received: {value}")
gen = echo_gen()
next(gen) # Инициализируем
gen.send("Hello") # Received: Hello
gen.close() # Закрываем генератор
Производительность
import sys
# Сравнение памяти
list_of_ints = [i for i in range(100000)]
print(sys.getsizeof(list_of_ints)) # Много МБ
gen_of_ints = (i for i in range(100000))
print(sys.getsizeof(gen_of_ints)) # Несколько байт!
Best Practices для Data Engineer
1. Используй генераторы для больших данных
# Плохо: может не хватить памяти
all_records = load_all_from_db()
for record in all_records:
process(record)
# Хорошо: потоковая обработка
for record in load_from_db_streaming():
process(record)
2. Комбинируй генераторы
def read_files(patterns):
for pattern in patterns:
for filepath in glob.glob(pattern):
yield filepath
def read_csvs(patterns):
for filepath in read_files(patterns):
for row in read_csv(filepath):
yield row
for row in read_csvs(['data/*.csv', 'archive/*.csv']):
process(row)
3. Обработка ошибок
def safe_generator(data_source):
for item in data_source:
try:
yield process(item)
except ProcessingError as e:
logger.error(f"Error: {e}")
continue
Выводы
Генераторы — это фундаментальный инструмент для работы с большими объёмами данных в Python. Они обеспечивают ленивое вычисление, экономят память и делают код чище. Для Data Engineer'ов это критически важно при работе с большими файлами, потоками данных и обработкой миллионов записей без перегрузки памяти.