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_TO 和 PRINT_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