2016-12-19 12 views
14

我很想看到這是由Python解釋該異常發生之前執行的最後10行:調試Python中:展會最後N執行的行

test_has_perm_in_foobar.py F 
Traceback (most recent call last): 
    File "/.../test_has_perm_in_foobar.py", line 50, in test_has_perm 
    self.assertFalse(check_perm(request, some_object)) 
    File "/usr/lib/python2.7/unittest/case.py", line 416, in assertFalse 
    raise self.failureException(msg) 
AssertionError: True is not false 

我想看到check_perm()返回True

我知道,我可以使用交互式調試找到匹配的行,但我懶,想要找的地方check_perm()返回的返回值就行了更簡單的方法。

我使用pyCharm,但是基於文本的工具也能解決我的需求。

順便說一句:請不要告訴我如何使用調試器的跨越和步入。我知道這個。

下面是一些代碼來說明它。

def check_perm(request, some_object): 
    if condition_1: 
     return True 
    if condition_2: 
     return sub_check(some_object) 
    if condition_3: 
     return sub_check2(some_object) 
    ... 

有幾種方法,其中check_perm()可以返回True。如果True是因爲condition_1的回來,然後我希望看到這樣的事情

+   if condition_1: 
+   return True 

輸出我心目中就像set -x外殼上。

更新

cgitb,pytest和其他工具可以顯示在斷言失敗前行線。但是,它們只顯示當前python文件的行。這個問題是關於斷言發生前執行的行,但涉及所有文件。在我的情況下,我想知道check_perm()的返回值是在哪裏創建的。工具pytest,cgitb,...不顯示這個。

我正在尋找像set -x外殼上:

幫助設置

-x打印命令及其參數,因爲他們被執行。

回答

1

因爲我無法找到一個解決方案,我寫了這個自己:

with trace_function_calls():  
    self.assertFalse(check_perm(request, some_object)) 

實施trace_function_calls()的:

class trace_function_calls(object): 
    depth_symbol = '+' 

    def __init__(self, write_method=None, log_lines=True): 
     ''' 
     write_method: A method which gets called for every executed line. Defauls to logger.info 

     # Simple example: 

     with debugutils.trace_function_calls(): 
      method_you_want_to_trace() 
     ''' 
     if write_method is None: 
      write_method=logger.info 
     self.write_method = write_method 
     self.log_lines = log_lines 

    def __enter__(self): 
     self.old = sys.gettrace() 
     self.depth = 0 
     sys.settrace(self.trace_callback) 

    def __exit__(self, type, value, traceback): 
     sys.settrace(self.old) 

    def trace_callback(self, frame, event, arg): 
     # from http://pymotw.com/2/sys/tracing.html#tracing-function-calls 
     if event == 'return': 
      self.depth -= 1 
      return self.trace_callback 

     if event == 'line': 
      if not self.log_lines: 
       return self.trace_callback 
     elif event == 'call': 
      self.depth += 1 
     else: 
      # self.write_method('unknown: %s' % event) 
      return self.trace_callback 

     msg = [] 
     msg.append(self.depth_symbol * self.depth) 

     co = frame.f_code 
     func_name = co.co_name 
     func_line_no = frame.f_lineno 

     func_filename = co.co_filename 
     if not is_python_file_from_my_codebase(func_filename): 
      return self.trace_callback 
     code_line = linecache.getline(func_filename, func_line_no).rstrip() 
     msg.append('%s: %s %r on line %s of %s' % (
      event, func_name, code_line, func_line_no, func_filename)) 
     self.write_method(' '.join(msg)) 
     return self.trace_callback 

PS :這是開源軟件。如果你想創建一個Python包,那麼告訴我,這會讓我很高興。

1

cgitb怎麼樣?你只需要將這個模塊導入你的代碼。

import cgitb 
cgitb.enable(format='text') 

def f(): 
    a = 1 
    b = 2 
    c = 3 
    x = 0 
    d = a * b * c/x 
    return d 

if __name__ == "__main__": 
    f() 

給出:

ZeroDivisionError 
Python 3.5.2: /usr/bin/python3 
Mon Dec 19 17:42:34 2016 

A problem occurred in a Python script. Here is the sequence of 
function calls leading up to the error, in the order they occurred. 
/home/user1/123.py in <module>() 
    10  d = a * b * c/x 
    11  return x 
    12 
    13 if __name__ == "__main__": 
    14  f() 
f = <function f> 
/home/user1/123.py in f() 
    8  c = 3 
    9  x = 0 
    10  d = a * b * c/x 
    11  return x 
    12 
d undefined 
a = 1 
b = 2 
c = 3 
x = 0 
ZeroDivisionError: division by zero 
... 
The above is a description of an error in a Python program. Here is 
the original traceback: 

Traceback (most recent call last): 
    File "123.py", line 14, in <module> 
    f() 
    File "123.py", line 10, in f 
    d = a * b * c/x 
ZeroDivisionError: division by zero 
+0

是的,我喜歡cgitb(和django調試頁面)的輸出。我想這些不會給我我正在尋找的信息。在我的情況下,我沒有得到例外。該方法返回一個值。但實際價值從哪裏來?我更新了這個問題來說明我想要的東西。儘管如此,感謝您的反饋。 – guettli

3

爲此,我已將測試切換到pytest

它可以顯示局部變量和跟蹤不同的受損級別。撥打電話的線路標有>

舉例來說,在我的Django項目:

$ py.test --showlocals --tb=long 
=============================== test session starts =============================== 
platform darwin -- Python 3.5.1, pytest-3.0.3, py-1.4.31, pluggy-0.4.0 
Django settings: dj_tg_bot.settings (from ini file) 
rootdir: /Users/el/Projects/dj-tg-alpha-bot, inifile: tox.ini 
plugins: django-3.0.0, cov-2.4.0 
collected 8 items 

tests/test_commands.py ....F 
tests/test_logger.py . 
tests/test_simple.py .. 

==================================== FAILURES ===================================== 
__________________________ TestSimpleCommands.test_start __________________________ 

self = <tests.test_commands.TestSimpleCommands testMethod=test_start> 

    def test_start(self,): 
     """ 
      Test bot accept normally command /start and replies as it should. 
      """ 
>  self._test_message_ok(self.start) 

self  = <tests.test_commands.TestSimpleCommands testMethod=test_start> 

tests/test_commands.py:56: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.test_commands.TestSimpleCommands testMethod=test_start> 
action = {'in': ' /start', 'out': {'parse_mode': 'Markdown', 'reply_markup': '', 'text': 'Welcome'}} 
update = <telegram.update.Update object at 0x113e16cf8>, number = 1 

    def _test_message_ok(self, action, update=None, number=1): 
     if not update: 
      update = self.update 
     with mock.patch("telegram.bot.Bot.sendMessage", callable=mock.MagicMock()) as mock_send: 
      if 'in' in action: 
       update.message.text = action['in'] 
      response = self.client.post(self.webhook_url, update.to_json(), **self.kwargs) 
      # Check response 200 OK 
      self.assertEqual(response.status_code, status.HTTP_200_OK) 
      # Check 
>   self.assertBotResponse(mock_send, action) 

action  = {'in': ' /start', 'out': {'parse_mode': 'Markdown', 'reply_markup': '', 'text': 'Welcome'}} 
mock_send = <MagicMock name='sendMessage' id='4619939344'> 
number  = 1 
response = <Response status_code=200, "application/json"> 
self  = <tests.test_commands.TestSimpleCommands testMethod=test_start> 
update  = <telegram.update.Update object at 0x113e16cf8> 

../../.pyenv/versions/3.5.1/lib/python3.5/site-packages/telegrambot/test/testcases.py:83: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.test_commands.TestSimpleCommands testMethod=test_start> 
mock_send = <MagicMock name='sendMessage' id='4619939344'> 
command = {'in': ' /start', 'out': {'parse_mode': 'Markdown', 'reply_markup': '', 'text': 'Welcome'}} 

    def assertBotResponse(self, mock_send, command): 
>  args, kwargs = mock_send.call_args 
E  TypeError: 'NoneType' object is not iterable 

command = {'in': ' /start', 'out': {'parse_mode': 'Markdown', 'reply_markup': '', 'text': 'Welcome'}} 
mock_send = <MagicMock name='sendMessage' id='4619939344'> 
self  = <tests.test_commands.TestSimpleCommands testMethod=test_start> 

../../.pyenv/versions/3.5.1/lib/python3.5/site-packages/telegrambot/test/testcases.py:61: TypeError 
------------------------------ Captured stderr call ------------------------------- 
Handler not found for {'message': {'from': {'username': 'username_4', 'last_name': 'last_name_4', 'id': 5, 'first_name': 'first_name_4'}, 'chat': {'username': 'username_4', 'last_name': 'last_name_4', 'first_name': 'first_name_4', 'title': 'title_4', 'type': 'private', 'id': 5}, 'text': ' /start', 'message_id': 5, 'date': 1482500826}, 'update_id': 5} 
======================= 1 failed, 7 passed in 2.29 seconds ======================== 
(.env) ✘-1 ~/Projects/dj-tg-alpha-bot [master|✚ 1…8⚑ 12] 
16:47 $ 
+0

我也使用pytest。是的,它顯示斷言失敗之前的行。但僅限於當前文件。如果有一個斷言檢查方法調用的返回值,那麼你看不到返回發生的地方。這就是我的問題所在。我知道這很難解釋。如果你知道如何更好地寫出我的問題,請告訴我。 – guettli

+0

我明白你的想法,收集以前來電的所有數據。但在我看來,它更多的是調試,而不是測試。如果用單位測試覆蓋他們以確保他們工作正常,會怎麼樣? –

1

trace模塊的Bourne兼容的shell set -x喜歡的功能。 trace.Trace類的trace參數啓用行跟蹤。該類採用參數,該參數用於忽略位於指定目錄下的跟蹤模塊或程序包。我在這裏使用它來讓跟蹤器跟蹤unittest模塊。

test_has_perm_in_foobar.py

import sys 
import trace 
import unittest 

from app import check_perm 

tracer = trace.Trace(trace=1, ignoredirs=(sys.prefix, sys.exec_prefix)) 

class Test(unittest.TestCase): 
    def test_one(self): 
     tracer.runctx('self.assertFalse(check_perm("dummy", 3))', globals(), locals()) 

if __name__ == '__main__': 
    unittest.main() 

app.py

def sub_check1(some_object): 
    if some_object * 10 == 20: 
     return True 

def sub_check2(some_object): 
    if some_object * 10 == 30: 
     return True 

def check_perm(request, some_object): 
    if some_object == 1: 
     return True 
    if some_object == 2: 
     return sub_check1(some_object) 
    if some_object == 3: 
     return sub_check2(some_object) 

測試;

$ python test_has_perm_in_foobar.py 
--- modulename: test_has_perm_in_foobar, funcname: <module> 
<string>(1): --- modulename: app, funcname: check_perm 
app.py(10):  if some_object == 1: 
app.py(12):  if some_object == 2: 
app.py(14):  if some_object == 3: 
app.py(15):   return sub_check2(some_object) 
--- modulename: app, funcname: sub_check2 
app.py(6):  if some_object * 10 == 30: 
app.py(7):   return True 
F 
====================================================================== 
FAIL: test_one (__main__.Test) 
---------------------------------------------------------------------- 
Traceback (most recent call last): 
    File "test_has_perm_in_foobar.py", line 23, in test_one 
    tracer.runctx('self.assertFalse(check_perm("dummy", 3))', globals(), locals()) 
    File "/usr/lib/python2.7/trace.py", line 513, in runctx 
    exec cmd in globals, locals 
    File "<string>", line 1, in <module> 
AssertionError: True is not false 

---------------------------------------------------------------------- 
Ran 1 test in 0.006s 

FAILED (failures=1) 

爲了使代碼和輸出更短,只需跟蹤所需的功能。

import trace 
import unittest 

from app import check_perm 

tracer = trace.Trace(trace=1) 

class Test(unittest.TestCase): 
    def test_one(self): 
     self.assertFalse(tracer.runfunc(check_perm, 'dummy', 3)) 

if __name__ == '__main__': 
    unittest.main() 

測試;

$ python test_has_perm_in_foobar.py 
--- modulename: app, funcname: check_perm 
app.py(10):  if some_object == 1: 
app.py(12):  if some_object == 2: 
app.py(14):  if some_object == 3: 
app.py(15):   return sub_check2(some_object) 
--- modulename: app, funcname: sub_check2 
app.py(6):  if some_object * 10 == 30: 
app.py(7):   return True 
F 
====================================================================== 
FAIL: test_one (__main__.Test) 
---------------------------------------------------------------------- 
Traceback (most recent call last): 
    File "test_has_perm_in_foobar.py", line 19, in test_one 
    self.assertFalse(tracer.runfunc(check_perm, 'dummy', 3)) 
AssertionError: True is not false 

---------------------------------------------------------------------- 
Ran 1 test in 0.005s 

FAILED (failures=1) 
0

您是否考慮過以下工作流程?我讀了你的BTW,但硬規則有時會阻止我們解決我們的問題(特別是如果你在XY陷阱中),所以我會建議你使用調試器。我遇到了一直失敗的測試。當完整的堆棧跟蹤對解決問題至關重要時,我使用pdbpy.test的組合來獲取整個shebang。考慮下面的程序...

import pytest 

@pytest.mark.A 
def test_add(): 

    a = 1 
    b = 2 

    add(a,b) 

def add(a, b): 
    assert a>b 

    return a+b 

def main(): 
    add(1,2) 
    add(2,1) 

if __name__ == "__main__": 
    # execute only if run as a script 
    main() 

運行命令py.test -v -tb=short -m A code.py結果如下輸出...

[email protected] ~/src/python/so-answer-stacktrace: py.test -v --tb=short -m A code.py 
============================= test session starts ============================== 
platform darwin -- Python 2.7.5 -- pytest-2.5.0 -- /Users/art/.pyenv/versions/2.7.5/bin/python 
collected 1 items 

code.py:3: test_add FAILED 

=================================== FAILURES =================================== 
___________________________________ test_add ___________________________________ 
code.py:9: in test_add 
>  add(a,b) 
code.py:12: in add 
>  assert a>b 
E  assert 1 > 2 
=========================== 1 failed in 0.01 seconds =========================== 

一種簡單的方法來研究堆棧跟蹤是砸在一個pdb調試點測試,Mark單獨測試一個pytest標記,調用該測試並檢查調試器中的堆棧。像這樣......

def add(a, b): 
    from pdb import set_trace;set_trace() 
    assert a>b 

    return a+b 

現在,當我再次運行相同的測試命令我得到一個暫停pdb調試器。是這樣的...

[email protected] ~/src/python/so-answer-stacktrace: py.test -v --tb=short -m A code.py 
=========================================================================================== test session starts ============================================================================================ 
platform darwin -- Python 2.7.5 -- pytest-2.5.0 -- /Users/art/.pyenv/versions/2.7.5/bin/python 
collected 1 items 

code.py:3: test_add 
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> PDB set_trace (IO-capturing turned off) >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 
> /Users/art/src/python/so-answer-stacktrace/code.py(13)add() 
-> assert a>b 
(Pdb) 

如果在這一點上,我鍵入魔wwhere,打enter我看到在其所有的榮耀全堆棧跟蹤...

(Pdb) w 
    /Users/art/.pyenv/versions/2.7.5/bin/py.test(9)<module>() 
-> load_entry_point('pytest==2.5.0', 'console_scripts', 'py.test')() 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/config.py(19)main() 
-> return config.hook.pytest_cmdline_main(config=config) 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/core.py(376)__call__() 
-> return self._docall(methods, kwargs) 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/core.py(387)_docall() 
-> res = mc.execute() 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/core.py(288)execute() 
-> res = method(**kwargs) 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/main.py(111)pytest_cmdline_main() 
-> return wrap_session(config, _main) 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/main.py(81)wrap_session() 
-> doit(config, session) 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/main.py(117)_main() 
-> config.hook.pytest_runtestloop(session=session) 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/core.py(376)__call__() 
-> return self._docall(methods, kwargs) 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/core.py(387)_docall() 
-> res = mc.execute() 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/core.py(288)execute() 
-> res = method(**kwargs) 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/main.py(137)pytest_runtestloop() 
-> item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem) 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/core.py(376)__call__() 
-> return self._docall(methods, kwargs) 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/core.py(387)_docall() 
-> res = mc.execute() 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/core.py(288)execute() 
-> res = method(**kwargs) 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/runner.py(62)pytest_runtest_protocol() 
-> runtestprotocol(item, nextitem=nextitem) 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/runner.py(72)runtestprotocol() 
-> reports.append(call_and_report(item, "call", log)) 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/runner.py(106)call_and_report() 
-> call = call_runtest_hook(item, when, **kwds) 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/runner.py(124)call_runtest_hook() 
-> return CallInfo(lambda: ihook(item=item, **kwds), when=when) 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/runner.py(137)__init__() 
-> self.result = func() 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/runner.py(124)<lambda>() 
-> return CallInfo(lambda: ihook(item=item, **kwds), when=when) 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/main.py(161)call_matching_hooks() 
-> return hookmethod.pcall(plugins, **kwargs) 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/core.py(380)pcall() 
-> return self._docall(methods, kwargs) 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/core.py(387)_docall() 
-> res = mc.execute() 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/core.py(288)execute() 
-> res = method(**kwargs) 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/runner.py(86)pytest_runtest_call() 
-> item.runtest() 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/python.py(1076)runtest() 
-> self.ihook.pytest_pyfunc_call(pyfuncitem=self) 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/main.py(161)call_matching_hooks() 
-> return hookmethod.pcall(plugins, **kwargs) 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/core.py(380)pcall() 
-> return self._docall(methods, kwargs) 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/core.py(387)_docall() 
-> res = mc.execute() 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/core.py(288)execute() 
-> res = method(**kwargs) 
    /Users/art/.pyenv/versions/2.7.5/lib/python2.7/site-packages/pytest-2.5.0-py2.7.egg/_pytest/python.py(188)pytest_pyfunc_call() 
-> testfunction(**testargs) 
    /Users/art/src/python/so-answer-stacktrace/code.py(9)test_add() 
-> add(a,b) 
> /Users/art/src/python/so-answer-stacktrace/code.py(13)add() 
-> assert a>b 
(Pdb) 

我做了很多在框架中的工作。 pdb + where爲您提供一切,直到程序的實際入口點。你可以看到我的功能以及測試跑者的框架。如果這是Django或Flask,我會看到與這些框架的內部相關的所有堆棧幀。當事情真的發生錯誤時,這是​​我的全面停滯。

如果您有很多迭代或條件的測試,您可能會發現自己一次又一次掛在同一行上。解決方法是在您選擇用pdb進行測試的地方靈活使用。將它嵌套在一個條件中,或者用條件進行迭代/遞歸(基本上是說當這變成True然後暫停,這樣我可以檢查上發生了什麼)。此外pdb讓你看看所有的運行環境(分配,狀態等)

對於你的情況,它看起來像check_perm的創意工具是爲了。

+0

是的,您的解決方案適用於您解釋的用例。在你的情況下,斷言在方法內部。如果斷言在方法調用之後,那麼堆棧跟蹤不會顯示錯誤值來自哪裏。你需要及時回去看看問題的根源。 AFAIK這在pdb中是不可能的。 – guettli

+0

對不起,我剛剛讀了這行'我知道我可以使用交互式調試來找到匹配的行,但我很懶,並且希望找到check_perm()返回返回值的行的更簡單的方法。「所以這是一個浪費時間。答案是什麼?我不知道編寫自己的編輯器/測試運行器組合嗎?提交一份請願書來改變物理定律爲更方便的事情?爲什麼問這種愚蠢的約束問題。 – nsfyn55

+0

這個問題得到14票。爲什麼我不應該問愚蠢的問題?也許有人有一個明智的答案。我怎麼知道沒有問?我很好奇:你怎麼看?順便說一句,有人告訴我,在C#中,你可以在調試器中向後邁進!也許這個問題不是愚蠢的,也許你從來不敢夢想調試器可以做後退步驟:-)這不是物理學,你甚至不需要DeepMind來解決這個問題。至少只讀跳回應該不難實現。 – guettli