Работа с файлове в Python#
План на лекцията:
Кратка интродукция за работата с пътища, Windows и Unix
Представяне на файловете в Python
Четене на файлове
Писане на файлове
Работа с файлове и пътища
Използване на
withtellиseekПримери
Задачи
Предварителна подготовка#
!mkdir files
!curl -o files/lorem_ipsum.txt https://raw.githubusercontent.com/lyubolp/PythonCourse2022/08_files/08%20-%20Files/files/1.txt
!echo "hello" > files/hello.txt
Кратка интродукция за работата с пътища, Windows и Unix.#
Път в една файлова система посочва локацията и името на даден обект (бил той файл или директория). Пример за път в Linux/MacOS е /home/user/myfile.txt, а в Windows - C:\Users\user\myfile.txt.
Забелязва се, че пътищата в Windows и Unix-базираните ОС се разделят с различни черти. Нашият Python код трябва да е съвместим и с двата начина за разделя на пътища.
За наше улеснение, Python предлага функцията os.path.join, която по подадени имена на директории/файлове, конструира правилния спрямо нашия OS път.
import os
os.path.join('/home', 'lyubo', 'myfile.txt')
Понеже кодът е изпълнен под Linux, получаваме Unix-ски път. Ако изпълним обаче същия код под Windows, ще получим правилен Windows-ки път.
Представяне на файловете в Python#
Python предоставя API за работа с файлове и потоци. Python работи с т.нар. “file objects” - това може да са файлове на диска, sockets, pipes и други потоци.
Освен с текстови файлове, можем да работим и с бинарни файлове. За момента обаче, ще се спрем само върху текстовите файлове.
Можем да отворим един файл за работа, с помощта на функцията open. В най-простия си вид, тя приема път към файл.
fd = open(os.path.join('files', 'lorem_ipsum.txt'))
print(fd)
fd.close()
След като приключим работа с един файл, не трябва да забравяме да го затворим. Затварянето на един файл става с помощта на close метода.
Забелязваме, че обекта който получаваме като резултат от open има име (files/lorem_ipsum.txt), режим (r) и кодиране (UTF-8).
Един файл може да бъде отворен в няколко различни режима:
r- отваря файла за четенеw- отваря файла за писане, като файла първо бива зачистенa- отваря файла за писане, като новото съдържание се записва в края на файлаx- създава файла, ако не съществува. Ако файла вече съществува, се хвърляFileExistsErrorb- отваря файл в бинарен режимt- отваря файл в текстови режим+- отваря файла за четене и писане
Освен режима на отваряне, можем да променим и кодирането, с което се опитваме да четем файла. По подразбиране, използваме UTF-8.
Ако се опитаме да запишем файл в несъществуваща директория, ще получим грешка
fd = open(os.path.join('files', 'non_existing_dir', 'new_file.txt'), 'w')
fd.write('content')
fd.close()
Четене на файлове#
В Python имаме три метода, чрез които можем да четем от файлове: read, readline и readlines.
Нека първо разгледаме метода read. Той прочита целия файл и запазва съдържанието му в променлива, като един голям низ.
import os
fd = open(os.path.join('files', 'lorem_ipsum.txt'))
content = fd.read()
print(f'content={content}')
fd.close()
Веднъж прочетен един файл, следващото прочитане ще ни върне празен низ.
fd = open(os.path.join('files', 'lorem_ipsum.txt'))
content = fd.read()
print(f'content={content}')
content = fd.read()
print(f'content={content}')
fd.close()
Друг вариант за четене на файл, е ред по ред - това става с помощта на метода readline.
import os
fd = open(os.path.join('files', 'lorem_ipsum.txt'))
content = fd.readline()
print(f'content={content}')
content = fd.readline()
print(f'content={content}')
fd.close()
След прочитане на последния ред, readline връща празен низ.
import os
fd = open(os.path.join('files', 'lorem_ipsum.txt'))
content = fd.readline()
print(f'content={content}')
while content != '':
content = fd.readline()
print(f'content={content}')
fd.close()
Ако искаме да получим списък от всички редове във файл, можем да използваме readlines.
import os
fd = open(os.path.join('files', 'lorem_ipsum.txt'))
content = fd.readlines()
print(f'content={content}')
fd.close()
import os
fd = open(os.path.join('files', 'lorem_ipsum.txt'))
content = fd.readlines()
for line in content:
print(f'content={line}')
fd.close()
Вместо да използваме readlines, можем да итерираме директно по файл обекта.
import os
fd = open(os.path.join('files', 'lorem_ipsum.txt'))
for line in fd:
print(f'content={line}')
fd.close()
Писане на файлове#
За писане на файлове в Python може да използваме методите write и writelines
Методът write записва низ във файла. Позицията зависи от начина по който е отворен файла (с или без изтриване на текущото съдържание).
import os
fd = open(os.path.join('files', 'hello_world.txt'), 'w')
fd.write('Hello world')
fd.close()
import os
fd = open(os.path.join('files', 'hello_world.txt'))
content = fd.read()
print(content)
fd.close()
Припомням, че ако отворим файла в режим w, то ще изтрием текущото му съдържание.
import os
fd = open(os.path.join('files', 'my_important_file.txt'), 'w')
fd.write('Really important')
fd.close()
# Oops, forgot to write something down
fd = open(os.path.join('files', 'my_important_file.txt'), 'w')
fd.write('Should really not forget this')
fd.close()
import os
fd = open(os.path.join('files', 'my_important_file.txt'))
content = fd.read()
print(content)
fd.close()
Ако искаме да пишем в края на файла, трябва да използваме режим a
import os
fd = open(os.path.join('files', 'my_important_file_2.txt'), 'a')
fd.write('Really important\n')
fd.close()
# Oops, forgot to write something down
fd = open(os.path.join('files', 'my_important_file_2.txt'), 'a')
fd.write('Should really not forget this\n')
fd.close()
import os
fd = open(os.path.join('files', 'my_important_file_2.txt'))
content = fd.read()
print(content)
fd.close()
Методът writelines приема списък от “редове”, които да бъдат записани във файла.
Забележка: writelines не добавя автоматично нови редове след всеки елемент, затова се очаква всеки елемент от списъка да съдържа нов ред в себе си.
import os
fd = open(os.path.join('files', 'writelines_example.txt'), 'w')
lines_to_write = ['hello\n', 'this\n', 'are\n', 'my\n', 'lines\n']
fd.writelines(lines_to_write)
fd.close()
import os
fd = open(os.path.join('files', 'writelines_example.txt'))
content = fd.read()
print(content)
fd.close()
Тук е добре да се отбележи, че можем да записваме и други типове данни, освен низове (е не точно, но…).
Стига даден тип (или обект) да има низово представяне, можем да го запишем във файл.
import os
other_types_in_files = os.path.join('files', 'other_types_in_files.txt')
fd = open(other_types_in_files, 'w')
fd.write(str(2) + '\n')
fd.write(str([2, 3, 4]) + '\n')
fd.write(str({'a': 2, 'b': 3, 'c': 4}) + '\n')
fd.write(str((2, 3)) + '\n')
fd.close()
fd = open(other_types_in_files)
content = fd.read()
print(content)
fd.close()
Работа с файлове и пътища#
Python предлага удобен начин за работа с файлове. Вградената библиотека os съдържа всичко необходимо за работата с файлове и директории.
Ще разгледаме как можем да:
Прегледаме съдържанието на директория
Преместим файл
Изтрием файл
Създадем директория
Преместим директория
Изтрием директория
Обща работа с пътища
Обхождане на директории
Преглеждане на съдържание на директория#
Можем да видим съдържанието на директория като използваме os.listdir метода. Той ни връща списък от низове, съдържащи имената на директориите и файловете в исканата от нас папка.
import os
print(os.listdir('files'))
Преместване на файл#
Преместването на файл става чрез “преименуването му” (или всъщност, преименуването на файл е преместването му като файл с друго име 🤔) - това в Python става с помощта на функцията os.rename. Тя приема два аргумента - source път и destination път (т.е. старото и новото име на файла)
import os
file_to_be_moved = os.path.join('files', 'to_be_moved.txt')
fp = open(file_to_be_moved, 'w')
fp.write('This file is to be moved')
fp.close()
print(f'Before = {os.listdir("files")}')
file_moved = os.path.join('files', 'file_moved.txt')
os.rename(file_to_be_moved, file_moved)
print(f'After = {os.listdir("files")}')
Ако вече съществува файл със същото име се случват едно от две неща:
Ако кодът се изпълнява под Windows, се хвърля
FileExistsError.Ако кодът се изпълнява под Linux/MacOS и имаме права върху файла върху който ще пишем, той ще бъде презаписан
Нека разгледаме следната ситуация: имаме файл files/a/file.txt, който искаме да преместим в files/b, но директорията b не съществува.
!mkdir files/a
import os
file_to_be_moved = os.path.join('files', 'a', 'file.txt')
fp = open(file_to_be_moved, 'w')
fp.write('This file is to be moved')
fp.close()
os.rename(file_to_be_moved, os.path.join('files', 'b', 'file.txt'))
Изтриване на файл#
Изтриването на файл се случва чрез метода os.remove. Той приема един аргумент - пътят към файла, който ще бъде изтрит.
import os
file_path = os.path.join('files', 'to_removed.txt')
fp = open(file_path, 'w')
fp.write('This file will be deleted')
fp.close()
print(f'Before = {os.listdir("files")}')
os.remove(file_path)
print(f'After = {os.listdir("files")}')
Ако се опитаме да изтрием директория с os.remove, ще получим грешка IsADirectoryError.
import os
os.remove('files')
А ако опитаме да изтрием файл, който не съществува, ще получим грешка FileNotFoundError.
import os
non_existant_path = os.path.join('files', 'this_file_does_not_exist.txt')
os.remove(non_existant_path)
Копиране на файлове#
В Python можем да копираме файлове с помощта на shutil библиотеката. Тя ни предоставя методи за копиране на файлове.
Ще разгледаме част от тях:
shutil.copy()- копира файл от едно място на друго. Приема два аргумента - source и target пътища. Ако файлът вече съществува на новото място, ще бъде презаписан. Ако target е директория, ще се запише копие на файла в тази директория със същото име.shutil.copy2()- работи по същия начин катоshutil.copy(), но запазва и метаданните на файла (например времето на създаване)shutil.copystat()- копира само метаданните на файлаshutil.copytree()- копира директория и всички файлове в нея. Приема два аргумента - source и target пътища.
import os
import shutil
print(f'Before = {os.listdir("files")}')
shutil.copy(os.path.join('files', '1.txt'), os.path.join('files', '1_1.txt'))
shutil.copy2(os.path.join('files', '1.txt'), os.path.join('files', '1_2.txt'))
print(f'After = {os.listdir("files")}')
Before = ['1.txt']
After = ['1_2.txt', '1.txt', '1_1.txt']
Създаване на директория#
В Python създаването на директория става чрез метода os.mkdir. Той приема пътя към директорията, която да бъде създадена.
import os
print(f'Before = {os.listdir("files")}')
file_path = os.path.join('files', 'mkdir_example')
os.mkdir(file_path)
print(f'After = {os.listdir("files")}')
Само за информация: Можем да зададем права на директорията, с помощта на аргумента mode.
Ако се опитаме да създадем директория, която вече съществува, ще получим FileExistsError.
import os
file_path = os.path.join('files', 'mkdir_existing_directory')
os.mkdir(file_path)
os.mkdir(file_path)
Нека опитаме да създадем няколко нови директории, една под друга.
import os
print(f'Before = {os.listdir("files")}')
file_path = os.path.join('files', 'mkdir_example_parent', 'mkdir_example_child')
os.mkdir(file_path)
print(f'After = {os.listdir("files")}')
Тук получаваме грешка - os.mkdir не може да създаде несъществуващите директории над последната. За целта трябва да използваме метода os.makedirs. Той приема отново като аргумент пътят към директорията, която искаме да създадем, както и права на директорията/директориите.
import os
print(f'Before = {os.listdir("files")}')
file_path = os.path.join('files', 'mkdir_example_parent', 'mkdir_example_child')
os.makedirs(file_path)
print(f'After = {os.listdir("files")}')
Един допълнителен аргумент, който makedirs приема, е аргумента exist_ok. Той контролира дали да се хвърли грешка, ако крайната директория която искаме да създадем, вече съществува.
Преместване на директория#
Освен за преместване на файлове, os.rename работи и за директории.
import os
file_path = os.path.join('files', 'rename_dir_example_1')
os.makedirs(file_path)
print(f'Before = {os.listdir("files")}')
target_file_path = os.path.join('files', 'renamed_dir_example_1')
os.rename(file_path, target_file_path)
print(f'After = {os.listdir("files")}')
Както споменахме по-горе, ако дестинацията съществува, под Windows os.rename хвърля FileExistsError.
Под Unix системи (Linux, MacOS) поведението е малко по-различно:
Ако source пътя е файл, а destination пътя е директория, се хвърля
IsADirectoryгрешкаАко source пътя е директория, а destination пътя е файл, се хвърля
NotADirectoryгрешкаАко destination е съществуваща директория:
Ако е празна, преместването е успешно
Ако не е празна, се хвърля
OSErrorгрешка
Изтриване на директория#
Изтриването на директория става чрез функцията os.rmdir. Тя приема пътя към директорията, която трябва да бъде изтрита.
import os
file_path = os.path.join('files', 'remove_dir_example_1')
os.makedirs(file_path)
print(f'Before = {os.listdir("files")}')
os.rmdir(file_path)
print(f'After = {os.listdir("files")}')
Ако директорията, която се опитаме да изтрием, не е празна, ще получим OSError. А ако тя не съществува, ще получим FileNotFoundError.
import os
non_existing_dir = os.path.join('files', 'non_existing_dir')
os.rmdir(non_existing_dir)
os.rmdir('files')
import os
os.rmdir('files')
Освен os.rmdir, Python предлага и друга функция за изтриване на директории - os.removedirs. Тя обаче работи по специфичен начин. os.removedirs приема път до директория, като освен да премахва последната директория от пътя, функцията се опитва да премахне и всички празни директории нагоре, като спира при първата директория, която не е успяла да премахне успешно.
Нека за целта създадем следната структура - files/remove_dirs_example ще е основната ни директория. В нея ще имаме две поддиректории - first и second. В first ще създадем още няколко поддиректории, като всичките ще са празни. В second ще създадем един файл и една поддиректория.
Ще се опитаме да извикаме os.removedirs върху най-долната директории в first и second
import os
main_dir = os.path.join('files', 'remove_dirs_example')
first_dir = os.path.join(main_dir, 'first')
empty_first_subdirs = os.path.join(first_dir, 'a', 'b', 'c')
second_dir = os.path.join(main_dir, 'second')
empty_second_subdir = os.path.join(second_dir, 'd')
empty_second_dir_file = os.path.join(second_dir, 'e')
os.makedirs(empty_first_subdirs)
os.makedirs(empty_second_subdir)
fd = open(empty_second_dir_file, 'w')
fd.write('Hi')
fd.close()
print(f'Before = {os.listdir(main_dir)}')
os.removedirs(empty_first_subdirs)
os.removedirs(empty_second_subdir)
print(f'After = {os.listdir(main_dir)}')
Как работи os.removedirs в този случай ?
Върху files/first/a/b/c:
Опитваме да изтрием
c=> тя е празна, изтрива сеОпитваме да изтрием
b=> тя е празна, изтрива сеОпитваме да изтрием
a=> тя е празна, изтрива сеОпитваме да изтрием
first=> тя е празна, изтрива сеОпитваме да изтрием
remove_dirs_example=> тя не е празна, спираме
Върху files/second/d:
Опитваме да изтрием
d=> тя е празна, изтрива сеОпитваме да изтрием
second=> тя не е празна, спираме
os.removedirs е много подходяща когато имаме много празни директории една в друга, но е редно да се използва внимателно.
За любопитните: Съществува функция rmtree, намираща се в shutil библиотеката, която изтрива папка и всички в нея. Нея няма да я разглеждаме в курса.
Обща работа с пътища#
В началото видяхме, че за да построим един път до файл или директория в Python, трябва да използваме os.path.join. Освен join, os.path предлага много други полезни функции за работа с пътища.
Ще се спрем върху една част от тях, която е по-вероятно да използвате във всекидневната си работа с Python:
os.path.exists - проверка дали пътят сочи към валиден файл или директория
os.path.isdir - дали пътят сочи към валидна директория
os.path.isfile - дали пътят сочи към валиден файл
os.path.split - отделя последната част от път
import os
print(f'Does the files directory exist: {os.path.exists("files")}')
print(f'Does the files/lorem_ipsum.txt file exist: {os.path.exists(os.path.join("files", "lorem_ipsum.txt"))}')
print(f'Does the files/ala-bala.txt file exist: {os.path.exists(os.path.join("files", "ala-bala.txt"))}')
import os
print(f'Is "files" a directory: {os.path.isdir("files")}')
print(f'Is "files/lorem_ipsum.txt" a directory: {os.path.isdir(os.path.join("files", "lorem_ipsum.txt"))}')
print(f'Is "files/ala-bala.txt" a directory: {os.path.isdir(os.path.join("files", "ala-bala.txt"))}') # Although the file does not exist, isdir returns False
import os
print(f'Is "files" a file: {os.path.isfile("files")}')
print(f'Is "files/lorem_ipsum.txt" a file: {os.path.isfile(os.path.join("files", "lorem_ipsum.txt"))}')
print(f'Is "files/ala-bala.txt" a directory: {os.path.isfile(os.path.join("files", "ala-bala.txt"))}') # Although the file does not exist, isfile returns False
os.path.split отделя последната част от пътя от останалия. Връща ни наредена двойка от head и tail, където tail съдържа последната част от пътя, а head останалото
import os
long_path = os.path.join('/', 'foo', 'bar', 'baz')
print(f'long_path = {long_path}')
print(f'long_path splitted: {os.path.split(long_path)}')
Важно е да отбележим няколко по-специални случая:
Ако пътят е празен,
splitще ни върне празниheadиtailАко пътят завършва с
/(или\под Windows),tailще е празен низАко пътят не съдържа
/(или\под Windows),headще е празен низ
empty_path = ''
print(f'split with empty path returns = {os.path.split(empty_path)}')
ending_with_separator = '/foo/bar/'
print(f'split with path ending in / returns = {os.path.split(ending_with_separator)}')
no_separators = 'foo'
print(f'split with no separators returns = {os.path.split(no_separators)}')
Обхождане на директории#
Понякога искаме да обходим цялото директорийно дърво отдадена точка надолу. Python ни позволява да направи това сравнително лесно, с помощта на функцията os.walk.
Тя приема като аргумент директорията, от което започва обхождането. Като резултат, тя ни връща генератор съдържащ името на текущата директория, имената на директориите в текущата директория и имената на файловете в текущата директория.
import os
# Setup
example_root_dir = os.path.join('files', 'walk_example')
d_dir = os.path.join(example_root_dir, 'a', 'b', 'c', 'd')
os.makedirs(d_dir, exist_ok=True)
os.makedirs(os.path.join(example_root_dir, 'a', 'b1'), exist_ok=True)
b2_dir = os.path.join(example_root_dir, 'a', 'b2')
os.makedirs(b2_dir, exist_ok=True)
os.makedirs(os.path.join(example_root_dir, 'a', 'b', 'c', 'd1'), exist_ok=True)
os.makedirs(os.path.join(example_root_dir, 'a', 'b', 'c', 'd2'), exist_ok=True)
files_for_d = [os.path.join(d_dir, f'file{i}') for i in range(5)]
for file_for_d in files_for_d:
with open(file_for_d, 'w') as fp:
fp.write(f'Content for {file_for_d}')
files_for_b2 = [os.path.join(d_dir, f'file{i}') for i in range(3)]
for file_for_b2 in files_for_b2:
with open(file_for_b2, 'w') as fp:
fp.write(f'Content for {file_for_b2}')
# os.walk
for dirname, subdirs, files in os.walk(example_root_dir):
print(f'In {dirname}, which has subdirs: {subdirs} and files: {files}')
Използване на with#
Досега в работата ни с файлове, забелязахме че трябва да подсигурим, че сме затворили файла. Проблемът идва, когато трябва да се справим с грешки, които могат да бъда хвърлени от различните операции с файлове. Дори и при хвърлена грешка, ние все пак трябва да си затворим файла.
Python ни позволява два начина да се справим с този проблем - try/finally и with.
След всеки try блок, може да поставим един друг блок, който да се изпълнява винаги след try или except блоковете. Този допълнителен блок се казва finally.
def raiser():
raise ValueError('Hello there')
try:
raiser()
finally:
print('After the exception, this will be printed')
По подобен начин можем да използваме finally за да затворим отворения файл.
import os
fp = open(os.path.join('files', 'lorem_ipsum.txt'))
try:
print(fp.read())
finally:
fp.close()
Така, дори и да получим грешка при четене (или каквато и да е друга операция в try блока), файла ще бъде затворен.
От тук можем да стигнем до заключението - при работата с ресурси, имаме процес по отваряне и процес по затваряне (независимо от грешки). Python ни предлага и друга синтактична конструкция за работа с такива структури - with.
with ни позволява отварянето на нов контекст. В него може да отворим ресурс, а след излизането от блока, този ресурс се затваря автоматично.
Общият вид на with е следния:
with <expression> as <variable>:
f(variable)
Ако искаме да отворим файл и да прочетем нещо с with, кода би изглеждал по следния начин:
import os
with open(os.path.join('files', 'lorem_ipsum.txt')) as fp:
print(fp.read())
print(f'Is the file closed ? {fp.closed}')
Дори и при грешка, файлът ще бъде затворен. Ако искаме обаче да хванем грешката, все пак трябва да изпозлваме try/catch.
import os
try:
with open(os.path.join('files', 'lorem_ipsum.txt')) as fp:
print(fp.read())
except OSError as err:
print(err)
Начинът по който with работи е, че извиква два специални магически метода - __enter__ и __exit__, който се изпълняват при влизане и излизане съответно от контекст мениджъра.
По-конкретно, __enter__ метода се изивиква в as частта на with. В него връщаме обекта, който ще бъде присвоен на променливата след as.
Файловите обекти имплементират __exit__ метода, където затварят отворения файл.
Ако искаме и нашите класове да работят с контекстните мениджъри, трябва да имплементираме __enter__ и __exit__ методите.
class ContextableClass:
def __init__(self, some_var=42):
self.some_var = some_var
def __enter__(self):
print('Entering the context manager')
return self
def __exit__(self, exc_type, exc_value, traceback):
print('Exiting the context manager')
with ContextableClass(5) as instance:
print(instance.some_var)
tell и seek#
Писането и четенето от файлове всъщност се осъществява символ по символ. Така например за да запишем ‘hello’, трябва първо да запишем h, после e и т.н.
Както при писането с клавиатура имаме позиция върху която пишем, така и при работата с файлове имаме позиция, на която четем или пишем.
Python ни позволява да вземем текущата позиция за четене/писане, както и да преместим тази позиция на друго място.
Можем да вземем позиция на курсора във файла с помощта на метода tell. Той ще ни върне като резултат цяло число, символизиращо броя символи след началото на файла.
import os
file_path = os.path.join('files', 'tell_exampe.txt')
# First lets create the file
with open(file_path, 'w') as fp:
fp.write('abc\n')
fp.write('def\n')
fp.write('ghi\n')
# Now lets read
with open(file_path) as fp:
print(f'Current position = {fp.tell()}')
print(f'Reading a line... {fp.readline()}')
print(f'Current position = {fp.tell()}')
print(f'Reading another line... {fp.readline()}')
print(f'Current position = {fp.tell()}')
Можем да зададем позиция на курсора във файла с помощта на метода seek. Той приема два аргумента - какво отместване (offset) да направим и от къде (whence).
whence приема три стойност:
0 (или
os.SEEK_SET) - означава спрямо началото на файла1 (или
os.SEEK_CUR) - означава спрямо текущата позиция2 (или
os.SEEK_END) - означава спрямо края на файла
При работа с текстови файлове, отместването е само спрямо началото на файла. Можем единствено да преместим курсора до края на файла с seek(0, 2)
import os
file_path = os.path.join('files', 'seek_exampe.txt')
# First lets create the file
with open(file_path, 'w') as fp:
fp.write('abcdefghijk')
with open(file_path, 'r+') as fp:
fp.seek(2)
fp.write('!')
fp.seek(0, 2)
fp.write('@')
with open(file_path) as fp:
print(fp.read())
Бележка: Ако бяхте отворили файла за писане с режим a, щяхме да можем да пишем само в края. Режим r+ ни дава права за четене и пренаписване.
pathlib#
pathlib е вграден в езика модул, който предоставя обектно-ориентиран интерфейс за работа с пътища, чрез абстракция над всички гореспоменати функции.
Основния клас, който използваме от pathlib е Path. Можем да си го импортнем по следния начин:
from pathlib import Path
Path предефинира оператор / за инуитивна конкатенация на пътища (алтернатива на os.path.join):
dataset_dir_path = Path("data_folder") / "raw_datasets" / "some_dataset_dir"
meta_file_path = dataset_dir_path / "meta.csv"
data_dir_path = dataset_dir_path / "data"
data_dir_path
WindowsPath('data_folder/raw_datasets/some_dataset_dir/data')
Path, както и os.path.join, автоматично проверяват дали пътят трябва да е Windows или Unix-съвместим.
Details for nerds: В случая на Path, който е базов клас, при създаване на обект той автоматично преценява кой от двата subclass-a всъщност да създаде - WindowsPath или PosixPath (това е възможно чрез предефиниране на дъндъра __new__).
Path има доста полезни атрибути и методи:
example_dir = Path('files')
example_file = example_dir / "1.txt"
example_file.parent
WindowsPath('files')
example_file.parent.parent
WindowsPath('.')
example_file.name
'1.txt'
example_file.stem
'1'
example_file.suffix
'.txt'
example_file.is_file()
True
example_file.read_text()
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. \nUt enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. \nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. \nExcepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum\n'
example_file.read_bytes()
b'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. \r\nUt enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. \r\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. \r\nExcepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum\r\n'
new_file = example_dir / "new_file.txt"
new_file.touch() # creates an empty file
example_file.write_text('Hello world') # returns the number of bytes written
11
renamed_file = new_file.rename("here.txt") # can move as well
renamed_file.unlink()
example_dir.is_dir()
True
list(example_dir.iterdir()) # iterates over all files inside
[WindowsPath('files/1.txt')]
list(example_dir.glob('*.txt')) # iterates over all files inside that end with .txt
# globs can have special syntax for powerful filtering: https://en.wikipedia.org/wiki/Glob_(programming)
[WindowsPath('files/1.txt')]
example_file.resolve() # relative path -> absolute path
WindowsPath('C:/Users/Owner/Documents/PythonCourse2024/08 - Files/files/1.txt')
# example_file.owner() # not available on Windows
new_dir = example_dir / "new_dir"
new_dir.mkdir()
new_dir.rmdir()
Ако ви трябва някой Path обект като низ, може да използвате str:
str(example_file)
'files\\1.txt'
Примери#
Пример 1#
Напишете функция split_path, която приема път (като низ), и го разделя на съставните му части. Използвайте os.path.split.
Решение на пример 1#
import os
from typing import List
def split_path(file_path: str) -> List[str]:
head, tail = os.path.split(file_path)
result = [tail]
while head != '' and head != file_path:
file_path = head
head, tail = os.path.split(file_path)
result.append(tail)
result.reverse()
return result
print(split_path('/foo/bar/baz'))
print(split_path('/foo/bar/baz/'))
print(split_path('/foo'))
print(split_path('foo'))
Пример 2#
Даден е клас Person, който съдържа информация за човек - неговите имена, рожденна дата, възраст и работа.
class Person:
def __init__(self, first_name: str='', last_name: str='', birthdate: str='', age: int=0, job: str=''):
self.__first_name = first_name
self.__last_name = last_name
self.__birthdate = birthdate
self.__age = age
self.__job = job
@property
def first_name(self) -> str:
return self.__first_name
@property
def last_name(self) -> str:
return self.__last_name
@property
def birthdate(self) -> str:
return self.__birthdate
@property
def age(self) -> str:
return self.__age
@property
def job(self) -> str:
return self.__job
Напишете клас PersonSerializer, съдържащ следните методи:
Метод, който приема обект от тип
Personи път. Методът записва във файл информацията за човекаМетод, който приема път и връща обект от тип
Person, който е създаден на база данните от файлаМетод, който приема списък от
Personи път. Методът записва във файл информация за хоратаМетод, който приема път и връща списък от
Person, които са създадени на база на информацията във файла
При грешка при писането или четенето класът да хвърля ValueError.
Може да приемем, че информацията за един човек ще бъде записана на един ред.
Решение на пример 2#
from typing import List
class Person:
def __init__(self, first_name: str='', last_name: str='', birthdate: str='', age: int=0, job: str=''):
self.__first_name = first_name
self.__last_name = last_name
self.__birthdate = birthdate
self.__age = age
self.__job = job
@property
def first_name(self) -> str:
return self.__first_name
@property
def last_name(self) -> str:
return self.__last_name
@property
def birthdate(self) -> str:
return self.__birthdate
@property
def age(self) -> str:
return self.__age
@property
def job(self) -> str:
return self.__job
# Adding a __str__ magic method helps us with printing the info
def __str__(self) -> str:
return f'{self.first_name}, {self.last_name}, {self.birthdate}, {self.age}, {self.job}'
@staticmethod
def from_string(person_info: str):
first_name, last_name, birthdate, age, job = person_info.strip().split(', ')
return Person(first_name, last_name, birthdate, age, job)
class PersonSerializer:
@staticmethod
def save_person(person: Person, filepath: str, mode: str='w'):
with open(filepath, mode) as fp:
fp.write(str(person) + '\n')
@staticmethod
def create_person_from_file(filepath: str) -> Person:
if not os.path.exists(filepath):
raise ValueError(f'File not found at {filepath}')
with open(filepath) as fp:
person_info = fp.read()
return Person.from_string(person_info)
@staticmethod
def save_people(people: List[Person], filepath: str):
for person in people:
PersonSerializer.save_person(person, filepath, mode='a')
@staticmethod
def create_people_from_file(filepath: str) -> List[Person]:
if not os.path.exists(filepath):
raise ValueError(f'File not found at {filepath}')
with open(filepath) as fp:
return [Person.from_string(person_info) for person_info in fp]
me = Person('Lyubo', 'Karev', '20-09-1998', 24, 'Developer')
my_filepath = os.path.join('files', 'lyubo.txt')
PersonSerializer.save_person(me, my_filepath)
me_again = PersonSerializer.create_person_from_file(my_filepath) # Hello me, meet the real me
print(me)
print(me_again)
bunch_of_people = [
Person('Lyubo', 'Karev', '20-09-1998', 24, 'Developer'),
Person('Alex', 'Ignatov', '22-10-1998', 24, 'Developer'),
Person('Ivan', 'Luchev', '28-04-1998', 24, 'Student')
]
our_filepath = os.path.join('files', 'us.txt')
PersonSerializer.save_people(bunch_of_people, our_filepath)
us_again = PersonSerializer.create_people_from_file(our_filepath)
print('Bunch of people:')
for person in bunch_of_people:
print(person)
print('Us again:')
for person in us_again:
print(person)
Пример 3#
Както споменахме по-горе, writelines не добавя автоматично нови редове след всеки подаден елемент от списъка.
Напишете функция better_writelines, която поправя тази грешка.
Решение на Пример 3#
def better_writelines(lines: list[str], filepath: str):
with open(filepath, 'w') as fp:
fp.writelines([line + '\n' for line in lines])
Пример 4#
os.rmdir изтрива директория, само ако е празна.
Напишете функция rm_rf, която по подадена директория, изтрива съдържанието ѝ, както и самата директория.
Решение на Пример 4#
def rm_rf(filepath: str):
if os.path.isdir(filepath):
for file in os.listdir(filepath):
rm_rf(os.path.join(filepath, file))
os.rmdir(filepath)
else:
os.remove(filepath)
Пример 5#
Напишете програма за управление на музикални файлове.
Програмата трябва да поддържа следните функционалности:
Задаване на работна директория (директория, в която са ни музикалните файлове)
Извеждане на списък с всички песни (приемете, че всеки файл ще е с име във формат
artist-song_name.mp3Търсене на песен по изпълнител (връща точно съвпадение)
Търсене на псене по име на песен (връща точно съвпадение)
Потребителския интефейс може да бъде следния:
Потребителя има достъп до 5 команди (
set,list,find artist,find songиexit)Потребителя може да въвежда (и изпълнява) команди, до въвеждане на
exit, при което програмата спира работа. (Ако имате други идеи за интерфейса, също ще бъдат приети)
Примерни песни може да свалите от тук