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'Wyniki {event} {year}'
     'Wyniki referendum 2016'

* 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. W przedstawionym bloku kodu są dwa przykłady, jak
  formatować zmienne.

     >>> yes_votes = 42_572_654
     >>> total_votes = 85_705_149
     >>> percentage = yes_votes / total_votes
     >>> '{:-9} głosów na TAK  {:2.2%}'.format(yes_votes, percentage)
     ' 42572654 głosów na TAK  49.67%'

  Zauważ, że "yes_votes" są wypełnione spacjami i znakiem minus tylko
  dla liczb ujemnych. Przykład wypisuje również "percentage" pomnożony
  przez 100, z 2 miejscami po przecinku i ze znakiem procentu
  (szczegóły: Format Specification Mini-Language).

* 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 = 'Witaj, świecie.'
   >>> str(s)
   'Witaj, świecie.'
   >>> repr(s)
   "'Witaj, świecie.'"
   >>> str(1/7)
   '0.14285714285714285'
   >>> x = 10 * 3.25
   >>> y = 200 * 200
   >>> s = 'Wartość x to ' + repr(x) + ', i y to ' + repr(y) + '...'
   >>> print(s)
   Wartość x to 32.5, i y to 40000...
   >>> # repr() ciągu znaków dodaje cudzysłów i backslashe:
   >>> hello = 'witaj, świecie\n'
   >>> hellos = repr(hello)
   >>> print(hellos)
   'witaj, świecie\n'
   >>> # argumentem repr() może być dowolny obiekt Pythona:
   >>> repr((x, y, ('szynka', 'jajka')))
   "(32.5, 40000, ('szynka', 'jajka'))"

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'Wartość pi wynosi w przybliżeniu {math.pi:.3f}.')
   Wartość pi wynosi w przybliżeniu 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 = 'węgorze'
   >>> print(f'Na moim poduszkowcu są {animals}.')
   Na moim poduszkowcu są węgorze.
   >>> print(f'Na moim poduszkowcu są {animals!r}.')
   Na moim poduszkowcu są 'węgorze'.

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} i {1}'.format('szynka', 'jajka'))
   szynka i jajka
   >>> print('{1} i {0}'.format('szynka', 'jajka'))
   jajka i szynka

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:

   >>> table = {k: str(v) for k, v in vars().items()}
   >>> message = " ".join([f'{k}: ' + '{' + k +'};' for k in table.keys()])
   >>> print(message.format(**table))
   __name__: __main__; __doc__: None; __package__: None; __loader__: ...

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 "format % values" (gdzie *format* jest ciągiem znaków),
specyfikacje konwersji "%" w ciągu znaków *format* 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.
