Jak przekierować wyjście „print” do pliku za pomocą Pythona?

184

Chcę przekierować wydruk do pliku .txt przy użyciu języka Python. Mam pętlę „for”, która „wypisze” dane wyjściowe dla każdego z moich plików .bam, podczas gdy chcę przekierować WSZYSTKIE dane wyjściowe do jednego pliku. Więc próbowałem umieścić

 f = open('output.txt','w'); sys.stdout = f

na początku mojego scenariusza. Jednak nic nie mam w pliku .txt. Mój skrypt to:

#!/usr/bin/python

import os,sys
import subprocess
import glob
from os import path

f = open('output.txt','w')
sys.stdout = f

path= '/home/xug/nearline/bamfiles'
bamfiles = glob.glob(path + '/*.bam')

for bamfile in bamfiles:
    filename = bamfile.split('/')[-1]
    print 'Filename:', filename
    samtoolsin = subprocess.Popen(["/share/bin/samtools/samtools","view",bamfile],
                                  stdout=subprocess.PIPE,bufsize=1)
    linelist= samtoolsin.stdout.readlines()
    print 'Readlines finished!'
    ........print....
    ........print....

Więc w czym problem? Jakikolwiek inny sposób poza tym sys.stdout?

Chcę, aby mój wynik wyglądał następująco:

Filename: ERR001268.bam
Readlines finished!
Mean: 233
SD: 10
Interval is: (213, 252)
LookIntoEast
źródło
7
Dlaczego nie używać f.write(data)?
Eran Zimmerman Gonen
tak, ale mam kilka danych dla każdego pliku bam (średnia, SD, interwał ...), jak mogę umieścić te dane jeden po drugim?
LookIntoEast
f.write(line)- na końcu wstawia znak końca wiersza.
Eran Zimmerman Gonen
8
@Eran Zimmerman: f.write(line)nie dodaje podziału wiersza do danych.
hughdbrown
Masz rację, moja wina. f.write(line+'\n')Jednak zawsze może …
Eran Zimmerman Gonen

Odpowiedzi:

274

Najbardziej oczywistym sposobem byłoby wydrukowanie do obiektu pliku:

with open('out.txt', 'w') as f:
    print >> f, 'Filename:', filename     # Python 2.x
    print('Filename:', filename, file=f)  # Python 3.x

Jednak przekierowanie stdout również działa dla mnie. Prawdopodobnie jest w porządku w przypadku jednorazowego scenariusza, takiego jak ten:

import sys

orig_stdout = sys.stdout
f = open('out.txt', 'w')
sys.stdout = f

for i in range(2):
    print 'i = ', i

sys.stdout = orig_stdout
f.close()

Przekierowanie na zewnątrz z samej powłoki to kolejna dobra opcja:

./script.py > out.txt

Inne pytania:

Jaka jest pierwsza nazwa pliku w twoim skrypcie? Nie widzę zainicjowania.

Moje pierwsze przypuszczenie jest takie, że glob nie znajduje żadnych plików bamfiles i dlatego pętla for nie działa. Sprawdź, czy folder istnieje i wydrukuj bamfiles w swoim skrypcie.

Ponadto użyj os.path.join i os.path.basename do manipulowania ścieżkami i nazwami plików.

Gringo Suave
źródło
Linia 8 twojego kodu używa zmiennej o nazwie filename, ale nie została jeszcze utworzona. W dalszej części pętli używasz go ponownie, ale nie ma to znaczenia.
Gringo Suave
2
Zła praktyka, aby zmienić sys.stdout, jeśli nie musisz.
maszyna tęskniła
3
@my Nie jestem przekonany, że jest zły dla tak prostego skryptu.
Gringo Suave
4
+1 Haha, możesz mieć moje upvote, ponieważ jest to właściwy sposób, aby to zrobić, jeśli absolutnie musisz to zrobić w niewłaściwy sposób ... Ale nadal twierdzę, że powinieneś to zrobić za pomocą zwykłego pliku wyjściowego.
maszyna tęskniła
1
Jak przekierować i wydrukować wynik na konsoli? Wydaje się, że "print ()" w Pythonie nie może być pokazane, gdy stdrr jest przekierowywany?
zewnętrzna
70

Możesz przekierować drukowanie z >>operatorem.

f = open(filename,'w')
print >>f, 'whatever'     # Python 2.x
print('whatever', file=f) # Python 3.x

W większości przypadków lepiej jest po prostu normalnie pisać do pliku.

f.write('whatever')

lub, jeśli masz kilka elementów, które chcesz zapisać ze spacjami, na przykład print:

f.write(' '.join(('whatever', str(var2), 'etc')))
agf
źródło
2
Jeśli jest dużo instrukcji wyjściowych, mogą one szybko się zestarzeć. Oryginalny pomysł plakatu obowiązuje; ze skryptem jest jeszcze coś nie tak.
Gringo Suave
1
Oryginalny pomysł autora jest całkowicie nieaktualny. Nie ma powodu, aby przekierowywać tutaj standardowe wyjście, ponieważ on już pobiera dane do zmiennej.
maszyna tęskniła
Myślę, że miał na myśli „technicznie poprawne”, ponieważ w rzeczywistości można przekierować sys.stdout, a nie to, że był to dobry pomysł.
agf
35

Dokumentacja API Python 2 lub Python 3 :

print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)

Plik argument musi być obiektem z write(string)metodą; jeśli nie jest obecny lub None, sys.stdoutzostaną wykorzystane. Ponieważ drukowane argumenty są konwertowane na łańcuchy tekstowe, print()nie można ich używać z obiektami plików trybu binarnego. W tym przypadku użyj file.write(...)zamiast tego.

Ponieważ obiekt plikowy zwykle zawiera write()metodę, wszystko, co musisz zrobić, to przekazać obiekt plikowy do jego argumentu.

Zapisz / nadpisz do pliku

with open('file.txt', 'w') as f:
    print('hello world', file=f)

Zapisz / dołącz do pliku

with open('file.txt', 'a') as f:
    print('hello world', file=f)
Yeo
źródło
2
Po prostu pomyliłem się, dlaczego niektóre z tych wcześniejszych odpowiedzi miały na celu małpowanie łatek globalnych sys.stdout:(
Yeo,
35

Działa to doskonale:

import sys
sys.stdout=open("test.txt","w")
print ("hello")
sys.stdout.close()

Teraz hello zostanie zapisane w pliku test.txt. Upewnij się, że zamykasz stdoutza pomocą close, bez niego zawartość nie zostanie zapisana w pliku

Pradeep Kumar
źródło
3
ale nawet jeśli wykonamysys.stdout.close() , jeśli wpiszesz cokolwiek w powłoce Pythona, wyświetli się błąd jako ValueError: I/O operation on closed file. imgur.com/a/xby9P . Najlepszym sposobem radzenia sobie z tym jest śledzenie tego, co @Gringo Suave opublikował
Mourya
24

Nie używaj print, używajlogging

Możesz zmienić, sys.stdoutaby wskazywał na plik, ale jest to dość niezgrabny i nieelastyczny sposób rozwiązania tego problemu. Zamiast używać print, użyj loggingmodułu.

Za pomocą loggingmożesz drukować tak, jak chcesz stdout, lub możesz również zapisać wynik do pliku. Można nawet korzystać z różnych poziomów wiadomość ( critical, error, warning, info, debug), na przykład, drukować tylko główne problemy do konsoli, ale nadal zalogować drobne działania kodu do pliku.

Prosty przykład

Importuj logging, pobierz loggeri ustaw poziom przetwarzania:

import logging
logger = logging.getLogger()
logger.setLevel(logging.DEBUG) # process everything, even if everything isn't printed

Jeśli chcesz drukować na standardowe wyjście:

ch = logging.StreamHandler()
ch.setLevel(logging.INFO) # or any other level
logger.addHandler(ch)

Jeśli chcesz również pisać do pliku (jeśli chcesz zapisywać tylko do pliku, pomiń ostatnią sekcję):

fh = logging.FileHandler('myLog.log')
fh.setLevel(logging.DEBUG) # or any level you want
logger.addHandler(fh)

Następnie, gdziekolwiek byś użył, printużyj jednej z loggermetod:

# print(foo)
logger.debug(foo)

# print('finishing processing')
logger.info('finishing processing')

# print('Something may be wrong')
logger.warning('Something may be wrong')

# print('Something is going really bad')
logger.error('Something is going really bad')

Aby dowiedzieć się więcej o korzystaniu z bardziej zaawansowanych loggingfunkcji, przeczytaj doskonały loggingsamouczek w dokumentacji Pythona .

jpyams
źródło
Cześć, chcę użyć tego rejestrowania do zapisania danych konsoli do pliku dziennika z czasem, w którym te dane są pobierane. Ale nie jestem w stanie poprawnie zrozumieć funkcji logowania lub biblioteki. Czy możesz mi w tym pomóc
haris
@haris Przeczytaj samouczek rejestrowania dokumentacji Pythona i sprawdź przykłady w innych pytaniach na temat przepełnienia stosu (jest ich dużo). Jeśli nadal nie możesz zmusić go do pracy, zadaj nowe pytanie.
jpyams
12

Najłatwiejszym rozwiązaniem nie jest użycie Pythona; to przez powłokę. Z pierwszego wiersza twojego pliku ( #!/usr/bin/python) Domyślam się, że pracujesz w systemie UNIX. Po prostu użyj printinstrukcji tak jak zwykle i nie otwieraj pliku w ogóle w swoim skrypcie. Kiedy idziesz uruchomić plik, zamiast

./script.py

aby uruchomić plik, użyj

./script.py > <filename>

gdzie zamieniasz <filename>na nazwę pliku, do którego ma trafić wyjście. >Tokena mówi (większość) muszle Aby ustawić standardowe wyjście do pliku opisanym brzmienie żeton.

Jedną ważną rzeczą, o której należy tutaj wspomnieć, jest to, że plik „script.py” musi być uruchamiany, aby ./script.pymógł działać.

Dlatego przed uruchomieniem ./script.pywykonaj to polecenie

chmod a+x script.py (spraw, aby skrypt był wykonywalny dla wszystkich użytkowników)

Aaron Dufour
źródło
3
./script.py> <nazwa_pliku> 2> & 1 Musisz także przechwycić stderr. 2> i 1 to zrobią
rtaft
1
@rtaft Why? Pytanie konkretnie chce skierować dane wyjściowe printdo pliku. Byłoby rozsądnym oczekiwać, że stdout (ślady stosu i tym podobne) nadal będzie drukowane na terminalu.
Aaron Dufour,
Powiedział, że to nie działa, mój też nie działał. Później odkryłem, że ta aplikacja, nad którą pracuję, została skonfigurowana tak, aby kierować wszystko na stderr ... idk dlaczego.
rtaft
5

Jeśli używasz Linuksa, sugeruję użycie teepolecenia. Wdrożenie wygląda następująco:

python python_file.py | tee any_file_name.txt

Jeśli nie chcesz niczego zmieniać w kodzie, myślę, że może to być najlepsze możliwe rozwiązanie. Możesz również zaimplementować rejestrator, ale musisz wprowadzić pewne zmiany w kodzie.

yunus
źródło
1
świetny; szukał tego
Vicrobot
4

Możesz nie lubić tej odpowiedzi, ale myślę, że jest WŁAŚCIWA. Nie zmieniaj miejsca docelowego na stdout, chyba że jest to absolutnie konieczne (być może używasz biblioteki, która wysyła tylko na stdout ???, oczywiście nie w tym przypadku).

Myślę, że dobrym nawykiem jest przygotowanie danych z wyprzedzeniem w postaci ciągu znaków, a następnie otwarcie pliku i zapisanie całości na raz. Dzieje się tak, ponieważ im dłużej masz otwarty uchwyt pliku, tym bardziej prawdopodobne jest wystąpienie błędu w tym pliku (błąd blokady pliku, błąd wejścia / wyjścia itp.). Wykonanie tego wszystkiego w jednej operacji nie pozostawia wątpliwości, kiedy mogło się to udać.

Oto przykład:

out_lines = []
for bamfile in bamfiles:
    filename = bamfile.split('/')[-1]
    out_lines.append('Filename: %s' % filename)
    samtoolsin = subprocess.Popen(["/share/bin/samtools/samtools","view",bamfile],
                                  stdout=subprocess.PIPE,bufsize=1)
    linelist= samtoolsin.stdout.readlines()
    print 'Readlines finished!'
    out_lines.extend(linelist)
    out_lines.append('\n')

A kiedy skończysz zbierać swoje „linie danych”, po jednym wierszu na element listy, możesz połączyć je kilkoma '\n'znakami, aby całość stała się tabelą wyjściową; może nawet zawiń swoją instrukcję wyjściową w withblok, dla dodatkowego bezpieczeństwa (automatycznie zamknie uchwyt wyjściowy, nawet jeśli coś pójdzie nie tak):

out_string = '\n'.join(out_lines)
out_filename = 'myfile.txt'
with open(out_filename, 'w') as outf:
    outf.write(out_string)
print "YAY MY STDOUT IS UNTAINTED!!!"

Jednak jeśli masz dużo danych, aby napisać, to mógłby napisać to jeden kawałek na raz. Wydaje mi się, że nie ma to znaczenia dla Twojej aplikacji, ale oto alternatywa:

out_filename = 'myfile.txt'
outf = open(out_filename, 'w')
for bamfile in bamfiles:
    filename = bamfile.split('/')[-1]
    outf.write('Filename: %s' % filename)
    samtoolsin = subprocess.Popen(["/share/bin/samtools/samtools","view",bamfile],
                                  stdout=subprocess.PIPE,bufsize=1)
    mydata = samtoolsin.stdout.read()
    outf.write(mydata)
outf.close()
tęsknota za maszyną
źródło
1
Przy buforowaniu dysku wydajność oryginału powinna być akceptowalna. To rozwiązanie ma jednak wadę polegającą na zwiększaniu wymagań dotyczących pamięci, jeśli było dużo danych wyjściowych. Chociaż prawdopodobnie nie ma się czym martwić, ogólnie dobrym pomysłem jest unikanie tego, jeśli to możliwe. Sam pomysł, jak za pomocą XRange (zakres py3) zamiast zasięgu, itp
Gringo Suave
@Gringo: Nie określił tego wymagania. Rzadko kiedy zapisuję w pliku wystarczającą ilość danych, aby miało to znaczenie. To nie jest ten sam pomysł co xrange, ponieważ xrange nie zajmuje się we / wy pliku. Buforowanie dysku może pomóc, ale nadal jest złą praktyką utrzymywanie otwartego uchwytu pliku dla dużej ilości kodu.
maszyna tęskni
1
Twój komentarz zaprzecza samemu sobie. Szczerze mówiąc, aspekt wydajności obu podejść nie ma znaczenia w przypadku niewielkich ilości danych. xrange z pewnością jest podobny, działa na jednym kawałku na raz, a nie na wszystkich naraz w pamięci. Być może lepszym przykładem jest lista generator vs lista.
Gringo Suave
@Gringo: Nie widzę, jak mój komentarz zaprzecza samemu sobie. Być może aspekt wydajności nie ma znaczenia, utrzymywanie otwartego uchwytu pliku przez dłuższy czas zawsze zwiększa ryzyko błędu. W programowaniu operacji wejścia / wyjścia plików jest zawsze z natury bardziej ryzykowne niż robienie czegoś we własnym programie, ponieważ oznacza to, że musisz sięgać przez system operacyjny i bawić się blokadami plików. Im krócej masz otwarty plik, tym lepiej, po prostu dlatego, że nie kontrolujesz systemu plików z kodu. xrange jest inny, ponieważ nie ma nic wspólnego z we / wy pliku, a FYI rzadko używam też xrange; okrzyki
maszyna tęskniła
2
@Gringo: Doceniam twoją krytykę i podobała mi się gorąca debata. Mimo że nie zgadzaliśmy się w niektórych punktach, nadal szanuję twoje poglądy, ponieważ jest jasne, że masz dobry powód, by zająć swoje stanowisko. Dziękuję za rozsądne zakończenie i życzę bardzo dobrej nocy. : P
maszyna tęskni
2

Jeśli przekierowanie stdoutdziała w Twoim problemie, odpowiedź Gringo Suave jest dobrą demonstracją, jak to zrobić.

Aby było jeszcze łatwiej , stworzyłem wersję wykorzystującą menedżery kontekstu do zwięzłej uogólnionej składni wywoływania, używając withinstrukcji:

from contextlib import contextmanager
import sys

@contextmanager
def redirected_stdout(outstream):
    orig_stdout = sys.stdout
    try:
        sys.stdout = outstream
        yield
    finally:
        sys.stdout = orig_stdout

Aby go użyć, po prostu wykonaj następujące czynności (zaczerpnięte z przykładu Suave'a):

with open('out.txt', 'w') as outfile:
    with redirected_stdout(outfile):
        for i in range(2):
            print('i =', i)

Jest to przydatne do selektywnego przekierowywania, printgdy moduł używa go w sposób, którego nie lubisz. Jedyną wadą (i jest to przełamanie transakcji w wielu sytuacjach) jest to, że nie działa, jeśli chce się mieć wiele wątków o różnych wartościach stdout, ale wymaga to lepszej, bardziej uogólnionej metody: pośredniego dostępu do modułu. Możesz zobaczyć implementacje tego w innych odpowiedziach na to pytanie.

Graham
źródło
0

Zmiana wartości sys.stdout powoduje zmianę miejsca docelowego wszystkich wywołań drukowania. Jeśli użyjesz alternatywnego sposobu zmiany miejsca docelowego drukowania, uzyskasz ten sam wynik.

Twój błąd jest gdzie indziej:

  • może znajdować się w kodzie, który usunąłeś ze swojego pytania (skąd pochodzi nazwa pliku, aby wywołanie się otworzyło?)
  • może być również tak, że nie czekasz na opróżnienie danych: jeśli drukujesz na terminalu, dane są opróżniane po każdym nowym wierszu, ale jeśli drukujesz do pliku, są one opróżniane tylko wtedy, gdy bufor standardowego wyjścia jest pełny (4096 bajtów w większości systemów).
Jerome
źródło
-1

Coś do rozszerzenia funkcji drukowania dla pętli

x = 0
while x <=5:
    x = x + 1
    with open('outputEis.txt', 'a') as f:
        print(x, file=f)
    f.close()
ishiry ish
źródło
nie ma potrzeby używania whilei nie ma potrzeby zamykania pliku podczas używaniawith
Daniel Stracaboško