GithubHelp home page GithubHelp logo

pwwang / python-varname Goto Github PK

View Code? Open in Web Editor NEW
299.0 5.0 19.0 1020 KB

Dark magics about variable names in python

License: MIT License

Python 75.11% Jupyter Notebook 24.76% Dockerfile 0.14%
variable-name-recovery variables python-variables python-varname nameof debugging-tool

python-varname's Introduction

varname

Pypi Github PythonVers Building Docs and API Codacy Codacy coverage Downloads

Dark magics about variable names in python

CHANGELOG | API | Playground | 🔥 StackOverflow answer

Installation

pip install -U varname

Note if you use python < 3.8, install varname < 0.11

Features

  • Core features:

    • Retrieving names of variables a function/class call is assigned to from inside it, using varname.
    • Retrieving variable names directly, using nameof
    • Detecting next immediate attribute name, using will
    • Fetching argument names/sources passed to a function using argname
  • Other helper APIs (built based on core features):

    • A value wrapper to store the variable name that a value is assigned to, using Wrapper
    • A decorator to register __varname__ to functions/classes, using register
    • A helper function to create dict without explicitly specifying the key-value pairs, using jsobj
    • A debug function to print variables with their names and values
    • exec_code to replace exec where source code is available at runtime

Credits

Thanks goes to these awesome people/projects:


executing

@alexmojaki

@breuleux

@ElCuboNegro

@thewchan

@LawsOfSympathy

Special thanks to @HanyuuLu to give up the name varname in pypi for this project.

Usage

Retrieving the variable names using varname(...)

  • From inside a function

    from varname import varname
    def function():
        return varname()
    
    func = function()  # func == 'func'

    When there are intermediate frames:

    def wrapped():
        return function()
    
    def function():
        # retrieve the variable name at the 2nd frame from this one
        return varname(frame=2)
    
    func = wrapped() # func == 'func'

    Or use ignore to ignore the wrapped frame:

    def wrapped():
        return function()
    
    def function():
        return varname(ignore=wrapped)
    
    func = wrapped() # func == 'func'

    Calls from standard libraries are ignored by default:

    import asyncio
    
    async def function():
        return varname()
    
    func = asyncio.run(function()) # func == 'func'

    Use strict to control whether the call should be assigned to the variable directly:

    def function(strict):
        return varname(strict=strict)
    
    func = function(True)     # OK, direct assignment, func == 'func'
    
    func = [function(True)]   # Not a direct assignment, raises ImproperUseError
    func = [function(False)]  # OK, func == ['func']
    
    func = function(False), function(False)   # OK, func = ('func', 'func')
  • Retrieving name of a class instance

    class Foo:
        def __init__(self):
            self.id = varname()
    
        def copy(self):
            # also able to fetch inside a method call
            copied = Foo() # copied.id == 'copied'
            copied.id = varname() # assign id to whatever variable name
            return copied
    
    foo = Foo()   # foo.id == 'foo'
    
    foo2 = foo.copy() # foo2.id == 'foo2'
  • Multiple variables on Left-hand side

    # since v0.5.4
    def func():
        return varname(multi_vars=True)
    
    a = func() # a == ('a',)
    a, b = func() # (a, b) == ('a', 'b')
    [a, b] = func() # (a, b) == ('a', 'b')
    
    # hierarchy is also possible
    a, (b, c) = func() # (a, b, c) == ('a', 'b', 'c')
  • Some unusual use

    def function(**kwargs):
        return varname(strict=False)
    
    func = func1 = function()  # func == func1 == 'func1'
    # if varname < 0.8: func == func1 == 'func'
    # a warning will be shown
    # since you may not want func to be 'func1'
    
    x = function(y = function())  # x == 'x'
    
    # get part of the name
    func_abc = function()[-3:]  # func_abc == 'abc'
    
    # function alias supported now
    function2 = function
    func = function2()  # func == 'func'
    
    a = lambda: 0
    a.b = function() # a.b == 'a.b'

The decorator way to register __varname__ to functions/classes

  • Registering __varname__ to functions

    from varname.helpers import register
    
    @register
    def function():
        return __varname__
    
    func = function() # func == 'func'
    # arguments also allowed (frame, ignore and raise_exc)
    @register(frame=2)
    def function():
        return __varname__
    
    def wrapped():
        return function()
    
    func = wrapped() # func == 'func'
  • Registering __varname__ as a class property

    @register
    class Foo:
        ...
    
    foo = Foo()
    # foo.__varname__ == 'foo'

Getting variable names directly using nameof

from varname import varname, nameof

a = 1
nameof(a) # 'a'

b = 2
nameof(a, b) # ('a', 'b')

def func():
    return varname() + '_suffix'

f = func() # f == 'f_suffix'
nameof(f)  # 'f'

# get full names of (chained) attribute calls
func.a = func
nameof(func.a, vars_only=False) # 'func.a'

func.a.b = 1
nameof(func.a.b, vars_only=False) # 'func.a.b'

Detecting next immediate attribute name

from varname import will
class AwesomeClass:
    def __init__(self):
        self.will = None

    def permit(self):
        self.will = will(raise_exc=False)
        if self.will == 'do':
            # let self handle do
            return self
        raise AttributeError('Should do something with AwesomeClass object')

    def do(self):
        if self.will != 'do':
            raise AttributeError("You don't have permission to do")
        return 'I am doing!'

awesome = AwesomeClass()
awesome.do() # AttributeError: You don't have permission to do
awesome.permit() # AttributeError: Should do something with AwesomeClass object
awesome.permit().do() == 'I am doing!'

Fetching argument names/sources using argname

from varname import argname

def func(a, b=1):
    print(argname('a'))

x = y = z = 2
func(x) # prints: x


def func2(a, b=1):
    print(argname('a', 'b'))
func2(y, b=x) # prints: ('y', 'x')


# allow expressions
def func3(a, b=1):
    print(argname('a', 'b', vars_only=False))
func3(x+y, y+x) # prints: ('x+y', 'y+x')


# positional and keyword arguments
def func4(*args, **kwargs):
    print(argname('args[1]', 'kwargs[c]'))
func4(y, x, c=z) # prints: ('x', 'z')


# As of 0.9.0 (see: https://pwwang.github.io/python-varname/CHANGELOG/#v090)
# Can also fetch the source of the argument for
# __getattr__/__getitem__/__setattr/__setitem__/__add__/__lt__, etc.
class Foo:
    def __setattr__(self, name, value):
        print(argname("name", "value", func=self.__setattr__))

Foo().a = 1 # prints: ("'a'", '1')

Value wrapper

from varname.helpers import Wrapper

foo = Wrapper(True)
# foo.name == 'foo'
# foo.value == True
bar = Wrapper(False)
# bar.name == 'bar'
# bar.value == False

def values_to_dict(*args):
    return {val.name: val.value for val in args}

mydict = values_to_dict(foo, bar)
# {'foo': True, 'bar': False}

Creating dictionary using jsobj

from varname.helpers import jsobj

a = 1
b = 2
jsobj(a, b) # {'a': 1, 'b': 2}
jsobj(a, b, c=3) # {'a': 1, 'b': 2, 'c': 3}

Debugging with debug

from varname.helpers import debug

a = 'value'
b = ['val']
debug(a)
# "DEBUG: a='value'\n"
debug(b)
# "DEBUG: b=['val']\n"
debug(a, b)
# "DEBUG: a='value'\nDEBUG: b=['val']\n"
debug(a, b, merge=True)
# "DEBUG: a='value', b=['val']\n"
debug(a, repr=False, prefix='')
# 'a=value\n'
# also debug an expression
debug(a+a)
# "DEBUG: a+a='valuevalue'\n"
# If you want to disable it:
debug(a+a, vars_only=True) # ImproperUseError

Replacing exec with exec_code

from varname import argname
from varname.helpers import exec_code

class Obj:
    def __init__(self):
        self.argnames = []

    def receive(self, arg):
        self.argnames.append(argname('arg', func=self.receive))

obj = Obj()
# exec('obj.receive(1)')  # Error
exec_code('obj.receive(1)')
exec_code('obj.receive(2)')
obj.argnames # ['1', '2']

Reliability and limitations

varname is all depending on executing package to look for the node. The node executing detects is ensured to be the correct one (see this).

It partially works with environments where other AST magics apply, including pytest, ipython, macropy, birdseye, reticulate with R, etc. Neither executing nor varname is 100% working with those environments. Use it at your own risk.

For example:

  • This will not work with pytest:

    a = 1
    assert nameof(a) == 'a' # pytest manipulated the ast here
    
    # do this instead
    name_a = nameof(a)
    assert name_a == 'a'

python-varname's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

python-varname's Issues

How to get argname of chain function

ds.sidebar().button(name='test')
def button(name):
      print(argname('name')

error:

 \varname\utils.py:447: 
UsingExecWarning: Cannot evaluate node Attribute(value=Call(func=Attribute(value=Name(id='ds', ctx=Load()), attr='sidebar', ctx=Load()), args=[], keywords=[]), attr='button', ctx=Load()) using 'pure_eval'. Using 'eval' to get the function that calls 'argname'. Try calling it using a variable reference to the function, or passing the function to 'argname' explicitly.

Can't retrieve varname of subclass as inherited or own attribute

I am running this in Jupyter Notebook, if that makes any difference. Here is my code:

I first make class Item which stores its varname. This works fine and the attribute varname returns the correct varname:

class Item:
    def __init__(self, slots = 1):
        self.slots = slots
        self.varname = varname()

I then make class Child which inherits from Item:

class Child(Item):
    def __init__(self):
        super().__init__()

This causes the following error when I try to initialize a Child object:

VarnameRetrievingError                    Traceback (most recent call last)
<ipython-input-133-082ce90989d2> in <module>
----> 1 child = Child()

<ipython-input-132-c688eea09291> in __init__(self)
      2 
      3     def __init__(self):
----> 4         super().__init__()

<ipython-input-130-f7ff8f322a43> in __init__(self, slots)
      3     def __init__(self, slots = 1):
      4         self.slots = slots
----> 5         self.varname = varname()

~\anaconda3\lib\site-packages\varname.py in varname(caller, ignore, multi_vars, raise_exc)
     91         if raise_exc:
     92             raise VarnameRetrievingError(
---> 93                 'Failed to retrieve the variable name.'
     94             )
     95         return None

VarnameRetrievingError: Failed to retrieve the variable name.

Rewriting like this caused the same error as above:

class Child(Item):
    
    def __init__(self, slots = 1):
        super().__init__(slots)
        self.varname = varname()

will docstring has broken example

This code raises an error AttributeError: 'function' object has no attribute 'abc':

    def i_will():
        will = varname.will()
        func = lambda: 0
        func.will = will
        return func

    func = i_will().abc

Also, you use:

```python

Do you have some tool that recognises that? Docstrings aren't typically treated like markdown in my experience.

argname doesn't work for binary operations

Hi,
I would like to use argname to get the argument for an overloaded binary operation (bitwise &), but it seems that this is not supported at the moment.

I get the following error:

...\varname\core.py in argname(arg, func, dispatch, frame, ignore, vars_only, *more_args)
    401 
    402     if not func:
--> 403         func = get_function_called_argname(func_frame, func_node)
    404 
    405     if dispatch:

...\varname\utils.py in get_function_called_argname(frame, node)
    356     # We need node to be ast.Call
    357     if not isinstance(node, ast.Call):
--> 358         raise VarnameRetrievingError(
    359             f"Expect an 'ast.Call' node, but got {type(node)!r}. "
    360             "Are you using 'argname' inside a function?"

VarnameRetrievingError: Expect an 'ast.Call' node, but got <class '_ast.BinOp'>. Are you using 'argname' inside a function?

Was wondering if there is a way to do that. Thanks!

nameof error when running program, but not when running in debug

Hi there, I've been developing a code for a couple of days and it worked seemingly well. However, today, I started seeing this error:

"Couldn't retrieve the call node. "
varname.utils.VarnameRetrievingError: Couldn't retrieve the call node. This may happen if you're using some other AST magic at the same time, such as pytest, ipython, macropy, or birdseye.

when running the program. However, when I break just above the problematic line and go step by step, the problem does not occur. I've also checked an no instances of pytest of any kind is running.

"Unable to retrieve the ast node" issue

Hi,

Could I get some help with the following ?
I am doing a dry run with the example script and hitting the following errors, was hitting similar issue when I tried argname, but nameof works fine here. It's running on M1 Mac.

> >>> from varname.helpers import register
> >>> @register
> ... def function():
> ...     return __varname__
> ...
> >>> func = function()
> Traceback (most recent call last):
>   File "<stdin>", line 1, in <module>
>   File "/Users/slu/Library/Python/3.9/lib/python/site-packages/varname/helpers.py", line 75, in wrapper
>     cls_or_func.__globals__["__varname__"] = varname(
>   File "/Users/slu/Library/Python/3.9/lib/python/site-packages/varname/core.py", line 108, in varname
>     raise VarnameRetrievingError("Unable to retrieve the ast node.")
> varname.utils.VarnameRetrievingError: Unable to retrieve the ast node.

executing

Just letting you know that magically detecting the call has been done more robustly in executing and building on that is sorcery which has functions that do the kind of thing varname does.

How to print the variable names inside a for loop ?

from varname import nameof

var1 = 1
var2 = 2
var3 = 3

inputs = [var1, var2, var3]

for i in inputs:
    print(nameof(i))

The code above prints three times the literal string "i" instead of printing the strings "var1", "var2" and "var3".

Is there a way to handle that, please ?

Partial assignment in case a starred variable is present

Hi,
In the case that there are properly named variables, but also a stared variable.
Would it be possible to be able to get the name for the named variables?

For example to make this work (currently it throws and exception(also when raise_exc=False)):

from varname import varname

a, *_ = varname(frame=0, multi_vars=True)
assert a == 'a'

_ Could be assigned None

Exploration of supporting single-dispatched functions for argname()

>>> from varname import argname
>>> from functools import singledispatch
>>> 
>>> @singledispatch
... def add(a, b):
...     ...
... 
>>> @add.register(int)
... def _(a, b):
...     aname = argname(a, b)
...     print(aname)
...     return a + b 
... 
>>> def mul(a, b):
...     aname = argname(a, b)
...     print(aname)
...     return a * b
... 
>>> x = y = 1
>>> 
>>> mul(x, y)
('x', 'y')
1
>>> 
>>> add(x, y)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    add(x, y)
  File "/.../lib/python3.7/functools.py", line 840, in wrappe
r
    return dispatch(args[0].__class__)(*args, **kw)
  File "<input>", line 3, in _
    aname = argname(a, b)
  File "/.../varname/core.py", line 401, in argnam
e
    f"No value passed for argument {argnode.id!r}, "
ValueError: No value passed for argument 'a', or it is not an argument at all.

Explore adding an `ignore` decorator to ignore the intermediate calls for varname

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)

Uniforming decorator for functions and classes to retrieve varname

Now we are able to do this with class:

from varname import inject_varname

@inject_varname
class Dict(dict):
    pass

a = Dict(a=1)
b = Dict(b=2)
a.__varname__ == 'a'
b.__varname__ == 'b'
a.update(b)
a == {'a':1, 'b':2}

Try to extend it to functions:

@inject_varname
def func():
  return __varname__

f = func() # f == 'f'

How it works:

def inject_varname(func):

    def wrapper():
        # get by varname.varname, also inherit the arguments from it for the decorator.
        func.__globals__['__varname__'] = var_name # get by varname.varname
        try:
          return func()
        finally:
          del func.__globals__['__varname__']

    return wrapper

Also considering rename inject_varname into register.

bytecode_nameof should check for full=True

If you try eval("varname.nameof(a.b, full=True)") you will get an error as expected, because the keyword argument changes the call bytecode.

However if you don't pass the keyword argument at the call site, you get the wrong result:

import varname

def full_nameof(_):
    return varname.nameof(_, full=True, caller=2)

assert full_nameof(varname.nameof) == "varname.nameof"
assert eval("full_nameof(varname.nameof)") == "nameof"

This is based on the suggestion given in #23

Cannot get the var name of the var in a list.

Thanks for your great job!

I am now using varname but found an strange thing is that we cannot get the var name of the var in a list. The code can be found below. If I use a piece of code from StackOverflow, all things look right.

from varname import varname, nameof, will, argname
a = 1
nameof(a) # 'a'
'a'
b = 3
c = 4
a = [b, c]
a
[3, 4]
import inspect
def get_varname(var):
    """
    Gets the name of var. Does it from the out most frame inner-wards.
    :param var: variable to get name from.
    :return: string
    """
    for fi in reversed(inspect.stack()):
        names = [var_name for var_name, var_val in fi.frame.f_locals.items() if var_val is var]
        if len(names) > 0:
            return names[0]
get_varname(a[0])
'b'
nameof(a[0])
---------------------------------------------------------------------------

ImproperUseError                          Traceback (most recent call last)

<ipython-input-45-2ef558708817> in <module>
----> 1 nameof(a[0])


~/anaconda3/envs/sslearning/lib/python3.7/site-packages/varname/core.py in nameof(var, frame, vars_only, *more_vars)
    313         func=nameof,
    314         frame=frame,
--> 315         vars_only=vars_only,
    316     )
    317     return out if more_vars else out[0]  # type: ignore


~/anaconda3/envs/sslearning/lib/python3.7/site-packages/varname/core.py in argname(arg, func, dispatch, frame, ignore, vars_only, *more_args)
    451         if isinstance(source, ast.AST):
    452             raise ImproperUseError(
--> 453                 f"Argument {ast.dump(source)} is not a variable "
    454                 "or an attribute."
    455             )


ImproperUseError: Argument Subscript(value=Name(id='a', ctx=Load()), slice=Index(value=Num(n=0)), ctx=Load()) is not a variable or an attribute.

It would be better if there exist some solutions.

Function will not return as expected

I think will is implemented wrong. Consider this example:

import varname


class Foo:
    @property
    def bar(self):
        print(varname.will())
        return "abc"


Foo().bar.upper()

print(varname.will()) prints bar, not upper.

Exception raised when assigning to subscript even using raise_exc=False

An exception is raised when attempting to assign in to a subscript, even when setting raise_exc=False

Here's a minimal reproduction of the problem:

def my_func():
    return varname(raise_exc=False)

x[0] = my_func()

which raises: Can only get name of a variable or attribute, not Subscript

This appears to be being caused here:

names = node_name(target)

as raise_exc doesn't propagate to node_name.

Adding a raise_exc argument to node_name would work, though I'm not familiar enough with the codebase to know if there's a better way?

How to retrieve var name from nested classes

Hi,

I did not find a way to get varname if I have nested classes

e.g.

class A:
    def __init__():
         self.name = varname()

class B(A):
   def __init__():
       super().__init__()

var = B() # here I have error

class C(B):
   def __init__():
       super().__init__()

var2 = C()  # also error here 

Get full path of variable name?

I really like the varname functionality of this package. It allows me to abandon hard-coded variable names e.g. when mocking something:

from unittest.mock import patch
import datetime
patch.object(nameof(datetime), 42)

However, for the same reason, I'd like to be able to fill in entire "variable paths" with python-varname, too. Pseudo code:

from unittest.mock import patch
import datetime
patch(nameof(datetime.MAXYEAR), 42)

However, nameof() only returns 'MAXYEAR' in this example. My current workaround is to write instead:

patch(f'{nameof(datetime)}.{nameof(dt.MAXYEAR)}'), 42)

but obviously, this is not very elegant.

Some method like varname.pathof for this would be a great addition IMO!
Other examples:

> pathof(x)
'x'
> pathof(x.y)
'x.y'
> pathof(x.y.z)
'x.y.z'
> pathof(x.y.z())
varname.VarnameRetrievingError

nameof not working inside of for loop

If I run this code the first call on a variable can be processed. But if I call it inside a for loop I get an error.

import varname
test = {}
print(varname.nameof(test))
for i in [0]:
  print(varname.nameof(test))
test
---------------------------------------------------------------------------
VarnameRetrievingError                    Traceback (most recent call last)
<ipython-input-39-f74218a72604> in <module>
      2 print(varname.nameof(test))
      3 for i in [0]:
----> 4   print(varname.nameof(test))

~/dev/textSum/.direnv/python-3.8.3/lib/python3.8/site-packages/varname.py in nameof(*args)
    183     if not exec_obj.node:
    184         # we cannot do: assert nameof(a) == 'a' in pytest
--> 185         raise VarnameRetrievingError("Callee's node cannot be detected.")
    186 
    187     assert isinstance(exec_obj.node, ast.Call)

VarnameRetrievingError: Callee's node cannot be detected.

Tested on macOS Catalina with python 3.8.3

argname seems not working

Hello,

I have a very simple test case, and I am not sure why argname does not work. Please let me know if I am missing something very basic.

The code is as follows:

import varname

def simple_fn(obj):
    return varname.argname(obj)

if __name__ == '__main__':
    class Missing:
        pass

    a = Missing()
    assert simple_fn(a) == 'a'

If I run in the terminal, I get the following error:

Traceback (most recent call last):
  File "temp.py", line 12, in <module>
    assert simple_fn(a) == 'a'
  File "temp.py", line 5, in simple_fn
    return varname.argname(obj)
  File "miniconda3/envs/py/lib/python3.10/site-packages/varname/core.py", line 432, in argname
    match = re.match(r"^([\w_]+)\[(.+)\]$", farg)
  File "miniconda3/envs/py/lib/python3.10/re.py", line 190, in match
    return _compile(pattern, flags).match(string)
TypeError: expected string or bytes-like object

Thank you!

argname doesn't seem to function, "cannot retrieve the node where the function is called"

hello, i'm implementing argname into my code but it didn't seem to work. decided to run the example code on a new module as a test, however i got the same error.
(using pyscripter IDE)

from varname import argname

def func(a, b=1):
    print(argname('a'))

x = y = z = 2
func(x) # prints: x

i get the error:
AppData\Local\Programs\Python\Python39\lib\site-packages\varname\core.py", line 445, in argname
raise VarnameRetrievingError(
varname.utils.VarnameRetrievingError: Cannot retrieve the node where the function is called.

please let me know if i'm missing something simple

VarnameRetrievingError when I use function as part of module

I have a function that uses argname(), taking in variable names and returns them as strings:

def get_var_names(*variables):
    """
    return variable names
    """
    var_names = []
    for var_ind in range(len(variables)):
        var_name = argname(f"variables[{var_ind}]")
        var_names.append(var_name)
    return var_names

It works great:

my_names= get_var_names(var1, var2)

Returns ['var1', 'var2']

But when I embed it in a module, let's call it name_grabber:

import namegrabber as ng
ng.get_var_names(var1, var2)

I get the following error:

var_name = argname(f"variables[{var_ind}]")

File "C:\Users\Eric\Miniconda3\envs\my_env_name\lib\site-packages\varname\core.py", line 400, in argname
raise VarnameRetrievingError(
varname.utils.VarnameRetrievingError: Cannot retrieve the node where the function is called.

Any idea what I am doing wrong here?

Note when I test it as part of main it works fine (when I run the script using python -m filename it runs great). The above error is only happening when I install things as part of a package in my dev environment using pip install -e . and then go in and import and use the code how I will want users to do things).

Failure to find parent assign node doesn't mean improper use

node = get_node(frame + 1, ignore, raise_exc=raise_exc)
if not node:
if raise_exc:
raise VarnameRetrievingError("Unable to retrieve the ast node.")
return None
node = lookfor_parent_assign(node, strict=strict)
if not node:
if strict and not strict_given:
warnings.warn(
"Calling `varname()` without passing `strict` "
"(that defaults to False) to get the variable name but the "
"caller does not assign the result directly to that variable. "
"`strict` will default to True and an `ImproperUseError` "
"will be raised in v0.8.0.",
DeprecationWarning,
)
return varname(
frame=frame,
ignore=ignore,
multi_vars=multi_vars,
raise_exc=raise_exc,
strict=False,
)
if raise_exc:
if strict:
msg = "Expression is not the direct RHS of an assignment."
else:
msg = "Expression is not part of an assignment."
raise ImproperUseError(msg)

The ImproperUseError (last line) might be misleading if the node passed to lookfor_parent_assign() is the wrong one (i.e. wrong frame or ignore passed).

An example:

def decor(f):
    def wrapper():
        return f()
    return wrapper

def func():
    return varname(ignore=(func2, 0)) # saying func2 is not decorated, but it actually is

@decor
def func2():
    return func()

var = func2()
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    var = func2()
  File "<input>", line 3, in wrapper
    return f()
  File "<input>", line 3, in func2
    return func()
  File "<input>", line 2, in func
    return varname(ignore=(func2, 0)) # saying func2 is not decorated, but it actually is
  File "/home/pwwang/github/python-varname/varname/core.py", line 129, in varname
    strict=False,
  File "/home/pwwang/github/python-varname/varname/core.py", line 137, in varname
    raise ImproperUseError(msg)
varname.utils.ImproperUseError: Expression is not part of an assignment.

It is indeed an ImproperUseError since ignore refers to return func() inside func2. However, it is still confusing, as people may be expecting the line var = func2(). We should either indicate the place where the ImproperUseError happens, or raise a general error (say VarnameRetrievingError) and indicating that it may be due to improper use, or locating of a wrong node.

I prefer the former way.

This also applies to the other ImportUseError for multi_vars=False but multiple variables on LHS.

@breuleux

how to get arg name in sub function?

Hi, i want get argname with captured variable func_p1 in function f1, expected result is origin_name=='func_main_a',how can i do?
Traceback:

Traceback (most recent call last):
  File "...", line 358, in <module>
    func_main()
  File "...", line 352, in func_main
    f1()
  File "...", line 344, in f1
    origin_name = varname.argname(name)
  File "...\lib\site-packages\varname\core.py", line 448, in argname
    raise ImproperUseError(
varname.utils.ImproperUseError: 'func_p1' is not a valid argument of 'func.<locals>.f1'.

I tries all possible values for func= and frame= in the varname.argname parameter,but still get Error

def func(func_p1):
    def f1():
        name = varname.nameof(func_p1)
        assert isinstance(name, str)
        origin_name = varname.argname(name)

    return f1


def func_main():
    func_main_a= 2
    f1 = func(func_main_a)
    f1()

func_main()

varname doesn't play well with type annotations

Thanks for making this available -- it's a very clever package.

I've noticed that varname doesn't play well with type annotations. Minimal code to reproduce an error:

from varname import varname

class Foo:
    def __init__(self):
        self.id = varname()

foo: Foo = Foo()
# => varname.VarnameRetrievingError: Failed to retrieve the variable name.

It also doesn't like Generic[T]:

from typing import Generic, TypeVar

T = TypeVar("T")

class Foo(Generic[T]):
    def __init__(self):
        self.id = varname()

foo = Foo[int]()
foo.id  # => 'result'

Are there any plans to support this better? I plead ignorance as to the complexity, admittedly, but it would be helpful for my use case.

"VarnameRetrievingError: Unable to retrieve the ast node." / "VarnameRetrievingError: Cannot retrieve the node where the function is called."

OK, so I'm running this on Windows 11 so maybe that is part of the problem? Anyway, I don't get very far along in your example usage from the README before I get an error. I get similar errors when I'm just poking around, doing almost anything I would want this module for.
`
C:\Python311>python
Python 3.11.3 (tags/v3.11.3:f3909b8, Apr 4 2023, 23:49:59) [MSC v.1934 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.

from varname import varname
def function():
... return varname()
...
func = function() # func == 'func'
Traceback (most recent call last):
File "", line 1, in
File "", line 2, in function
File "C:\Python311\Lib\site-packages\varname\core.py", line 108, in varname
raise VarnameRetrievingError("Unable to retrieve the ast node.")
varname.utils.VarnameRetrievingError: Unable to retrieve the ast node.

`

So here is my own example to show the partial functionality of the module:
`
C:\Python311>python
Python 3.11.3 (tags/v3.11.3:f3909b8, Apr 4 2023, 23:49:59) [MSC v.1934 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.

import varname
x=2
varname.nameof(x)
'x'
def f(foo):
... print(varname.nameof(foo))
... print(varname.argname(foo))
...
f(x)
foo
Traceback (most recent call last):
File "", line 1, in
File "", line 3, in f
File "C:\Python311\Lib\site-packages\varname\core.py", line 446, in argname
raise VarnameRetrievingError(
varname.utils.VarnameRetrievingError: Cannot retrieve the node where the function is called.

`

Traversal list

when i Traversal a list,how should i get the string of variable,such as ls=[a,b,c,d],both a,b,c,d are dict
for i in ls:
#the string name of i
the result should be : 'a','b','c','d',but not 'i'

type annotated class attribute

This is probably not an issue with the varname package.

I am looking for a way to get the name of a type annotated attribute of a class.

from varname import nameof

class MyClass:
    a: int

nameof(MyClass.a)  # AttributeError: type object 'MyClass' has no attribute 'a'

Replacing a: int with a: int = 8 works.

  1. Is there anyway to get this to work using your dark magic?

  2. Is there anyway to get this to work using your dark magic and without modifying MyClass?

My goal is to replace this MyClass.__annotations__["a"] with something like MyClass.__annotations__[nameof(MyClass.a)] which would enable refactoring without having to search for the "a" string.

how to nameof inside a loop ?

I'm using the fantastic varname python package https://pypi.org/project/varname/ to print python var names from inside the code:

    >>> from varname import nameof
    >>> myvar = 42
    >>> print(f'{nameof(myvar)} + 8: {myvar + 8}')
    myvar + 8: 50
    >>>

but when I try to use it in a loop:

    >>> a, b, c, d = '', '', '', ''
    >>> for i in (a, b, c, d):
        print(f'{nameof(i)}') 
    i
    i
    i
    i
    >>>

I do not get to print a, b, c, ...

How could I get something like this ?:

a 
b
c
d

Cf: https://stackoverflow.com/questions/73840841/how-to-use-python-varname-to-get-var-names-inside-loops

2.7 Support

Changed some things so that it'll work on python 2.7. Is it worth submitting PR?

Obtain current scope and pass into function

Hi, I am really appreciating your library and have used it in very helpful contexts in my development. I have a question; I'm unable to determine if the following is possible with your library:

Suppose I have a class method that takes in a class as a parameter:

class CreateSomething:
     def __init__(self, hosting_class):
           hosting_class.something = 'something'

     def create(self, hosting_class, new_value):
           hosting_class.something = new_value

And these methods are being called from a class like so:

class HostSomething:
      def __init__(self):
            create_something_class = CreateSomething(self)
            create_something_class.create(self, 'something else')

Is there a function in varname that could pass along the current class (HostSomething==self) to the class methods? Do you think it would be possible to make something like this:

class HostSomething:
      def __init__(self):
            create_something_class = CreateSomething()
            create_something_class.create('something else')

Searching online for a vanilla solution let me here: https://stackoverflow.com/a/14694234
If you any ideas, please let me know! Thanks!

VarnameRetrievingError when run py script in debug mode in PyCharm

When try to run code in PyCharm in Debug mode I get VarnameRetrievingError.

Code to reproduce:

import varname

abc = 5
abc_varname = varname.nameof(abc)
print(abc_varname)

Result:

E:\venv\Scripts\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 62134 --file "E:/Projects/python_projects/get_var_name.py"
Connected to pydev debugger (build 213.7172.26)
Traceback (most recent call last):
  File "C:\Program Files\JetBrains\PyCharm Community Edition 2020.3\plugins\python-ce\helpers\pydev\pydevd.py", line 1483, in _exec
    pydev_imports.execfile(file, globals, locals)  # execute the script
  File "C:\Program Files\JetBrains\PyCharm Community Edition 2020.3\plugins\python-ce\helpers\pydev\_pydev_imps\_pydev_execfile.py", line 18, in execfile
    exec(compile(contents+"\n", file, 'exec'), glob, loc)
  File "E:/Projects/python_projects/get_var_name.py", line 6, in <module>
    abc_varname = varname.nameof(abc)
  File "E:\venv\lib\site-packages\varname\core.py", line 277, in nameof
    node = get_node_by_frame(frameobj, raise_exc=True)
  File "E:\venv\lib\site-packages\varname\utils.py", line 141, in get_node_by_frame
    raise VarnameRetrievingError(
varname.utils.VarnameRetrievingError: Couldn't retrieve the call node. This may happen if you're using some other AST magic at the same time, such as pytest, ipython, macropy, or birdseye.
python-BaseException

Process finished with exit code 1

python 3.10
varname 0.8.3

Performance issues

Hello, I was using Python varname for some of my projects enum variables which made logging much easier. But I noticed when profiling my code with cProfile there were tens of thousands of calls to various built-in functions. My code definitely doesn't do that much work. So I replaced the varname code with integers and it fixed the problem. It may have just been my code - still investigating - but this is a heads up.

Error retrieving value for `__setitem__`

Really cool library! I'm attempting to override a dict to capture the variable names passed as values.

Inside a file:

from collections import UserDict
import varname

class MyDict(UserDict):
    def __init__(self, *args, **kwargs):
        # print(varname.argname("args"), varname.argname("kwargs"))
        super().__init__(*args, **kwargs)
    
    def __setitem__(self, key, value):
        print(varname.argname("value"))
        super().__setitem__(key, value)

x = "my_value"
d = MyDict()
d['x'] = x

Expected output: x

Actual output:

Traceback (most recent call last):
  File "/Users/edward/temp/mutmap.py", line 15, in <module>
    d['x'] = value
  File "/Users/edward/temp/mutmap.py", line 10, in __setitem__
    print(varname.argname("value"))
  File "/Users/edward/temp/python-varname/varname/core.py", line 391, in argname
    func_node = get_node_by_frame(func_frame)
  File "/Users/edward/temp/python-varname/varname/utils.py", line 141, in get_node_by_frame
    raise VarnameRetrievingError(
varname.utils.VarnameRetrievingError: Couldn't retrieve the call node. This may happen if you're using some other AST magic at the same time, such as pytest, ipython, macropy, or birdseye.

overload type returns for nameof

the nameof always return an union, it makes it hard to use in places where we just want a string

    foo = "..."
    x1: str = nameof(foo) #Expression of type "str | Tuple[str, ...]" cannot be assigned to declared type "Tuple[str, ...]"
    x2: Tuple[str, ...] = nameof(foo) # Expression of type "str | Tuple[str, ...]" cannot be assigned to declared type "Tuple[str, ...]"
    x3: Tuple[str, ...] | str = nameof(foo) # ok

would be great to have an overload to only return Tuple when more names are passed,

or maybe a new deterministic one var function?

some cases of argname don't know how to deal

Hi Devs,

I got some dumb questions when I using argname. I would like to know if the following two cases can be achieved:
First, I defined a class and three variables:


def test(a=None, b=None, c=None):
    return argname('b')

x = [[111]]
y = [[222]]
z = [x, y]

The first question is how to know when calling a function that one of the variables is a list.

That is, when I call it like this:
test(a=x,b=z,c=y)
I expect the return result of the current function to be
'[x,y]'
but the return result of the current function is
'z'
I tried to call
test(a=x,b=[x,y],c=y)
but it reports an error.

    498 source = argument_sources[farg_name]
    499 if isinstance(source, ast.AST):
--> 500     raise ImproperUseError(
    501         f"Argument {ast.dump(source)} is not a variable "
    502         "or an attribute."
    503     )
    505 if isinstance(farg_subscript, int) and not isinstance(source, tuple):
    506     raise ImproperUseError(
    507         f"`{farg_name}` is not a positional argument."
    508     )

ImproperUseError: Argument List(elts=[Name(id='x', ctx=Load()), Name(id='y', ctx=Load())], ctx=Load()) is not a variable or an attribute.

The second problem is that using argname and exec at the same time seems to report an error. For example, executing

test_str = 'test(a=x,b=z,c=y)'
exec(test_str)

will report an error:

    439 # Only do it when func_node are available
    440 if not func_node:
    441     # We can do something at bytecode level, when a single positional
    442     # argument passed to both functions (argname and the target function)
    443     # However, it's hard to ensure that there is only a single positional
    444     # arguments passed to the target function, at bytecode level.
--> 445     raise VarnameRetrievingError(
    446         "Cannot retrieve the node where the function is called."
    447     )
    449 func_node = reconstruct_func_node(func_node)
    451 if not func:

VarnameRetrievingError: Cannot retrieve the node where the function is called.

Can the above two problems be solved? Thank you for your time

Support multiple variables on LHS for varname.varname

The code in here

python-varname/varname.py

Lines 243 to 266 in 6a1e8bd

ret = []
for arg in node.args:
if not full or isinstance(arg, ast.Name):
ret.append(_node_name(arg))
else:
# traverse the node to get the full name: nameof(a.b.c)
# arg:
# Attribute(value=Attribute(value=Name(id='a', ctx=Load()),
# attr='b',
# ctx=Load()),
# attr='c',
# ctx=Load())
full_name = []
while not isinstance(arg, ast.Name):
if not isinstance(arg, ast.Attribute):
raise VarnameRetrievingError(
'Can only retrieve full names of '
'(chained) attribute calls by nameof.'
)
full_name.append(arg.attr)
arg = arg.value
# now it is an ast.Name
full_name.append(arg.id)
ret.append('.'.join(reversed(full_name)))

assumes the Node is a function call. There are other AST Node types. For example, the code below:

    class MyType(object):
        def __eq__(self, other):
            print(varname.nameof(self, caller=2))
            return super().__eq__(other)

    a = MyType()
    print(a == 3)

Throws an exception because The Node in this case is a Compare Node that doesn't have args. It does, however, have n.left.id to access the variable name we are calling __eq__() on.

Is there a way to get the argname with exec or eval

from varname import argname

class elements:

    def __init__(self):
        self.app = []

    def sidebar(self):
        c = elements()
        self.app.append(c)
        return c

    def button(self, name):
        self.app.append({"type":"button", "name":argname("name")})


ds = elements()
exec("ds.button('test button')")

Error:

\Lib\site-packages\varname\core.py", line 451, in argname
    raise VarnameRetrievingError(
varname.utils.VarnameRetrievingError: Cannot retrieve the node where the function is called.

Errors on Python 3.9.7

Hello, I'm trying to use this package on Python 3.9.7 (very recent I know) and the sample code throws errors. Here are the results:

>>> xxxxx = varname()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/ubuntu/.local/lib/python3.9/site-packages/varname/core.py", line 103, in varname
    refnode = get_node(frame + 1, ignore, raise_exc=raise_exc)
  File "/home/ubuntu/.local/lib/python3.9/site-packages/varname/utils.py", line 127, in get_node
    return get_node_by_frame(frameobj, raise_exc)
  File "/home/ubuntu/.local/lib/python3.9/site-packages/varname/utils.py", line 132, in get_node_by_frame
    exect = Source.executing(frame)
  File "/home/ubuntu/.local/lib/python3.9/site-packages/executing/executing.py", line 309, in executing
    lineno = frame.f_lineno
AttributeError: 'NoneType' object has no attribute 'f_lineno'

Then I also tried the following and got a different error:

>>> def function():
...     return varname()
... 
>>> xxx = function()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in function
  File "/home/ubuntu/.local/lib/python3.9/site-packages/varname/core.py", line 106, in varname
    raise VarnameRetrievingError("Unable to retrieve the ast node.")
varname.utils.VarnameRetrievingError: Unable to retrieve the ast node.

I think the idea behind the module though is a very good idea. Don't let it discourage you that there seems to be problems on the most recent versions of Python. This is a cool and useful hack. If you can get it working let me know! I have an elegant use-case in my state machine code.

how to get name inside function?

In this case:

def a_func(t):
    print(t)

currently, I only can print it's value, but I want print it name as label!!

v = 1098
a_func(v)
# it should print `v: 1098`
``

it's a way to do it with this lib?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.