Каков правильный способ определить, является ли объект байтовым объектом в Python?
У меня есть код, который ожидает str
, но будет обрабатывать случай передачи bytes
следующим образом:
if isinstance(data, bytes):
data = data.decode()
К сожалению, это не работает в случае bytearray
. Существует ли более общий способ проверки того, является ли объект либо bytes
, либо bytearray
, или я должен просто проверить для обоих? Является ли hasattr('decode')
настолько плохим, насколько я себя чувствую?
Ответы
Ответ 1
Здесь вы можете использовать несколько подходов.
Утка печатает
Так как Python набран уткой, вы можете просто сделать следующее (что, как обычно и предлагается):
try:
data = data.decode()
except (UnicodeDecodeError, AttributeError):
pass
Вы можете использовать hasattr
, как вы описываете, однако, и это, вероятно, будет хорошо. Это, конечно, при условии, что метод .decode()
для данного объекта возвращает строку и не имеет неприятных побочных эффектов.
Я лично рекомендую либо метод исключения, либо метод hasattr
, но все, что вы используете, зависит от вас.
Используйте str()
Такой подход необычен, но возможен:
data = str(data, "utf-8")
Допускаются другие кодировки, как и с буферным протоколом .decode()
. Вы также можете передать третий параметр, чтобы указать обработку ошибок.
Универсальные функции с одной отправкой (Python 3. 4+)
Python 3.4 и выше включает в себя изящную функцию, называемую универсальными функциями с одной отправкой, через functools.singledispatch. Это немного более многословно, но также и более явно:
def func(data):
# This is the generic implementation
data = data.decode()
...
@func.register(str)
def _(data):
# data will already be a string
...
Вы также можете создавать специальные обработчики для объектов bytearray
и bytes
, если хотите.
Осторожно,: функции с одной отправкой работают только с первым аргументом! Это преднамеренная функция, см. PEP 433.
Ответ 2
Вы можете использовать:
isinstance(data, (bytes, bytearray))
В связи с этим используется базовый класс.
>>> bytes.__base__
<type 'basestring'>
>>> bytearray.__base__
<type 'object'>
Чтобы проверить bytes
>>> by = bytes()
>>> isinstance(by, basestring)
True
Однако
>>> buf = bytearray()
>>> isinstance(buf, basestring)
False
Вышеуказанные коды являются тестами под python 2.7
К сожалению, под python 3.4 они одинаковы....
>>> bytes.__base__
<class 'object'>
>>> bytearray.__base__
<class 'object'>
Ответ 3
Этот код неверен, если вы не знаете, чего мы не знаем:
if isinstance(data, bytes):
data = data.decode()
Вы (кажется) не знаете кодировку data
. Вы предполагаете, что это UTF-8, но это вполне может быть ошибочным. Поскольку вы не знаете кодировку, у вас нет текста. У вас есть байты, которые могут иметь какое-либо значение под солнцем.
Хорошая новость заключается в том, что большинство случайных последовательностей байтов не являются допустимыми UTF-8, поэтому, когда это прерывается, оно громко прерывается (по умолчанию errors='strict'
) вместо того, чтобы молча делать неправильные вещи. Еще лучшая новость заключается в том, что большинство из тех случайных последовательностей, которые являются действительными UTF-8, также являются действительными ASCII, который (почти) каждый согласен с тем, как все равно анализировать.
Плохая новость в том, что нет разумного способа это исправить. Существует стандартный способ предоставления информации о кодировке: используйте str
вместо bytes
. Если какой-либо сторонний код передал вам bytes
или объект bytearray
без какого-либо дополнительного контекста или информации, единственное правильное действие - выполнить сбой.
Теперь, если вы знаете кодировку, вы можете использовать functools.singledispatch
здесь:
@functools.singledispatch
def foo(data, other_arguments, ...):
raise TypeError('Unknown type: '+repr(type(data)))
@foo.register(str)
def _(data, other_arguments, ...):
# data is a str
@foo.register(bytes)
@foo.register(bytearray)
def _(data, other_arguments, ...):
data = data.decode('encoding')
# explicit is better than implicit; don't leave the encoding out for UTF-8
return foo(data, other_arguments, ...)
Это не работает с методами, и data
должны быть первым аргументом. Если эти ограничения не работают для вас, используйте один из других ответов.
Ответ 4
Это зависит от того, что вы хотите решить. Если вы хотите иметь тот же код, который преобразует оба случая в строку, вы можете сначала преобразовать тип в bytes
, а затем декодировать. Таким образом, это однострочный:
#!python3
b1 = b'123456'
b2 = bytearray(b'123456')
print(type(b1))
print(type(b2))
s1 = bytes(b1).decode('utf-8')
s2 = bytes(b2).decode('utf-8')
print(s1)
print(s2)
Таким образом, для вас может быть ответ:
data = bytes(data).decode()
Во всяком случае, я предлагаю явно писать 'utf-8'
явно в декодирование, если вам не нужно оставлять несколько байтов. Причина в том, что в следующий раз, когда вы или кто-то еще прочитаете исходный код, ситуация будет более очевидной.
Ответ 5
Здесь есть два вопроса, и ответы на них разные.
Первый вопрос, заголовок этого сообщения: Каков правильный способ определить, является ли объект байтовым объектом в Python? Это включает в себя ряд встроенных типов (bytes
, bytearray
, array.array
, memoryview
, другие?) И, возможно, также пользовательские типы. Лучший способ, которым я знаю, проверить их - попытаться создать из них memoryview
:
>>> memoryview(b"foo")
<memory at 0x7f7c43a70888>
>>> memoryview(u"foo")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: memoryview: a bytes-like object is required, not 'str'
В теле исходного сообщения, однако, похоже, что вопрос заключается в следующем. Как проверить, поддерживает ли объект decode()? @elizabeth-myers 'выше ответ на этот вопрос велик. Обратите внимание, что не все байтоподобные объекты поддерживают декодирование().
Ответ 6
не уверен, почему никто не предложил следующее, которое ИМХО так чисто:
if type(data) == bytes:
data = data.decode()