オブジェクトがPythonでバイトのようなオブジェクトであるかどうかを判断する適切な方法は何ですか?


93

私は期待するstrbytes、次の方法で渡される場合を処理するコードを持っています:

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

残念ながら、これはの場合には機能しませんbytearray。オブジェクトがまたはのどちらであるかをテストするためのより一般的な方法はありますbytesbytearray、それとも両方をチェックする必要がありますか?でhasattr('decode')、私はそれは次のようになり感じるように悪いよう?


6
個人的には、次の人と同じくらいPythonのダックタイピングが大好きです。ただし、入力引数をチェックしてさまざまな型に強制変換する必要がある場合は、ダックタイピングを行う必要はありません。コードを維持するのが難しくなっているだけです。ここでの私の提案(および他の人は同意しないかもしれません)は、複数の関数(型強制を処理し、基本実装に委任する)を作成することです。
mgilson 2016年

(1)レガシーPython2コードとの互換性のために必要でない限り。テキストデータとバイナリデータの両方を同時に受け入れることは避けてください。関数がテキストで機能する場合は、のみを受け入れる必要がありstrます。他のコードは、入力時にできるだけ早くバイトからUnicodeに変換する必要があります。(2)「bytes-like」はPythonで特別な意味を持ちます(バッファプロトコルをサポートするオブジェクト(Cのみ))
jfs 2016年

主な問題は、この関数がPython 2で機能しないことです。この場合、単純なASCII文字列が<bytes>のテストに合格します。
アポストロス

回答:


75

ここで使用できるアプローチがいくつかあります。

ダックタイピング

Pythonはダックタイピングされているので、次のように簡単に行うことができます(これは通常提案されている方法のようです)。

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

あなたがhasattr説明するようにあなたは使うことができます、そしてそれはおそらく大丈夫でしょう。もちろん、これは、.decode()指定されたオブジェクトのメソッドが文字列を返し、厄介な副作用がないことを前提としています。

個人的には例外かhasattr方法のどちらかをお勧めしますが、使用するものはすべてあなた次第です。

str()を使用する

このアプローチは一般的ではありませんが、可能です。

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

バッファプロトコルのと同様に、他のエンコーディングも許可されます.decode()。3番目のパラメーターを渡して、エラー処理を指定することもできます。

シングルディスパッチジェネリック関数(Python 3.4以降)

Python 3.4以降には、functools.singledispatchを介したシングルディスパッチジェネリック関数と呼ばれる気の利いた機能が含まれています。これはもう少し冗長ですが、より明確でもあります。

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

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

必要に応じてbytearraybytesオブジェクトの特別なハンドラーを作成することもできます。

注意:シングルディスパッチ関数は最初の引数でのみ機能します!これは意図的な機能です。PEP433を参照してください。


提供された標準ライブラリを完全に忘れてしまったシングルディスパッチジェネリックについての言及は+1。
A. Wilcox

strでstrを呼び出しても何も起こらず、私には最も明確に思えたので、それを使用しました。
A. Wilcox

全体的hasattrに、デコード機能のバグを誤って飲み込むのを防ぐために、try / exceptよりも好きですが+1です。
keredson 2017年

39

次を使用できます。

isinstance(data, (bytes, bytearray))

基本クラスが異なるため、ここでは使用されます。

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

チェックする bytes

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

しかしながら、

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

上記のコードはPython2.7でテストされています

残念ながら、Python 3.4では、それらは同じです。

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

1
six.string_typesは2/3互換である必要があります。
Joshua Olson

この種のチェックは、単純なASCII文字列が<bytes>のテストに合格するPython2では機能しません。
アポストロス

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

これは、文字列オブジェクトがバイトとしても渡されるPython2での信頼できるテストではないことに注意してください。つまり、上記のコードに基づいて、type(text) is bytesTrueになります!
アポストロス

11

私たちが知らないことを知らない限り、このコードは正しくありません。

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

あなたはのエンコーディングを知らない(見える)data。あなたはそれがUTF-8だと思っますが、それは非常に間違っている可能性があります。エンコーディングがわからないため、テキストはありません。あなたはバイトを持っています、それは太陽の下でどんな意味も持つことができます。

幸いなことに、ほとんどのランダムなバイトシーケンスは有効なUTF-8ではないため、これが壊れると、errors='strict'静かに間違ったことをするのではなく、大声で壊れます(デフォルトです)。さらに良いニュースは、たまたま有効なUTF-8であるランダムシーケンスのほとんどが有効なASCIIでもあり、(ほぼ)誰もがとにかく解析する方法に同意していることです。

悪いニュースは、これを修正する合理的な方法がないということです。エンコーディング情報を提供する標準的な方法があります:のstr代わりに使用しますbytes。サードパーティのコードがコンテキストや情報なしでbytesまたはbytearrayオブジェクトを渡した場合、正しいアクションは失敗することだけです。


ここで、エンコーディングを知っていると仮定して、functools.singledispatchここで使用できます。

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

これはメソッドでは機能せずdata、最初の引数である必要があります。これらの制限が機能しない場合は、代わりに他の回答のいずれかを使用してください。


私が書いているライブラリでは、この特定のメソッドについて、受信しているバイトやバイト配列がUTF-8でエンコードされていることを確実に知っています。
A. Wilcox

1
@AndrewWilcox:十分に公平ですが、この情報は今後のGoogleトラフィックのために残しておきます。
ケビン

4

それはあなたが何を解決したいかによります。両方のケースを文字列に変換する同じコードが必要な場合は、タイプをbytes最初に変換してからデコードするだけです。このように、それはワンライナーです:

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

このように、あなたのための答えは次のようになります:

data = bytes(data).decode()

とにかく、'utf-8'数バイトを節約する必要がない場合は、デコードに明示的に書き込むことをお勧めします。その理由は、次にあなたや他の誰かがソースコードを読むときに、状況がより明らかになるからです。


3

ここには2つの質問があり、それらに対する答えは異なります。

この投稿のタイトルである最初の質問は、オブジェクトがPythonでバイトのようなオブジェクトであるかどうかを判断する適切な方法何ですか?これは、(組み込み型の数含むbytesbytearrayarray.arraymemoryview?、その他)、おそらく、ユーザ定義型。これらをチェックするために私が知っている最良の方法は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'

ただし、元の投稿の本文では、代わりに質問のように聞こえます。オブジェクトがdecode()をサポートしているかどうかをテストするにはどうすればよいですか?@ elizabeth-この質問に対するマイヤーズの上記の回答は素晴らしいです。すべてのバイトのようなオブジェクトがdecode()をサポートしているわけではないことに注意してください。


1
これを行う場合は、を呼び出す.release()か、コンテキストマネージャーバージョンを使用する必要があることに注意してください。
o11c

CPythonでは、一時的なものmemoryviewはすぐに解放され、.release()暗黙的に呼び出されると思います。ただし、すべてのPython実装が参照カウントされるわけではないため、これに依存しないことが最善であることに同意します。
ジャックオコナー

0

テストif isinstance(data, bytes)if type(data) == bytesなどは、単純なASCII文字列が!のテストに合格するPython2では機能しません。私はPython2とPython3の両方を使用しているため、これを克服するために次のチェックを行います。

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

それは少し醜いですが、それは質問が尋ねる仕事をし、最も簡単な方法で常に機能します。


Python2のstrオブジェクトはある bytesものの:str is bytes- > TruePython2に
snakecharmerb

明らかに、したがって、検出の問題!:)
アポストロス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.