确定对象在Python中是否是类似字节的对象的正确方法是什么?


90

我有期望的代码,str但可以通过bytes以下方式处理被传递的情况:

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

不幸的是,这不适用于bytearray。有没有更通用的方法来测试对象是否为bytesbytearray,或者我应该只检查两者?是否hasattr('decode')像我想的那样糟糕?


6
就个人而言,我喜欢与下一个家伙一样多的python鸭子。但是,如果您需要检查输入参数并强制转换为不同的类型,那么您就不再需要鸭子输入了–您只是在使代码更难以阅读维护。我在这里的建议(以及其他人可能不同意)是使多个功能(处理类型强制并委托给基本实现)。
mgilson '16

(1)除非您需要它以兼容旧版Python 2代码;避免同时接受文本和二进制数据。如果您的函数可以处理文本,则只能接受str。其他一些代码应尽快在输入时从字节转换为Unicode。(2)“类似字节的字节”在Python中具有特殊含义(支持缓冲区协议的对象(仅C))
jfs

主要问题是此函数在Python 2中不起作用,在Python 2中,一个简单的ASCII字符串通过了<bytes>的测试!
Apostolos

Answers:


73

您可以在此处使用几种方法。

鸭打字

由于Python是鸭子类型的,因此您可以简单地执行以下操作(这似乎通常是建议的方式):

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

您可以hasattr按照您的描述使用,但是可能会很好。当然,这是假定.decode()给定对象的方法返回一个字符串,并且没有讨厌的副作用。

我个人建议使用异常或hasattr方法,但是您可以使用任何方法。

使用str()

这种方法并不常见,但可以:

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

就像缓冲区协议的一样,其他编码也是允许的.decode()。您还可以传递第三个参数来指定错误处理。

单调度通用函数(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对象创建特殊的处理程序。

注意:单调度功能仅在第一个参数上起作用!这是有意的功能,请参阅PEP 433


+1提到了单派送泛型,我完全忘记了提供的标准库。
A. Wilcox

因为在str上调用str并没有任何作用,而且在我看来最清楚,所以我同意了。
A. Wilcox

总的来说,我喜欢hasattr尝试/例外,以防止您误吞一些解码功能中的错误,但+1。
keredson '17

37

您可以使用:

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下测试的

不幸的是,在python 3.4下,它们是相同的。

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

1
six.string_types应该是2/3兼容的。
约书亚·奥尔森

这种检查在Python 2中不起作用,在Python 2中,一个简单的ASCII字符串通过了<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

请注意,这在Python 2中不是可靠的测试,在该测试中,字符串对象也以字节形式通过!也就是说,根据以上代码,type(text) is bytes将为True!
Apostolos

11

除非您知道我们不了解的内容,否则此代码是不正确的:

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

您(似乎)不知道的编码data。您假设它是UTF-8,但这很可能是错误的。由于您不知道编码,因此没有text。您有字节,在阳光下可能有任何意义。

好消息是,大多数随机字节序列都不是有效的UTF-8,因此,一旦中断,它将大声中断(这errors='strict'是默认设置),而不是默默地做错事。更好的消息是,碰巧是有效UTF-8的大多数随机序列也是有效ASCII,几乎每个人都同意如何解析。

坏消息是,没有解决此问题的合理方法。提供编码信息的标准方法是:使用str代替bytes。如果某些第三方代码在没有任何其他上下文或信息的情况下将您bytesbytearray对象交给您,则唯一正确的措施是失败。


现在,假设您知道编码,可以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流量使用。
凯文(Kevin)

4

这取决于您要解决的问题。如果要使用相同的代码将两种情况都转换为字符串,则只需将类型转换为bytesfirst,然后解码即可。这样,它是单线的:

#!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中对象是否为类似字节的对象的正确方法什么?这包括在内置类型的若干(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-myers在上述问题上的答案很棒。请注意,并非所有类似字节的对象都支持decode()。


1
请注意,如果执行此操作,则必须调用.release(),或使用上下文管理器版本。
o11c

我认为在CPython中,该临时文件memoryview将立即释放并被.release()隐式调用。但是我同意最好不要依赖于此,因为并非所有Python实现都是引用计数的。
杰克·奥康纳

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>")

这有点丑陋,但是它可以完成问题所要求的工作并且始终以最简单的方式起作用。


Python2str对象 bytesstr is bytes->True在Python2中
snakecharmerb

显然,因此存在检测问题!:)
Apostolos
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.