Last active
February 1, 2024 23:45
-
-
Save XoseLluis/0c388e8a736ccb723b0d1477ec936ccb to your computer and use it in GitHub Desktop.
late-bound default parameters for python. see more here: https://deploytonenyures.blogspot.com/2024/02/python-late-bound-default-parameters.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# inspired by: | |
# https://github.com/neogeny/late/blob/master/late/__init__.py | |
# and | |
# href="https://peps.python.org/pep-0671/ | |
from dataclasses import dataclass | |
from typing import Callable, Any | |
import functools | |
import inspect | |
@dataclass | |
class LateBound: | |
resolver: Callable | |
def _invoke_late_bound(callable: Callable, arg_name_to_value: dict[str, Any]) -> Any: | |
""" | |
invokes a callable passing over to it the parameters defined in its signature | |
we obtain those values from the arg_name_to_value dictionary | |
""" | |
expected_params = inspect.signature(callable).parameters.keys() | |
kwargs = {name: arg_name_to_value[name] | |
for name in expected_params | |
} | |
return callable(**kwargs) | |
def _add_late_bounds(arg_name_to_value: dict[str, Any], late_bounds: list[str, Callable]): | |
"""resolves late-bound values and adds them to the arg_name_to_value dictionary""" | |
for name, callable in late_bounds: | |
val = _invoke_late_bound(callable, arg_name_to_value) | |
#this way one late bound can depend on a previous late boud | |
arg_name_to_value[name] = val | |
def _resolve_args(target_fn: Callable, *args, **kwargs) -> dict[str, Any]: | |
"""returns a dictionary with the name and value all the parameters (the ones already provided, the calculated latebounds and the normal defaults)""" | |
# dictionary of the arguments and values received by the function at runtime | |
# we use it to be able to calculate late_bound values based on other parameters | |
arg_name_to_value: dict[str, Any] = {} | |
arg_names = list(inspect.signature(target_fn).parameters.keys()) | |
for index, arg in enumerate(args): | |
arg_name_to_value[arg_names[index]] = arg | |
arg_name_to_value = {**arg_name_to_value, **kwargs} | |
# obtain the values for all default parameters that have not been provided | |
# we obtain them all here so that late_bounds can depend on other (compile-time or late-bound) default parameters | |
#late bounds to calculate (were not provided in args-kwargs) | |
not_late_bounds = {name: param.default | |
for name, param in inspect.signature(target_fn).parameters.items() | |
if not isinstance(param.default, LateBound) and not name in arg_name_to_value | |
} | |
arg_name_to_value = {**arg_name_to_value, **not_late_bounds} | |
# list rather than dictionary as order matters (so that a late-bound can depend on a previous late-bound) | |
late_bounds = [(name, param.default.resolver) | |
for name, param in inspect.signature(target_fn).parameters.items() | |
if isinstance(param.default, LateBound) and not name in arg_name_to_value | |
] | |
_add_late_bounds(arg_name_to_value, late_bounds) | |
return arg_name_to_value | |
#decorator function | |
def late_bind(target_fn: Callable | type) -> Callable | type: | |
"""decorates a function enabling late-binding of default parameters for it""" | |
@functools.wraps(target_fn) | |
def wrapper(*args, **kwargs): | |
kwargs = _resolve_args(target_fn, *args, **kwargs) | |
return target_fn(**kwargs) | |
return wrapper |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from datetime import datetime | |
from dataclasses import dataclass | |
from late_bound_default_args import late_bind, LateBound | |
@late_bind | |
def say_hi(source: str, target: str, greet: str, | |
extra = LateBound(lambda: f"[{datetime.now():%Y-%m-%d_%H%M%S}]"), | |
): | |
"""""" | |
return f"{greet} from {source} to {target}. {extra}" | |
@late_bind | |
def say_hi2(source: str, target: str, greet: str, | |
extra = LateBound(lambda greet: f"[{greet.upper()}!]"), | |
): | |
"""""" | |
return f"{greet} from {source} to {target}. {extra}" | |
print(say_hi("Xuan", "Francois", "Bonjour")) | |
print(say_hi2("Xuan", "Francois", "Bonjour")) | |
# Bonjour from Xuan to Francois. [2024-02-02_002939] | |
# Bonjour from Xuan to Francois. [BONJOUR!] | |
print("------------") | |
@dataclass | |
class Person: | |
name: str | |
birth_place: str | |
@late_bind | |
def travel(self, by: str, | |
start_city: str = LateBound(lambda self: self.birth_place), | |
to: str = "Paris" | |
): | |
"""""" | |
return(f"{self.name} is travelling from {start_city} to {to} by {by}") | |
p1 = Person("Xuan", "Xixon") | |
print(p1.travel("train")) | |
# Xuan is travelling from Xixon to Paris by train |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment