varname 0.14.0__py3-none-any.whl → 0.15.0__py3-none-any.whl
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.
- varname/__init__.py +2 -2
- varname/core.py +125 -1
- varname/utils.py +79 -8
- {varname-0.14.0.dist-info → varname-0.15.0.dist-info}/METADATA +27 -1
- varname-0.15.0.dist-info/RECORD +10 -0
- {varname-0.14.0.dist-info → varname-0.15.0.dist-info}/WHEEL +1 -1
- varname-0.14.0.dist-info/RECORD +0 -10
- {varname-0.14.0.dist-info → varname-0.15.0.dist-info}/LICENSE +0 -0
varname/__init__.py
CHANGED
varname/core.py
CHANGED
@@ -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,
|
varname/utils.py
CHANGED
@@ -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
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: varname
|
3
|
-
Version: 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
|
@@ -0,0 +1,10 @@
|
|
1
|
+
varname/__init__.py,sha256=msPiS0pHXpErWWjXSxkVWqjfjLHHX8zcVgWRBwfJlT4,369
|
2
|
+
varname/core.py,sha256=iuipHp0kavT5gR6VEYGDtpOdf23p_aYsll04f7woL0M,19346
|
3
|
+
varname/helpers.py,sha256=jZaP-qWQJwi8T2f886eHOj-llXAPHk5SMU6PfVn-9dg,9558
|
4
|
+
varname/ignore.py,sha256=-VC3oqag44y2UlAw0ErYKHotx616qJL3Sjb_5y7Tw1c,14400
|
5
|
+
varname/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
6
|
+
varname/utils.py,sha256=3Z0bEGbrKOWXsfRO5GeJBqqKNOprK-jO2MpVqYwgnIE,23188
|
7
|
+
varname-0.15.0.dist-info/LICENSE,sha256=3bS8O2tMbBPz8rWmZBAOzkHQjcK-b7KwFHyyghEZ-Ak,1063
|
8
|
+
varname-0.15.0.dist-info/METADATA,sha256=Z70KEyKxGrsdqNx83lg-9f8dOgFaMZpgTXtwO9rgp3U,13236
|
9
|
+
varname-0.15.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
10
|
+
varname-0.15.0.dist-info/RECORD,,
|
varname-0.14.0.dist-info/RECORD
DELETED
@@ -1,10 +0,0 @@
|
|
1
|
-
varname/__init__.py,sha256=J1-VOils3vUZH4QxbbtCH1likQee7vyTo_ocYxTz-kQ,361
|
2
|
-
varname/core.py,sha256=hFDoefsMc4TWBEzM-oMfzuCVFpmSv7iE82-m7cBv5s0,15027
|
3
|
-
varname/helpers.py,sha256=jZaP-qWQJwi8T2f886eHOj-llXAPHk5SMU6PfVn-9dg,9558
|
4
|
-
varname/ignore.py,sha256=-VC3oqag44y2UlAw0ErYKHotx616qJL3Sjb_5y7Tw1c,14400
|
5
|
-
varname/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
6
|
-
varname/utils.py,sha256=l1eIuZhXrrLgS1wM3MbVh10LQChLPOkfHz9F67fQnoY,20580
|
7
|
-
varname-0.14.0.dist-info/LICENSE,sha256=3bS8O2tMbBPz8rWmZBAOzkHQjcK-b7KwFHyyghEZ-Ak,1063
|
8
|
-
varname-0.14.0.dist-info/METADATA,sha256=jmqGTlr-MZDjGG47OQzeoWOuLVgILhmPzhoaqTZkUQs,12746
|
9
|
-
varname-0.14.0.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
10
|
-
varname-0.14.0.dist-info/RECORD,,
|
File without changes
|