Qual é a maneira adequada de determinar se um objeto é um objeto semelhante a bytes em Python?


93

Eu tenho um código que espera, strmas vai lidar com o caso de ser passado bytesda seguinte maneira:

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

Infelizmente, isso não funciona no caso de bytearray. Existe uma maneira mais genérica de testar se um objeto é bytesou bytearray, ou devo apenas verificar os dois? É hasattr('decode')tão ruim quanto eu sinto que seria?


6
Pessoalmente, adoro a digitação de pato do python tanto quanto qualquer outro cara. Mas se você precisa fazer verificações em seus argumentos de entrada e coagir para diferentes tipos, então você não está mais se esquivando de digitar - você está apenas tornando seu código mais difícil de ler e manter. Minha sugestão aqui (e outros podem discordar) seria fazer várias funções (que lidam com a coerção de tipo e delegam para uma implementação básica).
mgilson

(1) A menos que você precise para compatibilidade com o código Python 2 legado; evite aceitar texto e dados binários simultaneamente. Se sua função trabalhar com texto, ela deve aceitar apenas str. Algum outro código deve ser convertido de bytes para Unicode na entrada o mais rápido possível. (2) "semelhante a bytes" tem um significado especial em Python (objetos que suportam o protocolo de buffer (apenas C))
jfs

O principal problema é que esta função não funciona no Python 2, onde uma string ASCII simples passa no teste de <bytes>!
Apostolos

Respostas:


76

Existem algumas abordagens que você pode usar aqui.

Digitação de pato

Como o Python é digitado em pato , você poderia simplesmente fazer o seguinte (que parece ser a forma geralmente sugerida):

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

Você poderia usar hasattrcomo descreve, no entanto, e provavelmente seria bom. Isso, é claro, assumindo que o .decode()método para o objeto fornecido retorna uma string e não tem efeitos colaterais desagradáveis.

Eu pessoalmente recomendo a exceção ou o hasattrmétodo, mas o que quer que você use é com você.

Use str ()

Essa abordagem é incomum, mas é possível:

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

Outras codificações são permitidas, assim como com o protocolo de buffer .decode(). Você também pode passar um terceiro parâmetro para especificar o tratamento de erros.

Funções genéricas de despacho único (Python 3.4+)

Python 3.4 e superior incluem um recurso bacana chamado funções genéricas de despacho único, via functools.singledispatch . Isso é um pouco mais detalhado, mas também é mais explícito:

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

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

Você também pode fazer manipuladores especiais para objetos bytearraye bytes, se assim desejar.

Cuidado : as funções de despacho único funcionam apenas no primeiro argumento! Este é um recurso intencional, consulte PEP 433 .


+1 para a menção de genéricos de despacho único, que esqueci completamente da biblioteca padrão fornecida.
A. Wilcox

Como chamar str on str não adianta nada, e me pareceu o mais claro, aceitei.
A. Wilcox

no geral eu gosto hasattrmais do que tentar / exceto para evitar que você engula acidentalmente algum bug na função de decodificação, mas +1.
keredson

39

Você pode usar:

isinstance(data, (bytes, bytearray))

Devido à classe base diferente é usada aqui.

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

Checar bytes

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

Contudo,

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

Os códigos acima são testados em python 2.7

Infelizmente, no python 3.4, eles são os mesmos ....

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

1
six.string_types deve ser compatível com 2/3.
Joshua Olson

Esse tipo de verificação não funciona no Python 2, onde uma string ASCII simples passa no teste de <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

Observe que este não é um teste confiável no Python 2 , onde um objeto string passa também como bytes! Ou seja, com base no código acima, type(text) is bytesserá True!
Apostolos

11

Este código não está correto a menos que você saiba algo que não sabemos:

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

Você não (parece) saber a codificação de data. Você está assumindo que é UTF-8 , mas isso pode muito bem estar errado. Como você não sabe a codificação, não tem texto . Você tem bytes, que podem ter algum significado sob o sol.

A boa notícia é que a maioria das sequências aleatórias de bytes não são UTF-8 válidas, portanto, quando isso for interrompido, será interrompido ruidosamente ( errors='strict'é o padrão) em vez de fazer silenciosamente a coisa errada. A notícia ainda melhor é que a maioria dessas sequências aleatórias que por acaso são UTF-8 válidas também são ASCII válidas, que ( quase ) todos concordam em como analisar de qualquer maneira.

A má notícia é que não existe uma maneira razoável de corrigir isso. Existe uma maneira padrão de fornecer informações de codificação: use em strvez de bytes. Se algum código de terceiros entregou a você um objeto bytesou bytearraysem qualquer contexto ou informação adicional, a única ação correta é falhar.


Agora, supondo que você conheça a codificação, você pode usar functools.singledispatchaqui:

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

Isso não funciona em métodos e datadeve ser o primeiro argumento. Se essas restrições não funcionarem para você, use uma das outras respostas.


Na biblioteca que estou escrevendo, para este método específico, eu definitivamente sei que os bytes e / ou bytearray que estou recebendo são codificados em UTF-8.
A. Wilcox

1
@AndrewWilcox: É justo, mas estou deixando essas informações para o futuro tráfego do Google.
Kevin

4

Depende do que você deseja resolver. Se você deseja que o mesmo código que converta ambos os casos em uma string, você pode simplesmente converter o tipo em bytesprimeiro e depois decodificar. Dessa forma, é uma linha:

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

Dessa forma, a resposta para você pode ser:

data = bytes(data).decode()

De qualquer forma, sugiro escrever 'utf-8'explicitamente no decodificador, se você não se importar em poupar alguns bytes. O motivo é que da próxima vez que você ou outra pessoa ler o código-fonte, a situação ficará mais aparente.


3

Existem duas perguntas aqui, e as respostas a elas são diferentes.

A primeira pergunta, o título desta postagem, é: Qual é a maneira adequada de determinar se um objeto é um objeto semelhante a bytes em Python? Isto inclui uma série de built-in tipos ( bytes, bytearray, array.array, memoryview, outros?) E possivelmente também tipos definidos pelo usuário. A melhor maneira que conheço de verificar isso é tentar criar um a memoryviewpartir deles:

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

No corpo da postagem original, porém, parece que a pergunta é: Como faço para testar se um objeto oferece suporte a decode ()? A resposta acima de @elizabeth-myers a esta pergunta é excelente. Observe que nem todos os objetos semelhantes a bytes suportam decode ().


1
Observe que, se você fizer isso, deverá chamar .release()ou usar a versão do gerenciador de contexto.
o11c

Acho que no CPython o temporário memoryviewseria liberado imediatamente e .release()seria chamado implicitamente. Mas concordo que é melhor não confiar nisso, já que nem todas as implementações Python são contadas por referência.
Jack O'Connor

0

O teste if isinstance(data, bytes)ou if type(data) == bytesetc. não funciona no Python 2, onde uma string ASCII simples passa no teste de! Como uso Python 2 e Python 3, para resolver isso, faço a seguinte verificação:

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

É um pouco feio, mas cumpre o que a pergunta pede e sempre funciona, da maneira mais simples.


Os strobjetos Python2 são bytes : str is bytes-> Trueem Python2
snakecharmerb

Obviamente, daí o problema de detecção! :)
Apostolos
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.