2012-08-08 14 views
14

कई सालों पहले एक मंच में वार्तालाप देखने के बाद, जिसे कभी हल नहीं किया गया था, इससे मुझे आश्चर्य हुआ कि कोई व्यक्ति सही तरीके से संदर्भित टुपल कैसे बनायेगा। तकनीकी रूप से, यह एक बहुत बुरा विचार है क्योंकि टुपल्स को अपरिवर्तनीय माना जाता है। एक अपरिवर्तनीय वस्तु संभवतः स्वयं कैसे हो सकती है? हालांकि, यह प्रश्न सर्वोत्तम प्रथाओं के बारे में नहीं है लेकिन पाइथन में क्या संभव है इसके बारे में एक प्रश्न है।बिल्डिंग सेल्फ-रेफरेंसिंग टुपल्स

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

पिछले समारोह को ध्यान में आंतरिक संरचना और डेटाटाइप्स रखते हुए अजगर की सी 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 
>>> 

संपादित करें: यहाँ कि कुछ भ्रामक हैं दुभाषिया के साथ दो अलग अलग बातचीत कर रहे हैं। यदि मैं दस्तावेज़ को सही ढंग से समझता हूं तो ऊपर दिया गया कोड सही प्रतीत होता है। हालांकि, नीचे दी गई बातचीत एक-दूसरे के साथ संघर्ष और 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 
>>> 
+0

किस पायथन संस्करण में यह कम से कम एक बार काम करता था? – jsbueno

+0

संपादन आईडीईई में परिचालन करते समय पायथन के संस्करण को दिखाता है। साथ ही, क्या इससे कोई फर्क पड़ता है कि यह वास्तव में 64-बिट कंप्यूटर है? –

+0

मुझे लगता है कि यह पता चला है कि ट्यूपल्स सी स्तर – 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, कारण आप समस्याओं देख रहे हैं क्योंकि PyTuple_SetItem विफल रहता है टपल की refcount बिल्कुल नहीं है एक। यह कार्य को तब से रोकने के लिए है जब टुपल का पहले से ही कहीं और उपयोग किया जा रहा है। मुझे यकीन नहीं है कि आपको उस से एक्सेस उल्लंघन क्यों मिलता है, लेकिन ऐसा इसलिए हो सकता है क्योंकि PyTuple_SetItem द्वारा छोड़ा गया अपवाद ठीक से निपटाया नहीं गया है। इसके अलावा, कारण किसी अन्य ऑब्जेक्ट में सरणी को परिवर्तित करने का कारण यह है कि PyTuple_SetItem DECREF प्रत्येक विफलता पर टुपल है; दो विफलताओं के बाद, refcount शून्य है इसलिए वस्तु मुक्त हो जाती है (और कुछ अन्य वस्तु स्पष्ट रूप से उसी स्मृति स्थान में समाप्त होती है)।

pythonapi का उपयोग करके ctypes में ऑब्जेक्ट पाइथन डीएलएल तक पहुंच प्राप्त करने का पसंदीदा तरीका है, क्योंकि यह पाइथन अपवादों को सही तरीके से संभालता है और सही कॉलिंग सम्मेलन का उपयोग करने की गारंटी देता है।

मैं इस बाहर का परीक्षण करने के लिए एक Windows मशीन काम नहीं है, लेकिन अगले मैक ओएस एक्स (दोनों अजगर 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 वापसी शून्य, तो आपको ऑब्जेक्ट स्ट्रक्चर से रीफ्रॉउंट खींचना होगा। – nneonneo

+0

क्या मैंने आपके द्वारा प्रदान किया गया उत्तर आपके मंच पर काम करता है? मैंने केवल विंडोज़ पर परीक्षण किया। –

1

तकनीकी रूप से, आप एक म्यूटेबल ऑब्जेक्ट के अंदर टुपल के संदर्भ को लपेट सकते हैं।

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

लक्ष्य का एक प्रत्यक्ष संदर्भ होना था, किसी अन्य कंटेनर का उपयोग न करें। –

1

अपरिवर्तनीयता किसी वस्तु को संदर्भित करने से रोक नहीं सकती है। हास्केल में करना आसान है क्योंकि इसमें आलसी मूल्यांकन है।

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

यह एक पूरा जवाब है, सिर्फ प्रारंभिक नहीं है: यहाँ एक नकली है कि एक thunk का उपयोग करके करता है। यह देखने के लिए बाहर काम कर रहा हूं कि यह संभव बनाने के लिए कोई दूसरा तरीका है या नहीं।

+0

लक्ष्य का एक प्रत्यक्ष संदर्भ होना था, न कि थंक का उपयोग न करें। –

संबंधित मुद्दे