Devo passar nomes de arquivos para serem abertos ou abrir arquivos?

45

Suponha que eu tenha uma função que faz coisas com um arquivo de texto - por exemplo, lê e remove a palavra 'a'. Eu poderia passar um nome de arquivo e lidar com a abertura / fechamento da função, ou eu poderia passar o arquivo aberto e esperar que quem ligasse lidaria com o fechamento.

A primeira maneira parece ser uma maneira melhor de garantir que nenhum arquivo seja deixado aberto, mas me impede de usar coisas como objetos StringIO

A segunda maneira pode ser um pouco perigosa - não há como saber se o arquivo será fechado ou não, mas eu seria capaz de usar objetos semelhantes a arquivos

def ver_1(filename):
    with open(filename, 'r') as f:
        return do_stuff(f)

def ver_2(open_file):
    return do_stuff(open_file)

print ver_1('my_file.txt')

with open('my_file.txt', 'r') as f:
    print ver_2(f)

Um destes geralmente é preferido? É geralmente esperado que uma função se comporte de uma destas duas maneiras? Ou deveria ser apenas bem documentado de tal forma que o programador possa usar a função conforme apropriado?

    
por Dannnno 10.11.2014 / 20:09
fonte

3 respostas

33

Interfaces convenientes são boas e, às vezes, o caminho a percorrer. No entanto, na maioria das vezes uma boa composição é mais importante que a conveniência, já que uma abstração composta nos permite implementar outras funcionalidades (incluindo wrappers de conveniência) sobre ela.

A maneira mais geral para sua função usar arquivos é pegar um identificador de arquivo aberto como parâmetro, já que isso também permite usar identificadores de arquivos que não fazem parte do sistema de arquivos (por exemplo, pipes, sockets,…):

def your_function(open_file):
    return do_stuff(open_file)

Se soletrando with open(filename, 'r') as f: result = your_function(f) for demais para seus usuários, você pode escolher uma das seguintes soluções:

  • your_function usa um arquivo aberto ou um nome de arquivo como parâmetro. Se for um nome de arquivo, o arquivo é aberto e fechado e as exceções são propagadas. Há um pouco de problema com a ambigüidade aqui, que poderia ser trabalhada usando argumentos nomeados.
  • Ofereça um wrapper simples que cuida da abertura do arquivo, por exemplo

    def your_function_filename(file):
        with open(file, 'r') as f:
            return your_function(f)
    

    Eu geralmente percebo essas funções como inchaço da API, mas se elas fornecem funcionalidade comumente usada, a conveniência ganha é um argumento suficientemente strong.

  • Agrupe a funcionalidade with open em outra função composta:

    def with_file(filename, callback):
        with open(filename, 'r') as f:
            return callback(f)
    

    usado como with_file(name, your_function) ou em casos mais complicados with_file(name, lambda f: some_function(1, 2, f, named=4))

por 10.11.2014 / 20:34
fonte
17

A verdadeira questão é de completude. Sua função de processamento de arquivos é o processamento completo do arquivo ou é apenas uma parte de uma cadeia de etapas de processamento? Se estiver completo e por conta própria, sinta-se à vontade para encapsular todo o acesso a arquivos dentro de uma função.

def ver(filepath):
    with open(filepath, "r") as f:
        # do processing steps on f
        return result

Isso tem a propriedade muito boa de finalizar o recurso (fechar o arquivo) no final da instrução with .

Se, no entanto, houver necessidade de processar um arquivo já aberto, a distinção de ver_1 e ver_2 fará mais sentido. Por exemplo:

def _ver_file(f):
    # do processing steps on f
    return result

def ver(fileobj):
    if isinstance(fileobj, str):
        with open(fileobj, 'r') as f:
            return _ver_file(f)
    else:
        return _ver_file(fileobj)

Este tipo de teste de tipo explícito é frequentemente desaprovado , especialmente em idiomas como Java, Julia e Go, nos quais o envio baseado em tipo ou interface é diretamente suportado. No Python, no entanto, não há suporte de idioma para o despacho baseado em tipo. Você pode ocasionalmente ver críticas ao teste direto de tipos em Python, mas na prática é extremamente comum e bastante eficaz. Ele permite que uma função tenha um alto grau de generalidade, manipulando os tipos de dados que possam aparecer, também conhecidos como "tipagem de pato". Observe o sublinhado à esquerda em _ver_file ; essa é uma maneira convencional de designar uma função "privada" (ou método). Embora tecnicamente possa ser chamado diretamente, sugere que a função não é destinada ao consumo externo direto.

    
por 10.11.2014 / 21:04
fonte
4

Se você passar o nome do arquivo, em vez do identificador de arquivo, não há garantia de que o segundo arquivo seja o mesmo arquivo que o primeiro, quando estiver aberto; isso pode levar a erros de correção e falhas de segurança.

    
por 11.11.2014 / 01:47
fonte