返回 OSError 异常类的子类实例的逻辑在哪里?

2024-05-06

我一直在寻找一些对某些人来说可能相对愚蠢的东西,但对我来说非常有趣! :-)

输入和输出错误已合并为OSError在 Python 3.3 中,异常类层次结构发生了变化。关于内置类的一个有趣的特性OSError是这样,它在传递时返回它的子类errno and strerror

>>> OSError(2, os.strerror(2))
FileNotFoundError(2, 'No such file or directory')

>>> OSError(2, os.strerror(2)).errno
2
>>> OSError(2, os.strerror(2)).strerror
'No such file or directory'

正如你所看到的经过errno and strerror到构造函数OSError回报FileNotFoundError实例是 的子类OSError.

Python 文档:

构造函数通常实际上返回 OSError 的子类,如 OS 中所述例外情况 https://docs.python.org/3/library/exceptions.html#os-exceptions以下。特定的子类取决于最终的 errno 值。此行为仅在直接或通过别名构造 OSError 时发生,并且在子类化时不会继承。

我想编写一个以这种方式运行的子类。这主要是出于好奇,而不是现实世界的代码。我也想知道,创建子类对象的逻辑在哪里,它是用什么编码的__new__例如?如果__new__包含创建子类实例,然后继承的逻辑OSError通常会返回此行为,除非存在某种类型检查__new__:

>>> class A(OSError): pass 
>>> A(2, os.strerror(2))
A(2, 'No such file or directory')

那么必须进行类型检查:

# If passed OSError, returns subclass instance
>>> A.__new__(OSError, 2, os.strerror(2))         
FileNotFoundError(2, 'No such file or directory')

# Not OSError? Return instance of A
>>> A.__new__(A, 2, os.strerror(2)
A(2, 'No such file or directory')

我一直在深入研究 C 代码,以找出该代码的确切位置,并且由于我不是 C 方面的专家,我怀疑这确实是逻辑(坦率地说,我对此非常怀疑):

异常.c https://hg.python.org/cpython/file/5ae8756a1ae0/Objects/exceptions.c

if (myerrno && PyLong_Check(myerrno) &&
    errnomap && (PyObject *) type == PyExc_OSError) {
    PyObject *newtype;
    newtype = PyDict_GetItem(errnomap, myerrno);
    if (newtype) {
        assert(PyType_Check(newtype));
        type = (PyTypeObject *) newtype;
    }
    else if (PyErr_Occurred())
        goto error;
}
}

现在我想知道扩展的可能性errnomap来自Python本身而不使用C代码,所以OSErro可以创建用户定义类的实例,如果你问我为什么要这样做?我想说,只是为了好玩。


你是对的errnomap是保存从 errno 值到OSError子类,但不幸的是它没有导出到外部exceptions.c源文件,因此没有可移植的方法来修改它。


It is可以使用访问它highly不可移植的黑客,我纯粹出于乐趣的精神在下面提出了一种可能的方法(使用调试器)。这应该适用于任何 x86-64 Linux 系统。

>>> import os, sys
>>> os.system("""gdb -p %d \
-ex 'b PyDict_GetItem if (PyLong_AsLongLong($rsi) == -1 ? \
(PyErr_Clear(), 0) : PyLong_AsLongLong($rsi)) == 0xbaadf00d' \
-ex c \
-ex 'call PySys_SetObject("errnomap", $rdi)' --batch >/dev/null 2>&1 &""" % os.getpid()) 
0
>>> OSError(0xbaadf00d, '')
OSError(3131961357, '')
>>> sys.errnomap
{32: <class 'BrokenPipeError'>, 1: <class 'PermissionError'> [...]}
>>> class ImATeapotError(OSError):
    pass
>>> sys.errnomap[99] = ImATeapotError
>>> OSError(99, "I'm a teapot")
ImATeapotError(99, "I'm a teapot")

快速解释其工作原理:

gdb -p %d [...] --batch >/dev/null 2>&1 &

将调试器附加到当前 Python 进程(os.getpid()),在无人值守模式下(--batch),丢弃输出(>/dev/null 2>&1)和背景(&),让Python继续运行。

b PyDict_GetItem if (PyLong_AsLongLong($rsi) == -1 ? (PyErr_Clear(), 0) : PyLong_AsLongLong($rsi)) == 0xbaadf00d

当Python程序访问any字典 https://docs.python.org/3/c-api/dict.html#c.PyDict_GetItem,如果关键is an int https://docs.python.org/3/c-api/long.html#c.PyLong_AsLongLong具有魔法值(用作OSError(0xbaadf00d, '')之后);如果它不是一个 int,我们刚刚提出TypeError, so 抑制它 https://docs.python.org/3/c-api/exceptions.html#c.PyErr_Clear.

call PySys_SetObject("errnomap", $rdi)

当这种情况发生时,我们知道正在查找的字典是errnomap; 将其存储为属性sys module https://docs.python.org/3/c-api/sys.html#system-functions.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

返回 OSError 异常类的子类实例的逻辑在哪里? 的相关文章

随机推荐