2016-10-02 32 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'