2011-01-23 9 views
5

znajdę sobie pisanie twierdzenia takie jak:python: automatyczne drukowanie reprezentację każdego składnika w wyrażeniu

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

Zastanawiałem się, czy istnieje sposób, aby napisać funkcję, która będzie przyjmować prawidłową ekspresję Python i wyjątek klasa jako dane wejściowe, ocenia wyrażenie i jeśli stwierdzi, że jest to fałsz, wydrukuj reprezentację każdego z najniższych poziomów w wyrażeniu i podnieś dany wyjątek?

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

Przerzuciłbym komunikat wyjątku: zamiast tego, co chcesz, podaj to, co wykrywasz d być błędnym. To daje to samo wyrażenie (! =), Które testowałeś, zamiast jego odwrotności (==). –

Odpowiedz

2

Oto realizacja:

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") 
+0

Cool. Czy jest to coś, co polecasz, czy też sugerowałbyś stosowanie bardziej tradycyjnych metod? – max

1

Byłoby to możliwe. Możesz użyć kompilatora Python (szczegóły różnią się między wersjami, więc jest to tylko przegląd), aby skompilować dane wyrażenie do AST. Następnie możesz skompilować AST do obiektu kodu i ocenić go (lub po prostu zadzwonić pod numer eval). Następnie, jeśli wartość jest falsy, sprawdź AST, aby zobaczyć, jak konstruowane jest wyrażenie. Wydrukuj wartość każdego elementu w wyrażeniu, do którego można uzyskać dostęp poprzez nazwę według AST.

+2

Czy nie potrzebujesz dostępu do zakresu dzwoniącego? (możliwe do wykonania przy pomocy "inspect" lub czegoś, ale niezbyt ładnie lub niezawodnie). – delnan

+0

Cool. Zakładam, że nie będzie on przenośny między kompilatorami a wersjami kompilatora? Być może istnieje moduł (specyficzny dla kompilatora), który pozwala na iterację przez AST? – max

+0

@delnan: Tak, prawdopodobnie będziesz musiał przekazać 'globals()' i 'locals()' do funkcji 'validate()'. –

2

Co z twierdzeniami?

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

Edit

dodać% R wydrukować repr użytkownika - dzięki za komentarz.

+2

Chcesz '% r', ale poza tym: tak. – delnan

+0

Tak, masz rację. Właśnie edytowane z tą modyfikacją. Dzięki za podpowiedź. –

+0

@msavadores: Och, to by działało - ale miałem na myśli to, że wyrażenia byłyby za każdym razem inne. Może to być 'f (x, y)! = Z', następnym razem może to być' x max

1

Nie może być sposób włamać się coś zrobić, o co prosisz, ale alternatywy są przynajmniej znacznie łatwiejsze:

  1. Jak masz teraz, ręcznie rozszerzenie wartości odsetek. Prawdopodobnie będzie to bardziej wiarygodne, szczególnie w przypadku złożonych wyrażeń, ponieważ łatwiej jest Ci powiedzieć, co jest interesujące, niż komputer, aby to wydedukować. W szczególności możesz chcieć uwzględnić wartość, której nie ma nawet w wyrażeniu.

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

    Tutaj wydaje się chcieć x i y, ale też nie (wynik wywołania) f (x, y), podczas gdy w innych przypadkach, które mogą być odwrócone, lub może chcesz wszystkie trzy. Algorytm będzie miał trudności z określeniem twoich preferencji.

  2. Możesz uruchomić pdb w punkcie, w którym wykryjesz awarię, zbadać, co chcesz (nawet w górę stos wywołań), a następnie wznowić "normalne" wykonanie. Jest to możliwe tylko w niektórych kontekstach; na przykład prawdopodobnie nie dla aplikacji internetowej.

    if f(x, y) != z: 
        import pdb 
        pdb.set_trace() 
        raise MyException("Unexpected: ...") 
    
  3. Moduł rejestrowania zamiast instrukcji print lub w komunikacie wyjątku.

1

nose testy biegacz ma plug-in o nazwie "szczegółowo awaryjności", który zapewnia dokładnie tę usługę: http://somethingaboutorange.com/mrl/projects/nose/0.11.1/plugins/failuredetail.html. To rozwiązanie jest lepsze niż to, o co prosiłeś, ponieważ wyrażenie nie jest ciągiem znaków, jest faktycznym stwierdzeniem, które jest później analizowane, aby znaleźć kod źródłowy do analizy.

Przykładem jest w older docs:

Innymi słowy, jeśli masz test odczuwalna:

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

Dostaniesz wyjście jak:

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

można to zrobić na odwrót:

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

if eval(expr): 
    raise MyException(expr) 

lub innymi słowy:

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

Dość brudny choć :)

+0

% r zamiast% s, i zawiedzie z powodu błędu składni dla wielu typów. Bardzo brudny; Niepolecane. –

+0

Naprawiono. Rzeczywiście nie użyłbym tego w moim własnym kodzie ... –

Powiązane problemy