Cách thích hợp để xác định xem một đối tượng có phải là một đối tượng giống byte trong Python không?


90

Tôi có mã mong đợi strnhưng sẽ xử lý trường hợp được chuyển bytestheo cách sau:

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

Thật không may, điều này không hoạt động trong trường hợp của bytearray. Có cách nào chung chung hơn để kiểm tra xem một đối tượng là byteshoặc bytearray, hay tôi chỉ nên kiểm tra cả hai? Nó có hasattr('decode')tệ như tôi cảm thấy không?


6
Cá nhân tôi, tôi yêu tiếng gõ vịt của python nhiều như anh chàng tiếp theo. Nhưng nếu bạn cần phải kiểm tra các đối số đầu vào của mình và ép buộc các loại khác nhau, thì bạn không còn phải gõ phím nữa - Bạn chỉ đang làm cho mã của mình khó đọc hơn. Đề xuất của tôi ở đây (và những người khác có thể không đồng ý) sẽ là tạo nhiều hàm (xử lý kiểu ép buộc và ủy quyền cho một triển khai cơ sở).
mgilson

(1) Trừ khi bạn cần nó để tương thích với mã Python 2 kế thừa; tránh chấp nhận cả dữ liệu văn bản và dữ liệu nhị phân một cách mô phỏng. Nếu hàm của bạn hoạt động với văn bản thì nó chỉ chấp nhận str. Một số mã khác nên chuyển đổi từ byte sang Unicode khi đầu vào càng sớm càng tốt. (2) "byte-like" có một ý nghĩa đặc biệt trong Python (các đối tượng hỗ trợ giao thức bộ đệm (chỉ C))
jfs 22/01

Vấn đề chính là hàm này không hoạt động trong Python 2, trong đó một chuỗi ASCII đơn giản vượt qua kiểm tra của <byte>!
Apostolos

Câu trả lời:


73

Có một số cách tiếp cận bạn có thể sử dụng ở đây.

Gõ vịt

Vì Python là kiểu con vịt , bạn có thể đơn giản làm như sau (có vẻ là cách thường được đề xuất):

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

hasattrTuy nhiên, bạn có thể sử dụng như bạn mô tả, và nó có thể ổn. Tất nhiên, đây là giả sử .decode()phương thức cho đối tượng đã cho trả về một chuỗi và không có tác dụng phụ khó chịu.

Cá nhân tôi khuyên bạn nên sử dụng ngoại lệ hoặc hasattrphương pháp, nhưng bất cứ điều gì bạn sử dụng là tùy thuộc vào bạn.

Sử dụng str ()

Cách tiếp cận này không phổ biến, nhưng có thể:

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

Các mã hóa khác được cho phép, giống như với giao thức đệm .decode(). Bạn cũng có thể truyền tham số thứ ba để chỉ định xử lý lỗi.

Các hàm chung đơn gửi (Python 3.4+)

Python 3.4 trở lên bao gồm một tính năng tiện lợi được gọi là các hàm chung gửi đơn, thông qua functools.singledispatch . Điều này dài dòng hơn một chút, nhưng cũng rõ ràng hơn:

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

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

Bạn cũng có thể tạo các trình xử lý đặc biệt cho bytearraybytescác đối tượng nếu bạn chọn.

Hãy lưu ý : các hàm gửi đơn chỉ hoạt động trên đối số đầu tiên! Đây là một tính năng có chủ đích, xem PEP 433 .


+1 khi đề cập đến các generic gửi đơn, mà tôi hoàn toàn quên mất thư viện chuẩn được cung cấp.
A. Wilcox

Kể từ khi gọi str trên str không làm gì cả, và dường như với tôi là rõ ràng nhất, tôi đã đi với điều đó.
A. Wilcox

Nhìn chung, tôi thích hasattrnhiều hơn là thử / ngoại trừ để ngăn bạn vô tình nuốt phải một số lỗi trong chức năng giải mã, nhưng +1.
keredson

37

Bạn có thể dùng:

isinstance(data, (bytes, bytearray))

Do lớp cơ sở khác nhau được sử dụng ở đây.

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

Để kiểm tra bytes

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

Tuy nhiên,

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

Các mã trên được kiểm tra trong python 2.7

Thật không may, dưới python 3.4, chúng giống nhau ...

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

1
six.string_types phải tương thích 2/3.
Joshua Olson

Loại kiểm tra này không hoạt động trong Python 2, trong đó một chuỗi ASCII đơn giản vượt qua kiểm tra <byte>!
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

Lưu ý rằng đây không phải là một bài kiểm tra đáng tin cậy trong Python 2 , trong đó một đối tượng chuỗi cũng truyền dưới dạng byte! Tức là, dựa trên đoạn mã trên, type(text) is bytessẽ là True!
Apostolos

11

Mã này không đúng trừ khi bạn biết điều gì đó mà chúng tôi không:

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

Bạn không (dường như) biết mã hóa của data. Bạn đang giả định đó là UTF-8 , nhưng điều đó rất có thể sai. Vì bạn không biết mã hóa, bạn không có văn bản . Bạn có các byte, có thể có bất kỳ ý nghĩa nào dưới ánh mặt trời.

Tin tốt là hầu hết các chuỗi byte ngẫu nhiên không phải là UTF-8 hợp lệ, vì vậy khi điều này bị phá vỡ, nó sẽ phá vỡ lớn ( errors='strict'là mặc định) thay vì âm thầm làm điều sai trái. Tin tốt hơn nữa là hầu hết các chuỗi ngẫu nhiên đó là UTF-8 hợp lệ cũng là ASCII hợp lệ, mà ( gần như ) mọi người đều đồng ý về cách phân tích cú pháp.

Tin xấu là không có cách nào hợp lý để sửa lỗi này. Có một cách tiêu chuẩn để cung cấp thông tin mã hóa: sử dụng strthay vì bytes. Nếu một số mã của bên thứ ba chuyển cho bạn một byteshoặc bytearrayđối tượng mà không có bất kỳ ngữ cảnh hoặc thông tin nào khác, thì hành động đúng duy nhất là không thành công.


Bây giờ, giả sử bạn biết mã hóa, bạn có thể sử dụng functools.singledispatchở đây:

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

Điều này không hoạt động trên các phương thức và dataphải là đối số đầu tiên. Nếu những hạn chế đó không phù hợp với bạn, hãy sử dụng một trong các câu trả lời khác để thay thế.


Trong thư viện tôi đang viết, đối với phương pháp cụ thể này, tôi chắc chắn biết rằng các byte và / hoặc bytearray mà tôi đang nhận được mã hóa UTF-8.
A. Wilcox

1
@AndrewWilcox: Khá ổn, nhưng tôi vẫn để thông tin này cho lưu lượng truy cập Google trong tương lai.
Kevin

4

Nó phụ thuộc vào những gì bạn muốn giải quyết. Nếu bạn muốn có cùng một mã chuyển đổi cả hai trường hợp thành một chuỗi, bạn có thể chỉ cần chuyển đổi loại thành bytesđầu tiên, sau đó giải mã. Bằng cách này, nó là một lớp lót:

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

Bằng cách này, câu trả lời cho bạn có thể là:

data = bytes(data).decode()

Dù sao, tôi khuyên bạn nên viết 'utf-8'rõ ràng vào bộ giải mã, nếu bạn không quan tâm đến việc dành một vài byte. Lý do là lần sau khi bạn hoặc người khác đọc mã nguồn, tình hình sẽ rõ ràng hơn.


3

Có hai câu hỏi ở đây, và câu trả lời cho chúng là khác nhau.

Câu hỏi đầu tiên, tiêu đề của bài đăng này, là Cách thích hợp để xác định xem một đối tượng có phải là một đối tượng giống byte trong Python hay không? Điều này bao gồm một số được xây dựng trong các loại ( bytes, bytearray, array.array, memoryview, những người khác?) Và có thể cũng kiểu do người dùng định nghĩa. Cách tốt nhất tôi biết để kiểm tra những điều này là cố gắng tạo ra một memoryviewtrong số chúng:

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

Tuy nhiên, trong phần nội dung của bài đăng gốc, có vẻ như câu hỏi đặt ra là Làm cách nào để kiểm tra xem một đối tượng có hỗ trợ decode () hay không? Câu trả lời ở trên của @ elizabeth-myers cho câu hỏi này thật tuyệt. Lưu ý rằng không phải tất cả các đối tượng giống byte đều hỗ trợ decode ().


1
Lưu ý rằng nếu bạn làm điều này, bạn phải gọi .release()hoặc sử dụng phiên bản trình quản lý ngữ cảnh.
o11c

Tôi nghĩ trong CPython, tạm thời memoryviewsẽ được giải phóng ngay lập tức và .release()sẽ được gọi ngầm. Nhưng tôi đồng ý rằng tốt nhất là không nên dựa vào điều đó, vì không phải tất cả các triển khai Python đều được tính là tham chiếu.
Jack O'Connor

0

Kiểm tra if isinstance(data, bytes)hoặc if type(data) == bytes, v.v. không hoạt động trong Python 2, trong đó một chuỗi ASCII đơn giản vượt qua kiểm tra của! Vì tôi sử dụng cả Python 2 và Python 3, để khắc phục điều này, tôi thực hiện kiểm tra sau:

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

Nó hơi xấu một chút, nhưng nó thực hiện công việc mà câu hỏi yêu cầu và nó luôn hoạt động, theo cách đơn giản nhất.


Python2 strđối tượng bytes mặc dù: str is bytes-> Truetrong python2
snakecharmerb

Rõ ràng, do đó vấn đề phát hiện! :)
Apostolos
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.