Zanurkuj w Pythonie/Praca na plikach

Praca z obiektami plików

edytuj

Python posiada wbudowaną funkcję open, służącą do otwierania plików z dysku. open zwraca obiekt pliku posiadający metody i atrybuty, dzięki którym możemy dostać się do pliku i wykonywać na nim pewne operacje.

Przykład. Otwieranie plików
>>> f = open("/muzyka/_single/kairo.mp3", "rb")                     #(1)
>>> f                                                               #(2)
<open file '/muzyka/_single/kairo.mp3', mode 'rb' at 010E3988>
>>> f.mode                                                          #(3)
'rb'
>>> f.name                                                          #(4)
'/muzyka/_single/kairo.mp3'
  1. Metoda open przyjmuje do trzech argumentów: nazwę pliku, tryb i argument buforowania. Tylko pierwszy z nich, nazwa pliku, jest wymagany; pozostałe dwa są opcjonalne. Jeśli nie są podane, plik zostanie otwarty do odczytu w trybie tekstowym. Tutaj otworzyliśmy plik do odczytu w trybie binarnym. (print open.__doc__ da nam świetne objaśnienie wszystkich możliwych trybów.)
  2. Metoda open zwraca obiekt (w tym momencie nie powinno to już być zaskoczeniem). Obiekt pliku ma kilka użytecznych atrybutów.
  3. Atrybut mode obiektu pliku mówi nam, w jakim trybie został on otwarty.
  4. Atrybut name zwraca ścieżkę do pliku, który jest dostępny z tego obiektu.

Czytanie z pliku

edytuj

Otworzywszy plik, będziemy chcieli odczytać z niego informacje, tak jak pokazano to w następnym przykładzie.

Przykład. Czytanie pliku
>>> f
<open file '/muzyka/_single/kairo.mp3', mode 'rb' at 010E3988>
>>> f.tell()                                                        #(1)
0
>>> f.seek(-128, 2)                                                 #(2)
>>> f.tell()                                                        #(3)
7542909
>>> tagData = f.read(128)                                           #(4)
>>> tagData
'TAGKAIRO****THE BEST GOA         ***DJ MARY-JANE***            
Rave Mix                      2000http://mp3.com/DJMARYJANE     \037'
>>> f.tell()                                                        #(5)
7543037
  1. Obiekt pliku przechowuje stan otwartego pliku. Metoda tell zwraca aktualną pozycję w otwartym pliku. Z uwagi na to, że nie robiliśmy jeszcze nic z tym plikiem, aktualna pozycja to 0, czyli początek pliku.
  2. Metoda seek obiektu pliku służy do poruszania się po otwartym pliku. Jej drugi argument określa znaczenie pierwszego argumentu; jeśli argument drugi wynosi 0, oznacza to, że pierwszy argument odnosi się do pozycji bezwzględnej (czyli licząc od początku pliku), 1 oznacza przeskok do innej pozycji względem pozycji aktualnej (licząc od pozycji aktualnej), 2 oznacza przeskok do danej pozycji względem końca pliku. Jako że tagi MP3, o które nam chodzi, przechowywane są na końcu pliku, korzystamy z opcji 2 i przeskakujemy do pozycji oddalonej o 128 bajtów od końca pliku.
  3. Metoda tell potwierdza, że rzeczywiście zmieniliśmy pozycję pliku.
  4. Metoda read czyta określoną liczbę bajtów z otwartego pliku i zwraca dane w postaci łańcucha znaków, które zostały odczytane. Opcjonalny argument określa maksymalną liczbę bajtów do odczytu. Jeśli nie zostanie podany argument, read będzie czytał do końca pliku. (W tym przypadku moglibyśmy użyć samego read(), ponieważ wiemy dokładnie w jakiej pozycji w pliku jesteśmy i w rzeczywistości odczytujemy ostanie 128 bajtów.) Odczytane dane przypisujemy do zmiennej tagData, a bieżąca pozycja zostaje uaktualniana na podstawie ilości odczytanych bajtów.
  5. Metoda tell potwierdza, że zmieniła się bieżąca pozycja. Jeśli pokusimy się o wykonanie obliczenia, zauważymy, że po odczytaniu 128 bajtów aktualna pozycja wzrosła o 128.

Zamykanie pliku

edytuj

Otwarte pliki zajmują zasoby systemu, a inne aplikacje czasami mogą nie mieć do nich dostępu (zależy to od trybu otwarcia pliku), dlatego bardzo ważne jest zamykanie plików tak szybko, jak tylko skończymy na nich pracę.

Przykład. Zamykanie pliku
>>> f
<open file '/muzyka/_single/kairo.mp3', mode 'rb' at 010E3988>
>>> f.closed                                                        #(1)
False
>>> f.close()                                                       #(2)
>>> f
<closed file '/muzyka/_single/kairo.mp3', mode 'rb' at 010E3988>
>>> f.closed                                                        #(3)
True
>>> f.seek(0)                                                       #(4)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ValueError: I/O operation on closed file
>>> f.tell()
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ValueError: I/O operation on closed file
>>> f.read()
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ValueError: I/O operation on closed file
>>> f.close()                                                       #(5)
  1. Atrybut closed obiektu pliku mówi, czy plik jest otwarty, czy też nie. W tym przypadku plik jeszcze jest otwarty (closed jest równe False).
  2. Aby zamknąć plik należy wywołać metodę close obiektu pliku. Zwalnia to blokadę, która nałożona była na plik (jeśli była nałożona), oczyszcza buforowane dane (jeśli jakiekolwiek dane w nim występują), które system nie zdążył jeszcze rzeczywiście zapisać, a następnie zwalnia zasoby.
  3. Atrybut closed potwierdza, że plik jest zamknięty.
  4. To że plik został zamknięty, nie oznacza od razu, że obiekt przestaje istnieć. Zmienna f będzie istnieć póki nie wyjdzie poza swój zasięg, lub nie zostanie skasowana ręcznie. Aczkolwiek żadna z metod służących do operowania na otwartym pliku, nie będzie działać od momentu jego zamknięcia; wszystkie rzucają wyjątek.
  5. Wywołanie close na pliku uprzednio zamkniętym nie zwraca wyjątku; w przypadku błędu cicho sobie z nim radzi.

Błędy wejścia/wyjścia

edytuj

Zrozumienie kodu fileinfo.py z poprzedniego rozdziału, nie powinno już być problemem. Kolejny przykład pokazuje, jak bezpiecznie otwierać i zamykać pliki oraz jak należycie obchodzić się z błędami.

Przykład. Obiekty pliku w MP3FileInfo
try:                                                               #(1)
    fsock = open(filename, "rb", 0)                                #(2)
    try:
        fsock.seek(-128, 2)                                        #(3)
        tagdata = fsock.read(128)                                  #(4)
    finally:                                                       #(5)
        fsock.close()
    
    
    
except IOError:                                                    #(6)
    pass
  1. Ponieważ otwieranie pliku i czytanie z niego jest ryzykowne, a także operacje te mogą rzucić wyjątek, cały ten kod jest ujęty w blok try...except. (Hej, czy zestandaryzowane wcięcia nie są świetne? To moment, w którym zaczynasz je doceniać.)
  2. Funkcja open może rzucić wyjątek IOError. (Plik może nie istnieć.)
  3. Funkcja seek może rzucić wyjątek IOError. (Plik może być mniejszy niż 128 bajtów.)
  4. Funkcja read może rzucić wyjątek IOError. (Być może dysk posiada uszkodzony sektor, albo plik znajduje się na dysku sieciowym, a sieć właśnie przestała działać.)
  5. Nowość: blok try...finally. Nawet po udanym otworzeniu pliku przez open chcemy być całkowicie pewni, że zostanie on zamknięty niezależnie od tego, czy metody seek i read rzucą wyjątki, czy też nie. Właśnie do takich rzeczy służy blok try...finally: kod z bloku finally zostanie zawsze wykonany, nawet jeśli jakaś instrukcja bloku try rzuci wyjątek. Należy o tym myśleć jak o kodzie wykonywanym na zakończenie operacji, niezależnie od tego co działo się wcześniej.
  6. Nareszcie poradzimy sobie z wyjątkiem IOError. Może to być wyjątek wywołany przez którąkolwiek z funkcji open, seek, czy read. Tym razem nie jest to dla nas istotne, gdyż jedyną rzeczą, którą zrobimy to zignorowanie tego wyjątku i kontynuowanie dalszej pracy programu. (Pamiętajmy, pass jest wyrażeniem Pythona, które nic nie robi.) Takie coś jest całkowicie dozwolone; to że przechwyciliśmy dany wyjątek, nie oznacza, że musimy z nim cokolwiek robić. Wyjątek zostanie potraktowany tak, jakby został obsłużony, a kod będzie normalnie kontynuowany od następnej linijki kodu po bloku try...except.

Zapisywanie do pliku

edytuj

Jak można przypuszczać, istnieje również możliwość zapisywania danych do plików w sposób bardzo podobny do odczytywania. Wyróżniamy dwa podstawowe tryby zapisywania plików:

  • w trybie "append", w którym dane będą dodawane na końcu pliku
  • w trybie "write", w którym plik zostanie nadpisany.

Oba tryby, jeśli plik nie będzie istniał, utworzą go automatycznie, dlatego nie ma potrzeby na fikuśne działania typu "jeśli dany plik jeszcze nie istnieje, utwórz nowy pusty plik, aby móc go otworzyć po raz pierwszy". Po prostu otwieramy plik i zaczynamy do niego zapisywać dane.

Przykład. Pisanie do pliku
>>> logfile = open('test.log', 'w')                                           #(1)
>>> logfile.write('udany test')                                               #(2)
>>> logfile.close()
>>> print open('test.log').read()                                             #(3)
udany test
>>> logfile = open('test.log', 'a')                                           #(4)
>>> logfile.write('linia 2')
>>> logfile.close()
>>> print open('test.log').read()                                             #(5)
udany testlinia 2
  1. Zaczynamy odważnie: tworzymy nowy lub nadpisujemy istniejący plik test.log, a następnie otwieramy do zapisu. (Drugi argument "w" oznacza otwieranie pliku do zapisu.) Tak, jest to dokładnie tak niebezpieczne, jak brzmi. Miejmy nadzieję, że poprzednia zawartość pliku nie była istotna, bo już jej nie ma.
  2. Dane do nowo otwartego pliku dodajemy za pomocą metody write obiektu zwróconego przez open.
  3. Ten jednowierszowiec otwiera plik, czyta jego zawartość i drukuje na ekran.
  4. Przypadkiem wiemy, że test.log istnieje (w końcu właśnie skończyliśmy do niego pisać), więc możemy go otworzyć i dodawać dane. (Argument "a" oznacza otwieranie pliku w trybie dodawania danych na koniec pliku.) Właściwie moglibyśmy to zrobić nawet wtedy, gdyby plik nie istniał, ponieważ otworzenie pliku w trybie "a" spowoduje jego powstanie, jeśli będzie to potrzebne. Otworzenie w trybie "a" nigdy nie uszkodzi aktualnej zawartości pliku.
  5. Jak widać, zarówno pierwotnie zapisane, jak i dopisane dane, aktualnie znajdują się w pliku test.log. Należy zauważyć, że znaki końca linii nie są uwzględnione. Jako że nie zapisywaliśmy znaków końca linii w żadnym z przypadków, plik ich nie zawiera. Znaki końca linii możemy zapisać za pomocą symbolu "\n". Z uwagi na fakt, iż tego nie zrobiliśmy, całość danych w pliku wylądowała w jednej linijce.

Materiały dodatkowe

edytuj