Como abrir um arquivo usando a instrução open with

200

Estou vendo como fazer entrada e saída de arquivos em Python. Escrevi o código a seguir para ler uma lista de nomes (um por linha) de um arquivo para outro arquivo, verificando um nome com relação aos nomes no arquivo e anexando texto às ocorrências no arquivo. O código funciona. Poderia ser feito melhor?

Eu queria usar a with open(...instrução para os arquivos de entrada e saída, mas não consigo ver como eles podem estar no mesmo bloco, o que significa que eu precisaria armazenar os nomes em um local temporário.

def filter(txt, oldfile, newfile):
    '''\
    Read a list of names from a file line by line into an output file.
    If a line begins with a particular name, insert a string of text
    after the name before appending the line to the output file.
    '''

    outfile = open(newfile, 'w')
    with open(oldfile, 'r', encoding='utf-8') as infile:
        for line in infile:
            if line.startswith(txt):
                line = line[0:len(txt)] + ' - Truly a great person!\n'
            outfile.write(line)

    outfile.close()
    return # Do I gain anything by including this?

# input the name you want to check against
text = input('Please enter the name of a great person: ')    
letsgo = filter(text,'Spanish', 'Spanish2')
Disnami
fonte
"significando que eu precisaria armazenar os nomes em um local temporário"? Você pode explicar o que você quer dizer com isso?
31512 S.Lott
4
Observe que filter()é uma função interna e, portanto, você provavelmente deve escolher um nome diferente para sua função.
Tom
2
@ Tom uma função no espaço para nome substitui a função interna?
UpTide
2
@UpTide: Sim, o Python opera em ordem LEGB - Local, Anexo, Global, Integrado (consulte stackoverflow.com/questions/291978/… ). Portanto, se você criar uma função global ( filter()), ela será encontrada antes da incorporada.filter()
Tom

Respostas:

308

Python permite colocar várias open()instruções em um único with. Você os separa por vírgula. Seu código seria então:

def filter(txt, oldfile, newfile):
    '''\
    Read a list of names from a file line by line into an output file.
    If a line begins with a particular name, insert a string of text
    after the name before appending the line to the output file.
    '''

    with open(newfile, 'w') as outfile, open(oldfile, 'r', encoding='utf-8') as infile:
        for line in infile:
            if line.startswith(txt):
                line = line[0:len(txt)] + ' - Truly a great person!\n'
            outfile.write(line)

# input the name you want to check against
text = input('Please enter the name of a great person: ')    
letsgo = filter(text,'Spanish', 'Spanish2')

E não, você não ganha nada colocando um explícito returnno final de sua função. Você pode usar returnpara sair mais cedo, mas você o teve no final, e a função sairá sem ele. (Obviamente, com funções que retornam um valor, use o returnpara especificar o valor a ser retornado.)

O uso de vários open()itens com withnão era suportado no Python 2.5 quando a withinstrução foi introduzida ou no Python 2.6, mas é suportado no Python 2.7 e Python 3.1 ou mais recente.

http://docs.python.org/reference/compound_stmts.html#the-with-statement http://docs.python.org/release/3.1/reference/compound_stmts.html#the-with-statement

Se você estiver escrevendo um código que deve ser executado no Python 2.5, 2.6 ou 3.0, aninhe as withinstruções como as outras respostas sugeridas ou usadas contextlib.nested.

steveha
fonte
28

Use blocos aninhados como este,

with open(newfile, 'w') as outfile:
    with open(oldfile, 'r', encoding='utf-8') as infile:
        # your logic goes right here
RanRag
fonte
12

Você pode aninhar o seu com blocos. Como isso:

with open(newfile, 'w') as outfile:
    with open(oldfile, 'r', encoding='utf-8') as infile:
        for line in infile:
            if line.startswith(txt):
                line = line[0:len(txt)] + ' - Truly a great person!\n'
            outfile.write(line)

Isso é melhor que a sua versão, porque você garante que outfileserá fechado mesmo que seu código encontre exceções. Obviamente, você poderia fazer isso com try / finalmente, mas withé o caminho certo para fazer isso.

Ou, como acabei de aprender, você pode ter vários gerenciadores de contexto em uma declaração with, conforme descrito por @steveha . Isso me parece ser uma opção melhor do que aninhar.

E para sua última pergunta menor, o retorno não serve a nenhum propósito real. Eu o removeria.

David Heffernan
fonte
Muito obrigado. Vou experimentá-lo e aceitar sua resposta se / quando fazê-lo funcionar.
Disnami
Obrigado novamente. Preciso esperar sete minutos antes que eu possa aceitar.
Disnami
7
@Disnami Certifique-se de aceitar a resposta certa (e não é este!) ;-)
David Heffernan
1

Às vezes, convém abrir uma quantidade variável de arquivos e tratar cada um da mesma forma, você pode fazer isso com contextlib

from contextlib import ExitStack
filenames = [file1.txt, file2.txt, file3.txt]

with open('outfile.txt', 'a') as outfile:
    with ExitStack() as stack:
        file_pointers = [stack.enter_context(open(file, 'r')) for file in filenames]                
            for fp in file_pointers:
                outfile.write(fp.read())                   
irmão-bilo
fonte