2016-10-02 58 views
1

当我使用Python中的三引号多行字符串,我倾向于使用textwrap.dedent保持代码的可读性,具有良好的缩进:使用Python中与字节textwrap.dedent()3

some_string = textwrap.dedent(""" 
    First line 
    Second line 
    ... 
    """).strip() 

但是,在Python 3.x中,textwrap.dedent似乎不适用于字节字符串。我遇到过这种一边写单元测试为返回长的多字节字符串,例如一个方法:

# The function to be tested 

def some_function(): 
    return b'Lorem ipsum dolor sit amet\n consectetuer adipiscing elit' 

# Unit test 

import unittest 
import textwrap 

class SomeTest(unittest.TestCase): 
    def test_some_function(self): 
     self.assertEqual(some_function(), textwrap.dedent(b""" 
      Lorem ipsum dolor sit amet 
       consectetuer adipiscing elit 
      """).strip()) 

if __name__ == '__main__': 
    unittest.main() 

在Python 2.7.10上面的代码工作正常,但在Python 3.4.3失败:

E 
====================================================================== 
ERROR: test_some_function (__main__.SomeTest) 
---------------------------------------------------------------------- 
Traceback (most recent call last): 
    File "test.py", line 16, in test_some_function 
    """).strip()) 
    File "/usr/lib64/python3.4/textwrap.py", line 416, in dedent 
    text = _whitespace_only_re.sub('', text) 
TypeError: can't use a string pattern on a bytes-like object 

---------------------------------------------------------------------- 
Ran 1 test in 0.001s 

FAILED (errors=1) 

因此:是否有替代textwrap.dedent与字节字符串?

  • 我可以自己编写这样一个函数,但是如果有一个现有函数,我宁愿使用它。
  • 我可以转换为unicode,使用textwrap.dedent,并转换回字节。但是,如果字节字符串符合一些Unicode编码,这是唯一可行的。

回答

1

好像dedent不支持字节串,可悲。但是,如果你想跨兼容的代码,我建议您采取six库的优势:

import sys, unittest 
from textwrap import dedent 

import six 


def some_function(): 
    return b'Lorem ipsum dolor sit amet\n consectetuer adipiscing elit' 


class SomeTest(unittest.TestCase): 
    def test_some_function(self): 
     actual = some_function() 

     expected = six.b(dedent(""" 
      Lorem ipsum dolor sit amet 
       consectetuer adipiscing elit 
      """)).strip() 

     self.assertEqual(actual, expected) 

if __name__ == '__main__': 
    unittest.main() 

这是类似在问题

我都可以转换成你的子弹点建议unicode,使用textwrap.dedent,并转换回字节。但是,如果字节字符串符合一些Unicode编码,这是唯一可行的。

但是你在这里误解的东西有关编码 - 如果你能写出字符串在您的测试一样,摆在首位,并请有蟒蛇成功解析该文件(即正确编码申报是在模块),那么在这里没有“转换为unicode”的步骤。该文件以指定的编码(或sys.defaultencoding,如果您未指定)进行解析,然后当该字符串是python变量时,它已被解码。

+0

使用hex.b以外的好主意。我已经在我的项目中使用了六个,所以使用six.b不会增加额外的依赖。我的编码担忧并不是关于源文件中的非ASCII字符,而是像“\ xff”这样的十六进制转义序列。不过,我现在已经测试,它适用于所有这些序列(six.b(S)上的Python 3等同于s.encode(“拉丁-1”))。我会接受这个答案。 – nomadictype

0

答案1:三引号多行字符串(和缩进)是一种方便(有时),而不是必需的。您可以改为编写一个以b'\ n'结尾的单独字节文字,并让解析器加入它们。例如:

>>> b = (
    b'Lorem ipsum dolor sit amet\n' # first line 
    b'consectetuer adipiscing elit\n' # 2nd line 
    ) 
>>> b 
b'Lorem ipsum dolor sit amet\nconsectetuer adipiscing elit\n' 

我有意地在代码中添加了空格和注释,这些代码在结果字节中不需要,应该不包含它们。我有时用文本字符串做上面的等价物。

答2:转换textwrap.dedent处理字节(参见单独的答案)

答3:忽略b前缀和之前或之后.strip()添加.encode()

print(textwrap.dedent(""" 
      Lorem ipsum dolor sit amet 
       consectetuer adipiscing elit 
      """).encode()) 
# prints (same as Answer 2). 
b'\nLorem ipsum dolor sit amet\n consectetuer adipiscing elit\n' 
+1

很好的答案。我已经使用这种方法来处理少量的行;我想我不应该害怕将它用于更长的文本。尽管如此,我认为对于非常长的文本或文本,通常会通过复制+粘贴(发生在我的单元测试中有时)进行更新,三重引号+缩进会更好,并且视觉混乱更少。 – nomadictype

+0

查看我的答案2. –

+0

答案3在所有情况下都不起作用,例如textwrap.dedent(“”“\ xff”“”)。encode()是b'\ xc3 \ xbf',而不是b'\ xff'(我想要后者)。在Python 2中,它引发了一个UnicodeDecodeError。使用six.b()而不是.encode()修复这些问题,请参阅wim的答案。 – nomadictype

1

答2:textwrap主要是关于Textwrap类和功能。 dedent

# -- Loosely related functionality -------------------- 

上市尽可能接近我所知道的,只有具体东西,它使文本(Unicode的str)是重文字。我用b作为前缀,并且瞧! (我没有修改任何东西,但功能文档字符串应该进行调整。)

import re 

_whitespace_only_re = re.compile(b'^[ \t]+$', re.MULTILINE) 
_leading_whitespace_re = re.compile(b'(^[ \t]*)(?:[^ \t\n])', re.MULTILINE) 

def dedent_bytes(text): 
    """Remove any common leading whitespace from every line in `text`. 

    This can be used to make triple-quoted strings line up with the left 
    edge of the display, while still presenting them in the source code 
    in indented form. 

    Note that tabs and spaces are both treated as whitespace, but they 
    are not equal: the lines " hello" and "\\thello" are 
    considered to have no common leading whitespace. (This behaviour is 
    new in Python 2.5; older versions of this module incorrectly 
    expanded tabs before searching for common leading whitespace.) 
    """ 
    # Look for the longest leading string of spaces and tabs common to 
    # all lines. 
    margin = None 
    text = _whitespace_only_re.sub(b'', text) 
    indents = _leading_whitespace_re.findall(text) 
    for indent in indents: 
     if margin is None: 
      margin = indent 

     # Current line more deeply indented than previous winner: 
     # no change (previous winner is still on top). 
     elif indent.startswith(margin): 
      pass 

     # Current line consistent with and no deeper than previous winner: 
     # it's the new winner. 
     elif margin.startswith(indent): 
      margin = indent 

     # Find the largest common whitespace between current line 
     # and previous winner. 
     else: 
      for i, (x, y) in enumerate(zip(margin, indent)): 
       if x != y: 
        margin = margin[:i] 
        break 
      else: 
       margin = margin[:len(indent)] 

    # sanity check (testing/debugging only) 
    if 0 and margin: 
     for line in text.split(b"\n"): 
      assert not line or line.startswith(margin), \ 
        "line = %r, margin = %r" % (line, margin) 

    if margin: 
     text = re.sub(rb'(?m)^' + margin, b'', text) 
    return text 

print(dedent_bytes(b""" 
      Lorem ipsum dolor sit amet 
       consectetuer adipiscing elit 
      """) 
    ) 

# prints 
b'\nLorem ipsum dolor sit amet\n consectetuer adipiscing elit\n'