25

我被一个相当复杂的 Python 模块困住了,它不返回有用的错误代码(它实际上默默地失败了)。但是,它调用的底层 C 库设置了 errno。

通常 errno 来自 OSError 属性,但由于我没有异常,我无法理解它。

使用 ctypes,libc.errno 不起作用,因为 errno 是 GNU libc 中的宏。Python 2.6 有一些功能,但 Debian 仍然使用 Python 2.5。将 C 模块插入到我的纯 Python 程序中只是为了读取 errno 让我感到厌恶。

有没有办法访问errno?仅限 Linux 的解决方案很好,因为被包装的库是仅限 Linux 的。我也不必担心线程,因为在这可能失败的时间内我只运行一个线程。

4

6 回答 6

18

这是允许访问的代码片段errno

from ctypes import *

libc = CDLL("libc.so.6")

get_errno_loc = libc.__errno_location
get_errno_loc.restype = POINTER(c_int)

def errcheck(ret, func, args):
    if ret == -1:
        e = get_errno_loc()[0]
        raise OSError(e)
    return ret

copen = libc.open
copen.errcheck = errcheck

print copen("nosuchfile", 0)

重要的是您errno在函数调用后尽快检查,否则它可能已经被覆盖。

于 2009-03-19T07:20:16.067 回答
14

更新:在Python 2.6+ 上,使用ctypes.get_errno().

蟒蛇2.5

下面的代码不可靠(或全面,errno可以定义多种方法)但它应该让你开始(或重新考虑你在一个微小的扩展模块上的位置(毕竟在 Debian 上python setup.py install或者easy_install构建它应该没有问题)) . 来自 http://codespeak.net/pypy/dist/pypy/rpython/lltypesystem/ll2ctypes.py

if not hasattr(ctypes, 'get_errno'):
    # Python 2.5 or older
    if sys.platform == 'win32':
        standard_c_lib._errno.restype = ctypes.POINTER(ctypes.c_int)
        def _where_is_errno():
            return standard_c_lib._errno()

    elif sys.platform in ('linux2', 'freebsd6'):
        standard_c_lib.__errno_location.restype = ctypes.POINTER(ctypes.c_int)
        def _where_is_errno():
            return standard_c_lib.__errno_location()

    elif sys.platform in ('darwin', 'freebsd7'):
        standard_c_lib.__error.restype = ctypes.POINTER(ctypes.c_int)
        def _where_is_errno():
            return standard_c_lib.__error()
    ctypes.get_errno = lambda: _where_is_errno().contents.value 

哪里standard_c_lib

def get_libc_name():
    if sys.platform == 'win32':
        # Parses sys.version and deduces the version of the compiler
        import distutils.msvccompiler
        version = distutils.msvccompiler.get_build_version()
        if version is None:
            # This logic works with official builds of Python.
            if sys.version_info < (2, 4):
                clibname = 'msvcrt'
            else:
                clibname = 'msvcr71'
        else:
            if version <= 6:
                clibname = 'msvcrt'
            else:
                clibname = 'msvcr%d' % (version * 10)

        # If python was built with in debug mode
        import imp
        if imp.get_suffixes()[0][0] == '_d.pyd':
            clibname += 'd'

        return clibname+'.dll'
    else:
        return ctypes.util.find_library('c')

# Make sure the name is determined during import, not at runtime
libc_name = get_libc_name() 
standard_c_lib = ctypes.cdll.LoadLibrary(get_libc_name())
于 2009-03-19T07:19:50.483 回答
9

看起来你可以使用这个补丁,它将为你提供ctypes.get_errno/set_errno

http://bugs.python.org/issue1798

这是实际应用于存储库的补丁:

http://svn.python.org/view?view=rev&revision=63977

否则,添加一个新的 C 模块,它只会返回 errno /is/ disgusting,但您正在使用的库也是如此。我会这样做,而不是自己修补 python。

于 2009-03-19T04:10:16.990 回答
9

放弃并通过 C 标头进行跟踪。

import ctypes
c = ctypes.CDLL("libc.so.6")
c.__errno_location.restype = ctypes.POINTER(ctypes.c_int)
c.write(5000, "foo", 4)
print c.__errno_location().contents # -> c_long(9)

它在 python 命令提示符下不起作用,因为它将 errno 重置为从标准输入读取。

一旦你知道了__errno_location 的魔力,这看起来就像一个常见的模式。但是只有errno我很迷茫。

于 2009-03-19T07:18:23.450 回答
1

我不确定这是否是您和 Jerub 所指的,但是您可以编写一个非常短的 C 扩展,它只导出 errno,即使用python 语言接口

否则,我同意你的观点,必须添加这一小段编译代码是一件很痛苦的事情。

于 2009-03-19T05:30:18.640 回答
1

ctypes 实际上提供了一种访问 python 的 c 实现的标准方法,它使用 errno。除了我的(linux)系统之外,我还没有在其他任何东西上测试过这个,但这应该是非常便携的:

ctypes.c_int.in_dll(ctypes.pythonapi,"errno")

它返回一个包含当前值的 c_int。

于 2011-05-29T21:47:21.410 回答