codecamp

Python 字符串规范 | Google 官方 f-string 最佳实践

字符串

Tip

应该用 f-string、 % 运算符或 format 方法来格式化字符串. 即使所有参数都是字符串,也如此。你可以自行评判合适的选项。可以用 + 实现单次拼接,但是不要用 + 实现格式化。

正确:

x = f'名称: {name}; 分数: {n}'
x = '%s, %s!' % (imperative, expletive)
x = '{}, {}'.format(first, second)
x = '名称: %s; 分数: %d' % (name, n)
x = '名称: %(name)s; 分数: %(score)d' % {'name':name, 'score':n}
x = '名称: {}; 分数: {}'.format(name, n)
x = a + b

错误:

x = first + ', ' + second
x = '名称: ' + name + '; 分数: ' + str(n)

不要在循环中用 + 和 += 操作符来堆积字符串。这有时会产生平方而不是线性的时间复杂度。有时 CPython 会优化这种情况,但这是一种实现细节。我们无法轻易预测这种优化是否生效,而且未来情况可能出现变化。作为替代方案,你可以将每个子串加入列表,然后在循环结束后用 ''.join 拼接列表。也可以将每个子串写入一个 io.StringIO 缓冲区中。这些技巧保证始终有线性的平摊(amortized)时间复杂度。

正确:

items = ['<table>']
for last_name, first_name in employee_list:
    items.append('<tr><td>%s, %s</td></tr>' % (last_name, first_name))
items.append('</table>')
employee_table = ''.join(items)

错误:

employee_table = '<table>'
for last_name, first_name in employee_list:
    employee_table += '<tr><td>%s, %s</td></tr>' % (last_name, first_name)
employee_table += '</table>'

应该保持同一文件中字符串引号的一致性。选择 ' 或者 " 以后不要改变主意。如果需要避免用反斜杠来转义引号,则可以使用另一种引号。

正确:

Python('为什么你要捂眼睛?')
Gollum("I'm scared of lint errors. (我害怕格式错误.)")
Narrator('"很好!" 一个开心的 Python 审稿人心想.')

(译者注:注意 “I’m” 中间有一个单引号,所以这一行的外层引号可以用不同的引号。)

错误:

Python("为什么你要捂眼睛?")
Gollum('格式检查器. 它在闪耀. 它要亮瞎我们.')
Gollum("伟大的格式检查器永在. 它在看. 它在看.")

多行字符串推荐使用 """ 而非 '''。当且仅当项目中用 ' 给常规字符串打引号时,才能在文档字符串以外的多行字符串上使用 '''。无论如何,文档字符串必须使用 """

多行字符串不会跟进代码其他部分的缩进。如果需要避免字符串中的额外空格,可以用多个单行字符串拼接,或者用 textwrap.dedent() 删除每行开头的空格。

错误:

    long_string = """这样很难看.
不要这样做.
"""

正确:

long_string = """如果你可以接受多余的空格,
    就可以这样."""


long_string = ("如果你不能接受多余的空格,\n" +
               "可以这样.")


long_string = ("如果你不能接受多余的空格,\n"
               "也可以这样.")

import textwrap


long_string = textwrap.dedent("""\
  这样也行, 因为 textwrap.dedent()
  会删除每一行开头共有的空格.""")

注意,这里的反斜杠没有违反 对换行符转义.

日志

对于那些第一个参数是格式字符串 (包含 % 占位符) 的日志函数:一定要用字符串字面量(而非 f-string!)作为第一个参数,并用占位符的参数作为其他参数。有些日志的实现会收集未展开的格式字符串,作为可搜索的项目。这样也可以免于渲染那些被设置为不用输出的消息。

正确:

import tensorflow as tf
logger = tf.get_logger()
logger.info('TensorFlow 的版本是: %s', tf.__version__)

import os
from absl import logging


logging.info('当前的 $PAGER 是: %s', os.getenv('PAGER', default=''))


homedir = os.getenv('HOME')
if homedir is None or not os.access(homedir, os.W_OK):
    logging.error('无法写入主目录, $HOME=%r', homedir)

错误:

import os
from absl import logging


logging.info('当前的 $PAGER 是:')
logging.info(os.getenv('PAGER', default=''))


homedir = os.getenv('HOME')
if homedir is None or not os.access(homedir, os.W_OK):
    logging.error(f'无法写入主目录, $HOME={homedir!r}')

错误信息

错误信息(例如:诸如 ValueError 等异常的信息字符串和展示给用户的信息)应该遵守以下三条规范:

  1. 信息需要精确地匹配真正的错误条件。
  2. 插入的片段一定要能清晰地分辨出来。
  3. 要便于简单的自动化处理(例如正则搜索,也就是 grepping)。

正确:

if not 0 <= p <= 1:
    raise ValueError(f'这不是概率值: {p!r}')


try:
    os.rmdir(workdir)
except OSError as error:
    logging.warning('无法删除这个文件夹 (原因: %r): %r',
                    error, workdir)

错误:

if p < 0 or p > 1:  # 问题: 遇到 float('nan') 时也为假!
    raise ValueError(f'这不是概率值: {p!r}')


try:
    os.rmdir(workdir)
except OSError:
    # 问题: 信息中存在错误的揣测,
    # 删除操作可能因为其他原因而失败, 此时会误导调试人员.
    logging.warning('文件夹已被删除: %s', workdir)


try:
    os.rmdir(workdir)
except OSError:
    # 问题: 这个信息难以搜索, 而且某些 `workdir` 的值会让人困惑.
    # 假如有人调用这段代码时让 workdir = '已删除'. 这个警告会变成:
    # "无法删除已删除文件夹."
    logging.warning('无法删除%s文件夹.', workdir)
Python 注释写作规范 | Google 官方标点语法指南
Python 资源管理规范 | Google 官方 with 语句指南
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }