打印管道是否连接到引擎盖下的不同流?

2024-10-04 11:26:54 发布

您现在位置:Python中文网/ 问答频道 /正文

在运行下面的脚本时,我(对我)遇到了一点奇怪的行为。你知道吗

如您所见,似乎write被多次调用,我想知道为什么会这样,因为我已经显式地重写了file=sys.stdout行为。你知道吗

究竟如何打印管道流下引擎盖,它管道到所有渠道?它是否有一些默认行为,the docs除了以下几点之外,不是很具体:

The file argument must be an object with a write(string) method; if it is not present or None, sys.stdout will be used.

测试脚本

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模块来获取调用者,也许可以看到谁实际调用了write,但是我得到了module,idk为什么:(这很明显吗?你知道吗


进一步的问题:

有没有办法调试Python之外的函数并进入底层的C调用?因为主要的Python分布是CPython,如果我的理解是正确的,Python只是底层C代码的api。在Python中的调用最终会被转换成C在引擎盖下的调用。例如,我发现printis defined as follows in C,但我很难理解那里发生了什么(因为,呃,我不知道C),但也许通过使用调试器,我可以打印出一些东西,看看是什么,如果不是所有的话,至少可以找出流程。我很想了解引擎盖下发生了什么,而不是想当然。你知道吗

提前谢谢你的时间!你知道吗


Tags: thedebugcaterpillarreyousysatfile
2条回答

得到您看到的结果是因为builtin_print()调用PyFile_Write*()两次,一次是为了print the argument,另一次是为了print the EOL。它们是无序的,因为默认情况下stderr是无缓冲的,stdout是行缓冲的。你知道吗

当答案非常简单的时候,你在寻找一些非常复杂的东西。你知道吗

我甚至不知道“管道到所有通道”是什么意思,但是print并没有这样做。它所做的就是调用传递给它的write对象。你知道吗

但是,它为每个参数调用一次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是无缓冲的。)因此排序不是您天真地期望的,而是有意义的。如果只添加flushes,则输出将变成:

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

(结尾有一个空行)。你知道吗


对于您的奖励问题:

I tried to use inspect module to get the caller, maybe see who does the actual call to write but I get module, idk why :( is this obvious?

我假设你做了类似于inspect.stack()[1].function的事?如果是这样,那么您要检查的代码就是模块中的顶级代码,因此inspect将其显示为名为<module>的伪函数。你知道吗

Is there any way to debug a function beyond Python and go into the underlying C call?

当然。只需在lldb、gdb、微软的调试器或其他通常用于调试二进制程序的工具下运行CPython本身。您可以将断点放在ceval循环或特定的C API函数中或任何您想要的地方。您可能希望对CPython进行调试构建(do./configure help以查看选项),从而使其更加完善。你知道吗

Because well the main Python distribution, is CPython, and if my understanding is correct, Python is just an api for the underlying C code.

嗯,不是很好。它是一个编译器和一个字节码解释器。字节码解释器很大程度上使用了为扩展/嵌入接口公开的相同的C API,但是重叠不是100%;有些地方它处理的结构低于C API级别。你知道吗

A call in Python gets translated to a C call under the hood eventually. So for instance I found out that the print is defined as follows in C, but it's tough for me to understand what's going on there (because, erm, I don't know C) but maybe by going with a debugger I could print stuff out, see what is what and figure out maybe at least the flow if not everything. I'd very much like to understand what's going on under the hood in general instead of taking stuff for granted.

是的,您可以这样做,但是您需要同时了解C和cpythonapi(例如,如何找到等价于__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

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

当然,您甚至可以在PyPy或Jython中调试这些代码,看看它是否与“内置”级别以上的CPython有所不同。你知道吗

相关问题 更多 >