Explore adding an `ignore` decorator to ignore the intermediate calls for varname
pwwang opened this issue · 13 comments
I assume you mean allowing both Foo()
and Foo[int]()
which would require different values for caller
. I think this is a good example of how trying to specify a value for caller
is often difficult or impossible. An alternative strategy is to mark certain functions as functions that varname should 'ignore', as in "I never want the varname from here". So you might write something like:
import typing
import varname
varname.ignore(typing._GenericAlias.__call__)
And then when varname is looking for the caller, it skips frames with the code object from there.
This gets more complicated when the function has been decorated and you don't have easy access to the original. In that case one possibility is to pass the qualified function name as a string, maybe something like:
import typing
import varname
varname.ignore(typing, "_GenericAlias.__call__")
executing
can usually get the qualified name from a code object, although now of course many caveats apply like source code being available and the function name being unique.
Originally posted by @alexmojaki in #31 (comment)
@alexmojaki I am opening a new issue, as this could be a more general functionality.
I am also thinking that this should be function-specific, to make sure ignoring some calls don't affect other varname
wrappers if we have many.
By varname
wrappers I mean the functions directly call varname
to fetch the variable names.
I guess the best practice should be something like this:
@varname.inject_varname
def func():
return __varname__
@func.ignore
def func2():
return func()
To extend that varname.inject_varname
to function level, we could probably do this:
def inject_varname(func):
# get the variable name here, also based on func.ignore
var_name = 'somevar'
def wrapper():
func.__globals__['__varname__'] = var_name
try:
return func()
finally:
del func.__globals__['__varname__']
return wrapper
@inject_varname
def func():
return __varname__
foo = func() # 'foo'
Not sure about the caveats when injecting __varname__
to globals, even though it's removed finally.
One of them I can think of is that it is not thread-safe.
- I don't think this code makes sense, you can't calculate
var_name = 'somevar'
at function decoration time. - Why do you even want this
inject_varname
? What's wrong with the regularvarname()
function?
- I was wrong. The retrieval should happen inside the wrapper
- I was thinking we need some kind of function-specific ignoring calls. A decorator might be a good solution to do that.
Ah, so the point is that you want something less global like @func.ignore
? I hadn't noticed that, I thought it was basically just @ignore
.
For that I suggest selecting things to ignore when varname()
is called, e.g. varname(ignore=...)
.
Thought about that as well. Swinging between those two ways, or to have both of them?
Keep the API simple and consistent. inject_varname sounds like more confusion.
Yap, let's go for varname(ignore=...)
way.
Then the uniqueness thing is still a problem. So we just leave it for the developers to take care of, or we should consider that (even though I am not sure if it is feasible).
You can check for uniqueness, so things shouldn't silently go wrong:
import executing
import __main__
def ignore(module, qualname):
source = executing.Source.for_filename(module.__file__, module.__dict__)
assert list(source._qualnames.values()).count(qualname) == 1
class A:
def foo(self):
pass
class B:
def foo(self):
pass
class B:
def foo(self):
pass
ignore(__main__, "A.foo")
ignore(__main__, "B.foo") # fails
Bingo for introducing filename of the module to make it unique. That's clever.
Closing via v0.5.6