我一直在寻找一些对某些人来说可能比较愚蠢的东西,但对我来说非常有趣! : - )
输入和输出错误已在Python 3.3中与OSError
合并,因此异常类层次结构发生了变化。关于内置类OSError
的一个有趣的特性是,它在传递errno
和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
和strerror
传递给OSError
的构造函数会返回FileNotFoundError
实例,它是OSError
的子类。
Python Doc:
构造函数实际上经常返回OSError的子类,如下面的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的专家,我怀疑这是真正的逻辑和(我对这一点很坦诚) ):
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;
}
}
现在我想知道在不使用C代码的情况下从Python本身扩展errnomap
的可能性,以便OSErro
可以创建用户定义类的实例,如果你问我为什么会这样做去做?我会说,只是为了好玩。
答案 0 :(得分: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")
快速解释其工作原理:
gdb -p %d [...] --batch >/dev/null 2>&1 &
以无人参与模式(os.getpid()
)将调试器附加到当前Python进程(--batch
),丢弃输出(>/dev/null 2>&1
)并在后台(&
) ,允许Python继续运行。
b PyDict_GetItem if (PyLong_AsLongLong($rsi) == -1 ? (PyErr_Clear(), 0) : PyLong_AsLongLong($rsi)) == 0xbaadf00d
当Python程序accesses any dictionary时,如果键is an int
具有魔术值(稍后用作OSError(0xbaadf00d, '')
),则中断;如果它不是int,我们刚刚提出TypeError
,那么suppress it。
call PySys_SetObject("errnomap", $rdi)
当发生这种情况时,我们知道正在查找的字典是errnomap
; store it as an attribute on the sys
module
答案 1 :(得分:0)
您无法从Python更改OSError
的行为,因为它未在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>