python: автоматически печатать представление каждого компонента в выражении - PullRequest
5 голосов
/ 24 января 2011

Я пишу утверждения вроде этого:

if f(x, y) != z:
  print(repr(x))
  print(repr(y))
  print(repr(z))
  raise MyException('Expected: f(x, y) == z')

Мне было интересно, есть ли способ написать функцию, которая бы принимала допустимое выражение Python и класс исключений в качестве входных данных, оценивала выражение,и если он обнаружит, что он ложный, выведите представление каждого из терминов самого низкого уровня в выражении и вызовите данное исключение?

# validate is the mystery function
validate('f(x, y) == z', MyException)

Ответы [ 6 ]

2 голосов
/ 24 января 2011

Вот реализация:

import inspect, keyword, pprint, sys, tokenize

def value_in_frame(name, frame):
    try:
        return frame.f_locals[name]
    except KeyError:
        try:
            return frame.f_globals[name]
        except KeyError:
            raise ValueError("Couldn't find value for %s" % name)

def validate(expr, exc_class=AssertionError):
    """Evaluate `expr` in the caller's frame, raise `exc_class` if false."""
    frame = inspect.stack()[1][0]
    val = eval(expr, frame.f_globals, frame.f_locals)
    if not val:
        rl = iter([expr]).next
        for typ, tok, _, _, _ in tokenize.generate_tokens(rl):
            if typ == tokenize.NAME and not keyword.iskeyword(tok):
                try:
                    val = value_in_frame(tok, frame)
                except ValueError:
                    val = '???'
                else:
                    val = repr(val)
                print "  %s: %s" % (tok, val)
        raise exc_class("Failed to validate: %s" % expr)

if __name__ == '__main__':
    a = b = 3
    validate("a + b == 5")
2 голосов
/ 24 января 2011

А как насчет утверждений?

assert  f(x, y) != z, 'Expected: f(%r, %r) == %r'%(x,y,z)

Редактировать

, чтобы добавить% r в print repr - спасибо за комментарий.

1 голос
/ 24 января 2011

Вы могли бы сделать это наоборот:

expr = 'f(%r, %r) != %r' % (x,y,z)

if eval(expr):
    raise MyException(expr)

Или другими словами:

def validate(expr,myexception):
    if eval(expr):
        raise myexception(expr)

Довольно грязно, хотя:)

1 голос
/ 24 января 2011

У тестового бегуна nose есть плагин, называемый "подробности об ошибках", который предоставляет именно эту услугу: http://somethingaboutorange.com/mrl/projects/nose/0.11.1/plugins/failuredetail.html. Это решение лучше, чем то, о чем вы просили, потому что выражение не является строкой, это реальное утверждение, которое потом анализируется, чтобы найти исходный код для анализа.

Пример приведен в старых документах :

Другими словами, если у вас есть тест как:

def test_integers():
    a = 2
    assert a == 4, "assert 2 is 4"

Вы получите вывод как:

File "/path/to/file.py", line XX, in test_integers:
      assert a == 4, "assert 2 is 4" 
AssertionError: assert 2 is 4
  >>  assert 2 == 4, "assert 2 is 4"
1 голос
/ 24 января 2011

Может быть, есть способ взломать что-нибудь, чтобы сделать то, что вы просите, но альтернативы, по крайней мере, намного проще:

  1. Как и сейчас, вручную расширяем интересующие вас значения. Это, вероятно, будет более надежным, особенно для сложных выражений, потому что вам проще сказать, что интересно, чем компьютеру сделать вывод. В частности, вы можете включить значение, которого нет даже в выражении.

    if f(x, y) != z:
      raise MyException("Unexpected: the foobar is glowing; " +
                        "f(%r, %r) != %r" % (x, y, z))
    

    Здесь вы, кажется, хотите x и y, но не (результат вызова) f (x, y), в то время как в других случаях это может быть перевернуто, или вам могут потребоваться все три. Алгоритму будет сложно определить ваши предпочтения.

  2. Вы можете запустить pdb в точке, где вы обнаружите ошибку, изучить все, что вам нравится (даже до стека вызовов), а затем возобновить «нормальное» выполнение. Это возможно только в некоторых контекстах; например вероятно, не для веб-приложения.

    if f(x, y) != z:
      import pdb
      pdb.set_trace()
      raise MyException("Unexpected: ...")
    
  3. Модуль регистрации вместо операторов печати или сообщения об исключении.

1 голос
/ 24 января 2011

Это можно было бы сделать. Вы можете использовать компилятор Python (детали отличаются в разных версиях, так что это просто обзор), чтобы скомпилировать данное выражение в AST. Затем вы можете скомпилировать AST в объект кода и оценить его (или просто сначала вызвать eval, что угодно). Затем, если значение ложно, проверьте AST, чтобы увидеть, как строится выражение. Распечатайте значение каждого элемента в выражении, к которому обращаются по имени согласно AST.

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