和C语言一样,python的print功能通过内建函数实现,而不是通过statement实现。这意味着,名称print可以被重新定义,和一般函数无异。
例如,我们想设置print的默认参数为sep=', ',end='.\n',可做如下操作:
pprint = print # 保存内建print的指针
print = lambda *objects: pprint(*objects, sep=', ', end='.\n') # 令print以所需参数调用pprint
print(1, 2) # 测试
>> 1, 2.
成功。
为什么非要先把print保存在pprint里?为什么不能这样写:
print = lambda *object: print(*object, sep=', ', end='.\n')
print(1, 2)
毕竟,类似 L = L + [3]的语句都没问题。
![](https://img.haomeiwen.com/i16231433/6027fa51627ee68c.png)
这是怎么回事?
首先,这牵扯到函数中变量解引用的搜索顺序。即,local、nonlocal(若干层)、global、built-in。执行print(1, 2)时,lambda的local中没有print,global中有print。但global空间中的print是我们自己定义的,只接受一个*object参数的print,不是built-in print。
但疑云未散,按列表操作 L = L + [3] 的精神,名称L替换为所引用的列表,和 [3] 拼接,结果命名为L。类似地,定义时,将lambda中的print替换为built-in print,生成的函数命名为print,错在何处?原因在于,函数体不在定义时执行。而解引用发生在语句执行时,也就是函数被调用的时候。调用时(不是定义时)print指什么,才决定函数执行的效果。本例中,函数执行时名称print已经被自己的定义语句覆盖,意外地产生了调用自身的结果。
如此,python函数对象就像一个记录着几行代码的清单,只有在调用时才会执行所持有的代码,代码中解引用的操作是在调用时发生的。闭包 函数工厂中介绍了一个更微妙的例子。
另外,按照错误提示改变lambda的参数列表,使之接收sep和end,也不能达到目的(会产生无限递归)。原因还是,此时global空间中的print遮蔽了built-in空间中的print,lambda中调用built-in中print的意图无法实现。
覆盖了内建print的print需要调用内建print,所以内建print必须用某种方式保存下来。问题转化为如何进行函数状态保存,那么闭包、默认参数、函数属性、类,都是可以尝试的方法。
函数闭包:
def makeopen():
pprint = print
return lambda *objects: pprint(*objects, sep=', ', end='.\n')
open = makeopen()
open(1, 2, 4)
>> 1, 2, 4.
其他方法请君自行探索。
就这个具体问题而言,不使用内建print函数或许是最好的方法:
import sys
def print(*args, sep=', ', end='.\n', file=sys.stdout):
file.write(sep.join(str(arg) for arg in args) + end)
网友评论