Języki skryptowe Python Wykład 6 Moduły i pliki Janusz Szwabiński Plan wykładu: Moduły i ich importowanie Tworzenie własnych modułów Pliki odczyt i zapis Manipulowanie plikami i katalogami
Moduły i ich importowanie skrypty w Pythonie lub kod skompilowany np. w C/C++ najczęściej zawierają definicje obiektów do wielokrotnego użycia definicje w nich zawarte mogą być w każdej chwili dostępne w interpreterze lub innym skrypcie do załadowania modułu służy instrukcja import dodatkowo wbudowana funkcja import pozwala na załadowanie modułu, którego nazwa została określona w trakcie wykonywania programu moduł można przeładować w locie (np. po dokonaniu w nim zmian) poleceniem reload moduły to przykłady obiektów typy singleton. tzn. tylko jedna instancja konkretnego modułu jest załadowana do pamięci, i jest ona globalnie dostępna moduły mają własne przestrzenie nazw Kilka słów o przestrzeniach nazw Przestrzeń nazw (nie tylko) w Pythonie to abstrakcyjna przestrzeń, w której dowolne słowo może być jednoznacznie przypisane do reprezentowanego przez nie obiektu (funkcji, zmiennej itp.): wskazanie obiektu poprzez nazwę większość przestrzeni nazw zaimplementowana w postaci słowników (może się zmienić w przyszłości) trzy ważne przestrzenie nazw: zbiór nazw wbudowanych nazwy lokalne (podczas wywołania funkcji) nazwy globalne nie istnieje związek pomiędzy nazwami z różnych przestrzeni nazw przestrzenie nazw tworzone są w różnych chwilach i są aktywne przez różny czas przestrzeń zawierająca nazwy wbudowane tworzona jest podczas pracy Pythona i nigdy nie jest usuwana przestrzeń nazw globalnych modułu tworzona jest podczas wczytywania jego definicji i jest aktywna do chwili zakończenia pracy interpretera instrukcje wykonywane przez szczytowe wywołania interpretera, zarówno czytane z pliku jak i wprowadzane interaktywnie, są częścią modułu o nazwie main nazwy wbudowane przechowywane są w module builtin przestrzeń nazw lokalnych funkcji tworzona jest w momencie jej wywołania i niszczona, gdy następuje powrót z funkcji (również przez nieobsłużony wyjątek) wywołanie rekurencyjne powoduje tworzenie za każdym razem nowej przestrzeni nazw lokalnych zasięg jest tekstowym obszarem programu, w którym przestrzeń nazw jest wprost osiągalna, tzn. niekwalifikowane odniesienia do nazwy znajdują ją w obowiązującej przestrzeni nazw. Dla przykładu, jeżeli w celu wyliczenia pierwiastka kwadratowego z liczby 10 muszę wykonać polecenie math.sqrt(10)to funkcja sqrtnie jest dostępna bezpośrednio, czyli znajduje się poza zasięgiem aktualnej przestrzeni nazw. w każdym momencie wykonania programu używa się trzech zagnieżdżonych zasięgów nazw: najbardziej zagnieżdżony, nazwy lokalne środkowy, zawiera aktualne nazwy globalne modułu zewnętrzny, jest zasięgiem nazw wbudowanych przypisanie zawsze zachodzi w najbardziej zagnieżdżonym zasięgu przypisania nie powodują kopiowania danych, przywiązują jedynie nazwy do obiektów
In [1]: print dir( builtin ) ['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseExcepti on', 'BufferError', 'BytesWarning', 'DeprecationWarning', 'EOFErro r', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FloatingP ointerror', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportErro r', 'ImportWarning', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'Non e', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowErr or', 'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'True', 'T ypeerror', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncode Error', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', ' IPYT HON ', ' IPYTHON active', ' debug ', ' doc ', ' import ', ' name ', ' package ', 'abs', 'all', 'any', 'apply', 'basestrin g', 'bin', 'bool', 'buffer', 'bytearray', 'bytes', 'callable', 'ch r', 'classmethod', 'cmp', 'coerce', 'compile', 'complex', 'copyrigh t', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'dreload', 'enume rate', 'eval', 'execfile', 'file', 'filter', 'float', 'format', 'fro zenset', 'get_ipython', 'getattr', 'globals', 'hasattr', 'hash', 'he lp', 'hex', 'id', 'input', 'int', 'intern', 'isinstance', 'issubclas s', 'iter', 'len', 'license', 'list', 'locals', 'long', 'map', 'ma x', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'po w', 'print', 'property', 'range', 'raw_input', 'reduce', 'reload', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 's taticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'unichr', 'uni code', 'vars', 'xrange', 'zip'] Importowanie modułów Do załadowania modułu służy funkcja import. Jego nazwa trafia wówczas do bieżącej tablicy symboli. Przez tę nazwę odnosimy się do elementów modułu: In [2]: import math print math.sqrt(2) 1.41421356237 Funkcja import umożliwia również zaimportowanie wybranych elementów do aktualnej tablicy symboli. Możliwe są wówczas niekwalifikowane odniesienia do ich nazw: In [3]: from math import sqrt as SQ print SQ(2) 1.41421356237 Istnieje również możliwość załadowania całego modułu do bieżącej tablicy symboli, jednak powinniśmy go unikać, ponieważ może to prowadzić do niekontrolowanego nadpisania niektórych funkcji:
In [4]: from math import * print sqrt(2) 1.41421356237 Jeżeli używamy jej w skrypcie albo innym module, w dobrym zwyczaju załadowanie wszystkich potrzebnym modułów na początku pliku źródłowego. Tworzenie własnych modułów Rozważmy najpierw dwie funkcje : In [5]: def fib(n): # wypisz na ekranie wyrazy szeregu Fibonacciego mniej sze od n a, b = 0, 1 while b < n: print b, a, b = b, a+b def fib2(n): # zwróć listę wyrazów szeregu Fibonacciego mniejszych o d n result = [] a, b = 0, 1 while b < n: result.append(b) a, b = b, a+b return result In [6]: fib(10) 1 1 2 3 5 8 In [7]: fib2(10) Out[7]: [1, 1, 2, 3, 5, 8] Ponieważ funkcje te mogą nam się jeszcze przydać, zapiszmy je do pliku fibo.py:
In [108]: %%writefile fibo.py def fib(n): a, b = 0, 1 while b < n: print b, a, b = b, a+b def fib2(n): result = [] a, b = 0, 1 while b < n: result.append(b) a, b = b, a+b return result Writing fibo.py Ten plik to nic innego jak nasz pierwszy (dość prosty) moduł: In [8]: import fibo dir(fibo) Out[8]: [' builtins ', ' doc ', ' file ', ' name ', ' package ', 'fib', 'fib2'] In [9]: fibo.fib(10) 1 1 2 3 5 8 In [10]: fibo.fib2(10) Out[10]: [1, 1, 2, 3, 5, 8] Moduł ten możemy nieco "podrasować", dodając m.in. dokumentację:
In [117]: %%writefile fibo.py # -*- coding: utf-8 -*- """Funkcje generujące wyrazy szeregu Fibonacciego""" def fib(n): """Wyświetl wyrazy szeregu mniejsze od n na ekranie""" a, b = 0, 1 while b < n: print b, a, b = b, a+b def fib2(n): """Zwróć listę wyrazów szeregu mniejszych od n""" result = [] a, b = 0, 1 while b < n: result.append(b) a, b = b, a+b return result Overwriting fibo.py In [11]: reload(fibo) Out[11]: <module 'fibo' from 'fibo.pyc'> In [12]: help(fibo) Help on module fibo: NAME fibo - Funkcje generujące wyrazy szeregu Fibonacciego FILE /home/szwabin/dropbox/zajęcia/pythonintro/6_moduly_i_pliki/fib o.py FUNCTIONS fib(n) Wyświetl wyrazy szeregu Fibonacciego mniejsze od n fib2(n) Zwróć listę wyrazów szeregu Fibonacciego mniejszych od n Częstą praktyką jest dodawanie do modułu kodu, który zostanie wykonany w momencie uruchomienia modułu jako samodzielnego programu. Zrealizujemy to w następujący sposób (argument -aoznacza, że dodajemy do istniejącego pliku):
In [129]: %%writefile -a fibo.py def test(): """Funkcja testująca działanie modułu""" assert(fib2(4)[-1]==3) print "Test zakończony poprawnie" if name == ' main ': test() Appending to fibo.py Zmienna name zawiera z reguły nazwę modułu. Wyjątkiem jest sytuacja, w której moduł uruchomiony jest jako samodzielny program wówczas ta zmienna odnosi się do napisu main. Sprawdźmy, jak to działa. Po przeładowaniu modułu pojawiła się dodatkowa funkcja: In [13]: reload(fibo) Out[13]: <module 'fibo' from 'fibo.py'> In [14]: dir(fibo) Out[14]: [' builtins ', ' doc ', ' file ', ' name ', ' package ', 'fib', 'fib2', 'test'] Możemy ją oczywiście wywołać ręcznie: In [15]: fibo.test() Test zakończony poprawnie Przy uruchomieniu modułu jako skryptu, zostanie ona uruchomiona automatycznie: In [16]:!python fibo.py Test zakończony poprawnie Ścieżka wyszukiwania modułów gdy moduł fibojest importowany, interpreter Pythona poszukuje pliku fibo.pyw katalogu bieżącym, a następnie w katalogach określonych w zmiennej środowiskowej PYTHONPATH jeśli zmienna PYTHONPATHnie jest określona, lub modułu nie ma w katalogach zdefiniowanych w zmiennej, interpreter kontynuuje poszukiwanie w ścieżkach ustalonych w momencie instalacji (pod Linuksem najczęściej /usr/lib/pythonlub /usr/local/lib/python) na samym końcu przeszukiwane są katalogi umieszczone w zmiennej sys.path
In [17]: import sys print sys.path ['', '/usr/lib/python2.7', '/usr/lib/python2.7/plat-x86_64-linux-gn u', '/usr/lib/python2.7/lib-tk', '/usr/lib/python2.7/lib-old', '/us r/lib/python2.7/lib-dynload', '/usr/local/lib/python2.7/dist-package s', '/usr/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-pac kages/pilcompat', '/usr/lib/python2.7/dist-packages/gtk-2.0', '/us r/lib/pymodules/python2.7', '/usr/lib/python2.7/dist-packages/ubunt u-sso-client', '/usr/lib/python2.7/dist-packages/wx-3.0-gtk2', '/us r/lib/python2.7/dist-packages/ipython/extensions'] In [18]:!echo $PYTHONPATH Rozszerzanie ścieżki wyszukiwania Pod Linuksem dodajemy do pliku.bashrcw naszym katalogu domowym następujący kod: PYTHONPATH=$PYTHONPATH:$HOME/MyPython export PYTHONPATH Ze wzgledu na legendarną przyjazność systemu operacyjnego dla użytkownika, użytkownicy Windowsa radzą sobie sami :) Pliki odczyt i zapis Wbudowana funkcja openotwiera plik i tworzy odpowiadający mu obiekt. Jej składnia jest następująca: open(nazwa_pliku,tryb) pierwszy argument to ciąg znaków będący nazwą pliku drugi argument to ciąg znaków sposób pracy z plikiem 'r' tylko do odczytu (wartość domyślna) 'w' tylko do zapisu (istniejący plik o podanej nazwie zostanie nadpisany!) 'a' dopisywanie do istniejącego pliku 'r+' do odczytu i zapisu 'rb' tylko do odczytu w trybie binarnym 'wb' tylko do zapisu w trybie binarnym 'r+b' do odczytu i zapisu w trybie binarnym In [19]: f = open('workfile','w') print f <open file 'workfile', mode 'w' at 0x7f7a9dec4420>
Pracując z obiektami plików, mamy do dyspozycji następujące metody: read() wczytuje całą zawartość pliku i przekształca ją w napis (uwaga na duże pliki!) read(n) wczytuje nbajtów z pliku readline() czyta pojedynczy wiersz z pliku readlines() zwraca listę, której elementami są łańcuchy znaków reprezentujące poszczególne wiersze z pliku (uwaga na duże pliki!) write(napis) zapisuje łańcuch znaków do pliku tell() bieżąca pozycja w pliku mierzona w bajtach seek(k,p) zmienia pozycję w pliku dodając kbajtów do punktu odniesienia p: p=0 początek pliku p=1 pozycja bieżąca p=2 koniec pliku close() zamknięcie pliku In [20]: f.close() print f <closed file 'workfile', mode 'w' at 0x7f7a9dec4420> Otwórzmy powyższy plik raz jeszcze, tym razem w trybie do odczytu i zapisu: In [21]: f = open("workfile",'r+') Sprawdzamy jego zawartość: In [22]: f.read() #powinien być pusty Out[22]: '' Wpisujemy coś do pliku: In [23]: f.write("0123456789abcdef") Odczytujemy pozycję w pliku: In [24]: f.tell() Out[24]: 16 Przechodzimy do 5 bajtu w pliku i odczytujemy kolejny jeden bajt: In [25]: f.seek(5) f.read(1) Out[25]: '5' Ponownie odczytujemy pozycję:
In [26]: f.tell() Out[26]: 6 Trzeci bajt od końca: In [27]: f.seek(-3,2) f.read(1) Out[27]: 'd' Wracamy na początek i odczytujemy dwa kolejne bajty: In [28]: f.seek(0) f.read(2) Out[28]: '01' Wczytujemy cały wiersz: In [29]: f.seek(0) f.readline() Out[29]: '0123456789abcdef' Próba odczytu po zamknięciu pliku spowoduje błąd: In [30]: f.close() f.readline() -------------------------------------------------------------------- ------- ValueError Traceback (most recent cal l last) <ipython-input-30-02a0f4f4da9c> in <module>() 1 f.close() ----> 2 f.readline() ValueError: I/O operation on closed file Z życia (naukowców) wzięte Rozważmy plik będący wynikiem numerycznego rozwiązania równań ruchu oscylatora harmonicznego z tłumieniem:
In [71]:!head -20 osc_rk4.dat #osc_rk4.dat #oscylator harmoniczny z tlumieniem #i sila wymuszajaca #czas(s) polozenie(m) predkosc(m/s) 0.251327 0.0247305 0.113945 0.502655 0.0652931 0.204403 0.753982 0.125012 0.265655 1.00531 0.196106 0.294634 1.25664 0.270357 0.290914 1.50796 0.339749 0.256465 1.75929 0.397015 0.195193 2.01062 0.436054 0.112386 2.26195 0.452214 0.0141906 2.51327 0.442452-0.092775 2.7646 0.405419-0.201675 3.01593 0.341495-0.305649 3.26726 0.252758-0.398059 3.51858 0.142889-0.472887 3.76991 0.0169508-0.52526 4.02124-0.118975-0.551995 Widzimy, że pierwsze 4 wiersze w pliku to komentarz, natomiast kolejne zawierają dane opisujące położenie i prędkość oscylatora w funkcji czasu, uporządkowane w 3 kolumnach. Naszym celem jest wczytanie tego pliku w Pythonie. Możliwość pierwsza lista wszystkich wierszy: In [73]: results = [] f = open("osc_rk4.dat","r") #wczytujemy wiersze, pomijamy komentarz lines = f.readlines()[4:] #zamykamy plik f.close() #wydobywamy dane z wczytanych ciągów znaków for line in lines: #dzielimy wiersz na pola fields = line.split() #zamieniamy napisy na liczby time = float(fields[0]) pos = float(fields[1]) vel = float(fields[2]) #dopisujemy do listy z wynikami all = (time,pos,vel) results.append(all) #sprawdzamy wynik for i in results[:5]: print i (0.251327, 0.0247305, 0.113945) (0.502655, 0.0652931, 0.204403) (0.753982, 0.125012, 0.265655) (1.00531, 0.196106, 0.294634) (1.25664, 0.270357, 0.290914)
W przypadku bardzo dużych plików rozwiązanie to mocno obciąża pamięć, ponieważ cały plik musi być załadowany do pamięci operacyjnej komputera. Dlatego lepiej jest iterować po wierszach w pliku: In [74]: results = [] f = open("osc_rk4.dat","r") #iteracja po wierszach - w każdym kroku wczytywany tylko jeden wiers z for line in f: #ignorujemy komentarz if line[0] == "#": continue #wydobycie danych tym razem bardziej "pythonowo" all = [float(val) for val in line.split()] results.append(all) #zamykamy plik f.close() #sprawdzamy wynik for i in results[:5]: print i [0.251327, 0.0247305, 0.113945] [0.502655, 0.0652931, 0.204403] [0.753982, 0.125012, 0.265655] [1.00531, 0.196106, 0.294634] [1.25664, 0.270357, 0.290914] Iterację po wierszach można również wykonać w wersji tylko dla wtajemniczonych: In [75]: results3 = [[float(val) for val in l.split()] for l in open("osc_rk4.dat","r") if l[0]!= "#"] for i in results3[:5]: print i [0.251327, 0.0247305, 0.113945] [0.502655, 0.0652931, 0.204403] [0.753982, 0.125012, 0.265655] [1.00531, 0.196106, 0.294634] [1.25664, 0.270357, 0.290914] Inna możliwość to pętla while:
In [76]: f = open("osc_rk4.dat","r") while 1: line = f.readline() if not line: break if line[0] == "#": print line f.close() #osc_rk4.dat #oscylator harmoniczny z tlumieniem #i sila wymuszajaca #czas(s) polozenie(m) predkosc(m/s) Do dobrych praktyk należy jednak używanie polecenia withprzy pracy z plikami. Jego zaletą jest to, że odczytywany plik zostanie poprawnie (automatycznie) zamknięty albo po zakończeniu odczytu, albo po wystąpieniu wyjątku: In [78]: with open('osc_rk4.dat') as f: results4 = [[float(val) for val in l.split()] for l in f i f l[0]!= "#"] for i in results4[:5]: print i [0.251327, 0.0247305, 0.113945] [0.502655, 0.0652931, 0.204403] [0.753982, 0.125012, 0.265655] [1.00531, 0.196106, 0.294634] [1.25664, 0.270357, 0.290914] Ponieważ plik zostaje otworzony w obrębie bloku rozpoczętego poleceniem with, należy do zasięgu lokalnego. Tuż przed wyjściem z tego bloku interpreter zwalnia zasoby zajęte przez zmienne lokalne, a zatem powinien zamknąć plik. Sprawdźmy, czy jest tak rzeczywiście: In [79]: print f <closed file 'osc_rk4.dat', mode 'r' at 0x7f9125511420> Zapisywanie do pliku
In [80]: #otwieramy plik do zapisu of = open("dane.dat","w") #dobrym zwyczajem jest wpisać do niego jakiś komentarz of.write("#dane.dat\n") of.write("#oscylator harmoniczny z tlumieniem i sila wym.\n") of.write("#czas polozenie predkosc\n") #zapisujemy wyniki z listy results for l in results: of.write("%5.2f %5.2f %5.2f\n"%(l[0],l[1],l[2])) #zamykamy plik of.close() Sprawdźmy zawartość pliku dane.dat(w konsoli uniksowej): In [81]:!head -10 dane.dat #dane.dat #oscylator harmoniczny z tlumieniem i sila wym. #czas polozenie predkosc 0.25 0.02 0.11 0.50 0.07 0.20 0.75 0.13 0.27 1.01 0.20 0.29 1.26 0.27 0.29 1.51 0.34 0.26 1.76 0.40 0.20
Zauważmy, że writew pętli forw powyższym przykładzie zapisuje do pliku cały wiersz z danymi na raz. Wiersz ten utworzony jest z szablonu, czyli łancucha znaków zawierającego pewne symbole. W ich miejsce wstawiane są przy pomocy operatora %dane liczbowe z krotki będącej prawym argumentem tego operatora. Możliwe jest następujące formatowanie: %d liczba całkowita %5d liczba całkowita w polu o szerokosci 5 znaków %-5d j.w, ale wyrównana do lewej %05d j.w., ale uzupełniona zerami od lewej (np. 00041) %g liczba zmiennoprzecinkowa w formacie %f lub %e %e liczba zmiennoprzecinkowa w formacie naukowym (np. 1.100000e + 00) %E j.w., ale wykładnik oddzielony litera E %G liczba zmiennoprzecinkowa w formacie %f lub %E %11.3e liczba zmiennoprzecinkowa w formacie %e z trzema cyframi dziesiętnymi w polu o szerokości 11 znaków %.3e liczba zmiennoprzecinkowa w formacie %e z trzema cyframi dziesiętnymi w polu o najmniejszej możliwej szerokości %5.1f liczba zmiennoprzecinkowa zapisana w notacji dziesiętnej z jedną cyfrą dziesiętną w polu o szerokości 5 znaków %.3f j.w., ale z trzema liczbami dziesiętnymi w polu o możliwie najmniejszej szerokości %s ciąg znaków %-20s ciąg znaków wyrównany do lewej w polu o szerokości 20 znaków Nowszy sposób formatowania łańcuchów znaków Powyższy sposób formatowania łańcuchów znaków jest bardzo popularny, ponieważ przypomina on styl używany przez funkcję sprintfw języku C. Jednak uważany jest on za nieco przestarzały i obecnie zaleca się stosowanie metody formatobiektu str. W porównaniu ze starą składnią, tutaj pojawiają się nawiasy klamrowe i zamiast znaku %używany jest znak :. Np. '%03.2f'zostanie zastąpione przez '{:03.2f}'. Nowy sposób formatowania oferuje jednak szereg nowych możliwości: In [82]: print '{0}, {1}, {2}'.format('a', 'b', 'c') #wstawianie argumentów n a podstawie ich pozycji print '{}, {}, {}'.format('a', 'b', 'c') print '{2}, {1}, {0}'.format('a', 'b', 'c') a, b, c a, b, c c, b, a In [84]: print '{2},{1},{0}'.format(*'abc') ożna rozpakować print '{0}{1}{0}'.format('abra', 'cad') powtarzać # sekwencję w argumencie m # indeksy argumentów można c,b,a abracadabra
In [87]: #wartości wstawiane po kluczach print 'Współrzędne: {latitude}, {longitude}'.format(latitude='37.24 N', longitude='-115.81w') coord = {'latitude': '37.24N', 'longitude': '-115.81W'} print 'Współrzędne: {latitude}, {longitude}'.format(**coord) Współrzędne: 37.24N, -115.81W Współrzędne: 37.24N, -115.81W In [92]: #justowanie tekstu print '{:<30}'.format('left aligned') print '{:>30}'.format('right aligned') print '{:^30}'.format('centered') #jak wyżej, tylko z '*' jako znakiem wypełniającym print '{:*^30}'.format('centered') left aligned right aligned centered ***********centered*********** In [89]: print '{:+f}; {:+f}'.format(3.14, -3.14) #znak pojawia się zawsze print '{: f}; {: f}'.format(3.14, -3.14) #spacja w przypadku liczb dodatnich print '{:-f}; {:-f}'.format(3.14, -3.14) #pokazuje tylko minus +3.140000; -3.140000 3.140000; -3.140000 3.140000; -3.140000 In [90]: # możliwa jest konwersja w 'locie' print "int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}".format(42) int: 42; hex: 2a; oct: 52; bin: 101010 In [2]: '{:,}'.format(1234567890) #przecinek jako separator między tysiącami Out[2]: '1,234,567,890' Więcej na temat metody format pod adresem https://docs.python.org/2/library/string.html#formatstrings (https://docs.python.org/2/library/string.html#formatstrings) Marynowanie danych czyli moduł cpickle napisy łatwo zapisuje się do i odczytuje z pliku liczby wymagają trochę pracy przy konwersji z i na łańcuch znaków jeszcze trudniej jest zapisać i odczytać złożone typy danych moduł cpicklepotrafi przekształcić prawie każdy złożony typ danych w napis ("marynowanie") taki napis można zapisać do pliku, przesłać przez sieć itp. można go również "odmarynować", czyli wydobyć z niego oryginalny obiekt standardowy sposób na uczynienie obiektów Pythona trwałymi warto dbać o to, aby tworzone typy danych dawały się łatwo "marynować"
In [93]: import cpickle as pickle In [94]: dir(pickle) Out[94]: ['BadPickleGet', 'HIGHEST_PROTOCOL', 'PickleError', 'Pickler', 'PicklingError', 'UnpickleableError', 'Unpickler', 'UnpicklingError', ' builtins ', ' doc ', ' name ', ' package ', ' version ', 'compatible_formats', 'dump', 'dumps', 'format_version', 'load', 'loads'] In [95]: help(pickle.dump) Help on built-in function dump in module cpickle: dump(...) dump(obj, file, protocol=0) -- Write an object in pickle format to the given file. See the Pickler docstring for the meaning of optional argument p roto. Przygotujmy jakieś dane do zapisania: In [96]: tel = {'Yoda' : 1111, 'Chewie' : 2222, 'Luke' : 3333} lis = range(5) l = 123 Zapisujemy je do pliku: In [97]: of = open("marynata","w") pickle.dump(tel,of) pickle.dump(lis,of) pickle.dump(l,of) of.close() Podglądnijmy zapisany plik:
In [98]:!cat marynata (dp1 S'Chewie' p2 I2222 ss'yoda' p3 I1111 ss'luke' p4 I3333 s.(lp1 I0 ai1 ai2 ai3 ai4 a.i123. Dane z pliku ładujemy w kolejności zapisania: In [99]: f = open("marynata","r") telefony = pickle.load(f) zakres = pickle.load(f) liczba = pickle.load(f) f.close() In [100]: print telefony print zakres print liczba {'Chewie': 2222, 'Yoda': 1111, 'Luke': 3333} [0, 1, 2, 3, 4] 123 Manipulowanie plikami i katalogami Funkcje potrzebne do operacji na plikach znajdziemy w module os: większość funkcji zaimplementowana w modułach specyficznych dla danej platformy (np. posix, nt) podczas ładowania oswybierany jest odpowiedni z nich operacje na plikach (zmiana nazwy i atrybutów, usuwanie) operacje na katalogach operacje na procesach In [1]: import os
Usuwanie plików Tworzymy najpierw jakiś plik do usunięcia: In [2]: f = open("testos.txt","w") f.write("jakiś test") f.close() Usunięcie pliku jest proste: In [3]: os.remove("testos.txt") Powtórzenie usunięcie wygeneruje znany już wyjątek: In [4]: os.remove("testos.txt") -------------------------------------------------------------------- ------- OSError Traceback (most recent cal l last) <ipython-input-4-a342e4040f74> in <module>() ----> 1 os.remove("testos.txt") OSError: [Errno 2] No such file or directory: 'testos.txt' Podczas pracy interaktywnej w interpreterze Pythona taki błąd nie stanowi oczywiście wielkiego problemu, ponieważ programista może na niego natychmiast zareagować. Jeśli jednak wyjątek pojawi się podczas wykonywania dłuższego skryptu, przerwie on jego działanie. Jest to oczywiście sytuacja niepożądana, dlatego do dobrych praktyk należy wyłapywanie wyjątków. Zagadnieniu temu poświęcony będzie część jednego z kolejnych wykładów. Natomiast teraz powinien wystarczyć przykład, jak radzić sobie w tym konkretnym przypadku: In [5]: try: os.remove("testos.txt") except os.error: print "Operacja nie powiodła się" Operacja nie powiodła się Zmiana nazwy pliku Stwórzmy najpierw jakis plik roboczy: In [6]: name1 = "plik.txt" fi = open(name1,'w') fi.write("to jest plik roboczy o nazwie %s."%name1) fi.close()
Sprawdźmy, czy plik taki rzeczywiście powstał, i co zawiera: In [7]: %%bash ls -l plik.txt -rw-rw-r-- 1 szwabin szwabin 39 lis 10 18:10 plik.txt In [8]: %%bash cat plik.txt To jest plik roboczy o nazwie plik.txt. Dygresja Polecenie %%bash to przykład polecenia magicznego IPythona. W tym konkretnym przypadku pozwala on korzystać z funkcjonalności powłoki systemowej bez wychodzenia z notatnika. Zmiany nazwy pliku dokonamy przy pomocy polecenia os.rename: In [9]: name2 = "file.txt" os.rename(name1,name2) Sprawdźmy wynik działania tego polecenia: In [10]: fi = open(name1) -------------------------------------------------------------------- ------- IOError Traceback (most recent cal l last) <ipython-input-10-296f70111754> in <module>() ----> 1 fi = open(name1) IOError: [Errno 2] No such file or directory: 'plik.txt' In [12]: fi = open(name2) fi.read() Out[12]: 'To jest plik roboczy o nazwie plik.txt.' In [13]: fi.close() Informacje o pliku In [14]: os.stat(name2) Out[14]: posix.stat_result(st_mode=33204, st_ino=10092891, st_dev=2051, st_nl ink=1, st_uid=1000, st_gid=1000, st_size=39, st_atime=1447175813, s t_mtime=1447175435, st_ctime=1447175741) Jak widać, wynik działania polecenia os.statnie jest szczególnie czytelny. Możemy jednak to zmienić:
In [16]: import time def dump(st): mode,ino,dev,nlink,uid,gid,size,atime,mtime,ctime = st print "- rozmiar:", size, "bajtów" print "- właściciel:", uid, gid print "- utworzony:", time.ctime(ctime) print "- ostatni dostęp:", time.ctime(atime) print "- ostatnia modyfikacja:", time.ctime(mtime) st = os.stat(name2) dump(st) - rozmiar: 39 bajtów - właściciel: 1000 1000 - utworzony: Tue Nov 10 18:15:41 2015 - ostatni dostęp: Tue Nov 10 18:16:53 2015 - ostatnia modyfikacja: Tue Nov 10 18:10:35 2015 Dygresja! Gdybyśmy planowali stworzyć własny moduł z przydatnymi funkcjami do pracy na plikach, funkcja dumpbyłaby bardzo dobrą kandydatką na element tego modułu. Podstawowe operacje na katalogach Sprawdźmy bieżący katalog: In [17]: os.getcwd() Out[17]: '/home/szwabin/dropbox/zaj\xc4\x99cia/pythonintro/6_moduly_i_pliki' Jego zawartość wyświetlimy przy pomocy funkcji os.listdir: In [18]: print os.listdir(os.getcwd()) ['5-intro.pdf.gz', 'calc.py', 'modutils.py', 'Chapter8_Modules.ipyn b', '3-intro_utf.pdf', '6_moduly_i_pliki.ipynb', 'box.jpg', 'SciPyth on5.ipynb', '.ipynb_checkpoints', 'SciPython4.ipynb', '4-intro.pdf', 'file.txt', 'bpyfd_diags6.png', 'SciPython3.ipynb'] Można również tak:
In [19]: for item in os.listdir(os.getcwd()): print item 5-intro.pdf.gz calc.py modutils.py Chapter8_Modules.ipynb 3-intro_utf.pdf 6_moduly_i_pliki.ipynb box.jpg SciPython5.ipynb.ipynb_checkpoints SciPython4.ipynb 4-intro.pdf file.txt bpyfd_diags6.png SciPython3.ipynb Przy podawaniu ścieżki do katalogów można korzystać z typowych skrótów: In [20]: print "Zawartość katalogu bieżącego:\n" print os.listdir('.') print "\n\nzawartość katalogu nadrzędnego:\n" print os.listdir('..') Zawartość katalogu bieżącego: ['5-intro.pdf.gz', 'calc.py', 'modutils.py', 'Chapter8_Modules.ipyn b', '3-intro_utf.pdf', '6_moduly_i_pliki.ipynb', 'box.jpg', 'SciPyth on5.ipynb', '.ipynb_checkpoints', 'SciPython4.ipynb', '4-intro.pdf', 'file.txt', 'bpyfd_diags6.png', 'SciPython3.ipynb'] Zawartość katalogu nadrzędnego: ['Przyk\xc5\x82ady', '1_Intro', 'ricardoduarte-python-for-developer s-88df06e', 'thinkpython.pdf', '5_Funkcje', '2_Pierwsze_kroki', '3_Z mienne_wyrazenia_instrukcje', '6_Moduly_i_pliki', '4_Zlozone_typy_da nych'] Przejście do innego katalogu roboczego: In [22]: os.chdir("/home/szwabin/mypython") os.getcwd() Out[22]: '/home/szwabin/mypython' Tworzenie katalogów: In [23]: os.listdir('.') Out[23]: []
In [24]: os.mkdir('test') os.listdir('.') Out[24]: ['Test'] Jeśli katalog już istnieje: In [25]: os.mkdir('test') -------------------------------------------------------------------- ------- OSError Traceback (most recent cal l last) <ipython-input-25-f2fde2339d88> in <module>() ----> 1 os.mkdir('test') OSError: [Errno 17] File exists: 'Test' Możemy tworzyć kilka poziomów na raz: In [26]: os.makedirs('test2/poziom1/poziom2') os.listdir('.') Out[26]: ['Test', 'Test2'] In [27]: print os.listdir('test2') print os.listdir('test2/poziom1') ['Poziom1'] ['Poziom2'] Pojedynczy katalog usuniemy w następujący sposób: In [28]: os.rmdir("test") os.listdir('.') Out[28]: ['Test2'] Możliwe jest również usunięcie całej ścieżki na raz: In [29]: os.removedirs('test2/poziom1/poziom2') os.listdir('.') Out[29]: [] Uwaga! Usuwany katalog musi być pusty: In [30]: os.mkdir('test') f = open('test/plik.txt','w') f.write('bla bla bla') f.close()
In [31]: os.rmdir('test') -------------------------------------------------------------------- ------- OSError Traceback (most recent cal l last) <ipython-input-31-9d11237dd07e> in <module>() ----> 1 os.rmdir('test') OSError: [Errno 39] Directory not empty: 'Test' In [32]: os.remove('test/plik.txt') os.rmdir('test') In [33]: os.listdir('.') Out[33]: [] Ścieżki dostępu do plików In [34]: plik = "/home/szwabin/books/programming.collective.intelligence.pdf" Pełną ścieżkę do pliku możemy rozbić na nazwę pliku i katalog, w którym plik się znajduje: In [35]: os.path.split(plik) Out[35]: ('/home/szwabin/books', 'Programming.Collective.Intelligence.pdf') Nazwę katalogu i pliku możemy też odczytać specjalnymi funkcjami: In [36]: kat = os.path.dirname(plik) print kat pl = os.path.basename(plik) print pl #nazwa katalogu #nazwa pliku /home/szwabin/books Programming.Collective.Intelligence.pdf Dysponując nazwą katalogu i pliku, możemy je oczywiście połączyć w jedną ścieżkę dostępu: In [37]: os.path.join(kat,pl) Out[37]: '/home/szwabin/books/programming.collective.intelligence.pdf' Przeszukiwanie drzewa katalogów
In [47]: for root, dirs, files in os.walk("/home/szwabin/dropbox/zajęcia/pyth onintro/3_zmienne_wyrazenia_instrukcje/", topdown=false): print root print dirs print files print '-'*20 kcje/.ipynb_checkpoints [] ['3_Zmienne_wyrazenia_instrukcje-checkpoint.ipynb', 'Untitled0-check point.ipynb'] -------------------- kcje/preview/3_zmienne_wyrazenia_instrukcje_files [] ['require.min.js', 'MathJax.js', 'jquery.min.js'] -------------------- kcje/preview ['3_Zmienne_wyrazenia_instrukcje_files'] ['state_diagram.png', '3_Zmienne_wyrazenia_instrukcje.html', '3_Zmie nne_wyrazenia_instrukcje.html~'] -------------------- kcje/ ['.ipynb_checkpoints', 'Preview'] ['state_diagram.svg', 'Untitled0.ipynb', 'state_diagram.png', '3_Zmi enne_wyrazenia_instrukcje.ipynb', '3_zmienne_wyrazenia_instrukcje.pd f'] -------------------- In [48]: for root, dirs, files in os.walk("/home/szwabin/dropbox/zajęcia/pyth onintro/3_zmienne_wyrazenia_instrukcje/", topdown=false): for name in files: print os.path.join(root, name) for name in dirs: print os.path.join(root, name)
kcje/.ipynb_checkpoints/3_zmienne_wyrazenia_instrukcje-checkpoint.ip ynb kcje/.ipynb_checkpoints/untitled0-checkpoint.ipynb kcje/preview/3_zmienne_wyrazenia_instrukcje_files/require.min.js kcje/preview/3_zmienne_wyrazenia_instrukcje_files/mathjax.js kcje/preview/3_zmienne_wyrazenia_instrukcje_files/jquery.min.js kcje/preview/state_diagram.png kcje/preview/3_zmienne_wyrazenia_instrukcje.html kcje/preview/3_zmienne_wyrazenia_instrukcje.html~ kcje/preview/3_zmienne_wyrazenia_instrukcje_files kcje/state_diagram.svg kcje/untitled0.ipynb kcje/state_diagram.png kcje/3_zmienne_wyrazenia_instrukcje.ipynb kcje/3_zmienne_wyrazenia_instrukcje.pdf kcje/.ipynb_checkpoints kcje/preview Jeszcze o przeszukiwaniu katalogów Bardzo przydatnym modułem jest znajdujący się w bibliotece standardowej glob. Pozwala on na używanie dzikich kart w nazwach plikow na zasadach podobnych do powłoki uniksowej: *zastępuje zero lub więcej znaków?zastępuje dokładnie jeden znak nawiasy kwadratowe definiują zakresy, np. [1-9]oznacza dowolną cyfrę między 1 a 9 In [51]: import glob os.chdir('/home/szwabin/dropbox/zaj\xc4\x99cia/pythonintro/6_modul y_i_pliki') Wszystkie pliki o rozszerzeniu ipynb:
In [52]: print glob.glob('*.ipynb') ['Chapter8_Modules.ipynb', '6_moduly_i_pliki.ipynb', 'SciPython5.ipy nb', 'SciPython4.ipynb', 'SciPython3.ipynb'] Wszystkie pliki w aktualnym katalogu: In [53]: print glob.glob('*.*') ['5-intro.pdf.gz', 'calc.py', 'modutils.py', 'Chapter8_Modules.ipyn b', '3-intro_utf.pdf', '6_moduly_i_pliki.ipynb', 'box.jpg', 'SciPyth on5.ipynb', 'SciPython4.ipynb', '4-intro.pdf', 'file.txt', 'bpyfd_di ags6.png', 'SciPython3.ipynb'] Pliki o rozszerzeniu składającym się z 3 liter: In [54]: print glob.glob("*.???") ['3-intro_utf.pdf', 'box.jpg', '4-intro.pdf', 'file.txt', 'bpyfd_dia gs6.png'] Pliki o rozszerzeniu składającym się z 3 liter, przy czym pierwszą literą rozszerzenia jest plub t: In [56]: print glob.glob("*.[pt]??") ['3-intro_utf.pdf', '4-intro.pdf', 'file.txt', 'bpyfd_diags6.png']