Узнайте, что такое файлы в Python
Цель этой статьи — познакомить нас с различными способами работы с файлами и файловыми объектами в Python, в частности, мы увидим, как открывать, закрывать и выполнять операции с такими файловыми объектами, чтобы получить от них то, что нам нужно. Для простоты и прямолинейности я начну с предположения, что все мы знаем, что такое файлы и для чего они предназначены. Это просто данные (содержимое файла), хранящиеся в именованных местах (имя файла) в энергонезависимой памяти (SSD, HDD, USB/внешние флэш-накопители и т. д.). Типичный цикл операций над файлом состоит из трех важных шагов:
- открытие файла;
- чтение/запись в файл;
- закрыть файл.
Открытие файла достигается с помощью функции open()
:
my_file_object = open(filename, mode)
Эта функция возвращает объект _io.TextIOWrapper
, который затем присваивается переменной my_file_object
.
# say we have a file in our current directory # called "sample_text_file": my_file_object = open("sample_text_file", "r") print(type(my_file_object)) Output: <class '_io.TextIOWrapper'>
Это наиболее широко используемая форма вызова open()
. Есть и другие аргументы с ключевыми словами, о них можно прочитать подробнее здесь. На данный момент, сосредоточившись на двух предоставленных нами аргументах, сразу становится очевидным, что filename
— это строка, представляющая имя файла. Точнее, это путь к файлу, будь то относительный или абсолютный путь. Если мы предоставим функции только имя файла, например example.txt
, предполагаемый путь идентичен текущему рабочему каталогу, и, как прямое следствие, Python попытается открыть файл example.txt
в текущем рабочем каталоге.
Другой аргумент, mode
, — это еще одна строка, которая относится к режиму, в котором мы открываем файл. Существует несколько режимов, для которых мы можем открыть файл:
- чтение (по умолчанию):
r
; - запись (запись в файл — перезаписать, если существует, создать, если нет):
w
; - эксклюзивная запись (создайте файл, это не удастся, если он уже существует):
x
; - добавление (добавление к файлу, создание, если он не существует):
a
.
Также есть еще пара режимов, которые можно использовать вместе с 4 выше:
- открыть файл в текстовом режиме (по умолчанию):
t
; - открыть файл для обновления:
+
; - открыть файл в бинарном режиме:
b
.
Давайте рассмотрим несколько примеров:
# open file in text mode, for reading my_file = open("file.txt") # same file, same mode (text mode, open for reading) my_file = open("file.txt", "rt") # open file for reading and writing - overwrites previous contents my_file = open("file.txt", "r+") # open for writing and reading - completely wipes previous contents my_file = open("file.txt", "w+") # open for appending and reading - does not overwrite data my_file = open("file.txt", "a+") # open for reading, binary mode my_file = open("file.txt", "rb")
При открытии файлов в двоичном режиме данные будут обрабатываться как объекты bytes
, тогда как в текстовом режиме данные будут считаны из файла как str
. Скажем, у нас есть файл с именем file.txt
, находящийся в нашем текущем рабочем каталоге и содержащий строку abc
. Откроем файл как в бинарном, так и в текстовом режиме и прочитаем из него одну строку:
# binary mode my_file_obj = open("file.txt", "rb") line = my_file_obj.readline() print(f"{type(line)}: {line}") my_file_obj.close() # text mode my_other_file_obj = open("file.txt", "rt") line = my_other_file_obj.readline() print(f"{type(line)}: {line}") my_other_file_obj.close() Output: <class 'bytes'>: b'abc' <class 'str'>: abc
Что-то, что нужно иметь в виду. Способ открытия файла (текстовый/двоичный), как мы только что видели, отражается в типе данных, которые мы из него получаем. И наоборот, если мы открываем файл для записи, мы должны предпринять необходимые шаги, чтобы передать данные правильного типа для файлового объекта. Вот что произойдет, если мы не адаптируем наш тип данных к режиму, в котором мы открыли файл:
# open a file for writing in binary mode my_file_obj = open("file.txt", "wb") # create a string and attempt to write it to the file line = "abc" my_file_obj.write(line) my_file_obj.close() Output: Traceback (most recent call last): ... my_file_obj.write(line) TypeError: a bytes-like object is required, not 'str'
Также очень важно отметить, что режимы r+
и w+
ведут себя немного по-разному, поэтому их стоит изучить поближе.
Скажем, у нас есть файл с именем file.txt
, содержащий только одну строку данных: Python
. Давайте посмотрим, что произойдет, если мы откроем его в режимах r+
и w+
соответственно. Мы также запишем строку abc
в файл перед его закрытием. Затем мы проверяем содержимое файла:
my_file = open("file.txt", "r+") my_file.write("abc") my_file.close() File contents: abchon
Как мы могли сами убедиться, в случае с режимом r+
произошло то, что он установил курсор в самое начало файла и просто начал записывать строку, которую мы ему указали. Некоторые, но не все данные были перезаписаны, так как наша строка abc
была недостаточно длинной, чтобы перезаписать все содержимое файла.
Посмотрим, будет ли вести себя так же режим w+
:
my_file = open("file.txt", "w+") my_file.write("abc") my_file.close() File contents: abc
На этот раз при открытии файла в режиме w+
все содержащиеся в нем данные были потеряны. После этого в файл была записана строка abc
и, в результате, это все, что у нас осталось. Вся строка Python
, которая была там изначально, просто исчезла.
В заключение, мы должны быть очень осторожны при выборе правильного режима для открытия нашего файла.
Как и любой объект в Python, файловые объекты также могут иметь и действительно имеют несколько собственных свойств:
encoding
— кодировка текстового потока. Один очень известный стандарт кодирования —UTF-8
;errors
— установка ошибки энкодера/декодера. Может бытьstrict
(создать исключениеUnicodeDecodeError
),replace
(использоватьU+FFFD
,replacement_character
),ignore
(оставить символ вне результата) илиbackslashreplace
(вставить escape-последовательность\xNN
);newlines
— вернуть количество встреченных символов новой строки при выполнении операций чтения файла. Операции записи не влияют на это значение;raw
— это связано с классомRawIOBase
, который наследует классIOBase
. Специфика этого класса пока не входит в нашу задачу;
Наш файловый объект после создания имеет несколько собственных атрибутов:
buffer
— используется для буферизации необработанных бинарных потоков. Режим, в котором открывается файл, определяет тип этого буфера. Например, если файл открывается для чтения, наш буфер будет типа_io.BufferedReader
;closed
— закрыт файл или нет;line_buffering
— включать ли буферизацию строк;write_through
— передаются ли записи сразу в нижележащий бинарный буфер;closefd
— закрывать ли файловый дескриптор одновременно с закрытием объекта ввода-вывода. Можно использовать только в том случае, если вместоstr
представления имени файла предоставляется файловый дескриптор;name
— довольно просто, это свойство имени файла файлового объекта;mode
— опять же, очень просто, это режим, в котором был открыт наш файл.
Теперь, когда мы быстро рассмотрели некоторые атрибуты файлового объекта, давайте посмотрим, какие основные методы мы можем использовать для таких объектов.
Закрытие файла
Очень просто:
# open a file for appending my_file_obj = open("file.txt", "a") # close the file my_file_obj.close() # check if the file's been closed print(my_file_obj.closed) Output: True
Отделить буфер от текстового потока
Вы можете столкнуться с ситуациями, когда вам может понадобиться это сделать. Метод detach()
эффективно отключает буфер от необработанного потока, а затем возвращает его (буфер). Предостережение: отсоединенный буфер теперь находится в непригодном для использования состоянии. Попытка закрыть файл или выполнить операции чтения/записи над файловым объектом приведет к возникновению исключений:
# open a file for appending my_file_obj = open("file.txt", "a") # detach the buffer buffer = my_file_obj.detach() print(buffer) # attempt to close the file my_file_obj.close() Output: <_io.BufferedWriter name='file.txt'> Traceback (most recent call last): ... my_file_obj.close() ValueError: underlying buffer has been detached
Мы получили буфер, как показано выше, но любые дальнейшие операции с файловым объектом теперь приводят к ошибке.
Получить файловый дескриптор файлового объекта
Метод fileno()
возвращает базовый файловый дескриптор открытого файла (если он существует). Обычно это целое число:
# open a file for appending my_file_obj = open("file.txt", "a") # get its file descriptor file_descriptor = my_file_obj.fileno() print(file_descriptor) # close the file my_file_obj.close() Output: 3
Причина, по которой мы получаем 3, заключается в том, что у нас нет других файлов, открытых перед этим, а также в том, что файловые дескрипторы 0, 1 и 2 зарезервированы следующим образом:
- 0 — файловый дескриптор для стандартного ввода, или
stdin
; - 1 соответствует стандартному выводу, также известному как
stdout
; - 2 — файловый дескриптор, отвечающий за стандартную ошибку (
stderr
).
Очистка внутреннего буфера
Хорошим примером, подходящим для демонстрации метода flush()
, является запись данных в файл. Как правило, вы заметите, что данные попадают в файл после его закрытия:
# open a file for writing my_file_obj = open("file.txt", "w") # write 2 lines to the file my_file_obj.write("First line\n") my_file_obj.write("Second line\n") # wait for Enter key. # check file.txt. Right now it should be empty. input() my_file_obj.close() # check file.txt again. Now it should contain the data
Если вы следовали этому примеру, то заметили, что данные не записываются в файл до тех пор, пока файл не будет закрыт. Теперь давайте посмотрим, что произойдет, если мы используем flush()
для файла перед его закрытием:
# open a file for writing my_file_obj = open("file.txt", "w") # write a first line to the file my_file_obj.write("First line\n") # flush the buffer my_file_obj.flush() # write a second line to the file my_file_obj.write("Second line\n") # wait for Enter key. # check file.txt. It should contain the first line. input() my_file_obj.close() # check file.txt again. Now it should contain all data
Как мы видели, flush()
принудительно записывает данные внутреннего буфера в файл до того, как файл будет закрыт. Есть ряд ситуаций, в которых вы можете найти это очень полезным, и поэтому вам следует знать об этом полезном методе для вашего пояса инструментов файлового ввода-вывода Python.
Определите, является ли файловый поток интерактивным
Или, другими словами, если файл подключен к tty (или tty-подобному) устройству (например, терминалу), метод isatty()
вернет True
. В противном случае он, очевидно, вернет False
:
# open a file for writing my_file_obj = open("file.txt", "w") # is it associated with a terminal? result = my_file_obj.isatty() print(result) # close the file my_file_obj.close() Output: False
Очевидно, мы только что открыли обычный файл для записи, так что результат False
не должен нас удивлять. Но если мы пойдем дальше и создадим какой-нибудь файл канала, как в этом замечательном примере, то у нас будет файл, подключенный к терминалу.
Загляните в буфер
Метод, который помогает нам достичь этого, который работает с файлами, открытыми для чтения в двоичном режиме, peek()
дает нам содержимое буфера без необходимости выполнять операции чтения:
# say we have a file in our current directory # called "file.txt", having the following contents: # ABCD\nEFGH\nIJKL: # open the file for reading, in binary mode my_file_obj = open("file.txt", "rb") # return the current buffer contents result = my_file_obj.peek() print(result) # perform a reading operation result = my_file_obj.readline() print(result) # check the buffer once again result = my_file_obj.peek() print(result) # finally close the file my_file_obj.close() Output: b'ABCD\nEFGH\nIJKL' b'ABCD\n' b'EFGH\nIJKL'
Что мы сделали, так это распечатали содержимое буфера — первая строка вывода показывает, что все три строки файла отображаются в виде строки bytes
, за которой следует операция чтения, в которой мы прочитали одну строку и отобразили ее во второй строке вывода. , после чего мы еще раз посмотрели на буфер, который сократился ровно на одну строку — строку, которая была прочитана ранее. Третья строка вывода дает нам оставшееся содержимое буфера.
Читать из файла — читать()
Метод read()
можно использовать для чтения символов из файла. Необязательный целочисленный аргумент (size
) указывает количество символов, которое нужно прочитать из файла. Если его не указать или задать отрицательное значение, он будет считывать все доступные данные либо путем вызова readall()
, либо путем нескольких операций чтения в базовом необработанном потоке:
# say we have a file in our current directory # called "file.txt", having the following contents: # ABCD\nEFGH\nIJKL: # open the file for reading my_file_obj = open("file.txt", "r") # read 1 char result = my_file_obj.read(1) print(result) # argumentless read operation result = my_file_obj.read() print(result) # close the file my_file_obj.close() Output: A BCD EFGH IJKL
Сначала мы прочитали 1 символ. После этого мы выполнили операцию чтения без аргументов, отдав нам остальное содержимое файла.
Читать из файла — read1()
Нет, не опечатка, это метод, доступный для файлов, открытых для чтения — очевидно — в бинарном режиме. Он отличается от предыдущего метода read()
тем, что в случае недостаточности данных в буфере для удовлетворения необязательного аргумента size
будет выполнена не более одной операции чтения в базовый необработанный поток. Если size
отрицательное значение или опущено, в базовом необработанном потоке не выполняется операция чтения, и возвращается весь доступный буфер:
# say we have a file in our current directory # called "file.txt", having the following contents: # ABCD\nEFGH\nIJKL: # open the file for reading, in binary mode my_file_obj = open("file.txt", "rb") # read 1 byte result = my_file_obj.read1(1) print(result) # argumentless read operation result = my_file_obj.read1() print(result) # close the file my_file_obj.close() Output: b'A' b'BCD\nEFGH\nIJKL'
Результаты очень похожи на операцию read()
. За исключением того, что на этот раз результат возвращается в bytes
, а не в str
.
Определить, доступен ли файл для чтения
Метод readable()
сообщит нам, доступен ли файл для чтения или нет:
# open a file in append mode my_file_obj = open("file.txt", "a") # this file isn't readable result = my_file_obj.readable() print(result) # close the file my_file_obj.close() # open the same file, this time in read mode my_file_obj = open("file.txt", "r") # this time the file's readable result = my_file_obj.readable() print(result) # close the file once more my_file_obj.close() Output: False True
Считать байты в буфер — readinto()
Для этого нам нужен байтовый объект для представления буфера и файл для чтения, открытый в двоичном режиме. Затем метод readinto()
делает свое дело. Стоит отметить, что внутри базового потока может выполняться несколько операций чтения:
# say we have a file in our current directory # called "file.txt", having the following contents: # ABCD\nEFGH\nIJKL: # open a file in binary mode, for reading my_file_obj = open("file.txt", "rb") # construct our buffer buffer = bytearray(16) # read from the file into the buffer my_file_obj.readinto(buffer) print(buffer) # close the file my_file_obj.close() Output: bytearray(b'ABCD\nEFGH\nIJKL\x00\x00')
Как мы могли видеть, буфер изначально был заполнен \x00
bytes, а затем заполнен данными, которые мы считывали из файла.
Считать байты в буфер — readinto1()
Аналогично readinto()
, за исключением того, что в базовом потоке будет не более одной операции чтения, в отличие от readinto()
, где существует возможность выполнения нескольких операций чтения в базовом потоке:
# say we have a file in our current directory # called "file.txt", having the following contents: # ABCD\nEFGH\nIJKL: # open a file in binary mode, for reading my_file_obj = open("file.txt", "rb") # construct our buffer buffer = bytearray(16) # read from the file into the buffer my_file_obj.readinto1(buffer) print(buffer) # close the file my_file_obj.close() Output: bytearray(b'ABCD\nEFGH\nIJKL\x00\x00')
Прочитать одну строку из файла
В случае, когда нам нужно прочитать одну строку (или выполнить построчное чтение) из файла, метод readline()
поможет именно в этом:
# say we have a file in our current directory # called "file.txt", having the following contents: # ABCD\nEFGH\nIJKL: # open a file for reading my_file_obj = open("file.txt", "r") # read one line from the file result = my_file_obj.readline() print(result) # read a second line from the file result = my_file_obj.readline() print(result) # close the file my_file_obj.close() Output: ABCD EFGH
Следует отметить, что readline()
не удаляет строку чтения символа новой строки (\n
), как показано выше.
Читать строки из файла
Если нам нужно прочитать все строки из файла и сохранить их в списке, readlines()
— это метод для нас. Он имеет необязательный целочисленный аргумент, который останавливает добавление строк в выходной список, если общее количество возвращаемых байтов превышает этот аргумент. Давайте посмотрим на метод в действии:
# say we have a file in our current directory # called "file.txt", having the following contents: # ABCD\nEFGH\nIJKL\nMNOP: # open a file for reading my_file_obj = open("file.txt", "r") # read lines from the file, until 5+ bytes are returned result = my_file_obj.readlines(5) print(result) # read all remaining lines result = my_file_obj.readlines() print(result) # close the file my_file_obj.close() Output: ['ABCD\n', 'EFGH\n'] ['IJKL\n', 'MNOP\n']
Прямой обход файла
Следует сказать, что если все, что мы хотим, это читать из файла, строка за строкой, но мы не заинтересованы в сохранении всех этих строк в списке, мы можем просто читать непосредственно из файлового объекта. Файловый объект имеет тип _io.TextIOWrapper
, и, если мы открыли файл для чтения, его можно перебрать, открывая доступ к самому содержимому файла. Давайте посмотрим, как:
# say we have a file in our current directory # called "file.txt", having the following contents: # ABCD\nEFGH\nIJKL\nMNOP: # open a file for reading my_file_obj = open("file.txt", "r") # read directly from the file, line by line for line in my_file_obj: print(line) # close the file my_file_obj.close() Output: ABCD EFGH IJKL MNOP
Подробнее об этом здесь, где официальные документы поддерживают это и даже советуют нам это делать, так как это приводит к более простому, более читаемому коду, в дополнение к тому, что он более эффективен и быстрее. Обратите внимание, что итерация по файловому объекту работает до тех пор, пока есть строки для чтения. Как только мы достигли конца, нам нужно сделать что-то вроде my_file_obj.seek(0)
, чтобы переместить курсор в начало файла, чтобы иметь возможность снова перебирать файловый объект.
Перенастройка текстового потока
Предположим, нам нужна немного другая конфигурация текстового потока после того, как файл был открыт, и закрытие файла только для его повторного открытия в другой конфигурации не является вариантом. Метод reconfigure()
позволяет нам установить значения для нескольких параметров, например:
encoding
errors
newline
line_buffering
write_through
# say we have a file in our current directory # called "file.txt" # open a file for reading, UTF-16 encoding my_file_obj = open("file.txt", "r", encoding="UTF-16") print(my_file_obj.encoding) # reconfigure the text stream for UTF-8 encoding my_file_obj.reconfigure(encoding="UTF-8") print(my_file_obj.encoding) # close the file my_file_obj.close() Output: UTF-16 UTF-8
Ищем в файлах
Метод seek()
оказывается очень полезным в ситуациях, когда вам нужно только прочитать определенные фрагменты файла. Он поставляется с 2 аргументами:
offset
— целое число, определяющее смещение в байтах;whence
— это необязательный аргумент, который по умолчанию равен 0, если он опущен, и имеет 3 возможных значения: 0 означает абсолютное позиционирование файла, поэтому смещение относительно начала файла, 1 означает поиск относительно текущей позиции в файле, а 2 означает поиск относительно конца файла;
Этот метод лучше всего использовать при работе с файлами, открытыми в двоичном режиме. Для файлов, открытых в текстовом режиме, мы обнаружим, что его функциональные возможности ограничены только поиском с начала файла (seek(i, 0)
) и переходом прямо в конец файла через seek(0, 2)
.
Посмотрим, как ведет себя этот метод:
# say we have a file in our current directory # called "file.txt", having the following contents: # ABCDEFGHIJKLMNOPQRSTUVWXYZ: # open a file for reading my_file_obj = open("file.txt", "rb") # move the cursor 3 bytes from the start location = my_file_obj.seek(3, 0) print(location) # from that location, read 2 bytes result = my_file_obj.read(2) print(result) # advance cursor 1 byte from current location location = my_file_obj.seek(1, 1) print(location) # from that new location, read 3 bytes result = my_file_obj.read(3) print(result) # move cursor 5 bytes before end of file location = my_file_obj.seek(-5, 2) print(location) # from that new location, read 4 bytes result = my_file_obj.read(4) print(result) # close the file my_file_obj.close() Output: 3 b'DE' 6 b'GHI' 21 b'VWXY'
Определить, доступен ли файл для поиска
Теперь, когда мы только что увидели, как ведет себя seek()
, мы также можем проверить, доступен ли файл для поиска, прежде чем мы даже попытаемся выполнить операцию поиска в файле. Простой метод seekable()
возвращает логическое значение (True
/False
), указывающее, разрешает ли файл доступ к файловому потоку или нет.
# open a file for reading my_file_obj = open("file.txt", "rb") # is the file seekable? result = my_file_obj.seekable() print(result) # close the file my_file_obj.close() Output: True
Получение текущего местоположения курсора
Бывают случаи, когда нам нужно получить текущее местоположение курсора в файле. Это место — это место, откуда данные будут записываться или считываться, и это важная часть информации, которую необходимо знать. Сразу после открытия файла курсор находится в начале файла, поэтому изначально это местоположение равно 0. Когда мы записываем/читаем данные в/из файла, это местоположение автоматически перемещается. Также возможно переместить этот курсор с помощью метода seek()
. Чтобы получить местоположение курсора, мы с большим успехом используем метод tell()
:
# say we have a file in our current directory # called "file.txt", having the following contents: # ABCDEFGHIJKLMNOPQRSTUVWXYZ: # open a file for reading my_file_obj = open("file.txt", "r") # read 5 bytes my_file_obj.read(5) # get the current position within the file result = my_file_obj.tell() print(result) # close the file my_file_obj.close() Output: 5
Как и ожидалось, мы прочитали 5 байтов с начала файла, и это автоматически передвинуло курсор на 5 байтов. Итак, естественно, в результате курсор теперь находится на 0+5=5.
Обрезать файл
Метод truncate()
эффективно усекает размер файла. Существует необязательный аргумент size
, который указывает методу обрезать файл не более чем до size
байт. Если его нет, то он просто обрежет все от текущей позиции до конца.
# say we have a file in our current directory # called "file.txt", having the following contents: # ABCDEFGHIJKLMNOPQRSTUVWXYZ: # open a file for reading and updating, binary mode my_file_obj = open("file.txt", "r+b") # read 4 bytes my_file_obj.read(4) # truncate all remaining data result = my_file_obj.truncate() # wait for Enter key. # check file.txt. Right now it should contain 'ABCD' input() # truncate the file size to 3 bytes result = my_file_obj.truncate(3) # close the file my_file_obj.close() # now check file.txt again. Should contain 'ABC'
Здесь важно отметить, что если аргумент size
больше фактического размера файла, поведение Python в этом случае будет зависеть от платформы. Согласно этому, возможности включают в себя то, что файл может оставаться неизменным, увеличиваться до указанного размера, как если бы он был заполнен нулями, или увеличиваться до указанного размера с неопределенным новым содержимым.
Проверьте, доступен ли файл для записи
Метод writable()
возвращает логическое значение True
или False
, в зависимости от того, доступен ли файл для записи или нет:
# say we have a file in our current directory # called "file.txt", having the following contents: # ABCDEFGHIJKLMNOPQRSTUVWXYZ: # open a file for appending my_file_obj = open("file.txt", "a") # this file should be writable result = my_file_obj.writable() print(result) # close the file my_file_obj.close() # now open the same file, for reading my_file_obj = open("file.txt", "r") # this time the file should not be writable result = my_file_obj.writable() print(result) # close the file again my_file_obj.close() Output: True False
Записать в файл
Метод write()
записывает указанную строку в файл, а также возвращает целое число, представляющее количество байтов, успешно записанных в файл.
# say we have a file in our current directory # called "file.txt", having the following contents: # ABCDEFGHIJKLMNOPQRSTUVWXYZ: # open a file for appending my_file_obj = open("file.txt", "a") # write 'Hello, World!' (append it) to the file result = my_file_obj.write("Hello, World!") print(result) # close the file my_file_obj.close() # the file should now contain: # "ABCDEFGHIJKLMNOPQRSTUVWXYZHello, World!" Output: 13
Записать последовательность строк в файл
Мы также можем записать последовательность строк в файл, используя метод writelines()
. Примечание: символы-разделители не добавляются.
# open a file for writing my_file_obj = open("file.txt", "w") # list of lines to write to the file lines = ["ABCD", "EFGH", "IJKL", "MNOP"] # write the list of lines my_file_obj.writelines(lines) # close the file my_file_obj.close() # the file should now be created in your current directory # and contain: "ABCDEFGHIJKLMNOP"
Надеюсь, что этот довольно длинный обзор файлов в Python был полезен. Если да, то его цель достигнута. В Интернете полно информации на эту тему, поэтому обязательно используйте ее, чтобы обогатить свои знания о файлах и передовых методах работы с ними в Python. Вы поблагодарите себя позже. Небольшое усилие каждый день имеет большое значение.
При этом я благодарю вас и с нетерпением жду следующего. Удачного кодирования! Ваше здоровье!
Дак — инженер-программист, наставник, писатель и иногда даже учитель. Обладая более чем 12-летним опытом разработки программного обеспечения, он теперь является настоящим сторонником языка программирования Python, а его страстью является помощь людям в оттачивании их навыков Python и программирования в целом. Вы можете связаться с колодой в Linkedin, Facebook, Twitter и Discord: Deck451#6188, а также следить за его публикациями здесь, на Medium.
Дополнительные материалы на PlainEnglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter и LinkedIn. Посетите наш Community Discord и присоединитесь к нашему Коллективу талантов.