[多年以前在一个论坛上看到一个对话之后,这个对话一直没有解决,这让我想知道人们是如何正确创建一个引用自己的元组的。从技术上讲,这是一个非常糟糕的主意,因为元组应该是不可变的。不可变的对象如何包含自身?但是,此问题不是关于最佳实践的问题,而是关于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')
先前的功能旨在访问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
>>>
Edit:
这是与解释器的两种不同的对话,有些混淆。如果我正确理解文档,上面的代码似乎是正确的。但是,下面的对话似乎互相冲突,并且上面的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)
>>>
对话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
>>>
[多年以前在一个论坛上看到一个对话之后,这个对话一直没有解决,这让我想知道人们是如何正确创建一个引用自己的元组的。从技术上讲,这是一个非常...
[AFAICT,您看到问题的原因是,如果元组的引用计数不正确,则PyTuple_SetItem
失败。如果元组已经在其他地方使用过,这是为了防止使用该功能。我不确定为什么会收到访问冲突,但这可能是因为PyTuple_SetItem
引发的异常未得到正确处理。此外,数组似乎突变为其他对象的原因是因为PyTuple_SetItem
DECREF是每次失败时的元组。在两次失败之后,引用计数为零,因此该对象被释放(并且其他一些对象显然位于同一内存位置)。
感谢nneonneo的帮助,我决定采用self_reference
方法的以下实现。
从技术上讲,您可以将对元组的引用包装在可变对象内。
不可移植性不应阻止对象引用自身。在Haskell中,这很容易做到,因为它的评估很懒。这是一个模仿,通过使用一个thunk:
更简单的解决方案: