gpt4 book ai didi

python - 如何将 ctypes C 函数的打印输出打印到 Jupyter/IPython 笔记本中?

转载 作者:太空狗 更新时间:2023-10-29 23:58:06 29 4
gpt4 key购买 nike

简介

假设我有这个 C 代码:

#include <stdio.h>

// Of course, these functions are simplified for the purposes of this question.
// The actual functions are more complex and may receive additional arguments.

void printout() {
puts("Hello");
}
void printhere(FILE* f) {
fputs("Hello\n", f);
}

我正在编译为共享对象 (DLL):gcc -Wall -std=c99 -fPIC -shared example.c -o example.so

然后我导入它 into Python 3.x在里面跑JupyterIPython notebook :

import ctypes
example = ctypes.cdll.LoadLibrary('./example.so')

printout = example.printout
printout.argtypes = ()
printout.restype = None

printhere = example.printhere
printhere.argtypes = (ctypes.c_void_p) # Should have been FILE* instead
printhere.restype = None

问题

如何执行 printout()printhere() C 函数(通过 ctypes)并在 Jupyter 中打印输出/IPython 笔记本?

如果可能,我想避免编写更多的 C 代码。我更喜欢纯 Python 解决方案。

我也希望避免写入临时文件。不过,写入管道/套接字可能是合理的。

预期状态,当前状态

如果我在一个笔记本单元格中键入以下代码:

print("Hi")           # Python-style print
printout() # C-style print
printhere(something) # C-style print
print("Bye") # Python-style print

我想得到这个输出:

Hi
Hello
Hello
Bye

但是,相反,我只在笔记本中获得了 Python 风格的输出结果。 C 风格的输出被打印到启动笔记本进程的终端。

研究

据我所知,在 Jupyter/IPython notebook 中,sys.stdout 不是任何文件的包装器:

import sys

sys.stdout

# Output in command-line Python/IPython shell:
<_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'>
# Output in IPython Notebook:
<IPython.kernel.zmq.iostream.OutStream at 0x7f39c6930438>
# Output in Jupyter:
<ipykernel.iostream.OutStream at 0x7f6dc8f2de80>

sys.stdout.fileno()

# Output in command-line Python/IPython shell:
1
# Output in command-line Jupyter and IPython notebook:
UnsupportedOperation: IOStream has no fileno.

相关问题和链接:

以下两个链接使用涉及创建临时文件的类似解决方案。但是,在实现此类解决方案时必须小心,以确保以正确的顺序打印 Python 风格的输出和 C 风格的输出。

是否可以避免临时文件?

我尝试使用 C open_memstream() 寻找解决方案并将返回的 FILE* 分配给 stdout,但它不起作用 because stdout cannot be assigned .

然后我尝试获取 open_memstream() 返回的流的 fileno(),但我不能 because it has no file descriptor .

然后我看了freopen() , 但它的 API requires passing a filename .

然后查看Python的标准库,发现tempfile.SpooledTemporaryFile() ,它是内存中的临时文件类对象。但是,一旦调用 fileno(),它就会被写入磁盘。

到目前为止,我找不到任何仅内存解决方案。无论如何,我们很可能需要使用一个临时文件。 (这没什么大不了的,只是我希望避免的一些额外开销和额外清理。)

也许可以使用 os.pipe() , 但如果不 fork ,这似乎很难做到。

最佳答案

我终于找到了解决方案。它需要将整个单元包装在上下文管理器中(或仅包装 C 代码)。它还使用一个临时文件,因为我找不到任何解决方案而不使用它。

完整的笔记本可作为 GitHub Gist 获得:https://gist.github.com/denilsonsa/9c8f5c44bf2038fd000f


第 1 部分:在 Python 中准备 C 库

import ctypes

# use_errno parameter is optional, because I'm not checking errno anyway.
libc = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True)

class FILE(ctypes.Structure):
pass

FILE_p = ctypes.POINTER(FILE)

# Alternatively, we can just use:
# FILE_p = ctypes.c_void_p

# These variables, defined inside the C library, are readonly.
cstdin = FILE_p.in_dll(libc, 'stdin')
cstdout = FILE_p.in_dll(libc, 'stdout')
cstderr = FILE_p.in_dll(libc, 'stderr')

# C function to disable buffering.
csetbuf = libc.setbuf
csetbuf.argtypes = (FILE_p, ctypes.c_char_p)
csetbuf.restype = None

# C function to flush the C library buffer.
cfflush = libc.fflush
cfflush.argtypes = (FILE_p,)
cfflush.restype = ctypes.c_int

第 2 部分:构建我们自己的上下文管理器以捕获标准输出

import io
import os
import sys
import tempfile
from contextlib import contextmanager

@contextmanager
def capture_c_stdout(encoding='utf8'):
# Flushing, it's a good practice.
sys.stdout.flush()
cfflush(cstdout)

# We need to use a actual file because we need the file descriptor number.
with tempfile.TemporaryFile(buffering=0) as temp:
# Saving a copy of the original stdout.
prev_sys_stdout = sys.stdout
prev_stdout_fd = os.dup(1)
os.close(1)

# Duplicating the temporary file fd into the stdout fd.
# In other words, replacing the stdout.
os.dup2(temp.fileno(), 1)

# Replacing sys.stdout for Python code.
#
# IPython Notebook version of sys.stdout is actually an
# in-memory OutStream, so it does not have a file descriptor.
# We need to replace sys.stdout so that interleaved Python
# and C output gets captured in the correct order.
#
# We enable line_buffering to force a flush after each line.
# And write_through to force all data to be passed through the
# wrapper directly into the binary temporary file.
temp_wrapper = io.TextIOWrapper(
temp, encoding=encoding, line_buffering=True, write_through=True)
sys.stdout = temp_wrapper

# Disabling buffering of C stdout.
csetbuf(cstdout, None)

yield

# Must flush to clear the C library buffer.
cfflush(cstdout)

# Restoring stdout.
os.dup2(prev_stdout_fd, 1)
os.close(prev_stdout_fd)
sys.stdout = prev_sys_stdout

# Printing the captured output.
temp_wrapper.seek(0)
print(temp_wrapper.read(), end='')

乐趣部分:使用它!

libfoo = ctypes.CDLL('./foo.so')

printout = libfoo.printout
printout.argtypes = ()
printout.restype = None

printhere = libfoo.printhere
printhere.argtypes = (FILE_p,)
printhere.restype = None


print('Python Before capturing')
printout() # Not captured, goes to the terminal

with capture_c_stdout():
print('Python First')
printout()
print('Python Second')
printhere(cstdout)
print('Python Third')

print('Python After capturing')
printout() # Not captured, goes to the terminal

输出:

Python Before capturing
Python First
C printout puts
Python Second
C printhere fputs
Python Third
Python After capturing

学分和进一步的工作

这个解决方案是阅读我在问题中链接的所有链接以及大量试验和错误的结果。

此解决方案仅重定向 stdout,同时重定向 stdoutstderr 可能会很有趣。现在,我将此作为练习留给读者。 ;)

此外,此解决方案中没有异常处理(至少目前还没有)。

关于python - 如何将 ctypes C 函数的打印输出打印到 Jupyter/IPython 笔记本中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35745541/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com