Verifique se o objeto é semelhante a um arquivo em Python


93

Objetos semelhantes a arquivos são objetos em Python que se comportam como um arquivo real, por exemplo, têm um método read () e um método write (), mas têm uma implementação diferente. É uma realização do conceito Duck Typing .

É considerada uma boa prática permitir um objeto semelhante a um arquivo em todos os lugares onde um arquivo é esperado para que, por exemplo, um objeto StringIO ou um objeto Socket possa ser usado em vez de um arquivo real. Portanto, é ruim realizar uma verificação como esta:

if not isinstance(fp, file):
   raise something

Qual é a melhor maneira de verificar se um objeto (por exemplo, um parâmetro de um método) é "semelhante a um arquivo"?

Respostas:


44

Geralmente, não é uma boa prática ter verificações como essa em seu código, a menos que você tenha requisitos especiais.

Em Python, a digitação é dinâmica, por que você sente necessidade de verificar se o objeto é como um arquivo, em vez de apenas usá-lo como se fosse um arquivo e lidar com o erro resultante?

Qualquer verificação que você possa fazer vai acontecer no tempo de execução de qualquer maneira, então fazer algo como if not hasattr(fp, 'read')e lançar alguma exceção fornece pouco mais utilidade do que apenas chamar fp.read()e tratar o erro de atributo resultante se o método não existir.


whyo que acontece com os operadores como __add__, __lshift__ou __or__em classes personalizadas? (objeto de arquivo e API: docs.python.org/glossary.html#term-file-object )
n611x007

@naxa: E exatamente sobre essas operadoras?
martineau de

32
Muitas vezes, apenas tentar funciona, mas eu não acredito na máxima Pythônica de que se for difícil de fazer em Python, então está errado. Imagine que você passou por um objeto e há 10 coisas diferentes que você pode fazer com aquele objeto, dependendo de seu tipo. Você não vai tentar cada possibilidade e lidar com o erro até que finalmente acerte. Isso seria totalmente ineficiente. Você não precisa necessariamente perguntar que tipo é esse, mas você precisa ser capaz de perguntar se este objeto implementa a interface X.
jcoffland

31
O fato de que a biblioteca de coleções python fornece o que pode ser chamado de "tipos de interface" (por exemplo, sequência) mostra o fato de que isso é frequentemente útil, mesmo em python. Em geral, quando alguém pergunta "como fazer foo", "não foo" não é uma resposta altamente satisfatória.
AdamC

1
AttributeError pode ser gerado por todos os tipos de razões que nada têm a ver com o fato de o objeto oferecer suporte à interface de que você precisa. hasattr é necessário para filelikes que não derivam de IOBase
Erik Aronesty

74

Para 3.1+, um dos seguintes:

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

Para 2.x, "objeto semelhante a arquivo" é uma coisa muito vaga para verificar, mas a documentação para qualquer função (ões) com a qual você está lidando, esperançosamente, dirá o que eles realmente precisam; se não, leia o código.


Como outras respostas apontam, a primeira coisa a se perguntar é o que exatamente você está verificando. Normalmente, o EAFP é suficiente e mais idiomático.

O glossário diz "arquivo-como objeto" é sinônimo de "objeto de arquivo", que em última análise significa que é uma instância de uma das três classes base abstratas definidas no o iomódulo , que são eles próprios todas as subclasses de IOBase. Portanto, a forma de verificar é exatamente como mostrado acima.

(No entanto, a verificação IOBasenão é muito útil. Você pode imaginar um caso em que você precisa distinguir um arquivo real read(size)de alguma função de um argumento chamada readque não é semelhante a um arquivo, sem precisar também distinguir entre arquivos de texto e raw arquivos binários? Então, na verdade, você quase sempre deseja verificar, por exemplo, "é um objeto de arquivo de texto", não "é um objeto semelhante a um arquivo".)


Para 2.x, embora o iomódulo exista desde 2.6+, os objetos de arquivo embutidos não são instâncias de ioclasses, nem qualquer um dos objetos semelhantes a arquivos no stdlib, nem a maioria dos objetos semelhantes a arquivos de terceiros que você é provável que encontre. Não havia uma definição oficial do que significa "objeto semelhante a um arquivo"; é apenas "algo como um objeto de arquivo embutido ", e funções diferentes significam coisas diferentes por "curtir". Essas funções devem documentar o que significam; se não, você deve olhar o código.

No entanto, os significados mais comuns são "tem read(size)", "tem read()" ou "é um iterável de strings", mas algumas bibliotecas antigas podem esperar em readlinevez de um desses, algumas bibliotecas gostam de close()arquivos que você fornece, alguns esperam que se filenoestá presente, então outra funcionalidade está disponível, etc. E da mesma forma para write(buf)(embora haja muito menos opções nessa direção).


1
Finalmente, alguém está sendo real.
Anthony Rutledge

16
A única resposta útil. Por que StackOverflowers continua votando "Pare de fazer o que você está tentando fazer, porque eu conheço melhor ... e PEP 8, EAFP e outras coisas!" posts está além da minha frágil sanidade. ( Talvez Cthulhu saiba? )
Cecil Curry

1
Porque nós encontramos muitos códigos escritos por pessoas que não pensaram no futuro, e ele quebra quando você passa algo que é quase, mas não exatamente um arquivo porque eles verificam explicitamente. Todo o EAFP, coisa de digitação de pato não é um teste de pureza de merda. É uma decisão real de engenharia,
engenharia

1
Isso pode ser visto como uma engenharia melhor e eu preferiria pessoalmente, mas pode não funcionar. Geralmente não é necessário que objetos semelhantes a arquivos sejam herdados de IOBase. Por exemplo, as luminárias pytest fornecem o _pytest.capture.EncodedFileque não herda de nada.
Tomáš Gavenčiak

45

Como outros já disseram, você geralmente deve evitar tais verificações. Uma exceção é quando o objeto pode ser legitimamente de tipos diferentes e você deseja um comportamento diferente dependendo do tipo. O método EAFP nem sempre funciona aqui, pois um objeto pode se parecer com mais de um tipo de pato!

Por exemplo, um inicializador pode pegar um arquivo, string ou instância de sua própria classe. Você pode então ter um 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

Usar o EAFP aqui pode causar todos os tipos de problemas sutis, pois cada caminho de inicialização é executado parcialmente antes de lançar uma exceção. Essencialmente, essa construção imita a sobrecarga de função e, portanto, não é muito Pythônica, mas pode ser útil se usada com cuidado.

Como uma observação lateral, você não pode fazer a verificação de arquivo da mesma maneira no Python 3. Você precisará de algo como isinstance(f, io.IOBase) .


27

O paradigma dominante aqui é EAFP: mais fácil pedir perdão do que permissão. Vá em frente e use a interface de arquivo e, a seguir, trate a exceção resultante ou deixe-as se propagarem para o chamador.


9
+1: Se xnão for semelhante a um arquivo, x.read()gerará sua própria exceção. Por que escrever uma declaração if extra? Basta usar o objeto. Vai funcionar ou quebrar.
S.Lott

3
Nem lide com a exceção. Se alguém passou algo que não corresponde à API que você esperava, não é problema seu.
habnabit

1
@Aaron Gallagher: Não tenho certeza. A sua afirmação é verdadeira, mesmo que seja difícil para mim preservar um estado consistente?
dmeister

1
Para preservar um estado consistente, você pode usar "try / finally" (mas não exceto!) Ou a nova instrução "with".
drxzcl

Isso também é consistente com o paradigma "Falha rápido e falha ruidosamente". A menos que você seja meticuloso, as verificações explícitas de hasattr (...) podem ocasionalmente fazer com que uma função / método retorne normalmente sem realizar a ação pretendida.
Ben Burns

11

Geralmente é útil levantar um erro verificando uma condição, quando esse erro normalmente não seria levantado até muito mais tarde. Isso é especialmente verdadeiro para o limite entre o código 'terreno do usuário' e 'api'.

Você não colocaria um detector de metais em uma delegacia de polícia na porta de saída, mas sim na entrada! Se não verificar uma condição significa que pode ocorrer um erro que poderia ter sido detectado 100 linhas antes, ou em uma superclasse em vez de ser gerado na subclasse, então eu digo que não há nada de errado com a verificação.

Verificar os tipos adequados também faz sentido quando você está aceitando mais de um tipo. É melhor levantar uma exceção que diz "Eu preciso de uma subclasse de cadeia de base, arquivo OR" do que apenas lançar uma exceção porque alguma variável não tem um método de 'busca' ...

Isso não significa que você enlouqueça e faça isso em todos os lugares, na maioria das vezes eu concordo com o conceito de exceções se levantando, mas se você puder tornar sua API drasticamente clara ou evitar a execução desnecessária de código porque uma condição simples não foi atendida faça isso!


1
Eu concordo, mas no sentido de não enlouquecer com isso em todos os lugares - muitas dessas preocupações devem ser abaladas durante o teste, e algumas das perguntas "onde pegar isso / como mostrar ao usuário" serão respondidas pelos requisitos de usabilidade.
Ben Burns

7

Você pode tentar chamar o método e capturar a exceção:

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

Se você deseja apenas um método de leitura e gravação, pode fazer o seguinte:

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

Se eu fosse você, escolheria o método try / except.


Eu sugiro mudar a ordem dos exemplos. tryé sempre a primeira escolha. Os hasattrcheques são apenas - por algum motivo realmente obscuro - você não pode simplesmente usar try.
S.Lott

1
Sugiro usar em fp.read(0)vez de fp.read()para evitar colocar todo o código no trybloco se quiser processar os dados fpposteriormente.
Meow

3
Observe que fp.read()arquivos grandes aumentam imediatamente o uso de memória.
Kyrylo Perevozchikov

Eu entendo que isso é pythônico, mas então temos que ler o arquivo duas vezes entre outras questões. Por exemplo, Flaskeu fiz isso e percebi que o FileStorageobjeto subjacente precisava de seu ponteiro redefinido após ser lido.
Adam Hughes

2

Na maioria das circunstâncias, a melhor maneira de lidar com isso é não. Se um método pega um objeto semelhante a um arquivo e descobre que o objeto que foi passado não é, a exceção que é gerada quando o método tenta usar o objeto não é menos informativa do que qualquer exceção que você possa ter levantado explicitamente.

Porém, há pelo menos um caso em que você pode querer fazer esse tipo de verificação e é quando o objeto não está sendo usado imediatamente pelo que você o passou, por exemplo, se está sendo definido no construtor de uma classe. Nesse caso, eu acho que o princípio do EAFP é superado pelo princípio de "falha rápida". Eu verificaria o objeto para ter certeza de que implementou os métodos de que minha classe precisa (e que eles são métodos), por exemplo:

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

1
Por que em getattr(file, 'read')vez de apenas file.read? Isso faz exatamente a mesma coisa.
abarnert em

1
Mais importante, essa verificação está errada. Ele aumentará quando for dado, digamos, uma fileinstância real . (Métodos de instâncias de tipos de extensão embutida / C são do tipo builtin_function_or_method, enquanto aqueles de classes de estilo antigo são instancemethod). O fato de que esta é uma classe de estilo antigo, e que ela usa ==em tipos em vez de ininstanceou issubclass, são problemas adicionais, mas se a ideia básica não funcionar, isso pouco importa.
abarnert em

2

Acabei encontrando sua pergunta quando estava escrevendo um open função semelhante a ela que poderia aceitar um nome de arquivo, descritor de arquivo ou objeto semelhante a arquivo pré-aberto.

Em vez de testar um readmétodo, como sugerem as outras respostas, acabei verificando se o objeto pode ser aberto. Se puder, é uma string ou descritor, e eu tenho um objeto semelhante a um arquivo válido em mãos do resultado. Se opengerar um TypeError, o objeto já é um arquivo.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.