2012-08-08 15 views
14

何年も前のフォーラムで会話を見たことが決して解決されなかったので、自分自身を参照するタプルを正しく作成する方法を知りました。技術的には、タプルは不変であるはずなので、これは非常に悪い考えです。どのようにして不変のオブジェクトが自身を含む可能性がありますか?しかし、この質問はベストプラクティスではなく、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 
>>> 

編集:ここではは、やや混乱しているインタプリタを持つ2人の異なる会話です。上記のコードは、ドキュメントが正しく理解されていれば正しいと思われます。ただし、下の会話は、互いに矛盾しているように見えますが、上記の機能は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:タプルの参照カウントが正確でない場合PyTuple_SetItemが失敗したので

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 
>>> 
+0

どのPythonバージョンで少なくとも1回動作しましたか? – jsbueno

+0

編集では、IDLEで操作している間にPythonのバージョンが表示されます。また、実際には64ビットコンピュータであることが重要ですか? –

+0

タプルはCレベルで不変ではないと分かります。 – Claudiu

答えて

6

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() 

この方法を使用するには、独自の自己参照タプルを作成するために以下に示す例に従ってください。

>>> array = tuple(range(5)) 
>>> Tuple.self_reference(array, 1) 
>>> array 
(0, (...), 2, 3, 4) 
>>> Tuple.self_reference(array, 3) 
>>> array 
(0, (...), 2, (...), 4) 
>>> 
6

AFAICT、あなたが問題を見ている理由は、 1。タプルが既に他の場所で使用されていた場合、その関数が使用されないようにするためです。なぜアクセス違反があるのか​​わからないのですが、PyTuple_SetItemによってスローされた例外が適切に処理されていない可能性があります。さらに、配列が他のオブジェクトに変異しているように見える理由は、PyTuple_SetItem DECREFが各失敗時のタプルであるためです。 2つの失敗の後、refcountはゼロであるため、オブジェクトは解放されます(そして、他のオブジェクトは明らかに同じメモリ位置に終わります)。

Python DLLにアクセスするには、ctypesにpythonapiオブジェクトを使用することが好ましい方法です。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) 

結果:

>>> 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) 
+0

ありがとうございました!私は私たちの仕事を一つの答えにまとめました。 'ctypes.pythonapi.Py_DecRef(arrayobj)'は参照カウントではなくオブジェクトのアドレスを返していたので、コードを手動で変更するように変更しました。あなたの洞察は本当に質問の答えを得るのを助けました。 –

+0

はい、私の悪いです。 Py_DecRefとPy_IncRefはvoidを返すので、refcountをオブジェクト構造体から引き出す必要があります。 – nneonneo

+0

私の回答はあなたのプラットフォームでうまく働いていますか?私はWindows上でのみテストしました。 –

1

技術的には、可変オブジェクト内のタプルへの参照をラップすることができます。

>>> c = ([],) 
>>> c[0].append(c) 
>>> c 
([(...)],) 
>>> c[0] 
[([...],)] 
>>> 
+0

目的は直接参照することでした。別のコンテナを使用することはありませんでした。 –

1

不変性によってオブジェクトが参照されないようにしてはいけません。これは、怠惰な評価があるので、Haskellで簡単に行うことができます。

>>> def self_ref_tuple(): 
    a = (1, 2, lambda: a) 
    return a 

>>> ft = self_ref_tuple() 
>>> ft 
(1, 2, <function <lambda> at 0x02A7C330>) 
>>> ft[2]() 
(1, 2, <function <lambda> at 0x02A7C330>) 
>>> ft[2]() is ft 
True 

これは単なる予備完全な答えではない。ここではそのサンクを使用してい模倣です。これを可能にする別の方法があるかどうかを確認するために努力しています。

+0

目標はサンクを使用しないで直接参照することでした。 –