Wie lässt sich feststellen, ob ein Objekt in Python ein byteähnliches Objekt ist?

92

Ich habe Code, der erwartet, straber den Fall der Übergabe bytesfolgendermaßen behandelt:

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

Leider funktioniert dies bei nicht bytearray. Gibt es eine allgemeinere Möglichkeit zu testen, ob ein Objekt entweder bytesoder ist bytearray, oder sollte ich nur nach beiden suchen? Ist hasattr('decode')so schlimm, wie ich es mir vorstelle?

A. Wilcox
quelle
6
Persönlich liebe ich Pythons Ententipp genauso wie den nächsten. Wenn Sie jedoch Ihre Eingabeargumente überprüfen und zu verschiedenen Typen zwingen müssen, müssen Sie keine Enten mehr eingeben. Sie erschweren lediglich das Lesen und Verwalten Ihres Codes. Mein Vorschlag hier (und andere mögen anderer Meinung sein) wäre, mehrere Funktionen zu erstellen (die den Typenzwang behandeln und an eine Basisimplementierung delegieren).
mgilson
(1) Es sei denn, Sie benötigen es für die Kompatibilität mit altem Python 2-Code. Vermeiden Sie es, sowohl Text- als auch Binärdaten gleichzeitig zu akzeptieren. Wenn Ihre Funktion mit Text arbeitet, sollte sie nur akzeptieren str. Ein anderer Code sollte bei der Eingabe so schnell wie möglich von Bytes in Unicode konvertiert werden. (2) "bytes-like" hat eine besondere Bedeutung in Python (Objekte, die das Pufferprotokoll unterstützen (nur C))
jfs
Das Hauptproblem ist, dass diese Funktion in Python 2 nicht funktioniert, wo eine einfache ASCII-Zeichenfolge den Test von <Bytes> besteht!
Apostolos

Antworten:

74

Es gibt einige Ansätze, die Sie hier verwenden können.

Ente tippen

Da Python vom Typ Ente ist , können Sie einfach Folgendes tun (was normalerweise der vorgeschlagene Weg ist):

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

Sie könnten es jedoch so verwenden, hasattrwie Sie es beschreiben, und es wäre wahrscheinlich in Ordnung. Dies setzt natürlich voraus, dass die .decode()Methode für das angegebene Objekt eine Zeichenfolge zurückgibt und keine bösen Nebenwirkungen hat.

Ich persönlich empfehle entweder die Ausnahme oder die hasattrMethode, aber was auch immer Sie verwenden, liegt bei Ihnen.

Verwenden Sie str ()

Dieser Ansatz ist ungewöhnlich, aber möglich:

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

Andere Codierungen sind zulässig, genau wie bei den Pufferprotokollen .decode(). Sie können auch einen dritten Parameter übergeben, um die Fehlerbehandlung festzulegen.

Generische Single-Dispatch-Funktionen (Python 3.4+)

Python 3.4 und höher enthält über functools.singledispatch eine raffinierte Funktion, die als generische Single-Dispatch-Funktionen bezeichnet wird . Dies ist etwas ausführlicher, aber auch expliziter:

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

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

Sie können auch spezielle Handler für bytearrayund bytesObjekte erstellen , wenn Sie dies wünschen.

Achtung : Single-Dispatch-Funktionen funktionieren nur beim ersten Argument! Dies ist eine beabsichtigte Funktion, siehe PEP 433 .

Elizafox
quelle
+1 für die Erwähnung von Single-Dispatch-Generika, die ich in der bereitgestellten Standardbibliothek völlig vergessen habe.
A. Wilcox
Da das Aufrufen von str on str nichts bewirkt und mir am klarsten erschien, ging ich damit um.
A. Wilcox
Insgesamt mag ich hasattrmehr als den Versuch / außer zu verhindern, dass Sie versehentlich einen Fehler in der Dekodierungsfunktion verschlucken, aber +1.
Keredson
38

Sie können verwenden:

isinstance(data, (bytes, bytearray))

Aufgrund der unterschiedlichen Basisklasse wird hier verwendet.

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

Überprüfen bytes

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

Jedoch,

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

Die obigen Codes werden unter Python 2.7 getestet

Leider sind sie unter Python 3.4 gleich ....

>>> bytes.__base__
<class 'object'>
>>> bytearray.__base__
<class 'object'>
zangw
quelle
1
six.string_types sollte 2/3 kompatibel sein.
Joshua Olson
Diese Art der Überprüfung funktioniert nicht in Python 2, wo eine einfache ASCII-Zeichenfolge den Test von <Bytes> besteht!
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
quelle
Beachten Sie, dass dies kein zuverlässiger Test in Python 2 ist , bei dem ein Zeichenfolgenobjekt auch als Bytes übergeben wird! Das heißt, basierend auf dem obigen Code, type(text) is byteswird wahr sein!
Apostolos
11

Dieser Code ist nicht korrekt, es sei denn, Sie wissen etwas, was wir nicht wissen:

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

Sie kennen (anscheinend) die Kodierung von nicht data. Sie nehmen an, dass es UTF-8 ist , aber das könnte sehr gut falsch sein. Da Sie die Codierung nicht kennen, haben Sie keinen Text . Sie haben Bytes, die unter der Sonne jede Bedeutung haben könnten.

Die gute Nachricht ist, dass die meisten zufälligen Folgen von Bytes nicht für UTF-8 gültig sind. Wenn dies unterbrochen wird, wird es laut unterbrochen ( errors='strict'ist die Standardeinstellung), anstatt stillschweigend das Falsche zu tun. Die noch bessere Nachricht ist, dass die meisten dieser zufälligen Sequenzen, die zufällig UTF-8 sind, auch gültige ASCII-Sequenzen sind, denen ( fast ) alle zustimmen, wie sie sowieso analysiert werden sollen.

Die schlechte Nachricht ist, dass es keinen vernünftigen Weg gibt, dies zu beheben. Es gibt eine Standardmethode zum Bereitstellen von Codierungsinformationen: Verwenden Sie stranstelle von bytes. Wenn Ihnen ein Code eines Drittanbieters ein bytesoder ein bytearrayObjekt ohne weiteren Kontext oder Informationen übergeben hat, besteht die einzig richtige Aktion darin, fehlzuschlagen.


Angenommen, Sie kennen die Codierung, können Sie sie functools.singledispatchhier verwenden:

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

Dies funktioniert bei Methoden nicht und datamuss das erste Argument sein. Wenn diese Einschränkungen bei Ihnen nicht funktionieren, verwenden Sie stattdessen eine der anderen Antworten.

Kevin
quelle
In der Bibliothek, die ich für diese spezielle Methode schreibe, weiß ich definitiv, dass die Bytes und / oder Bytearrays, die ich empfange, UTF-8-codiert sind.
A. Wilcox
1
@ AndrewWilcox: Fair genug, aber ich lasse diese Informationen für den zukünftigen Google-Verkehr offen.
Kevin
4

Es kommt darauf an, was Sie lösen möchten. Wenn Sie denselben Code haben möchten, der beide Fälle in eine Zeichenfolge konvertiert, können Sie den Typ einfach zuerst konvertieren bytesund dann dekodieren. Auf diese Weise ist es ein Einzeiler:

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

Auf diese Weise könnte die Antwort für Sie sein:

data = bytes(data).decode()

Wie auch immer, ich schlage vor, 'utf-8'explizit in die Dekodierung zu schreiben , wenn Sie nicht wenige Bytes sparen möchten. Der Grund ist, dass das nächste Mal, wenn Sie oder jemand anderes den Quellcode lesen, die Situation offensichtlicher wird.

pepr
quelle
3

Hier gibt es zwei Fragen, und die Antworten darauf sind unterschiedlich.

Die erste Frage, der Titel dieses Beitrags, lautet: Wie lässt sich feststellen, ob ein Objekt in Python ein byteähnliches Objekt ist? Dazu gehört eine Reihe von eingebauten Typen ( bytes, bytearray, array.array, memoryview, andere?) Und eventuell auch Arten benutzerdefiniert. Der beste Weg, nach diesen zu suchen, besteht darin, zu versuchen, memoryviewaus ihnen ein zu erstellen :

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

Im Hauptteil des ursprünglichen Beitrags klingt die Frage jedoch wie folgt: Wie teste ich, ob ein Objekt decode () unterstützt? @ elizabeth-myers 'obige Antwort auf diese Frage ist großartig. Beachten Sie, dass nicht alle byteähnlichen Objekte decode () unterstützen.

Jack O'Connor
quelle
1
Beachten Sie, dass Sie in diesem .release()Fall die Kontextmanager-Version aufrufen oder verwenden müssen .
o11c
Ich denke, in CPython würde das Temporäre memoryviewsofort freigegeben und .release()implizit aufgerufen. Aber ich stimme zu, dass es am besten ist, sich nicht darauf zu verlassen, da nicht alle Python-Implementierungen mit Referenzen gezählt werden.
Jack O'Connor
0

Der Test if isinstance(data, bytes)oder if type(data) == bytesusw. funktioniert nicht in Python 2, wo eine einfache ASCII-Zeichenfolge den Test von! Besteht. Da ich sowohl Python 2 als auch Python 3 verwende, überprüfe ich Folgendes, um dies zu überwinden:

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

Es ist ein bisschen hässlich, aber es macht den Job, den die Frage stellt, und es funktioniert immer auf einfachste Weise.

Apostolos
quelle
Python2- strObjekte sind bytes jedoch: str is bytes-> Truein Python2
snakecharmerb
Offensichtlich daher das Erkennungsproblem! :)
Apostolos