Module for decorators, wrappers and monkey patching.
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.
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.
class FunctionWrapper(ObjectProxy):
def __init__(self, wrapped, wrapper, enabled=None):
"""
Create a function wrapper with proper binding support.
Args:
wrapped: The function to wrap
wrapper: Wrapper function with signature (wrapped, instance, args, kwargs) -> result
enabled: Boolean or callable to enable/disable wrapper (optional)
"""
@property
def _self_wrapper(self):
"""The wrapper function."""
@property
def _self_enabled(self):
"""Enable/disable flag or callable."""
@property
def _self_instance(self):
"""Instance the function is bound to (if any)."""
@property
def _self_binding(self):
"""Type of binding ('function', 'method', 'classmethod', etc.)."""
@property
def _self_parent(self):
"""Parent wrapper (for bound functions)."""
def __get__(self, instance, owner):
"""
Descriptor protocol for method binding.
Args:
instance: Instance object (None for class access)
owner: Owner class
Returns:
BoundFunctionWrapper or self
"""
def __set_name__(self, owner, name):
"""
Support for descriptor naming (Python 3.6+).
Args:
owner: Owner class
name: Attribute name
"""
def __call__(self, *args, **kwargs):
"""
Call the wrapped function through the wrapper.
Args:
*args: Positional arguments
**kwargs: Keyword arguments
Returns:
Result from wrapper function
"""
def __instancecheck__(self, instance):
"""Support for isinstance() checks."""
def __subclasscheck__(self, subclass):
"""Support for issubclass() checks."""Usage Example:
import wrapt
def logging_wrapper(wrapped, instance, args, kwargs):
print(f"Calling {wrapped.__name__} with args={args}, kwargs={kwargs}")
if instance is not None:
print(f"Instance: {instance}")
result = wrapped(*args, **kwargs)
print(f"Result: {result}")
return result
# Wrap a function
def add(a, b):
return a + b
wrapped_add = wrapt.FunctionWrapper(add, logging_wrapper)
result = wrapped_add(2, 3) # Logs call details
# Wrap a method
class Calculator:
def multiply(self, a, b):
return a * b
Calculator.multiply = wrapt.FunctionWrapper(Calculator.multiply, logging_wrapper)
calc = Calculator()
result = calc.multiply(4, 5) # Logs with instance informationCreated 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.
class BoundFunctionWrapper:
"""
Wrapper for bound functions (methods). Created automatically by
FunctionWrapper.__get__() when accessing wrapped methods on instances.
"""
def __call__(self, *args, **kwargs):
"""
Call the bound method with proper instance binding.
Args:
*args: Positional arguments
**kwargs: Keyword arguments
Returns:
Result from wrapper function
"""Example of automatic binding:
import wrapt
def method_wrapper(wrapped, instance, args, kwargs):
print(f"Method {wrapped.__name__} called on {type(instance).__name__}")
return wrapped(*args, **kwargs)
class MyClass:
def __init__(self, name):
self.name = name
def greet(self, message):
return f"{self.name}: {message}"
# Wrap the method
MyClass.greet = wrapt.FunctionWrapper(MyClass.greet, method_wrapper)
# Create instance and call method
obj = MyClass("Alice")
result = obj.greet("Hello!") # Creates BoundFunctionWrapper automatically
# Prints: "Method greet called on MyClass"
# Returns: "Alice: Hello!"Use the enabled parameter to conditionally enable/disable wrapping:
import wrapt
import os
def debug_wrapper(wrapped, instance, args, kwargs):
print(f"DEBUG: Calling {wrapped.__name__}")
return wrapped(*args, **kwargs)
# Enable only when DEBUG environment variable is set
debug_enabled = lambda: os.environ.get('DEBUG') == '1'
@wrapt.FunctionWrapper(enabled=debug_enabled)
def my_function():
return "result"
# Wrapper only executes if DEBUG=1 is set
result = my_function()FunctionWrapper supports chaining multiple wrappers:
import wrapt
def timing_wrapper(wrapped, instance, args, kwargs):
import time
start = time.time()
result = wrapped(*args, **kwargs)
print(f"Time: {time.time() - start:.4f}s")
return result
def logging_wrapper(wrapped, instance, args, kwargs):
print(f"Calling: {wrapped.__name__}")
result = wrapped(*args, **kwargs)
print(f"Result: {result}")
return result
def slow_function():
import time
time.sleep(0.1)
return "done"
# Chain wrappers (inner wrapper applied first)
wrapped_once = wrapt.FunctionWrapper(slow_function, timing_wrapper)
wrapped_twice = wrapt.FunctionWrapper(wrapped_once, logging_wrapper)
wrapped_twice() # Logs then times the functionExtend FunctionWrapper for specialized behavior:
import wrapt
class CachingWrapper(wrapt.FunctionWrapper):
def __init__(self, wrapped, wrapper):
super().__init__(wrapped, wrapper)
self._self_cache = {}
def __call__(self, *args, **kwargs):
# Create cache key from arguments
key = (args, tuple(sorted(kwargs.items())))
if key in self._self_cache:
print(f"Cache hit for {self.__wrapped__.__name__}")
return self._self_cache[key]
result = super().__call__(*args, **kwargs)
self._self_cache[key] = result
return result
def identity_wrapper(wrapped, instance, args, kwargs):
return wrapped(*args, **kwargs)
def expensive_function(n):
import time
time.sleep(0.5) # Simulate expensive operation
return n * 2
cached_function = CachingWrapper(expensive_function, identity_wrapper)
print(cached_function(5)) # Slow first call
print(cached_function(5)) # Fast cached callFunctionWrapper fully implements the descriptor protocol, making it work correctly with class methods, static methods, and properties:
import wrapt
def method_wrapper(wrapped, instance, args, kwargs):
print(f"Wrapper called with instance: {instance}")
return wrapped(*args, **kwargs)
class MyClass:
@wrapt.FunctionWrapper(method_wrapper)
def instance_method(self):
return "instance"
@classmethod
@wrapt.FunctionWrapper(method_wrapper)
def class_method(cls):
return "class"
@staticmethod
@wrapt.FunctionWrapper(method_wrapper)
def static_method():
return "static"
obj = MyClass()
obj.instance_method() # instance != None
MyClass.class_method() # instance = MyClass
MyClass.static_method() # instance = NoneFunctionWrapper preserves exception information and stack traces:
import wrapt
def error_wrapper(wrapped, instance, args, kwargs):
try:
return wrapped(*args, **kwargs)
except Exception as e:
print(f"Exception in {wrapped.__name__}: {e}")
raise # Re-raise preserving original traceback
def failing_function():
raise ValueError("Something went wrong")
wrapped_function = wrapt.FunctionWrapper(failing_function, error_wrapper)
try:
wrapped_function()
except ValueError as e:
# Original traceback is preserved
import traceback
traceback.print_exc()Install with Tessl CLI
npx tessl i tessl/pypi-wrapt