在三引号 fstring 中保留缩进

Preserving indentation in a triple-quoted fstring

我正在尝试使用 Python3(.7) 中的三引号字符串来构建一些格式化字符串。

我有一个内部字符串列表,所有这些都需要在以下选项卡中:

    This is some text
    across multiple
    lines.

还有一个应该包含内部字符串的字符串

data{
    // string goes here
}

我在创建内部字符串时无法制表。所以,我的想法是使用 dedent 和 Python3 三引号 fstrings:

import textwrap

inner_str = textwrap.dedent(
    '''\
    This is some text
    across multiple
    lines.'''
)

full_str = textwrap.dedent(
    f'''\
    data{{
        // This should all be tabbed
        {inner_str}
    }}'''
)

print(full_str)

但是,没有保留缩进:

    data{
        // This should all be tabbed
        This is some text
across multiple
lines.
    }

想要的结果:

data{
    // This should all be tabbed
    This is some text
    across multiple
    lines.
}

如何在不对内部字符串进行预制表的情况下保留 fstring 的缩进?

已编辑以避免使用 Tab 键 inner_str

import textwrap

line_tab = '\n\t'

inner_str = f'''\
This is some text
across multiple
lines.
'''

full_str = textwrap.dedent(f'''\
data{{
    // This should all be tabbed 
    {line_tab.join(inner_str.splitlines())}
}}''')
                           )

print(full_str)

输出:

data{
    // This should all be tabbed 
    This is some text
    across multiple
    lines.
}

这似乎提供了你想要的。

import textwrap

inner_str = textwrap.dedent(
    '''\
    This is some text
    across multiple
    lines.'''
)

full_str = textwrap.dedent(
    f'''
    data{{
{textwrap.indent(inner_str, "        ")}
    }}'''
)

更好的解决方案:

idt = str.maketrans({'\n': "\n        "})
print(textwrap.dedent(
    f'''
    data{{
        {inner_str.translate(idt)}
    }}'''
))

自定义标签宽度的另一种解决方案:

def indent_inner(inner_str, indent):
    return inner_str.replace('\n', '\n' + indent)   # os.linesep could be used if the function is needed across different OSs

print(textwrap.dedent(
    f'''
    data{{
        {indent_inner(inner_str, "        ")}
    }}'''
))

None 这里的答案似乎符合我的要求,所以我用一个尽可能接近我正在寻找的解决方案来回答我自己的问题。不需要预先制表符来定义其缩进级别,也不需要不缩进行(这会破坏可读性)。相反,fstring 中当前行 的缩进级别在使用时传递。

不完美,但它有效。

import textwrap


def code_indent(text, tab_sz, tab_chr=' '):
    def indented_lines():
        for i, line in enumerate(text.splitlines(True)):
            yield (
                tab_chr * tab_sz + line if line.strip() else line
            ) if i else line
    return ''.join(indented_lines())


inner_str = textwrap.dedent(
    '''\
    This is some text
    across multiple
    lines.'''
)


full_str = textwrap.dedent(
    f'''
    data{{
        {code_indent(inner_str, 8)}
    }}'''
)

print(full_str)