pudb
pudb copied to clipboard
Add the ability to set a watch expression on an object by ID
Is your feature request related to a problem? Please describe.
I would love to be able to trace the evolution of a value across stack frames. This means that variables don't exist, but often the object that I care about is being mutated.
Describe the solution you'd like
The nicest thing, I think, would be for the watch expression dialog to get an enhanced view (maybe a select box that changes it to be this view)
Object Reference | Watch expression |
---|---|
myobj________ |
myobj.value___ |
In an ideal world, pudb would look up the id(myobj)
and then every time it entered a frame it would scan the variables to see if any of them have the same ID as myobj. If they do, then:
- if the object exists and has the same name you get the current display
myobj.value: ...
- if the variable has a different name you get:
(myobj -> newname).value: ...
- if the object does not exist:
myobj: <missing>
Describe alternatives you've considered
Currently I just constantly re-enter watch expressions in every frame, whether or not the variable name has changed.
Having the option to have watch expressions persist across frames would be helpful, usually there is a set of at most 4 var names that I care about and I wouldn't mind having 3 of the 4 be an error at any given time if it meant I didn't need to re-enter expressions all the time.
I can see the use case. I'd be happy to consider a PR. This could be done by simply storing a reference to an object instead of the name in a watch expression, maybe with a "by-name" vs "by-value" toggle in watch expression creation.
I'm taking a look at this, and I see two potentially separate ideas here:
- Whether the watch expression exists globally in all frames or only the current/local one. At the moment all watch expressions exist on in their "local" frame.
- Whether the watch expression should be re-evaluated every time using the currently existing local/global namespaces, or should store a reference to the first evaluated result and always display that result- most likely because it is a reference to a variable that exists in the current scope.
So, some questions:
- Is it reasonable to join those two ideas together, or should they be distinct? I.e. should a watch always be either a local frame expression or an "all frames" reference to a variable?
- Is it a valid use case to want to create a watch expression that will persist and be re-evaluated in multiple frames against different variables that share the same name? (Perhaps if you're dealing with several instances of the same class across multiple methods?)
- Should the references be
weakref
references? a) Would this require a slightly adjusted UI flow where users have to write/select a currently available variable instead of an arbirtrary expression? - Should we try to preserve information about where the reference came from?
- For capturing the reference, if there is an error when evaluating the expression for the first time, should we keep trying to evaluate as they step through the program until a valid result is found, or preserve the error until they edit the expression?
Super happy you're thinking about this @mvanderkamp! I'm not a maintainer, but I can respond with my thoughts when I was writing this issue. Obviously take it or leave it:
Two Separate Ideas:
Whether the watch expression exists globally in all frames or only the current/local one. At the moment all watch expressions exist on in their "local" frame.
Yes, I feel like this should be a toggle. Sometimes I want to watch how a variable name changes within a scope (usually because I'm watching how a number or string changes in response to operations), and sometimes I want to observe how different scopes mutate a value (often a mutable class or dict).
Whether the watch expression should be re-evaluated every time using the currently existing local/global namespaces, or should store a reference to the first evaluated result and always display that result
I think that this is a question about whether the watch expression should evaluate the name vs the value? If so, I think it should be a toggle as mentioned above. I might be misunderstanding here.
So, some questions:
Is it reasonable to join those two ideas together, or should they be distinct?
IMO they need to be distinct, because some values are immutable and so the only thing you can do is watch a variable name. Being able to watch a variable name across scopes is also interesting, but I'm most interested in watching a value as it mutates across frames.
Is it a valid use case to want to create a watch expression that will persist and be re-evaluated in multiple frames against different variables that share the same name?
This seems valid, although I haven't specifically wanted it. I can imagine those times where it is suprisingly useful being extremely useful (e.g. not realizing that the same variable name is used for two entirely unrelated purposes in the same stack. That has bit me at least once.)
- Should the references be weakref references? a) Would this require a slightly adjusted UI flow where users have to write/select a currently available variable instead of an arbitrary expression?
No opinion on the implementation, but I did suggest an alternate UI flow to select an available value (not variable, e.g. foo.bar
would be useful, even if foo.bar + "watchme"
isn't necessary) in my original request.
- Should we try to preserve information about where the reference came from?
IMO this would be great, e.g.:
- if the object exists and has the same name you get the current display myobj.value: ...
- if the variable has a different name you get: (myobj -> newname).value: ...
- if the object does not exist in the current scope: myobj:
- For capturing the reference, if there is an error when evaluating the expression for the first time, should we keep trying to evaluate as they step through the program until a valid result is found, or preserve the error until they edit the expression?
I don't see any specific benefit to the "preserve the error" behavior (maybe it's more comprehensible?) but I have no strong opinion.
Thanks for the input!
I think I need to clarify a bit about the "global" vs "local" distinction. Currently when you create a watch expression, it only exists for the current stack frame ("local"). If you're watching some variable by value that probably indicates that you want to continue using that same watch expression in other stack frames ("global"). So the question is: is there a need for more than one toggle? I.e. one would be "always eval the expression" vs "watch the value, don't eval again", and the other would be "show this watch expression in this stack frame only" vs "show this watch expression in all stack frames".
- If we do go with a "select from available variables" UI, that gets rid of one of the toggles- we know which it is based on which field the user filled in: text box == always eval, select from a list == watch a value and show local renames
Ah, yes. I think they should be separate toggles. I can imagine (and have wanted) to just watch the same variable name across multiple stack frames. I can also imagine not wanting to watch a value across frames, even though this isn't something I specifically recall desiring.
If you have a chance to take a look at #525 I'm curious to hear your thoughts.
Thanks for working on this. I just returned from a trip and will be swamped for a bit, but the PR is on my radar.
This looks very related to this issue https://github.com/inducer/pudb/issues/65