varname 0.14.0__tar.gz → 0.15.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: varname
3
- Version: 0.14.0
3
+ Version: 0.15.0
4
4
  Summary: Dark magics about variable names in python.
5
5
  License: MIT
6
6
  Author: pwwang
@@ -18,6 +18,7 @@ Provides-Extra: all
18
18
  Requires-Dist: asttokens (==3.*) ; extra == "all"
19
19
  Requires-Dist: executing (>=2.1,<3.0)
20
20
  Requires-Dist: pure_eval (==0.*) ; extra == "all"
21
+ Requires-Dist: typing_extensions (>=4.13,<5.0) ; python_version < "3.10"
21
22
  Project-URL: Homepage, https://github.com/pwwang/python-varname
22
23
  Project-URL: Repository, https://github.com/pwwang/python-varname
23
24
  Description-Content-Type: text/markdown
@@ -268,6 +269,31 @@ Special thanks to [@HanyuuLu][2] to give up the name `varname` in pypi for this
268
269
  # foo.__varname__ == 'foo'
269
270
  ```
270
271
 
272
+ ### Getting variable names directly using `nameof`
273
+
274
+ ```python
275
+ from varname import varname, nameof
276
+
277
+ a = 1
278
+ nameof(a) # 'a'
279
+
280
+ b = 2
281
+ nameof(a, b) # ('a', 'b')
282
+
283
+ def func():
284
+ return varname() + '_suffix'
285
+
286
+ f = func() # f == 'f_suffix'
287
+ nameof(f) # 'f'
288
+
289
+ # get full names of (chained) attribute calls
290
+ func.a = func
291
+ nameof(func.a, vars_only=False) # 'func.a'
292
+
293
+ func.a.b = 1
294
+ nameof(func.a.b, vars_only=False) # 'func.a.b'
295
+ ```
296
+
271
297
  ### Detecting next immediate attribute name
272
298
 
273
299
  ```python
@@ -244,6 +244,31 @@ Special thanks to [@HanyuuLu][2] to give up the name `varname` in pypi for this
244
244
  # foo.__varname__ == 'foo'
245
245
  ```
246
246
 
247
+ ### Getting variable names directly using `nameof`
248
+
249
+ ```python
250
+ from varname import varname, nameof
251
+
252
+ a = 1
253
+ nameof(a) # 'a'
254
+
255
+ b = 2
256
+ nameof(a, b) # ('a', 'b')
257
+
258
+ def func():
259
+ return varname() + '_suffix'
260
+
261
+ f = func() # f == 'f_suffix'
262
+ nameof(f) # 'f'
263
+
264
+ # get full names of (chained) attribute calls
265
+ func.a = func
266
+ nameof(func.a, vars_only=False) # 'func.a'
267
+
268
+ func.a.b = 1
269
+ nameof(func.a.b, vars_only=False) # 'func.a.b'
270
+ ```
271
+
247
272
  ### Detecting next immediate attribute name
248
273
 
249
274
  ```python
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "varname"
7
- version = "0.14.0"
7
+ version = "0.15.0"
8
8
  description = "Dark magics about variable names in python."
9
9
  authors = [ "pwwang <pwwang@pwwang.com>",]
10
10
  license = "MIT"
@@ -20,8 +20,9 @@ python = "^3.8"
20
20
  executing = "^2.1"
21
21
  asttokens = { version = "3.*", optional = true }
22
22
  pure_eval = { version = "0.*", optional = true }
23
+ typing_extensions = { version = "^4.13", markers = "python_version < '3.10'" }
23
24
 
24
- [tool.poetry.dev-dependencies]
25
+ [tool.poetry.group.dev.dependencies]
25
26
  pytest = "^8"
26
27
  pytest-cov = "^5"
27
28
  coverage = { version = "^7", extras = ["toml"] }
@@ -11,13 +11,14 @@ install_requires = \
11
11
  ['executing>=2.1,<3.0']
12
12
 
13
13
  extras_require = \
14
- {'all': ['asttokens==3.*', 'pure_eval==0.*']}
14
+ {':python_version < "3.10"': ['typing_extensions>=4.13,<5.0'],
15
+ 'all': ['asttokens==3.*', 'pure_eval==0.*']}
15
16
 
16
17
  setup_kwargs = {
17
18
  'name': 'varname',
18
- 'version': '0.14.0',
19
+ 'version': '0.15.0',
19
20
  'description': 'Dark magics about variable names in python.',
20
- 'long_description': '![varname][7]\n\n[![Pypi][3]][4] [![Github][5]][6] [![PythonVers][8]][4] ![Building][10]\n[![Docs and API][9]][15] [![Codacy][12]][13] [![Codacy coverage][14]][13]\n![Downloads][17]\n\nDark magics about variable names in python\n\n[CHANGELOG][16] | [API][15] | [Playground][11] | :fire: [StackOverflow answer][20]\n\n## Installation\n\n```shell\npip install -U varname\n```\n\nNote if you use `python < 3.8`, install `varname < 0.11`\n\n## Features\n\n- Core features:\n\n - Retrieving names of variables a function/class call is assigned to from inside it, using `varname`.\n - Detecting next immediate attribute name, using `will`\n - Fetching argument names/sources passed to a function using `argname`\n\n- Other helper APIs (built based on core features):\n\n - A value wrapper to store the variable name that a value is assigned to, using `Wrapper`\n - A decorator to register `__varname__` to functions/classes, using `register`\n - A helper function to create dict without explicitly specifying the key-value pairs, using `jsobj`\n - A `debug` function to print variables with their names and values\n - `exec_code` to replace `exec` where source code is available at runtime\n\n## Credits\n\nThanks goes to these awesome people/projects:\n\n<table>\n <tr>\n <td align="center" style="min-width: 75px">\n <a href="https://github.com/alexmojaki/executing">\n <img src="https://ui-avatars.com/api/?color=3333ff&background=ffffff&bold=true&name=e&size=400" width="50px;" alt=""/>\n <br /><sub><b>executing</b></sub>\n </a>\n </td>\n <td align="center" style="min-width: 75px">\n <a href="https://github.com/alexmojaki">\n <img src="https://avatars0.githubusercontent.com/u/3627481?s=400&v=4" width="50px;" alt=""/>\n <br /><sub><b>@alexmojaki</b></sub>\n </a>\n </td>\n <td align="center" style="min-width: 75px">\n <a href="https://github.com/breuleux">\n <img src="https://avatars.githubusercontent.com/u/599820?s=400&v=4" width="50px;" alt=""/>\n <br /><sub><b>@breuleux</b></sub>\n </a>\n </td>\n <td align="center" style="min-width: 75px">\n <a href="https://github.com/ElCuboNegro">\n <img src="https://avatars.githubusercontent.com/u/5524219?s=400&v=4" width="50px;" alt=""/>\n <br /><sub><b>@ElCuboNegro</b></sub>\n </a>\n </td>\n <td align="center" style="min-width: 75px">\n <a href="https://github.com/thewchan">\n <img src="https://avatars.githubusercontent.com/u/49702524?s=400&v=4" width="50px;" alt=""/>\n <br /><sub><b>@thewchan</b></sub>\n </a>\n </td>\n <td align="center" style="min-width: 75px">\n <a href="https://github.com/LawsOfSympathy">\n <img src="https://avatars.githubusercontent.com/u/96355982?s=400&v=4" width="50px;" alt=""/>\n <br /><sub><b>@LawsOfSympathy</b></sub>\n </a>\n </td>\n <td align="center" style="min-width: 75px">\n <a href="https://github.com/elliotgunton">\n <img src="https://avatars.githubusercontent.com/u/17798778?s=400&v=4" width="50px;" alt=""/>\n <br /><sub><b>@elliotgunton</b></sub>\n </a>\n </td>\n </tr>\n</table>\n\nSpecial thanks to [@HanyuuLu][2] to give up the name `varname` in pypi for this project.\n\n## Usage\n\n### Retrieving the variable names using `varname(...)`\n\n- From inside a function\n\n ```python\n from varname import varname\n def function():\n return varname()\n\n func = function() # func == \'func\'\n ```\n\n When there are intermediate frames:\n\n ```python\n def wrapped():\n return function()\n\n def function():\n # retrieve the variable name at the 2nd frame from this one\n return varname(frame=2)\n\n func = wrapped() # func == \'func\'\n ```\n\n Or use `ignore` to ignore the wrapped frame:\n\n ```python\n def wrapped():\n return function()\n\n def function():\n return varname(ignore=wrapped)\n\n func = wrapped() # func == \'func\'\n ```\n\n Calls from standard libraries are ignored by default:\n\n ```python\n import asyncio\n\n async def function():\n return varname()\n\n func = asyncio.run(function()) # func == \'func\'\n ```\n\n Use `strict` to control whether the call should be assigned to\n the variable directly:\n\n ```python\n def function(strict):\n return varname(strict=strict)\n\n func = function(True) # OK, direct assignment, func == \'func\'\n\n func = [function(True)] # Not a direct assignment, raises ImproperUseError\n func = [function(False)] # OK, func == [\'func\']\n\n func = function(False), function(False) # OK, func = (\'func\', \'func\')\n ```\n\n- Retrieving name of a class instance\n\n ```python\n class Foo:\n def __init__(self):\n self.id = varname()\n\n def copy(self):\n # also able to fetch inside a method call\n copied = Foo() # copied.id == \'copied\'\n copied.id = varname() # assign id to whatever variable name\n return copied\n\n foo = Foo() # foo.id == \'foo\'\n\n foo2 = foo.copy() # foo2.id == \'foo2\'\n ```\n\n- Multiple variables on Left-hand side\n\n ```python\n # since v0.5.4\n def func():\n return varname(multi_vars=True)\n\n a = func() # a == (\'a\',)\n a, b = func() # (a, b) == (\'a\', \'b\')\n [a, b] = func() # (a, b) == (\'a\', \'b\')\n\n # hierarchy is also possible\n a, (b, c) = func() # (a, b, c) == (\'a\', \'b\', \'c\')\n ```\n\n- Some unusual use\n\n ```python\n def function(**kwargs):\n return varname(strict=False)\n\n func = func1 = function() # func == func1 == \'func1\'\n # if varname < 0.8: func == func1 == \'func\'\n # a warning will be shown\n # since you may not want func to be \'func1\'\n\n x = function(y = function()) # x == \'x\'\n\n # get part of the name\n func_abc = function()[-3:] # func_abc == \'abc\'\n\n # function alias supported now\n function2 = function\n func = function2() # func == \'func\'\n\n a = lambda: 0\n a.b = function() # a.b == \'a.b\'\n ```\n\n### The decorator way to register `__varname__` to functions/classes\n\n- Registering `__varname__` to functions\n\n ```python\n from varname.helpers import register\n\n @register\n def function():\n return __varname__\n\n func = function() # func == \'func\'\n ```\n\n ```python\n # arguments also allowed (frame, ignore and raise_exc)\n @register(frame=2)\n def function():\n return __varname__\n\n def wrapped():\n return function()\n\n func = wrapped() # func == \'func\'\n ```\n\n- Registering `__varname__` as a class property\n\n ```python\n @register\n class Foo:\n ...\n\n foo = Foo()\n # foo.__varname__ == \'foo\'\n ```\n\n### Detecting next immediate attribute name\n\n```python\nfrom varname import will\nclass AwesomeClass:\n def __init__(self):\n self.will = None\n\n def permit(self):\n self.will = will(raise_exc=False)\n if self.will == \'do\':\n # let self handle do\n return self\n raise AttributeError(\'Should do something with AwesomeClass object\')\n\n def do(self):\n if self.will != \'do\':\n raise AttributeError("You don\'t have permission to do")\n return \'I am doing!\'\n\nawesome = AwesomeClass()\nawesome.do() # AttributeError: You don\'t have permission to do\nawesome.permit() # AttributeError: Should do something with AwesomeClass object\nawesome.permit().do() == \'I am doing!\'\n```\n\n### Fetching argument names/sources using `argname`\n\n```python\nfrom varname import argname\n\ndef func(a, b=1):\n print(argname(\'a\'))\n\nx = y = z = 2\nfunc(x) # prints: x\n\n\ndef func2(a, b=1):\n print(argname(\'a\', \'b\'))\nfunc2(y, b=x) # prints: (\'y\', \'x\')\n\n\n# allow expressions\ndef func3(a, b=1):\n print(argname(\'a\', \'b\', vars_only=False))\nfunc3(x+y, y+x) # prints: (\'x+y\', \'y+x\')\n\n\n# positional and keyword arguments\ndef func4(*args, **kwargs):\n print(argname(\'args[1]\', \'kwargs[c]\'))\nfunc4(y, x, c=z) # prints: (\'x\', \'z\')\n\n\n# As of 0.9.0 (see: https://pwwang.github.io/python-varname/CHANGELOG/#v090)\n# Can also fetch the source of the argument for\n# __getattr__/__getitem__/__setattr/__setitem__/__add__/__lt__, etc.\nclass Foo:\n def __setattr__(self, name, value):\n print(argname("name", "value", func=self.__setattr__))\n\nFoo().a = 1 # prints: ("\'a\'", \'1\')\n\n```\n\n### Value wrapper\n\n```python\nfrom varname.helpers import Wrapper\n\nfoo = Wrapper(True)\n# foo.name == \'foo\'\n# foo.value == True\nbar = Wrapper(False)\n# bar.name == \'bar\'\n# bar.value == False\n\ndef values_to_dict(*args):\n return {val.name: val.value for val in args}\n\nmydict = values_to_dict(foo, bar)\n# {\'foo\': True, \'bar\': False}\n```\n\n### Creating dictionary using `jsobj`\n\n```python\nfrom varname.helpers import jsobj\n\na = 1\nb = 2\njsobj(a, b) # {\'a\': 1, \'b\': 2}\njsobj(a, b, c=3) # {\'a\': 1, \'b\': 2, \'c\': 3}\n```\n\n### Debugging with `debug`\n\n```python\nfrom varname.helpers import debug\n\na = \'value\'\nb = [\'val\']\ndebug(a)\n# "DEBUG: a=\'value\'\\n"\ndebug(b)\n# "DEBUG: b=[\'val\']\\n"\ndebug(a, b)\n# "DEBUG: a=\'value\'\\nDEBUG: b=[\'val\']\\n"\ndebug(a, b, merge=True)\n# "DEBUG: a=\'value\', b=[\'val\']\\n"\ndebug(a, repr=False, prefix=\'\')\n# \'a=value\\n\'\n# also debug an expression\ndebug(a+a)\n# "DEBUG: a+a=\'valuevalue\'\\n"\n# If you want to disable it:\ndebug(a+a, vars_only=True) # ImproperUseError\n```\n\n### Replacing `exec` with `exec_code`\n\n```python\nfrom varname import argname\nfrom varname.helpers import exec_code\n\nclass Obj:\n def __init__(self):\n self.argnames = []\n\n def receive(self, arg):\n self.argnames.append(argname(\'arg\', func=self.receive))\n\nobj = Obj()\n# exec(\'obj.receive(1)\') # Error\nexec_code(\'obj.receive(1)\')\nexec_code(\'obj.receive(2)\')\nobj.argnames # [\'1\', \'2\']\n```\n\n## Reliability and limitations\n\n`varname` is all depending on `executing` package to look for the node.\nThe node `executing` detects is ensured to be the correct one (see [this][19]).\n\nIt partially works with environments where other AST magics apply, including [`exec`][24] function,\n[`macropy`][21], [`birdseye`][22], [`reticulate`][23] with `R`, etc. Neither\n`executing` nor `varname` is 100% working with those environments. Use\nit at your own risk.\n\nFor example:\n\n- This will not work:\n\n ```python\n from varname import argname\n\n def getname(x):\n print(argname("x"))\n\n a = 1\n exec("getname(a)") # Cannot retrieve the node where the function is called.\n\n ## instead\n # from varname.helpers import exec_code\n # exec_code("getname(a)")\n ```\n\n[1]: https://github.com/pwwang/python-varname\n[2]: https://github.com/HanyuuLu\n[3]: https://img.shields.io/pypi/v/varname?style=flat-square\n[4]: https://pypi.org/project/varname/\n[5]: https://img.shields.io/github/tag/pwwang/python-varname?style=flat-square\n[6]: https://github.com/pwwang/python-varname\n[7]: logo.png\n[8]: https://img.shields.io/pypi/pyversions/varname?style=flat-square\n[9]: https://img.shields.io/github/actions/workflow/status/pwwang/python-varname/docs.yml?branch=master\n[10]: https://img.shields.io/github/actions/workflow/status/pwwang/python-varname/build.yml?branch=master\n[11]: https://mybinder.org/v2/gh/pwwang/python-varname/dev?filepath=playground%2Fplayground.ipynb\n[12]: https://img.shields.io/codacy/grade/6fdb19c845f74c5c92056e88d44154f7?style=flat-square\n[13]: https://app.codacy.com/gh/pwwang/python-varname/dashboard\n[14]: https://img.shields.io/codacy/coverage/6fdb19c845f74c5c92056e88d44154f7?style=flat-square\n[15]: https://pwwang.github.io/python-varname/api/varname\n[16]: https://pwwang.github.io/python-varname/CHANGELOG/\n[17]: https://img.shields.io/pypi/dm/varname?style=flat-square\n[19]: https://github.com/alexmojaki/executing#is-it-reliable\n[20]: https://stackoverflow.com/a/59364138/5088165\n[21]: https://github.com/lihaoyi/macropy\n[22]: https://github.com/alexmojaki/birdseye\n[23]: https://rstudio.github.io/reticulate/\n[24]: https://docs.python.org/3/library/functions.html#exec\n',
21
+ 'long_description': '![varname][7]\n\n[![Pypi][3]][4] [![Github][5]][6] [![PythonVers][8]][4] ![Building][10]\n[![Docs and API][9]][15] [![Codacy][12]][13] [![Codacy coverage][14]][13]\n![Downloads][17]\n\nDark magics about variable names in python\n\n[CHANGELOG][16] | [API][15] | [Playground][11] | :fire: [StackOverflow answer][20]\n\n## Installation\n\n```shell\npip install -U varname\n```\n\nNote if you use `python < 3.8`, install `varname < 0.11`\n\n## Features\n\n- Core features:\n\n - Retrieving names of variables a function/class call is assigned to from inside it, using `varname`.\n - Detecting next immediate attribute name, using `will`\n - Fetching argument names/sources passed to a function using `argname`\n\n- Other helper APIs (built based on core features):\n\n - A value wrapper to store the variable name that a value is assigned to, using `Wrapper`\n - A decorator to register `__varname__` to functions/classes, using `register`\n - A helper function to create dict without explicitly specifying the key-value pairs, using `jsobj`\n - A `debug` function to print variables with their names and values\n - `exec_code` to replace `exec` where source code is available at runtime\n\n## Credits\n\nThanks goes to these awesome people/projects:\n\n<table>\n <tr>\n <td align="center" style="min-width: 75px">\n <a href="https://github.com/alexmojaki/executing">\n <img src="https://ui-avatars.com/api/?color=3333ff&background=ffffff&bold=true&name=e&size=400" width="50px;" alt=""/>\n <br /><sub><b>executing</b></sub>\n </a>\n </td>\n <td align="center" style="min-width: 75px">\n <a href="https://github.com/alexmojaki">\n <img src="https://avatars0.githubusercontent.com/u/3627481?s=400&v=4" width="50px;" alt=""/>\n <br /><sub><b>@alexmojaki</b></sub>\n </a>\n </td>\n <td align="center" style="min-width: 75px">\n <a href="https://github.com/breuleux">\n <img src="https://avatars.githubusercontent.com/u/599820?s=400&v=4" width="50px;" alt=""/>\n <br /><sub><b>@breuleux</b></sub>\n </a>\n </td>\n <td align="center" style="min-width: 75px">\n <a href="https://github.com/ElCuboNegro">\n <img src="https://avatars.githubusercontent.com/u/5524219?s=400&v=4" width="50px;" alt=""/>\n <br /><sub><b>@ElCuboNegro</b></sub>\n </a>\n </td>\n <td align="center" style="min-width: 75px">\n <a href="https://github.com/thewchan">\n <img src="https://avatars.githubusercontent.com/u/49702524?s=400&v=4" width="50px;" alt=""/>\n <br /><sub><b>@thewchan</b></sub>\n </a>\n </td>\n <td align="center" style="min-width: 75px">\n <a href="https://github.com/LawsOfSympathy">\n <img src="https://avatars.githubusercontent.com/u/96355982?s=400&v=4" width="50px;" alt=""/>\n <br /><sub><b>@LawsOfSympathy</b></sub>\n </a>\n </td>\n <td align="center" style="min-width: 75px">\n <a href="https://github.com/elliotgunton">\n <img src="https://avatars.githubusercontent.com/u/17798778?s=400&v=4" width="50px;" alt=""/>\n <br /><sub><b>@elliotgunton</b></sub>\n </a>\n </td>\n </tr>\n</table>\n\nSpecial thanks to [@HanyuuLu][2] to give up the name `varname` in pypi for this project.\n\n## Usage\n\n### Retrieving the variable names using `varname(...)`\n\n- From inside a function\n\n ```python\n from varname import varname\n def function():\n return varname()\n\n func = function() # func == \'func\'\n ```\n\n When there are intermediate frames:\n\n ```python\n def wrapped():\n return function()\n\n def function():\n # retrieve the variable name at the 2nd frame from this one\n return varname(frame=2)\n\n func = wrapped() # func == \'func\'\n ```\n\n Or use `ignore` to ignore the wrapped frame:\n\n ```python\n def wrapped():\n return function()\n\n def function():\n return varname(ignore=wrapped)\n\n func = wrapped() # func == \'func\'\n ```\n\n Calls from standard libraries are ignored by default:\n\n ```python\n import asyncio\n\n async def function():\n return varname()\n\n func = asyncio.run(function()) # func == \'func\'\n ```\n\n Use `strict` to control whether the call should be assigned to\n the variable directly:\n\n ```python\n def function(strict):\n return varname(strict=strict)\n\n func = function(True) # OK, direct assignment, func == \'func\'\n\n func = [function(True)] # Not a direct assignment, raises ImproperUseError\n func = [function(False)] # OK, func == [\'func\']\n\n func = function(False), function(False) # OK, func = (\'func\', \'func\')\n ```\n\n- Retrieving name of a class instance\n\n ```python\n class Foo:\n def __init__(self):\n self.id = varname()\n\n def copy(self):\n # also able to fetch inside a method call\n copied = Foo() # copied.id == \'copied\'\n copied.id = varname() # assign id to whatever variable name\n return copied\n\n foo = Foo() # foo.id == \'foo\'\n\n foo2 = foo.copy() # foo2.id == \'foo2\'\n ```\n\n- Multiple variables on Left-hand side\n\n ```python\n # since v0.5.4\n def func():\n return varname(multi_vars=True)\n\n a = func() # a == (\'a\',)\n a, b = func() # (a, b) == (\'a\', \'b\')\n [a, b] = func() # (a, b) == (\'a\', \'b\')\n\n # hierarchy is also possible\n a, (b, c) = func() # (a, b, c) == (\'a\', \'b\', \'c\')\n ```\n\n- Some unusual use\n\n ```python\n def function(**kwargs):\n return varname(strict=False)\n\n func = func1 = function() # func == func1 == \'func1\'\n # if varname < 0.8: func == func1 == \'func\'\n # a warning will be shown\n # since you may not want func to be \'func1\'\n\n x = function(y = function()) # x == \'x\'\n\n # get part of the name\n func_abc = function()[-3:] # func_abc == \'abc\'\n\n # function alias supported now\n function2 = function\n func = function2() # func == \'func\'\n\n a = lambda: 0\n a.b = function() # a.b == \'a.b\'\n ```\n\n### The decorator way to register `__varname__` to functions/classes\n\n- Registering `__varname__` to functions\n\n ```python\n from varname.helpers import register\n\n @register\n def function():\n return __varname__\n\n func = function() # func == \'func\'\n ```\n\n ```python\n # arguments also allowed (frame, ignore and raise_exc)\n @register(frame=2)\n def function():\n return __varname__\n\n def wrapped():\n return function()\n\n func = wrapped() # func == \'func\'\n ```\n\n- Registering `__varname__` as a class property\n\n ```python\n @register\n class Foo:\n ...\n\n foo = Foo()\n # foo.__varname__ == \'foo\'\n ```\n\n### Getting variable names directly using `nameof`\n\n```python\nfrom varname import varname, nameof\n\na = 1\nnameof(a) # \'a\'\n\nb = 2\nnameof(a, b) # (\'a\', \'b\')\n\ndef func():\n return varname() + \'_suffix\'\n\nf = func() # f == \'f_suffix\'\nnameof(f) # \'f\'\n\n# get full names of (chained) attribute calls\nfunc.a = func\nnameof(func.a, vars_only=False) # \'func.a\'\n\nfunc.a.b = 1\nnameof(func.a.b, vars_only=False) # \'func.a.b\'\n```\n\n### Detecting next immediate attribute name\n\n```python\nfrom varname import will\nclass AwesomeClass:\n def __init__(self):\n self.will = None\n\n def permit(self):\n self.will = will(raise_exc=False)\n if self.will == \'do\':\n # let self handle do\n return self\n raise AttributeError(\'Should do something with AwesomeClass object\')\n\n def do(self):\n if self.will != \'do\':\n raise AttributeError("You don\'t have permission to do")\n return \'I am doing!\'\n\nawesome = AwesomeClass()\nawesome.do() # AttributeError: You don\'t have permission to do\nawesome.permit() # AttributeError: Should do something with AwesomeClass object\nawesome.permit().do() == \'I am doing!\'\n```\n\n### Fetching argument names/sources using `argname`\n\n```python\nfrom varname import argname\n\ndef func(a, b=1):\n print(argname(\'a\'))\n\nx = y = z = 2\nfunc(x) # prints: x\n\n\ndef func2(a, b=1):\n print(argname(\'a\', \'b\'))\nfunc2(y, b=x) # prints: (\'y\', \'x\')\n\n\n# allow expressions\ndef func3(a, b=1):\n print(argname(\'a\', \'b\', vars_only=False))\nfunc3(x+y, y+x) # prints: (\'x+y\', \'y+x\')\n\n\n# positional and keyword arguments\ndef func4(*args, **kwargs):\n print(argname(\'args[1]\', \'kwargs[c]\'))\nfunc4(y, x, c=z) # prints: (\'x\', \'z\')\n\n\n# As of 0.9.0 (see: https://pwwang.github.io/python-varname/CHANGELOG/#v090)\n# Can also fetch the source of the argument for\n# __getattr__/__getitem__/__setattr/__setitem__/__add__/__lt__, etc.\nclass Foo:\n def __setattr__(self, name, value):\n print(argname("name", "value", func=self.__setattr__))\n\nFoo().a = 1 # prints: ("\'a\'", \'1\')\n\n```\n\n### Value wrapper\n\n```python\nfrom varname.helpers import Wrapper\n\nfoo = Wrapper(True)\n# foo.name == \'foo\'\n# foo.value == True\nbar = Wrapper(False)\n# bar.name == \'bar\'\n# bar.value == False\n\ndef values_to_dict(*args):\n return {val.name: val.value for val in args}\n\nmydict = values_to_dict(foo, bar)\n# {\'foo\': True, \'bar\': False}\n```\n\n### Creating dictionary using `jsobj`\n\n```python\nfrom varname.helpers import jsobj\n\na = 1\nb = 2\njsobj(a, b) # {\'a\': 1, \'b\': 2}\njsobj(a, b, c=3) # {\'a\': 1, \'b\': 2, \'c\': 3}\n```\n\n### Debugging with `debug`\n\n```python\nfrom varname.helpers import debug\n\na = \'value\'\nb = [\'val\']\ndebug(a)\n# "DEBUG: a=\'value\'\\n"\ndebug(b)\n# "DEBUG: b=[\'val\']\\n"\ndebug(a, b)\n# "DEBUG: a=\'value\'\\nDEBUG: b=[\'val\']\\n"\ndebug(a, b, merge=True)\n# "DEBUG: a=\'value\', b=[\'val\']\\n"\ndebug(a, repr=False, prefix=\'\')\n# \'a=value\\n\'\n# also debug an expression\ndebug(a+a)\n# "DEBUG: a+a=\'valuevalue\'\\n"\n# If you want to disable it:\ndebug(a+a, vars_only=True) # ImproperUseError\n```\n\n### Replacing `exec` with `exec_code`\n\n```python\nfrom varname import argname\nfrom varname.helpers import exec_code\n\nclass Obj:\n def __init__(self):\n self.argnames = []\n\n def receive(self, arg):\n self.argnames.append(argname(\'arg\', func=self.receive))\n\nobj = Obj()\n# exec(\'obj.receive(1)\') # Error\nexec_code(\'obj.receive(1)\')\nexec_code(\'obj.receive(2)\')\nobj.argnames # [\'1\', \'2\']\n```\n\n## Reliability and limitations\n\n`varname` is all depending on `executing` package to look for the node.\nThe node `executing` detects is ensured to be the correct one (see [this][19]).\n\nIt partially works with environments where other AST magics apply, including [`exec`][24] function,\n[`macropy`][21], [`birdseye`][22], [`reticulate`][23] with `R`, etc. Neither\n`executing` nor `varname` is 100% working with those environments. Use\nit at your own risk.\n\nFor example:\n\n- This will not work:\n\n ```python\n from varname import argname\n\n def getname(x):\n print(argname("x"))\n\n a = 1\n exec("getname(a)") # Cannot retrieve the node where the function is called.\n\n ## instead\n # from varname.helpers import exec_code\n # exec_code("getname(a)")\n ```\n\n[1]: https://github.com/pwwang/python-varname\n[2]: https://github.com/HanyuuLu\n[3]: https://img.shields.io/pypi/v/varname?style=flat-square\n[4]: https://pypi.org/project/varname/\n[5]: https://img.shields.io/github/tag/pwwang/python-varname?style=flat-square\n[6]: https://github.com/pwwang/python-varname\n[7]: logo.png\n[8]: https://img.shields.io/pypi/pyversions/varname?style=flat-square\n[9]: https://img.shields.io/github/actions/workflow/status/pwwang/python-varname/docs.yml?branch=master\n[10]: https://img.shields.io/github/actions/workflow/status/pwwang/python-varname/build.yml?branch=master\n[11]: https://mybinder.org/v2/gh/pwwang/python-varname/dev?filepath=playground%2Fplayground.ipynb\n[12]: https://img.shields.io/codacy/grade/6fdb19c845f74c5c92056e88d44154f7?style=flat-square\n[13]: https://app.codacy.com/gh/pwwang/python-varname/dashboard\n[14]: https://img.shields.io/codacy/coverage/6fdb19c845f74c5c92056e88d44154f7?style=flat-square\n[15]: https://pwwang.github.io/python-varname/api/varname\n[16]: https://pwwang.github.io/python-varname/CHANGELOG/\n[17]: https://img.shields.io/pypi/dm/varname?style=flat-square\n[19]: https://github.com/alexmojaki/executing#is-it-reliable\n[20]: https://stackoverflow.com/a/59364138/5088165\n[21]: https://github.com/lihaoyi/macropy\n[22]: https://github.com/alexmojaki/birdseye\n[23]: https://rstudio.github.io/reticulate/\n[24]: https://docs.python.org/3/library/functions.html#exec\n',
21
22
  'author': 'pwwang',
22
23
  'author_email': 'pwwang@pwwang.com',
23
24
  'maintainer': 'None',
@@ -11,6 +11,6 @@ from .utils import (
11
11
  MaybeDecoratedFunctionWarning,
12
12
  UsingExecWarning,
13
13
  )
14
- from .core import varname, will, argname
14
+ from .core import varname, nameof, will, argname
15
15
 
16
- __version__ = "0.14.0"
16
+ __version__ = "0.15.0"
@@ -3,11 +3,12 @@ from __future__ import annotations
3
3
  import ast
4
4
  import re
5
5
  import warnings
6
- from typing import List, Union, Tuple, Type, Callable, overload
6
+ from typing import Any, List, Union, Tuple, Type, Callable, overload
7
7
 
8
8
  from executing import Source
9
9
 
10
10
  from .utils import (
11
+ bytecode_nameof,
11
12
  get_node,
12
13
  get_node_by_frame,
13
14
  lookfor_parent_assign,
@@ -217,6 +218,129 @@ def will(frame: int = 1, raise_exc: bool = True) -> str:
217
218
  return node.attr
218
219
 
219
220
 
221
+ @overload
222
+ def nameof(
223
+ var: Any,
224
+ *,
225
+ frame: int = 1,
226
+ vars_only: bool = True,
227
+ ) -> str: # pragma: no cover
228
+ ...
229
+
230
+
231
+ @overload
232
+ def nameof(
233
+ var: Any,
234
+ more_var: Any,
235
+ /, # introduced in python 3.8
236
+ *more_vars: Any,
237
+ frame: int = 1,
238
+ vars_only: bool = True,
239
+ ) -> Tuple[str, ...]: # pragma: no cover
240
+ ...
241
+
242
+
243
+ def nameof(
244
+ var: Any,
245
+ *more_vars: Any,
246
+ frame: int = 1,
247
+ vars_only: bool = True,
248
+ ) -> Union[str, Tuple[str, ...]]:
249
+ """Get the names of the variables passed in
250
+
251
+ Examples:
252
+ >>> a = 1
253
+ >>> nameof(a) # 'a'
254
+
255
+ >>> b = 2
256
+ >>> nameof(a, b) # ('a', 'b')
257
+
258
+ >>> x = lambda: None
259
+ >>> x.y = 1
260
+ >>> nameof(x.y, vars_only=False) # 'x.y'
261
+
262
+ Note:
263
+ This function works with the environments where source code is
264
+ available, in other words, the callee's node can be retrieved by
265
+ `executing`. In some cases, for example, running code from python
266
+ shell/REPL or from `exec`/`eval`, we try to fetch the variable name
267
+ from the bytecode. This requires only a single variable name is passed
268
+ to this function and no keyword arguments, meaning that getting full
269
+ names of attribute calls are not supported in such cases.
270
+
271
+ Args:
272
+ var: The variable to retrieve the name of
273
+ *more_vars: Other variables to retrieve the names of
274
+ frame: The this function is called from the wrapper of it. `frame=1`
275
+ means no wrappers.
276
+ Note that the calls from standard libraries are ignored.
277
+ Also note that the wrapper has to have signature as this one.
278
+ vars_only: Whether only allow variables/attributes as arguments or
279
+ any expressions. If `False`, then the sources of the arguments
280
+ will be returned.
281
+
282
+ Returns:
283
+ The names/sources of variables/expressions passed in.
284
+ If a single argument is passed, return the name/source of it.
285
+ If multiple variables are passed, return a tuple of their
286
+ names/sources.
287
+ If the argument is an attribute (e.g. `a.b`) and `vars_only` is
288
+ `True`, only `"b"` will returned. Set `vars_only` to `False` to
289
+ get `"a.b"`.
290
+
291
+ Raises:
292
+ VarnameRetrievingError: When the callee's node cannot be retrieved or
293
+ trying to retrieve the full name of non attribute series calls.
294
+ """
295
+ # Frame is anyway used in get_node
296
+ frameobj = IgnoreList.create(
297
+ ignore_lambda=False,
298
+ ignore_varname=False,
299
+ ).get_frame(frame)
300
+
301
+ node = get_node_by_frame(frameobj, raise_exc=True)
302
+ if not node:
303
+ # We can't retrieve the node by executing.
304
+ # It can be due to running code from python/shell, exec/eval or
305
+ # other environments where sourcecode cannot be reached
306
+ # make sure we keep it simple (only single variable passed and no
307
+ # full passed) to use bytecode_nameof
308
+ #
309
+ # We don't have to check keyword arguments here, as the instruction
310
+ # will then be CALL_FUNCTION_KW.
311
+ if not more_vars:
312
+ return bytecode_nameof(frameobj.f_code, frameobj.f_lasti)
313
+
314
+ # We are anyway raising exceptions, no worries about additional burden
315
+ # of frame retrieval again
316
+ source = frameobj.f_code.co_filename
317
+ if source == "<stdin>":
318
+ raise VarnameRetrievingError(
319
+ "Are you trying to call nameof in REPL/python shell? "
320
+ "In such a case, nameof can only be called with single "
321
+ "argument and no keyword arguments."
322
+ )
323
+ if source == "<string>":
324
+ raise VarnameRetrievingError(
325
+ "Are you trying to call nameof from exec/eval? "
326
+ "In such a case, nameof can only be called with single "
327
+ "argument and no keyword arguments."
328
+ )
329
+ raise VarnameRetrievingError(
330
+ "Source code unavailable, nameof can only retrieve the name of "
331
+ "a single variable, and argument `full` should not be specified."
332
+ )
333
+
334
+ out = argname(
335
+ "var",
336
+ "*more_vars",
337
+ func=nameof,
338
+ frame=frame,
339
+ vars_only=vars_only,
340
+ )
341
+ return out if more_vars else out[0] # type: ignore
342
+
343
+
220
344
  @overload
221
345
  def argname(
222
346
  arg: str,
@@ -10,6 +10,7 @@ Attributes:
10
10
  `inspect.getmodule(frame)`
11
11
  """
12
12
  import sys
13
+ import dis
13
14
  import ast
14
15
  import warnings
15
16
  import inspect
@@ -17,7 +18,12 @@ from os import path
17
18
  from pathlib import Path
18
19
  from functools import lru_cache, singledispatch
19
20
  from types import ModuleType, FunctionType, CodeType, FrameType
20
- from typing import Tuple, Union, List, Mapping, Callable
21
+ from typing import Tuple, Union, List, Mapping, Callable, Dict
22
+
23
+ if sys.version_info < (3, 10):
24
+ from typing_extensions import TypeAlias # pragma: no cover
25
+ else:
26
+ from typing import TypeAlias
21
27
 
22
28
  from executing import Source
23
29
 
@@ -62,16 +68,16 @@ IgnoreElemType = Union[
62
68
  ]
63
69
  IgnoreType = Union[IgnoreElemType, List[IgnoreElemType]]
64
70
 
65
- ArgSourceType = Union[ast.AST, str]
66
- ArgSourceType = Union[ArgSourceType, Tuple[ArgSourceType, ...]]
67
- ArgSourceType = Union[ArgSourceType, Mapping[str, ArgSourceType]]
71
+ ArgSourceType: TypeAlias = Union[ast.AST, str]
72
+ ArgSourceType: TypeAlias = Union[ArgSourceType, Tuple[ArgSourceType, ...]]
73
+ ArgSourceType: TypeAlias = Union[ArgSourceType, Mapping[str, ArgSourceType]]
68
74
 
69
75
  if sys.version_info >= (3, 8):
70
76
  ASSIGN_TYPES = (ast.Assign, ast.AnnAssign, ast.NamedExpr)
71
- AssignType = Union[ASSIGN_TYPES] # type: ignore
72
- else: # pragma: no cover
77
+ AssignType: TypeAlias = Union[ASSIGN_TYPES] # type: ignore
78
+ else: # pragma: no cover # Python < 3.8
73
79
  ASSIGN_TYPES = (ast.Assign, ast.AnnAssign)
74
- AssignType = Union[ASSIGN_TYPES] # type: ignore
80
+ AssignType: TypeAlias = Union[ASSIGN_TYPES] # type: ignore
75
81
 
76
82
  PY311 = sys.version_info >= (3, 11)
77
83
  MODULE_IGNORE_ID_NAME = "__varname_ignore_id__"
@@ -166,7 +172,7 @@ def get_node_by_frame(frame: FrameType, raise_exc: bool = True) -> ast.AST:
166
172
  raise VarnameRetrievingError(
167
173
  "Couldn't retrieve the call node. "
168
174
  "This may happen if you're using some other AST magic at the "
169
- "same time, such as pytest, macropy, or birdseye."
175
+ "same time, such as pytest, ipython, macropy, or birdseye."
170
176
  )
171
177
 
172
178
  return None
@@ -243,6 +249,71 @@ def node_name(
243
249
  )
244
250
 
245
251
 
252
+ @lru_cache()
253
+ def bytecode_nameof(code: CodeType, offset: int) -> str:
254
+ """Cached Bytecode version of nameof
255
+
256
+ We are trying this version only when the sourcecode is unavisible. In most
257
+ cases, this will happen when user is trying to run a script in REPL/
258
+ python shell, with `eval`, or other circumstances where the code is
259
+ manipulated to run but sourcecode is not available.
260
+ """
261
+ kwargs: Dict[str, bool] = (
262
+ {"show_caches": True} if sys.version_info[:2] >= (3, 11) else {}
263
+ )
264
+
265
+ instructions = list(dis.get_instructions(code, **kwargs))
266
+ ((current_instruction_index, current_instruction),) = (
267
+ (index, instruction)
268
+ for index, instruction in enumerate(instructions)
269
+ if instruction.offset == offset
270
+ )
271
+
272
+ while current_instruction.opname == "CACHE": # pragma: no cover
273
+ current_instruction_index -= 1
274
+ current_instruction = instructions[current_instruction_index]
275
+
276
+ pos_only_error = VarnameRetrievingError(
277
+ "'nameof' can only be called with a single positional argument "
278
+ "when source code is not avaiable."
279
+ )
280
+ if current_instruction.opname in ( # pragma: no cover
281
+ "CALL_FUNCTION_EX",
282
+ "CALL_FUNCTION_KW",
283
+ ):
284
+ raise pos_only_error
285
+
286
+ if current_instruction.opname not in (
287
+ "CALL_FUNCTION",
288
+ "CALL_METHOD",
289
+ "CALL",
290
+ "CALL_KW",
291
+ ):
292
+ raise VarnameRetrievingError("Did you call 'nameof' in a weird way?")
293
+
294
+ current_instruction_index -= 1
295
+ name_instruction = instructions[current_instruction_index]
296
+ while name_instruction.opname in ("CACHE", "PRECALL"): # pragma: no cover
297
+ current_instruction_index -= 1
298
+ name_instruction = instructions[current_instruction_index]
299
+
300
+ if name_instruction.opname in ("KW_NAMES", "LOAD_CONST"): # LOAD_CONST python 3.13
301
+ raise pos_only_error
302
+
303
+ if not name_instruction.opname.startswith("LOAD_"):
304
+ raise VarnameRetrievingError("Argument must be a variable or attribute")
305
+
306
+ name = name_instruction.argrepr
307
+ if not name.isidentifier():
308
+ raise VarnameRetrievingError(
309
+ f"Found the variable name {name!r} which is obviously wrong. "
310
+ "This may happen if you're using some other AST magic at the "
311
+ "same time, such as pytest, ipython, macropy, or birdseye."
312
+ )
313
+
314
+ return name
315
+
316
+
246
317
  def attach_ignore_id_to_module(module: ModuleType) -> None:
247
318
  """Attach the ignore id to module
248
319
 
File without changes
File without changes
File without changes
File without changes