0
# Advanced Functions
1
2
Advanced varname functions for attribute detection and argument inspection. These functions provide more specialized capabilities for complex runtime introspection scenarios.
3
4
## Capabilities
5
6
### Attribute Access Detection
7
8
Detects the attribute name that will be accessed immediately after a function call returns, enabling dynamic attribute resolution patterns.
9
10
```python { .api }
11
def will(frame: int = 1, raise_exc: bool = True) -> Union[str, None]:
12
"""
13
Detect the attribute name right immediately after a function call.
14
15
Args:
16
frame: At which frame this function is called. frame=1 means
17
the immediate upper frame. frame=0 means the current frame.
18
raise_exc: Raise exception if failed to detect the ast node.
19
If False, return None when detection fails.
20
21
Returns:
22
The attribute name right after the function call as string.
23
None if detection fails and raise_exc=False.
24
25
Raises:
26
VarnameRetrievingError: When unable to retrieve ast node and raise_exc=True
27
ImproperUseError: When will() is not used immediately before an attribute access
28
29
Note:
30
Must be used in the context where the returned value will have an
31
attribute accessed immediately, like: obj.method().some_attr
32
"""
33
```
34
35
#### Usage Examples
36
37
```python
38
from varname import will
39
40
# Basic attribute detection
41
class DynamicObject:
42
def get_data(self):
43
attr_name = will()
44
return f"You requested: {attr_name}"
45
46
obj = DynamicObject()
47
result = obj.get_data().username # result == "You requested: username"
48
49
# With error handling
50
class SafeObject:
51
def query(self):
52
attr_name = will(raise_exc=False)
53
if attr_name:
54
return f"Accessing: {attr_name}"
55
return "Direct call"
56
57
safe_obj = SafeObject()
58
result1 = safe_obj.query().data # result1 == "Accessing: data"
59
result2 = safe_obj.query() # result2 == "Direct call"
60
61
# Dynamic method routing
62
class Router:
63
def route(self):
64
endpoint = will()
65
return f"Routing to: {endpoint}"
66
67
def __getattr__(self, name):
68
return f"Handler for {name}"
69
70
router = Router()
71
handler = router.route().api_endpoint # handler == "Routing to: api_endpoint"
72
```
73
74
### Function Argument Inspection
75
76
Retrieves the names/sources of arguments passed to a function, enabling introspection of how functions are called and with what variable names.
77
78
```python { .api }
79
def argname(
80
arg: str,
81
*more_args: str,
82
func: Optional[Callable] = None,
83
dispatch: Optional[Type] = None,
84
frame: int = 1,
85
ignore: Optional[IgnoreType] = None,
86
vars_only: bool = True
87
) -> Union[ArgSourceType, Tuple[ArgSourceType, ...]]:
88
"""
89
Get the names/sources of arguments passed to a function.
90
91
Args:
92
arg: Name of the argument to retrieve name/source of. Use special names:
93
- '*args' for positional arguments tuple
94
- 'kwargs' for keyword arguments dict
95
- '**kwargs' for keyword arguments dict (same as 'kwargs')
96
*more_args: Names of other arguments to retrieve names/sources of
97
func: Target function to inspect arguments for. If None, automatically
98
detects the function that called argname().
99
dispatch: Type for the dispatched function in single-dispatch scenarios.
100
Only used when auto-detecting function fails.
101
frame: Frame where target function is called. frame=1 means the immediate
102
upper frame where the target function is called.
103
ignore: Intermediate calls to be ignored to reach the target frame.
104
Similar to varname's ignore parameter.
105
vars_only: Whether to require arguments to be variables only or allow
106
any expressions. If False, returns source expressions.
107
108
Returns:
109
Source/name of argument if single argument requested.
110
Tuple of sources/names if multiple arguments requested.
111
For '*args': tuple of positional argument names
112
For 'kwargs'/'**kwargs': dict mapping parameter names to argument names
113
114
Raises:
115
VarnameRetrievingError: When unable to retrieve function call or arguments
116
ImproperUseError: When argname is not called from inside a function that
117
can be analyzed or when specified arguments don't exist
118
119
Note:
120
Works by analyzing the AST of the function call. In environments where
121
source code is not available (REPL, exec), uses exec with temporary
122
files to enable source analysis.
123
"""
124
```
125
126
#### Usage Examples
127
128
```python
129
from varname import argname
130
131
# Basic argument name retrieval
132
def process_data(data, options=None):
133
data_name = argname('data')
134
opts_name = argname('options')
135
print(f"Processing {data_name} with {opts_name}")
136
return f"Processed {data_name}"
137
138
dataset = [1, 2, 3]
139
config = {'mode': 'fast'}
140
result = process_data(dataset, options=config)
141
# Prints: Processing dataset with config
142
# Returns: "Processed dataset"
143
144
# Multiple arguments
145
def analyze(*args, **kwargs):
146
arg_names = argname('*args', 'kwargs')
147
print(f"Args: {arg_names[0]}")
148
print(f"Kwargs: {arg_names[1]}")
149
150
x, y = 10, 20
151
analyze(x, y, method='linear', debug=True)
152
# Prints: Args: ('x', 'y')
153
# Prints: Kwargs: {'method': 'method', 'debug': 'debug'}
154
155
# With function specification
156
def wrapper(func, *args, **kwargs):
157
# Get argument names for the wrapped function
158
arg_sources = argname('*args', func=func, frame=2)
159
print(f"Calling {func.__name__} with: {arg_sources}")
160
return func(*args, **kwargs)
161
162
def compute(a, b):
163
return a + b
164
165
p, q = 5, 3
166
result = wrapper(compute, p, q)
167
# Prints: Calling compute with: ('p', 'q')
168
169
# With ignore parameter for decorators
170
def logged(func):
171
def decorator(*args, **kwargs):
172
# Skip the decorator frame
173
arg_names = argname('*args', ignore=logged)
174
print(f"Logged call with args: {arg_names}")
175
return func(*args, **kwargs)
176
return decorator
177
178
@logged
179
def calculate(value1, value2):
180
return value1 * value2
181
182
num1, num2 = 4, 7
183
result = calculate(num1, num2)
184
# Prints: Logged call with args: ('num1', 'num2')
185
```
186
187
### Expression vs Variable Names
188
189
The `vars_only` parameter controls whether to return just variable names or full expressions:
190
191
```python
192
from varname import argname
193
194
def analyze_call(data):
195
var_name = argname('data', vars_only=True) # Variable name only
196
full_expr = argname('data', vars_only=False) # Full expression
197
return var_name, full_expr
198
199
# With simple variable
200
items = [1, 2, 3]
201
var_result, expr_result = analyze_call(items)
202
# var_result == 'items', expr_result == 'items'
203
204
# With attribute access
205
class Container:
206
def __init__(self):
207
self.data = [4, 5, 6]
208
209
container = Container()
210
var_result, expr_result = analyze_call(container.data)
211
# var_result == 'data', expr_result == 'container.data'
212
213
# With method call
214
def get_items():
215
return [7, 8, 9]
216
217
var_result, expr_result = analyze_call(get_items())
218
# var_result raises VarnameRetrievingError (not a variable)
219
# expr_result == 'get_items()'
220
```
221
222
## Advanced Use Cases
223
224
### Dynamic API Discovery
225
226
```python
227
from varname import will, argname
228
229
class APIBuilder:
230
def __init__(self):
231
self.endpoints = {}
232
233
def endpoint(self):
234
# Detect what endpoint is being accessed
235
name = will()
236
return EndpointBuilder(name, self)
237
238
class EndpointBuilder:
239
def __init__(self, name, api):
240
self.name = name
241
self.api = api
242
243
def handler(self, func):
244
# Get the function and arguments info
245
func_name = argname('func')
246
self.api.endpoints[self.name] = {
247
'handler': func,
248
'source_name': func_name
249
}
250
return func
251
252
# Usage
253
api = APIBuilder()
254
255
def user_handler():
256
return "User data"
257
258
api.endpoint().users.handler(user_handler)
259
# Automatically registers 'users' endpoint with user_handler
260
```
261
262
### Smart Debugging with Context
263
264
```python
265
from varname import argname, will
266
267
def smart_assert(condition, message="Assertion failed"):
268
if not condition:
269
# Get the condition expression
270
cond_expr = argname('condition', vars_only=False)
271
raise AssertionError(f"{message}: {cond_expr}")
272
273
def conditional_processor():
274
next_action = will(raise_exc=False)
275
if next_action:
276
return f"Will perform: {next_action}"
277
return "Direct processing"
278
279
# Usage
280
x = 5
281
smart_assert(x > 10) # AssertionError: Assertion failed: x > 10
282
283
processor = conditional_processor()
284
result = processor.validate # result == "Will perform: validate"
285
```
286
287
## Ignore System Classes
288
289
Advanced frame filtering system for precise control over which frames are skipped during variable name retrieval.
290
291
### Core Ignore Classes
292
293
```python { .api }
294
class IgnoreList:
295
"""A list of ignore elements for frame filtering."""
296
297
@classmethod
298
def create(cls, ignore: IgnoreType) -> "IgnoreList":
299
"""Create ignore list from ignore specification."""
300
301
def get_frame(self, frame: int) -> FrameType:
302
"""Get the target frame after applying ignore rules."""
303
304
class IgnoreElem:
305
"""Abstract base class for ignore elements."""
306
307
def match(self, frame: FrameType) -> bool:
308
"""Check if frame matches this ignore element."""
309
```
310
311
### Ignore Element Types
312
313
```python { .api }
314
# Module-based ignoring
315
class IgnoreModule(IgnoreElem):
316
"""Ignore calls from a module or its submodules."""
317
318
class IgnoreFilename(IgnoreElem):
319
"""Ignore calls from a module by matching filename."""
320
321
class IgnoreDirname(IgnoreElem):
322
"""Ignore calls from modules inside a directory."""
323
324
class IgnoreStdlib(IgnoreElem):
325
"""Ignore standard library calls."""
326
327
# Function-based ignoring
328
class IgnoreFunction(IgnoreElem):
329
"""Ignore a non-decorated function."""
330
331
class IgnoreDecorated(IgnoreElem):
332
"""Ignore a decorated function with decorator count."""
333
334
# Qualified name ignoring
335
class IgnoreModuleQualname(IgnoreElem):
336
"""Ignore by module and qualified name."""
337
338
class IgnoreFilenameQualname(IgnoreElem):
339
"""Ignore by filename and qualified name."""
340
341
class IgnoreOnlyQualname(IgnoreElem):
342
"""Ignore by qualified name only."""
343
```
344
345
#### Usage Examples
346
347
```python
348
from varname import varname
349
from varname.ignore import IgnoreModule, IgnoreFunction
350
351
# Custom ignore list for complex scenarios
352
def complex_wrapper():
353
# Ignore specific modules and functions
354
ignore_list = [
355
IgnoreModule("decorator_library"),
356
IgnoreFunction(some_wrapper_function),
357
("mymodule", "MyClass.method") # Module + qualname
358
]
359
return varname(ignore=ignore_list)
360
361
# The ignore system automatically handles standard patterns
362
result = complex_wrapper()
363
```
364
365
**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.