Sprawdź, czy obiekt jest podobny do pliku w Pythonie

94

Obiekty podobne do plików to obiekty w Pythonie, które zachowują się jak prawdziwy plik, np. Mają metodę read () i metodę zapisu (), ale mają inną implementację. To realizacja koncepcji Duck Typing .

Uważa się, że dobrą praktyką jest dopuszczenie obiektu podobnego do pliku wszędzie tam, gdzie oczekiwany jest plik, tak aby np. Obiekt StringIO lub Socket mógł być użyty zamiast prawdziwego pliku. Więc źle jest wykonać taką kontrolę:

if not isinstance(fp, file):
   raise something

Jaki jest najlepszy sposób sprawdzenia, czy obiekt (np. Parametr metody) jest „podobny do pliku”?

dmeister
źródło

Odpowiedzi:

44

Generalnie nie jest dobrą praktyką umieszczanie takich kontroli w kodzie, chyba że masz specjalne wymagania.

W Pythonie pisanie jest dynamiczne, dlaczego czujesz potrzebę sprawdzania, czy obiekt jest podobny do pliku, zamiast po prostu używać go tak, jakby był plikiem i obsługiwać wynikowy błąd?

Każde sprawdzenie, które możesz wykonać, i tak nastąpi w czasie wykonywania, więc zrobienie czegoś podobnego if not hasattr(fp, 'read')i podniesienie jakiegoś wyjątku zapewnia niewiele więcej użyteczności niż tylko wywołanie fp.read()i obsługa wynikowego błędu atrybutu, jeśli metoda nie istnieje.

Tendayi Mawushe
źródło
whyco operatorzy podoba __add__, __lshift__lub __or__w niestandardowych klas? (obiekt pliku i API: docs.python.org/glossary.html#term-file-object )
n611x007
@naxa: Więc co dokładnie z tymi operatorami?
martineau
34
Często po prostu próbowanie to działa, ale nie kupuję Pythonowej maksymy, że jeśli jest to trudne do zrobienia w Pythonie, to jest złe. Wyobraź sobie, że przekazano ci obiekt i jest 10 różnych rzeczy, które możesz zrobić z tym obiektem w zależności od jego typu. Nie będziesz próbować każdej możliwości i zajmować się błędem, dopóki w końcu nie rozwiążesz problemu. Byłoby to całkowicie nieefektywne. Nie musisz koniecznie pytać, jaki to typ, ale musisz być w stanie zapytać, czy ten obiekt implementuje interfejs X.
jcoffland
33
Fakt, że biblioteka kolekcji Pythona udostępnia coś, co można by nazwać „typami interfejsów” (np. Sekwencją), świadczy o tym, że jest to często przydatne, nawet w Pythonie. Ogólnie rzecz biorąc, gdy ktoś pyta „jak się foo”, „nie foo” nie jest wysoce satysfakcjonującą odpowiedzią.
AdamC
1
AttributeError może zostać zgłoszony z różnych powodów, które nie mają nic wspólnego z tym, czy obiekt obsługuje interfejs, którego potrzebujesz. hasattr jest potrzebny w przypadku plików, które nie pochodzą z IOBase
Erik Aronesty
77

W przypadku wersji 3.1+, jedno z poniższych:

isinstance(something, io.TextIOBase)
isinstance(something, io.BufferedIOBase)
isinstance(something, io.RawIOBase)
isinstance(something, io.IOBase)

W przypadku 2.x „obiekt plikopodobny” jest zbyt niejasny, aby go sprawdzić, ale dokumentacja wszelkich funkcji, z którymi masz do czynienia, powie ci, czego faktycznie potrzebują; jeśli nie, przeczytaj kod.


Jak wskazują inne odpowiedzi, pierwszą rzeczą, o którą należy zapytać, jest to, czego dokładnie szukasz. Zwykle EAFP jest wystarczający i bardziej idiomatyczny.

Słowniczek mówi „plikopodobnym obiektu” jest synonimem „obiektu pliku”, co ostatecznie oznacza, że jest to przykład jednej z trzech klas abstrakcyjnych zasad określonych w tym iomodule , które same są wszystkie podklasy IOBase. Tak więc sposób sprawdzenia jest dokładnie taki, jak pokazano powyżej.

(Jednak sprawdzanie IOBasenie jest zbyt przydatne. Czy możesz sobie wyobrazić przypadek, w którym musisz odróżnić rzeczywisty plik podobny read(size)do jakiejś jednoargumentowej funkcji o nazwie, readktóra nie jest podobna do pliku, bez konieczności rozróżniania między plikami tekstowymi i nieprzetworzonymi pliki binarne? Więc tak naprawdę prawie zawsze chcesz sprawdzić, np. „czy jest obiektem pliku tekstowego”, a nie „jest obiektem podobnym do pliku”).


W przypadku wersji 2.x, chociaż iomoduł istnieje od wersji 2.6+, wbudowane obiekty plików nie są instancjami ioklas, ani żadne z obiektów typu plikowego w standardowej bibliotece, ani też większość obiektów podobnych do plików innych firm. mogą się spotkać. Nie było oficjalnej definicji tego, co oznacza „obiekt podobny do pliku”; jest to po prostu „coś w rodzaju wbudowanego obiektu pliku ”, a różne funkcje oznaczają różne rzeczy przez „polubienie”. Takie funkcje powinny dokumentować ich znaczenie; jeśli nie, musisz spojrzeć na kod.

Jednak najczęstsze znaczenia to „ma read(size)”, „ma read()” lub „jest iterowalna z ciągami znaków”, ale niektóre stare biblioteki mogą oczekiwać readlinezamiast jednego z nich, niektóre biblioteki lubią close()pliki, które im dajesz, inne będą oczekiwać, że jeśli filenojest obecny, wtedy dostępna jest inna funkcjonalność itp. I podobnie w przypadku write(buf)(chociaż jest o wiele mniej opcji w tym kierunku).

abarnert
źródło
1
Wreszcie, ktoś musi to zachować.
Anthony Rutledge,
18
Jedyna przydatna odpowiedź. Dlaczego StackOverflowers kontynuują głosowanie w górę "Przestań robić to, co próbujesz zrobić, ponieważ wiem lepiej ... i PEP 8, EAFP i takie tam!" postów jest poza moim kruchym zdrowiem psychicznym. ( Może Cthulhu wie? )
Cecil Curry
1
Ponieważ natknęliśmy się na zbyt wiele kodu napisanego przez ludzi, którzy nie myśleli z wyprzedzeniem, i psuje się, gdy przekazujesz mu coś, co jest prawie, ale nie całkiem plikiem, ponieważ sprawdzają jawnie. Cały EAFP, pisanie na maszynie kaczych, nie jest jakimś bzdurnym testem czystości. To właściwa decyzja,
drxzcl
1
Może to być postrzegane jako lepsza inżynieria i wolałbym to osobiście, ale może nie działać. Zwykle nie jest wymagane, aby obiekty podobne do plików dziedziczyły po IOBase. Na przykład pytest daję oprawy _pytest.capture.EncodedFilektóre nie dziedziczą z niczego.
Tomáš Gavenčiak
46

Jak powiedzieli inni, generalnie należy unikać takich kontroli. Jedynym wyjątkiem jest sytuacja, gdy obiekt może mieć różne typy i chcesz mieć różne zachowanie w zależności od typu. Metoda EAFP nie zawsze działa tutaj, ponieważ obiekt może wyglądać jak więcej niż jeden rodzaj kaczki!

Na przykład inicjator może pobrać plik, ciąg znaków lub wystąpienie własnej klasy. Możesz wtedy mieć kod taki jak:

class A(object):
    def __init__(self, f):
        if isinstance(f, A):
            # Just make a copy.
        elif isinstance(f, file):
            # initialise from the file
        else:
            # treat f as a string

Używanie EAFP tutaj może powodować różnego rodzaju subtelne problemy, ponieważ każda ścieżka inicjalizacji jest częściowo uruchamiana przed rzuceniem wyjątku. Zasadniczo ta konstrukcja naśladuje przeciążenie funkcji, a więc nie jest zbyt Pythonic, ale może być przydatna, jeśli jest używana ostrożnie.

Na marginesie, nie możesz sprawdzić pliku w ten sam sposób w Pythonie 3. Będziesz potrzebować czegoś takiego jak isinstance(f, io.IOBase).

Scott Griffiths
źródło
28

Dominującym paradygmatem jest tutaj EAFP: łatwiej prosić o przebaczenie niż o pozwolenie. Śmiało i użyj interfejsu pliku, a następnie obsłuż wynikowy wyjątek lub pozwól im propagować do wywołującego.

drxzcl
źródło
9
+1: Jeśli xnie jest podobny do pliku, x.read()zgłosi swój własny wyjątek. Po co pisać dodatkowe oświadczenie „if”? Po prostu użyj obiektu. To zadziała lub się zepsuje.
S.Lott
3
Nawet nie obsługuj wyjątku. Jeśli ktoś przekazał coś, co nie pasuje do oczekiwanego interfejsu API, to nie jest twój problem.
habnabit
1
@Aaron Gallagher: Nie jestem pewien. Czy twoje stwierdzenie jest prawdziwe, nawet jeśli trudno jest mi zachować spójny stan?
dmeister
1
Aby zachować spójny stan, możesz użyć „try / w końcu” (ale bez wyjątku!) Lub nowej instrukcji „with”.
drxzcl
Jest to również zgodne z paradygmatem „szybkie niepowodzenie i głośne niepowodzenie”. O ile nie jesteś skrupulatny, jawne sprawdzenia hasattr (...) mogą czasami powodować normalny powrót funkcji / metody bez wykonywania zamierzonej akcji.
Ben Burns
11

Często warto zgłosić błąd, sprawdzając warunek, gdy normalnie błąd ten nie zostanie zgłoszony dużo później. Jest to szczególnie prawdziwe w przypadku granicy między „obszarem użytkownika” a kodem „api”.

Nie umieściłbyś wykrywacza metalu na posterunku policji przy drzwiach wyjściowych, umieściłbyś go przy wejściu! Jeśli brak sprawdzenia warunku oznacza, że ​​może wystąpić błąd, który mógł zostać przechwycony 100 linii wcześniej lub w superklasie zamiast zostać podniesiony w podklasie, to mówię, że nie ma nic złego w sprawdzaniu.

Sprawdzanie poprawnych typów ma również sens, gdy akceptujesz więcej niż jeden typ. Lepiej jest zgłosić wyjątek, który mówi „Wymagam podklasy bazowej, plik OR”, niż tylko zgłosić wyjątek, ponieważ pewna zmienna nie ma metody wyszukiwania ...

Nie oznacza to, że oszalałeś i robisz to wszędzie, w większości zgadzam się z koncepcją podnoszących się wyjątków, ale jeśli możesz drastycznie uczynić swoje API drastycznie przejrzystym lub uniknąć niepotrzebnego wykonywania kodu, ponieważ prosty warunek nie został spełniony Zrób tak!

Ben DeMott
źródło
1
Zgadzam się, ale na wzór tego, żeby nie szaleć z tym wszędzie - wiele z tych obaw powinno zostać wyrzuconych podczas testów, a na niektóre z pytań „gdzie to złapać / jak wyświetlić użytkownikowi” odpowiedzą wymagania dotyczące użyteczności.
Ben Burns
7

Możesz spróbować wywołać metodę, a następnie złapać wyjątek:

try:
    fp.read()
except AttributeError:
    raise something

Jeśli potrzebujesz tylko metody odczytu i zapisu, możesz to zrobić:

if not (hasattr(fp, 'read') and hasattr(fp, 'write')):
   raise something

Na twoim miejscu wybrałbym metodę try / except.

Nadia Alramli
źródło
Proponuję zmienić kolejność przykładów. tryjest zawsze pierwszym wyborem. Te hasattrkontrole są tylko - dla niektórych naprawdę niejasnych powodów - nie można po prostu używać try.
S.Lott
1
Sugeruję użycie fp.read(0)zamiast fp.read(), aby uniknąć umieszczania całego kodu w trybloku, jeśli chcesz przetworzyć dane z fppóźniej.
Miau,
3
Zauważ, że w fp.read()przypadku dużych plików natychmiast zwiększy się użycie pamięci.
Kyrylo Perevozchikov
Rozumiem, że jest to pythonowe, ale między innymi musimy dwukrotnie przeczytać plik. Na przykład, Flaskzrobiłem to i zdałem sobie sprawę, że podstawowy FileStorageobiekt wymaga resetowania wskaźnika po przeczytaniu.
Adam Hughes
2

W większości przypadków najlepszym sposobem na rozwiązanie tego problemu jest nie. Jeśli metoda przyjmuje obiekt podobny do pliku i okaże się, że przekazany obiekt nie jest, wyjątek, który jest zgłaszany, gdy metoda próbuje użyć obiektu, nie jest mniej informacyjny niż jakikolwiek wyjątek, który mógłbyś zgłosić jawnie.

Jest przynajmniej jeden przypadek, w którym możesz chcieć wykonać tego rodzaju sprawdzenie, a to wtedy, gdy obiekt nie jest natychmiast używany przez to, do czego go przekazałeś, np. Jeśli jest ustawiany w konstruktorze klasy. W takim przypadku myślę, że zasada EAFP jest podważona przez zasadę „szybkiej porażki”. Sprawdziłbym obiekt, aby upewnić się, że zaimplementował metody, których potrzebuje moja klasa (i że są to metody), np .:

class C():
    def __init__(self, file):
        if type(getattr(file, 'read')) != type(self.__init__):
            raise AttributeError
        self.file = file
Robert Rossney
źródło
1
Dlaczego getattr(file, 'read')zamiast po prostu file.read? Robi dokładnie to samo.
abarnert
1
Co ważniejsze, ta kontrola jest błędna. Podniesie się, gdy otrzyma, powiedzmy, rzeczywistą fileinstancję. (Metody instancji typów wbudowanych / rozszerzeń C są typu builtin_function_or_method, podczas gdy metody klas starego typu są instancemethod). Fakt, że jest to klasa w starym stylu i że używa ==na typach zamiast ininstancelub issubclass, jest kolejnym problemem, ale jeśli podstawowa idea nie działa, to nie ma znaczenia.
abarnert
2

Skończyło się na tym, że natknąłem się na twoje pytanie, kiedy pisałem openpodobną do funkcji funkcję, która mogła akceptować nazwę pliku, deskryptor pliku lub wstępnie otwarty obiekt podobny do pliku.

Zamiast testować readmetodę, jak sugerują inne odpowiedzi, w końcu sprawdziłem, czy obiekt można otworzyć. Jeśli tak, jest to ciąg lub deskryptor, a na podstawie wyniku mam w ręku prawidłowy obiekt podobny do pliku. Jeśli openpodnosi TypeError, to obiekt jest już plikiem.

Szalony Fizyk
źródło