Программирование на Python

Файлы

Разбить на страницы
Показывать лекцию целиком

Для решения задач, возникающих в прикладной области, программист ищет подходящий алгоритм и подходящие структуры данных. Списки, кортежи, словари, множества - эти структуры данных используются во многих задачах при реализации алгоритмов. Конечно, часто используются и другие структуры данных, задаваемые в пакетах Python или специально спроектированные с учетом особенности решаемой задачи. Все эти структуры данных создаются в процессе выполнения программы и исчезают по окончании сеанса работы с программой.

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

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

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

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

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

Создание файла. Открытие и закрытие файла

Когда мы говорим о файлах, то следует различать:

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

    Файл, как объект языка, создается при вызове метода open. В процессе выполнения метода устанавливается связь между логическим и физическим файлами.

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

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

    r - от (read) файл открывается для чтения. Физический файл должен существовать в указанном месте.

    w - от (write) физический файл открывается для записи. Если файл существует, то он перезаписывается, - старые записи удаляются. Если файл не существует, то он создается;

    a - от (append) файл открывается для добавления в конец файла. Новые записи добавляются в конец существующего файла. Если файл не существует, то он создается.

    x - новый файл, создаваемый для записи. Если файл существует, то возникнет ошибка.

    b - от (binary) задает тип бинарного файла. Символ задается в комбинации с другими символами статуса. Комбинация 'b + r' или просто 'br' открывает существующий бинарный файл для чтения. Комбинация 'b + w' ('bw') в существующем файле удаляет содержимое, так что длина физического файла становится равной нулю байтов.

    t - от (text) задает тип текстового файла. По умолчанию файл считается текстовым и имеет статус 'rt'.

    Открыв файл, в зависимости от статуса, над ним выполняются те или иные операции по чтению или записи данных. Для текстовых файлов работа идет со строками текста, для бинарных файлов - с байтами. По окончании работы следует файл закрыть, выполнив метод close.

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

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

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

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

    #Работа с текстовыми файлами
    def test1():
        #Открытие файлов
        #запись
        one = open("One.txt", 'w')
        one.write("запись_1" + "\n")
        one.write("запись_2" + "\n")
        one.close()
        print("файл One создан!")
        #чтение
        one = open("One.txt", 'r')
        print(one.readline())
        print(one.readline()) 
        one.close()
        #перезапись
        one = open("One.txt", 'w')
        one.write("запись_3" + "\n")
        one.write("запись_4" + "\n")
        one.close()
        one = open("One.txt", 'r')
        print(one.readline())
        print(one.readline())
        one.close()
        #создание нового файла
        two = open("Two.txt", 'x')
        two.write("запись_5" + "\n")
        two.write("запись_6" + "\n")
        two.close()
        print("файл Two создан!")
        #дозапись
        two = open("Two.txt", 'a')
        two.write("запись_7"+ "\n")
        two.write("запись_8" + "\n")
        two.close() 
        #чтение
        two = open("Two.txt", 'r')
        print(two.readline())
        print(two.readline())
        print(two.readline())
        print(two.readline())
        two.flush()
        two.close()
    test1()
    

    Результаты работы:

    В каталоге, где хранится программный код, созданы два текстовых файла с именами One и Two. В файле One две строки: запись_3 и запись_4. В файле Two - четыре строки.

    Заметьте, при повторном запуске этого теста возникнет ошибка, если предварительно не удалить созданный файл Two.

    Операции над файлами. Чтение и запись

    Текстовый файл можно рассматривать как последовательность записей файла, каждая из которых представляет строку текста, имеющую тип str.

    Запись в файл осуществляется методом write. У метода один аргумент типа str, представляющий запись, добавляемую в файл. При записи данных в текстовый файл, автоматического преобразования данных в строку, записываемую в файл, не происходит. Если данные не принадлежат типу str, то перед записью их необходимо преобразовать в строку, заканчивающуюся символом окончания строки. Так что вся работа по преобразованию данных разного типа в строку текста возлагается на программиста. Метод write эту работу не делает. Более того, если мы хотим, чтобы каждое выполнение метода write создавало новую запись, то строку текста следует заканчивать символом \n - перехода на новую строку. В противном случае метод write будет дописывать строку в конец предыдущей записи файла.

    Метод writelines позволяет записать в файл группу записей. Аргументом метода является список, все элементы которого должны быть строками текста - str объектами - и должны заканчиваться символом конца строки.

    Не имеет особого смысла специально создавать список строк, чтобы затем этот список записать в файл одним вызовом метода writelines. Однако, если по ходу решения задачи такой список создается, то применение групповой записи вполне оправдано.

    Приведу пример записи в файл с использованием методов write и writelines:

    def Test2():
        # Запись в файл
        three = open("Three.txt", 'w')
        print(type(three))
        three.write("запись_1\n")
        three.write("запись_2\n")
        list = ["запись_3\n", "запись_4\n", "запись_5\n"]
        three.writelines(list)
        three.flush()
        three.close()
        print("Файл Three создан")
    Test2() 
    

    Результаты:

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

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

    Метод readline - метод построчного чтения. У метода нет аргументов, метод читает текущую запись файла, и возвращает в качестве результата прочитанную строку. Текущей становится очередная запись файла. После прочтения последней строки файла, текущей становится пустая строка. Повторное применение метода после прочтения всех его записей будет возвращать пустую строку.

    Метод readlines - метод группового чтения. У метода есть один возможный аргумент - hint. Если аргумент не задан, то метод читает записи файла, начиная с текущей до последней записи файла, возвращая в качестве результата список строк. Каждый элемент в этом списке представляет прочитанную запись файла. Аргумент hint позволяет ограничить число читаемых записей, - размер прочитанных записей (в байтах) не должен превосходить значения аргумента. Нужно понимать, что аргумент hint задается в байтах, а не в количестве записей.

    Есть смысл поговорить об особенностях чтения файлов Python.

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

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

    Для текстовых файлов, у которых строки имеют разную длину вычисление адреса нужной строки затруднительно. Однако, ничто не запрещает рассматривать текстовый файл как бинарный файл, состоящий из байтов, поскольку каждая строка - это последовательность байтов. Аргумент hint в методе readlines как раз исходит из этой точки зрения. Метод seek, ориентированный на работу с бинарными файлами, применим и к текстовым файлам, задавая смещение, конечно же, в байтах, а не количестве записей. Формально возможно организовать прямой доступ к записям текстового файла при его чтении. Чаще всего, этого делать не следует. Текстовый файл следует рассматривать как структуру с последовательным доступом, оставив возможности прямого доступа для бинарных файлов.

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

      
    def Test3():
        # Чтение файла
        three = open("Three.txt")
        print("Файл Three открывается для чтения")
        first = three.readline()
        print("first = ", first)
        all = three.read()
        all_1 = three.read()
        all_2 = three.read()
        print("all = ", all)
        print(" all_1 = " , all_1, " all_2 = " , all_2) 
        three.flush()
        three.close()
        print("Файл Three прочитан и закрыт")
        three = open("Three.txt")
        for i in range(0, 7):
            r = three.readline()
            print("r" + str(i) + " = ", r)
        three.close()
        three = open("Three.txt")
        list = three.readlines(20)
        print(list)
        list1 = three.readlines(20)
        print(list1)
        list2 = three.readlines(20)
        print(list2)
        three.close()
    Test3()
    

    Результаты:

    Файл как итерируемый объект

    В наших примерах файлы содержали до десятка записей. Для учебных целей этого вполне достаточно. Реальные же файлы содержат миллионы записей. Если файл относительно небольшой, то его можно прочитать полностью методом read в оперативную память и далее работать со строкой, содержащей все записи файла. Чаще всего, записи в файле читаются в цикле последовательно одна за одной. Цикл заканчивается, либо когда будут прочитаны все нужные записи, либо, когда будут прочитаны все записи и достигнут конец файла.

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

    def Test4():
        #Схема чтения файла (на примере чтения файла Three)
        three = open("Three.txt")
        end_reading = False
        while not end_reading:
            record = three.readline()
            if Condition(record):
                Working(record)
            else:
                end_reading = True
        three.close()
    Test4( )
    

    Конечно, предварительно нужно определить булевскую функцию Condition, которая возвращает True, если запись требует обработки, False - если чтение записей файла следует прекратить, поскольку все нужные записи уже обработаны. Процедура Working обрабатывает текущую запись. В случае, когда нужно обработать все записи файла, функция Condition записывается в одну строчку. Процедура Working в нашем примере будет сводиться к печати записи. Вот текст этих методов:

    def Condition(record):
        #Чтение файла полностью
        return not record == '' 
    def Working(record):
        #Обработка записи
    print(record) 
    

    Результаты работа теста Test4():

    На практике при чтении файла применяется более простая схема, основанная на том, что текстовый файл является итерируемым объектом, так что к нему применим оператор for для чтения всех записей файла. Ранее я говорил, что при чтении записей файла есть некоторый указатель, устанавливаемый на начало текущей записи файла, перемещающийся к началу следующей записи при каждом выполнении метода readline. Это позволяет при работе с файлом иметь внутренний метод Next, возвращающий при каждом вызове очередную запись. Так что файл является итерируемым объектом, таким же как все структуры данных, являющиеся потомками последовательности. Так что задача чтения файла, решаемая по схеме, реализованной в тесте Test4, может быть решена значительно проще, используя тот факт, что файл является итерируемым объектом и к нему применим цикл for. Вот тест, демонстрирующий эту возможность:

    def Test5():
        #Чтение файла как итерируемого объекта
        three = open("Three.txt")
        for record in three:
            print(record)
        three.close()
    Test5() 
    

    Результаты работы, естественно те же, что и у теста Test4.

    Запись в файл и чтение данных разных типов

    Файлы предназначены для хранения данных самых разных типов. Но в языке Python, как мы знаем, метод write позволяет писать в файл только строки - объекты класса str, а методы чтения создают объект этого же класса. Никакие преобразования типов при выполнении этих методов не проводятся. На программиста возлагается обязанность перед записью объекта типа Т преобразовать объект в строку - привести к типу str, а после чтения строки из файла выполнить обратное преобразование - приведение строки к исходному типу Т.

    Задача приведения типа Т к типу str (Т => str) для простых типов (int, float, bool) и сложных типов (list, dict, set, tuple) решается просто - функция str объект типа Т приводит к типу str. Так что никаких сложностей не возникает при записи данных разных типов в файл. Приведу пример:

    def Test6():
        # Создание текстового файла, хранящего данные разных типов:
        # числа, логические значения, списки, словари, множества, кортежи
        r1 = 33
        r2 = 7.77
        r3 = True
        r4 = [33, 7.77, 'four', [1, 2, 3]]
        r5 = {'one' : 1, 'two': 2, 'three' : 3}
        r6 = { 1, 2, 3, 3}
        r7 = (1, 2, 3, 3)
        four = open("Four.txt", 'w') 
        four.write(str(r1) + '\n')
        four.write(str(r2) + '\n')
        four.write(str(r3) + '\n')
        four.write(str(r4) + '\n')
        four.write(str(r5) + '\n')
        four.write(str(r6) + '\n')
        four.write(str(r7) + '\n')
        four.close()
        print('Файл Four создан')
    Test6() 
    

    В результате выполнения этого теста успешно будет создан файл, каждая запись которого содержит объект некоторого типа, приведенный к типу str.

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

    На практике часто бывает, что файл хранит данные простых типов. Для простых типов все интуитивно понятно. Пусть x - объект типа int. Тогда справедливо: x = int(str(x) . Пусть x - объект типа float. Тогда справедливо: x = float(str(x) . Пусть x - объект типа bool. Тогда справедливо: x = bool(str(x) . Функции int, float, bool выполняют обратное приведение к простому типу.

    Со сложными типами все сложнее. Хотя для сложных типов также существуют функции приведения: list, dict, set, tuple, - они не решают обратную задачу. Пусть x - объект типа list. Тогда в результате приведения y = list(str(x) получим объект y, не совпадающий с исходным объектом x. Обратное приведение не работает для всех сложных типов. Как же решить данную задачу? Решение есть! В языке Python, благодаря его интерпретирующему характеру исполнения программ, существует мощный инструмент - функция eval (evaluation - вычисление). Если на вход этой функции подать строку - некоторый текст, представляющий программу Python, то функция выполнит эту программу. В частном случае справедливо следующее утверждение. Пусть x - объект произвольного типа T. Тогда справедливо: x = eval(str(x) . Так что для объектов любых типов - простых и сложных, функция eval выполняет обратное преобразование.

    Я все-таки рекомендую использовать эту функцию для сложных типов, а для простых типов применять более простые инструменты приведения.

    Давайте рассмотрим пример чтения файла four с последующим приведением записей к нужному типу:

    def Test7():
        #Чтение текстового файла,хранящего данные разных типов:
        # числа, логические значения, списки, словари, множества, кортежи
        four = open("Four.txt")
        record = four.readline()
        r1 = int(record)
        print(r1)
        record = four.readline()
        r2 = float(record)
        print(r2)
        record = four.readline()
        r3 = bool(record)
        print(r3)
        record = four.readline()    
        r4 = eval(record)
        print(r4)
        record = four.readline()    
        r5 = eval(record)
        print(r5)
        record = four.readline()
        r6 = eval(record)
        print(r6)
        record = four.readline()
        r7 = eval(record)
        print(r7)
        four.close()
    Test7()
    

    Результаты:

    Итоги

    Файлы позволяют сохранять результаты вычислений в долговременной памяти и использовать файлы как источник входных данных. Файлы Python могут быть двух типов - текстовые и бинарные. Записями текстового файла являются строки, бинарного - последовательности байтов. Логический файл как объект языка создается при вызове метода open. Методы write и readline позволяют записывать данные в текстовый файл и читать записи файла. Эти операции выполняются над строками - объектами класса str. Приведение объектов разных типов к типу str (T => str) , также как и обратное приведение типа (str => T) выполняется в Python достаточно просто.

    Страницы:

    Для решения задач, возникающих в прикладной области, программист ищет подходящий алгоритм и подходящие структуры данных. Списки, кортежи, словари, множества - эти структуры данных используются во многих задачах при реализации алгоритмов. Конечно, часто используются и другие структуры данных, задаваемые в пакетах Python или специально спроектированные с учетом особенности решаемой задачи. Все эти структуры данных создаются в процессе выполнения программы и исчезают по окончании сеанса работы с программой.

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

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

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

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

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

    Создание файла. Открытие и закрытие файла

    Когда мы говорим о файлах, то следует различать:

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

    Файл, как объект языка, создается при вызове метода open. В процессе выполнения метода устанавливается связь между логическим и физическим файлами.

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

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

    r - от (read) файл открывается для чтения. Физический файл должен существовать в указанном месте.

    w - от (write) физический файл открывается для записи. Если файл существует, то он перезаписывается, - старые записи удаляются. Если файл не существует, то он создается;

    a - от (append) файл открывается для добавления в конец файла. Новые записи добавляются в конец существующего файла. Если файл не существует, то он создается.

    x - новый файл, создаваемый для записи. Если файл существует, то возникнет ошибка.

    b - от (binary) задает тип бинарного файла. Символ задается в комбинации с другими символами статуса. Комбинация 'b + r' или просто 'br' открывает существующий бинарный файл для чтения. Комбинация 'b + w' ('bw') в существующем файле удаляет содержимое, так что длина физического файла становится равной нулю байтов.

    t - от (text) задает тип текстового файла. По умолчанию файл считается текстовым и имеет статус 'rt'.

    Открыв файл, в зависимости от статуса, над ним выполняются те или иные операции по чтению или записи данных. Для текстовых файлов работа идет со строками текста, для бинарных файлов - с байтами. По окончании работы следует файл закрыть, выполнив метод close.

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

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

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

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

    #Работа с текстовыми файлами
    def test1():
        #Открытие файлов
        #запись
        one = open("One.txt", 'w')
        one.write("запись_1" + "\n")
        one.write("запись_2" + "\n")
        one.close()
        print("файл One создан!")
        #чтение
        one = open("One.txt", 'r')
        print(one.readline())
        print(one.readline()) 
        one.close()
        #перезапись
        one = open("One.txt", 'w')
        one.write("запись_3" + "\n")
        one.write("запись_4" + "\n")
        one.close()
        one = open("One.txt", 'r')
        print(one.readline())
        print(one.readline())
        one.close()
        #создание нового файла
        two = open("Two.txt", 'x')
        two.write("запись_5" + "\n")
        two.write("запись_6" + "\n")
        two.close()
        print("файл Two создан!")
        #дозапись
        two = open("Two.txt", 'a')
        two.write("запись_7"+ "\n")
        two.write("запись_8" + "\n")
        two.close() 
        #чтение
        two = open("Two.txt", 'r')
        print(two.readline())
        print(two.readline())
        print(two.readline())
        print(two.readline())
        two.flush()
        two.close()
    test1()
    

    Результаты работы:

    В каталоге, где хранится программный код, созданы два текстовых файла с именами One и Two. В файле One две строки: запись_3 и запись_4. В файле Two - четыре строки.

    Заметьте, при повторном запуске этого теста возникнет ошибка, если предварительно не удалить созданный файл Two.

    Операции над файлами. Чтение и запись

    Текстовый файл можно рассматривать как последовательность записей файла, каждая из которых представляет строку текста, имеющую тип str.

    Запись в файл осуществляется методом write. У метода один аргумент типа str, представляющий запись, добавляемую в файл. При записи данных в текстовый файл, автоматического преобразования данных в строку, записываемую в файл, не происходит. Если данные не принадлежат типу str, то перед записью их необходимо преобразовать в строку, заканчивающуюся символом окончания строки. Так что вся работа по преобразованию данных разного типа в строку текста возлагается на программиста. Метод write эту работу не делает. Более того, если мы хотим, чтобы каждое выполнение метода write создавало новую запись, то строку текста следует заканчивать символом \n - перехода на новую строку. В противном случае метод write будет дописывать строку в конец предыдущей записи файла.

    Метод writelines позволяет записать в файл группу записей. Аргументом метода является список, все элементы которого должны быть строками текста - str объектами - и должны заканчиваться символом конца строки.

    Не имеет особого смысла специально создавать список строк, чтобы затем этот список записать в файл одним вызовом метода writelines. Однако, если по ходу решения задачи такой список создается, то применение групповой записи вполне оправдано.

    Приведу пример записи в файл с использованием методов write и writelines:

    def Test2():
        # Запись в файл
        three = open("Three.txt", 'w')
        print(type(three))
        three.write("запись_1\n")
        three.write("запись_2\n")
        list = ["запись_3\n", "запись_4\n", "запись_5\n"]
        three.writelines(list)
        three.flush()
        three.close()
        print("Файл Three создан")
    Test2() 
    

    Результаты:

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

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

    Метод readline - метод построчного чтения. У метода нет аргументов, метод читает текущую запись файла, и возвращает в качестве результата прочитанную строку. Текущей становится очередная запись файла. После прочтения последней строки файла, текущей становится пустая строка. Повторное применение метода после прочтения всех его записей будет возвращать пустую строку.

    Метод readlines - метод группового чтения. У метода есть один возможный аргумент - hint. Если аргумент не задан, то метод читает записи файла, начиная с текущей до последней записи файла, возвращая в качестве результата список строк. Каждый элемент в этом списке представляет прочитанную запись файла. Аргумент hint позволяет ограничить число читаемых записей, - размер прочитанных записей (в байтах) не должен превосходить значения аргумента. Нужно понимать, что аргумент hint задается в байтах, а не в количестве записей.

    Есть смысл поговорить об особенностях чтения файлов Python.

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

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

    Для текстовых файлов, у которых строки имеют разную длину вычисление адреса нужной строки затруднительно. Однако, ничто не запрещает рассматривать текстовый файл как бинарный файл, состоящий из байтов, поскольку каждая строка - это последовательность байтов. Аргумент hint в методе readlines как раз исходит из этой точки зрения. Метод seek, ориентированный на работу с бинарными файлами, применим и к текстовым файлам, задавая смещение, конечно же, в байтах, а не количестве записей. Формально возможно организовать прямой доступ к записям текстового файла при его чтении. Чаще всего, этого делать не следует. Текстовый файл следует рассматривать как структуру с последовательным доступом, оставив возможности прямого доступа для бинарных файлов.

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

      
    def Test3():
        # Чтение файла
        three = open("Three.txt")
        print("Файл Three открывается для чтения")
        first = three.readline()
        print("first = ", first)
        all = three.read()
        all_1 = three.read()
        all_2 = three.read()
        print("all = ", all)
        print(" all_1 = " , all_1, " all_2 = " , all_2) 
        three.flush()
        three.close()
        print("Файл Three прочитан и закрыт")
        three = open("Three.txt")
        for i in range(0, 7):
            r = three.readline()
            print("r" + str(i) + " = ", r)
        three.close()
        three = open("Three.txt")
        list = three.readlines(20)
        print(list)
        list1 = three.readlines(20)
        print(list1)
        list2 = three.readlines(20)
        print(list2)
        three.close()
    Test3()
    

    Результаты:

    Файл как итерируемый объект

    В наших примерах файлы содержали до десятка записей. Для учебных целей этого вполне достаточно. Реальные же файлы содержат миллионы записей. Если файл относительно небольшой, то его можно прочитать полностью методом read в оперативную память и далее работать со строкой, содержащей все записи файла. Чаще всего, записи в файле читаются в цикле последовательно одна за одной. Цикл заканчивается, либо когда будут прочитаны все нужные записи, либо, когда будут прочитаны все записи и достигнут конец файла.

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

    def Test4():
        #Схема чтения файла (на примере чтения файла Three)
        three = open("Three.txt")
        end_reading = False
        while not end_reading:
            record = three.readline()
            if Condition(record):
                Working(record)
            else:
                end_reading = True
        three.close()
    Test4( )
    

    Конечно, предварительно нужно определить булевскую функцию Condition, которая возвращает True, если запись требует обработки, False - если чтение записей файла следует прекратить, поскольку все нужные записи уже обработаны. Процедура Working обрабатывает текущую запись. В случае, когда нужно обработать все записи файла, функция Condition записывается в одну строчку. Процедура Working в нашем примере будет сводиться к печати записи. Вот текст этих методов:

    def Condition(record):
        #Чтение файла полностью
        return not record == '' 
    def Working(record):
        #Обработка записи
    print(record) 
    

    Результаты работа теста Test4():

    На практике при чтении файла применяется более простая схема, основанная на том, что текстовый файл является итерируемым объектом, так что к нему применим оператор for для чтения всех записей файла. Ранее я говорил, что при чтении записей файла есть некоторый указатель, устанавливаемый на начало текущей записи файла, перемещающийся к началу следующей записи при каждом выполнении метода readline. Это позволяет при работе с файлом иметь внутренний метод Next, возвращающий при каждом вызове очередную запись. Так что файл является итерируемым объектом, таким же как все структуры данных, являющиеся потомками последовательности. Так что задача чтения файла, решаемая по схеме, реализованной в тесте Test4, может быть решена значительно проще, используя тот факт, что файл является итерируемым объектом и к нему применим цикл for. Вот тест, демонстрирующий эту возможность:

    def Test5():
        #Чтение файла как итерируемого объекта
        three = open("Three.txt")
        for record in three:
            print(record)
        three.close()
    Test5() 
    

    Результаты работы, естественно те же, что и у теста Test4.

    Запись в файл и чтение данных разных типов

    Файлы предназначены для хранения данных самых разных типов. Но в языке Python, как мы знаем, метод write позволяет писать в файл только строки - объекты класса str, а методы чтения создают объект этого же класса. Никакие преобразования типов при выполнении этих методов не проводятся. На программиста возлагается обязанность перед записью объекта типа Т преобразовать объект в строку - привести к типу str, а после чтения строки из файла выполнить обратное преобразование - приведение строки к исходному типу Т.

    Задача приведения типа Т к типу str (Т => str) для простых типов (int, float, bool) и сложных типов (list, dict, set, tuple) решается просто - функция str объект типа Т приводит к типу str. Так что никаких сложностей не возникает при записи данных разных типов в файл. Приведу пример:

    def Test6():
        # Создание текстового файла, хранящего данные разных типов:
        # числа, логические значения, списки, словари, множества, кортежи
        r1 = 33
        r2 = 7.77
        r3 = True
        r4 = [33, 7.77, 'four', [1, 2, 3]]
        r5 = {'one' : 1, 'two': 2, 'three' : 3}
        r6 = { 1, 2, 3, 3}
        r7 = (1, 2, 3, 3)
        four = open("Four.txt", 'w') 
        four.write(str(r1) + '\n')
        four.write(str(r2) + '\n')
        four.write(str(r3) + '\n')
        four.write(str(r4) + '\n')
        four.write(str(r5) + '\n')
        four.write(str(r6) + '\n')
        four.write(str(r7) + '\n')
        four.close()
        print('Файл Four создан')
    Test6() 
    

    В результате выполнения этого теста успешно будет создан файл, каждая запись которого содержит объект некоторого типа, приведенный к типу str.

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

    На практике часто бывает, что файл хранит данные простых типов. Для простых типов все интуитивно понятно. Пусть x - объект типа int. Тогда справедливо: x = int(str(x) . Пусть x - объект типа float. Тогда справедливо: x = float(str(x) . Пусть x - объект типа bool. Тогда справедливо: x = bool(str(x) . Функции int, float, bool выполняют обратное приведение к простому типу.

    Со сложными типами все сложнее. Хотя для сложных типов также существуют функции приведения: list, dict, set, tuple, - они не решают обратную задачу. Пусть x - объект типа list. Тогда в результате приведения y = list(str(x) получим объект y, не совпадающий с исходным объектом x. Обратное приведение не работает для всех сложных типов. Как же решить данную задачу? Решение есть! В языке Python, благодаря его интерпретирующему характеру исполнения программ, существует мощный инструмент - функция eval (evaluation - вычисление). Если на вход этой функции подать строку - некоторый текст, представляющий программу Python, то функция выполнит эту программу. В частном случае справедливо следующее утверждение. Пусть x - объект произвольного типа T. Тогда справедливо: x = eval(str(x) . Так что для объектов любых типов - простых и сложных, функция eval выполняет обратное преобразование.

    Я все-таки рекомендую использовать эту функцию для сложных типов, а для простых типов применять более простые инструменты приведения.

    Давайте рассмотрим пример чтения файла four с последующим приведением записей к нужному типу:

    def Test7():
        #Чтение текстового файла,хранящего данные разных типов:
        # числа, логические значения, списки, словари, множества, кортежи
        four = open("Four.txt")
        record = four.readline()
        r1 = int(record)
        print(r1)
        record = four.readline()
        r2 = float(record)
        print(r2)
        record = four.readline()
        r3 = bool(record)
        print(r3)
        record = four.readline()    
        r4 = eval(record)
        print(r4)
        record = four.readline()    
        r5 = eval(record)
        print(r5)
        record = four.readline()
        r6 = eval(record)
        print(r6)
        record = four.readline()
        r7 = eval(record)
        print(r7)
        four.close()
    Test7()
    

    Результаты:

    Итоги

    Файлы позволяют сохранять результаты вычислений в долговременной памяти и использовать файлы как источник входных данных. Файлы Python могут быть двух типов - текстовые и бинарные. Записями текстового файла являются строки, бинарного - последовательности байтов. Логический файл как объект языка создается при вызове метода open. Методы write и readline позволяют записывать данные в текстовый файл и читать записи файла. Эти операции выполняются над строками - объектами класса str. Приведение объектов разных типов к типу str (T => str) , также как и обратное приведение типа (str => T) выполняется в Python достаточно просто.

    Вернуться к учебному плану