我一直在寻找一些对某些人来说相对愚蠢的东西,但对我来说非常有趣!:-)
输入和输出错误已OSError在Python 3.3中合并,因此异常类层次结构发生了变化.关于内建类的一个有趣的特点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'
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的专家,我怀疑这是真正的逻辑和(我很坦率地说这是坦率的):
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可以创建用户定义类的实例,如果你问我为什么要这样做呢?我会说,只是为了好玩.
你是正确的,它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模块的属性.
您无法更改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)
| 归档时间: |
|
| 查看次数: |
304 次 |
| 最近记录: |