Сегодня на работе я увидел следующую строку кода:
decoded_token = base64.b64decode(api_token.encode("utf-8")).decode("utf-8")
Он является частью ETL-скрипта AirFlow, а decoded_token
используется в качестве токена носителя в запросе API. Этот код выполняется на сервере, который использует Python 2.7, и мой коллега сказал мне, что этот код работает ежедневно и успешно.
Тем не менее, насколько я понимаю, код сначала пытается превратить api_token
в байты (.encode), затем превратить байты в строку (base64.b64decode) и, наконец, снова превратить строку в строку (.decode). Я думаю, что это всегда приводит к ошибке.
import base64
api_token = "random-string"
decoded_token = base64.b64decode(api_token.encode("utf-8")).decode("utf-8")
Запуск кода локально дает мне:
Ошибка: UnicodeDecodeError: 'utf8' codec can't decode byte 0xad in position 0: invalid start byte
Какой ввод/тип должен быть api_token
, чтобы эта строка нет выдавала ошибку? Это вообще возможно или должно быть что-то еще в игре?
Редактировать: Как упоминал Клаус Д., по-видимому, в Python 2 и encode
, и decode
потребляли и возвращали строку. Тем не менее, запуск приведенного выше кода в Python 2.7 дает мне ту же ошибку, и мне еще предстоит найти ввод для api_token
, который не выдает ошибку.
Да, как я уже говорил, это не мой код, а якобы код, который отлично работает в продакшене, и я не понимаю, как он может...
В Python 2 не было байтов. И encode()
, и decode()
были str
→ str
.
@Клаус В Python 2 это было str
<-> unicode
…!?
@Клаус Д. Разве они не добавили тип bytes
в Python 2.7, который является просто псевдонимом для str
?
api_token
ожидается, что это строка в кодировке base 64.@deceze: Ах, ты прав! Спасибо - я как-то пропустил это... И base64.b64decode
возвращает Unicode, который затем превращается в обычную строку с помощью .decode("utf-8")
, верно? (P.S. Мне кажется хорошей идеей прочитать вашу последнюю статью в блоге :-))
@MarkRansom Верно. Теперь я вспоминаю, как взбудоражил колледж переход на Python 2 → 3.
Проблема, скорее всего, просто в том, что ваша тестовая входная строка не является строкой в кодировке base64, в то время как в производстве, какой бы вход уже ни был!
Python 2.7.18 (default, Jan 4 2022, 17:47:56)
...
>>> import base64
>>> api_token = "random-string"
>>> base64.b64decode(api_token)
'\xad\xa9\xdd\xa2k-\xae)\xe0'
>>> base64.b64decode(api_token).decode("utf-8")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/utf_8.py", line 16, in decode
return codecs.utf_8_decode(input, errors, True)
UnicodeDecodeError: 'utf8' codec can't decode byte 0xad in position 0: invalid start byte
кодируя строку как base64, вам также не нужно впоследствии декодировать ее как «utf-8», хотя вы можете, если ожидаете символы Unicode
>>> api_token = base64.b64encode(api_token)
>>> api_token
'cmFuZG9tLXN0cmluZw=='
>>> base64.b64decode(api_token)
'random-string'
>>> base64.b64decode(api_token).decode("utf-8")
u'random-string'
Пример с не-ascii-символами
>>> base64.b64decode(base64.b64encode("random string后缀"))
'random string\xe5\x90\x8e\xe7\xbc\x80'
>>> base64.b64decode(base64.b64encode("random string后缀")).decode("utf-8")
u'random string\u540e\u7f00'
>>> sys.stdout.write(base64.b64decode(base64.b64encode("random string后缀")) + "\n")
random string后缀
Обратите внимание, что в Python 2.7 bytes
— это просто псевдоним для str
, а специальный unicode
был добавлен для поддержки юникода!
>>> bytes is str
True
>>> bytes is unicode
False
>>> str("foo")
'foo'
>>> unicode("foo")
u'foo'
Я думаю, что древняя поговорка «Мусор на входе, мусор на выходе» здесь как нельзя лучше подходит.
для этого случая вам нужно
.b64encode()
перед декодированием, так как это не base64 (пока), а вашей входящей строке это может не понадобиться.. вам может понадобиться.encode()
тоже (без"utf-8"
), так как строка уже знает свою кодировку (упс, может дополнительно применимо только к Python 3.x)