Controlla se l'oggetto è simile a un file in Python


93

Gli oggetti simili a file sono oggetti in Python che si comportano come un file reale, ad esempio hanno un metodo read () e un metodo di scrittura (), ma hanno un'implementazione diversa. È la realizzazione del concetto di Duck Typing .

È considerata una buona pratica consentire un oggetto simile a un file ovunque sia previsto un file in modo che, ad esempio, un oggetto StringIO o Socket possa essere utilizzato al posto di un file reale. Quindi è brutto eseguire un controllo come questo:

if not isinstance(fp, file):
   raise something

Qual è il modo migliore per verificare se un oggetto (ad esempio un parametro di un metodo) è "simile a un file"?

Risposte:


45

Generalmente non è buona norma avere controlli come questo nel codice a meno che non si abbiano requisiti speciali.

In Python la digitazione è dinamica, perché senti il ​​bisogno di controllare se l'oggetto è simile a un file, invece di usarlo come se fosse un file e gestire l'errore risultante?

Qualsiasi controllo che puoi fare avverrà comunque in fase di esecuzione, quindi fare qualcosa di simile if not hasattr(fp, 'read')e sollevare qualche eccezione fornisce un po 'più di utilità rispetto alla semplice chiamata fp.read()e gestione dell'errore di attributo risultante se il metodo non esiste.


whyche dire degli operatori come __add__, __lshift__o __or__in classi personalizzate? (oggetto file e API: docs.python.org/glossary.html#term-file-object )
n611x007

@naxa: Allora che dire esattamente di quegli operatori?
martineau

32
Spesso solo provandolo funziona, ma non compro la massima Pythonic che se è difficile da fare in Python, allora è sbagliato. Immagina di essere passato a un oggetto e ci sono 10 diverse cose che potresti fare con quell'oggetto a seconda del suo tipo. Non proverai ogni possibilità e gestirai l'errore finché non avrai finalmente capito bene. Sarebbe totalmente inefficiente. Non devi necessariamente chiedere che tipo è questo, ma devi essere in grado di chiedere se questo oggetto implementa l'interfaccia X.
jcoffland

31
Il fatto che la libreria delle collezioni python fornisca quelli che potrebbero essere chiamati "tipi di interfaccia" (ad esempio, sequenza) indica che questo è spesso utile, anche in python. In generale, quando qualcuno chiede "come foo", "don't foo" non è una risposta molto soddisfacente.
AdamC

1
AttributeError può essere sollevato per tutti i tipi di motivi che non hanno nulla a che fare con se l'oggetto supporta l'interfaccia di cui hai bisogno. hasattr è necessario per i file che non derivano da IOBase
Erik Aronesty

74

Per 3.1+, uno dei seguenti:

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

Per 2.x, "un oggetto simile a un file" è una cosa troppo vaga da controllare, ma la documentazione per qualsiasi funzione con cui hai a che fare, si spera, ti dirà di cosa hanno effettivamente bisogno; in caso contrario, leggi il codice.


Come sottolineano altre risposte, la prima cosa da chiedere è cosa stai controllando esattamente. Di solito, EAFP è sufficiente e più idiomatico.

Il glossario dice "file come oggetto" è sinonimo di "oggetto file", che in ultima analisi significa che è un esempio di una delle tre classi base astratte definite nel il iomodulo , che sono essi stessi tutte le sottoclassi di IOBase. Quindi, il modo per controllare è esattamente come mostrato sopra.

(Tuttavia, il controllo IOBasenon è molto utile. Riuscite a immaginare un caso in cui è necessario distinguere un effettivo file-like read(size)da una funzione con un argomento chiamata readche non è file-like, senza dover distinguere anche tra file di testo e raw file binari? Quindi, in realtà, vuoi quasi sempre controllare, ad esempio, "è un oggetto file di testo", non "è un oggetto simile a file".)


Per 2.x, mentre il iomodulo esiste dalla 2.6+, gli oggetti file incorporati non sono istanze di ioclassi, né lo sono nessuno degli oggetti simili a file nello stdlib, e nemmeno la maggior parte degli oggetti file di terze parti è probabile che tu incontri. Non esisteva una definizione ufficiale di cosa significhi "oggetto simile a file"; è solo "qualcosa come un oggetto file incorporato ", e funzioni diverse significano cose diverse con "mi piace". Tali funzioni dovrebbero documentare cosa significano; se non lo fanno, devi guardare il codice.

Tuttavia, i significati più comuni sono "ha read(size)", "ha read()" o "è un iterabile di stringhe", ma alcune vecchie librerie potrebbero aspettarsi readlineinvece di uno di questi, ad alcune librerie piacciono i close()file che gli dai, alcuni si aspetteranno che se filenoè presente, quindi sono disponibili altre funzionalità, ecc. E allo stesso modo per write(buf)(sebbene ci siano molte meno opzioni in quella direzione).


1
Infine, qualcuno lo mantiene reale.
Anthony Rutledge,

16
L'unica risposta utile. Perché StackOverflowers continua a votare "Smettila di fare quello che stai cercando di fare, perché lo so meglio ... e PEP 8, EAFP e altro!" post è al di là della mia fragile sanità mentale. ( Forse Cthulhu lo sa? )
Cecil Curry

1
Perché ci siamo imbattuti in troppo codice scritto da persone che non hanno pensato al futuro, e si rompe quando gli passi qualcosa che è quasi, ma non proprio un file perché controllano esplicitamente. L'intera faccenda dell'EAFP, la dattilografia non è un test di purezza di cazzate. È una vera decisione ingegneristica,
drxzcl

1
Questo può essere visto come un'ingegneria migliore e lo preferirei personalmente, ma potrebbe non funzionare. In genere non è necessario che gli oggetti simili a file ereditino da IOBase. Ad esempio gli infissi più belli ti danno _pytest.capture.EncodedFileche non ereditano da nulla.
Tomáš Gavenčiak,

46

Come altri hanno già detto, in genere dovresti evitare tali controlli. Un'eccezione è quando l'oggetto potrebbe essere legittimamente di tipo diverso e si desidera un comportamento diverso a seconda del tipo. Il metodo EAFP non funziona sempre qui poiché un oggetto potrebbe assomigliare a più di un tipo di anatra!

Ad esempio, un inizializzatore potrebbe prendere un file, una stringa o un'istanza della propria classe. Potresti quindi avere un codice come:

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

L'uso di EAFP qui potrebbe causare tutti i tipi di problemi sottili poiché ogni percorso di inizializzazione viene eseguito parzialmente prima di lanciare un'eccezione. Essenzialmente questa costruzione imita il sovraccarico della funzione e quindi non è molto pitonica, ma può essere utile se usata con cura.

Come nota a margine, non puoi eseguire il controllo dei file allo stesso modo in Python 3. Avrai invece bisogno di qualcosa di simile isinstance(f, io.IOBase).


28

Il paradigma dominante qui è EAFP: è più facile chiedere perdono che permesso. Vai avanti e utilizza l'interfaccia del file, quindi gestisci l'eccezione risultante o lascia che si propagino al chiamante.


9
+1: se xnon è simile a un file, x.read()solleverà la propria eccezione. Perché scrivere un'istruzione if in più? Usa l'oggetto. Funzionerà o si romperà.
S.Lott

3
Non gestire nemmeno l'eccezione. Se qualcuno ha passato qualcosa che non corrisponde all'API che ti aspetti, non è un tuo problema.
habnabit

1
@Aaron Gallagher: non ne sono sicuro. La tua affermazione è vera anche se è difficile per me mantenere uno stato coerente?
dmeister

1
Per preservare uno stato consistente puoi usare "prova / finalmente" (ma no tranne!) O la nuova istruzione "con".
drxzcl

Ciò è anche coerente con il paradigma "Fallisci velocemente e fallisci rumorosamente". A meno che tu non sia meticoloso, i controlli hasattr (...) espliciti possono occasionalmente causare il ritorno normale di una funzione / metodo senza eseguire l'azione prevista.
Ben Burns

11

È spesso utile sollevare un errore controllando una condizione, quando normalmente l'errore non verrebbe generato fino a molto tempo dopo. Ciò è particolarmente vero per il confine tra codice "user-land" e "api".

Non metteresti un metal detector in una stazione di polizia sulla porta di uscita, lo metteresti all'ingresso! Se non controllare una condizione significa che potrebbe verificarsi un errore che potrebbe essere stato rilevato 100 righe prima, o in una superclasse invece di essere sollevato nella sottoclasse, allora dico che non c'è niente di sbagliato nel controllo.

Controllare i tipi corretti ha senso anche quando accetti più di un tipo. È meglio sollevare un'eccezione che dice "Richiedo una sottoclasse di stringa di base, OR file" piuttosto che sollevare semplicemente un'eccezione perché alcune variabili non hanno un metodo 'seek' ...

Questo non significa che impazzisci e lo fai ovunque, per la maggior parte sono d'accordo con il concetto di eccezioni che si sollevano da sole, ma se puoi rendere la tua API drasticamente chiara o evitare l'esecuzione di codice non necessaria perché una semplice condizione non è stata soddisfatta fare così!


1
Sono d'accordo, ma sulla falsariga di non impazzire con questo ovunque - molte di queste preoccupazioni dovrebbero essere risolte durante i test, e alcune delle domande "dove catturarlo / come mostrarle all'utente" riceveranno risposta dai requisiti di usabilità.
Ben Burns

7

Puoi provare a chiamare il metodo quindi catturare l'eccezione:

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

Se vuoi solo un metodo di lettura e scrittura puoi farlo:

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

Se fossi in te sceglierei il metodo try / tranne.


Suggerirei di cambiare l'ordine degli esempi. tryè sempre la prima scelta. I hasattrcontrolli sono solo - per qualche motivo davvero oscuro - che non puoi semplicemente usare try.
S.Lott

1
Suggerisco di utilizzare al fp.read(0)posto di fp.read()per evitare di inserire tutto il codice nel tryblocco se si desidera elaborare i dati in un fpsecondo momento.
Meow

3
Nota che fp.read()con file di grandi dimensioni aumenterà immediatamente l'utilizzo della memoria.
Kyrylo Perevozchikov

Ho capito che è pitonico, ma poi dobbiamo leggere il file due volte tra gli altri problemi. Ad esempio, l' Flaskho fatto e ho realizzato che l' FileStorageoggetto sottostante necessitava del ripristino del puntatore dopo essere stato letto.
Adam Hughes,

2

Nella maggior parte dei casi, il modo migliore per gestire questo problema è non farlo. Se un metodo accetta un oggetto simile a un file e risulta che l'oggetto che è passato non lo è, l'eccezione che viene sollevata quando il metodo tenta di utilizzare l'oggetto non è meno informativa di qualsiasi eccezione che potresti aver sollevato esplicitamente.

C'è almeno un caso in cui potresti voler fare questo tipo di controllo, però, ed è quando l'oggetto non viene utilizzato immediatamente da ciò a cui lo hai passato, ad esempio se è impostato nel costruttore di una classe. In tal caso, penso che il principio dell'EAFP sia superato dal principio del "fail fast". Controllerei l'oggetto per assicurarmi che implementasse i metodi di cui la mia classe ha bisogno (e che siano metodi), ad esempio:

class C():
    def __init__(self, file):
        if type(getattr(file, 'read')) != type(self.__init__):
            raise AttributeError
        self.file = file

1
Perché getattr(file, 'read')invece di solo file.read? Questo fa esattamente la stessa cosa.
abarnert

1
Ancora più importante, questo controllo è sbagliato. Si solleverà quando viene fornita, diciamo, fileun'istanza reale . (I metodi delle istanze dei tipi builtin / C-extension sono di tipo builtin_function_or_method, mentre quelli delle classi vecchio stile lo sono instancemethod). Il fatto che questa sia una classe vecchio stile e che utilizzi ==sui tipi invece di ininstanceo issubclass, sono ulteriori problemi, ma se l'idea di base non funziona, non importa.
abarnert

2

Mi sono imbattuto nella tua domanda mentre stavo scrivendo una openfunzione simile a quella che poteva accettare un nome file, un descrittore di file o un oggetto simile a un file pre-aperto.

Invece di provare un readmetodo, come suggeriscono le altre risposte, ho finito per controllare se l'oggetto può essere aperto. Se possibile, è una stringa o un descrittore e ho in mano un oggetto simile a un file valido dal risultato. Se opensolleva a TypeError, l'oggetto è già un file.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.