Python записать обе команды и их вывод в файл - PullRequest
7 голосов
/ 19 февраля 2020

Есть ли способ записать обе команды и их вывод во внешний файл?

Допустим, у меня есть скрипт 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}')

Теперь, если я хочу захватить только вывод, я знаю, что могу сделать:

python3 outtest.py > outtest.txt

Однако, это даст мне только файл outtest.txt , например:

mean: 0.34
median: 0.27

Мне нужен способ получить выходной файл, например :

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

или какой-либо другой формат (уценка, что угодно). По сути, что-то вроде jupyter notebook или Rmarkdown , но с использованием стандартных .py файлов.

Есть ли простой способ добиться этого?

Ответы [ 4 ]

2 голосов
/ 19 февраля 2020

Вот скрипт, который я только что написал, который довольно полно фиксирует напечатанный вывод и печатает его вместе с кодом, независимо от того, как он напечатан или сколько напечатано в одном go. Он использует модуль ast для анализа источника Python, выполняет программу по одному выражению за раз (как если бы он был передан в REPL), а затем печатает выходные данные каждого оператора. Python 3.6+ (но легко модифицируется, например, 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()

Вот тестовый скрипт:

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)

и результат:

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'}
1 голос
/ 19 февраля 2020

Вы можете вызвать скрипт и проверить его вывод. Вы должны будете сделать некоторые предположения, хотя. Вывод только из стандартного вывода, только из строк, содержащих строку «печать», и каждый вывод производит только одну строку вывода. В этом случае пример команды для его запуска:

> python writer.py script.py

И скрипт будет выглядеть так:

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

И вывод:

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

Более сложные случаи с многострочным выводом или другими операторами, производящими вывод, это не будет работать. Я предполагаю, что для этого потребуется глубокий самоанализ.

0 голосов
/ 19 февраля 2020

Мой ответ может быть похож на ответ @Felix, но я пытаюсь сделать это в стиле pythoni c.

Просто захватите исходный код (mycode), затем запустите его и запишите результаты, наконец запишите их обратно в выходной файл.

Помните, что я использовал exec для демонстрации решения, и вы не должны использовать его в производственной среде.

Я также использовал этот ответ для захвата stdout из exec. import mycode import осмотреть import sys из io import StringIO import contextlib

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 голосов
/ 19 февраля 2020

Вы можете присвоить результат переменной перед распечаткой, а затем записать эти значения в файл

Вот пример

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

аргумент «+» означает открытый файл и добавление содержимого в файл вместо перезаписи, если файл существовал.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...