Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 214 – 扩展 print 语句

作者:
Barry Warsaw <barry at python.org>
状态:
最终版
类型:
标准跟踪
创建日期:
2000 年 7 月 24 日
Python 版本:
2.0
发布历史:
2000 年 8 月 16 日

目录

引言

本 PEP 描述了一种语法,用于扩展标准“print”语句,以便它可以用于打印到任何类文件对象,而不是默认的 sys.stdout。本 PEP 跟踪此功能的状况和所有权。它包含对该功能的描述,并概述了支持该功能所需的更改。本 PEP 总结了邮件列表论坛中进行的讨论,并提供了适当的 URL 以获取更多信息。此文件的 CVS 修订历史记录包含权威的历史记录。

提案

本提案引入了 print 语句的语法扩展,允许程序员可选地指定输出文件目标。用法示例如下:

print >> mylogfile, 'this message goes to my log file'

形式上,扩展 print 语句的语法是:

print_stmt: ... | '>>' test [ (',' test)+ [','] ] )

其中省略号表示原始 print_stmt 语法不变。在扩展形式中,>> 之后的表达式必须生成一个具有 write() 方法的对象(即一个类文件对象)。因此,以下两个语句是等价的:

print 'hello world'
print >> sys.stdout, 'hello world'

以下两个语句也是等价的:

print
print >> sys.stdout

以下两个语句是语法错误:

print ,
print >> sys.stdout,

理由

“print”是 Python 关键字,并引入了语言参考手册 [1] 第 6.6 节中描述的 print 语句。print 语句具有多项特性:

  • 它自动将项转换为字符串
  • 它在项之间自动插入空格
  • 除非语句以逗号结尾,否则它会附加一个换行符

print 语句执行的格式化是有限的;要更好地控制输出,可以使用 sys.stdout.write() 和字符串插值的组合。

print 语句默认输出到 sys.stdout。更具体地说,sys.stdout 必须是一个具有 write() 方法的类文件对象,但它可以重新绑定以将输出重定向到除标准输出之外的文件。一个典型的惯用法是:

save_stdout = sys.stdout
try:
    sys.stdout = mylogfile
    print 'this message goes to my log file'
finally:
    sys.stdout = save_stdout

这种方法的问题在于绑定是全局的,因此会影响 try: 子句中的每个语句。例如,如果我们在一个确实想打印到 stdout 的函数中添加了一个调用,这个输出也会被重定向到日志文件。

这种方法对于将打印交错到各种输出流也非常不方便,并且在面对合法的 try/except 或 try/finally 子句时使编码复杂化。

参考实现

一个参考实现,以针对 Python 2.0 源代码树的补丁形式,可在 SourceForge 的补丁管理器上获取 [2]。这种方法添加了两个新的操作码,PRINT_ITEM_TOPRINT_NEWLINE_TO,它们只是从栈顶弹出类文件对象并将其用作输出流,而不是 sys.stdout

(此参考实现已在 Python 2.0 中采用。)

替代方法

有人(最初由 Moshe Zadka 提出)提出了一种替代此语法更改的方法,它不需要对 Python 进行任何语法更改。可以提供一个 writeln() 函数(可能作为内置函数),其功能与扩展 print 非常相似,并带有一些附加功能:

def writeln(*args, **kws):
    import sys
    file = sys.stdout
    sep = ' '
    end = '\n'
    if kws.has_key('file'):
        file = kws['file']
        del kws['file']
    if kws.has_key('nl'):
        if not kws['nl']:
            end = ' '
        del kws['nl']
    if kws.has_key('sep'):
        sep = kws['sep']
        del kws['sep']
    if kws:
        raise TypeError('unexpected keywords')
    file.write(sep.join(map(str, args)) + end)

writeln() 接受三个可选的关键字参数。在此提案的上下文中,相关的参数是“file”,它可以设置为一个具有 write() 方法的类文件对象。因此:

print >> mylogfile, 'this goes to my log file'

将被写成:

writeln('this goes to my log file', file=mylogfile)

writeln() 具有额外的功能,即关键字参数“nl”是一个标志,用于指定是否附加换行符,以及一个参数“sep”,用于指定在每个项之间输出的分隔符。

BDFL 的更多理由

该提案在新闻组中受到了质疑。一系列质疑不喜欢“>>”,宁愿看到其他符号。

  • 挑战:为什么不用其中一个呢?
    print in stderr items,....
    print + stderr items,.......
    print[stderr] items,.....
    print to stderr items,.....
    

    回应:如果我们想使用特殊符号(print <symbol> 表达式),Python 解析器要求它不是已经可以开始表达式的符号——否则它无法决定使用了哪种形式的 print 语句。(Python 解析器是一个简单的 LL(1) 或递归下降解析器。)

    这意味着我们不能使用用于“import as”的“仅在上下文中关键字”技巧,因为标识符可以开始一个表达式。这排除了 +stderr、[sterr] 和 to stderr。这给我们留下了二元运算符符号和其他目前在此处非法的杂项符号,例如“import”。

    如果我必须在“print in file”和“print >> file”之间做出选择,我肯定会选择“>>”。部分原因是“in”将是一个新发明(我不知道有其他语言使用它,而“>>”在 sh、awk、Perl 和 C++ 中使用),部分原因是“>>”是非字母的,因此更突出,更有可能引起读者的注意。

  • 挑战:为什么文件和其余部分之间必须有逗号?

    回应:将文件与后面的表达式分隔开的逗号是必需的!当然,您希望文件是一个任意表达式,而不仅仅是一个单词。(您肯定希望能够编写 print >>sys.stderr。)如果没有表达式,解析器将无法区分该表达式在哪里结束,下一个表达式从哪里开始,例如:

    print >>i +1, 2
    print >>a [1], 2
    print >>f (1), 2
    
  • 挑战:为什么需要语法扩展?为什么不用 writeln(file, item, …)?

    回应:首先,这缺少 print 语句的一个特性:用于抑制最终换行符的尾随逗号。请注意,“print a,”仍然不等同于“sys.stdout.write(a)”——print 在项之间插入空格,并接受任意对象作为参数;write() 不插入空格,并且需要单个字符串。

    当您考虑对 print 语句进行扩展时,添加一个在某个维度(输出去向)上增加新功能但在另一个维度(项之间的空格以及是否选择尾随换行符)上减少功能的函数或方法是不对的。我们可以添加一系列方法或函数来处理各种情况,但这似乎增加了不必要的混乱,并且只有在我们完全弃用 print 语句的情况下才有意义。

    我觉得这场争论实际上是关于 print 应该是一个函数或方法,而不是一个语句。如果您是函数阵营的人,当然不喜欢向现有 print 语句添加特殊语法。我怀疑对新语法的反对主要来自那些已经认为 print 语句是个坏主意的人。我说的对吗?

    大约 10 年前,我曾考虑过是把最基本的输出形式做成函数还是语句;基本上我是在“print(item, …)”和“print item, …”之间做选择。我选择把它做成语句,因为打印需要在很早的时候教授,并且在初学者编写的程序中非常重要。此外,ABC 作为许多事物的先驱,也把它做成了语句。在 ABC 和 Python 之间典型的交互中,我把名称从 WRITE 改为 print,并且改变了添加换行符的约定:从需要额外语法来添加换行符(ABC 使用尾随斜杠表示换行符)变为需要额外语法(尾随逗号)来抑制换行符。我保留了输出时项之间用空格分隔的特性。

    完整示例:在 ABC 中,

    WRITE 1
    WRITE 2/
    

    具有与此相同的效果:

    print 1,
    print 2
    

    在 Python 中,输出效果是“1 2n”。

    我不能 100% 确定选择语句是正确的(ABC 有一个令人信服的理由,即它对任何有副作用的东西都使用语句语法,但 Python 没有这个约定),但我也不相信它是错误的。我当然喜欢 print 语句的简洁性。(我是一个狂热的 Lisp 厌恶者——语法方面,而不是语义方面!——语法中过多的括号让我恼火。千万不要在你的 Python 代码中写 return(i) or if(x==y):!:-)

    无论如何,我还没准备好弃用 print 语句,多年来我们收到了许多关于指定文件选项的请求。

  • 挑战:为什么不用 > 而不是 >>?

    回应:对于 DOS 和 Unix 用户,>> 表示“追加”,而 > 表示“覆盖”;语义最接近追加。此外,对于 C++ 程序员,>> 和 << 是 I/O 运算符。

  • 挑战:但是在 C++ 中,>> 是输入,<< 是输出!

    回应:没关系;C++ 显然是从 Unix 借鉴并反转了箭头。重要的是,对于输出,箭头指向文件。

  • 挑战:你肯定能设计一个 println() 函数,它可以做 print>>file 能做的一切;为什么这还不够?

    回应:我将这看作一个简单的编程练习。假设一位初级程序员被要求编写一个打印乘法表的函数。一个合理的解决方案是:

    def tables(n):
        for j in range(1, n+1):
            for i in range(1, n+1):
                print i, 'x', j, '=', i*j
            print
    

    现在假设第二个练习是添加打印到不同的文件。使用新语法,程序员只需要学习一个新东西:print >> file,答案可以像这样:

    def tables(n, file=sys.stdout):
        for j in range(1, n+1):
            for i in range(1, n+1):
                print >> file, i, 'x', j, '=', i*j
            print >> file
    

    如果只有 print 语句和一个 println() 函数,程序员首先必须了解 println(),将原始程序转换为使用 println()

    def tables(n):
        for j in range(1, n+1):
            for i in range(1, n+1):
                println(i, 'x', j, '=', i*j)
            println()
    

    然后了解 file 关键字参数:

    def tables(n, file=sys.stdout):
        for j in range(1, n+1):
            for i in range(1, n+1):
                println(i, 'x', j, '=', i*j, file=sys.stdout)
            println(file=sys.stdout)
    

    因此,转换路径更长:

    (1) print
    (2) print >> file
    

    对比:

    (1) print
    (2) println()
    (3) println(file=...)
    

    注意:在编译时将文件参数默认设置为 sys.stdout 是错误的,因为当调用者将 sys.stdout 赋值,然后使用 tables() 而不指定文件时,它无法正常工作。这是一个常见问题(println() 函数也会出现)。到目前为止的标准解决方案是:

    def tables(n, file=None):
        if file is None:
            file = sys.stdout
        for j in range(1, n+1):
            for i in range(1, n+1):
                print >> file, i, 'x', j, '=', i*j
            print >> file
    

    我为实现添加了一个功能(我也建议 println() 也这样做),即如果文件参数为 None,则自动使用 sys.stdout。因此:

    print >> None, foo bar
    

    (当然,或者 print >> x,其中 x 是一个值为 None 的变量)与此相同:

    print foo, bar
    

    并且 tables() 函数可以编写如下:

    def tables(n, file=None):
        for j in range(1, n+1):
            for i in range(1, n+1):
                print >> file, i, 'x', j, '=', i*j
            print >> file
    

参考资料


来源:https://github.com/python/peps/blob/main/peps/pep-0214.rst

最后修改:2025-02-01 08:55:40 GMT