อะไรคือวิธีที่เหมาะสมในการตรวจสอบว่าวัตถุเป็นวัตถุคล้ายไบต์ใน Python หรือไม่


93

ฉันมีรหัสที่คาดหวังstrแต่จะจัดการกรณีที่ถูกส่งผ่านbytesด้วยวิธีต่อไปนี้:

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

น่าเสียดายที่ไม่สามารถใช้งานได้ในกรณีbytearrayนี้ มีวิธีทั่วไปมากกว่านี้ในการทดสอบว่าวัตถุเป็นอย่างใดอย่างหนึ่งbytesหรือbytearrayหรือฉันควรตรวจสอบทั้งสองอย่าง คือhasattr('decode')ไม่ดีเท่าที่ผมรู้สึกว่ามันจะเป็นอย่างไร


6
โดยส่วนตัวแล้วฉันรักการพิมพ์เป็ดของหลามมากพอ ๆ กับผู้ชายคนต่อไป แต่ถ้าคุณต้องทำการตรวจสอบอาร์กิวเมนต์ที่คุณป้อนและบังคับไปยังประเภทต่างๆคุณจะไม่ต้องพิมพ์ผิดอีกต่อไปคุณแค่ทำให้โค้ดของคุณอ่านการบำรุงรักษาได้ยากขึ้น ข้อเสนอแนะของฉันที่นี่ (และคนอื่น ๆ อาจไม่เห็นด้วย) คือการสร้างฟังก์ชันหลายอย่าง (ซึ่งจัดการกับการบังคับประเภทและมอบหมายให้นำไปใช้งานพื้นฐาน)
mgilson

(1) เว้นแต่คุณต้องการเพื่อให้เข้ากันได้กับรหัส Python 2 แบบเดิม หลีกเลี่ยงการยอมรับทั้งข้อความและข้อมูลไบนารีพร้อมกัน strหากฟังก์ชั่นการทำงานของที่มีข้อความแล้วมันควรจะยอมรับเฉพาะ รหัสอื่น ๆ ควรแปลงจากไบต์เป็น Unicode เมื่อป้อนข้อมูลโดยเร็วที่สุด (2) "ไบต์เหมือน" มีความหมายพิเศษใน Python (วัตถุที่รองรับโปรโตคอลบัฟเฟอร์ (C เท่านั้น))
jfs

ปัญหาหลักคือฟังก์ชันนี้ใช้ไม่ได้ใน Python 2 โดยที่สตริง ASCII ธรรมดาผ่านการทดสอบ <bytes>!
Apostolos

คำตอบ:


75

มีแนวทางบางอย่างที่คุณสามารถใช้ได้ที่นี่

พิมพ์เป็ด

เนื่องจาก Python เป็นรูปเป็ดคุณสามารถทำได้ดังนี้ (ซึ่งดูเหมือนจะเป็นวิธีที่แนะนำโดยทั่วไป):

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

อย่างไรก็ตามคุณสามารถใช้hasattrตามที่คุณอธิบายและมันอาจจะดี แน่นอนว่านี่คือสมมติว่า.decode()วิธีการสำหรับวัตถุที่กำหนดส่งคืนสตริงและไม่มีผลข้างเคียงที่น่ารังเกียจ

ฉันขอแนะนำข้อยกเว้นหรือhasattrวิธีการเป็นการส่วนตัว แต่สิ่งที่คุณใช้ขึ้นอยู่กับคุณ

ใช้ str ()

วิธีนี้เป็นวิธีที่ผิดปกติ แต่เป็นไปได้:

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

การเข้ารหัสอื่น ๆ .decode()จะได้รับอนุญาตเช่นเดียวกับของบัฟเฟอร์โปรโตคอล คุณยังสามารถส่งผ่านพารามิเตอร์ที่สามเพื่อระบุการจัดการข้อผิดพลาด

ฟังก์ชั่นทั่วไปแบบ Single-dispatch (Python 3.4+)

งูหลาม 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
    ...

คุณยังสามารถสร้างตัวจัดการพิเศษสำหรับbytearrayและbytesวัตถุได้หากคุณเลือกเช่นนั้น

ระวัง : ฟังก์ชั่นการส่งครั้งเดียวใช้ได้กับอาร์กิวเมนต์แรกเท่านั้น! นี้คือคุณลักษณะที่จงใจดูPEP 433


+1 สำหรับการกล่าวถึง generics แบบ single-dispatch ซึ่งฉันลืมไลบรารีมาตรฐานที่ให้มาโดยสิ้นเชิง
อ. วิลค็อกซ์

เนื่องจากการโทรหา str บน str ไม่ได้ทำอะไรเลยและดูเหมือนว่าฉันจะชัดเจนที่สุดฉันก็เลยไปด้วย
อ. วิลคอกซ์

โดยรวมแล้วฉันชอบhasattrมากกว่าการพยายาม / ยกเว้นเพื่อป้องกันไม่ให้คุณกลืนข้อผิดพลาดบางอย่างในฟังก์ชันถอดรหัสโดยไม่ได้ตั้งใจ แต่ +1
keredson

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

โค้ดข้างต้นทดสอบภายใต้ python 2.7

น่าเสียดายที่ใต้หลาม 3.4 เหมือนกัน ....

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

1
six.string_types ควรเข้ากันได้ 2/3
Joshua Olson

การตรวจสอบแบบนี้ใช้ไม่ได้ใน Python 2 โดยที่สตริง ASCII ธรรมดาผ่านการทดสอบ <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

โปรดทราบว่านี่ไม่ใช่การทดสอบที่เชื่อถือได้ในPython 2ซึ่งอ็อบเจ็กต์สตริงจะส่งผ่านเป็นไบต์ด้วย! นั่นคือตามโค้ดด้านบนtype(text) is bytesจะเป็น True!
Apostolos

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
อ. วิลค็อกซ์

1
@AndrewWilcox: พอใช้ แต่ฉันจะทิ้งข้อมูลนี้ไว้สำหรับการเข้าชม Google ในอนาคต
Kevin

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

มีคำถามสองข้อที่นี่และคำตอบของคำถามนั้นแตกต่างกัน

คำถามแรกชื่อกระทู้นี้คือ อะไรคือวิธีที่เหมาะสมในการตรวจสอบว่าวัตถุเป็นวัตถุคล้ายไบต์ใน Python หรือไม่? ซึ่งรวมถึงจำนวนของในตัวชนิด ( bytes, bytearray, array.array, memoryviewอื่น ๆ ?) และอาจจะยังมีประเภทที่ผู้ใช้กำหนด วิธีที่ดีที่สุดที่ฉันรู้ในการตรวจสอบสิ่งเหล่านี้คือพยายามสร้าง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'

ในเนื้อหาของโพสต์ต้นฉบับดูเหมือนว่าจะเป็นคำถามแทนฉันจะทดสอบได้อย่างไรว่าวัตถุรองรับการถอดรหัส () คำตอบของ @ elizabeth-myers ข้างต้นสำหรับคำถามนี้ดีมากโปรดทราบว่าอ็อบเจ็กต์ที่มีลักษณะคล้ายไบต์ทั้งหมดไม่สนับสนุนการถอดรหัส ()


1
โปรดทราบว่าหากคุณทำเช่นนี้คุณต้องเรียก.release()ใช้หรือใช้เวอร์ชันตัวจัดการบริบท
o11c

ฉันคิดว่าใน CPython ชั่วคราวmemoryviewจะถูกปลดปล่อยทันทีและ.release()จะถูกเรียกโดยปริยาย แต่ฉันยอมรับว่าไม่ควรพึ่งพาสิ่งนั้นเนื่องจากการใช้งาน Python ทั้งหมดไม่ได้รับการอ้างอิง
Jack O'Connor

0

การทดสอบif isinstance(data, bytes)หรือif type(data) == bytesฯลฯ ไม่ทำงานใน Python 2 โดยที่สตริง ASCII ธรรมดาผ่านการทดสอบของ! เนื่องจากฉันใช้ทั้ง Python 2 และ Python 3 เพื่อเอาชนะสิ่งนี้ฉันจึงทำการตรวจสอบต่อไปนี้:

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

มันน่าเกลียดเล็กน้อย แต่มันทำงานได้ตามที่คำถามถามและได้ผลเสมอในวิธีที่ง่ายที่สุด


strวัตถุPython2 คือ bytes : str is bytes-> Trueใน Python2
snakecharmerb

เห็นได้ชัดว่าปัญหาการตรวจจับ! :)
Apostolos
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.