Python:打印管道是否可以在后台进行传输?

时间:2018-06-20 18:24:26

标签: python python-3.x python-internals

在运行以下脚本时,我遇到了一些奇怪的行为。

如您所见,似乎write被多次调用了,我不知道为什么会这样,因为我已经明确覆盖了file=sys.stdout的行为。

引擎盖下的打印管道流如何精确地输送到所有通道?它是否具有某些默认行为,the docs除了以下内容以外,不是很具体:

  

file参数必须是带有write(string)方法的对象;如果它   不存在或没有,将使用sys.stdout。

测试脚本

import sys

def debug(*args, **kwargs):
    pass

def _debugwrite(obj):
    print("You're looking at Attila, the psychopathic killer, the caterpillar")
    out = sys.stderr
    out.write(obj)

debug.write = _debugwrite

print("Don't you ever disrespect the caterpillar", file=debug)

输出

You're looking at Attila, the psychopathic killer, the caterpillar
You're looking at Attila, the psychopathic killer, the caterpillar
Don't you ever disrespect the caterpillar

我期望的结果

You're looking at Attila, the psychopathic killer, the caterpillar
Don't you ever disrespect the caterpillar

我尝试过的事情

我试图使用inspect模块来获取调用者,也许看到实际的调用是谁写的,但是我却得到了module,idk为什么:(这很明显吗?


其他问题

除了Python以外,还有什么方法可以调试函数并进入基础的C调用?因为主要的Python分布是CPython,如果我的理解是正确的,那么Python只是基础api代码的C。最终,Python中的呼叫被转换为C呼叫。例如,我发现了print is defined as follows in C,但是我很难理解那里发生了什么(因为,嗯,我不知道C),但也许通过使用调试器,我可以打印出东西,看看是什么,并弄清楚至少(如果不是全部)流程。我非常想了解一般情况下发生的事情,而不是理所当然。

在您的时间里提前致谢!

2 个答案:

答案 0 :(得分:4)

答案很简单时,您正在寻找的是非常复杂的东西。

我什至不知道“通向所有渠道”的含义,但是print却什么也不做。它所做的只是在您传递的write对象上调用file

但是,它对每个参数调用一次write,对每个sep调用一次,对end调用一次。

因此,这一行:

print("Don't you ever disrespect the caterpillar", file=debug)

…大致等同于:

debug.write(str("Don't you ever disrespect the caterpillar"))
debug.write("\n")

...当然,这意味着您两次收到额外的print消息。


顺便说一句,以便将来调试或理解这样的事情:如果您将额外的print更改为包含repr(obj),那么将会很明显:​​

def _debugwrite(obj):
    print("stderring " + repr(obj))
    out = sys.stderr
    out.write(obj)

则输出为:

stderring "Don't you ever disrespect the caterpillar"
stderring '\n'
Don't you ever disrespect the caterpillar

不再很神秘了吧?


当然stdoutstderr是独立的流,具有自己的缓冲区。 (默认情况下,与TTY通话时,stdout是行缓冲的,而stderr是未缓冲的。)因此排序不是您天真希望的,但是这是有道理的。如果仅添加flush es,则输出将变为:

stderring "Don't you ever disrespect the caterpillar"
Don't you ever disrespect the caterpillarstderring '\n'

(末尾有空白行)。


关于奖金问题:

  

我试图使用检查模块来获取调用者,也许看看实际的调用是谁写的,但是我得到了模块,idk为什么:(这很明显吗?

我假设您做过类似inspect.stack()[1].function的事情?如果是这样,则您要检查的代码是模块中的顶级代码,因此inspect会将其显示为名为<module>的伪函数。

  

除了Python以外,还有什么方法可以调试函数并进入基础C调用?

好的。只需在lldb,gdb,Microsoft的调试器或通常用于调试二进制程序的其他程序下运行CPython本身。您可以将断点放在ceval循环中或特定的C API函数中,也可以放置在任意位置。您可能需要构建CPython的调试版本(执行./configure --help来查看选项),以使其变得更好。

  

因为Python的主要发行版是CPython,如果我的理解是正确的,Python只是底层C代码的api。

嗯,不是相当。它是一个编译器和一个字节码解释器。该字节码解释器很大程度上使用与扩展/嵌入接口公开的相同的C API,但是重叠并不是100%;在某些地方它处理C API级别以下的结构。

  

Python中的调用最终在后台转换为C调用。例如,我发现打印在C中定义如下,但是对我来说很难理解那里发生了什么(因为erm,我不了解C),但是也许通过调试器我可以打印出东西弄清楚是什么,如果不是全部,至少要弄清楚流程。我非常想了解一般情况下发生的事情,而不是理所当然。

是的,您可以这样做,但是您将需要了解C和CPython API(例如,如何查找与__call__等效的C插槽之类的东西),以找出将断点放在哪里和开始跟踪。

对于这种情况,只用Python编写包装程序并在Python中调试它们就容易得多。例如:

import builtins
def print(*args, **kwargs):
    return builtins.print(*args, **kwargs)

或者,如果您担心print在其他模块中而不是在您的模块中被调用,您甚至可以在builtins中对其进行阴影:

builtins._print = builtins.print
def print(*args, **kwargs):
    return builtins._print(*args, **kwargs)
builtins.print = print

现在,您只需使用pdb就可以在Python级别中断对print的每次调用,而不必担心C。

当然,您甚至可以在PyPy或Jython中调试此代码,或者查看它是否与“内置”级别以上的CPython有什么不同。

答案 1 :(得分:1)

您得到的结果是您看到的,因为builtin_print()调用PyFile_Write*() 两次,一次是为了print the argument,然后是print the EOL。它们是乱序的,因为默认情况下stderr是未缓冲的,而stdout是行缓冲的。