Debugging in Python: Show last N executed lines - python

I would love to see the last 10 lines which were executed by the python interpreter before this exception occured:
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
I want to see where check_perm() returned True.
I know that I could use interactive debugging to find the matching line, but I am lazy and want to find a easier way to the line where check_perm() returned the return value.
I use pyCharm, but a text based tool, would solve my need, too.
BTW: Please don't tell me how to use the debugger with step-over and step-into. I know this.
Here is some code to illustrate it.
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)
...
There are several ways where check_perm() could return True. If True was returned because of condition_1, then I want to see something like this
+ if condition_1:
+ return True
The output I have in mind is like set -x on the shell.
Update
cgitb, pytest and other tools can show the lines before the line where the assertion failed. BUT, they only show the lines of the current python file. This question is about the lines which were executed before the assertion happens, but covering all files. In my case I want to know where the return value of check_perm() was created. The tools pytest, cgitb, ... don't show this.
What I am searching is like set -x on the shell:
help set
-x Print commands and their arguments as they are executed.

For this reason I've switched testing to pytest.
It can show local variables and traceback with different detalization level. Line where call was done is marked with >.
For example in my django project:
$ 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 $

What about cgitb? You just need import this module to your code.
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()
Gives:
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

Since I could not find a solution, I wrote this myself:
with trace_function_calls():
self.assertFalse(check_perm(request, some_object))
Implementation of 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: This is open source software. If you want to create a python package, do it, tell me, it would make me glad.

The trace module has bourne compatible shell set -x like feature. The trace parameter of trace.Trace class enables line execution tracing. This class takes an ignoredirs parameter which is used to ignore tracing modules or packages located below the specified directory. I use it here to keep the tracer from tracing the unittest module.
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)
Test;
$ 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)
To make the code and the output even more shorter, just trace the required function alone.
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()
Test;
$ 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)

Have you considered the following workflow? I read your BTW but hard rules sometimes stop us from solving our problems(especially if you are in an XY trap) so I'm going to suggest you use the debugger anyway. I run into tests that fail all the time. When a full stack trace is critical to solving the problem, I use a combination of pdb and py.test to get the whole shebang. Considering the following program...
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()
Running the command py.test -v -tb=short -m A code.py results in the following output...
art#macky ~/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 ===========================
One simple way to investigate the stack trace is to drop a pdb debug point in the test, Mark the individual test with a pytest mark, invoke that test, and inspect the stack inside the debugger. like so...
def add(a, b):
from pdb import set_trace;set_trace()
assert a>b
return a+b
Now when I run the same test command again I get a suspended pdb debugger. Like so...
art#macky ~/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)
If at this point I type the magic w for where and hit enter I see the full stack trace in all its glory...
(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)
I do a lot of work in frameworks. pdb + where gives you everything up to the actual entry point of the program. You can see buried in there my functions as well as the test runner's frames. If this were Django or Flask I would see all the stack frames involved with the internals of those frameworks. Its my full stop gap for when things go really wrong.
If you have a test with lots of iterations or conditionals you might find yourself getting hung up on the same lines again and again. The solution is to be clever about where you choose to instrument with pdb. Nesting it inside a conditional or instrumenting an iteration/recursion with a conditional(essentially saying When this becomes True then suspend so I can inspect what is going on). Furthermore pdb lets you look at all the runtime context(assignments, state, etc.)
For your case it looks like a creative instrumentation of check_perm is in order.

Related

Stuck on Python "KeyError: <exception str() failed>" in BFS code of a water jug scenario

Intended Function of code: Takes a user input for the volume of 3 jars(1-9) and output the volumes with one of the jars containing the target length. jars can be Emptied/Filled a jar, or poured from one jar to another until one is empty or full.
With the code I have, i'm stuck on a key exception error .
Target length is 4 for this case
Code:
`
class Graph:
class GraphNode:
def __init__(self, jar1 = 0, jar2 = 0, jar3 = 0, color = "white", pi = None):
self.jar1 = jar1
self.jar2 = jar2
self.jar3 = jar3
self.color = color
self.pi = pi
def __repr__(self):
return str(self)
def __init__(self, jl1 = 0, jl2 = 0, jl3 = 0, target = 0):
self.jl1 = jl1
self.jl2 = jl2
self.jl3 = jl3
self.target = target
self.V = {}
for x in range(jl1 + 1):
for y in range(jl2 + 1):
for z in range(jl3 + 1):
node = Graph.GraphNode(x, y, z, "white", None)
self.V[node] = None
def isFound(self, a: GraphNode) -> bool:
if self.target in [a.jar1, a.jar2, a.jar3]:
return True
return False
pass
def isAdjacent(self, a: GraphNode, b: GraphNode) -> bool:
if self.V[a]==b:
return True
return False
pass
def BFS(self) -> [] :
start = Graph.GraphNode(0, 0, 0, "white")
queue=[]
queue.append(start)
while len(queue)>0:
u=queue.pop(0)
for v in self.V:
if self.isAdjacent(u,v):
if v.color =="white":
v.color == "gray"
v.pi=u
if self.isFound(v):
output=[]
while v.pi is not None:
output.insert(0,v)
v=v.pi
return output
else:
queue.append(v)
u.color="black"
return []
#######################################################
j1 = input("Size of first jar: ")
j2 = input("Size of second jar: ")
j3 = input("Size of third jar: ")
t = input("Size of target: ")
jar1 = int(j1)
jar2 = int(j2)
jar3 = int(j3)
target = int(t)
graph1 = Graph(jar1, jar2, jar3, target)
output = graph1.BFS()
print(output)
`
**Error: **
line 37, in isAdjacent
if self.V[a]==b:
KeyError: <exception str() failed>
Strange but when I first ran this in the IPython interpreter I got a different exception:
... :35, in Graph.isAdjacent(self, a, b)
34 def isAdjacent(self, a: GraphNode, b: GraphNode) -> bool:
---> 35 if self.V[a]==b:
36 return True
37 return False
<class 'str'>: (<class 'RecursionError'>, RecursionError('maximum recursion depth exceeded while getting the str of an object'))
When I run it as a script or in the normal interpreter I do get the same one you had:
... line 35, in isAdjacent
if self.V[a]==b:
KeyError: <exception str() failed>
I'm not sure what this means so I ran the debugger and got this:
File "/Users/.../stackoverflow/bfs1.py", line 1, in <module>
class Graph:
File "/Users/.../stackoverflow/bfs1.py", line 47, in BFS
if self.isAdjacent(u,v):
File "/Users/.../stackoverflow/bfs1.py", line 35, in isAdjacent
if self.V[a]==b:
KeyError: <unprintable KeyError object>
Uncaught exception. Entering post mortem debugging
Running 'cont' or 'step' will restart the program
> /Users/.../stackoverflow/bfs1.py(35)isAdjacent()
-> if self.V[a]==b:
(Pdb) type(a)
<class '__main__.Graph.GraphNode'>
(Pdb) str(a)
*** RecursionError: maximum recursion depth exceeded while calling a Python object
So it does seem like a maximum recursion error. (The error message you originally got is not very helpful). But the words <unprintable KeyError object> are a clue. It looks like it was not able to display the KeyError exception...
The culprit is this line in your class definition:
def __repr__(self):
return str(self)
What were you trying to do here?
The __repr__ function is called when the class is asked to produce a string representation of itself. But yours calls the string function on the instance of the class so it will call itself! So I think you actually generated a second exception while the debugger was trying to display the first!!!.
I replaced these lines with
def __repr__(self):
return f"GraphNode({self.jar1}, {self.jar2}, {self.jar3}, {self.color}, {self.pi})"
and I don't get the exception now:
Size of first jar: 1
Size of second jar: 3
Size of third jar: 6
Size of target: 4
Traceback (most recent call last):
File "/Users/.../stackoverflow/bfs1.py", line 77, in <module>
output = graph1.BFS()
File "/Users/.../stackoverflow/bfs1.py", line 45, in BFS
if self.isAdjacent(u,v):
File "/Users/.../stackoverflow/bfs1.py", line 33, in isAdjacent
if self.V[a]==b:
KeyError: GraphNode(0, 0, 0, white, None)
This exception is easier to interpret. Now it's over to you to figure out why this GraphNode was not found in the keys of self.V!

Get name of attributes

I'd like to get the name of any attribute while iterating over it.
the ts3defines.py looks like this:
class VirtualServerProperties(object):
VIRTUALSERVER_UNIQUE_IDENTIFIER = 0
VIRTUALSERVER_NAME = 1
VIRTUALSERVER_WELCOMEMESSAGE = 2
VIRTUALSERVER_PLATFORM = 3
VIRTUALSERVER_VERSION = 4
VIRTUALSERVER_MAXCLIENTS = 5
VIRTUALSERVER_PASSWORD = 6
VIRTUALSERVER_CLIENTS_ONLINE = 7
VIRTUALSERVER_CHANNELS_ONLINE = 8
VIRTUALSERVER_CREATED = 9
VIRTUALSERVER_UPTIME = 10
VIRTUALSERVER_CODEC_ENCRYPTION_MODE = 11
VIRTUALSERVER_ENDMARKER = 12
the getItems(object) function looks like this:
def getItems(object):
return [getattr(object, a) for a in dir(object)
if not a.startswith('__') and not callable(getattr(object, a))]
the code in question looks like this:
for var in getItems(ts3defines.VirtualServerProperties):
(err, var) = ts3.getServerVariable(schid, var)
if err == ts3defines.ERROR_ok and var != "" and var != 0:
i.append('{0}: {1}'.format(var.__name__, var))
My question is about the var.__name__ shouldn't that return the string VIRTUALSERVER_BLA, etc?
Why does it cause?
11/25/2017 16:07:44 pyTSon.PluginHost.infoData Error Error calling infoData of python plugin Extended Info: Traceback (most recent call last):
File "C:/Users/blusc/AppData/Roaming/TS3Client/plugins/pyTSon/scripts\pluginhost.py", line 476, in infoData
data = p.infoData(schid, aid, atype)
File "C:/Users/blusc/AppData/Roaming/TS3Client/plugins/pyTSon/scripts\info\__init__.py", line 160, in infoData
return self.getServerInfo(schid)
File "C:/Users/blusc/AppData/Roaming/TS3Client/plugins/pyTSon/scripts\info\__init__.py", line 148, in getServerInfo
i.append('{0}: {1}'.format(var.__name__, var))
AttributeError: 'int' object has no attribute '__name__'
I don't understand why you're trying to access __name__. You already have the name within the getItems method; it's a. You should return that and use it in the loop.
def getItems(object):
return [(a, getattr(object, a)) for a in dir(object)
if not a.startswith('__') and not callable(getattr(object, a))]
...
for name, var in getItems(ts3defines.VirtualServerProperties):
(err, var) = ts3.getServerVariable(schid, var)
if err == ts3defines.ERROR_ok and var != "" and var != 0:
i.append('{0}: {1}'.format(name, var))

Python 2.7 yield is creating strange behavior

Using Python 2.7....
The print thread["Subject"] should return:
please ignore - test2
and
Please ignore - test
It does successfully with this:
def thread_generator(threads):
tz = pytz.timezone('America/Chicago')
POSTS_SINCE_HOURS = 24
now_date = datetime.datetime.now(tz)
time_stamp = now_date - datetime.timedelta(hours=POSTS_SINCE_HOURS)
for thread in threads:
last_post_time = convert_time(thread["LatestPostDate"])
if last_post_time > time_stamp:
print thread["Subject"]
row = {
"url": thread["Url"],
"author_username": thread["Author"]["Username"],
"author_name": thread["Author"]["DisplayName"],
"thread_id": thread["Id"],
"forum_id": thread["ForumId"],
"subject": thread["Subject"],
"created_date": thread["Content"]["CreatedDate"],
"reply_count": thread["ReplyCount"],
"latest_post_date": thread["LatestPostDate"],
"latest_reply_author": thread["LatestForumReplyAuthorId"] }
But, when adding the yield row the print thread["Subject"] does not show Please ignore - test as it should.
def thread_generator(threads):
tz = pytz.timezone('America/Chicago')
POSTS_SINCE_HOURS = 24
now_date = datetime.datetime.now(tz)
time_stamp = now_date - datetime.timedelta(hours=POSTS_SINCE_HOURS)
for thread in threads:
last_post_time = convert_time(thread["LatestPostDate"])
if last_post_time > time_stamp:
print thread["Subject"]
row = {
"url": thread["Url"],
"author_username": thread["Author"]["Username"],
"author_name": thread["Author"]["DisplayName"],
"thread_id": thread["Id"],
"forum_id": thread["ForumId"],
"subject": thread["Subject"],
"created_date": thread["Content"]["CreatedDate"],
"reply_count": thread["ReplyCount"],
"latest_post_date": thread["LatestPostDate"],
"latest_reply_author": thread["LatestForumReplyAuthorId"] }
yield row
Why is this? Please ignore - test should still show with print thread["Subject"]. Makes no sense to me.
UPDATE: How the generators is called
def sanitize_threads(threads):
for thread in thread_generator(threads):
do stuff
thread_batch.append(thread)
return thread_batch
def get_unanswered_threads():
slug = 'forums/threads/unanswered.json?PageSize=100'
status_code, threads = do_request(slug)
if status_code == 200:
threads = threads["Threads"]
thread_batch = sanitize_threads(threads)
database_update(thread_batch)
Have you tried actually calling next() on the resultant generator? If you call the function with yield the same way you call the function without, in the yield case you'll get a generator object as a result. A generator doesn't evaluate what's inside it until you actually require a value of it, which can be done with next(generator).
For example:
>>> def nogen():
... '''Create a list of values 0-3 and print each one as you build the list.'''
... r = []
... for i in range(3):
... print(i)
... r.append(i)
... return r
...
>>> def firstgen():
... '''Create an iterator of values 0-3 and print each one as you yield the value.'''
... for i in range(3):
... print(i)
... yield i
...
>>> a = nogen() # This function is eager and does everything at once.
0
1
2
>>> a
[0, 1, 2]
>>> b = firstgen() # Note there's no output immediately after this line.
>>> list(b) # The output happens as the list coercion causes the generator to execute.
0
1
2
[0, 1, 2]
>>> c = firstgen() # You can also see this a step at a time using `next`
>>> next(c)
0
0
>>> next(c)
1
1
>>> next(c)
2
2
>>> next(c)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

Weird bug in python project

I'm working on a big project in Python, and I've run into a bizarre error I can't explain. In one of my classes, I have a private method being called during instantiation:
def _convertIndex(self, dimInd, dimName):
'''Private function that converts numbers to numbers and non-integers using
the subclass\'s convertIndex.'''
print dimInd, ' is dimInd'
try:
return int(dimName)
except:
if dimName == '*':
return 0
else:
print self.param.sets, ' is self.param.sets'
print type(self.param.sets), ' is the type of self.param.sets'
print self.param.sets[dimInd], ' is the param at dimind'
return self.param.sets[dimInd].value(dimName)
What it's printing out:
0 is dimInd
[<coremcs.SymbolicSet.SymbolicSet object at 0x10618ad90>] is self.param.sets
<type 'list'> is the type of self.param.sets
<SymbolicSet BAZ=['baz1', 'baz2', 'baz3', 'baz4']> is the param at dimind
======================================================================
ERROR: testParameterSet (GtapTest.TestGtapParameter)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/myuser/Documents/workspace/ilucmc/gtapmcs/test/GtapTest.py", line 116, in testParameterSet
pset = ParameterSet(prmFile, dstFile, GtapParameter)
File "/Users/myuser/Documents/workspace/ilucmc/coremcs/ParameterSet.py", line 103, in __init__
self.distroDict, corrDefs = AbsBaseDistro.readFile(distFile, self.paramDict)
File "/Users/myuser/Documents/workspace/ilucmc/coremcs/Distro.py", line 359, in readFile
distro = cls.parseDistro(param, target, distroType, args)
File "/Users/myuser/Documents/workspace/ilucmc/coremcs/Distro.py", line 301, in parseDistro
return cls(param, target, distro, dim_names, argDict)
File "/Users/myuser/Documents/workspace/ilucmc/coremcs/Distro.py", line 150, in __init__
self.dim_indxs = list(starmap(self._convertIndex, enumerate(dim_names))) # convert to numeric values and save in dim_indxs
File "/Users/myuser/Documents/workspace/ilucmc/coremcs/Distro.py", line 194, in _convertIndex
print self.param.sets[dimInd], ' is the param at dimind'
IndexError: list index out of range
Obviously this isn't the code for the whole class, but it represents something that I don't understand. The error is coming when I index into self.param.sets. Apparently, dimInd is out of range. the problem is, dimInd is 0, and self.param.sets is a list of length 1 (as shown from the print statements), so why can't I index into it?
EDIT: For what it's worth, the __init__ method looks like this:
'''
Stores a definitions of a distribution to be applied to a header variable.
See the file setup/gtap/DistroDoc.txt for the details.
'''
def __init__(self, param, target, distType, dim_names, argDict):
self.name = param.name
self.dim_names = dim_names
self.dim_indxs = []
self.target = target.lower() if target else None
self.distType = distType.lower() if distType else None
self.rv = None
self.argDict = {}
self.modifier = defaultdict(lambda: None)
self.param = param
# Separate args into modifiers and distribution arguments
for k, v in argDict.iteritems():
if k[0] == '_': # modifiers start with underscore
self.modifier[k] = v
else:
self.argDict[k] = v # distribution arguments do not have underscore
if self.target == 'index':
print dim_names
self.dim_indxs = list(starmap(self._convertIndex, enumerate(dim_names))) # convert to numeric values and save in dim_indxs
if distType == 'discrete':
entries = self.modifier['_entries']
if not entries:
raise DistributionSpecError("Not enough arguments given to discrete distribution.")
modDict = {k[1:]: float(v) for k, v in self.modifier.iteritems() if k[1:] in getOptionalArgs(DiscreteDist.__init__)}
self.rv = DiscreteDist(entries, **modDict)
return
sig = DistroGen.signature(distType, self.argDict.keys())
gen = DistroGen.generator(sig)
if gen is None:
raise DistributionSpecError("Unknown distribution signature %s" % str(sig))
self.rv = gen.makeRV(self.argDict) # generate a frozen RV with the specified arguments
self.isFactor = gen.isFactor

Recursive directory list/analyze function doesn't seem to recurse right

I wrote what I thought was a straightforward Python script to traverse a given directory and tabulate all the file suffixes it finds. The output looks like this:
OTUS-ASIO:face fish$ sufs
>>> /Users/fish/Dropbox/ost2/face (total 194)
=== 1 1 -
=== css 16 -----
=== gif 14 -----
=== html 12 ----
=== icc 87 --------------------------
=== jpg 3 -
=== js 46 --------------
=== png 3 -
=== zip 2 -
... which would be great, if those values were correct. They are not. Here's what happens when I run it in a subdirectory of the directory I listed above:
OTUS-ASIO:face fish$ cd images/
OTUS-ASIO:images fish$ sufs
>>> /Users/fish/Dropbox/ost2/face/images (total 1016)
=== JPG 3 -
=== gif 17 -
=== ico 1 -
=== jpeg 1 -
=== jpg 901 --------------------------
=== png 87 ---
... It only seems to go one directory level down. Running the script one level up didn't pick up on the 'jpeg' suffix at all, and seemed to miss a good 898 jpg files.
The script in question is here:
#!/usr/bin/env python
# encoding: utf-8
"""
getfilesuffixes.py
Created by FI$H 2000 on 2010-10-15.
Copyright (c) 2010 OST, LLC. All rights reserved.
"""
import sys, os, getopt
help_message = '''
Prints a list of all the file suffixes found in each DIR, with counts.
Defaults to the current directory wth no args.
$ %s DIR [DIR DIR etc ...]
''' % os.path.basename(__file__)
dirs = dict()
skips = ('DS_Store','hgignore')
class Usage(Exception):
def __init__(self, msg):
self.msg = msg
def getmesomesuffixes(rootdir, thisdir=None):
if not thisdir:
thisdir = rootdir
for thing in [os.path.abspath(h) for h in os.listdir(thisdir)]:
if os.path.isdir(thing):
getmesomesuffixes(rootdir), thing)
else:
if thing.rfind('.') > -1:
suf = thing.rsplit('.').pop()
dirs[rootdir][suf] = dirs[rootdir].get(suf, 0) + 1
return
def main(argv=None):
if argv is None:
argv = sys.argv
try:
try:
opts, args = getopt.getopt(argv[1:], "h", ["help",])
except getopt.error, msg:
raise Usage(msg)
for option, value in opts:
if option == "-v":
verbose = True
if option in ("-h", "--help"):
raise Usage(help_message)
if len(args) == 0:
args.append(os.getcwd())
for durr in [os.path.abspath(arg) for arg in args]:
if os.path.isdir(durr):
dirs[durr] = dict()
for k, v in dirs.items():
getmesomesuffixes(k)
print ""
for k, v in dirs.items():
sufs = v.items()
sufs.sort()
maxcount = reduce(lambda fs, ns: fs > ns and fs or ns, map(lambda t: t[1], sufs), 1)
mincount = reduce(lambda fs, ns: fs < ns and fs or ns, map(lambda t: t[1], sufs), 1)
total = reduce(lambda fs, ns: fs + ns, map(lambda t: t[1], sufs), 0)
print ">>>\t\t\t%s (total %s)" % (k, total)
for suf, sufcount in sufs:
try:
skips.index(suf)
except ValueError:
print "===\t\t\t%12s\t %3s\t %s" % (suf, sufcount, "-" * (int(float(float(sufcount) / float(maxcount)) * 25) + 1))
print ""
except Usage, err:
print >> sys.stderr, sys.argv[0].split("/")[-1] + ": " + str(err.msg)
print >> sys.stderr, "\t for help use --help"
return 2
if __name__ == "__main__":
sys.exit(main())
It seems that getmesomesuffixes() is subtly not doing what I want it to. I hate to ask such an annoying question, but if anyone can spot whatever amateur-hour error I am making with a quick once-over, it would save me some serious frustration.
Yeah, Won't you be better off if you used os.walk
for root, dirs, files in os.walk(basedir):
... do you stuff ..
See the example at
http://docs.python.org/library/os.html
Also look at os.path.splitext(path), a finer way to find the type of your file.
>>> os.path.splitext('/d/c/as.jpeg')
('/d/c/as', '.jpeg')
>>>
Both of these together should simplify your code.
import os
import os.path
from collections import defaultdict
def foo(dir='.'):
d = defaultdict(int)
for _, _, files in os.walk(dir):
for f in files:
d[os.path.splitext(f)[1]] += 1
return d
if __name__ == '__main__':
d = foo()
for k, v in sorted(d.items()):
print k, v

Categories

Resources