# 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, ('mielonka', 'jajka')))
    "(32.5, 40000, ('mielonka', '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('Jesteśmy {}, którzy mówią „{}!”'.format('rycerzami', 'Ni'))
    Jesteśmy rycerzami, którzy mówią „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('mielonka', 'jajka'))
    mielonka i jajka
    >>> print('{1} i {0}'.format('mielonka', 'jajka'))
    jajka i mielonka

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

    >>> print('Ta {food} jest {adjective}.'.format(
    ...       food='mielonka', adjective='absolutnie okropna'))
    Ta mielonka jest absolutnie okropna.

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

    >>> print('Historia {0}, {1} i {other}.'.format('Billa', 'Manfreda',
    ...                                                    other='Georga'))
    Historia of Billa, Manfreda i Georga.

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=' ')
    ...     # zwróć uwagę na użycie 'end' w poprzedniej linii
    ...     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('Wartość pi to około %5.3f.' % math.pi)
    Wartość pi to około 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('plikroboczy', '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('plikroboczy', encoding="utf-8") as f:
    ...     read_data = f.read()

    >>> # Możemy sprawdzić, że plik został automatycznie zamknięty.
    >>> 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()
    'To jest cały plik.\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()
    'To jest pierwsza linia pliku.\n'
    >>> f.readline()
    'Druga linia pliku\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='')
    ...
    To jest pierwsza linia pliku.
    Druga linia pliku

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('To jest test\n')
    13

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 = ('odpowiedź', 42)
    >>> s = str(value)  # konwersja krotki na ciąg znaków
    >>> f.write(s)
    17

`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('plikroboczy', 'rb+')
    >>> f.write(b'0123456789abcdef')
    16
    >>> f.seek(5)      # Idź do szóstego bajtu w pliku
    5
    >>> f.read(1)
    b'5'
    >>> f.seek(-3, 2)  # Idź do trzeciego bajtu przed końcem
    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, 'prosta', 'lista']
    >>> json.dumps(x)
    '[1, "prosta", "lista"]'

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.