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

dir*_*obs 9 python

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

输入和输出错误已OSError在Python 3.3中合并,因此异常类层次结构发生了变化.关于内建类的一个有趣的特点OSError是,它通过时返回它的一个子类errnostrerror

>>> 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'
Run Code Online (Sandbox Code Playgroud)

正如你可以看到传递errno和返回实例strerror的构造函数,它是一个子类.OSErrorFileNotFoundErrorOSError

Python Doc:

构造函数实际上经常返回OSError的子类,如下面的OS 异常中所述.特定的子类取决于最终的errno值.此行为仅在直接构造OSError或通过别名构造时发生,并且在子类化时不会继承.

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

>>> class A(OSError): pass 
>>> A(2, os.strerror(2))
A(2, 'No such file or directory')
Run Code Online (Sandbox Code Playgroud)

必须进行类型检查:

# 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')
Run Code Online (Sandbox Code Playgroud)

我一直在挖掘C代码,以找出这个代码的确切位置,因为我不是C的专家,我怀疑这是真正的逻辑和(我很坦率地说这是坦率的):

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;
}
}
Run Code Online (Sandbox Code Playgroud)

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

eca*_*mur 6

你是正确的,它errnomap是保存从errno值到OSError子类的映射的变量,但遗憾的是它不会被导出到exceptions.c源文件之外,因此没有可移植的方法来修改它.


可以利用访问它的高度不可移植的黑客,我在下面纯粹的乐趣精神提出这样做的一种可能的方法(使用调试器).这适用于任何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")
Run Code Online (Sandbox Code Playgroud)

快速解释这是如何工作的:

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

将调试器附加到当前的Python进程(os.getpid()),无人参与模式(--batch),丢弃output(>/dev/null 2>&1)和后台(&),允许Python继续运行.

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

当Python程序访问任何字典时,如果该键int具有魔术值的话,则中断(OSError(0xbaadf00d, '')稍后使用); 如果它不是int,我们刚刚提出TypeError,所以压制它.

call PySys_SetObject("errnomap", $rdi)

当发生这种情况时,我们知道正在查找的字典是errnomap; 将其存储为sys模块的属性.


kin*_*all 0

您无法更改OSErrorPython 的行为,因为它不是在 Python 中实现的。

对于用 Python 实现的类,您可以__new__这样编写:如果在基类上调用它,则仅返回子类。那么该行为将不会被继承。

class MyClass(object):
    def __new__(cls, sub=0, _subtypes={}):
        if cls is MyClass:
             if sub not in _subtypes:
                 _subtypes[sub] = type("MyClass(%s)" % sub, (MyClass,), {})
             return _subtypes[sub](sub)
        return object.__new__(cls, sub)
    def __init__(self, sub):
        assert type(self).__name__ == "MyClass(%s)" % sub

class SubClass(MyClass):
     def __init__(self, sub=None):
         assert type(self).__name__ == "SubClass"

print(MyClass(1))    # <__main__.MyClass(1) object at 0x01EB1EB0>
print(SubClass())    # <__main__.SubClass object at 0x01EB1CD0>
Run Code Online (Sandbox Code Playgroud)