Powrót do bloga

Rozwiązywanie problemówPrzewodnik

UnicodeEncodeError przy wypisywaniu odpowiedzi LLM w Pythonie: 3 poprawki

print(reply) pada na cyrylicy lub emoji? Zobacz, dlaczego Python zawodzi przy przekierowanym wyjściu lub starszej stronie kodowej, i napraw to trybem UTF-8, reconfigure lub encoding=.

Autor: CheapTokens TeamOpublikowano 6 min czytania

UnicodeEncodeError na print(reply) i poprawka UTF-8, narysowane jako karta błędu
Na tej stronie
  1. Jak wygląda błąd
  2. Sprawdź, jakie kodowanie faktycznie masz
  3. Poprawka 1: włącz tryb UTF-8
  4. Poprawka 2: przekonfiguruj stdout wewnątrz skryptu
  5. Poprawka 3: nadaj plikom jawne kodowanie
  6. Której poprawki powinieneś użyć?
  7. Jeśli nadal zawodzi

Twoje żądanie przeszło, model odpowiedział, a potem Python padł na print(reply). To nie wina API. Odpowiedź jest w porządku; miejsce, do którego ją wypisujesz, nie potrafi przedstawić niektórych jej znaków. Najszybsza poprawka to włączenie trybu UTF-8 przez PYTHONUTF8=1. Jeśli wolisz trzymać to w skrypcie, wywołaj sys.stdout.reconfigure(encoding="utf-8") na górze, a gdy zapisujesz odpowiedź do pliku, przekaż encoding="utf-8" do open().

Jak wygląda błąd#

Załóżmy, że zadajesz modelowi pytanie po rosyjsku i wypisujesz odpowiedź. To cały skrypt, korzystający z zwykłego wywołania requests z naszego przewodnika po pierwszym żądaniu:

python
import os, requests

r = requests.post(
    "https://api.cheaptokens.dev/v1/chat/completions",
    headers={"Authorization": f"Bearer {os.environ['CHEAPTOKENS_API_KEY']}"},
    json={
        "model": "deepseek-ai/DeepSeek-V4-Flash-0731",
        "messages": [{"role": "user", "content": "Ответь одним предложением: что такое токен?"}],
        "max_tokens": 200,
    },
)
reply = r.json()["choices"][0]["message"]["content"]
print(reply)

Gdy stdout jest przekierowany do pliku lub potoku, albo Python został uruchomiony ze starszą stroną kodową dla swoich strumieni, ostatnia linia kończy się tak:

text
UnicodeEncodeError: 'charmap' codec can't encode characters in position 0-5: character maps to <undefined>

Nazwa kodeka w komunikacie zależy od kodowania strumienia (dla strony kodowej Windows to zwykle charmap, dla ascii to ascii), a pozycje zależą od odpowiedzi. Kształt jest zawsze ten sam: traceback wskazuje na print, a nie na żądanie.

Traceback UnicodeEncodeError wskazujący na print(reply)
Typowy kształt błędu: jest zgłaszany przez print, po tym jak wywołanie API już się powiodło

Dlaczego tak się dzieje? print konwertuje tekst na bajty, używając kodowania sys.stdout. Jeśli to kodowanie to starsza strona kodowa, każdy znak spoza niej, taki jak cyrylica w zachodniej stronie kodowej, emoji, czasem typograficzny myślnik, nie ma bajtu, w który można go zamienić. Python zgłasza wyjątek zamiast zgadywać.

Pojawia się w trzech sytuacjach:

  • stdout jest przekierowany lub podłączony do potoku (python app.py > out.txt, | another program), albo skrypt działa z harmonogramu, CI lub edytora, gdzie stdout nie jest konsolą;
  • ustawiono PYTHONLEGACYWINDOWSSTDIO, albo strumień z innego powodu dostaje wąskie kodowanie;
  • zapisujesz odpowiedź do pliku przez open(path, "w") bez encoding=.

Który Python? Wszystko poniżej dotyczy Pythona 3.7–3.14, gdzie kodowanie strumieni i plików pochodzi z systemu. Zgodnie z PEP 686 (status Final, https://peps.python.org/pep-0686/, sprawdzone 5 listopada 2026) tryb UTF-8 jest domyślnie włączony od Pythona 3.15, więc open() i stdio używają tam już UTF-8, chyba że ktoś go wyłączył przez PYTHONUTF8=0 lub -X utf8=0. Sprawdź przez python --version.

Sprawdź, jakie kodowanie faktycznie masz#

Zanim cokolwiek zmienisz, zobacz, co wybrał Python. To zajmuje dziesięć sekund.

Spróbuj samWypróbuj: jakie według Pythona jest kodowanie twojego stdout?Pokaż odpowiedź

Uruchom python -c "import sys; print(sys.stdout.encoding)" w tym samym oknie, w którym skrypt zawodzi. W konsoli interaktywnej zwykle wypisuje utf-8, więc samo to niewiele dowodzi. Uruchom to ponownie z > check.txt na końcu i otwórz plik: przekierowane wyjście to to, co częściej daje nazwę cp… lub ascii, i to jest twój winowajca. Jeśli wszystko mówi utf-8, sprawdź plik, do którego zapisujesz.

Chcesz zobaczyć awarię bez czekania na zły dzień? Na macOS lub Linuksie wymuś wąskie kodowanie dla jednego uruchomienia:

bash
PYTHONIOENCODING=ascii python -c "print('Привет')"

To kończy się komunikatem o kodeku ascii, z tej samej rodziny co błąd powyżej. W Windows zmienna jest ignorowana dla konsoli interaktywnej, ale działa, gdy wyjście jest przekierowane: w cmd uruchom set PYTHONIOENCODING=ascii, potem python -c "print('Привет')" > out.txt (w PowerShell: $env:PYTHONIOENCODING = "ascii").

Poprawka 1: włącz tryb UTF-8#

Python ma tryb, który czyni UTF-8 domyślnym dla tekstu. Zgodnie z dokumentacją Pythona (strona wiersza poleceń, sprawdzona 5 listopada 2026) PYTHONUTF8=1 go włącza, a -X utf8 robi to samo; oba istnieją od Pythona 3.7. Jeden haczyk z tej samej strony: każda inna niepusta wartość PYTHONUTF8 powoduje błąd przy starcie, więc używaj dokładnie 1 (albo 0, by go wyłączyć). W 3.15 potrzebujesz tego tylko, by jawnie włączyć tryb na starszych interpreterach; jeśli tam już jest włączony, poszukaj PYTHONUTF8=0 w swoim środowisku.

Włącz tryb UTF-8 dla jednego uruchomienia

cmd

bat
set PYTHONUTF8=1
python app.py

PowerShell

powershell
$env:PYTHONUTF8 = "1"
python app.py

macOS / Linux

bash
PYTHONUTF8=1 python app.py

Bez zmian w kodzie, a obejmuje konsolę, przekierowania i pliki w tym uruchomieniu. Jeśli nie chcesz ustawiać zmiennej za każdym razem, python -X utf8 app.py daje ten sam wynik z wiersza poleceń.

Jest też PYTHONIOENCODING=utf-8, który nadpisuje kodowanie tylko stdin, stdout i stderr. Przeczytaj jego wpis w dokumentacji, zanim polegasz na nim w Windows: od Pythona 3.6 jest ignorowany dla interaktywnego bufora konsoli, chyba że ustawiono PYTHONLEGACYWINDOWSSTDIO. Przekierowane pliki i potoki nie podlegają temu wyjątkowi. Więc może naprawić python app.py > out.txt i nadal nic nie robić w oknie konsoli. Dla stderr handler błędów to zawsze backslashreplace.

Poprawka 2: przekonfiguruj stdout wewnątrz skryptu#

Jeśli nie kontrolujesz, jak ludzie uruchamiają skrypt, zmień sam strumień na samym początku:

python
import sys

sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8")

print(reply)

reconfigure jest udokumentowany dla io.TextIOWrapper (dodany w 3.7). Gdy przekażesz encoding i pominiesz errors, handler to strict, więc znak, który się nie mieści, nadal zgłasza wyjątek; w typowym przypadku UTF-8 nic nie zostaje. Dwa szczegóły z dokumentacji: kodowania nie można zmienić po odczytaniu danych ze strumienia (można po zapisie), a encoding="locale" jest obsługiwany od 3.11.

Potrzebujesz, by skrypt nigdy nie padał, nawet na terminalu, który naprawdę nie potrafi pokazać tekstu? Dodaj errors="replace". Nieznane znaki pojawiają się wtedy jako ? zamiast zatrzymywać uruchomienie. To ukrywa problem, a nie go naprawia, więc trzymaj to dla logów.

Poprawka 3: nadaj plikom jawne kodowanie#

Zapisywanie odpowiedzi to ta sama awaria, tylko cichsza w kodzie:

python
# before: on Python up to 3.14 this uses the system's default encoding
with open("reply.txt", "w") as f:
    f.write(reply)

# after: the file is UTF-8 on every machine
with open("reply.txt", "w", encoding="utf-8") as f:
    f.write(reply)

Użyj tego samego encoding="utf-8", gdy odczytujesz plik z powrotem, inaczej tekst może się zepsuć przy wczytywaniu.

Przed: print(reply) zgłasza UnicodeEncodeError. Po: ta sama linia wypisuje pełną odpowiedź
Ta sama linia print, przed i po trybie UTF-8

Której poprawki powinieneś użyć?#

Wybierz swój przypadek

Wybierz swój przypadek

Uruchamiam własny skrypt w terminalu

Ustaw PYTHONUTF8=1 raz w swoim środowisku albo uruchamiaj z python -X utf8 app.py. Nic w kodzie się nie zmienia.

Wysyłam skrypt innym ludziom

Umieść sys.stdout.reconfigure(encoding="utf-8") na górze, żeby działał niezależnie od tego, jak go uruchomią.

Awaria dotyczy pliku, nie ekranu

Dodaj encoding="utf-8" do open(), przy zapisie i odczycie.

Możesz je łączyć. Skrypt, który wypisuje i zapisuje odpowiedzi, potrzebuje Poprawki 2 lub Poprawki 1 dla ekranu i Poprawki 3 dla pliku.

Jeśli nadal zawodzi#

  • Błąd przeniósł się do innej linii. Wtedy zawodzące wywołanie to inne print lub write; poprawka musi zadziałać przed nim, więc umieść reconfigure na samym początku.
  • Błąd wspomina PYTHONUTF8. Zmienna ma wartość inną niż 1 lub 0. Usuń ją i ustaw ponownie.
  • Brak awarii, ale widzisz kwadraty lub ? w konsoli. Python zapisał poprawny UTF-8; czcionka lub strona kodowa konsoli nie potrafi go wyświetlić. Przekieruj do pliku i otwórz plik w edytorze, by potwierdzić, że odpowiedź jest nienaruszona.
  • Błąd to JSONDecodeError, nie UnicodeEncodeError. Inny problem: zobacz jak wyodrębnić JSON z odpowiedzi modelu.

Powyższy kod żądania to zwykłe wywołanie requests; dokumentacja CheapTokens pokazuje to samo wywołanie dla OpenAI SDK. Inny model działa z tą samą poprawką: zamień pole model na zai-org/GLM-5.3-Flash (do 8192 tokenów wyjściowych na odpowiedź, kontekst 390K). Błąd kodowania nie ma nic wspólnego z modelem, tylko z tym, dokąd trafia tekst.