Part 6

Чтение файлов

Очень распространенный случай использования программирования — обработка данных, хранящихся в файлах. Программы могут читать данные из файлов и записывать вычисленные результаты в файлы. Даже большие объемы данных становится легко обрабатывать автоматически, когда используются файлы.

В этом курсе мы работаем только с текстовыми файлами. Все используемые файлы будут состоять из строк текста. Например, редактор Visual Studio Code, который мы используем в этом курсе, работает с текстовыми файлами. Обратите внимание: хотя текстовые процессоры, такие как Microsoft Word, обычно используются с файлами, содержащими текст, сами документы Word не являются текстовыми файлами. Они также содержат информацию о форматировании и закодированы таким образом, который усложняет их обработку в программе.

Чтение данных из файла

Сначала поработаем с файлом example.txt со следующим содержимым:

Пример данных

Hello there! This example file contains three lines of text. This is the last line.

Простой способ использовать файлы в программе Python — оператор with. Заголовочная строка открывает файл, а затем следует блок, где к файлу можно обращаться. После блока файл автоматически закрывается, и к нему больше нельзя обращаться.

Итак, следующий код открывает файл, читает содержимое, выводит его и затем закрывает файл:

with open("example.txt") as new_file:
    contents = new_file.read()
    print(contents)
Пример вывода

Hello there! This example file contains three lines of text. This is the last line.

Переменная new_file выше — это дескриптор файла. Через него к файлу можно обращаться, пока он открыт. Здесь мы использовали метод read, который возвращает содержимое файла как одну строку. То есть в этом случае строка, возвращенная read, была бы такой:

"Hello there!\nThis example file contains three lines of text.\nThis is the last line."

Обход содержимого файла

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

Текстовые файлы можно представлять как списки строк, где каждая строка представляет одну строку в файле. По списку можно пройти циклом for.

Следующий пример читает наш пример файла с помощью цикла for, удаляет переносы строк из конца каждой строки, считает количество строк и выводит каждую строку с ее номером. Он также отслеживает длину строк:

with open("example.txt") as new_file:
    count = 0
    total_length = 0

    for line in new_file:
        line = line.replace("\n", "")
        count += 1
        print("Line", count, line)
        length = len(line)
        total_length += length

print("Total length of lines:", total_length)
Пример вывода

Line 1 Hello there! Line 2 This example file contains three lines of text. Line 3 This is the last line. Total length of lines: 81

В конце каждой строки в файле есть перенос строки \n, но функция print тоже по умолчанию добавляет перенос строки. В выводе выше нет лишних переносов строк, потому что переносы в конце строк удаляются методом replace. Он заменяет каждый символ переноса строки пустой строкой. Так длины строк тоже вычисляются правильно.

Loading

Что делать, если Visual Studio Code не может найти мой файл?

Когда вы выполняете код, Visual Studio Code может пожаловаться, что файл не найден, даже если вы проверили и перепроверили, что имя файла написано правильно и файл существует. Изменение следующей настройки может решить проблему:

  • Откройте настройки из строки меню: File -> Preferences -> Settings
  • Найдите нужную настройку по поисковому запросу "executeinfile"
  • Выберите вкладку Workspace
  • Выберите опцию в разделе Python -> Terminal -> Execute In File Dir

Окно настроек теперь должно выглядеть примерно так:

6 1 1

Если это не помогает, можно скопировать файл из директории src

6 1 2

напрямую в корень директории упражнения

6 1 3

Отладка кода, который работает с файлами

Использование отладчика Visual Studio Code с программами, которые обрабатывают файлы, часто приводит к неприятно выглядящему сообщению об ошибке:

6 1 4

Причина в том, что отладчик всегда ищет файлы в корне директории упражнения. Упомянутая выше настройка Execute In File Dir на это не влияет. Самое простое решение — просто скопировать файл в корень директории.

После копирования всех нужных файлов вам может понадобиться перезапустить Visual Studio Code.

Чтение CSV-файлов

CSV-файл, сокращение от comma-separated Values, — это текстовый файл, содержащий данные, разделенные заранее выбранным символом. Чаще всего для этой цели используются запятая , и точка с запятой ;, но в принципе возможен любой символ.

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

Мы уже узнали, что можем проходить по строкам файла циклом for, но как разделить разные поля в одной строке? В Python для этого есть строковый метод split. Метод принимает символ или символы-разделители как строковый аргумент и возвращает содержимое целевой строки как список строк, разделенный по этому разделителю.

Пример работы метода:

text = "monkey,banana,harpsichord"
words = text.split(",")
for word in words:
    print(word)
Пример вывода

monkey banana harpsichord

Предположим, у нас есть файл grades.csv, который содержит имена студентов и оценки, полученные ими на некоторых курсах. В каждой строке находятся данные одного студента, а данные разделены точкой с запятой.

Пример данных

Paul;5;4;5;3;4;5;5;4;2;4 Beth;3;4;2;4;4;2;3;1;3;3 Ruth;4;5;5;4;5;5;4;5;4;4

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

with open("grades.csv") as new_file:
    for line in new_file:
        line = line.replace("\n", "")
        parts = line.split(";")
        name = parts[0]
        grades = parts[1:]
        print("Name:", name)
        print("Grades:", grades)
Пример вывода

Name: Paul Grades: ['5', '4', '5', '3', '4', '5', '5', '4', '2', '4'] Name: Beth Grades: ['3', '4', '2', '4', '4', '2', '3', '1', '3', '3'] Name: Ruth Grades: ['4', '5', '5', '4', '5', '5', '4', '5', '4', '4']

Loading
Loading

Чтение одного и того же файла несколько раз

Иногда в одной программе необходимо обработать содержимое файла более одного раза. Рассмотрим программу, которая работает с персональными данными, хранящимися в CSV-файле:

Пример данных
Peter;40;Helsinki Emily;34;Espoo Eric;42;London Adam;100;Amsterdam Alice;58;Paris
with open("people.csv") as new_file:
    # print out the names
    for line in new_file:
        parts = line.split(";")
        print("Name:", parts[0])

    # find the oldest
    age_of_oldest = -1
    for line in new_file:
        parts = line.split(";")
        name = parts[0]
        age = int(parts[1])
        if age > age_of_oldest:
            age_of_oldest = age
            oldest = name
    print("the oldest is", oldest)

Запуск этого кода приведет к довольно загадочному сообщению об ошибке:

Traceback (most recent call last):
    print("the oldest is"; oldest)
UnboundLocalError: local variable 'oldest' referenced before assignment

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

Если мы хотим получить доступ к содержимому во втором цикле for, нам придется open файл второй раз:

with open("people.csv") as new_file:
    # print out the names
    for line in new_file:
        parts = line.split(";")
        print("Name:", parts[0])

with open("people.csv") as new_file:
    # find the oldest
    age_of_oldest = -1
    for line in new_file:
        parts = line.split(";")
        name = parts[0]
        age = int(parts[1])
        if age > age_of_oldest:
            age_of_oldest = age
            oldest = name
    print("the oldest is", oldest)

Хотя код выше будет работать, в нем есть ненужное повторение. Обычно лучше прочитать файл только один раз и сохранить его содержимое в подходящем формате для дальнейшей обработки:

people = []
# read the contents of the file and store it in a list
with open("people.csv") as new_file:
    for line in new_file:
        parts = line.split(";")
        people.append((parts[0], int(parts[1]), parts[2]))

# print out the names
for person in people:
    print("Name:", person[0])

# find the oldest
age_of_oldest = -1
for person in people:
    name = person[0]
    age = person[1]
    if age > age_of_oldest:
        age_of_oldest = age
        oldest = name
print("the oldest is", oldest)

Дополнительная обработка CSV-файлов

Продолжим с файлом grades.csv, который имеет следующее содержимое:

Пример данных

Paul;5;4;5;3;4;5;5;4;2;4 Beth;3;4;2;4;4;2;3;1;3;3 Ruth;4;5;5;4;5;5;4;5;4;4

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

grades = {}
with open("grades.csv") as new_file:
    for line in new_file:
        line = line.replace("\n", "")
        parts = line.split(";")
        name = parts[0]
        grades[name] = []
        for grade in parts[1:]:
            grades[name].append(int(grade))

print(grades)
Пример вывода

{'Paul': [5, 4, 5, 3, 4, 5, 5, 4, 2, 4], 'Beth': [3, 4, 2, 4, 4, 2, 3, 1, 3, 3], 'Ruth': [4, 5, 5, 4, 5, 5, 4, 5, 4, 4]}

Теперь мы можем вывести некоторую статистику по каждому студенту на основе содержимого словаря grades:

for name, grade_list in grades.items():
    best = max(grade_list)
    average = sum(grade_list) / len(grade_list)
    print(f"{name}: best grade {best}, average {average:.2f}")
Пример вывода

Paul: best grade 5, average 4.10 Beth: best grade 4, average 2.90 Ruth: best grade 5, average 4.50

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

Удаление ненужных строк, пробелов и переносов строк

Предположим, у нас есть CSV-файл с некоторыми именами, экспортированный из Excel:

first; last
Paul; Python
Jean; Java
Harry; Haskell

Excel известен тем, что добавляет лишние пробелы. Здесь у нас есть лишний пробел между элементами после каждой точки с запятой.

Мы хотим вывести фамилии каждого человека в списке. Первая строка содержит заголовки данных, и ее можно безопасно игнорировать:

last_names = []
with open("people.csv") as new_file:
    for line in new_file:
        parts = line.split(";")
        # ignore the header line
        if parts[0] == "first":
            continue
        last_names.append(parts[1])

print(last_names)

Выполнение этого кода выведет

Пример вывода

[' Python\n', ' Java\n', ' Haskell']

У первых двух элементов в конце есть символ переноса строки, а у всех трех есть лишний пробел в начале.

Мы уже использовали метод replace для удаления лишних пробельных символов, но более эффективное решение — использовать строковый метод Python strip, который удаляет пробельные символы из начала и конца строки. Он удаляет все пробелы, переносы строк, табуляции и другие символы, которые обычно не выводятся.

Можно попробовать его в консоли Python:

>>> " tryout ".strip()
'tryout'
>>> "\n\ntest\n".strip()
'test'
>>>

Для очистки строки нужно лишь небольшое изменение программы:

last_names = []
with open("people.csv") as new_file:
    for line in new_file:
        parts = line.split(';')
        if parts[0] == "first":
            continue # this was the header line, so it is ignored
        last_names.append(parts[1].strip())
print(last_names)

Теперь у нас есть желаемый аккуратный вывод:

Пример вывода

['Python', 'Java', 'Haskell']

Есть также связанные строковые методы lstrip и rstrip. Они удаляют только начальные или конечные непечатаемые символы: l — для левого края строки, r — для правого:

>>> " teststring  ".rstrip()
' teststring'
>>> " teststring  ".lstrip()
'teststring  '

Объединение данных из разных файлов

Очень часто данные, обрабатываемые программой, разбросаны по нескольким файлам. Рассмотрим ситуацию, где персональные данные сотрудников компании хранятся в файле employees.csv:

pic;name;address;city
080488-123X;Pekka Mikkola;Vilppulantie 7;00700 Helsinki
290274-044S;Liisa Marttinen;Mannerheimintie 100 A 10;00100 Helsinki
010479-007Z;Arto Vihavainen;Pihapolku 4;01010 Kerava
010499-345K;Leevi Hellas;Tapiolantie 11 B;02000 Espoo

Зарплаты хранятся в отдельном файле salaries.csv:

pic;salary;bonus
080488-123X;3300;0
290274-044S;4150;200
010479-007Z;1300;1200

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

Пример вывода
incomes:
Pekka Mikkola    3300 euros
Liisa Marttinen  4350 euros
Arto Vihavainen  2500 euros

Эта программа использует два словаря как вспомогательные структуры данных: names и salaries. Оба используют PIC как ключ:

names = {}

with open("employees.csv") as new_file:
    for line in new_file:
        parts = line.split(';')
        if parts[0] == "pic":
            continue
        names[parts[0]] = parts[1]

salaries = {}

with open("salaries.csv") as new_file:
    for line in new_file:
        parts = line.split(';')
        if parts[0] == "pic":
            continue
        salaries[parts[0]] = int(parts[1]) +int(parts[2])

print("incomes:")

for pic, name in names.items():
    if pic in salaries:
        salary = salaries[pic]
        print(f"{name:16} {salary} euros")
    else:
        print(f"{name:16} 0 euros")

Сначала программа создает словари names и salaries. Они имеют следующее содержимое:

{
    '080488-123X': 'Pekka Mikkola',
    '290274-044S': 'Liisa Marttinen',
    '010479-007Z': 'Arto Vihavainen',
    '010499-345K': 'Leevi Hellas'
}

{
    '080488-123X': 3300,
    '290274-044S': 4350,
    '010479-007Z': 2500
}

Цикл for в конце программы объединяет имена сотрудников с соответствующими зарплатами.

Программа также учитывает ситуации, где PIC сотрудника отсутствует в файле зарплат.

Помните, что порядок хранения элементов в словаре не имеет значения, потому что ключи обрабатываются на основе хеш-значений.

Loading
Loading
Loading
Loading
Loading
Loading
Вы дошли до конца этого раздела! Перейти к следующему разделу:

Текущие баллы можно посмотреть в синем индикаторе в правом нижнем углу страницы.