如何在python 3中实现字节对象的sprintf样式格式化?

时间:2017-07-29 04:00:55

标签: python python-3.x templates bytestring incompatibility

我想在python3上做sprintf但是使用原始字节对象,而不必为%s进行任何手动转换。因此,将一个bytes对象作为“模板”,加上任意类型的任意数量的对象,并返回一个呈现的字节对象。这就是python 2的sprintf%运算符一直在运行的方式。

b'test %s %s %s' % (5, b'blah','strblah') # python3 ==> error
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: %b requires bytes, or an object that implements __bytes__, not 'int'

def to_bytes(arg):
    if hasattr(arg,'encode'): return arg.encode()
    if hasattr(arg,'decode'): return arg
    return repr(arg).encode()

def render_bytes_template(btemplate : bytes, *args):
    return btemplate % tuple(map(to_bytes,args))

render_bytes_template(b'this is how we have to write raw strings with unknown-typed arguments? %s %s %s',5,b'blah','strblah')

# output: b'this is how we have to render raw string templates with unknown-typed arguments? 5 blah strblah'

但是在python 2中,它只是内置:

'example that just works %s %s %s' % (5,b'blah',u'strblah')
# output: 'example that just works 5 blah strblah'

有没有办法在python 3中执行此操作但仍能实现与python 2相同的性能?请告诉我,我错过了什么。这里的后备是在cython中实现(或者有没有用于python 3的库有帮助吗?)但仍然没有看到为什么它被从标准库中删除而不是字符串对象的隐式编码。我们不能只添加像format_any()的字节方法吗?

顺便说一下,它并不像这个警察那么简单:

def render_bytes_template(btemplate : bytes, *args):
    return (btemplate.decode() % args).encode()

我不仅不想进行任何不必要的编码/解码,而是重新编写字节args而不是注入原始数据。

2 个答案:

答案 0 :(得分:2)

  

我想在python3上做sprintf但是使用原始字节对象,而不必为%s进行任何手动转换。

为此,所有格式化参数也必须已为bytes

自从Py2允许甚至unicode字符串格式化为字节字符串以来,这已经发生了变化,因为一旦引入了具有unicode字符的unicode字符串,Py2实现就容易出错。

例如,在Python 2上:

In [1]: '%s' % (u'é',)
Out[1]: u'\xe9'

从技术上讲,这是正确的,但不是开发人员的意图。它也不考虑使用的任何编码。

在Python 3 OTOH中:

In [2]: '%s' % ('é',)
Out[2]: 'é'

对于格式化字节字符串,请使用字节字符串参数(仅限Py3.5)

b'%s %s' % (b'blah', 'strblah'.encode('utf-8'))

其他类型如整数也需要转换为字节字符串。

答案 1 :(得分:1)

这样的事情对你有用吗?您只需确保在开始某个bytes对象时将其包装在新的B字节对象中,该对象会重载%%=运算符:

class B(bytes):
    def __init__(self, template):
        self._template = template

    @staticmethod
    def to_bytes(arg):
        if hasattr(arg,'encode'): return arg.encode()
        if hasattr(arg,'decode'): return arg
        return repr(arg).encode()

    def __mod__(self, other):
        if hasattr(other, '__iter__') and not isinstance(other, str):
            ret = self._template % tuple(map(self.to_bytes, other))
        else: 
            ret = self._template % self.to_bytes(other)
        return ret

    def __imod__(self, other):
        return self.__mod__(other)

a = B(b'this %s good')
b = B(b'this %s %s good string')
print(a % 'is')
print(b % ('is', 'a'))

a = B(b'this %s good')
a %= 'is'
b = B(b'this %s %s good string')
b %= ('is', 'a')
print(a)
print(b)

输出:

b'this is good'
b'this is a good string'
b'this is good'
b'this is a good string'