Qual è il modo corretto per determinare se un oggetto è un oggetto simile a byte in Python?


93

Ho un codice che si aspetta strma gestirà il caso di essere passato bytesnel modo seguente:

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

Sfortunatamente, questo non funziona nel caso di bytearray. Esiste un modo più generico per verificare se un oggetto è byteso bytearray, o devo semplicemente controllare entrambi? È hasattr('decode')così brutto come penso che sarebbe?


6
Personalmente, adoro la digitazione a papera di Python tanto quanto il ragazzo successivo. Ma se hai bisogno di controllare i tuoi argomenti di input e forzare a tipi diversi, allora non stai più digitando anatra: stai solo rendendo il tuo codice più difficile da leggere in un mantenimento. Il mio suggerimento qui (e altri potrebbero non essere d'accordo) sarebbe di creare più funzioni (che gestiscono la coercizione del tipo e delegano a un'implementazione di base).
mgilson

(1) A meno che non sia necessario per la compatibilità con il codice Python 2 legacy; evitare di accettare simultaneamente dati di testo e binari. Se la tua funzione funziona con il testo, dovrebbe accettare solo str. Qualche altro codice dovrebbe essere convertito da byte in Unicode in input il prima possibile. (2) "byte-like" ha un significato speciale in Python (oggetti che supportano il protocollo buffer (solo C))
jfs

Il problema principale è che questa funzione non funziona in Python 2, dove una semplice stringa ASCII supera il test di <bytes>!
Apostolos

Risposte:


76

Ci sono alcuni approcci che potresti usare qui.

Digitazione di anatra

Poiché Python è digitato a papera , potresti semplicemente fare quanto segue (che sembra essere il modo solitamente suggerito):

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

Potresti usare hasattrcome descrivi, tuttavia, e probabilmente andrebbe bene. Questo, ovviamente, presuppone che il .decode()metodo per l'oggetto dato restituisca una stringa e non abbia effetti collaterali negativi.

Personalmente raccomando l'eccezione o il hasattrmetodo, ma qualunque cosa tu usi dipende da te.

Usa str ()

Questo approccio è raro, ma è possibile:

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

Sono consentite altre codifiche, proprio come con il protocollo buffer .decode(). È inoltre possibile passare un terzo parametro per specificare la gestione degli errori.

Funzioni generiche a invio singolo (Python 3.4+)

Python 3.4 e versioni successive includono un'elegante funzionalità chiamata funzioni generiche a invio singolo, tramite functools.singledispatch . Questo è un po 'più prolisso, ma è anche più esplicito:

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

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

Puoi anche creare gestori speciali per oggetti bytearraye bytesse lo desideri.

Attenzione : le funzioni di invio singolo funzionano solo sul primo argomento! Questa è una caratteristica intenzionale, vedere PEP 433 .


+1 per la menzione dei generici a invio singolo, che ho completamente dimenticato la libreria standard fornita.
A. Wilcox

Dato che chiamare str su str non fa nulla, e mi è sembrato il più chiaro, sono andato con quello.
A. Wilcox

nel complesso mi piace di hasattrpiù del tentativo / tranne per evitare di ingoiare accidentalmente qualche bug nella funzione di decodifica, ma +1.
keredson

39

Puoi usare:

isinstance(data, (bytes, bytearray))

A causa della diversa classe di base viene utilizzata qui.

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

Controllare bytes

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

Però,

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

I codici precedenti sono testati con python 2.7

Sfortunatamente, sotto Python 3.4, sono gli stessi ...

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

1
six.string_types dovrebbe essere 2/3 compatibile.
Joshua Olson

Questo tipo di controllo non funziona in Python 2, dove una semplice stringa ASCII supera il test di <bytes>!
Apostolos

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

Nota che questo non è un test affidabile in Python 2 , dove un oggetto stringa passa anche come byte! Cioè, in base al codice sopra, type(text) is bytessarà True!
Apostolos

11

Questo codice non è corretto a meno che tu non sappia qualcosa che noi non sappiamo:

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

Non conosci (sembra) la codifica di data. Stai assumendo che sia UTF-8 , ma potrebbe essere sbagliato. Dato che non conosci la codifica, non hai testo . Hai byte, che potrebbero avere qualsiasi significato sotto il sole.

La buona notizia è che la maggior parte delle sequenze casuali di byte non sono UTF-8 valide, quindi quando si interrompe, si interromperà rumorosamente ( errors='strict'è l'impostazione predefinita) invece di fare silenziosamente la cosa sbagliata. La notizia ancora migliore è che la maggior parte di quelle sequenze casuali che sono UTF-8 valide sono anche ASCII valide, che ( quasi ) tutti sono d'accordo su come analizzare comunque.

La cattiva notizia è che non esiste un modo ragionevole per risolvere questo problema. Esiste un modo standard per fornire informazioni sulla codifica: utilizzare strinvece di bytes. Se un codice di terze parti ti ha consegnato un oggetto byteso bytearraysenza ulteriori informazioni o contesto, l'unica azione corretta è fallire.


Ora, supponendo che tu conosca la codifica, puoi usare functools.singledispatchqui:

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

Questo non funziona con i metodi e datadeve essere il primo argomento. Se queste restrizioni non funzionano per te, usa invece una delle altre risposte.


Nella libreria che sto scrivendo, per questo metodo specifico, so sicuramente che i byte e / o il bytearray che sto ricevendo sono codificati UTF-8.
A. Wilcox

1
@ AndrewWilcox: Giusto, ma lascio queste informazioni per il futuro traffico di Google.
Kevin

4

Dipende da cosa vuoi risolvere. Se vuoi avere lo stesso codice che converte entrambi i casi in una stringa, puoi semplicemente convertire prima il tipo in bytese poi decodificare. In questo modo, è una battuta:

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

In questo modo, la risposta per te potrebbe essere:

data = bytes(data).decode()

Ad ogni modo suggerisco di scrivere 'utf-8'esplicitamente al decode, se non vi interessa risparmiare pochi byte. Il motivo è che la prossima volta che tu o qualcun altro leggerai il codice sorgente, la situazione sarà più evidente.


3

Ci sono due domande qui e le risposte sono diverse.

La prima domanda, il titolo di questo post, è qual è il modo corretto per determinare se un oggetto è un oggetto simile a byte in Python? Questo include un certo numero di tipi predefiniti ( bytes, bytearray, array.array, memoryview, altri?) Ed eventualmente anche tipi definiti dall'utente. Il modo migliore che conosco per verificarli è provare a crearne uno 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'

Nel corpo del post originale, tuttavia, sembra che la domanda sia invece: Come faccio a verificare se un oggetto supporta decode ()? La risposta di @ elizabeth-myers a questa domanda è ottima. Notare che non tutti gli oggetti simili a byte supportano decode ().


1
Nota che se lo fai, devi chiamare .release()o usare la versione del gestore di contesto.
o11c

Penso che in CPython il temporaneo memoryviewverrebbe immediatamente liberato e .release()verrebbe chiamato implicitamente. Ma sono d'accordo che è meglio non fare affidamento su questo, poiché non tutte le implementazioni di Python sono conteggiate come riferimento.
Jack O'Connor

0

Il test if isinstance(data, bytes)o if type(data) == bytes, ecc. Non funziona in Python 2, dove una semplice stringa ASCII supera il test di! Poiché utilizzo sia Python 2 che Python 3, per ovviare a questo faccio il seguente controllo:

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

È un po 'brutto, ma fa il lavoro che fa la domanda e funziona sempre, nel modo più semplice.


Gli stroggetti Python2 sono bytes però: str is bytes-> Truein Python2
snakecharmerb

Ovviamente, da qui il problema di rilevamento! :)
Apostolos
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.