Работа с файлове в Python#
План на лекцията:
Кратка интродукция за работата с пътища, Windows и Unix
Представяне на файловете в Python
Четене на файлове
Писане на файлове
Работа с файлове и пътища
Използване на
with
tell
и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
- създава файла, ако не съществува. Ако файла вече съществува, се хвърляFileExistsError
b
- отваря файл в бинарен режим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+
ни дава права за четене и пренаписване.
Примери#
Пример 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
, при което програмата спира работа. (Ако имате други идеи за интерфейса, също ще бъдат приети)
Примерни песни може да свалите от тук