Noc*_*wer 14 python ctypes tuples creation self-reference
在多年前在一个论坛中看到一个从未解决过的对话之后,它让我想知道如何正确地创建一个引用自身的元组.从技术上讲,这是一个非常糟糕的主意,因为元组应该是不可变的.一个不可变对象怎么可能包含自己呢?但是,这个问题不是关于最佳实践,而是关于Python中可能的内容的查询.
import ctypes
def self_reference(array, index):
if not isinstance(array, tuple):
raise TypeError('array must be a tuple')
if not isinstance(index, int):
raise TypeError('index must be an int')
if not 0 <= index < len(array):
raise ValueError('index is out of range')
address = id(array)
obj_refcnt = ctypes.cast(address, ctypes.POINTER(ctypes.c_ssize_t))
obj_refcnt.contents.value += 1
if ctypes.cdll.python32.PyTuple_SetItem(ctypes.py_object(array),
ctypes.c_ssize_t(index),
ctypes.py_object(array)):
raise RuntimeError('PyTuple_SetItem signaled an error')
Run Code Online (Sandbox Code Playgroud)
前一个函数旨在访问Python的C API,同时牢记内部结构和数据类型.但是,运行该函数时通常会生成以下错误.通过未知的过程,之前可以通过类似的技术创建自引用元组.
问题:如何self_reference
修改功能以始终如一地工作?
>>> import string
>>> a = tuple(string.ascii_lowercase)
>>> self_reference(a, 2)
Traceback (most recent call last):
File "<pyshell#56>", line 1, in <module>
self_reference(a, 2)
File "C:/Users/schappell/Downloads/srt.py", line 15, in self_reference
ctypes.py_object(array)):
WindowsError: exception: access violation reading 0x0000003C
>>>
Run Code Online (Sandbox Code Playgroud)
编辑:这是与解释器的两个不同的对话,有点令人困惑.如果我正确理解文档,上面的代码似乎是正确的.但是,下面的对话似乎彼此冲突,而且self_reference
上面的功能也相互冲突.
会话1:
Python 3.2.3 (default, Apr 11 2012, 07:15:24) [MSC v.1500 32 bit (Intel)]
on win32
Type "copyright", "credits" or "license()" for more information.
>>> from ctypes import *
>>> array = tuple(range(10))
>>> cast(id(array), POINTER(c_ssize_t)).contents.value
1
>>> cast(id(array), POINTER(c_ssize_t)).contents.value += 1
>>> cast(id(array), POINTER(c_ssize_t)).contents.value
2
>>> array
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
>>> cdll.python32.PyTuple_SetItem(c_void_p(id(array)), 0,
c_void_p(id(array)))
Traceback (most recent call last):
File "<pyshell#6>", line 1, in <module>
cdll.python32.PyTuple_SetItem(c_void_p(id(array)), 0,
c_void_p(id(array)))
WindowsError: exception: access violation reading 0x0000003C
>>> cdll.python32.PyTuple_SetItem(c_void_p(id(array)), 0,
c_void_p(id(array)))
Traceback (most recent call last):
File "<pyshell#7>", line 1, in <module>
cdll.python32.PyTuple_SetItem(c_void_p(id(array)), 0,
c_void_p(id(array)))
WindowsError: exception: access violation reading 0x0000003C
>>> array
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
>>> cdll.python32.PyTuple_SetItem(c_void_p(id(array)), 0,
c_void_p(id(array)))
0
>>> array
((<NULL>, <code object __init__ at 0x02E68C50, file "C:\Python32\lib
kinter\simpledialog.py", line 121>, <code object destroy at 0x02E68CF0,
file "C:\Python32\lib kinter\simpledialog.py", line 171>, <code object
body at 0x02E68D90, file "C:\Python32\lib kinter\simpledialog.py",
line 179>, <code object buttonbox at 0x02E68E30, file "C:\Python32\lib
kinter\simpledialog.py", line 188>, <code object ok at 0x02E68ED0, file
"C:\Python32\lib kinter\simpledialog.py", line 209>, <code object
cancel at 0x02E68F70, file "C:\Python32\lib kinter\simpledialog.py",
line 223>, <code object validate at 0x02E6F070, file "C:\Python32\lib
kinter\simpledialog.py", line 233>, <code object apply at 0x02E6F110, file
"C:\Python32\lib kinter\simpledialog.py", line 242>, None), 1, 2, 3, 4,
5, 6, 7, 8, 9)
>>>
Run Code Online (Sandbox Code Playgroud)
会话2:
Python 3.2.3 (default, Apr 11 2012, 07:15:24) [MSC v.1500 32 bit (Intel)]
on win32
Type "copyright", "credits" or "license()" for more information.
>>> from ctypes import *
>>> array = tuple(range(10))
>>> cdll.python32.PyTuple_SetItem(c_void_p(id(array)), c_ssize_t(1),
c_void_p(id(array)))
0
>>> array
(0, (...), 2, 3, 4, 5, 6, 7, 8, 9)
>>> array[1] is array
True
>>>
Run Code Online (Sandbox Code Playgroud)
AFAICT,你看到问题的原因是因为PyTuple_SetItem
如果元组的引用计数不完全是一个则失败.这是为了防止在元组已经在别处使用过时使用该函数.我不确定为什么你会从中获得访问冲突,但可能是因为抛出的异常PyTuple_SetItem
没有得到妥善处理.此外,数组似乎变异到其他对象的原因是因为PyTuple_SetItem
DECREF是每个失败的元组; 在两次失败之后,refcount为零,因此释放了对象(并且其他一些对象显然最终位于相同的内存位置).
pythonapi
在ctypes中使用对象是访问Python DLL的首选方法,因为它正确处理Python异常并保证使用正确的调用约定.
我没有Windows机器来测试它,但以下在Mac OS X(Python 2.7.3和3.2.2)上工作正常:
import ctypes
def self_reference(array, index):
# Sanity check. We can't let PyTuple_SetItem fail, or it will Py_DECREF
# the object and destroy it.
if not isinstance(array, tuple):
raise TypeError("array must be a tuple")
if not 0 <= index < len(array):
raise IndexError("tuple assignment index out of range")
arrayobj = ctypes.py_object(array)
# Need to drop the refcount to 1 in order to use PyTuple_SetItem.
# Needless to say, this is incredibly dangerous.
refcnt = ctypes.pythonapi.Py_DecRef(arrayobj)
for i in range(refcnt-1):
ctypes.pythonapi.Py_DecRef(arrayobj)
try:
ret = ctypes.pythonapi.PyTuple_SetItem(arrayobj, ctypes.c_ssize_t(index), arrayobj)
if ret != 0:
raise RuntimeError("PyTuple_SetItem failed")
except:
raise SystemError("FATAL: PyTuple_SetItem failed: tuple probably unusable")
# Restore refcount and add one more for the new self-reference
for i in range(refcnt+1):
ctypes.pythonapi.Py_IncRef(arrayobj)
Run Code Online (Sandbox Code Playgroud)
结果:
>>> x = (1,2,3,4,5)
>>> self_reference(x, 1)
>>> import pprint
>>> pprint.pprint(x)
(1, <Recursion on tuple with id=4299516720>, 3, 4, 5)
Run Code Online (Sandbox Code Playgroud)
感谢nneonneo的帮助,我决定采用以下self_reference
方法实现.
import ctypes
ob_refcnt_p = ctypes.POINTER(ctypes.c_ssize_t)
class GIL:
acquire = staticmethod(ctypes.pythonapi.PyGILState_Ensure)
release = staticmethod(ctypes.pythonapi.PyGILState_Release)
class Ref:
dec = staticmethod(ctypes.pythonapi.Py_DecRef)
inc = staticmethod(ctypes.pythonapi.Py_IncRef)
class Tuple:
setitem = staticmethod(ctypes.pythonapi.PyTuple_SetItem)
@classmethod
def self_reference(cls, array, index):
if not isinstance(array, tuple):
raise TypeError('array must be a tuple')
if not isinstance(index, int):
raise TypeError('index must be an int')
if not 0 <= index < len(array):
raise ValueError('index is out of range')
GIL.acquire()
try:
obj = ctypes.py_object(array)
ob_refcnt = ctypes.cast(id(array), ob_refcnt_p).contents.value
for _ in range(ob_refcnt - 1):
Ref.dec(obj)
if cls.setitem(obj, ctypes.c_ssize_t(index), obj):
raise SystemError('PyTuple_SetItem was not successful')
for _ in range(ob_refcnt):
Ref.inc(obj)
finally:
GIL.release()
Run Code Online (Sandbox Code Playgroud)
要使用此方法,请按照下面显示的示例创建自己的自引用元组.
>>> array = tuple(range(5))
>>> Tuple.self_reference(array, 1)
>>> array
(0, (...), 2, 3, 4)
>>> Tuple.self_reference(array, 3)
>>> array
(0, (...), 2, (...), 4)
>>>
Run Code Online (Sandbox Code Playgroud)
更简单的解决方案:
import ctypes
tup = (0,)
ctypes.c_longlong.from_address(id(tup)+24).value = id(tup)
Run Code Online (Sandbox Code Playgroud)
结果:
>>> tup
((...),)
>>> type(tup)
tuple
>>> tup[0] is tup
True
Run Code Online (Sandbox Code Playgroud)