2013-04-18 14 views
18

Załóżmy, że chcę rozszerzyć wbudowaną abstrakcję plików o dodatkowe operacje w open i close czas. W Pythonie 2.7 to działa:podklasowanie obiektów plików (w celu rozszerzenia operacji otwierania i zamykania) w python 3

class ExtFile(file): 
    def __init__(self, *args): 
     file.__init__(self, *args) 
     # extra stuff here 

    def close(self): 
     file.close(self) 
     # extra stuff here 

Teraz czekam na aktualizację programu do Python 3, w którym open jest funkcją fabryczną, która może zwrócić wystąpienie któregokolwiek z kilku różnych klas z modułu io zależności od jak się nazywa. Mogłem w zasadzie podklasować je wszystkie, ale to jest żmudne i musiałbym ponownie zaimplementować wysyłkę, którą wykonuje open. (W Pythonie 3 rozróżnienie pomiędzy plikami binarnymi i tekstowymi ma znaczenie bardziej niż w 2.x i potrzebuję obu). Te obiekty zostaną przekazane do kodu biblioteki, który może zrobić z nimi wszystko, więc idiom tworzenia "podobnej do pliku" klasy z kaczkami, która zawija wartość zwracaną przez open, a niezbędne metody będą najdokładniejsze.

Czy ktoś może zaproponować podejście 3.x, które obejmuje jak najmniej dodatkowego zestawu znaków poza wskazanym kodem?

+0

Być może zamiast rozszerzania obiektu pliku można utworzyć niestandardowy obiekt do użycia z instrukcją 'with'? –

+1

Dlaczego klasa, która zawija wartość zwracaną 'otwartego', jest tak zła? Możesz przesłonić '__getattr__', aby przekazywać metody hurtowo. –

+0

@BenjaminHodgson To było wystarczająco długie, że nie pamiętam dokładnie, co myślałem, ale prawdopodobnie było to zgodne z następującymi argumentami: "łatwo byłoby wykonać 90% pracy i ogromny ból, aby zdobyć każdą ostatnią narożną skrzynkę przybity. " Nie zrobiłem zbyt wiele z introspekcji obiektów w Pythonie, a kiedy spróbowałem, potknęło mnie to w mylący sposób. – zwol

Odpowiedz

14

Zamiast tego można użyć menedżera kontekstu. Na przykład ten:

class SpecialFileOpener: 
    def __init__ (self, fileName, someOtherParameter): 
     self.f = open(fileName) 
     # do more stuff 
     print(someOtherParameter) 
    def __enter__ (self): 
     return self.f 
    def __exit__ (self, exc_type, exc_value, traceback): 
     self.f.close() 
     # do more stuff 
     print('Everything is over.') 

Wtedy można go używać tak:

>>> with SpecialFileOpener('C:\\test.txt', 'Hello world!') as f: 
     print(f.read()) 

Hello world! 
foo bar 
Everything is over. 

Korzystanie blok kontekstowe with jest korzystne dla obiektów (plików i innych zasobów) w każdym razie.

+0

W odpowiedzi na żądanie nagrody: Naprawdę nie powinieneś podtypować obiektów plików; różne typy plików nigdy nie są tworzone bezpośrednio, ale zamiast tego są tworzone przez niektóre programy otwierające (np. 'open',' urlopen', ...). Jeśli chcesz rozszerzyć tę funkcjonalność, powinieneś dostarczyć niestandardowy * opener *, który otacza obiekt podobny do pliku i zapewnia dodatkową funkcjonalność. – poke

6

Miałem podobny problem i wymaganie obsługi zarówno Python 2.x, jak i 3.x. Co zrobiłem było podobne do następujących (current full version):

class _file_obj(object): 
    """Check if `f` is a file name and open the file in `mode`. 
    A context manager.""" 
    def __init__(self, f, mode): 
     if isinstance(f, str): 
      self.file = open(f, mode) 
     else: 
      self.file = f 
     self.close_file = (self.file is not f) 
    def __enter__(self): 
     return self 
    def __exit__(self, *args, **kwargs): 
     if (not self.close_file): 
      return # do nothing 
     # clean up 
     exit = getattr(self.file, '__exit__', None) 
     if exit is not None: 
      return exit(*args, **kwargs) 
     else: 
      exit = getattr(self.file, 'close', None) 
      if exit is not None: 
       exit() 
    def __getattr__(self, attr): 
     return getattr(self.file, attr) 
    def __iter__(self): 
     return iter(self.file) 

przechodzi on wszystkie połączenia do podstawowych obiektów i plików mogą być inicjowane z otwartego pliku lub z pliku. Działa również jako menedżer kontekstu. Zainspirowany przez this answer.

+0

To nie rozwiązuje zadanego pytania; Dostarczenie opakowania, które obsługuje zarówno python2, jak i python3, rozprasza uwagę, ponieważ rozszerzenie obiektu pliku python2 jest już dość trywialne i pytanie to zostało zadane, ponieważ robienie tego samego w python3 najwyraźniej nie jest banalnie proste. – ThorSummoner

+4

@ThorSummoner Myślę, że "robi więcej niż pytano" nie jest tym samym, co "nie odnosi się do pytania". Biorąc pod uwagę, że już istniała odpowiedź, pomyślałem, że nie zaszkodzi dodać rozszerzoną wersję. Odpowiedź wyjaśnia, jaką dodatkową funkcję ma kod. Dzięki za opinie. –

+0

Ta sugestia jest przydatna. –

10

tl; drUżyj menedżera kontekstu. Zobacz na dole tej odpowiedzi ważne uwagi na ich temat.


Pliki dostał bardziej skomplikowana w Pythonie 3. Chociaż istnieje kilka metod, które mogą być stosowane w normalnych klasach użytkownika, te metody nie działają z wbudowanych klas. Jednym ze sposobów jest mieszanie w żądanej klasie przed instanciating, ale to wymaga wiedząc, co powinno być najpierw wymieszać w klasie:

class MyFileType(???): 
    def __init__(...) 
     # stuff here 
    def close(self): 
     # more stuff here 

Ponieważ istnieje tak wiele rodzajów, a mógłby być dodany w przyszłości (mało prawdopodobne, ale możliwe), i nie wiemy na pewno, która zostanie zwrócona do po połączenie z open, ta metoda nie działa.

Inną metodą jest zmiana zarówno nasz typ niestandardowy mieć zwrócony File ___bases__ i modyfikując atrybut Zwracany instancji __class__ do naszego typu niestandardowe:

class MyFileType: 
    def close(self): 
     # stuff here 

some_file = open(path_to_file, '...') # ... = desired options 
MyFileType.__bases__ = (some_file.__class__,) + MyFile.__bases__ 

ale to daje

Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: __bases__ assignment: '_io.TextIOWrapper' deallocator differs from 'object' 

Jeszcze inną metodą, która może działać z czystymi klasami użytkowników, jest tworzenie niestandardowego typu pliku w locie, bezpośrednio z klasy zwróconej instancji, a następnie aktualizacja klasy zwróconego egzemplarza:

some_file = open(path_to_file, '...') # ... = desired options 

class MyFile(some_file.__class__): 
    def close(self): 
     super().close() 
     print("that's all, folks!") 

some_file.__class__ = MyFile 

ale znowu:

Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: __class__ assignment: only for heap types 

Tak, wygląda na to najlepszym sposobem, który będzie działał w ogóle w Pythonie 3, a szczęście będzie również pracować w Pythonie 2 (przydatne, jeśli chcesz tego samego kodu baza do pracy w obu wersjach) ma mieć menedżera kontekstowe niestandardowe:

class Open(object): 
    def __init__(self, *args, **kwds): 
     # do custom stuff here 
     self.args = args 
     self.kwds = kwds 
    def __enter__(self): 
     # or do custom stuff here :) 
     self.file_obj = open(*self.args, **self.kwds) 
     # return actual file object so we don't have to worry 
     # about proxying 
     return self.file_obj 
    def __exit__(self, *args): 
     # and still more custom stuff here 
     self.file_obj.close() 
     # or here 

i go używać:

with Open('some_file') as data: 
    # custom stuff just happened 
    for line in data: 
     print(line) 
# data is now closed, and more custom stuff 
# just happened 

Ważnym punktem, aby pamiętać: każdy nieobsługiwany wyjątek w __init__ lub __enter__ uniemożliwi __exit__ z systemem, więc w tych dwóch miejscach nadal trzeba używać try/except i/lub try/finally idiomów, aby upewnić się, że don Przeciek zasobów.

+0

Dzięki za wyjaśnienie tego ze mną. W moim szczególnym przypadku wiem, że pliki zawsze będą miały format 'io.TextIOWrapper'. Czy rozszerzenie klasy TextIOWrapper jest znaczne? A jeśli mogę to rozszerzyć i użyć tego rozszerzenia, to gdzie zajmę się przypadkiem odbierania pliku tekstowego? – ThorSummoner

+0

Czy kontrolujesz, gdzie te pliki są otwierane, czy też chcesz śledzić pliki otwierane przez inne moduły? –

+0

Kontroluję całą operację otwierania pliku. Środkowym krokiem, jaki chcę osiągnąć, to dodanie metody do wykonania skrótu pliku. – ThorSummoner

Powiązane problemy