apfel.core.function_object
Extend functions with operator overloads for function calling.
Conceptually, function objects are objects that implement the function call operator.
Rationale
Python codes contain extensive use of parentheses, which can be cumbersome in interactive environments.
This module simply provides a way to reduce the pain of wrapping huge expressions in parentheses.
You can use application operators provided by FunctionObject to apply the function on the following expression.
Use the fob function to turn a function or a sequence of functions into FunctionObjects.
Usage
For example, f(..) is equivalent to f | (...) or f @ (...).
Writing f(...) as f@(...) seems redundant,
but these @ usages can be purged easily with simple search and replace.
FunctionObject’s operators are carefully chosen, as they:
- are relatively rare in Python
- have different precedence and associativity
This is especially useful when you wrap some debugging or logging code around a function call.
For example, with the following usage with icecream,
you can replace ic @ with empty string to remove all debugging code,
without worrying of breaking the get_pic() call:
from icecream import ic as _ic
ic = fob(_ic)result = ic @ ( get_pic(iris) .filter(...) .sort_values(...) .apply(...) .head(10))| and @ have different operator precedence,
the | has almost the lowest precedence of all Python operators, while the @ has almost the highest precedence.
This allows you combine expressions more flexibly without worrying about parentheses.
& operator can be used to apply the function to its left-hand side,
if the left-hand side does not overload the & operator.
This is similar to &, |> or roughly %>%.
With & you can write your code naturally from left to right.
from icecream import ic as _icic = fob(_ic)
result = ( get_pic(iris) .filter(...) # ...) & ic** operator has the highest precedence of all, and it has a unique
associtivity from right to left.
It can be used in wrapping multiple calls together without parentheses, like f(g(x)) can be written as f ** g ** x.
It roughly simulates $.
% operator is used for calling multi-argument functions.
Check its documentation for more details.
API#
Classes
| Name | Description |
|---|---|
FunctionObject | No description. |
FunctionObjectA0 | No description. |
FunctionObjectA0P | No description. |
FunctionObjectA1 | No description. |
FunctionObjectA1P | No description. |
Functions
| Name | Description |
|---|---|
fob | python |
reveal_fob | python |
FunctionObjectclass#
class FunctionObject(func)__call__attributeinstance attribute#
__call__ = func__doc__attributeinstance attribute#
__doc__ = func.__doc____name__attributeinstance attribute#
__name__ = getattr(func, '__name__', '<λ>')__qualname__attributeinstance attribute#
__qualname__ = getattr(func, '__qualname__', '<λ>')__slots__attributeclass attributeinstance attribute#
__slots__ = ('__call__', '__wrapped__', '__doc__', '__name__', '__qualname__')__wrapped__attributeinstance attribute#
__wrapped__ = func__init__method#
def __init__(func)__matmul__method#
def __matmul__(value)def @[T, R](self, value: T) -> RFunction application operator @ for FunctionObjects.
f @ x is equivalent to f(x). This operator behaves the same as |, but with a different precedence.
@fobdef f(x): return x * 2
f @ 1 + 2# 4__mod__method#
def __mod__(value)def %[R](self, value) -> RFunction application operator % for FunctionObjects of multi-argument functions.
- If the right hand side is a
Sequence, spreads the sequence as positional arguments. For example,x % (a, b, c)is equivalent tox(a, b, c). - If the right hand side is a
Mapping, spreads the mapping as keyword arguments. For example,x % { "a": 1, "b": 2, "c": 3 }is equivalent tox(a=1, b=2, c=3). - Specifically, you can use
...as the map key to pass keyword arguments with keyword arguments at the same time,x % { ...: (1, 2), "c": 3 }is equivalent tox(1, 2, c=3). - Otherwise, it calls on the right-hand side. This catches the case where you forget the trailing comma in the right-hand side tuple.
@fobdef f(a, b, c): return a + b + c
f % (1, 2, 3) # 6f % { "a": 1, "b": 2, "c": 3 } # 6f % { ...: (1, 2), "c": 3 } # 6__or__method#
def __or__(value, /)def |[T, R](self, value: T) -> RFunction application operator | for FunctionObjects.
f | x is equivalent to f(x). This operator behaves the same as @, but with a different precedence.
@fobdef f(x): return x * 2
f | 1 + 2# 6__pow__method#
def __pow__(value)def **[T, R](self, value: T) -> RFunction application operator ** for FunctionObjects.
f ** g ** x is equivalent to f(g(x)).
This operator has the highest precedence of all overloadable operators,
and it binds from right to left.
It intends to simulate $ operator, except the precedence.
@fobdef f(x): return x + 1
@fobdef g(x): return x * 2
f ** g ** 1# 3__rand__method#
def __rand__(value)def &[T, R](self, value: T) -> RReverse function application operator & for FunctionObjects.
This operator overloading targets the right-hand side.
x & f is equivalent to f(x), if & operator (left, __and__) is not overloaded by x’s type.
@fobdef f(x): return x + 1
1 + 2 & f# 4__repr__method#
def __repr__()__str__method#
def __str__()FunctionObjectA0class#
class FunctionObjectA0(FunctionObject)Bases: FunctionObject
__call__method#
def __call__() -> RFunctionObjectA0Pclass#
class FunctionObjectA0P(FunctionObject)Bases: FunctionObject
__call__method#
def __call__(*args: P.args, **kwargs: P.kwargs) -> RFunctionObjectA1class#
class FunctionObjectA1(FunctionObject)Bases: FunctionObject
FunctionObjectA1Pclass#
class FunctionObjectA1P(FunctionObject)Bases: FunctionObject
__call__method#
def __call__(value: T, *args: P.args, **kwargs: P.kwargs) -> Rfobfunction#
def fob(func: F, /, *funcs: *Fs) -> F | tuple[*Fs]def fob[F: Callable](func: F) -> Fdef fob[*Fs](*funcs: *Fs) -> tuple[*Fs]Turn callables into FunctionObject yet keeps their original type hints.
@fobdef f(a: int) -> int: return a + 1
f | 1# 2
print, display, str = fob(print, display, str)reveal_fobfunction#
def reveal_fob(func: Callable[[], R] | Callable[[T], R] | Callable[Concatenate[T, P], R] | Callable[P, R] | Callable, /) -> FunctionObjectA0[R] | FunctionObjectA1[T, R] | FunctionObjectA1P[T, P, R] | FunctionObjectA0P[P, R] | FunctionObjectdef reveal_fob(func: Any) -> FunctionObject raise TypeErrorCast a FunctionObject to FunctionObject type.
Raises
TypeError- This function performs runtime check and raises
TypeErrorif the input is not aFunctionObject.
