Допустим, у нас есть текстовый файл student_records.txt. Каждая строка содержит имя и возраст учащегося. Мы хотим проанализировать имена учащихся и их возраст.

#student_records.txt
greg:15
matthew:14
ram:16
raju:14

Нашим первым подходом было бы чтение файлов и сохранение значений в списке / словаре.

def read_student_records(path):
  records = []
  with open(path) as file:
    for line in file:
      name, age = line.split(":")
      records.append((name, age))
  return records
for record in read_student_records(path):
  print(record)  # do something with record

Это выполняет свою работу. Однако функция read_student_records () беспорядочная. Что, если нам нужно читать из нескольких файлов?

Давайте очистим наш код выше и сделаем его более модульным.

def lines_from_file(path):
  lines = []
  with open(path) as file:
    for line in file:
      lines.append(line)
  return lines
def student_records(lines):
  records = []
  for line in lines:
    name, age = line.split(":")
    records.append((name, age))
  return records
def student_records_from_file(path):
  lines = lines_from_file(path)  # 1
  records = student_records(lines)  # 2
  return records
# read from multiple files and do something with it
def student_records_from_files(filenames):
  records = []
  for filename in filenames:
    records_from_file = student_records_from_file(filename)  # 3
    records.extend(records_from_file)
  return records
filenames = ['r1.txt', 'r2.txt']
records = student_records_from_multiple_files(filenames)
for record in records:
  print(record)  # do something with record

Вышеупомянутые функции говорят сами за себя.

  • Функция lines_from_file считывает файл построчно, сохраняет его в списке и возвращает.
  • student_records читает список строк, извлекает имя и возраст из каждой строки и сохраняет его в списке.
  • student_records_from_file объединяет lines_from_file и student_records.
  • student_records_from_files вызывает student_records_from_file, сохраняет записи и возвращает их.

Кажется, все в порядке? Не могли бы вы выяснить некоторые проблемы в приведенном выше коде? Я буду ждать.

В функции student_records_from_file

  • # 1 блоки, пока не будут прочитаны все строки.
  • # 2 блока для разбора каждой записи.
  • # 3 блока для чтения всех записей из файла перед переходом к следующему.

В каждой из этих функций мы храним данные и выбрасываем их, когда возвращаем значения другой функции.

Что, если в некоторых файлах содержится 1 миллиард записей или более?

Нам не нужно хранить все строки в памяти перед их анализом. Желательно перебирать каждую строку и анализировать их, по одной строке в памяти за раз.

Генераторы помогут вам в этом. Это очень полезный инструмент для создания итераторов. Генераторы отличаются от обычных функций Python тем, что они yield вместо return.

Итератор создает значения в последовательности, по одному за раз. Вызов next () на итераторе возвращает следующее значение в последовательности.

А теперь вернемся к генераторам. Есть разница между функцией генератора и объектом-генератором. Когда вы определяете функцию, которая «дает», а не «возвращает», это функция-генератор. Когда вы вызываете функцию-генератор, создается объект-генератор. Выполнение не происходит до тех пор, пока для объекта-генератора не будет вызвана функция next ().

Давайте сейчас перепишем наш код с помощью генераторов.

def lines_from_file(path):
  with open(path) as file:
    for line in file:
      yield line
def student_records(lines):
  for line in lines:
    name, age = line.split(":")
    yield name, age
def student_records_from_file(path):
  lines = lines_from_file(path)
  yield from student_records(lines)
def student_records_from_files(filenames):
  for file in filenames:
    yield from student_records_from_file(file)  # 4

yield from - это ярлык для получения данных из другого объекта-генератора:

#4 can be also be written as
for record in student_records_from_file(file):
  yield record

Теперь вы можете перебирать записи учеников одну за другой и что-то с ними делать.

for record in student_records_from_files(filenames):
    print(record) #do something with record

Это приводит к простой модульной конструкции. Каждая функция отвечает за выполнение одной задачи, поэтому она оптимизирована для повторного использования. Кроме того, он занимает мало места в памяти, поскольку в каждый момент времени в памяти хранится одна запись об учащемся.