2016-01-19 13 views
24

Ho codice che prevede str ma gestirà il caso di essere passato bytes nel seguente modo:Qual è il modo corretto per determinare se un oggetto è un oggetto simile a un byte in Python?

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

Purtroppo, questo non funziona nel caso di bytearray. Esiste un modo più generico per verificare se un oggetto è o bytes o bytearray oppure devo controllare per entrambi? È hasattr('decode') grave come credo che sarebbe?

+4

Personalmente, adoro il dattilografo di Python tanto quanto il ragazzo successivo. Ma se hai bisogno di fare controlli sui tuoi argomenti di input e forzare a tipi diversi, allora non stai più digitando - Stai rendendo il tuo codice più difficile da leggere in una manutenzione. Il mio suggerimento qui (e altri potrebbero non essere d'accordo) sarebbe quello di rendere più funzioni (che gestiscono il tipo di coercizione e delegare a un'implementazione di base). – mgilson

+0

(1) A meno che non sia necessario per la compatibilità con il codice legacy Python 2; evitare di accettare contemporaneamente sia testo che dati binari. Se la tua funzione funziona con il testo, allora dovrebbe accettare solo 'str'. Qualche altro codice dovrebbe convertirsi 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

risposta

21

Ci sono alcuni approcci che è possibile utilizzare qui.

duck typing

Poiché Python è duck typed, si può semplicemente fare come segue (che sembra essere il modo in cui di solito consigliata):

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

Si potrebbe usare hasattr come lei, tuttavia, e probabilmente andrebbe bene. Questo è, naturalmente, assumendo che il metodo .decode() per l'oggetto dato restituisca una stringa e non abbia effetti collaterali sgradevoli.

Personalmente raccomando l'eccezione o il metodo hasattr, ma qualunque sia il vostro utilizzo dipende da voi.

Usa str()

Questo approccio è raro, ma è possibile:

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

Altre codifiche sono ammissibili, proprio come con il protocollo .decode() buffer. È anche possibile passare un terzo parametro per specificare la gestione degli errori.

singola spedizione funzioni generiche (Python 3.4+)

Python 3.4 e superiori comprendono una bella funzionalità denominata single-spedizione funzioni generiche, 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 
    ... 

Si potrebbe anche fare i gestori speciali per bytearray e bytes oggetti se lo è scelto.

Attenzione: le funzioni di sola spedizione funzionano solo sul primo argomento! Questa è una funzionalità intenzionale, vedere PEP 433.

+0

+1 per la menzione di generici a invio singolo, che ho completamente dimenticato della libreria standard fornita. –

+0

Dal momento che chiamare str on non fa nulla, e mi sembrava il più chiaro, sono andato con quello. –

+0

nel complesso mi piace 'hasattr' più del try/tranne che per evitare di ingerire accidentalmente qualche bug nella funzione di decodifica, ma +1. – keredson

13

È possibile utilizzare:

isinstance(data, (bytes, bytearray)) 

A causa della diversa classe di base viene qui utilizzato.

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

Per controllare bytes

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

Tuttavia,

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

I codici di cui sopra sono prova in python 2.7

Purtroppo, in pitone 3.4, essi sono gli stessi ...

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

Funziona molto bene. Mi chiedo se c'è qualcosa di più generico, però. –

+0

six.string_types dovrebbe essere 2/3 compatibile. –

4

Questo codice non è corretto a meno che non si sa qualcosa che noi non sappiamo:

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

Non è (sembri) conosce la codifica dei data. Stai assumendo it's UTF-8, ma potrebbe benissimo essere sbagliato. Dal momento che non si conosce la codifica, you do not have text. Hai byte, che potrebbero avere qualsiasi significato sotto il sole.

La buona notizia è che la maggior parte delle sequenze casuali di byte non è UTF-8 valida, quindi quando si interrompe, si romperà 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 valide come UTF-8 sono anche ASCII valide, che (nearly) sono tutti 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 str anziché bytes. Se alcuni codici di terze parti ti hanno consegnato un oggetto bytes o bytearray senza ulteriori contesti o informazioni, l'unica azione corretta è fallire.


Ora, supponendo che si conosce la codifica, è possibile utilizzare functools.singledispatch qui:

@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 sui metodi, e data deve essere il primo argomento. Se quelle restrizioni non funzionano per te, usa invece una delle altre risposte.

+0

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

+1

@AndrewWilcox: abbastanza corretto, ma sto lasciando queste informazioni per il futuro traffico di Google. – Kevin

2

Dipende da ciò che si vuole risolvere. Se si desidera avere lo stesso codice che converte entrambi i casi in una stringa, è sufficiente convertire il tipo in bytes prima e quindi decodificare. In questo modo, si tratta di un one-liner:

#!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 voi potrebbe essere:

data = bytes(data).decode() 

In ogni caso, vi suggerisco di scrivere 'utf-8' esplicitamente alla decodifica, se non lo fai cura di risparmiare pochi byte. Il motivo è che la prossima volta che tu o qualcun altro leggerete il codice sorgente, la situazione sarà più evidente.

1

Ci sono due domande qui, e le risposte a loro sono diverse.

La prima domanda, il titolo di questo post, è Qual è il modo corretto per determinare se un oggetto è un oggetto simile a un byte in Python? Questo include un numero di tipi built-in (bytes, bytearray, array.array, memoryview, altri?) Ed eventualmente anche tipi definiti dall'utente. Il modo migliore che conosco per verificare la presenza di questi è quello di cercare di creare un memoryview fuori di essi:

>>> 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, però, suona come la questione è invece Come faccio a testare se un oggetto supporta la decodifica()? @ elizabeth-myers 'sopra la risposta a questa domanda è grande. Si noti che non tutti gli oggetti simili a byte supportano la decodifica().

Problemi correlati