# Addition: \`functools.argorder\`

**URL:** <https://discuss.python.org/t/addition-functools-argorder/53427>\
**Category:** Ideas\
**Created:** [May 15, 2024, 5:27pm UTC](https://discuss.python.org/t/addition-functools-argorder/53427 "2024-05-15T17:27:14Z")\
**Posts on this page:** 18\
**Page:** 1

<div class="post-metadata">

**Author:** ![dg-pb](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/dg-pb/32/16248_2.png) [@dg-pb](https://discuss.python.org/u/dg-pb)\
**Post date:** [May 15, 2024, 5:27pm UTC](https://discuss.python.org/t/addition-functools-argorder/53427/1 "2024-05-15T17:27:14Z")

</div>

_Proposal: add the function with the following logic to `functools` module_

```python
def argorder(func, order):
    def inner(*args, **kwds):
        return func(*[args[i] for i in order], **kwds)
    return inner

```

_What issues is this a potential answer to:_  
a) It would improve efficient interactions between different modules in standard library. Specifically: `functools`, `itertools` and `operator`  
b) There are already a lot of packages that have invented various variations of `partial` to address issue which can be solved using this proposal. To name a few packages:  
\* [partiell · PyPI](https://pypi.org/project/partiell/)  
\* [partial-apply · PyPI](https://pypi.org/project/partial-apply/)  
\* [partial · PyPI](https://pypi.org/project/partial/)  
\* [better-partial · PyPI](https://pypi.org/project/better-partial/)  
c) Also this is related to threads:  
\* [Support for using partial with positional only argument functions - #3 by mariocj89](https://discuss.python.org/t/support-for-using-partial-with-positional-only-argument-functions/9821/3)  
\* [Functools.partial extension to support specific positional arguments](https://discuss.python.org/t/functools-partial-extension-to-support-specific-positional-arguments/19163)  
c) Also directly related to: [Builtins.any performance - #28 by dgrigonis](https://discuss.python.org/t/builtins-any-performance/53141/28)  
d) And somewhat related to: [For any & every - #53 by Nineteendo](https://discuss.python.org/t/for-any-every/53302/53) as it improves performance of “short-circuiting” solutions that do not involve loop, but use `map` function.

_why is this better than all of the above?_  
a) More efficient solution to the problem when combined with `functools.partial` compared to pure python external packages or to `lambda` solution.  
b) It doesn’t require any changes to existing code in contrast to modification proposals.

The most common response to making changes to functional tools regarding arguments is:

- “you can just use lambda”

However, this is a good argument when the cost is making existing tools (e.g. `partial`) implementation more complex. While in this case, the proposal is well contained in itself and doesn’t require any changes to existing code.

It non-invasively improves performance by more than 50% compared to `lambda` solution, which amounts to significant improvements when predicates are called on every iteration.

`initial implementation`

```C
static PyObject *
argorder_call(argorder *self, PyObject *args, PyObject *kwds)
{
    PyObject *new_args = PyTuple_New(self->nargs);
    PyObject *arg, *index;
    PyObject *result;
    Py_ssize_t pos;
    for (int i=0; i < self->nargs; i++) {
        index = PySequence_Fast_GET_ITEM(self->order, i);
        pos = PyLong_AsSize_t(index);
        arg = PyTuple_GetItem(args, pos);
        PyTuple_SET_ITEM(new_args, i, arg);
    }
    result = PyObject_Call(self->func, new_args, kwds);
    Py_DECREF(new_args);
    return result;
}

```

_naming_  
Didn’t give much thought about it yet, I am open to suggestions, if this was considered.

---

<div class="post-metadata">

**Author:** ![Stefan2](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/stefan2/32/18492_2.png) [@Stefan2](https://discuss.python.org/u/Stefan2)\
**Post date:** [May 15, 2024, 8:51pm UTC](https://discuss.python.org/t/addition-functools-argorder/53427/2 "2024-05-15T20:51:02Z")

</div>

Just another Python version ([Attempt This Online!](https://ato.pxeger.com/run?1=pZFBCsIwEEVxm1PMMgkRVFyI4ElKF9UmNUiTMp0ggjdx040eytOYtLW4E3RWA3_-zBv-7dFc6Ohd190DmfnmObsa9DX4RmNBHsHWjUeCNuwVWNJ1pYk0MlZqAwVWHkuN3AR3UND3YssgVpKtc1GTcapVIOXpXLajmgo1BXSQrFxmaSizOZh0MjqHZfnkYx-Wfi9jDVpHfGLoCflSwUIIvlKwFuIrpa1g9_EVl4P4ywe2Siit-I94CGHM4p3JCw)):

```python
def argorder(func, order):
    ig = operator.itemgetter(*order)
    def inner(*args, **kwds):
        return func(*ig(args), **kwds)
    return inner

```

---

<div class="post-metadata">

**Author:** ![kknechtel](https://avatars.discourse-cdn.com/v4/letter/k/e47c2d/32.png) [@kknechtel](https://discuss.python.org/u/kknechtel)\
**Post date:** [May 15, 2024, 8:56pm UTC](https://discuss.python.org/t/addition-functools-argorder/53427/3 "2024-05-15T20:56:55Z")

</div>

Could you be a bit more concrete about what problems you hope to solve with this tool, please?

---

<div class="post-metadata">

**Author:** ![pf\_moore](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pf_moore/32/35_2.png) [@pf\_moore](https://discuss.python.org/u/pf_moore)\
**Post date:** [May 15, 2024, 9:02pm UTC](https://discuss.python.org/t/addition-functools-argorder/53427/4 "2024-05-15T21:02:35Z")

</div>

Also, why is it not sufficient to add this 3-line function to your code when you need it?

---

<div class="post-metadata">

**Author:** ![dg-pb](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/dg-pb/32/16248_2.png) [@dg-pb](https://discuss.python.org/u/dg-pb)\
**Post date:** [May 15, 2024, 10:34pm UTC](https://discuss.python.org/t/addition-functools-argorder/53427/5 "2024-05-15T22:34:54Z")

</div>

> [@pf\_moore](#):
>
> Also, why is it not sufficient to add this 3-line function to your code when you need it?

It is a matter of micro-optimization here. Lambda function is more efficient than this. It only makes sense in C.

It is generally the same reason why C `partial` implementation exists. Just the benefit here is much smaller.

---

<div class="post-metadata">

**Author:** ![dg-pb](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/dg-pb/32/16248_2.png) [@dg-pb](https://discuss.python.org/u/dg-pb)\
**Post date:** [May 15, 2024, 11:36pm UTC](https://discuss.python.org/t/addition-functools-argorder/53427/6 "2024-05-15T23:36:47Z")

</div>

> [@kknechtel](#):
>
> Could you be a bit more concrete about what problems you hope to solve with this tool, please?

Ah yes. This should have been part of the thread. I think I just looked at this enough to start thinking that others have seen what I have seen. This tool is mainly for predicate construction in conjunction with partial.

It as a faster convenience than lambda for cases when function arguments are not in convenient order.  
E.g.

```python
from operator import contains
from functools import partial
pred = partial(argorder(contains, [1, 0]), 9)
pred2 = lambda d: contains(d, 9)

a = [{i: i} for i in range(10)]
list(filter(pred, a)) # [{9: 9}]
list(filter(pred2, a)) # [{9: 9}]
%timeit list(filter(pred, a)) # 600 ns (30% faster)
%timeit list(filter(pred2, a)) # 860 ns
# -----
b = [{i: i} for i in range(100_000)]
%timeit list(filter(pred, b)) # 4.1 ms (40% faster)
%timeit list(filter(pred2, b)) # 6.8 ms

```

Also, note that it might be possible to optimize it better. I am far from expert in writing extensions.

---

<div class="post-metadata">

**Author:** ![Stefan2](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/stefan2/32/18492_2.png) [@Stefan2](https://discuss.python.org/u/Stefan2)\
**Post date:** [May 15, 2024, 11:52pm UTC](https://discuss.python.org/t/addition-functools-argorder/53427/7 "2024-05-15T23:52:22Z")

</div>

What are the times with `lambda d: 9 in d`? Or even `[d for d in b if 9 in d]`?

The “65% faster” doesn’t look right, btw.

---

<div class="post-metadata">

**Author:** ![dg-pb](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/dg-pb/32/16248_2.png) [@dg-pb](https://discuss.python.org/u/dg-pb)\
**Post date:** [May 15, 2024, 11:53pm UTC](https://discuss.python.org/t/addition-functools-argorder/53427/8 "2024-05-15T23:53:09Z")

</div>

> [@Stefan2](#):
>
> Just another Python version

Python implementation, although elegant, has no benefits compared to lambda:

```python
pred3 = partial(argorder2(contains, [1, 0]), 9)
a = [{i: i} for i in range(10)]
b = [{i: i} for i in range(100_000)]
%timeit list(filter(pred3, a)) # 2.4 µs
%timeit list(filter(pred3, b)) # 22.5 ms

```

---

<div class="post-metadata">

**Author:** ![kknechtel](https://avatars.discourse-cdn.com/v4/letter/k/e47c2d/32.png) [@kknechtel](https://discuss.python.org/u/kknechtel)\
**Post date:** [May 16, 2024, 12:03am UTC](https://discuss.python.org/t/addition-functools-argorder/53427/9 "2024-05-16T00:03:58Z")

</div>

> [@Stefan2](#):
>
> The “65% faster” doesn’t look right, btw.

It should say 40% faster. Rather, the slow version is 65% slower. It’s not valid to reverse the numbers directly.

---

<div class="post-metadata">

**Author:** ![dg-pb](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/dg-pb/32/16248_2.png) [@dg-pb](https://discuss.python.org/u/dg-pb)\
**Post date:** [May 16, 2024, 12:06am UTC](https://discuss.python.org/t/addition-functools-argorder/53427/10 "2024-05-16T00:06:27Z")

</div>

> [@Stefan2](#):
>
> What are the times with `lambda d: 9 in d`? Or even `[d for d in b if 9 in d]`?
> 
> The “65% faster” doesn’t look right, btw.

Yes, its 40%. Thanks.

Lambda although more efficient, still slower:

```python
pred3 = lambda d: 9 in d
%timeit list(filter(pred3, a)) # 730 ns (20% slower than argorder)
%timeit list(filter(pred3, b)) # 5.3 ms (30% slower than argorder)

```

List comprehension is much faster, but it is not the same. I.e. list conversion in examples is only made to process the iterable - in reality, what is being tested is `filter(pred)`. Also, it uses expression and not predicate function, which is not always possible. Results:

```python
%timeit [d for d in a if 9 in d] # 500 ms (20% faster than argorder)
%timeit [d for d in b if 9 in d] # 2.5 ms (40% faster than argorder)

```

---

<div class="post-metadata">

**Author:** ![dg-pb](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/dg-pb/32/16248_2.png) [@dg-pb](https://discuss.python.org/u/dg-pb)\
**Post date:** [May 16, 2024, 12:13am UTC](https://discuss.python.org/t/addition-functools-argorder/53427/11 "2024-05-16T00:13:04Z")

</div>

> [@Stefan2](#):
>
> What are the times with `lambda d: 9 in d`? Or even `[d for d in b if 9 in d]`?

A closer equivalent, although still doesn’t apply to much of what proposal applies to. Expression can not be supplied to functions that accept predicate, also list comprehension functionality is limited compared to functionality of many functions that accept predicates.

```python
%timeit list((d for d in a if 9 in d)) # 700 ms (17% slower than proposed)
%timeit list((d for d in b if 9 in d)) # 3 ms (30% faster than proposed)

```

---

<div class="post-metadata">

**Author:** ![blhsing](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/blhsing/32/25812_2.png) [@blhsing](https://discuss.python.org/u/blhsing)\
**Post date:** [May 16, 2024, 1:29am UTC](https://discuss.python.org/t/addition-functools-argorder/53427/12 "2024-05-16T01:29:38Z")

</div>

> [@Stefan2](#):
>
> ```python
> def argorder(func, order):
> ig = operator.itemgetter(*order)
> def inner(*args, **kwds):
> return func(*ig(args), **kwds)
> return inner
> 
> ```

`operator.itemgetter` unfortunately has the design choice of an inconsistent return type when given a single argument, which is convenient when arguments are fixed but troubling for generalization.

---

<div class="post-metadata">

**Author:** ![blhsing](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/blhsing/32/25812_2.png) [@blhsing](https://discuss.python.org/u/blhsing)\
**Post date:** [May 16, 2024, 2:38am UTC](https://discuss.python.org/t/addition-functools-argorder/53427/13 "2024-05-16T02:38:46Z")

</div>

I’m -1 on this because I find explicit argument indices unreadable and unmaintanable.

A wrapper function with named arguments may be slower but is much friendlier to both code readers and writers.

Using a custom string class with a regex substitution method as an example:

```python
class Str(str):
    def sub(self, pattern, replacement):
        return re.sub(pattern, replacement, self)

```

versus:

```python
class Str(str):
    sub = argorder(re.sub, [1, 2, 0])

```

the former is much clearer and maintainable IMHO.

---

<div class="post-metadata">

**Author:** ![dg-pb](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/dg-pb/32/16248_2.png) [@dg-pb](https://discuss.python.org/u/dg-pb)\
**Post date:** [May 16, 2024, 2:59am UTC](https://discuss.python.org/t/addition-functools-argorder/53427/14 "2024-05-16T02:59:38Z")

</div>

> [@blhsing](#):
>
> the former is much clearer and maintainable IMHO.

I generally agree. However this is mostly intended to be used in conjunction with `partial`, although could be used as utility on its own.

Having that said, I myself am at 0 at this point, maybe even slightly negative. Although it does offer some improvements, overall they aren’t big enough to get where I wanted.

Good findings, but unless someone can provide more support for this, I believe this is a no-go.

---

<div class="post-metadata">

**Author:** ![dg-pb](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/dg-pb/32/16248_2.png) [@dg-pb](https://discuss.python.org/u/dg-pb)\
**Post date:** [May 16, 2024, 3:08am UTC](https://discuss.python.org/t/addition-functools-argorder/53427/15 "2024-05-16T03:08:17Z")

</div>

Also, just to add. I explored alternative ways to achieve this small benefit without explicit addition in C. No way to get to the same performance with cython.

One last idea is that maybe `lambda` could compile to optimized version in case where only argument passing forward is done.

(These are for 2-arg case)  
Calling `partial` is 10-20 ns overhead on top of function call.  
Calling `argorder` is ~20 ns overhead.  
Wrapping in `lambda` is ~40 ns overhead.

Although I highly doubt it.

---

<div class="post-metadata">

**Author:** ![kknechtel](https://avatars.discourse-cdn.com/v4/letter/k/e47c2d/32.png) [@kknechtel](https://discuss.python.org/u/kknechtel)\
**Post date:** [May 16, 2024, 3:39am UTC](https://discuss.python.org/t/addition-functools-argorder/53427/16 "2024-05-16T03:39:27Z")

</div>

> [@dg-pb](#):
>
> However this is mostly intended to be used in conjunction with `partial`, although could be used as utility on its own.

I would rather just see a more powerful third-party replacement for `partial`, honestly.

---

<div class="post-metadata">

**Author:** ![dg-pb](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/dg-pb/32/16248_2.png) [@dg-pb](https://discuss.python.org/u/dg-pb)\
**Post date:** [May 16, 2024, 4:48am UTC](https://discuss.python.org/t/addition-functools-argorder/53427/17 "2024-05-16T04:48:29Z")

</div>

> [@kknechtel](#):
>
> I would rather just see a more powerful third-party replacement for `partial`, honestly.

What do you have in mind by saying “powerful”?

---

<div class="post-metadata">

**Author:** ![pedromj](https://avatars.discourse-cdn.com/v4/letter/p/dec6dc/32.png) [@pedromj](https://discuss.python.org/u/pedromj)\
**Post date:** [May 7, 2025, 1:37am UTC](https://discuss.python.org/t/addition-functools-argorder/53427/18 "2025-05-07T01:37:40Z")

</div>

It is somewhat strange that partial does not provide a way to reorder arguments. Many use-cases of partial require to flip arguments.

Generally, there are two common ways of defining functions that are later used with partial: f(receiver, arg1, arg2, arg3, …) and f(arg1, arg2, arg3, …, receiver). For some functions, the argument considered “receiver” depends on the context.

The best alternative is to either provide a “flip” function that will be composed with partial or make partial have a parameter that tells it to flip the arguments. I have not tested, but the latter sounds more efficient for me.

PS: As a side note in relation to the benchmarking discussed above, when dealing with this kind of issues, it is better to cmpare the number of low-level instructions generated for one or other alterantive. To make time good for comparison, the operation must be repeated many times, and avg and std should be considered. The number of instructions is supposed to have 0 std, so the result is more reliable.
