Python中的多行字符串格式化

13

首先,我正在努力实现如下所示的期望输出:

*********************************************************************
                                 hello
********************************************************************* 
为了实现这一点,我已经将所需输出指定为一个具有多行字符串的变量,并使用format打印出相同结果。
$ cat varibale.py 
decorator = """ **********************************************************************
                               {}
            ********************************************************************** """
print(decorator.format("hello"))

输出:

**********************************************************************
                               hello
            **********************************************************************

以上方法存在的问题是输出的第三行有额外空格,看起来很奇怪。

我能够通过以下方式实现:

Translated text:

The issue with above approach is the extra spaces in the third line of output which is looking odd.

I am able to achieve this in the following way:

$ cat varibale.py 
decorator = """ **********************************************************************
                             {}
********************************************************************* 
"""
print(decorator.format("hello"))

输出:

 **********************************************************************
                             hello
********************************************************************* 

但是这样我的代码看起来不太好,因为它没有遵循缩进。

请建议实现所需输出的正确方法。


在这里添加 [tag:python-3.x] 标签会有意义吗?顺便说一下,您还可以使用三引号格式化字符串字面值,在我的答案中有一个使用示例,这确实很有帮助。 - Wolf
4个回答

13

让多行文本字符串更易于阅读的一种方法是使用反斜杠来转义换行符,就像这样:

s = '''\
*********************************************************************
                                 hello
*********************************************************************
'''
print(s)

输出

*********************************************************************
                                 hello
*********************************************************************

然而,PEP-008不鼓励像这样使用反斜杠。这太脆弱了:如果在反斜杠和换行符之间有一个空格,那么换行符将不会被转义,而反斜杠则会被打印出来。

一种更灵活的方法是使用一个函数来计算文本居中所需的填充量,并通过嵌套的格式说明符来应用它。例如:

def banner(s, width=69):
    stars = '*' * width
    pad = (width + len(s)) // 2
    return '{0}\n{1:>{2}}\n{0}'.format(stars, s, pad)

print(banner('hello'))
print(banner('Hello, world', width=16))

输出

*********************************************************************
                                hello
*********************************************************************
****************
  Hello, world
****************

它是如何工作的

那个格式化字符串有点密集,所以我想我应该尝试解释一下。 ;) 有关此主题的完整信息,请参见文档中的格式化字符串语法。以下解释部分借鉴了并改编自文档。

'{0}\n{1:>{2}}\n{0}'.format(stars, s, pad)
格式化字符串中用花括号{}和冒号:包裹的内容被称为“替换域(replacement field)”。替换域中的第一项是可选的字段名。这让我们能够确定哪个.format的参数对应该替换域。字段名有几种可能的变化形式,此格式字符串使用数字名称,因此它通过其位置来标识.format的参数。也就是说,0对应于stars,1对应于s,2对应于pad
如果没有给出字段名,它们将自动填充为数字 0、1、2 等(除非您使用的是 Python 2.6,在那里字段名是强制性的)。这在大多数情况下非常有用,因此大多数格式字符串不会使用字段名。
在字段名之后,我们可以给出一个“格式说明符(format specifier)”或“格式说明(format spec)”,用于描述如何呈现值。 冒号 : 将字段名与格式说明符分开。如果您不提供格式说明符,则会得到默认的格式说明符,大多数情况下都足够了。但是在这里,我们需要更多的控制,因此需要提供格式说明符。
在格式说明符中,> 符号强制将字段右对齐在可用空间内。在对齐标记之后,我们可以提供一个数字来指定最小字段宽度;如果必要,字段将自动变大。
例如,'{0:>6}'.format('test') 表示将参数 0('test')放置在至少为 6 个字符宽的空间中,右对齐,结果是字符串 ' test'
但是格式说明符实际上可以包含一个全新的替换域!这使我们能够提供一个变量以控制字段宽度。因此,在我的格式字符串中,{1:>{2}} 表示将参数 1(s)放置在这里,右对齐,宽度由参数 2(pad)给出的字段中。只允许嵌套一级替换域,但很难想象您实际上需要更深层次的嵌套。
综合以上,'{0}\n{1:>{2}}\n{0}'表示告诉.format构建一个以arg 0(stars)开头、使用默认格式说明符的字符串,后跟一个换行符,然后是arg 1(s)右对齐的宽度为pad的字段,再跟一个换行符,最后跟着arg 0(stars)。
在Python 3.6+中,我们可以使用f-string。
def banner(s, width=69):
    stars = '*' * width
    pad = (width + len(s)) // 2
    return f'{stars}\n{s:>{pad}}\n{stars}'

请问您能否解释一下这里使用的逻辑 " {0}\n{1:>{2}}\n{0}"? - Here_2_learn

8
你可以按照以下步骤进行操作:

例如,你可以这样做:

print('*'*80)
print('{msg:^80s}'.format(msg = 'HELLO')) #^ centers the message
print('*'*80)

或者如果您想要文本宽度动态调整:

def fn(msg, w = 80):
    delim = '*'*w
    fmt = '{msg:^%ds}'%w

    print(delim)
    print(fmt.format(msg=msg))
    print(delim)

fn('hello')

或者稍微通用一些,如果您需要写入文件:
import sys

def fn(msg, w = 80, F = sys.stdout):
    delim = '*'*w
    fmt = '{delim:s}\n{msg:^%ds}\n{delim:s}\n'%w
    F.write(fmt.format(delim = delim, msg = msg))

fn('hello')

整洁简单。我喜欢它。 - Matias Cicero
如果我需要将相同的内容写入文件,第一个解决方案就不太好。 - Here_2_learn
@MatiasCicero 谢谢你的代码修正,漏掉了那个... :) - ewcz
@Here_2_learn 这个想法是一样的。只需要将 print 语句更改为 f.write 并在末尾添加一个换行符即可。 - Matias Cicero

0
也许:
print '*' * 80 + '\n' + ' ' * 38 + 'hello' + '\n' + '*' *80

或者,如果是Python3

a = lambda x,c,mess: print(c*x + ('\n' if not mess else mess))
a(80, '*', None)
a(38, ' ', 'Hello')
a(80, '*', None)

0
自Python 3.6开始,可以在格式化字符串字面值内进行格式化,还可以跨越多行。格式化参数(如宽度)通常作为整数字面值添加;要使用任意整数表达式,将其括在花括号中即可。
在给定的情况下,这样做
print(f"""{'*'*width}
{'hello':^{width}}
{'*'*width}""")

可能看起来比较混乱
print(f"{'*'*width}\n{'hello':^{width}}\n{'*'*width}")

但是当涉及到动态生成代码时,你可以通过使用多行格式化字符串字面值来更容易地实现正确的缩进和良好的可读性。
def gen_recursion(name, op, neutral): return f"""
def {name}(n):
    if n > {neutral}:
        return n {op} {name}(n-1)
    else:
        return {neutral}
"""

samples = (
    ('gauss', '+', 0),
    ('factorial', '*', 1)
)
for (name, op, neutral) in samples:
    s = gen_recursion(name, op, neutral)
    print(s)
    exec(s, globals())
    s = f"{name}(5)"
    print(f"{s} -> {eval(s)}")

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接