0
# Function Wrappers
1
2
Specialized wrappers for functions that handle method binding, descriptor protocol, and function-specific behavior with proper signature preservation. These wrappers are designed specifically for wrapping functions and methods while maintaining their introspection capabilities and binding behavior.
3
4
## Capabilities
5
6
### FunctionWrapper
7
8
A specialized wrapper for functions that properly handles method binding, the descriptor protocol, and function-specific behavior. This is the core wrapper used by the `@decorator` function and provides the foundation for creating robust function decorators.
9
10
```python { .api }
11
class FunctionWrapper(ObjectProxy):
12
def __init__(self, wrapped, wrapper, enabled=None):
13
"""
14
Create a function wrapper with proper binding support.
15
16
Args:
17
wrapped: The function to wrap
18
wrapper: Wrapper function with signature (wrapped, instance, args, kwargs) -> result
19
enabled: Boolean or callable to enable/disable wrapper (optional)
20
"""
21
22
@property
23
def _self_wrapper(self):
24
"""The wrapper function."""
25
26
@property
27
def _self_enabled(self):
28
"""Enable/disable flag or callable."""
29
30
@property
31
def _self_instance(self):
32
"""Instance the function is bound to (if any)."""
33
34
@property
35
def _self_binding(self):
36
"""Type of binding ('function', 'method', 'classmethod', etc.)."""
37
38
@property
39
def _self_parent(self):
40
"""Parent wrapper (for bound functions)."""
41
42
def __get__(self, instance, owner):
43
"""
44
Descriptor protocol for method binding.
45
46
Args:
47
instance: Instance object (None for class access)
48
owner: Owner class
49
50
Returns:
51
BoundFunctionWrapper or self
52
"""
53
54
def __set_name__(self, owner, name):
55
"""
56
Support for descriptor naming (Python 3.6+).
57
58
Args:
59
owner: Owner class
60
name: Attribute name
61
"""
62
63
def __call__(self, *args, **kwargs):
64
"""
65
Call the wrapped function through the wrapper.
66
67
Args:
68
*args: Positional arguments
69
**kwargs: Keyword arguments
70
71
Returns:
72
Result from wrapper function
73
"""
74
75
def __instancecheck__(self, instance):
76
"""Support for isinstance() checks."""
77
78
def __subclasscheck__(self, subclass):
79
"""Support for issubclass() checks."""
80
```
81
82
**Usage Example:**
83
84
```python
85
import wrapt
86
87
def logging_wrapper(wrapped, instance, args, kwargs):
88
print(f"Calling {wrapped.__name__} with args={args}, kwargs={kwargs}")
89
if instance is not None:
90
print(f"Instance: {instance}")
91
result = wrapped(*args, **kwargs)
92
print(f"Result: {result}")
93
return result
94
95
# Wrap a function
96
def add(a, b):
97
return a + b
98
99
wrapped_add = wrapt.FunctionWrapper(add, logging_wrapper)
100
result = wrapped_add(2, 3) # Logs call details
101
102
# Wrap a method
103
class Calculator:
104
def multiply(self, a, b):
105
return a * b
106
107
Calculator.multiply = wrapt.FunctionWrapper(Calculator.multiply, logging_wrapper)
108
calc = Calculator()
109
result = calc.multiply(4, 5) # Logs with instance information
110
```
111
112
### BoundFunctionWrapper
113
114
Created automatically when a FunctionWrapper is accessed as a method. Handles the complexities of method calls, instance binding, and argument handling. You typically don't create these directly.
115
116
```python { .api }
117
class BoundFunctionWrapper:
118
"""
119
Wrapper for bound functions (methods). Created automatically by
120
FunctionWrapper.__get__() when accessing wrapped methods on instances.
121
"""
122
123
def __call__(self, *args, **kwargs):
124
"""
125
Call the bound method with proper instance binding.
126
127
Args:
128
*args: Positional arguments
129
**kwargs: Keyword arguments
130
131
Returns:
132
Result from wrapper function
133
"""
134
```
135
136
**Example of automatic binding:**
137
138
```python
139
import wrapt
140
141
def method_wrapper(wrapped, instance, args, kwargs):
142
print(f"Method {wrapped.__name__} called on {type(instance).__name__}")
143
return wrapped(*args, **kwargs)
144
145
class MyClass:
146
def __init__(self, name):
147
self.name = name
148
149
def greet(self, message):
150
return f"{self.name}: {message}"
151
152
# Wrap the method
153
MyClass.greet = wrapt.FunctionWrapper(MyClass.greet, method_wrapper)
154
155
# Create instance and call method
156
obj = MyClass("Alice")
157
result = obj.greet("Hello!") # Creates BoundFunctionWrapper automatically
158
# Prints: "Method greet called on MyClass"
159
# Returns: "Alice: Hello!"
160
```
161
162
## Advanced Usage
163
164
### Conditional Wrappers
165
166
Use the `enabled` parameter to conditionally enable/disable wrapping:
167
168
```python
169
import wrapt
170
import os
171
172
def debug_wrapper(wrapped, instance, args, kwargs):
173
print(f"DEBUG: Calling {wrapped.__name__}")
174
return wrapped(*args, **kwargs)
175
176
# Enable only when DEBUG environment variable is set
177
debug_enabled = lambda: os.environ.get('DEBUG') == '1'
178
179
@wrapt.FunctionWrapper(enabled=debug_enabled)
180
def my_function():
181
return "result"
182
183
# Wrapper only executes if DEBUG=1 is set
184
result = my_function()
185
```
186
187
### Wrapper Chains
188
189
FunctionWrapper supports chaining multiple wrappers:
190
191
```python
192
import wrapt
193
194
def timing_wrapper(wrapped, instance, args, kwargs):
195
import time
196
start = time.time()
197
result = wrapped(*args, **kwargs)
198
print(f"Time: {time.time() - start:.4f}s")
199
return result
200
201
def logging_wrapper(wrapped, instance, args, kwargs):
202
print(f"Calling: {wrapped.__name__}")
203
result = wrapped(*args, **kwargs)
204
print(f"Result: {result}")
205
return result
206
207
def slow_function():
208
import time
209
time.sleep(0.1)
210
return "done"
211
212
# Chain wrappers (inner wrapper applied first)
213
wrapped_once = wrapt.FunctionWrapper(slow_function, timing_wrapper)
214
wrapped_twice = wrapt.FunctionWrapper(wrapped_once, logging_wrapper)
215
216
wrapped_twice() # Logs then times the function
217
```
218
219
### Custom Wrapper Classes
220
221
Extend FunctionWrapper for specialized behavior:
222
223
```python
224
import wrapt
225
226
class CachingWrapper(wrapt.FunctionWrapper):
227
def __init__(self, wrapped, wrapper):
228
super().__init__(wrapped, wrapper)
229
self._self_cache = {}
230
231
def __call__(self, *args, **kwargs):
232
# Create cache key from arguments
233
key = (args, tuple(sorted(kwargs.items())))
234
235
if key in self._self_cache:
236
print(f"Cache hit for {self.__wrapped__.__name__}")
237
return self._self_cache[key]
238
239
result = super().__call__(*args, **kwargs)
240
self._self_cache[key] = result
241
return result
242
243
def identity_wrapper(wrapped, instance, args, kwargs):
244
return wrapped(*args, **kwargs)
245
246
def expensive_function(n):
247
import time
248
time.sleep(0.5) # Simulate expensive operation
249
return n * 2
250
251
cached_function = CachingWrapper(expensive_function, identity_wrapper)
252
print(cached_function(5)) # Slow first call
253
print(cached_function(5)) # Fast cached call
254
```
255
256
## Descriptor Protocol
257
258
FunctionWrapper fully implements the descriptor protocol, making it work correctly with class methods, static methods, and properties:
259
260
```python
261
import wrapt
262
263
def method_wrapper(wrapped, instance, args, kwargs):
264
print(f"Wrapper called with instance: {instance}")
265
return wrapped(*args, **kwargs)
266
267
class MyClass:
268
@wrapt.FunctionWrapper(method_wrapper)
269
def instance_method(self):
270
return "instance"
271
272
@classmethod
273
@wrapt.FunctionWrapper(method_wrapper)
274
def class_method(cls):
275
return "class"
276
277
@staticmethod
278
@wrapt.FunctionWrapper(method_wrapper)
279
def static_method():
280
return "static"
281
282
obj = MyClass()
283
obj.instance_method() # instance != None
284
MyClass.class_method() # instance = MyClass
285
MyClass.static_method() # instance = None
286
```
287
288
## Error Handling
289
290
FunctionWrapper preserves exception information and stack traces:
291
292
```python
293
import wrapt
294
295
def error_wrapper(wrapped, instance, args, kwargs):
296
try:
297
return wrapped(*args, **kwargs)
298
except Exception as e:
299
print(f"Exception in {wrapped.__name__}: {e}")
300
raise # Re-raise preserving original traceback
301
302
def failing_function():
303
raise ValueError("Something went wrong")
304
305
wrapped_function = wrapt.FunctionWrapper(failing_function, error_wrapper)
306
307
try:
308
wrapped_function()
309
except ValueError as e:
310
# Original traceback is preserved
311
import traceback
312
traceback.print_exc()
313
```