functions will always return the first argument
While looking for a fix for issue #114 I got the problem that only the first argument is evaluated. In other words inside a function it is always the first argument which will be returned.
Reproduce
from utils import nameof
def fn(a, b):
# nameof returns here always the first argument of function fn.
# Instead of resolving the argument b it is resolving argument a here.
print(nameof(b, frame=2))
# ...
x = 5
y = 42
fn(x, y) # Output expected: 'y', actual: 'x'
Problem
It is quite difficult to identify the right point of failure, since there are multiple ways to approach this issue. I also do not know at the moment how this issue should be fixed. But the final moment where we get the wrong return is line 340:
https://github.com/pwwang/python-varname/blob/1e07fcecef2d15c6802b76fe45bf9230a77d4832/varname/core.py#L333-L340
out contains all variables ('x', 'y') from the function fn (which should IMHO not be the case) and returns the first one since nameof is called with a single argument (no *more_vars).
Workaround
If possible, adjust the argument order.
Well, I have been thinking of deprecating nameof. It's actually misleading. What you actually should use here, is argname.
In [1]: from varname import argname
...:
...: def fn(a, b):
...: print(argname("b"))
...:
...: x = 5
...: y = 42
...: fn(x, y)
y
Well, I am fine with using argname. Unfortunally, I run into issus using argname as well.
Reproduce
from varname import argname
def fn(*, b=1):
print(argname("b"))
a = 42
fn(b=a) # Error
Stacktrace
Traceback (most recent call last):
File "/home/hein_f0/dev/slki-code/.venv/lib/python3.11/site-packages/varname/core.py", line 469, in argname
argument_sources = get_argument_sources(
^^^^^^^^^^^^^^^^^^^^^
File "/home/hein_f0/dev/slki-code/.venv/lib/python3.11/site-packages/varname/utils.py", line 442, in get_argument_sources
bound_args = signature.bind_partial(*arg_sources, *kwarg_sources.values())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.11/inspect.py", line 3219, in bind_partial
return self._bind(args, kwargs, partial=True)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.11/inspect.py", line 3138, in _bind
raise TypeError(
TypeError: too many positional arguments
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/hein_f0/dev/slki-code/test.py", line 9, in <module>
fn(b=a)
File "/home/hein_f0/dev/slki-code/test.py", line 5, in fn
print(argname("b"))
^^^^^^^^^^^^
File "/home/hein_f0/dev/slki-code/.venv/lib/python3.11/site-packages/varname/core.py", line 476, in argname
raise ImproperUseError(
varname.utils.ImproperUseError: Have you specified the right `frame` or `func`?
@HeinrichAD I can't reproduce it with python3.12:
15:24:01 ❯ ipython
Python 3.12.2 | packaged by conda-forge | (main, Feb 16 2024, 20:50:58) [GCC 12.3.0]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.26.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: from varname import argname
...:
...: def fn(*, b=1):
...: print(argname("b"))
...:
...: a = 42
...: fn(b=a)
a
Could you attach the environment you used (e.g. python version and varname version)?
I do not know why, but it differs between python and ipython.
I used Python 3.10, but I also tested Python 3.11 and 3.12.
What I did:
virtualenv -p $(which python3.10) .venv
. .venv/bin/activate
pip install -U varname
python
>>> from varname import argname
>>> def fn(*, b=1):
... print(argname("b"))
...
>>> a = 42
>>> fn(b=a)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in fn
File "/home/<user>/dev/test/python-varname-tests/.venv/lib/python3.10/site-packages/varname/core.py", line 451, in argname
raise VarnameRetrievingError(
varname.utils.VarnameRetrievingError: Cannot retrieve the node where the function is called.
If I do the some command with ipython, it is working:
pip install -U ipython
ipython
In [1]: from varname import argname
In [2]: def fn(*, b=1):
...: print(argname("b"))
...:
In [3]: a = 42
In [4]: fn(b=a)
a
The problem is that if I run that in a normal script, I run it with python and not ipython.
See #107
Ok I see. Thank you. I will think about it, check it and come back to you later. (Most likely just to close this issue.)
Since nameof is already deprecated, I am closing this issue.