Comment rediriger la sortie 'print' vers un fichier en utilisant python?

194

Je souhaite rediriger l'impression vers un fichier .txt en utilisant python. J'ai une boucle «for», qui «imprimera» la sortie pour chacun de mes fichiers .bam pendant que je souhaite rediriger TOUTES ces sorties vers un fichier. Alors j'ai essayé de mettre

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

au début de mon scénario. Cependant, je n'obtiens rien dans le fichier .txt. Mon script est:

#!/usr/bin/python

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

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

path= '/home/xxx/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....

Donc quel est le problème? Un autre moyen en plus de ce sys.stdout?

J'ai besoin que mon résultat ressemble à:

Filename: ERR001268.bam
Readlines finished!
Mean: 233
SD: 10
Interval is: (213, 252)

================= Comment le temps passe vite. Il y a près de 10 ans, j'ai commencé à travailler sur la bioinformatique sans aucune compétence en codage. Ensuite, j'ai abandonné mon doctorat et ma carrière précédente et suis devenu un pur ingénieur logiciel.

LookIntoEast
la source
7
Pourquoi ne pas l'utiliser f.write(data)?
Eran Zimmerman Gonen
ouais, mais j'ai plusieurs données pour chaque fichier bam (moyenne, SD, intervalle ...), comment puis-je mettre ces données une par une?
LookIntoEast
f.write(line)- il insère un saut de ligne à la fin.
Eran Zimmerman Gonen
8
@Eran Zimmerman: f.write(line)n'ajoute pas de saut de ligne aux données.
hughdbrown
Tu as raison, mon mauvais. Pourrait toujours f.write(line+'\n'), cependant ..
Eran Zimmerman Gonen

Réponses:

290

La manière la plus évidente de le faire serait d'imprimer dans un objet fichier:

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

Cependant, la redirection stdout fonctionne également pour moi. C'est probablement bien pour un script unique comme celui-ci:

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

La redirection externe à partir du shell lui-même est une autre bonne option:

./script.py > out.txt

D'autres questions:

Quel est le premier nom de fichier dans votre script? Je ne le vois pas initialisé.

Ma première hypothèse est que glob ne trouve aucun fichier bam et que la boucle for ne s'exécute donc pas. Vérifiez que le dossier existe et imprimez les bamfiles dans votre script.

Utilisez également os.path.join et os.path.basename pour manipuler les chemins et les noms de fichiers.

Gringo Suave
la source
La ligne 8 de votre code utilise une variable nommée filename, mais elle n'a pas encore été créée. Plus tard dans la boucle, vous l'utilisez à nouveau, mais ce n'est pas pertinent.
Gringo Suave
2
Mauvaise pratique pour changer sys.stdout si vous n'en avez pas besoin.
désir de machine
5
@my Je ne suis pas convaincu que ce soit mauvais pour un simple script comme celui-ci.
Gringo Suave
4
+1 Haha eh bien, vous pouvez avoir mon vote positif car c'est la bonne façon de le faire si vous devez absolument le faire dans le mauvais sens ... Mais je dis toujours que vous devriez le faire avec une sortie de fichier régulière.
désir de machine
1
Comment rediriger et imprimer la sortie sur la console? Semble le "print ()" en Python ne peut pas être affiché lorsque le stdrr est redirigé?
extérieur du
72

Vous pouvez rediriger l'impression avec l' >>opérateur.

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

Dans la plupart des cas, il vaut mieux simplement écrire normalement dans le fichier.

f.write('whatever')

ou, si vous souhaitez écrire plusieurs éléments avec des espaces, comme print:

f.write(' '.join(('whatever', str(var2), 'etc')))
agf
la source
2
S'il y a beaucoup d'instructions de sortie, celles-ci peuvent vieillir rapidement. L'idée originale des affiches est valable; il y a autre chose qui ne va pas avec le script.
Gringo Suave
1
L'idée originale de l'affiche est absolument invalide. Il n'y a aucune raison de rediriger stdout ici, car il récupère déjà les données dans une variable.
désir de machine
Je pense qu'il voulait dire «techniquement valable», en ce sens que vous pouvez, en fait, rediriger sys.stdout, non pas que c'était une bonne idée.
agf
36

Référence de l'API Python 2 ou Python 3 :

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

L' argument file doit être un objet avec une write(string)méthode; s'il n'est pas présent ou None, sys.stdoutsera utilisé. Les arguments imprimés étant convertis en chaînes de texte, print()ne peuvent pas être utilisés avec les objets fichier en mode binaire. Pour ceux-ci, utilisez file.write(...)plutôt.

Étant donné que l'objet fichier contient normalement une write()méthode, tout ce que vous avez à faire est de passer un objet fichier dans son argument.

Ecrire / écraser dans un fichier

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

Écrire / ajouter au fichier

with open('file.txt', 'a') as f:
    print('hello world', file=f)
Yeo
la source
2
J'ai juste confondu pourquoi certaines de ces réponses précédentes étaient de monkey patch the global sys.stdout:(
Yeo
36

Cela fonctionne parfaitement:

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

Maintenant, le bonjour sera écrit dans le fichier test.txt. Assurez-vous de fermer le stdoutavec un close, sans lui, le contenu ne sera pas enregistré dans le fichier

Pradeep Kumar
la source
3
mais même si nous exécutons sys.stdout.close(), si vous tapez quelque chose dans le shell python, l'erreur sera ValueError: I/O operation on closed file. affichée sous la forme imgur.com/a/xby9P . La meilleure façon de gérer cela est de suivre ce que @Gringo Suave a publié
Mourya
25

Ne pas utiliser print , utiliserlogging

Vous pouvez changer sys.stdoutpour pointer vers un fichier, mais c'est une manière assez maladroite et inflexible de gérer ce problème. Au lieu d'utiliser print, utilisez lelogging module.

Avec logging, vous pouvez imprimer comme vous le feriez stdoutou vous pouvez également écrire la sortie dans un fichier. Vous pouvez même utiliser les différents niveaux de message ( critical, error, warning, info,debug ), par exemple, n'imprimer les grandes questions à la console, mais toujours enregistrer les actions mineures du code dans un fichier.

Un exemple simple

Importez logging, récupérez loggeret définissez le niveau de traitement:

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

Si vous souhaitez imprimer sur stdout:

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

Si vous souhaitez également écrire dans un fichier (si vous ne souhaitez écrire que dans un fichier, passez la dernière section):

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

Ensuite, où que vous utilisiez, printutilisez l'une des loggerméthodes:

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

Pour en savoir plus sur l'utilisation de fonctionnalités plus avancées logging, lisez l'excellent loggingdidacticiel de la documentation Python .

jpyams
la source
Salut, je souhaite utiliser cette journalisation pour écrire les données de la console dans le fichier journal avec l'heure à laquelle ces données sont prises. Mais je ne peux pas comprendre correctement la fonction de journalisation ou la bibliothèque. Pouvez-vous m'aider avec ça
haris
@haris Lisez le didacticiel de journalisation de la documentation Python et consultez des exemples dans d'autres questions sur Stack Overflow (il y en a beaucoup). Si vous ne parvenez toujours pas à le faire fonctionner, posez une nouvelle question.
jpyams
12

La solution la plus simple n'est pas via python; c'est à travers la coque. Depuis la première ligne de votre fichier ( #!/usr/bin/python), je suppose que vous êtes sur un système UNIX. Utilisez simplement des printinstructions comme vous le feriez normalement, et n'ouvrez pas du tout le fichier dans votre script. Lorsque vous exécutez le fichier, au lieu de

./script.py

pour exécuter le fichier, utilisez

./script.py > <filename>

où vous remplacez <filename>par le nom du fichier dans lequel vous voulez que la sortie entre. Le >jeton dit (à la plupart) des shells de définir stdout sur le fichier décrit par le jeton suivant.

Une chose importante qui doit être mentionnée ici est que "script.py" doit être rendu exécutable pour ./script.pyfonctionner.

Alors avant de courir ./script.py, exécutez cette commande

chmod a+x script.py (rendre le script exécutable pour tous les utilisateurs)

Aaron Dufour
la source
3
./script.py> <filename> 2> & 1 Vous devez également capturer stderr. 2> & 1 fera ça
rtaft
1
@rtaft Pourquoi? La question veut spécifiquement diriger la sortie de printvers un fichier. Il serait raisonnable de s'attendre à ce que stdout (traces de pile et autres) s'imprime toujours sur le terminal.
Aaron Dufour
Il a dit que ça ne fonctionnait pas, que le mien ne fonctionnait pas non plus. J'ai découvert plus tard que cette application sur laquelle je travaille était configurée pour tout diriger vers stderr ... idk why.
rtaft
6

Si vous utilisez Linux, je vous suggère d'utiliser la teecommande. La mise en œuvre va comme ceci:

python python_file.py | tee any_file_name.txt

Si vous ne voulez rien changer dans le code, je pense que c'est peut-être la meilleure solution possible. Vous pouvez également implémenter un enregistreur, mais vous devez apporter des modifications au code.

yunus
la source
1
génial; le cherchait
Vicrobot
4

Vous n'aimerez peut-être pas cette réponse, mais je pense que c'est la BONNE. Ne changez pas votre destination stdout sauf si c'est absolument nécessaire (peut-être que vous utilisez une bibliothèque qui ne sort que vers stdout ??? clairement pas le cas ici).

Je pense que comme une bonne habitude, vous devez préparer vos données à l'avance sous forme de chaîne, puis ouvrez votre fichier et écrivez le tout en même temps. En effet, les opérations d'entrée / sortie sont plus longues que vous avez un descripteur de fichier ouvert, plus une erreur est susceptible de se produire avec ce fichier (erreur de verrouillage de fichier, erreur d'E / S, etc.). Faire tout cela en une seule opération ne laisse aucune question quant au moment où cela aurait pu mal tourner.

Voici un exemple:

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

Et puis lorsque vous avez fini de collecter vos "lignes de données" une ligne par élément de liste, vous pouvez les joindre avec quelques '\n'caractères pour créer une table de sortie; peut-être même envelopper votre instruction de sortie dans un withbloc, pour plus de sécurité (fermera automatiquement votre poignée de sortie même si quelque chose ne va pas):

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!!!"

Cependant, si vous avez beaucoup de données à écrire, vous pouvez les écrire un morceau à la fois. Je ne pense pas que ce soit pertinent pour votre application, mais voici l'alternative:

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()
désir de machine
la source
1
Avec la mise en cache du disque, les performances de l'original devraient être acceptables. Cette solution a cependant l'inconvénient de gonfler les besoins en mémoire s'il y avait beaucoup de sortie. Bien que probablement rien à craindre ici, c'est généralement une bonne idée d'éviter cela si possible. Même idée que d'utiliser xrange (gamme py3) au lieu de range, etc.
Gringo Suave
@Gringo: Il n'a pas précisé cette exigence. J'écris rarement suffisamment de données dans un fichier pour que cela soit pertinent. Ce n'est pas la même idée que xrange car xrange ne traite pas les entrées / sorties de fichiers. La mise en cache du disque peut aider, mais c'est toujours une mauvaise pratique de garder un descripteur de fichier ouvert pour un grand corps de code.
désir de machine
1
Votre commentaire se contredit. Pour être honnête, l'aspect performance des deux approches n'est pas pertinent pour des quantités de données non importantes. xrange est certainement similaire, il fonctionne sur une seule pièce à la fois au lieu de tous à la fois en mémoire. Peut-être qu'un générateur vs une liste est un meilleur exemple.
Gringo Suave
@Gringo: Je ne vois pas en quoi mon commentaire se contredit. Peut-être que l'aspect performances n'est pas pertinent, garder un descripteur de fichier ouvert pendant une période prolongée augmente toujours le risque d'erreur. Dans la programmation, les entrées / sorties de fichiers sont toujours intrinsèquement plus risquées que de faire quelque chose dans votre propre programme, car cela signifie que vous devez tendre la main via le système d'exploitation et jouer avec les verrous de fichiers. Plus vous avez un fichier ouvert, mieux c'est, simplement parce que vous ne contrôlez pas le système de fichiers à partir de votre code. xrange est différent car il n'a rien à voir avec les entrées / sorties de fichiers, et pour info j'utilise rarement xrange non plus; cheers
machine ardent
2
@Gringo: J'apprécie vos critiques et j'ai apprécié le débat houleux. Même si nous n'étions pas d'accord sur certains points, je respecte toujours votre point de vue car il est clair que vous avez une bonne raison de prendre votre position. Merci d'avoir terminé raisonnablement et passez une très bonne nuit. : P
désir de machine
3

Si la redirection stdoutfonctionne pour votre problème, la réponse de Gringo Suave est une bonne démonstration pour savoir comment le faire.

Pour rendre les choses encore plus faciles , j'ai créé une version utilisant des contextmanagers pour une syntaxe d'appel généralisée succincte en utilisant l' withinstruction:

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

Pour l'utiliser, il suffit de faire ce qui suit (dérivé de l'exemple de Suave):

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

C'est utile pour rediriger sélectivement printlorsqu'un module l'utilise d'une manière que vous n'aimez pas. Le seul inconvénient (et c'est le dealbreaker dans de nombreuses situations) est que cela ne fonctionne pas si l'on veut plusieurs threads avec des valeurs différentes de stdout, mais cela nécessite une meilleure méthode plus généralisée: l'accès indirect aux modules. Vous pouvez voir des implémentations de cela dans d'autres réponses à cette question.

Graham
la source
0

La modification de la valeur de sys.stdout change la destination de tous les appels à imprimer. Si vous utilisez une autre manière de changer la destination de l'impression, vous obtiendrez le même résultat.

Votre bug est ailleurs:

  • il peut s'agir du code que vous avez supprimé pour votre question (d'où vient le nom de fichier pour que l'appel s'ouvre?)
  • il se peut aussi que vous n'attendiez pas que les données soient vidées: si vous imprimez sur un terminal, les données sont vidées après chaque nouvelle ligne, mais si vous imprimez dans un fichier, elles ne sont vidées que lorsque le tampon stdout est plein (4096 octets sur la plupart des systèmes).
Jérôme
la source
-1

Quelque chose pour étendre la fonction d'impression pour les boucles

x = 0
while x <=5:
    x = x + 1
    with open('outputEis.txt', 'a') as f:
        print(x, file=f)
    f.close()
ishiry ish
la source
pas besoin d'utiliser whileet pas besoin de fermer le fichier lors de l'utilisationwith
Daniel