2015-06-23 2 views
7

Was ich erreichen müssen:Prevent TextIOWrapper von Schließung auf GC in einem Py2/PY3 verträglich

eine Binärdatei gegeben, dekodieren es in ein paar verschiedene Möglichkeiten, um eine TextIOBase API bereitstellt. Im Idealfall können diese nachfolgenden Dateien weitergegeben werden, ohne dass ich ihre Lebensdauer explizit verfolgen muss.

Leider BufferedReader Wicklung wird geschlossen, wenn der TextIOWrapper außerhalb des Geltungsbereichs geht.

Hier ist eine einfache Demo dafür:

In [1]: import io 

In [2]: def mangle(x): 
    ...:  io.TextIOWrapper(x) # Will get GCed causing __del__ to call close 
    ...:  

In [3]: f = io.open('example', mode='rb') 

In [4]: f.closed 
Out[4]: False 

In [5]: mangle(f) 

In [6]: f.closed 
Out[6]: True 

ich dies in Python 3 durch zwingende __del__ beheben kann (dies ist eine vernünftige Lösung für meinen Anwendungsfall, wie ich die vollständige Kontrolle über den Decodierungsprozess, ich brauchen nur eine sehr einheitliche API am Ende zu belichten):

In [1]: import io 

In [2]: class MyTextIOWrapper(io.TextIOWrapper): 
    ...:  def __del__(self): 
    ...:   print("I've been GC'ed") 
    ...:   

In [3]: def mangle2(x): 
    ...:  MyTextIOWrapper(x) 
    ...:  

In [4]: f2 = io.open('example', mode='rb') 

In [5]: f2.closed 
Out[5]: False 

In [6]: mangle2(f2) 
I've been GC'ed 

In [7]: f2.closed 
Out[7]: False 

dies gilt jedoch nicht in Python arbeiten 2:

In [7]: class MyTextIOWrapper(io.TextIOWrapper): 
    ...:  def __del__(self): 
    ...:   print("I've been GC'ed") 
    ...:   

In [8]: def mangle2(x): 
    ...:  MyTextIOWrapper(x) 
    ...:  

In [9]: f2 = io.open('example', mode='rb') 

In [10]: f2.closed 
Out[10]: False 

In [11]: mangle2(f2) 
I've been GC'ed 

In [12]: f2.closed 
Out[12]: True 

Ich habe ein bisschen Zeit mit dem Python-Quellcode verbracht und es sieht bemerkenswert ähnlich zwischen 2.7 und 3.4, also verstehe ich nicht, warum die __del__ geerbt von IOBase in Python 2 nicht überschreibbar ist (oder sogar sichtbar in dir), scheint aber immer noch ausgeführt zu werden. Python 3 funktioniert genau wie erwartet.

Kann ich irgendetwas tun?

+0

Idealerweise möchte ich vermeiden, den GC zu deaktivieren und danach wieder zu aktivieren, aber ich habe definitiv darüber nachgedacht ... – ebolyen

Antwort

4

Es stellt sich heraus, dass im Prinzip nichts über den Dekonstruktor, der in Python 2.7 close aufruft, gemacht werden kann. Dies ist in den C-Code fest codiert.Stattdessen können wir close so ändern, dass es den Puffer nicht schließt, wenn __del__ passiert (__del__ wird vor _PyIOBase_finalize in dem C-Code ausgeführt, die uns eine Möglichkeit gibt, das Verhalten von close zu ändern). Dies lässt close wie erwartet funktionieren, ohne den GC den Puffer schließen zu lassen.

class SaneTextIOWrapper(io.TextIOWrapper): 
    def __init__(self, *args, **kwargs): 
     self._should_close_buffer = True 
     super(SaneTextIOWrapper, self).__init__(*args, **kwargs) 

    def __del__(self): 
     # Accept the inevitability of the buffer being closed by the destructor 
     # because of this line in Python 2.7: 
     # https://github.com/python/cpython/blob/2.7/Modules/_io/iobase.c#L221 
     self._should_close_buffer = False 
     self.close() # Actually close for Python 3 because it is an override. 
         # We can't call super because Python 2 doesn't actually 
         # have a `__del__` method for IOBase (hence this 
         # workaround). Close is idempotent so it won't matter 
         # that Python 2 will end up calling this twice 

    def close(self): 
     # We can't stop Python 2.7 from calling close in the deconstructor 
     # so instead we can prevent the buffer from being closed with a flag. 

     # Based on: 
     # https://github.com/python/cpython/blob/2.7/Lib/_pyio.py#L1586 
     # https://github.com/python/cpython/blob/3.4/Lib/_pyio.py#L1615 
     if self.buffer is not None and not self.closed: 
      try: 
       self.flush() 
      finally: 
       if self._should_close_buffer: 
        self.buffer.close() 

Meine bisherige Lösung verwendete hier _pyio.TextIOWrapper, die langsamer als die oben ist, weil es in Python geschrieben ist, nicht C.

Es einfach beteiligt zwingende __del__ mit einem noop, die auch in Py2/3 arbeiten.

0

Eine einfache Lösung wäre, die Variable aus der Funktion zurückzugeben und sie im Skriptbereich zu speichern, so dass keine Müllsammlung erfasst wird, bis das Skript beendet wird oder sich der Verweis darauf ändert. Aber es kann andere elegante Lösungen geben.

0

EDIT:

fand ich eine viel bessere Lösung (vergleichsweise), aber ich werde diese Antwort im Falle lassen Sie es ist nützlich für jedermann von zu lernen. (Es ist eine ziemlich einfache Art und Weise zu zeigen gc.garbage)

Bitte verwenden Sie eigentlich nicht, was folgt.

OLD:

fand ich eine mögliche Lösung, wenn es schrecklich ist:

Was können wir tun, ist eine zyklische Referenz in dem destructor eingerichtet, die die GC Veranstaltung halten sie aus. Wir können dann die garbage von gc betrachten, um diese nicht referenzierbaren Objekte zu finden, den Zyklus zu unterbrechen und diese Referenz zu löschen.

In [1]: import io 

In [2]: class MyTextIOWrapper(io.TextIOWrapper): 
    ...:  def __del__(self): 
    ...:   if not hasattr(self, '_cycle'): 
    ...:    print "holding off GC" 
    ...:    self._cycle = self 
    ...:   else: 
    ...:    print "getting GCed!" 
    ...: 

In [3]: def mangle(x): 
    ...:  MyTextIOWrapper(x) 
    ...:  

In [4]: f = io.open('example', mode='rb') 

In [5]: mangle(f) 
holding off GC 

In [6]: f.closed 
Out[6]: False 

In [7]: import gc 

In [8]: gc.garbage 
Out[8]: [] 

In [9]: gc.collect() 
Out[9]: 34 

In [10]: gc.garbage 
Out[10]: [<_io.TextIOWrapper name='example' encoding='UTF-8'>] 

In [11]: gc.garbage[0]._cycle=False 

In [12]: del gc.garbage[0] 
getting GCed! 

In [13]: f.closed 
Out[13]: True 

Ehrlich gesagt ist dies ein ziemlich schreckliches Problem zu umgehen, aber es könnte transparent sein, um das API ich liefern werde. Dennoch würde ich eine Möglichkeit bevorzugen, die __del__ von IOBase zu überschreiben.