¿Cuál es la forma correcta de determinar si un objeto es un objeto de tipo bytes en Python?

90

Tengo un código que espera, strpero manejará el caso de que se pase bytesde la siguiente manera:

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

Desafortunadamente, esto no funciona en el caso de bytearray. ¿Existe una forma más genérica de probar si un objeto es byteso bytearray, o debería simplemente verificar ambos? ¿Es hasattr('decode')tan malo como creo que sería?

A. Wilcox
fuente
6
Personalmente, me encanta escribir pato de Python tanto como cualquier otro tipo. Pero si necesita realizar comprobaciones en sus argumentos de entrada y coaccionar a diferentes tipos, entonces ya no estará escribiendo eludiendo: solo está haciendo que su código sea más difícil de leer y mantener. Mi sugerencia aquí (y otros pueden estar en desacuerdo) sería hacer múltiples funciones (que manejan la coerción de tipos y delegan a una implementación base).
mgilson
(1) A menos que lo necesite para compatibilidad con el código heredado de Python 2; Evite aceptar tanto texto como datos binarios simultáneamente. Si su función funciona con texto, solo debería aceptar str. Algún otro código debería convertir de bytes a Unicode en la entrada tan pronto como sea posible. (2) "tipo bytes" tiene un significado especial en Python (objetos que admiten el protocolo de búfer (solo C))
jfs
El principal problema es que esta función no funciona en Python 2, donde una simple cadena ASCII pasa la prueba de <bytes>.
Apostolos

Respuestas:

73

Hay algunos enfoques que puede utilizar aquí.

Tipeo de pato

Dado que Python se escribe pato , simplemente puede hacer lo siguiente (que parece ser la forma generalmente sugerida):

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

hasattrSin embargo, podría usarlo como lo describe, y probablemente estaría bien. Esto es, por supuesto, asumiendo que el .decode()método para el objeto dado devuelve una cadena y no tiene efectos secundarios desagradables.

Personalmente recomiendo la excepción o el hasattrmétodo, pero lo que sea que use depende de usted.

Utilice str ()

Este enfoque es poco común, pero es posible:

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

Se permiten otras codificaciones, al igual que con el protocolo de búfer .decode(). También puede pasar un tercer parámetro para especificar el manejo de errores.

Funciones genéricas de envío único (Python 3.4+)

Python 3.4 y superior incluyen una característica ingeniosa llamada funciones genéricas de envío único, a través de functools.singledispatch . Esto es un poco más detallado, pero también más explícito:

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

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

También puede hacer manipuladores bytearrayy bytesobjetos especiales si así lo desea.

Atención : las funciones de envío único solo funcionan en el primer argumento. Esta es una característica intencionada, consulte PEP 433 .

Elizafox
fuente
+1 por la mención de genéricos de envío único, que olvidé por completo de la biblioteca estándar proporcionada.
A. Wilcox
Como llamar str en str no hace nada, y me pareció lo más claro, seguí con eso.
A. Wilcox
en general me gusta hasattrmás que el try / excepto para evitar que te tragues accidentalmente algún error en la función de decodificación, pero +1.
keredson
37

Puedes usar:

isinstance(data, (bytes, bytearray))

Debido a la diferente clase base se utiliza aquí.

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

Verificar bytes

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

Sin embargo,

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

Los códigos anteriores se prueban en python 2.7

Desafortunadamente, en Python 3.4, son iguales ...

>>> bytes.__base__
<class 'object'>
>>> bytearray.__base__
<class 'object'>
zangw
fuente
1
six.string_types debe ser compatible con 2/3.
Joshua Olson
¡Este tipo de verificación no funciona en Python 2, donde una simple cadena ASCII pasa la prueba de <bytes>!
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
ZeroErr0r
fuente
Tenga en cuenta que esta no es una prueba confiable en Python 2 , donde un objeto de cadena pasa también como bytes. Es decir, según el código anterior, type(text) is bytesserá Verdadero.
Apostolos
11

Este código no es correcto a menos que sepa algo que nosotros no:

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

No (parece) conocer la codificación de data. Está asumiendo que es UTF-8 , pero eso podría estar mal. Como no conoce la codificación, no tiene texto . Tienes bytes, que podrían tener cualquier significado bajo el sol.

La buena noticia es que la mayoría de las secuencias aleatorias de bytes no son UTF-8 válidas, por lo que cuando esto se rompe, se romperá en voz alta ( errors='strict'es lo predeterminado) en lugar de hacer lo incorrecto en silencio. La mejor noticia es que la mayoría de esas secuencias aleatorias que resultan ser UTF-8 válidas también son ASCII válidas, que ( casi ) todos están de acuerdo en cómo analizar de todos modos.

La mala noticia es que no existe una forma razonable de solucionar este problema. Existe una forma estándar de proporcionar información de codificación: use en strlugar de bytes. Si algún código de terceros le entregó un objeto bytesu bytearraysin más contexto o información, la única acción correcta es fallar.


Ahora, suponiendo que conozca la codificación, puede usar functools.singledispatchaquí:

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

Esto no funciona con métodos y datadebe ser el primer argumento. Si esas restricciones no funcionan para usted, use una de las otras respuestas en su lugar.

Kevin
fuente
En la biblioteca que estoy escribiendo, para este método específico, definitivamente sé que los bytes y / o bytearray que estoy recibiendo están codificados en UTF-8.
A. Wilcox
1
@AndrewWilcox: Es justo, pero dejo esta información para el tráfico futuro de Google.
Kevin
4

Depende de lo que quieras resolver. Si desea tener el mismo código que convierta ambos casos en una cadena, simplemente puede convertir el tipo a bytesprimero y luego decodificar. De esta manera, es una sola línea:

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

De esta manera, la respuesta para ti puede ser:

data = bytes(data).decode()

De todos modos, sugiero escribir 'utf-8'explícitamente en la decodificación, si no le importa ahorrar algunos bytes. La razón es que la próxima vez que usted u otra persona lea el código fuente, la situación será más evidente.

pepr
fuente
3

Aquí hay dos preguntas y las respuestas son diferentes.

La primera pregunta, el título de esta publicación, es ¿Cuál es la forma correcta de determinar si un objeto es un objeto de tipo bytes en Python? Esto incluye una serie de tipos predefinidos ( bytes, bytearray, array.array, memoryview, otros?) Y posiblemente también los tipos definidos por el usuario. La mejor manera que conozco de verificar estos es intentar crear un memoryviewfuera de ellos:

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

Sin embargo, en el cuerpo de la publicación original, parece que la pregunta es ¿Cómo pruebo si un objeto admite decode ()? La respuesta anterior de @ elizabeth-myers a esta pregunta es excelente. Tenga en cuenta que no todos los objetos de tipo bytes admiten decode ().

Jack O'Connor
fuente
1
Tenga en cuenta que si hace esto, debe llamar .release()o usar la versión del administrador de contexto.
o11c
Creo que en CPython el temporal se memoryviewliberaría de inmediato y .release()se llamaría implícitamente. Pero estoy de acuerdo en que es mejor no confiar en eso, ya que no todas las implementaciones de Python cuentan con referencias.
Jack O'Connor
0

La prueba if isinstance(data, bytes)o if type(data) == bytes, etc. no funciona en Python 2, donde una simple cadena ASCII pasa la prueba de! Debido a que utilizo Python 2 y Python 3, para superar esto, hago la siguiente verificación:

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

Es un poco feo, pero hace el trabajo que pide la pregunta y siempre funciona, de la manera más simple.

Apostolos
fuente
Sin embargo, los strobjetos de Python2 son bytes : str is bytes-> Trueen Python2
snakecharmerb
Obviamente, ¡de ahí el problema de detección! :)
Apostolos