为什么print语句不符合Python风格?

34 投票
5 回答
10910 浏览
提问于 2025-04-15 12:32

这个问题让我困扰了很久(可以参考我之前的问题这里):为什么说print(x)print x更好(被称为更“Pythonic”)呢?

对于那些不知道的人来说,print这个语句在Python 3.0中变成了一个函数。详细的说明可以在PEP 3105找到,动机则在Guido van Rossum的邮件中有提到。

对此,我想提出一些反对意见:

  1. 还有其他操作符,比如import,我们是以语句的形式写的,尽管它的功能实际上可以用一个函数__import__来实现。
    • 对于初学者来说,操作符print并不属于一般的应用逻辑。对他们来说,它是一个神秘的操作符,是他们程序的高潮部分。他们期望它看起来会有所不同。
    • 所有描述基本Python 2.x的初学者书籍现在从第一个例子开始就肯定会出错。当然,语言有时会变化,但这些变化通常对新手来说不那么明显。
    • 我并不立即明白print的功能可以在应用层面上重复实现。例如,有时我希望将打印输出重定向到一个操作系统的模态对话框中。
    • 虽然人们说将所有print语句重写为函数很困难,但他们实际上迫使每个Python 2.x的开发者在所有项目中都这样做。好吧,使用自动转换工具其实并不难。
    • 每个喜欢操作print函数的人,如果print是一个包装函数__print__的语句,效果也会一样好。

所以,我们能不能在Stack Overflow上对此问题给出一个权威的答案呢?

5 个回答

8

print 这个语句有一个特别的写法,就是用 >> 来指定输出到某个特定的文件。Python 里没有其他语句会用这种写法,所以这点挺特别的。

我觉得你说得对,其实大部分关于 print 语句的问题,如果有一个 __print__ 函数的话,应该都能解决。

11

我讨厌2.x版本中的打印语句,原因如下。

>>> something()
<something instance at 0xdeadbeef>
>>> print something()
<something instance at 0xdeadbeef>

这个没用的对象没有什么有用的__str__方法,没关系,我可以再看看。

>>> dir(something())
['foo', 'bar', 'baz', 'wonderful']
>>> help(something().foo)
"foo(self, callable)"

嗯……这个可调用的东西需要参数吗?

>>> something().foo(print)
    something().foo(print)
                        ^
SyntaxError: invalid syntax
>>> something().foo(lambda *args: print(*args))
    something().foo(lambda *args: print(*args))
                                      ^
SyntaxError: invalid syntax

所以……我得定义一个函数来使用。

>>> def myPrint(*args): print *args
    def myPrint(*args): print *args
                              ^
SyntaxError: invalid syntax
>>> def myPrint(*args): print args
...
>>> myPrint(1)
(1,)

真让人不舒服,或者我可以用sys.stdout.write,但这也差不多是个麻烦,因为它的行为和print完全不同。而且它看起来也不一样,这意味着我几乎永远不会记得它的存在。

在短小的、一次性的功能中使用print语句,然后再改进成使用日志记录或其他更好的方式,这样做就是不够优雅。如果print能像那些东西一样工作,特别是能和高阶函数一起使用,那它就会比你在不使用真正的日志记录或真正的调试工具时用的东西要好得多。

58

看起来你在争论,而不是在提问——你真的会接受一个答案,告诉你之前的观点是多么错误吗?

接下来谈谈你的争论点:

还有其他操作符,比如 import,我们写成一个语句, 虽然它的功能实际上 是用一个函数 __import__ 实现的。

这完全是错误的:函数 __import__(就像所有其他函数和操作符一样)并不会在“调用者”的范围内绑定任何名称(包含它的代码)。任何在“调用者范围”内绑定名称的“东西”必须是一个语句(就像赋值、defcall 一样)。你的“观点”似乎完全忽视了Python在语句和表达式之间划分的深刻而重要的区别——人们可以合理地不喜欢这种区别,但忽视它显然是错误的。

Python语句是Python编译器必须特别关注的东西——它们可能会改变名称的绑定,可能会改变控制流,或者在某些情况下需要完全从生成的字节码中移除(后者适用于 assert)。在Python 2中,print是唯一的例外;通过将其从语句列表中移除,Python 3消除了一个例外,使得一般的说法“只需保持”成立,因此它是一个更规范的语言。特殊情况不足以打破规则一直是Python的一个信条(在交互式解释器的 >>> 提示符下输入 import this 可以看到“Python的禅”),而这个语言的变化消除了一个多年来由于早期错误设计决策而不得不保留的信条的违反。

对初学者来说,操作符print 不属于一般的应用逻辑。 对他们来说,它是一个神秘的 操作符,是他们程序的高潮。 他们希望它看起来 不一样。

尽早纠正初学者的误解是非常好的事情。

所有描述基本Python 2.x的初学者书籍 现在从第一个例子开始就注定要出错。 当然,语言有时会变化,但变化 通常对新手来说不那么明显。

语言很少会以深刻且不兼容的方式变化(Python大约每十年会这样一次),而且很少有语言特性对新手来说是“高度可见的”,所以观察的总数很少——但即使在这个小范围内,我们也能轻易找到反例,其中一个对初学者高度可见的特性设计得非常糟糕,以至于移除它是值得的。例如,现代的Basic方言,如微软的Visual Basic,不使用用户输入的行号,这个“特性”在早期的Basic方言中是强制性的,既糟糕又对每个人都非常明显。现代的Lisp变种(从Scheme开始)不使用动态作用域,这是一个不好的特性,对初学者来说非常明显(通常表现为代码中难以理解的错误),基本上在他们开始编写Lisp 1.5中的函数时就显现出来了(我曾经是初学者,可以证明这对我造成了多大的困扰)。

我并不立即明白 print的功能可以在应用层面上 复制。例如,有时我想 将print从控制台重定向为 一个模态操作系统对话框。

我不太明白你的“观点”。只需将 sys.stdout 更改为你喜欢的伪文件对象,随心所欲地重定向——你有选择的权利去修改内置函数 print(在Python 2中你是没有这个权利的),但没有人强迫你这样做。

虽然人们说重写 所有print语句为一个函数很难, 但他们已经迫使每个Python 2.x 开发者为他们所有的项目 做了完全相同的事情。好吧, 用自动转换器这并不难。

2to3工具确实处理了所有这些简单的表面不兼容性——毫不费力(而且它无论如何都需要运行,以处理除了 print 之外的许多其他问题,所以人们广泛使用它)。那么,你的“观点”是什么呢?

每个喜欢操控函数print的人 如果print是一个包装函数print的语句, 也会得到同样的服务。

这样的安排并不会本质上消除一个不必要的关键字(尤其是一个不合理的不规则性,正如我上面解释的:一个没有充分理由成为语句的语句,因为编译器根本不需要以任何方式、形状或形式特别关注它!)。我不太清楚拥有这样一个底层函数是否会增加任何实际价值,但如果你有真实的用例,当然可以在Python Ideas邮件列表中提出这个案例——如果证明这个底层函数确实很珍贵,它可以被改装为在Python 2.7中与 print 语句一起使用,也可以在Python 3.2中与 print 函数一起使用。

然而,考虑一个典型的情况,人们可能想要修改内置的 print:添加关键字参数以允许一些花哨的调整。你所提出的 __print__ 函数将如何从 __print__ 语句中获取这些关键字参数呢?是否会有比 >> myfile 和尾随逗号更奇怪的语法呢?有了 print 作为一个函数,关键字参数遵循适用于所有函数和函数调用的完全正常和普通的规则——真是太好了!

所以,总的来说,print 作为一个函数更符合Python的风格,因为它消除了异常情况、不规则性和任何需要奇怪的特殊语法的需求——简单性、规律性和统一性是Python的标志。

撰写回答