func.py/func_py/func.py
2025-07-14 08:56:44 +02:00

124 lines
3.6 KiB
Python

from inspect import signature, Parameter, _empty
from functools import partial
from typing import Any
def flyweight(cls):
def f(*args, **kwargs):
return cls(*args, **kwargs)
return f
#@flyweight
class function:
__match_args__ = ["signature", "is_pure"]
@staticmethod
def compose(f, g):
if type(g) == function:
g = g.original_func
else:
#whatif lambda?
if "__name__" in dir(g) and g.__name__ == "<lambda>":
sig_g = signature(g)
g.__signature__ = sig_g.replace(parameters=[
Parameter(name=name, annotation=Any, kind=Parameter.POSITIONAL_ONLY)
for name, param in sig_g.parameters.items() if param.default == _empty
], return_annotation=Any)
else:
pass
#its a functools.partial func with no name...
if type(f) == function:
f = f.original_func
sig_g = signature(g)
g_req_params = [v for k, v in sig_g.parameters.items() if v.default == _empty]
g_typed_req_params = [p if p.annotation == _empty else Parameter(name=p.name, annotation=Any, kind=Parameter.POSITIONAL_OR_KEYWORD) for p in g_req_params]
def h(*args, **kwargs):
return f(g(*args, **kwargs))
f_ret_type = signature(f).return_annotation
h.__signature__ = sig_g.replace(parameters=g_typed_req_params, return_annotation=Any if f_ret_type == _empty else f_ret_type)
return function(h)
#TODO: complexity prop
#TODO: getter for inverse function
#TODO: implement fly weight pattern
def __init__(self, f):
self.original_func = f
self.signature = signature(f) #TODO: change to support pattern matching: sig ((int, str), {"name": str})
sig = signature(f)
req_params = {k: v for k, v in sig.parameters.items() if v.default == _empty}
params = []
for name, p in req_params.items():
if p.annotation == _empty:
params.append( Parameter(name=name, annotation=Any, kind=Parameter.POSITIONAL_OR_KEYWORD) )
else:
params.append(p)
self.__signature__ = sig.replace(parameters=params, return_annotation=Any if sig.return_annotation == _empty else sig.return_annotation)
self.cache = {}
self.is_pure = True
def __str__(self):
return str(self.__signature__)
def __repr__(self):
return str(self)
def __mul__(self, g):
return function.compose(self, g)
def __rmul__(self, g):
return function.compose(g, self)
def __eq__(self, other):
return self.original_func == other.original_func
@property
def required_args(self):
return [(name, param) for name, param in self.__signature__.parameters.items() if param.default == _empty]
def __call__(self, *args, **kwargs):
provided_req_kwargs = {k for k, p in self.required_args if k in kwargs}
num_given_req_args = len(args) + len(provided_req_kwargs)
if num_given_req_args < len(self.required_args):
sig = self.__signature__
f = partial(self.original_func, *args, **kwargs)
remaining_params = [v for k, v in sig.parameters.items() if v.default == _empty and k not in provided_req_kwargs][len(args):]
f.__signature__ = sig.replace(parameters=remaining_params, return_annotation=Any if sig.return_annotation == _empty else sig.return_annotation)
return function(f)
r = self.original_func(*args, **kwargs)
if self.is_pure:
pass
#do caching
return r