在运行以下脚本时,我遇到了一些奇怪的行为。
如您所见,似乎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
),但也许通过使用调试器,我可以打印出东西,看看是什么,并弄清楚至少(如果不是全部)流程。我非常想了解一般情况下发生的事情,而不是理所当然。
在您的时间里提前致谢!
答案 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
不再很神秘了吧?
当然stdout
和stderr
是独立的流,具有自己的缓冲区。 (默认情况下,与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是行缓冲的。