Метод print() для печати переданного выражения буквально вместе с вычисленным выводом для быстрой отладки

Я хотел бы иметь возможность выполнять отладку Python, используя print() или аналогичный метод, где он печатает переданное выражение в дополнение к обычному выводу.

Например, для следующего кода:

print(42 + 42)
print(type(list))
print(datetime.now())

Текущий вывод:

84
<class 'type'>
2019-08-15 22:43:57.805861

Ожидаемый результат:

42 + 42 : 84
type(list) : <class 'type'>
datetime.now() : 2019-08-15 22:43:57.805861

В настоящее время то же самое может быть достигнуто путем добавления строки выражения вручную (не очень элегантно, imho и нарушает принцип DRY).

print("42 + 42 : ", 42 + 42)
print("type(list) : ", type(list))
print("datetime.now() : ", datetime.now())

Я пытался переопределить встроенную печать, но безуспешно:

import builtins
def print(*args, **kwargs):
    return builtins.print(*args, **kwargs)  # passed expression isn't available here as string!

Есть ли способ достичь этого? Спасибо!

Ответы

Ответ 1

Используя Augusto Men Answer в качестве основы, следует переопределить встроенный метод python print(). Это поможет выполнить отладку без каких-либо изменений в тестируемом коде, просто добавьте это определение функции поверх любого файла, и он будет работать.

from inspect import getframeinfo, currentframe
import builtins

def print(*args, **kwargs):
    info = getframeinfo(currentframe().f_back)
    label = ''.join(info.code_context).strip()
    label = label.replace('print(', '', 1)[:-1].strip()  # (optional)
    return builtins.print(label, ':', *args, **kwargs)

print(42 + 42)
print(type(list))
print(datetime.now())
print([i for i in range(5)])
if 1 < 2: print('True')

Выход:

42 + 42 : 84
type(list) : <class 'type'>
datetime.now() : 2019-08-28 16:00:10.812306
[i for i in range(5)] : [0, 1, 2, 3, 4]
if 1 < 2: 'True' : True

Ответ 2

f-strings будет поддерживать что-то подобное в Python 3.8 (в настоящее время в бета-версии).

Из документов:

F-строка, такая как f '{expr =}', расширится до текста выражения, знака равенства, а затем представления вычисленного выражения. Например:

>>> user = 'eric_idle'
>>> member_since = date(1975, 7, 31)
>>> f'{user=} {member_since=}'
"user='eric_idle' member_since=datetime.date(1975, 7, 31)"

Обычные описатели формата f-строки позволяют лучше контролировать отображение результата выражения:

>>> delta = date.today() - member_since
>>> f'{user=!s}  {delta.days=:,d}'
'user=eric_idle  delta.days=16,075'

Спецификатор = отобразит все выражение, чтобы можно было увидеть вычисления:

>>> print(f'{theta=}  {cos(radians(theta))=:.3f}')
theta=30  cos(radians(theta))=0.866

Ответ 3

Обычно я думаю, что если вы используете eval, возможно, есть лучший способ сделать то, что вы пытаетесь сделать, но:

for statement in ["42 + 42", "type(list)", "datetime.now()"]:
    print("{} : {}".format(statement, eval(statement))

Ответ 4

Вы можете определить функцию superprint и распечатать ее, а затем вычислить строку:

from datetime import datetime

def superprint(str):
    print(str," : ",eval(str))

a = "42 + 42"
b = "type(list)"
c = "datetime.now()"
superprint(a)
superprint(b)
superprint(c)

OUTPUT

42 + 42  :  84
type(list)  :  <class 'type'>
datetime.now()  :  2019-08-15 14:44:43.072780

Если вы можете скинуть все, что хотите напечатать в кавычках, это может сработать для вас.

Ответ 5

Вы можете использовать модуль inspect для получения исходной строки (code_context) от вызывающей стороны:

from inspect import getframeinfo, currentframe


def vprint(value):
    caller = currentframe().f_back
    info = getframeinfo(caller)
    label = ''.join(info.code_context).strip()
    label = label.replace('vprint(', '')[:-1].strip()
    print(label, '=', value)


>>> vprint(12 + 3)
12 + 3 = 15
>>> vprint(type(list))
type(list) = <type 'type'>
>>> vprint(lambda x: x + 1)
lambda x: x + 1 = <function <lambda> at 0x7f93c104b9b0>

Хорошо подойдет только для однострочных оценок. Поскольку code_context возвращает только выполненную строку (а не всю инструкцию), это может произойти:

>>> vprint([''] +
...:     ['a', 'b'])
['a', 'b'] = ['', 'a', 'b']
>>> vprint(math.log(
...:   2 * math.pi))
2 * math.pi) = 1.83787706641

Примечание: разрыв строки с \ исправляет это (хотя отступ будет выглядеть странно):

>>> vprint(math.log( \
...:   2 * math.pi))
math.log(   2 * math.pi) = 1.83787706641

Ответ 6

Когда вы вызываете метод print, переданные аргументы не оцениваются методом print, они оцениваются перед передачей в метод print в качестве аргумента.

print(42 + 42)  => print(84)
print(type(list)) => print(<type 'type'>)
print(datetime.now()) => print(datetime.datetime(2019, 8, 15, 23, 9, 50, 619157))

Метод внутренней печати просто преобразует данный объект в строку, вызывая метод __str __() переданного объекта

Ответ 7

Изменение: Извините, мой ответ не совсем правильный, пожалуйста, смотрите @Augusto Men.

Ну, нет ничего невозможного в Python, но использование eval не всегда может работать:

import inspect
from datetime import datetime

def my_print(a):
    assert(callable(a))
    source = inspect.getsource(a).replace("my_print(lambda:", "").strip()[:-1]
    print(source + " : " + str(a()))

# pass lambda function to my_print
print("my_print_result:")
my_print(lambda: 42 + 42)
my_print(lambda: type(list))
my_print(lambda: datetime.now())


def eval_print(s):
    print(s + " : " + str(eval(s)))

# another way is passing string to eval_print
# but eval has its own special evaluation rules
# which will not work as expected when used in function
print("\neval_print result:")
eval_print("42 + 42")
eval_print("type(list)")
eval_print("datetime.now()")

def test():
    local_test_1 = 1
    my_print(lambda: local_test_1 + local_test_1)
    eval_print("local_test_1 + local_test_1")

print("\ntest in function:")
test()

выход:

my_print_result:
42 + 42 : 84
type(list) : <class 'type'>
datetime.now() : 2019-08-26 07:06:30.550408

eval_print result:
42 + 42 : 84
type(list) : <class 'type'>
datetime.now() : 2019-08-26 07:06:30.551110

test in function:
local_test_1 + local_test_1 : 2
NameError: name 'local_test_1' is not defined

При использовании eval в функции у нас будет NameError, потому что eval в Python имеет специальное правило:

eval() иexec() функции не имеют доступа к полной среде для разрешения имен. имена может быть разрешен в локальных и глобальных пространствах имен вызывающей стороны. Free переменные не разрешаются в ближайшем окружающем пространстве имен, но в глобальное пространство имен.

ссылка из 4. Модель исполнения - документация Python 3.7.4

Ответ 8

Вы можете использовать https://github.com/cool-RR/PySnooper

In [1]: from datetime import datetime                                                                                                                                                                                                         

In [2]: import pysnooper                                                                                                                                                                                                                      

In [3]: @pysnooper.snoop() 
   ...: def output(): 
   ...:     print(42 + 42) 
   ...:     print(type(list)) 
   ...:     print(datetime.now()) 
   ...:                                                                                                                                                                                                                                       

In [4]: output()                                                                                                                                                                                                                              
Source path:... <ipython-input-3-d5732f8e9c36>
22:14:08.934915 call         2 def output():
22:14:08.935031 line         3     print(42 + 42)
84
22:14:08.935061 line         4     print(type(list))
<class 'type'>
22:14:08.935083 line         5     print(datetime.now())
2019-08-25 22:14:08.935100
22:14:08.935109 return       5     print(datetime.now())
Return value:.. None

Ответ 9

Просто используйте трассировку и найдите аргумент вызова.

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

import re
import traceback
def prnt_expression(expression):
    for s in traceback.format_stack():
        match = re.search('prnt_expression\((.*)\)', s)
        if match:
            expression_string = match.group(1)
            break
    print(f'{expression_string} : {expression}')

называйте это так:

prnt_expression(42 + 42)