Jak otworzyć plik za pomocą instrukcji open with

200

Patrzę na to, jak zrobić wejście i wyjście pliku w Pythonie. Napisałem następujący kod, aby odczytać listę nazw (po jednym w wierszu) z pliku do innego pliku, jednocześnie sprawdzając nazwę względem nazw w pliku i dołączając tekst do wystąpień w pliku. Kod działa. Czy można to zrobić lepiej?

Chciałem użyć with open(...instrukcji zarówno dla plików wejściowych, jak i wyjściowych, ale nie widzę, jak mogą znajdować się w tym samym bloku, co oznacza, że ​​muszę przechowywać nazwy w tymczasowej lokalizacji.

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
źródło
„co oznacza, że ​​muszę przechowywać nazwy w tymczasowej lokalizacji”? Czy możesz wyjaśnić, co masz na myśli?
S.Lott
4
Zauważ, że filter()jest to funkcja wbudowana, dlatego prawdopodobnie powinieneś wybrać inną nazwę dla swojej funkcji.
Tom
2
@Tom czy funkcja w przestrzeni nazw zastępuje funkcję wbudowaną?
UpTide
2
@UpTide: Tak, Python działa w kolejności LEGB - lokalna, załączająca, globalna, wbudowana (patrz stackoverflow.com/questions/291978/... ). Tak więc, jeśli utworzysz funkcję globalną ( filter()), zostanie ona znaleziona przed wbudowanymfilter()
Tom

Odpowiedzi:

308

Python pozwala na umieszczanie wielu open()instrukcji w jednym with. Rozdziel je przecinkami. Twój kod będzie wtedy:

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')

I nie, nic nie zyskujesz, umieszczając wyraźne returnna końcu swojej funkcji. Możesz użyć, returnaby wyjść wcześniej, ale masz go na końcu, a funkcja zakończy się bez niego. (Oczywiście w przypadku funkcji, które zwracają wartość, służy returndo określania wartości do zwrócenia.)

Używanie wielu open()elementów withnie było obsługiwane w Pythonie 2.5, gdy wprowadzono withinstrukcję lub w Pythonie 2.6, ale jest obsługiwane w Pythonie 2.7 i Pythonie 3.1 lub nowszym.

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

Jeśli piszesz kod, który musi działać w Python 2.5, 2.6 lub 3.0, zagnieżdż withinstrukcje, tak jak sugerują inne odpowiedzi lub użyj contextlib.nested.

steveha
źródło
28

Użyj takich zagnieżdżonych bloków,

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

Możesz zagnieździć swoje klocki. Lubię to:

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)

Jest to lepsze niż Twoja wersja, ponieważ gwarantujesz, że outfilezostanie zamknięty, nawet jeśli Twój kod napotka wyjątki. Oczywiście możesz to zrobić za pomocą try / wreszcie, ale withjest to właściwy sposób na zrobienie tego.

Lub, jak właśnie się dowiedziałem, możesz mieć wielu menedżerów kontekstu w instrukcji z opisem opisanym przez @steveha . Wydaje mi się, że jest to lepsza opcja niż zagnieżdżanie.

A w przypadku ostatniego drobnego pytania zwrot nie ma żadnego rzeczywistego celu. Usunę to.

David Heffernan
źródło
Dziękuję bardzo. Spróbuję i zaakceptuję twoją odpowiedź, jeśli / kiedy ją uruchomię.
Disnami
Dzięki jeszcze raz. Zanim będę mógł zaakceptować, muszę poczekać na siedem mennic.
Disnami
7
@Disnami upewnij się, że akceptujesz poprawną odpowiedź (i to nie ta!) ;-)
David Heffernan
1

Czasami możesz chcieć otworzyć zmienną liczbę plików i traktować każdy z nich tak samo, możesz to zrobić 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())                   
brat-bilo
źródło