我想修补Python列表,特别是用自定义代码替换__setitem__
方法。请注意,我不是试图扩展,而是覆盖内置类型。例如:
>>> # Monkey Patch
... # Replace list.__setitem__ with a Noop
...
>>> myList = [1,2,3,4,5]
>>> myList[0] = "Nope"
>>> myList
[1, 2, 3, 4, 5]
是的,我知道对于python代码来说,这是一个彻头彻尾的变态事情。不,我的用例并没有多大意义。尽管如此,可以做到吗?
forbiddenfruit
模块允许修补C内置函数,但在尝试覆盖列表方法时不起作用我实际上设法覆盖了方法本身,如下所示:
import ctypes
def magic_get_dict(o):
# find address of dict whose offset is stored in the type
dict_addr = id(o) + type(o).__dictoffset__
# retrieve the dict object itself
dict_ptr = ctypes.cast(dict_addr, ctypes.POINTER(ctypes.py_object))
return dict_ptr.contents.value
def magic_flush_mro_cache():
ctypes.PyDLL(None).PyType_Modified(ctypes.cast(id(object), ctypes.py_object))
print(list.__setitem__)
dct = magic_get_dict(list)
dct['__setitem__'] = lambda s, k, v: s
magic_flush_mro_cache()
print(list.__setitem__)
x = [1,2,3,4,5]
print(x.__setitem__)
x.__setitem__(0,10)
x[1] = 20
print(x)
其中输出以下内容:
➤ python3 override.py
<slot wrapper '__setitem__' of 'list' objects>
<function <lambda> at 0x10de43f28>
<bound method <lambda> of [1, 2, 3, 4, 5]>
[1, 20, 3, 4, 5]
但是如输出所示,这似乎不会影响设置项目的正常语法(x[0] = 0
)
作为一个较小的替代方案,如果我能够修补单个列表的实例,这也可以工作。也许通过将列表的类指针更改为自定义类。
答案 0 :(得分:3)
聚会有点晚了,不过,这就是答案。
正如 user2357112 在上面的评论中所暗示的,修改 dict 是不够的,因为 __getitme__
(和其他双下划线名称)被映射到它们的插槽,并且不会在不调用 {{1 }}(没有导出,所以会有点棘手)。
受上述评论的启发,这里有一个使 update_slot
成为特定列表的空操作的工作示例:
__setitem__
编辑
请参阅 here 为什么不支持开始。主要是作为 explained by Guido van Rossum:
这是故意禁止的,以防止对内置类型的意外致命更改(对您从未想过的代码部分是致命的)。此外,这样做是为了防止更改影响驻留在地址空间中的不同解释器,因为内置类型(与用户定义的类不同)在所有此类解释器之间共享。
我还搜索了 cpython 中 # assuming v3.8 (tested on Windows x64 and Ubuntu x64)
# definition of PyTypeObject: https://github.com/python/cpython/blob/3.8/Include/cpython/object.h#L177
# no extensive testing was performed and I'll let other decide if this is a good idea or not, but it's possible
import ctypes
Py_TPFLAGS_HEAPTYPE = (1 << 9)
# calculate the offset of the tp_flags field
offset = ctypes.sizeof(ctypes.c_ssize_t) * 1 # PyObject_VAR_HEAD.ob_base.ob_refcnt
offset += ctypes.sizeof(ctypes.c_void_p) * 1 # PyObject_VAR_HEAD.ob_base.ob_type
offset += ctypes.sizeof(ctypes.c_ssize_t) * 1 # PyObject_VAR_HEAD.ob_size
offset += ctypes.sizeof(ctypes.c_void_p) * 1 # tp_name
offset += ctypes.sizeof(ctypes.c_ssize_t) * 2 # tp_basicsize+tp_itemsize
offset += ctypes.sizeof(ctypes.c_void_p) * 1 # tp_dealloc
offset += ctypes.sizeof(ctypes.c_ssize_t) * 1 # tp_vectorcall_offset
offset += ctypes.sizeof(ctypes.c_void_p) * 7 # tp_getattr+tp_setattr+tp_as_async+tp_repr+tp_as_number+tp_as_sequence+tp_as_mapping
offset += ctypes.sizeof(ctypes.c_void_p) * 6 # tp_hash+tp_call+tp_str+tp_getattro+tp_setattro+tp_as_buffer
tp_flags = ctypes.c_ulong.from_address(id(list) + offset)
assert(tp_flags.value == list.__flags__) # should be the same
lst1 = [1,2,3]
lst2 = [1,2,3]
dont_set_me = [lst1] # these lists cannot be set
# define new method
orig = list.__setitem__
def new_setitem(self, *args):
if [_ for _ in dont_set_me if _ is self]: # check for identical object in list
print('Nope')
else:
return orig(self, *args)
tp_flags.value |= Py_TPFLAGS_HEAPTYPE # add flag, to allow type_setattro to continue
list.__setitem__ = new_setitem # set method, this will already call PyType_Modified and update_slot
tp_flags.value &= (~Py_TPFLAGS_HEAPTYPE) # remove flag
print(lst1, lst2) # > [1, 2, 3] [1, 2, 3]
lst1[0],lst2[0]='x','x' # > Nope
print(lst1, lst2) # > [1, 2, 3] ['x', 2, 3]
的所有用法,它们似乎都与 GC 或某些验证有关。
所以我想如果:
你没事<此处的通用免责声明>。
答案 1 :(得分:0)
无法完成。如果你强制使用CTypes,那么你将比其他任何东西更快地崩溃Python运行时 - 因为很多事情只是使用Python数据类型。