Який правильний спосіб визначити, чи є об’єкт подібним до байтів у Python?


90

У мене є код, який очікує, strале буде обробляти випадки передачі bytesнаступним чином:

if isinstance(data, bytes):
    data = data.decode()

На жаль, це не працює у випадку з bytearray. Чи існує більш загальний спосіб перевірити, чи є об'єкт bytesабо bytearray, або мені слід просто перевірити обидва? Невже hasattr('decode')так погано, як мені здається?


6
Особисто я люблю друкувати качок на пітоні так само, як наступний хлопець. Але якщо вам потрібно перевіряти введені аргументи та примушувати різні типи, то ви більше не набираєте качок - ви просто ускладнюєте читання коду. Моя пропозиція тут (а інші можуть не погодитися) полягала б у створенні декількох функцій (які обробляють примус типу та делегують базовій реалізації).
mgilson

(1) Якщо це вам не потрібно для сумісності із застарілим кодом Python 2; уникайте одночасного прийому як текстових, так і двійкових даних. Якщо ваша функція працює з текстом, вона повинна приймати лише str. Деякий інший код повинен якнайшвидше перетворити байт в Unicode при введенні. (2) "байт-подібний" має особливе значення в Python (об'єкти, що підтримують буферний протокол (лише C))
jfs

Основна проблема полягає в тому, що ця функція не працює в Python 2, де простий рядок ASCII проходить перевірку <байт>!
Апостолос

Відповіді:


73

Тут можна використати кілька підходів.

Качка набравши

Оскільки 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 .


+1 за згадку про загальнодоступні дженерики, про які я повністю забув стандартну бібліотеку.
A. Wilcox

Оскільки виклик str on str нічого не робить, і здався мені найбільш зрозумілим, я пішов із цим.
A. Wilcox

загалом мені подобається hasattrбільше, ніж спроба / за винятком того, щоб запобігти випадковому ковтанню якоїсь помилки у функції декодування, але +1.
keredson

37

Ви можете використовувати:

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'>

1
шість типів_струнків мають бути сумісними на 2/3.
Джошуа Олсон,

Цей тип перевірки не працює в Python 2, де простий рядок ASCII проходить перевірку <байт>!
Апостолос

12
>>> content = b"hello"
>>> text = "hello"
>>> type(content)
<class 'bytes'>
>>> type(text)
<class 'str'>
>>> type(text) is str
True
>>> type(content) is bytes
True

Зверніть увагу, що це не надійний тест у Python 2 , де рядовий об'єкт передається також як байти! Тобто, виходячи з наведеного вище коду, type(text) is bytesбуде True!
Апостолос

11

Цей код неправильний, якщо ви не знаєте чогось, чого ми не знаємо:

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має бути першим аргументом. Якщо ці обмеження не працюють для вас, використовуйте замість цього одну з інших відповідей.


У бібліотеці, яку я пишу, для цього конкретного методу я точно знаю, що байти та / або байтовий масив, який я отримую, кодуються UTF-8.
A. Wilcox

1
@AndrewWilcox: Досить справедливо, але я залишаю цю інформацію для майбутнього трафіку Google.
Кевін

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'явно для декодування, якщо ви не хочете витрачати кілька байтів. Причина полягає в тому, що наступного разу, коли ви або хтось інший прочитаєте вихідний код, ситуація стане більш очевидною.


3

Тут є два запитання, і відповіді на них різні.

Перше запитання, заголовок цього допису, полягає у тому, як правильно визначити, чи є об’єкт байтовим об’єктом у 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 на це запитання чудова. Зверніть увагу, що не всі байтоподібні об'єкти підтримують decode ().


1
Зверніть увагу, що якщо ви це зробите, вам слід зателефонувати .release()або скористатися версією менеджера контексту.
o11c

Я думаю, що в CPython тимчасовий memoryviewбуде негайно звільнений і .release()буде називатися неявно. Але я погоджуюсь, що на це краще не покладатися, оскільки не всі реалізації Python враховуються за посиланнями.
Джек О'Коннор,

0

Тест if isinstance(data, bytes)або if type(data) == bytesтощо не працює в Python 2, де простий рядок ASCII проходить тест! Оскільки я використовую і Python 2, і Python 3, щоб подолати це, я роблю наступну перевірку:

if str(type(data)).find("bytes") != -1: print("It's <bytes>")

Це трохи потворно, але він виконує роботу, яку задає питання, і завжди працює найпростішим способом.


strОб'єкти Python2 є bytes : str is bytes-> Trueу Python2
snakecharmerb

Очевидно, звідси проблема виявлення! :)
Апостолос
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.