7. Wejście i wyjście

Wyjście programu można zaprezentować na różne sposoby; można wypisać dane w formie czytelnej dla człowieka lub zapisać do pliku do wykorzystania w przyszłości. Ten rozdział omówi niektóre z możliwości.

7.1. Wymyślniejsze formatowanie wyjścia

Do tej pory natknęliśmy się na dwa sposoby wypisywania wartości: wyrażenia i funkcję print(). (Trzecim sposobem jest użycie metody write() obiektu pliku; do pliku standardowego wyjścia można odwołać się używając sys.stdout. Więcej informacji na ten temat znajdziesz w dokumentacji biblioteki standardowej.)

Często będziesz chciał(a) mieć więcej kontroli nad formatowaniem swojego wyjścia niż proste wypisywanie wartości rozdzielonych spacją. Jest kilka sposobów na formatowanie wyjścia.

  • Aby użyć literału ciągu znaków, rozpocznij ciąg znaków znakiem f lub F przed otwierającym znakiem cudzysłowu. Wewnątrz ciągu znaków, możesz wpisać wyrażenia Pythona pomiędzy znakami { i }, które mogą odnosić się do zmiennych lub wartości.

    >>> year = 2016
    >>> event = 'Referendum'
    >>> f'Results of the {year} {event}'
    'Results of the 2016 Referendum'
    
  • Metoda str.format() ciągów znaków wymaga większego ręcznego wysiłku. Nadal będziesz używał(a) { i } do zaznaczenia gdzie zmienna zostanie podstawiona i możesz podać dokładne dyrektywy formatowania, ale będziesz musiał(a) podać również informacje do sformatowania.

    >>> yes_votes = 42_572_654
    >>> no_votes = 43_132_495
    >>> percentage = yes_votes / (yes_votes + no_votes)
    >>> '{:-9} YES votes  {:2.2%}'.format(yes_votes, percentage)
    ' 42572654 YES votes  49.67%'
    
  • Wreszcie, możesz wykonać całą obsługę ciągów znaków samodzielnie, używając operacji krojenia (slicing) i konkatenacji, aby stworzyć dowolny układ, jaki możesz sobie wyobrazić. Typ ciągu znaków posiada kilka metod, które wykonują przydatne operacje do wypełniania ciągów znaków do danej szerokości kolumny.

Gdy nie potrzebujesz wymyślnego wyjścia, tylko chcesz szybko wyświetlić zmienne do debugowania, możesz zkonwertować dowolną wartość do ciągu znaków przy pomocy funkcji repr() lub str().

Funkcja str() ma na celu zwrócenie reprezentacji wartości, które są dość czytelne dla człowieka, podczas gdy repr() ma na celu wygenerowanie reprezentacji, które mogą być odczytane przez interpreter (lub wymuszą SyntaxError, jeśli nie ma równoważnej składni). Dla obiektów, które nie mają określonej reprezentacji do użycia przez człowieka, str() zwróci taką samą wartość jak repr(). Wiele wartości, takich jak liczby lub struktury takie jak listy i słowniki, ma taką samą reprezentację przy użyciu obu funkcji. Ciągi znaków, w szczególności, mają dwie odrębne reprezentacje.

Trochę przykładów:

>>> s = 'Hello, world.'
>>> str(s)
'Hello, world.'
>>> repr(s)
"'Hello, world.'"
>>> str(1/7)
'0.14285714285714285'
>>> x = 10 * 3.25
>>> y = 200 * 200
>>> s = 'The value of x is ' + repr(x) + ', and y is ' + repr(y) + '...'
>>> print(s)
The value of x is 32.5, and y is 40000...
>>> # The repr() of a string adds string quotes and backslashes:
... hello = 'hello, world\n'
>>> hellos = repr(hello)
>>> print(hellos)
'hello, world\n'
>>> # The argument to repr() may be any Python object:
... repr((x, y, ('spam', 'eggs')))
"(32.5, 40000, ('spam', 'eggs'))"

Moduł string zawiera klasę Template, która oferuje jeszcze jeden sposób zastępowania wartości ciągami znaków, przy użyciu placeholderów takich jak $x i zastępując je wartościami ze słownika, ale daje mniejszą kontrolę nad formatowaniem.

7.1.1. f-stringi

Formatowane literały ciągów znaków (nazywane też krótko f-stringami) pozwalają zawrzeć wartość pythonowych wyrażeń wewnątrz ciągu znaków przez dodanie prefiksu f lub F i zapisanie wyrażenia jako {expression}.

Po wyrażeniu może nastąpić opcjonalny specyfikator formatu. Umożliwia on większą kontrolę nad sposobem formatowania wartości. Poniższy przykład zaokrągla pi do trzech miejsc po przecinku:

>>> import math
>>> print(f'The value of pi is approximately {math.pi:.3f}.')
The value of pi is approximately 3.142.

Przekazanie liczby całkowitej po znaku ':' spowoduje, że pole będzie miało minimalną liczbę znaków szerokości. Jest to przydatne przy tworzeniu linii kolumn.

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 7678}
>>> for name, phone in table.items():
...     print(f'{name:10} ==> {phone:10d}')
...
Sjoerd     ==>       4127
Jack       ==>       4098
Dcab       ==>       7678

Inne modyfikatory mogą być użyte do konwersji wartości przed jej sformatowaniem. '!a' stosuje ascii(), '!s' stosuje str(), a '!r' stosuje repr():

>>> animals = 'eels'
>>> print(f'My hovercraft is full of {animals}.')
My hovercraft is full of eels.
>>> print(f'My hovercraft is full of {animals!r}.')
My hovercraft is full of 'eels'.

Specyfikator = może być użyty do rozwinięcia wyrażenia do tekstu wyrażenia, znaku równości, a następnie reprezentacji obliczonego wyrażenia:

>>> bugs = 'roaches'
>>> count = 13
>>> area = 'living room'
>>> print(f'Debugging {bugs=} {count=} {area=}')
Debugging bugs='roaches' count=13 area='living room'

Zobacz wyrażenia samodokumentujące po więcej informacji na temat specyfikatora =. Informacje o specyfikacjach formatu znajdziesz w przewodniku referencyjnym dla mini-języka specyfikacji formatu.

7.1.2. Metoda format() ciągu znaków

Podstawowe użycie metody str.format() wygląda tak:

>>> print('We are the {} who say "{}!"'.format('knights', 'Ni'))
We are the knights who say "Ni!"

Nawiasy i znaki wewnątrz nich (zwane polami formatu) są zastępowane obiektami przekazywanymi do metody str.format(). Liczba w nawiasach może być użyta do odwołania się do pozycji obiektu przekazanego do metody str.format().

>>> print('{0} and {1}'.format('spam', 'eggs'))
spam and eggs
>>> print('{1} and {0}'.format('spam', 'eggs'))
eggs and spam

Jeśli argumenty nazwane są używane w metodzie str.format(), ich wartości są przywoływane przy użyciu nazwy argumentu.

>>> print('This {food} is {adjective}.'.format(
...       food='spam', adjective='absolutely horrible'))
This spam is absolutely horrible.

Argumenty pozycyjne i nazwane mogą być dowolnie łączone:

>>> print('The story of {0}, {1}, and {other}.'.format('Bill', 'Manfred',
...                                                    other='Georg'))
The story of Bill, Manfred, and Georg.

Jeśli masz naprawdę długi ciąg znaków formatu, którego nie chcesz dzielić, byłoby miło, gdybyś mógł odwoływać się do zmiennych, które mają być sformatowane według nazwy zamiast według pozycji. Można to zrobić, po prostu przekazując dict i używając nawiasów kwadratowych '[]', aby uzyskać dostęp do kluczy.

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
>>> print('Jack: {0[Jack]:d}; Sjoerd: {0[Sjoerd]:d}; '
...       'Dcab: {0[Dcab]:d}'.format(table))
Jack: 4098; Sjoerd: 4127; Dcab: 8637678

Można to również zrobić, przekazując dict table jako argumenty nazwane przy użyciu notacji **.

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
>>> print('Jack: {Jack:d}; Sjoerd: {Sjoerd:d}; Dcab: {Dcab:d}'.format(**table))
Jack: 4098; Sjoerd: 4127; Dcab: 8637678

Jest to szczególnie przydatne w połączeniu z wbudowaną funkcją vars(), która zwraca słownik zawierający wszystkie zmienne lokalne.

Jako przykład, poniższe wiersze tworzą uporządkowany zestaw kolumn zawierających liczby całkowite oraz ich kwadraty i sześciany:

>>> for x in range(1, 11):
...     print('{0:2d} {1:3d} {2:4d}'.format(x, x*x, x*x*x))
...
 1   1    1
 2   4    8
 3   9   27
 4  16   64
 5  25  125
 6  36  216
 7  49  343
 8  64  512
 9  81  729
10 100 1000

Aby uzyskać pełny przegląd formatowania ciągów znaków za pomocą funkcji str.format(), zobacz Format String Syntax.

7.1.3. Ręczne formatowanie ciągów znaków

Oto ta sama tabela kwadratów i sześcianów, sformatowana ręcznie:

>>> for x in range(1, 11):
...     print(repr(x).rjust(2), repr(x*x).rjust(3), end=' ')
...     # Note use of 'end' on previous line
...     print(repr(x*x*x).rjust(4))
...
 1   1    1
 2   4    8
 3   9   27
 4  16   64
 5  25  125
 6  36  216
 7  49  343
 8  64  512
 9  81  729
10 100 1000

(Zauważ, że jedna spacja między każdą kolumną została dodana przez sposób działania funkcji print(): zawsze dodaje ona spacje między swoimi argumentami.)

Metoda str.rjust() obiektów typu string wyrównuje ciąg znaków do prawej strony w polu o zadanej szerokości poprzez wypełnienie go spacjami po lewej stronie. Istnieją podobne metody str.ljust() i str.center(). Metody te niczego nie wypisują, a jedynie zwracają nowy ciąg znaków. Jeśli wejściowy ciąg jest zbyt długi, nie obcinają go, ale zwracają go bez zmian; spowoduje to bałagan w układzie kolumn, ale zwykle jest to lepsze niż alternatywa, która polegałaby na kłamaniu na temat wartości. (Jeśli naprawdę chcesz przycinania, zawsze możesz dodać operację wycinania, jak w x.ljust(n)[:n].)

Istnieje jeszcze jedna metoda, str.zfill(), która wypełnia ciąg liczbowy po lewej stronie zerami. Rozumie ona znaki plus i minus:

>>> '12'.zfill(5)
'00012'
>>> '-3.14'.zfill(7)
'-003.14'
>>> '3.14159265359'.zfill(5)
'3.14159265359'

7.1.4. Stare formatowanie ciągów znaków

Operator % (modulo) może być również używany do formatowania ciągów znaków. Dla 'string' % values, wystąpienia % w ciągu znaków string są zastępowane przez zero lub więcej elementów values. Operacja ta jest powszechnie znana jako interpolacja ciągów znaków. Na przykład:

>>> import math
>>> print('The value of pi is approximately %5.3f.' % math.pi)
The value of pi is approximately 3.142.

Więcej informacji można znaleźć w sekcji printf-style String Formatting.

7.2. Odczytywanie i zapisywanie plików

open() zwraca obiekt pliku i jest najczęściej używany z dwoma argumentami pozycyjnymi i jednym argumentem nazwanym: open(filename, mode, encoding=None)

>>> f = open('workfile', 'w', encoding="utf-8")

Pierwszy argument to ciąg znaków zawierający nazwę pliku. Drugi argument to kolejny ciąg znaków zawierający kilka znaków opisujących sposób, w jaki plik będzie używany. Tryb może być 'r' gdy plik będzie tylko do odczytu, 'w' tylko do zapisu (istniejący plik o tej samej nazwie zostanie usunięty), a 'a' otwiera plik do dołączania; wszelkie dane zapisane do pliku są automatycznie dodawane na koniec.``»r+»`` otwiera plik zarówno do odczytu, jak i zapisu. Argument trybu jest opcjonalny; jeśli zostanie pominięty, zakłada się, że domyślnie został wybrany 'r'.

Zwykle pliki są otwierane w trybie tekstowym text mode, co oznacza, że czytasz i zapisujesz ciągi znaków do i z pliku, które są kodowane w określonym kodowaniu. Jeśli kodowanie nie jest określone, domyślne jest zależne od platformy (patrz open()). Ponieważ UTF-8 jest współczesnym standardem, zaleca się stosowanie encoding="utf-8", chyba że wiesz, że musisz użyć innego kodowania. Dodanie 'b' do trybu otwiera plik w trybie binarnym binary mode. Dane w trybie binarnym są odczytywane i zapisywane jako obiekty bytes. Nie możesz określić kodowania podczas otwierania pliku w trybie binarnym.

W trybie tekstowym domyślnym podczas odczytywania następuje konwersja końcówek linii specyficznych dla platformy (\n na systemie Unix, \r\n na systemie Windows) na pojedynczy znak \n. Podczas zapisu w trybie tekstowym domyślnym jest konwersja wystąpień \n z powrotem na końcówki linii specyficzne dla platformy. Ta niewidoczna modyfikacja danych pliku jest odpowiednia dla plików tekstowych, ale uszkodzi dane binarne, takie jak te w plikach JPEG lub EXE. Bądź bardzo ostrożny, używając trybu binarnego podczas odczytywania i zapisywania takich plików.

Używanie with podczas pracy z obiektami plików należy do dobrych praktyk. Zaletą tego podejścia jest to, że plik jest prawidłowo zamykany po zakończeniu jego bloku, nawet jeśli w pewnym momencie zostanie zgłoszony wyjątek. Użycie with jest również znacznie krótsze niż pisanie równoważnych bloków try - finally:

>>> with open('workfile', encoding="utf-8") as f:
...     read_data = f.read()

>>> # We can check that the file has been automatically closed.
>>> f.closed
True

Jeśli nie używasz with to powinieneś wywołać f.close() w celu zamknięcia pliku i natychmiastowego zwolnienia wszystkich zasobów systemowych wykorzystywanych przez ten plik.

Ostrzeżenie

Wywołanie f.write() bez użycia with lub f.close() może spowodować, że argumenty f.write() nie zostaną w pełni zapisane na dysku, nawet jeśli program zakończy się pomyślnie.

Po zamknięciu obiektu pliku, zarówno przez instrukcję with, jak i f.close(), wszystkie próby użycia obiektu pliku automatycznie się nie powiodą.

>>> f.close()
>>> f.read()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: I/O operation on closed file.

7.2.1. Metody obiektów plików

Reszta przykładów w tej sekcji zakłada, że obiekt pliku o nazwie f został już utworzony.

Aby odczytać zawartość pliku, należy wywołać polecenie f.read(size), które odczytuje pewną ilość danych i zwraca je jako ciąg znaków (w trybie tekstowym) lub obiekt bajtowy (w trybie binarnym). size jest opcjonalnym argumentem numerycznym. Gdy size jest pominięty lub ujemny, zostanie odczytana i zwrócona cała zawartość pliku; to twój problem, jeśli plik jest dwa razy większy niż pamięć twojego komputera. W przeciwnym razie odczytane i zwrócone zostanie co najwyżej size znaków (w trybie tekstowym) lub size bajtów (w trybie binarnym). Jeśli został osiągnięty koniec pliku, f.read() zwróci pusty ciąg znaków ('').

>>> f.read()
'This is the entire file.\n'
>>> f.read()
''

f.readline() odczytuje pojedynczą linię z pliku; znak nowej linii (\n) jest pozostawiony na końcu ciągu znaków i jest pomijany tylko w ostatniej linii pliku, jeśli plik nie kończy się nową linią. To sprawia, że wartość zwracana jest jednoznaczna; jeśli f.readline() zwróci pusty ciąg znaków, koniec pliku został osiągnięty, podczas gdy pusta linia jest reprezentowana przez '\n', ciąg znaków zawierający tylko pojedynczą nową linię.

>>> f.readline()
'This is the first line of the file.\n'
>>> f.readline()
'Second line of the file\n'
>>> f.readline()
''

Aby odczytać wiersze z pliku, można wykonać pętlę na obiekcie pliku. Jest to wydajne pamięciowo, szybkie i prowadzi do prostego kodu:

>>> for line in f:
...     print(line, end='')
...
This is the first line of the file.
Second line of the file

Jeśli chcesz wczytać wszystkie wiersze pliku w listę, możesz również użyć list(f) lub f.readlines().

f.write(string) zapisuje zawartość string do pliku, zwracając liczbę zapisanych znaków.

>>> f.write('This is a test\n')
15

Inne typy obiektów muszą zostać przekonwertowane – albo na ciąg znaków (w trybie tekstowym), albo na obiekt bajtowy (w trybie binarnym) – przed ich zapisaniem:

>>> value = ('the answer', 42)
>>> s = str(value)  # convert the tuple to string
>>> f.write(s)
18

f.tell() zwraca liczbę całkowitą podającą aktualną pozycję obiektu pliku w pliku reprezentowaną jako liczba bajtów od początku pliku w trybie binarnym i nieprzejrzystą liczbę w trybie tekstowym.

Aby zmienić pozycję obiektu pliku, należy użyć f.seek(offset, whence). Pozycja jest obliczana przez dodanie offset do punktu odniesienia; punkt odniesienia jest wybierany przez argument whence. Wartość whence równa 0 mierzy od początku pliku, 1 używa bieżącej pozycji pliku, a 2 używa końca pliku jako punktu odniesienia. whence może zostać pominięty i domyślnie przyjmuje wartość 0, używając początku pliku jako punktu odniesienia.

>>> f = open('workfile', 'rb+')
>>> f.write(b'0123456789abcdef')
16
>>> f.seek(5)      # Go to the 6th byte in the file
5
>>> f.read(1)
b'5'
>>> f.seek(-3, 2)  # Go to the 3rd byte before the end
13
>>> f.read(1)
b'd'

W plikach tekstowych (otwieranych bez b w ciągu znaków trybu) dozwolone jest tylko wyszukiwanie względem początku pliku (wyjątkiem jest wyszukiwanie do samego końca pliku z seek(0, 2)), a jedynymi poprawnymi wartościami offset są te zwrócone z f.tell() lub zero. Każda inna wartość offset powoduje niezdefiniowane zachowanie.

Obiekty plikowe mają kilka dodatkowych metod, takich jak isatty() i truncate(), które są rzadziej używane; zapoznaj się z Library Reference, aby uzyskać pełny przewodnik po obiektach plikowych.

7.2.2. Zapisywanie struktur danych przy użyciu modułu json

Ciągi znaków można łatwo zapisywać i odczytywać z pliku. Liczby wymagają nieco więcej wysiłku, ponieważ metoda read() zwraca tylko ciągi znaków, które będą musiały zostać przekazane do funkcji takiej jak int(), która pobiera ciąg znaków taki jak '123' i zwraca jego wartość liczbową 123. W przypadku zapisywania bardziej złożonych typów danych, takich jak zagnieżdżone listy i słowniki, ręczne parsowanie i serializowanie staje się skomplikowane.

Zamiast zmuszać użytkowników do ciągłego pisania i debugowania kodu w celu zapisania skomplikowanych typów danych do plików, Python pozwala na użycie popularnego formatu wymiany danych zwanego JSON (JavaScript Object Notation). Standardowy moduł o nazwie json może pobierać hierarchie danych Pythona i konwertować je na reprezentacje ciągów znaków; proces ten nazywany jest serializacją. Rekonstrukcja danych z reprezentacji łańcuchowej nazywana jest deserializacją. Pomiędzy serializacją i deserializacją, ciąg znaków reprezentujący obiekt może być przechowywany w pliku lub bazie danych, lub wysłany przez połączenie sieciowe do odległej maszyny.

Informacja

Format JSON jest powszechnie używany przez nowoczesne aplikacje w celu umożliwienia wymiany danych. Wielu programistów jest już z nim zaznajomionych, co czyni go dobrym wyborem dla interoperacyjności.

Jeśli masz obiekt x, możesz wyświetlić jego reprezentację JSON za pomocą prostej linii kodu:

>>> import json
>>> x = [1, 'simple', 'list']
>>> json.dumps(x)
'[1, "simple", "list"]'

Inny wariant funkcji dumps(), zwany dump(), po prostu serializuje obiekt do pliku tekstowego. Jeśli więc f jest obiektem pliku tekstowego otwartym do zapisu, możemy zrobić tak:

json.dump(x, f)

Aby ponownie zdekodować obiekt, jeśli f jest obiektem pliku binarnego lub pliku tekstowego, który został otwarty do odczytu:

x = json.load(f)

Informacja

Pliki JSON muszą być zakodowane w UTF-8. Użyj encoding="utf-8" podczas otwierania pliku JSON jako pliku tekstowego zarówno do odczytu jak i zapisu.

Ta prosta technika serializacji może obsługiwać listy i słowniki, ale serializacja dowolnych instancji klas w JSON wymaga nieco dodatkowego wysiłku. Wyjaśnienie tej kwestii znajduje się w dokumentacji modułu json.

Zobacz także

pickle – moduł pickle

W przeciwieństwie do JSON, pickle jest protokołem, który pozwala na serializację dowolnie złożonych obiektów Pythona. Jako taki, jest specyficzny dla Pythona i nie może być używany do komunikacji z aplikacjami napisanymi w innych językach. Jest on również domyślnie niezabezpieczony: deserializacja danych pickle pochodzących z niezaufanego źródła może wykonać dowolny kod, jeśli dane zostały spreparowane przez doświadczonego atakującego.