PEP 214 – 扩展打印语句
- 作者:
- Barry Warsaw <barry at python.org>
- 状态:
- 最终
- 类型:
- 标准轨迹
- 创建:
- 2000-07-24
- Python 版本:
- 2.0
- 历史记录:
- 2000-08-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 关键字,它引入了语言参考手册第 6.6 节中描述的 print 语句 [1]。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”。
我并不确定选择语句是否正确(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=...)
注意:在编译时将 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()
),如果 file 参数是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
最后修改时间:2023-09-09 17:39:29 GMT