Gibt es eine Möglichkeit, beide Befehle und ihre Ausgabe in eine externe Datei zu schreiben?

Angenommen, ich habe ein Skript outtest.py :

import random
from statistics import median, mean

d = [random.random()**2 for _ in range(1000)]
d_mean = round(mean(d), 2)
print(f'mean: {d_mean}')
d_median = round(median(d), 2)
print(f'median: {d_median}')

Wenn ich nur die Ausgabe erfassen möchte, weiß ich, dass ich Folgendes tun kann:

python3 outtest.py > outtest.txt

Dies gibt mir jedoch nur eine outtest.txt -Datei mit z.

mean: 0.34
median: 0.27

Was ich suche, ist eine Möglichkeit, eine Ausgabedatei wie zu erhalten:

import random
from statistics import median, mean

d = [random.random()**2 for _ in range(1000)]
d_mean = round(mean(d), 2)
print(f'mean: {d_mean}')
>> mean: 0.34
d_median = round(median(d), 2)
print(f'median: {d_median}')
>> median: 0.27

Oder ein anderes Format (Abschlag, was auch immer). Im Wesentlichen etwas wie jupyter notebook oder Rmarkdown , jedoch mit Standarddateien .py .

Gibt es eine einfache Möglichkeit, dies zu erreichen?

7
pieca 19 Feb. 2020 im 12:37

4 Antworten

Beste Antwort

Hier ist ein Skript, das ich gerade geschrieben habe und das die gedruckte Ausgabe ziemlich umfassend erfasst und neben dem Code druckt, unabhängig davon, wie sie gedruckt wird oder wie viel auf einmal gedruckt wird. Es verwendet das Modul ast, um die Python-Quelle zu analysieren, führt das Programm einzeln aus (so als ob es der REPL zugeführt wurde) und druckt dann die Ausgabe jeder Anweisung. Python 3.6+ (aber leicht modifizierbar für beispielsweise Python 2.x):

import ast
import sys

if len(sys.argv) < 2:
    print(f"Usage: {sys.argv[0]} <script.py> [args...]")
    exit(1)

# Replace stdout so we can mix program output and source code cleanly
real_stdout = sys.stdout
class FakeStdout:
    ''' A replacement for stdout that prefixes # to every line of output, so it can be mixed with code. '''
    def __init__(self, file):
        self.file = file
        self.curline = ''

    def _writerow(self, row):
        self.file.write('# ')
        self.file.write(row)
        self.file.write('\n')

    def write(self, text):
        if not text:
            return
        rows = text.split('\n')
        self.curline += rows.pop(0)
        if not rows:
            return
        for row in rows:
            self._writerow(self.curline)
            self.curline = row

    def flush(self):
        if self.curline:
            self._writerow(self.curline)
            self.curline = ''

sys.stdout = FakeStdout(real_stdout)

class EndLineFinder(ast.NodeVisitor):
    ''' This class functions as a replacement for the somewhat unreliable end_lineno attribute.

    It simply finds the largest line number among all child nodes. '''

    def __init__(self):
        self.max_lineno = 0

    def generic_visit(self, node):
        if hasattr(node, 'lineno'):
            self.max_lineno = max(self.max_lineno, node.lineno)
        ast.NodeVisitor.generic_visit(self, node)

# Pretend the script was called directly
del sys.argv[0]

# We'll walk each statement of the file and execute it separately.
# This way, we can place the output for each statement right after the statement itself.
filename = sys.argv[0]
source = open(filename, 'r').read()
lines = source.split('\n')
module = ast.parse(source, filename)
env = {'__name__': '__main__'}

prevline = 0
endfinder = EndLineFinder()

for stmt in module.body:
    # note: end_lineno will be 1-indexed (but it's always used as an endpoint, so no off-by-one errors here)
    endfinder.visit(stmt)
    end_lineno = endfinder.max_lineno
    for line in range(prevline, end_lineno):
        print(lines[line], file=real_stdout)
    prevline = end_lineno
    # run a one-line "module" containing only this statement
    exec(compile(ast.Module([stmt]), filename, 'exec'), env)
    # flush any incomplete output (FakeStdout is "line-buffered")
    sys.stdout.flush()

Hier ist ein Testskript:

print(3); print(4)
print(5)

if 1:
    print(6)

x = 3
for i in range(6):
    print(x + i)

import sys
sys.stdout.write('I love Python')

import pprint
pprint.pprint({'a': 'b', 'c': 'd'}, width=5)

Und das Ergebnis:

print(3); print(4)
# 3
# 4
print(5)
# 5

if 1:
    print(6)
# 6

x = 3
for i in range(6):
    print(x + i)
# 3
# 4
# 5
# 6
# 7
# 8

import sys
sys.stdout.write('I love Python')
# I love Python

import pprint
pprint.pprint({'a': 'b', 'c': 'd'}, width=5)
# {'a': 'b',
#  'c': 'd'}
2
nneonneo 19 Feb. 2020 im 11:28

Sie können das Skript aufrufen und seine Ausgabe überprüfen. Sie müssen jedoch einige Annahmen treffen. Die Ausgabe erfolgt nur von stdout, nur von Zeilen, die die Zeichenfolge "print" enthalten, und jeder Druck erzeugt nur eine Ausgabezeile. In diesem Fall ein Beispielbefehl zum Ausführen:

> python writer.py script.py

Und das Skript würde so aussehen:

from sys import argv
from subprocess import run, PIPE

script = argv[1]
r = run('python ' + script, stdout=PIPE)
out = r.stdout.decode().split('\n')

with open(script, 'r') as f:
    lines = f.readlines()

with open('out.txt', 'w') as f:
    i = 0
    for line in lines:
        f.write(line)
        if 'print' in line:
            f.write('>>> ' + out[i])
            i += 1

Und die Ausgabe:

import random
from statistics import median, mean

d = [random.random()**2 for _ in range(1000)]
d_mean = round(mean(d), 2)
print(f'mean: {d_mean}')
>>> mean: 0.33
d_median = round(median(d), 2)
print(f'median: {d_median}')
>>> median: 0.24

Bei komplexeren Fällen mit mehrzeiliger Ausgabe oder anderen Anweisungen, die eine Ausgabe erzeugen, funktioniert dies nicht. Ich denke, es würde eine gründliche Selbstbeobachtung erfordern.

1
Felix 19 Feb. 2020 im 10:04

Sie können der Variablen vor dem Ausdruck ein Ergebnis zuweisen und diese Werte später in eine Datei schreiben

Hier ist ein Beispiel

import random
from statistics import median, mean

d = [random.random()**2 for _ in range(1000)]
d_mean = round(mean(d), 2)
foo = f'mean: {d_mean}'
print(foo)
d_median = round(median(d), 2)
bar = f'median: {d_median}'
print(bar)

# save to file
with open("outtest.txt", "a+") as file:
    file.write(foo + "\n")
    file.write(bar + "\n")
    file.close()

Argument "a +" bedeutet "Datei öffnen" und "Inhalt an Datei anhängen" anstatt zu überschreiben, wenn eine Datei vorhanden war.

-1
Audy 19 Feb. 2020 im 09:50

Meine Antwort ähnelt möglicherweise der Antwort von @Felix, aber ich versuche es auf pythonische Weise.

Nehmen Sie einfach den Quellcode (mycode), führen Sie ihn aus, erfassen Sie die Ergebnisse und schreiben Sie sie schließlich zurück in die Ausgabedatei.

Denken Sie daran, dass ich exec für die Demonstration verwendet habe für die Lösung und Sie sollten es nicht in einer Produktionsumgebung verwenden.

Ich habe auch diese Antwort verwendet, um stdout von exec zu erfassen. Mycode importieren Import inspizieren sys importieren von io StringIO importieren Kontextlib importieren

source_code = inspect.getsource(mycode)
output_file = "output.py"


@contextlib.contextmanager
def stdoutIO(stdout=None):
    old = sys.stdout
    if stdout is None:
        stdout = StringIO()
    sys.stdout = stdout
    yield stdout
    sys.stdout = old


# execute code
with stdoutIO() as s:
    exec(source_code)

# capture stdout
stdout = s.getvalue().splitlines()[::-1]

# write to file
with open(output_file, "w") as f:
    for line in source_code.splitlines():
        f.write(line)
        f.write('\n')
        if 'print' in line:
            f.write(">> {}".format(stdout.pop()))
            f.write('\n')
0
M. R. 19 Feb. 2020 im 11:16