More intuitive partial function application in Python
github.com
github.com
>>> list(map(partial(pow, exp=3), range(10)))
[0, 1, 8, 27, 64, 125, 216, 343, 512, 729]
>>> list(map(lambda base: pow(base, 3), range(10)))
[0, 1, 8, 27, 64, 125, 216, 343, 512, 729]
>>> [pow(base, 3) for base in range(10)]
[0, 1, 8, 27, 64, 125, 216, 343, 512, 729]
Proposed solution: list(map(bp.partial(pow)(bp._, 3), range(10))
ISTM the only time you come out a little ahead is if you know in advance that a function is going to be partialed (so you can decorate it), that you will need to partial arguments in other than a left to right fashion (otherwise a plain partial will suffice), that you won't use keyword arguments (the current partial works with keywords), and that you really dislike the lambda keyword (which neatly covers all cases from simple to complex without a new notation).That said, this is a clever recipe. Kudos to the author for putting it together. I don't think it really rises to the level of "better" or "more intuitive", but it is an interesting experiment.
What if you have a function "f" that takes like 10 arguments and you need to pass it to a function "g" that expects as input a function takes 8 arguments.
@partial
def f(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10):
return "stuff"
g(f(..., p4=1, p7=2))
# seems nicer than
g(lambda p1, p2, p3, p5, p6, p8, p9, p10: f(p1, p2, p3, 1, p5, p6, 2, p8, p9, p10))We have data structures for that, if you need it. Just pass in a big struct/named tuple as one parameter. Bonus points if it is type hinted.
Could you compare it to functools.partial?
In the example on the Readme:
import better_partial as bp
@bp.partial
def some_operation(x, p1, p2):
return (x + p1) * p2
It claims that the bp approach is superior because: func = some_operation(bp._, 10, 20)
...is better than: func = lambda x: some_operation(x, 10, 20)
But is it better than just: partial(some_operation, p1=10, p2=20)
? partial(some_operation, p1=10, p2=20)
Suffers from inconsistent syntax compared to normal function call. This is probably a negative, but it could be a positive.https://gist.github.com/chrisgrimm/64bca66f14528cfda6d865cc2...
In practice i worry about static checkers and IDE param hints though, how have you fared with these?
Not your fault at all, but mypy/pyright have very poor support for wrappers like this, and in IDEs it's common for their arg hints to devolve to a copout (args, kwargs) as well.
You might be able to annotate it accurately with generics, ParamSpec and Concatenate[1].
Accordingly there are a lot of things I have to do to make better_partial a strong tool for the community. I'll probably have time to work on the project again later in the week.
Somewhat related, here's a similar PR for native support of this feature in Julia: https://github.com/JuliaLang/julia/pull/24990.
In my opinion, I'd love to see this baked in as part of the language. Getting buy in from my team to use this in production is just not going to happen. But cool package though!
from better_partial import partial, _
presumably. Though since _ has some meaning in Python maybe you'd want to do
from better_partial import partial, _ as x
instead (or something like it).
Also note that the _ placeholder isn't the only way that better_partial helps you. You can also use ... to indicate that you want to omit all arguments except ones you explicitly pass as kwargs. For instance:
from better_partial import partial, _
@partial
def f(a,b,c,d,e):
return (a,b,c,d,e)
f(_,_,3,_,_)(1,2,4,5) == f(..., c=3)(1,2,4,5)(mtcars >> group_by(_.cyl) >> summarize(avg_hp = _.hp.mean()) )
(Edit: ok, it seems siuba doesn't have the "parameter fixing" thing.)
Related: Coconut - a functional superset of Python - https://github.com/evhub/coconut
range(10) |> map$(pow$(?, 2)) |> list
There were others but I can't remember which now.
My only grief is that decorator, which forces you to wrap existing functions anyway (same way I had to define lambdas in my example anyway).
Do you have any insight on that?
I'd rather see partial() promoted to a universal static method attached to any callable, so it's easier to discover, and use.
I'm sure it is! That's why I'd be leery of using it in production code.
https://gist.github.com/chrisgrimm/64bca66f14528cfda6d865cc2...