Apa cara yang tepat untuk menentukan apakah suatu objek adalah objek seperti byte di Python?

90

Saya memiliki kode yang mengharapkan strtetapi akan menangani kasus yang diteruskan bytesdengan cara berikut:

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

Sayangnya, ini tidak berhasil dalam kasus bytearray. Apakah ada cara yang lebih umum untuk menguji apakah suatu objek adalah salah satu bytesatau bytearray, atau haruskah saya memeriksa keduanya? Apakah hasattr('decode')seburuk yang saya rasakan?

A. Wilcox
sumber
6
Secara pribadi, saya suka mengetik bebek python sama seperti orang berikutnya. Tetapi jika Anda perlu melakukan pemeriksaan pada argumen masukan Anda dan memaksa jenis yang berbeda, maka Anda tidak lagi bebek mengetik - Anda hanya membuat kode Anda lebih sulit untuk membaca pemeliharaan. Saran saya di sini (dan orang lain mungkin tidak setuju) akan membuat beberapa fungsi (yang menangani pemaksaan tipe dan mendelegasikan ke implementasi dasar).
mgilson
(1) Kecuali Anda memerlukannya untuk kompatibilitas dengan kode Python 2 lama; hindari menerima teks dan data biner secara bersamaan. Jika fungsi Anda berfungsi dengan teks, maka itu harus menerima saja str. Beberapa kode lain harus dikonversi dari byte ke Unicode pada input secepat mungkin. (2) "seperti byte" memiliki arti khusus dalam Python (objek yang mendukung protokol buffer (hanya C))
jfs
Masalah utamanya adalah bahwa fungsi ini tidak berfungsi di Python 2, di mana string ASCII sederhana melewati pengujian <bytes>!
Apostolos

Jawaban:

73

Ada beberapa pendekatan yang dapat Anda gunakan di sini.

Mengetik bebek

Karena Python adalah tipe bebek , Anda dapat melakukan hal berikut (yang sepertinya biasanya disarankan):

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

Anda bisa menggunakan hasattrapa yang Anda gambarkan, bagaimanapun, dan itu mungkin akan baik-baik saja. Ini, tentu saja, dengan asumsi .decode()metode untuk objek yang diberikan mengembalikan string, dan tidak memiliki efek samping yang buruk.

Saya pribadi merekomendasikan pengecualian atau hasattrmetode, tetapi apa pun yang Anda gunakan terserah Anda.

Gunakan str ()

Pendekatan ini tidak umum, tetapi mungkin:

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

Pengkodean lain diizinkan, seperti halnya dengan protokol buffer .decode(). Anda juga dapat memberikan parameter ketiga untuk menentukan penanganan kesalahan.

Fungsi umum pengiriman tunggal (Python 3.4+)

Python 3.4 dan yang lebih baru menyertakan fitur bagus yang disebut fungsi generik pengiriman tunggal, melalui functools.singledispatch . Ini sedikit lebih bertele-tele, tetapi juga lebih eksplisit:

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

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

Anda juga dapat membuat penangan khusus untuk bytearraydan bytesobjek jika Anda mau.

Hati-hati : fungsi pengiriman tunggal hanya berfungsi pada argumen pertama! Ini adalah fitur yang disengaja, lihat PEP 433 .

Elizafox
sumber
1 untuk menyebutkan generik pengiriman tunggal, yang saya benar-benar lupa perpustakaan standar yang disediakan.
A.Wilcox
Sejak memanggil str pada str tidak melakukan apa-apa, dan menurut saya yang paling jelas, saya pergi dengan itu.
A.Wilcox
secara keseluruhan saya hasattrlebih suka daripada mencoba / kecuali untuk mencegah Anda secara tidak sengaja menelan beberapa bug dalam fungsi decode, tetapi +1.
keredson
37

Kamu bisa memakai:

isinstance(data, (bytes, bytearray))

Karena kelas dasar yang berbeda digunakan di sini.

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

Untuk memeriksa bytes

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

Namun,

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

Kode di atas diuji di bawah python 2.7

Sayangnya, di bawah python 3.4, keduanya sama ....

>>> bytes.__base__
<class 'object'>
>>> bytearray.__base__
<class 'object'>
zangw
sumber
1
six.string_types harus kompatibel 2/3.
Joshua Olson
Pemeriksaan semacam ini tidak berfungsi di Python 2, di mana string ASCII sederhana lulus pengujian <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
sumber
Perhatikan bahwa ini bukan pengujian yang dapat diandalkan di Python 2 , di mana objek string juga diteruskan sebagai byte! Artinya, berdasarkan kode di atas, type(text) is bytesakan Benar!
Apostolos
11

Kode ini tidak benar kecuali Anda mengetahui sesuatu yang tidak kami ketahui:

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

Anda tidak (tampaknya) mengetahui pengkodean data. Anda berasumsi bahwa itu UTF-8 , tetapi itu bisa jadi salah. Karena Anda tidak tahu pengkodeannya, Anda tidak memiliki teks . Anda memiliki byte, yang bisa memiliki arti apa pun di bawah matahari.

Kabar baiknya adalah bahwa sebagian besar urutan byte yang acak bukanlah UTF-8 yang valid, jadi ketika ini rusak, itu akan rusak dengan keras ( errors='strict'adalah default) daripada diam-diam melakukan hal yang salah. Berita yang lebih baik lagi adalah bahwa sebagian besar urutan acak yang kebetulan merupakan UTF-8 yang valid juga merupakan ASCII yang valid, yang ( hampir ) semua orang setuju tentang cara mengurai.

Kabar buruknya adalah tidak ada cara yang masuk akal untuk memperbaikinya. Ada cara standar untuk memberikan informasi pengkodean: gunakan strsebagai ganti bytes. Jika beberapa kode pihak ketiga memberi Anda objek bytesatau bytearraytanpa konteks atau informasi lebih lanjut, satu-satunya tindakan yang benar adalah gagal.


Sekarang, dengan asumsi Anda mengetahui pengkodeannya, Anda dapat menggunakan di functools.singledispatchsini:

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

Ini tidak berfungsi pada metode, dan dataharus menjadi argumen pertama. Jika batasan tersebut tidak berhasil untuk Anda, gunakan salah satu jawaban lainnya.

Kevin
sumber
Di perpustakaan yang saya tulis, untuk metode khusus ini, saya pasti tahu bahwa byte dan / atau bytearray yang saya terima adalah dikodekan UTF-8.
A. Wilcox
1
@AndrewWilcox: Cukup adil, tetapi saya meninggalkan informasi ini untuk lalu lintas Google di masa mendatang.
Kevin
4

Itu tergantung apa yang ingin Anda selesaikan. Jika Anda ingin memiliki kode yang sama yang mengonversi kedua kasus menjadi string, Anda cukup mengonversi tipe menjadi bytespertama, lalu mendekodekan. Dengan cara ini, ini adalah satu baris:

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

Dengan cara ini, jawaban untuk Anda mungkin:

data = bytes(data).decode()

Bagaimanapun, saya menyarankan untuk menulis 'utf-8'secara eksplisit ke decode, jika Anda tidak peduli untuk menyisihkan beberapa byte. Alasannya adalah saat Anda atau orang lain membaca kode sumbernya, situasinya akan lebih jelas.

pepr
sumber
3

Ada dua pertanyaan di sini, dan jawabannya berbeda.

Pertanyaan pertama, judul posting ini, adalah Apa cara yang tepat untuk menentukan apakah suatu objek adalah objek seperti byte dengan Python? Ini mencakup sejumlah built-in jenis ( bytes, bytearray, array.array, memoryview, orang lain?) Dan mungkin juga jenis yang ditetapkan pengguna. Cara terbaik yang saya ketahui untuk memeriksanya adalah dengan mencoba membuatnya 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'

Namun, di badan postingan asli, sepertinya pertanyaannya adalah Bagaimana cara menguji apakah suatu objek mendukung decode ()? Jawaban @ elizabeth-myers di atas untuk pertanyaan ini sangat bagus. Perhatikan bahwa tidak semua objek mirip byte mendukung decode ().

Jack O'Connor
sumber
1
Perhatikan bahwa jika Anda melakukan ini, Anda harus memanggil .release(), atau menggunakan versi pengelola konteks.
o11c
Saya pikir di CPython sementara memoryviewitu akan segera dibebaskan dan .release()akan dipanggil secara implisit. Tetapi saya setuju bahwa yang terbaik adalah tidak mengandalkan itu, karena tidak semua implementasi Python dihitung berdasarkan referensi.
Jack O'Connor
0

Tes if isinstance(data, bytes)atau if type(data) == bytes, dll. Tidak berfungsi di Python 2, di mana string ASCII sederhana lolos tes! Karena saya menggunakan Python 2 dan Python 3, untuk mengatasinya, saya melakukan pengecekan berikut:

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

Ini sedikit jelek, tapi itu melakukan pekerjaan yang diminta dan selalu berhasil, dengan cara yang paling sederhana.

Apostolos
sumber
strObjek Python2 adalah bytes : str is bytes-> Truedengan Python2
snakecharmerb
Jelas, itulah masalah pendeteksiannya! :)
Apostolos