2011-01-23 25 views
5

tôi thấy mình viết khẳng định như thế này:python: tự động in đại diện của mỗi thành phần trong một biểu thức

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

Tôi đã tự hỏi nếu có một cách để viết một hàm có thể chấp nhận một biểu Python hợp lệ và một ngoại lệ lớp như một đầu vào, đánh giá biểu thức và nếu nó tìm thấy nó là sai, in ra biểu diễn của từng thuật ngữ mức thấp nhất trong biểu thức và tăng ngoại lệ đã cho?

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

Tôi sẽ lật thông điệp ngoại lệ: thay vì nói những gì bạn muốn, chỉ định những gì bạn phát hiện d là sai. Điều này cho cùng một biểu thức (! =) Mà bạn đã thử nghiệm thay vì nghịch đảo của nó (==). –

Trả lời

2

Dưới đây là một thực hiện:

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. Đây có phải là điều bạn khuyên bạn nên sử dụng hay bạn vẫn đề xuất sử dụng các phương pháp tiếp cận truyền thống hơn? – max

1

Điều này có thể thực hiện được. Bạn có thể sử dụng trình biên dịch Python (chi tiết khác nhau giữa các phiên bản, vì vậy đây chỉ là tổng quan) để biên dịch biểu thức đã cho thành một AST. Sau đó, bạn có thể biên dịch AST thành một đối tượng mã và đánh giá nó (hoặc chỉ cần gọi eval ở nơi đầu tiên, bất cứ điều gì). Sau đó, nếu giá trị là giả, hãy kiểm tra AST để xem cách biểu thức được xây dựng. In ra giá trị của mỗi mục trong biểu thức được truy cập theo tên theo AST.

+2

Bạn sẽ không cần quyền truy cập vào phạm vi của người gọi? (có thể có khả năng với 'kiểm tra' hoặc một cái gì đó, nhưng không phải là rất tốt đẹp hoặc đáng tin cậy). – delnan

+0

Tuyệt. Tôi giả sử nó sẽ không được di động giữa các trình biên dịch và các phiên bản trình biên dịch mặc dù? Có lẽ có một mô-đun (trình biên dịch cụ thể) cho phép lặp qua AST? – max

+0

@delnan: Có, có thể bạn sẽ cần phải chuyển 'globals()' và 'locals()' thành hàm 'validate()'. –

2

Điều gì về xác nhận?

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

Sửa

thêm% r in repr của - cảm ơn cho nhận xét.

+2

Bạn muốn '% r', nhưng ngược lại: có. – delnan

+0

Vâng, bạn nói đúng. Chỉ cần chỉnh sửa với sửa đổi đó. Cảm ơn gợi ý. –

+0

@msavadores: Ồ nó sẽ hoạt động - nhưng ý tôi là các biểu thức sẽ khác nhau mỗi lần. Nó có thể là 'f (x, y)! = Z', lần sau nó có thể là' x max

1

Có thể có một cách để hack lên một cái gì đó để làm những gì bạn yêu cầu, nhưng các lựa chọn thay thế là, ít nhất, dễ dàng hơn nhiều:

  1. Như bạn có bây giờ, tay mở rộng các giá trị của sự quan tâm. Điều này có khả năng đáng tin cậy hơn, đặc biệt là cho các biểu thức phức tạp, bởi vì nó dễ dàng hơn cho bạn để nói những gì thú vị hơn cho máy tính để suy luận nó. Cụ thể, bạn có thể muốn bao gồm giá trị không có trong biểu thức.

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

    Ở đây, bạn dường như muốn x và y nhưng không (kết quả của việc gọi) f (x, y), trong khi ở các trường hợp khác có thể được lộn, hoặc bạn có thể muốn cả ba. Một thuật toán sẽ có một thời gian khó xác định sở thích của bạn.

  2. Bạn có thể bắt đầu pdb tại điểm mà bạn phát hiện lỗi, kiểm tra bất cứ điều gì bạn thích (thậm chí lên ngăn xếp cuộc gọi), và sau đó tiếp tục thực hiện "bình thường". Điều này chỉ có thể trong một số ngữ cảnh; ví dụ. có thể không dành cho ứng dụng web.

    if f(x, y) != z: 
        import pdb 
        pdb.set_trace() 
        raise MyException("Unexpected: ...") 
    
  3. Mô-đun ghi nhật ký thay vì báo cáo in hoặc trong thông báo ngoại lệ.

1

Á hậu thử nghiệm nose có một trình cắm gọi là "chi tiết lỗi" cung cấp chính xác dịch vụ này: http://somethingaboutorange.com/mrl/projects/nose/0.11.1/plugins/failuredetail.html. Giải pháp này là tốt hơn so với những gì bạn đã yêu cầu, bởi vì biểu thức không phải là một chuỗi, đó là một xác nhận thực tế mà là introspected sau này để tìm mã nguồn để phân tích.

Một ví dụ là trong older docs:

Nói cách khác, nếu bạn có một bài kiểm tra như:

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

Bạn sẽ nhận được kết quả như sau:

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

Bạn có thể làm điều đó theo chiều ngược lại:

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

if eval(expr): 
    raise MyException(expr) 

Hoặc nói cách khác:

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

Khá bẩn mặc dù :)

+0

% r thay vì% s và sẽ không thành công với lỗi cú pháp cho nhiều loại. Rất bẩn; không được khuyến khích. –

+0

Đã sửa lỗi. Thật vậy, tôi sẽ không sử dụng nó trong mã của riêng tôi ... –

Các vấn đề liên quan