Source code for duck.utils.callabletools

"""
Module for callable utilities.
"""
import inspect
import functools

from types import MethodType, FunctionType


[docs] def duplicate_callable(callable_obj, new_name=None, decorator=None): """ Creates a duplicate of the given callable (function or method) with the same signature. This function is useful for dynamically generating new callables with the same signature as an existing callable, while optionally allowing the application of a decorator. The new callable can also be given a custom name. Args: callable_obj (callable): The original callable to duplicate. new_name (str, optional): The name for the new duplicated callable. Defaults to None. decorator (callable, optional): A decorator to apply to the callable. Defaults to None. Returns: callable: A new callable with the same signature as the original callable. Safety: - This function avoids using exec, which mitigates the risk of executing arbitrary code. - The function uses closures to ensure that the wrapper is correctly referenced, preventing name errors. - By preserving the callable's signature and using functools.wraps, the callable's metadata (like name and docstring) is maintained. Example Usage: ```py # Define a function to be duplicated def example_function(arg1, arg2, kwarg1=None, kwarg2=None): ''' An example function that takes two positional arguments and two keyword arguments. ''' return f"arg1: {arg1}, arg2: {arg2}, kwarg1: {kwarg1}, kwarg2: {kwarg2}" # Create a duplicate of the function with a custom name duplicated_function = duplicate_callable(example_function, new_name="duplicated_function") result = duplicated_function(1, 2, kwarg1="test", kwarg2="example") print(result) # Output: arg1: 1, arg2: 2, kwarg1: test, kwarg2: example # Define a class with a method to be duplicated class MyClass: def example_method(self, arg1, arg2, kwarg1=None, kwarg2=None): ''' An example method that takes two positional arguments and two keyword arguments. ''' return f"arg1: {arg1}, arg2: {arg2}, kwarg1: {kwarg1}, kwarg2: {kwarg2}" # Create a duplicate of the method with a custom name MyClass.duplicated_method = duplicate_callable(MyClass.example_method, new_name="duplicated_method") obj = MyClass() result = obj.duplicated_method(1, 2, kwarg1="test", kwarg2="example") print(result) # Output: arg1: 1, arg2: 2, kwarg1: test, kwarg2: example ``` """ # Get the signature of the original callable sig = inspect.signature(callable_obj) # Define the wrapper with the same signature @functools.wraps(callable_obj) def wrapper(*args, **kwargs): if decorator: decorated_callable = decorator( callable_obj) # Apply the decorator if provided else: decorated_callable = callable_obj return decorated_callable(*args, **kwargs) # Use the signature to dynamically construct the new callable parameters = ", ".join(str(param) for param in sig.parameters.values()) # Create a closure to contain the wrapper reference def create_callable(wrapper): # Construct the new callable within the closure if hasattr(callable_obj, "__self__"): # Handle methods def new_method(self, *args, **kwargs): return wrapper(self, *args, **kwargs) return new_method else: # Handle functions def new_func(*args, **kwargs): return wrapper(*args, **kwargs) return new_func # Create and return the new callable new_callable = create_callable(wrapper) new_callable.__name__ = new_name if new_name else callable_obj.__name__ new_callable.__signature__ = sig return new_callable
[docs] def get_callable_type(obj, owner: type = None) -> str: """ Return a descriptive type for the given callable. Args: obj: Callable object to classify. owner (type, optional): Class owning the callable if known. Needed only to detect unbound methods. Returns: str: One of: - "bound_method" - "unbound_method" - "classmethod" - "staticmethod" - "function" - "callable_object" - "builtin_function" - "unknown" """ if inspect.ismethod(obj): return "bound_method" if inspect.isbuiltin(obj): return "builtin_function" if not isinstance(obj, (FunctionType, MethodType)) and callable(obj): return "callable_object" if owner is not None: attr = getattr(owner, obj.__name__, None) # Classmethod: stored as function but wrapped in descriptor if isinstance(attr, classmethod): return "classmethod" # Staticmethod: stored as StaticMethod object if isinstance(attr, staticmethod): return "staticmethod" # Unbound method (Python 3 treats as function) if isinstance(obj, FunctionType) and attr is obj: return "unbound_method" if isinstance(obj, FunctionType): # Plain function return "function" return "unknown"