2012-08-08 9 views
14

Po obejrzeniu konwersacji na forum sprzed wielu lat, która nigdy nie została rozwiązana, sprawiło, że zastanawiałem się, jak poprawnie utworzyć krotkę, która się do niej odnosi. Technicznie jest to bardzo zły pomysł, ponieważ krotki mają być niezmienne. W jaki sposób niezmienny obiekt może się zawierać? Jednak to pytanie nie dotyczy najlepszych praktyk, ale jest zapytaniem dotyczącym tego, co jest możliwe w Pythonie.Budowanie kłamstw o ​​własnych referencjach

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

Poprzednia funkcja została zaprojektowana, aby uzyskać dostęp do API C w Pythonie, pamiętając jednocześnie o wewnętrznych strukturach i typach danych. Jednak następujący błąd jest zwykle generowany podczas uruchamiania funkcji. Dzięki nieznanym procesom możliwe było stworzenie samodopasującej krotki za pomocą podobnych technik.

Pytanie: W jaki sposób należy zmodyfikować funkcję self_reference, aby konsekwentnie działała przez cały czas?

>>> 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: Oto dwa różne rozmowy z tłumacza, które są nieco mylące. Powyższy kod wydaje się być poprawny, jeśli dobrze rozumiem dokumentację. Jednak rozmowy poniżej wydają się sprzeczne ze sobą i funkcją self_reference wyżej.

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

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

W jakiej wersji Pythona działało co najmniej raz? – jsbueno

+0

Edycja pokazuje wersję Pythona podczas pracy w trybie IDLE. Czy ma to znaczenie, że jest to komputer 64-bitowy? –

+0

Domyślam się, że krotki nie są niezmienne na poziomie C – Claudiu

Odpowiedz

6

Dzięki pomocy nneonneo, zdecydowałem się na następujące wdrożenie metody 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() 

Aby użyć tej metody, wykonaj poniższy przykład, aby utworzyć własne krotki z samoczynnym odwołaniem.

>>> 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, powód widzisz problemy dlatego PyTuple_SetItem zawiedzie jeśli RefCount krotki nie jest dokładnie jeden. Ma to na celu uniemożliwienie użycia tej funkcji, jeśli krotka została już użyta w innym miejscu. Nie wiem, dlaczego dostaniesz naruszenie zasad dostępu, ale może to wynikać z tego, że wyjątek zgłoszony przez PyTuple_SetItem nie został poprawnie rozpatrzony. Ponadto powodem, dla którego macierz wydaje się mutować do jakiegoś innego obiektu, jest to, że PyTuple_SetItem DECREF jest krotką dla każdej awarii; po dwóch niepowodzeniach, refcount wynosi zero, więc obiekt jest zwalniany (i jakiś inny obiekt najwyraźniej kończy się w tej samej lokalizacji pamięci).

Obiekt w ctypes jest preferowanym sposobem uzyskania dostępu do biblioteki DLL Pythona, ponieważ obsługuje on poprawnie wyjątki Python i gwarantuje poprawną konwencję wywoływania.

nie mam pod ręką komputer z systemem Windows, aby przetestować to uwagę, ale następujący działa poprawnie na Mac OS X (zarówno Python 2.7.3 i 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) 

Wynik:

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

Dziękuję bardzo za pomoc! Połączyłem naszą pracę w jedną odpowiedź. 'ctypes.pythonapi.Py_DecRef (arrayobj)' zwracał adres obiektu zamiast licznika odwołań, więc zmodyfikowałem kod, aby ręcznie uzyskać numer. Twój wgląd naprawdę pomógł w uzyskaniu odpowiedzi na pytanie. –

+0

Tak, moje złe. Py_DecRef i Py_IncRef zwracają pustkę, więc musisz wyciągnąć refcount z struct struct. – nneonneo

+0

Czy odpowiedź, którą podałem, działa na twojej platformie? Testowałem tylko w systemie Windows. –

1

Technicznie można zawinąć odniesienie do krotki wewnątrz zmiennego obiektu.

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

Celem było posiadanie bezpośredniego odniesienia, aby nie używać innego pojemnika. –

1

Immutabilność nie powinna uniemożliwiać obiektowi odniesienia się. Jest to łatwe do wykonania w Haskell, ponieważ ma leniwą ocenę.Oto imitacja, która robi to za pomocą thunk:

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

To nie jest pełna odpowiedź, tylko wstępna. Pracuję nad sprawdzeniem, czy jest inny sposób, aby było to możliwe.

+0

Celem było uzyskanie bezpośredniego odniesienia, aby nie używać thunk. –