Compruebe si el objeto es similar a un archivo en Python

93

Los objetos similares a archivos son objetos en Python que se comportan como un archivo real, por ejemplo, tienen un método de lectura () y un método de escritura (), pero tienen una implementación diferente. Es una realización del concepto Duck Typing .

Se considera una buena práctica permitir un objeto similar a un archivo en todas partes donde se espera un archivo de modo que, por ejemplo, un objeto StringIO o Socket pueda usarse en lugar de un archivo real. Entonces es malo realizar una verificación como esta:

if not isinstance(fp, file):
   raise something

¿Cuál es la mejor manera de comprobar si un objeto (por ejemplo, un parámetro de un método) es "similar a un archivo"?

dmeister
fuente

Respuestas:

45

Por lo general, no es una buena práctica tener comprobaciones como esta en su código a menos que tenga requisitos especiales.

En Python, la escritura es dinámica, ¿por qué siente la necesidad de verificar si el objeto es como un archivo, en lugar de simplemente usarlo como si fuera un archivo y manejar el error resultante?

Cualquier verificación que pueda hacer se realizará en tiempo de ejecución de todos modos, por lo que hacer algo como if not hasattr(fp, 'read')y generar alguna excepción proporciona poca más utilidad que simplemente llamar fp.read()y manejar el error de atributo resultante si el método no existe.

Tendayi Mawushe
fuente
why¿Qué pasa con los operadores como __add__, __lshift__o __or__en clases personalizadas? (objeto de archivo y API: docs.python.org/glossary.html#term-file-object )
n611x007
@naxa: Entonces, ¿qué pasa exactamente con esos operadores?
martineau
32
A menudo, solo intentarlo funciona, pero no creo en la máxima de Pythonic de que si es difícil de hacer en Python, entonces está mal. Imagina que te pasan un objeto y hay 10 cosas diferentes que podrías hacer con ese objeto dependiendo de su tipo. No probará todas las posibilidades y manejará el error hasta que finalmente lo haga bien. Eso sería totalmente ineficaz. No es necesario que pregunte qué tipo es este, pero sí debe poder preguntar si este objeto implementa la interfaz X.
jcoffland
31
El hecho de que la biblioteca de colecciones de Python proporcione lo que podría llamarse "tipos de interfaz" (por ejemplo, secuencia) habla del hecho de que esto a menudo es útil, incluso en Python. En general, cuando alguien pregunta "cómo engañar", "no engañar" no es una respuesta muy satisfactoria.
AdamC
1
AttributeError se puede generar por todo tipo de razones que no tienen nada que ver con si el objeto es compatible con la interfaz que necesita. hasattr es necesario para filelikes que no derivan de IOBase
Erik Aronesty
74

Para 3.1+, uno de los siguientes:

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

Para 2.x, "objeto similar a un archivo" es una cosa demasiado vaga para verificar, pero la documentación de cualquier función con la que esté tratando le dirá lo que realmente necesita; si no, lea el código.


Como señalan otras respuestas, lo primero que debe preguntar es qué es exactamente lo que está buscando. Por lo general, EAFP es suficiente y más idiomático.

El glosario dice que "objeto de archivo" es sinónimo de "objeto de archivo", lo que en última instancia significa que es una instancia de una de las tres clases base abstractas definidas en el iomódulo , que son todas subclases de IOBase. Entonces, la forma de verificar es exactamente como se muestra arriba.

(Sin embargo, la verificación IOBaseno es muy útil. ¿Se imagina un caso en el que necesite distinguir un archivo real similar read(size)a una función de un solo argumento readque no sea similar a un archivo, sin necesidad de distinguir también entre archivos de texto y archivos sin formato? ¿archivos binarios? Entonces, realmente, casi siempre desea verificar, por ejemplo, "es un objeto de archivo de texto", no "es un objeto similar a un archivo".)


Para 2.x, aunque el iomódulo ha existido desde 2.6+, los objetos de archivo integrados no son instancias de ioclases, tampoco lo son ninguno de los objetos similares a archivos en stdlib, ni tampoco la mayoría de los objetos similares a archivos de terceros que usted es probable que encuentre. No había una definición oficial de lo que significa "objeto similar a un archivo"; es simplemente "algo así como un objeto de archivo incorporado ", y diferentes funciones significan cosas diferentes por "me gusta". Tales funciones deben documentar lo que significan; si no es así, tienes que mirar el código.

Sin embargo, los significados más comunes son "tiene read(size)", "tiene read()" o "es un iterable de cadenas", pero algunas bibliotecas antiguas pueden esperar en readlinelugar de una de esas, algunas bibliotecas prefieren los close()archivos que les proporcionas, algunos esperarán que si filenoestá presente, entonces hay otra funcionalidad disponible, etc. Y de manera similar para write(buf)(aunque hay muchas menos opciones en esa dirección).

abarnert
fuente
1
Finalmente, alguien lo mantiene real.
Anthony Rutledge
16
La única respuesta útil. ¿Por qué StackOverflowers continúa votando a favor? "¡Deja de hacer lo que estás tratando de hacer, porque lo sé mejor ... y PEP 8, EAFP y demás!" mensajes está más allá de mi frágil cordura. (¿ Quizás Cthulhu lo sepa? )
Cecil Curry
1
Porque nos hemos topado con demasiado código escrito por personas que no pensaron en el futuro, y se rompe cuando le pasa algo que es casi, pero no del todo, un archivo porque lo verifican explícitamente. Toda la EAFP, el tipeo de pato no es una prueba de pureza de mierda. Es una decisión de ingeniería real,
drxzcl
1
Esto puede verse como una mejor ingeniería y lo preferiría personalmente, pero puede que no funcione. Por lo general, no se requiere que hereden objetos similares a archivos IOBase. Por ejemplo, los accesorios de pytest le dan lo _pytest.capture.EncodedFileque no hereda de nada.
Tomáš Gavenčiak
46

Como han dicho otros, generalmente debe evitar tales controles. Una excepción es cuando el objeto puede ser legítimamente de diferentes tipos y desea un comportamiento diferente según el tipo. El método EAFP no siempre funciona aquí, ya que un objeto podría parecerse a más de un tipo de pato.

Por ejemplo, un iniciador podría tomar un archivo, una cadena o una instancia de su propia clase. Entonces podría tener un código como:

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

El uso de EAFP aquí podría causar todo tipo de problemas sutiles, ya que cada ruta de inicialización se ejecuta parcialmente antes de lanzar una excepción. Esencialmente, esta construcción imita la sobrecarga de funciones y, por lo tanto, no es muy Pythonic, pero puede ser útil si se usa con cuidado.

Como nota al margen, no puede hacer la verificación de archivos de la misma manera en Python 3. Necesitará algo como isinstance(f, io.IOBase).

Scott Griffiths
fuente
28

El paradigma dominante aquí es EAFP: más fácil pedir perdón que permiso. Continúe y use la interfaz de archivo, luego maneje la excepción resultante o deje que se propaguen a la persona que llama.

drxzcl
fuente
9
+1: si xno es similar a un archivo, x.read()generará su propia excepción. ¿Por qué escribir una declaración if adicional? Solo usa el objeto. Funcionará o se romperá.
S.Lott
3
Ni siquiera manejes la excepción. Si alguien pasó algo que no coincide con la API que espera, no es su problema.
Habnabit
1
@Aaron Gallagher: No estoy seguro. ¿Es cierta su afirmación incluso si me resulta difícil mantener un estado coherente?
dmeister
1
Para preservar un estado consistente, puede usar "probar / finalmente" (¡pero no excepto!) O la nueva instrucción "con".
drxzcl
Esto también es consistente con el paradigma de "Fallar rápido y fallar en voz alta". A menos que sea meticuloso, las comprobaciones explícitas de hasattr (...) pueden ocasionalmente hacer que una función / método regrese normalmente sin realizar la acción prevista.
Ben Burns
11

A menudo es útil generar un error al verificar una condición, cuando ese error normalmente no se generaría hasta mucho más tarde. Esto es especialmente cierto para el límite entre el código 'user-land' y 'api'.

No colocaría un detector de metales en una estación de policía en la puerta de salida, ¡lo colocaría en la entrada! Si no marcar una condición significa que podría ocurrir un error que podría haberse detectado 100 líneas antes, o en una superclase en lugar de aparecer en la subclase, entonces digo que no hay nada de malo en verificar.

También tiene sentido comprobar los tipos adecuados cuando acepta más de un tipo. Es mejor generar una excepción que diga "Necesito una subclase de cadena base, archivo OR" que simplemente generar una excepción porque alguna variable no tiene un método de 'búsqueda' ...

Esto no significa que te vuelvas loco y hagas esto en todas partes, en su mayor parte estoy de acuerdo con el concepto de excepciones que surgen, pero si puedes dejar tu API drásticamente clara o evitar la ejecución de código innecesario porque no se ha cumplido una condición simple hazlo!

Ben DeMott
fuente
1
Estoy de acuerdo, pero en la línea de no volverme loco con esto en todas partes, muchas de estas preocupaciones deberían surgir durante las pruebas, y algunas de las preguntas de "dónde captar esto / cómo mostrar al usuario" serán respondidas por los requisitos de usabilidad.
Ben Burns
7

Puede intentar llamar al método y luego detectar la excepción:

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

Si solo desea un método de lectura y escritura, puede hacer esto:

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

Si yo fuera usted, optaría por el método try / except.

Nadia Alramli
fuente
Sugeriría cambiar el orden de los ejemplos. tryes siempre la primera opción. Los hasattrcheques son solo, por alguna razón realmente oscura, que no puede simplemente usar try.
S.Lott
1
Sugiero usar en fp.read(0)lugar de fp.read()para evitar poner todo el código en el trybloque si desea procesar los datos fpposteriormente.
Miau
3
Tenga en cuenta que fp.read()con archivos grandes aumentará inmediatamente el uso de memoria.
Kyrylo Perevozchikov
Entiendo que esto es pitónico, pero luego tenemos que leer el archivo dos veces entre otros problemas. Por ejemplo, Flaskhice esto y me di cuenta de que el FileStorageobjeto subyacente necesitaba el reinicio del puntero después de leerlo.
Adam Hughes
2

En la mayoría de las circunstancias, la mejor manera de manejar esto es no hacerlo. Si un método toma un objeto similar a un archivo, y resulta que el objeto que se le pasa no lo es, la excepción que se genera cuando el método intenta usar el objeto no es menos informativa que cualquier excepción que haya generado explícitamente.

Sin embargo, hay al menos un caso en el que es posible que desee hacer este tipo de verificación, y es cuando el objeto no está siendo utilizado inmediatamente por aquello a lo que se lo ha pasado, por ejemplo, si está configurado en el constructor de una clase. En ese caso, pensaría que el principio de EAFP es superado por el principio de "falla rápido". Verificaría el objeto para asegurarme de que implementó los métodos que mi clase necesita (y que son métodos), por ejemplo:

class C():
    def __init__(self, file):
        if type(getattr(file, 'read')) != type(self.__init__):
            raise AttributeError
        self.file = file
Robert Rossney
fuente
1
¿Por qué en getattr(file, 'read')lugar de solo file.read? Esto hace exactamente lo mismo.
abarnert
1
Más importante aún, esta verificación es incorrecta. Aumentará cuando se le dé, digamos, una fileinstancia real . (Los métodos de instancias de tipos de extensión C / incorporados son de tipo builtin_function_or_method, mientras que los de clases de estilo antiguo lo son instancemethod). El hecho de que esta sea una clase de estilo antiguo, y que use ==tipos en lugar de ininstanceo issubclass, son problemas adicionales, pero si la idea básica no funciona, eso no importa.
abarnert
2

Terminé encontrándome con tu pregunta cuando estaba escribiendo una openfunción similar a la que podría aceptar un nombre de archivo, un descriptor de archivo o un objeto similar a un archivo pre-abierto.

En lugar de probar un readmétodo, como sugieren las otras respuestas, terminé verificando si el objeto se puede abrir. Si puede, es una cadena o descriptor, y tengo un objeto similar a un archivo válido en la mano del resultado. Si opengenera un TypeError, entonces el objeto ya es un archivo.

Físico loco
fuente