Jaki jest właściwy sposób określenia, czy obiekt jest obiektem typu bajtowego w Pythonie?


90

Mam kod, który oczekuje, strale zajmie się przypadkiem przekazania bytesw następujący sposób:

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

Niestety to nie działa w przypadku bytearray. Czy istnieje bardziej ogólny sposób sprawdzenia, czy obiekt jest albo bytesalbo bytearray, czy powinienem po prostu sprawdzić oba? Czy jest hasattr('decode')tak źle, jak się czuję?


6
Osobiście uwielbiam pisać kaczkę Pythona tak samo jak następny facet. Ale jeśli musisz sprawdzać swoje argumenty wejściowe i wymuszać na różnych typach, to nie musisz już kaczkować - po prostu utrudniasz odczytanie kodu. Moją sugestią tutaj (a inni mogą się z tym nie zgodzić) byłoby utworzenie wielu funkcji (które obsługują wymuszanie typu i delegują do podstawowej implementacji).
— mgilson

(1) Chyba że jest to potrzebne do zgodności ze starszym kodem Python 2; unikaj jednoczesnego akceptowania danych tekstowych i binarnych. Jeśli twoja funkcja działa z tekstem, powinna akceptować tylko str. Inny kod powinien konwertować z bajtów do Unicode na wejściu tak szybko, jak to możliwe. (2) „podobne do bajtów” ma specjalne znaczenie w Pythonie (obiekty obsługujące protokół bufora (tylko C))
— jfs

Głównym problemem jest to, że ta funkcja nie działa w Pythonie 2, gdzie prosty łańcuch ASCII przechodzi test <bajtów>!
— Apostolos

Odpowiedzi:


73

Jest kilka podejść, których możesz tutaj użyć.

Pisanie kaczki

Ponieważ Python jest pisany kaczką , możesz po prostu wykonać następujące czynności (co wydaje się być sposobem zwykle sugerowanym):

try:
    data = data.decode()
except (UnicodeDecodeError, AttributeError):
    pass

Możesz jednak użyć hasattrtego, co opisujesz i prawdopodobnie będzie dobrze. Jest to oczywiście założenie, że .decode()metoda dla danego obiektu zwraca ciąg znaków i nie ma żadnych nieprzyjemnych skutków ubocznych.

Osobiście polecam albo wyjątek, albo hasattrmetodę, ale cokolwiek użyjesz, zależy od ciebie.

Użyj str ()

To podejście jest rzadkie, ale jest możliwe:

data = str(data, "utf-8")

Inne kodowania są dozwolone, podobnie jak w przypadku protokołu bufora .decode(). Możesz również przekazać trzeci parametr, aby określić obsługę błędów.

Funkcje ogólne z pojedynczą wysyłką (Python 3.4+)

Python 3.4 i nowsze wersje zawierają fajną funkcję zwaną funkcjami ogólnymi o pojedynczej wysyłce, za pośrednictwem functools.singledispatch . Jest to trochę bardziej szczegółowe, ale jest też bardziej wyraźne:

def func(data):
    # This is the generic implementation
    data = data.decode()
    ...

@func.register(str)
def _(data):
    # data will already be a string
    ...

Jeśli chcesz, możesz również tworzyć specjalne programy obsługi bytearrayi bytesobiekty.

Uwaga : funkcje pojedynczego wysyłania działają tylko na pierwszym argumencie! Jest to celowa funkcja, zobacz PEP 433 .


+1 za wzmiankę o typach generycznych z pojedynczą wysyłką, o których zupełnie zapomniałem dostarczonej biblioteki standardowej.
— A. Wilcox

Ponieważ wywołanie str na str nic nie daje i wydawało mi się to najbardziej oczywiste, poszedłem z tym.
— A. Wilcox

ogólnie lubię hasattrbardziej niż try /, z wyjątkiem tego, aby zapobiec przypadkowemu połknięciu jakiegoś błędu w funkcji dekodowania, ale +1.
— keredson

37

Możesz użyć:

isinstance(data, (bytes, bytearray))

Ze względu na inną klasę bazową zastosowano tutaj.

>>> bytes.__base__
<type 'basestring'>
>>> bytearray.__base__
<type 'object'>

Sprawdzić bytes

>>> by = bytes()
>>> isinstance(by, basestring)
True

Jednak,

>>> buf = bytearray()
>>> isinstance(buf, basestring)
False

Powyższe kody są testowane w Pythonie 2.7

Niestety w Pythonie 3.4 są takie same ...

>>> bytes.__base__
<class 'object'>
>>> bytearray.__base__
<class 'object'>

1
six.string_types powinno być zgodne w 2/3.
— Joshua Olson,

Ten rodzaj sprawdzania nie działa w Pythonie 2, gdzie prosty łańcuch ASCII przechodzi test <bajtów>!
— Apostolos

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

Zauważ, że nie jest to niezawodny test w Pythonie 2 , w którym obiekt łańcuchowy jest przekazywany również jako bajty! Oznacza to, że na podstawie powyższego kodu type(text) is bytesbędzie True!
— Apostolos

11

Ten kod nie jest poprawny, chyba że wiesz coś, czego nie wiemy:

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

Nie znasz (wydajesz się) znać kodowania data. Zakładasz, że to UTF-8 , ale to może być bardzo złe. Ponieważ nie znasz kodowania, nie masz tekstu . Masz bajty, które pod słońcem mogą mieć jakiekolwiek znaczenie.

Dobrą wiadomością jest to, że większość losowych sekwencji bajtów nie jest prawidłowym kodem UTF-8, więc kiedy to się zepsuje, będzie się łamać głośno ( errors='strict'jest to ustawienie domyślne) zamiast po cichu robić źle. Jeszcze lepszą wiadomością jest to, że większość tych losowych sekwencji, które okazały się być prawidłowymi kodami UTF-8, to również prawidłowe ASCII, które i tak ( prawie ) wszyscy zgadzają się co do sposobu parsowania.

Zła wiadomość jest taka, że ​​nie ma rozsądnego sposobu, aby to naprawić. Istnieje standardowy sposób dostarczania informacji o kodowaniu: użyj strzamiast bytes. Jeśli jakiś kod innej firmy przekazał ci obiekt byteslub bytearraybez dalszego kontekstu lub informacji, jedyną poprawną czynnością jest niepowodzenie.


Zakładając, że znasz kodowanie, możesz użyć functools.singledispatchtutaj:

@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, ...)

To nie działa na metodach i datamusi być pierwszym argumentem. Jeśli te ograniczenia nie działają w Twoim przypadku, użyj jednej z pozostałych odpowiedzi.


W bibliotece, którą piszę, dla tej konkretnej metody zdecydowanie wiem, że bajty i / lub bajty, które otrzymuję, są zakodowane w UTF-8.
— A. Wilcox

1
@AndrewWilcox: W porządku, ale zostawiam te informacje dla przyszłego ruchu Google.
— Kevin

4

To zależy od tego, co chcesz rozwiązać. Jeśli chcesz mieć ten sam kod, który konwertuje obie obserwacje na ciąg, możesz po prostu przekonwertować typ na bytespierwszy, a następnie zdekodować. W ten sposób jest to jednowierszowy:

#!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)

W ten sposób odpowiedzią dla Ciebie może być:

data = bytes(data).decode()

W każdym razie proponuję napisać 'utf-8'wprost do dekodowania, jeśli nie zależy ci na oszczędzeniu kilku bajtów. Powodem jest to, że gdy następnym razem ty lub ktoś inny przeczytacie kod źródłowy, sytuacja będzie bardziej widoczna.


3

Są tu dwa pytania, na które odpowiedzi są różne.

Pierwsze pytanie, tytuł tego posta, brzmi: Jaki jest właściwy sposób określenia, czy obiekt jest obiektem bajtopodobnym w Pythonie? Obejmuje szereg wbudowanych typów ( bytes, bytearray, array.array, memoryview, inne?) I ewentualnie także typy zdefiniowane przez użytkownika. Najlepszym sposobem, jaki znam, aby to sprawdzić, jest próba utworzenia memoryviewz nich:

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

Jednak w treści oryginalnego posta brzmi to tak, jakby zamiast tego pytanie brzmiało: Jak sprawdzić, czy obiekt obsługuje dekodowanie ()? Powyższa odpowiedź @ elizabeth-myers na to pytanie jest świetna. Zauważ, że nie wszystkie obiekty podobne do bajtów obsługują decode ().


1
Zauważ, że jeśli to zrobisz, musisz zadzwonić .release()lub użyć wersji menedżera kontekstu.
— o11c

Myślę, że w CPythonie plik tymczasowy memoryviewzostałby natychmiast zwolniony i .release()zostałby wywołany niejawnie. Ale zgadzam się, że najlepiej na tym nie polegać, ponieważ nie wszystkie implementacje Pythona są liczone jako odwołania.
— Jack O'Connor

0

Test if isinstance(data, bytes)lub if type(data) == bytesitp. Nie działa w Pythonie 2, gdzie prosty łańcuch ASCII przechodzi test! Ponieważ używam zarówno Pythona 2, jak i Pythona 3, aby temu zaradzić, wykonuję następujące sprawdzenie:

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

Jest trochę brzydki, ale spełnia to, o co chodzi w pytaniu, i zawsze działa, w najprostszy sposób.


Python2 strobiekty są bytes jednak: str is bytes-> Truew python2
— snakecharmerb

Oczywiście stąd problem z wykrywaniem! :)
— Apostolos
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.