python3 定制print

和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]的语句都没问题。


错误提示.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)

你可能感兴趣的:(python3 定制print)