-
Notifications
You must be signed in to change notification settings - Fork 9
Introduce first dependencies? #96
Comments
Possible simpler alternative: use Python's ast to figure out if the line pointed at is part of a multiline statement, and leave it at that. |
Hi! I'm currently working on https://github.com/alexmojaki/futurecoder to teach Python to beginners. I came here to look into using friendly-traceback to enhance the tracebacks shown to students. On a whim I opened the issues and then the first issue, and got a pleasant surprise! I'm glad you like my projects! I would be happy to help you integrate in executing or stack_data to add any features you're interested in. Or maybe I could extract any specific source code you need without adding a dependency, like whatever it is you want to do with multiline statements. Just playing around, here's a rough PoC of the kind of thing that could be done if you add the dependencies: import ast
import sys
import executing
import pure_eval
def explain_index_error():
ex = executing.Source.executing(sys.exc_info()[2])
if not (ex.node and isinstance(ex.node, ast.Subscript)):
return
evaluator = pure_eval.Evaluator.from_frame(ex.frame)
atok = ex.source.asttokens()
seq = evaluator[ex.node.value]
index = evaluator[ex.node.slice.value]
seq_source = atok.get_text(ex.node.value)
index_source = atok.get_text(ex.node.slice.value)
print(f"IndexError in expression {ex.text()}:")
print(f"{seq_source} has length {len(seq)} so the largest valid index is {len(seq) - 1}, "
f"you tried to access index {index_source} = {index}.")
a = [[[]], 3]
try:
print(a[a[1]] + 4)
except IndexError:
explain_index_error() Output:
On the flip side, would you be interested in contributing to futurecoder? This could be to integrate friendly-traceback, or anything else you can think of to ease learning for beginners. It looks like it aligns very well with your interests. |
@alexmojaki I might be interested in either integrating parts of your code, or contributing to it. Right now, I'm on a bit of a hiatus when it comes to coding as I work at a university and it is the beginning of an extremely busy term. I'm hoping that things will calm down in a few weeks and that I will be able to go back to programming. I'm a tiny bit hesitant in having any formal dependencies. One of the reasons is that beginners using Mu, that wish to use friendly-tracebacks would be surprised to see other packages appearing "magically" in their environment. (See https://aroberge.github.io/friendly-traceback-docs/docs/html/mu.html for a screenshot). Deleting a package in MU is done by deleting an entry in a "text window" ... which may leave things in an inconsistent state. So, I do like your idea of extracting some code from your project and incorporating it in Friendly-tracebacks. Let's keep this discussion open. When I resume coding, I'll try to focus on this issue first. |
@alexmojaki I have looked at futurecoder and given some more thoughts about the ideas discussed in this issue/thread.
If and when I use |
Cool! I've opened alexmojaki/futurecoder#75 to talk about integration into futurecoder.
I've mentioned this at the end of the issue. I think there's no need for it to be drop-in, I'd rather have some crude working code sooner than perfect code later. |
On Sat, Oct 3, 2020 at 7:01 PM Alex Hall ***@***.***> wrote:
Cool! I've opened alexmojaki/futurecoder#75
<alexmojaki/futurecoder#75> to talk about
integration into futurecoder.
However, I think it should wait until any information form executing could
be used by friendly-traceback, so that friendly-traceback could be used as
a "drop-in" replacement for whatever error analysis you would have in place
at the time.
I've mentioned this at the end of the issue. Personally there's no need
for it to be drop-in, I'd rather have some crude working code sooner than
perfect code later.
I understand ... and, from the point of view of the futurecoder project, I
think I would come to the same conclusion. However, I was thinking of where
I would/should put in my time and efforts first from the point of view of
advancing friendly-traceback.
… —
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#96 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAEZXQUSJ6GULP5YGC57O2TSI6NKFANCNFSM4QLVVCMA>
.
|
That's fine, I understand that 😄 |
I am going to add one of my own project (https://github.com/aroberge/token-utils) as a first dependency. My objection to not adding dependencies based on avoiding confusing beginners using the editor Mu is thus moot, and I will likely look more closely at what other dependencies from @alexmojaki projects (such as https://github.com/alexmojaki/pure_eval, https://github.com/alexmojaki/executing, https://github.com/alexmojaki/stack_data) to add. |
Awesome! In the meantime, I have overhauled tracebacks in futurecoder and introduced friendly-traceback. Take a look at alexmojaki/futurecoder#77. You can try it out by clicking View Deployment and logging in with admin@example.com:admin. |
Very nice! The amount of work you have done on this project is really impressive. |
@alexmojaki Could b = 2
c = "3"
d = 4
a = b + c + d In this case, knowing that |
If you run that exact script in the editor in futurecoder (you need to open a later chapter) you can see that futurecoder doesn't show a traceback in the shell because it's largely redundant, but this whole thing has made me realise that this decision means students can't see the highlighted executed node when they cause an exception in the shell. Thanks for bringing that to my attention. Anyway yes, |
Ok, while I had reverted back to having no dependencies, the ability to do this has convinced me that I really need to plan to use However, I have other issues to take core of before looking at using this. |
@alexmojaki I've decided to take the plunge and try to use executing/stack_data/pure_eval/asttokens. When you have the time, I would appreciate if you could give me pointers. For the example given above, namely: b = 2
c = "3"
d = 4
a = b + c + d how would I use these packages to obtain the two objects causing the problem as you identified them. When I tried to obtain the node using executing, I found records = inspect.getinnerframes(tb)
# Note, the above records have then been cleaned up to remove my own code which sandwiches the code from the user.
exception_frame, *rest = records[-1]
# etype, value, tb = sys.exc_info()
while True:
if tb.tb_frame == exception_frame:
break
tb = tb.tb_next
ex = executing.Source.executing(tb)
# To explore the available values, I did the following:
for key in dir(ex):
if key.startswith("__"):
continue
obj = getattr(ex, key)
if callable(obj):
print(key, obj.__call__())
else:
print(key, obj)
# Among others, idenfied the node as "None". Ideally, I'd like to extract all the relevant information about objects causing the exception ( |
I think it comes down to how you're getting the frame. Are you 100% sure it's the right frame? I don't know what I can do to help without a complete reproducible script to debug. Here's an example of it working: import executing
import sys
def foo():
bar()
def bar():
b = 2
c = "3"
d = 4
return b + c + d
try:
foo()
except:
etype, value, tb = sys.exc_info()
while tb:
ex = executing.Source.executing(tb)
print(ex.text(), ex.node)
tb = tb.tb_next In general I think it would be ideal if you dropped |
On Mon, Dec 7, 2020 at 2:29 PM Alex Hall ***@***.***> wrote:
I think it comes down to how you're getting the frame. Are you 100% sure
it's the right frame?
I don't know what I can do to help without a complete reproducible script
to debug. Here's an example of it working:
import executingimport sys
def foo():
bar()
def bar():
b = 2
c = "3"
d = 4
return b + c + d
try:
foo()except:
etype, value, tb = sys.exc_info()
while tb:
ex = executing.Source.executing(tb)
print(ex.text(), ex.node)
tb = tb.tb_next
In general I think it would be ideal if you dropped inspect.getinnerframes
entirely in favour of stack_data. But that's a big leap and I don't know if
that will solve your current problem.
Ah, the problem was that I was testing using my interactive interpreter.
Even using a normal Python program, the basic code you suggested yields
`None`:
```python
Python 3.8.4 (tags/v3.8.4:dfa645a, Jul 13 2020, 16:30:28)
Type "help", "copyright", "credits" or "license" for more
>> import executing
>> import sys
>> b = 2
>> c = "3"
>> try:
... b + c
... except:
... etype, value, tb = sys.exc_info()
... while tb:
... ex = executing.Source.executing(tb)
... print(ex.text(), ex.node)
... tb = tb.tb_next
...
None
>>
```
If I use friendly-traceback to run a .py file either directly, or by
importing it, it does yield a proper node.
```
code_qualname <module>
frame <frame at 0x042D2028, file '....ignore.py', line 5, code <module>>
node <_ast.BinOp object at 0x042C41A8>
source <executing.executing.Source object at 0x042C43A0>
statements {<_ast.Assign object at 0x042C4298>}
text b + c
text_range (25, 30)
```
Thanks for your help.
… —
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#96 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAEZXQWPH44KDE5TZ3HTLYDSTUNJLANCNFSM4QLVVCMA>
.
|
@alexmojaki I figured out how to make executing work with my console. I don't know if there is a better way, or if you'd want to implement a customizable option to do something like what I did. As you know, from executing import executing
old_getlines = executing.linecache.getlines
def getlines(*args):
lines = old_getlines(*args)
if lines:
return lines
else:
return cache.getlines(*args)
executing.linecache.getlines = getlines (I had also to define my own Now that I know I can use |
What exactly is stored in your cache? Is there metadata in addition to the lines? I suggest that:
Side notes:
|
I only store the lines of code. I see that linecache store the module globals as well. I'll have a look at what IPython does, and try to do something similar to what it does. |
Sounds good. Here's the code: linecache doesn't store the module globals, it just looks for |
Thanks @alexmojaki , I am now using
I've also started to use
I have a question for you, but first some information about what's new for context. I use evaluating, pure_eval and ASTTokens to highlight variables of interest and, when to flag parts of a line (i.e. a node) if it can be identified as causing an error. Prior to using these, I was simply tokenizing a line and trying to figure out what objects were present. This would miss dotted names (e.g. something like and here's the new version: I was wondering if it was possible to use pure_eval to evaluate function calls. For example, suppose I have a = "2"
b = int(a) + a It would be really useful if I could extract Perhaps this question could be redirected to the Discussion (https://github.com/aroberge/friendly-traceback/discussions) which I have just enabled for this project. If so, I would then close this issue as I have proceeded with using dependencies which was the whole point of creating this issue. |
This would be best handled as a new feature of pure_eval. Basically every kind of expression needs to be specially handled in pure_eval. We'd need to add handling for function calls which would in turn need to handle each supported function specially. For |
Ok, I will try to experiment with it and will likely have to fork it. For my purpose, I know (in theory) that the argument is safe. For example, suppose that I have something like: def to_str(arg):
return str(arg)
x = 42
y = x + to_str(x) This will generate an exception, and I can get something like Traceback ...
File "<friendly-console:3>", line 1, in <module>
y = x + to_str(x)
TypeError: unsupported operand type(s) for +: 'int' and 'str'
-->1: y = x + to_str(x)
^^^^^^^^^^^^^
x: 42
to_str: <function to_str> Because the exception was raised on that line, I know that the call to |
The problem is not raising an exception, but causing side effects. You don't want generating a traceback to print things, write to files, or wait for user input. If you want to bypass such concerns, you can just use regular |
Good point ... Before I dive in and try something: do you think it might be possible to have implement a custom object in a fork of pure_eval so that the previous example would yield something like:
|
Well in the example |
I was essentially using the code from your readme: from asttokens import ASTTokens
from pure_eval import Evaluator
source = """
x = 1
d = {x: 2}
y = d[x]
"""
names = {}
exec(source, names)
atok = ASTTokens(source, parse=True)
for nodes, value in Evaluator(names).interesting_expressions_grouped(atok.tree):
print(atok.get_text(nodes[0]), "=", value) I guess I'll need to essentially start from a modified version of |
Yes but the idea in those methods is to list a bunch of possibly unrelated expressions. Which is great for beefing up your current list of local variables, but you don't want a bunch of pointless noise like |
I agree that I would not want too many |
I think the number of such unevaluated nodes could very easily grow quite large, and even just one seems unhelpful to me. The only reason I can see is if you use it to say "if you extract this expression into a variable, then I'll know what it is which might let me give you more advice" or maybe "Can I |
You could also order expressions by their closeness to the executing node/statement, as in https://github.com/getsentry/sentry-python/blob/c277ed5d1170a7d58fe3482173d391ae799fdc0a/sentry_sdk/integrations/pure_eval.py#L84 Then the most relevant values, especially ones inside the failed expression, will be at the top. |
From #126 (comment) (seems a better fit here)
It should be fine as long as the lines are in linecache, each with a unique filename (e.g.
stack_data just doesn't handle those at all, they are a different breed.
That's easy to deal with, just don't render those frames. What went wrong? |
Except for programs running in the console, where I execute code using However, in other situations where the exceptions are caught by a normal excepthook, such as the one below, the frame where they are captured was incorrect, and I had to make sure that I went back in the stack to get the right frame. I run my tests with pytest, capturing the result, like so: def test_name_error():
try:
this = something
except Exception as e:
message = str(e)
friendly_traceback.explain_traceback(redirect="capture") # <--
result = friendly_traceback.get_output()
... When I used stack_data on these files and try to remove extra frame until I find the right one, the line that was flagged was the one indicated above with the comment, instead of the line
I imagine there must be a way to do it so that it works reliably, but I have not been able to get it working. This does not mean that I will not go back and try to do it again - I just moved on to something else to feel productive. |
Make sure that the filename given to
This suggests that you passed the frame object instead of the traceback object. Make sure you walk the traceback when choosing the correct frames rather than walking through the frames themselves. See https://docs.python.org/3/reference/datamodel.html?highlight=tb_lineno :
Demo script: import sys
import stack_data
formatter = stack_data.Formatter()
def print_frame(f):
print("".join(formatter.format_frame(f)))
def explain_traceback():
tb = sys.exc_info()[2]
print_frame(tb) # correct
print_frame(tb.tb_frame) # wrong
def test_name_error():
try:
this = something
except Exception:
explain_traceback()
test_name_error() Output: File "/home/alex/.config/JetBrains/PyCharm2020.3/scratches/scratch_1016.py", line 20, in test_name_error
18 | def test_name_error():
19 | try:
--> 20 | this = something
21 | except Exception:
File "/home/alex/.config/JetBrains/PyCharm2020.3/scratches/scratch_1016.py", line 22, in test_name_error
19 | try:
20 | this = something
21 | except Exception:
--> 22 | explain_traceback()
^^^^^^^^^^^^^^^^^^^ |
Look at using https://github.com/alexmojaki/executing and possibly https://github.com/alexmojaki/stack_data which uses executing.
Check to ensure that doing something like
from Tkinter import *
does not result in a huge amount of variable names.The text was updated successfully, but these errors were encountered: