Dark magics about variable names in python
Overall
score
90%
Advanced varname functions for attribute detection and argument inspection. These functions provide more specialized capabilities for complex runtime introspection scenarios.
Detects the attribute name that will be accessed immediately after a function call returns, enabling dynamic attribute resolution patterns.
def will(frame: int = 1, raise_exc: bool = True) -> Union[str, None]:
"""
Detect the attribute name right immediately after a function call.
Args:
frame: At which frame this function is called. frame=1 means
the immediate upper frame. frame=0 means the current frame.
raise_exc: Raise exception if failed to detect the ast node.
If False, return None when detection fails.
Returns:
The attribute name right after the function call as string.
None if detection fails and raise_exc=False.
Raises:
VarnameRetrievingError: When unable to retrieve ast node and raise_exc=True
ImproperUseError: When will() is not used immediately before an attribute access
Note:
Must be used in the context where the returned value will have an
attribute accessed immediately, like: obj.method().some_attr
"""from varname import will
# Basic attribute detection
class DynamicObject:
def get_data(self):
attr_name = will()
return f"You requested: {attr_name}"
obj = DynamicObject()
result = obj.get_data().username # result == "You requested: username"
# With error handling
class SafeObject:
def query(self):
attr_name = will(raise_exc=False)
if attr_name:
return f"Accessing: {attr_name}"
return "Direct call"
safe_obj = SafeObject()
result1 = safe_obj.query().data # result1 == "Accessing: data"
result2 = safe_obj.query() # result2 == "Direct call"
# Dynamic method routing
class Router:
def route(self):
endpoint = will()
return f"Routing to: {endpoint}"
def __getattr__(self, name):
return f"Handler for {name}"
router = Router()
handler = router.route().api_endpoint # handler == "Routing to: api_endpoint"Retrieves the names/sources of arguments passed to a function, enabling introspection of how functions are called and with what variable names.
def argname(
arg: str,
*more_args: str,
func: Optional[Callable] = None,
dispatch: Optional[Type] = None,
frame: int = 1,
ignore: Optional[IgnoreType] = None,
vars_only: bool = True
) -> Union[ArgSourceType, Tuple[ArgSourceType, ...]]:
"""
Get the names/sources of arguments passed to a function.
Args:
arg: Name of the argument to retrieve name/source of. Use special names:
- '*args' for positional arguments tuple
- 'kwargs' for keyword arguments dict
- '**kwargs' for keyword arguments dict (same as 'kwargs')
*more_args: Names of other arguments to retrieve names/sources of
func: Target function to inspect arguments for. If None, automatically
detects the function that called argname().
dispatch: Type for the dispatched function in single-dispatch scenarios.
Only used when auto-detecting function fails.
frame: Frame where target function is called. frame=1 means the immediate
upper frame where the target function is called.
ignore: Intermediate calls to be ignored to reach the target frame.
Similar to varname's ignore parameter.
vars_only: Whether to require arguments to be variables only or allow
any expressions. If False, returns source expressions.
Returns:
Source/name of argument if single argument requested.
Tuple of sources/names if multiple arguments requested.
For '*args': tuple of positional argument names
For 'kwargs'/'**kwargs': dict mapping parameter names to argument names
Raises:
VarnameRetrievingError: When unable to retrieve function call or arguments
ImproperUseError: When argname is not called from inside a function that
can be analyzed or when specified arguments don't exist
Note:
Works by analyzing the AST of the function call. In environments where
source code is not available (REPL, exec), uses exec with temporary
files to enable source analysis.
"""from varname import argname
# Basic argument name retrieval
def process_data(data, options=None):
data_name = argname('data')
opts_name = argname('options')
print(f"Processing {data_name} with {opts_name}")
return f"Processed {data_name}"
dataset = [1, 2, 3]
config = {'mode': 'fast'}
result = process_data(dataset, options=config)
# Prints: Processing dataset with config
# Returns: "Processed dataset"
# Multiple arguments
def analyze(*args, **kwargs):
arg_names = argname('*args', 'kwargs')
print(f"Args: {arg_names[0]}")
print(f"Kwargs: {arg_names[1]}")
x, y = 10, 20
analyze(x, y, method='linear', debug=True)
# Prints: Args: ('x', 'y')
# Prints: Kwargs: {'method': 'method', 'debug': 'debug'}
# With function specification
def wrapper(func, *args, **kwargs):
# Get argument names for the wrapped function
arg_sources = argname('*args', func=func, frame=2)
print(f"Calling {func.__name__} with: {arg_sources}")
return func(*args, **kwargs)
def compute(a, b):
return a + b
p, q = 5, 3
result = wrapper(compute, p, q)
# Prints: Calling compute with: ('p', 'q')
# With ignore parameter for decorators
def logged(func):
def decorator(*args, **kwargs):
# Skip the decorator frame
arg_names = argname('*args', ignore=logged)
print(f"Logged call with args: {arg_names}")
return func(*args, **kwargs)
return decorator
@logged
def calculate(value1, value2):
return value1 * value2
num1, num2 = 4, 7
result = calculate(num1, num2)
# Prints: Logged call with args: ('num1', 'num2')The vars_only parameter controls whether to return just variable names or full expressions:
from varname import argname
def analyze_call(data):
var_name = argname('data', vars_only=True) # Variable name only
full_expr = argname('data', vars_only=False) # Full expression
return var_name, full_expr
# With simple variable
items = [1, 2, 3]
var_result, expr_result = analyze_call(items)
# var_result == 'items', expr_result == 'items'
# With attribute access
class Container:
def __init__(self):
self.data = [4, 5, 6]
container = Container()
var_result, expr_result = analyze_call(container.data)
# var_result == 'data', expr_result == 'container.data'
# With method call
def get_items():
return [7, 8, 9]
var_result, expr_result = analyze_call(get_items())
# var_result raises VarnameRetrievingError (not a variable)
# expr_result == 'get_items()'from varname import will, argname
class APIBuilder:
def __init__(self):
self.endpoints = {}
def endpoint(self):
# Detect what endpoint is being accessed
name = will()
return EndpointBuilder(name, self)
class EndpointBuilder:
def __init__(self, name, api):
self.name = name
self.api = api
def handler(self, func):
# Get the function and arguments info
func_name = argname('func')
self.api.endpoints[self.name] = {
'handler': func,
'source_name': func_name
}
return func
# Usage
api = APIBuilder()
def user_handler():
return "User data"
api.endpoint().users.handler(user_handler)
# Automatically registers 'users' endpoint with user_handlerfrom varname import argname, will
def smart_assert(condition, message="Assertion failed"):
if not condition:
# Get the condition expression
cond_expr = argname('condition', vars_only=False)
raise AssertionError(f"{message}: {cond_expr}")
def conditional_processor():
next_action = will(raise_exc=False)
if next_action:
return f"Will perform: {next_action}"
return "Direct processing"
# Usage
x = 5
smart_assert(x > 10) # AssertionError: Assertion failed: x > 10
processor = conditional_processor()
result = processor.validate # result == "Will perform: validate"Advanced frame filtering system for precise control over which frames are skipped during variable name retrieval.
class IgnoreList:
"""A list of ignore elements for frame filtering."""
@classmethod
def create(cls, ignore: IgnoreType) -> "IgnoreList":
"""Create ignore list from ignore specification."""
def get_frame(self, frame: int) -> FrameType:
"""Get the target frame after applying ignore rules."""
class IgnoreElem:
"""Abstract base class for ignore elements."""
def match(self, frame: FrameType) -> bool:
"""Check if frame matches this ignore element."""# Module-based ignoring
class IgnoreModule(IgnoreElem):
"""Ignore calls from a module or its submodules."""
class IgnoreFilename(IgnoreElem):
"""Ignore calls from a module by matching filename."""
class IgnoreDirname(IgnoreElem):
"""Ignore calls from modules inside a directory."""
class IgnoreStdlib(IgnoreElem):
"""Ignore standard library calls."""
# Function-based ignoring
class IgnoreFunction(IgnoreElem):
"""Ignore a non-decorated function."""
class IgnoreDecorated(IgnoreElem):
"""Ignore a decorated function with decorator count."""
# Qualified name ignoring
class IgnoreModuleQualname(IgnoreElem):
"""Ignore by module and qualified name."""
class IgnoreFilenameQualname(IgnoreElem):
"""Ignore by filename and qualified name."""
class IgnoreOnlyQualname(IgnoreElem):
"""Ignore by qualified name only."""from varname import varname
from varname.ignore import IgnoreModule, IgnoreFunction
# Custom ignore list for complex scenarios
def complex_wrapper():
# Ignore specific modules and functions
ignore_list = [
IgnoreModule("decorator_library"),
IgnoreFunction(some_wrapper_function),
("mymodule", "MyClass.method") # Module + qualname
]
return varname(ignore=ignore_list)
# The ignore system automatically handles standard patterns
result = complex_wrapper()Note: These classes are primarily used internally by varname's ignore system. Most users should use the simpler ignore specifications supported by the main functions (modules, functions, tuples) rather than constructing these classes directly.
Install with Tessl CLI
npx tessl i tessl/pypi-varnameevals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10