News for hackers. Stuff that matters.
Re:Python Proposal (Inspired by Lua) by necovek on Sunday August 27, 2023 @03:21PM (#37285778)
Lists (like *args) are by definition ordered, and dicts (and thus **kwargs) are by definition unordered key-value pairs.

This would be very confusing in suddenly giving dicts a half-ordered, half-not semantics in a particular context.

Why wouldn't you simply switch to setting everything through kwargs instead?

Not to mention that this would have a performance penalty for dicts as they would have to keep an ordered set of integer keys in case they are used with **.

Re:Python Proposal (Inspired by Lua) by ledauphin on Sunday August 27, 2023 @03:51PM (#37286024)
in Python, dicts are actually definitionally ordered, since 3.7, so this objection doesn't carry much weight.

additionally, you might be interested to know that the implementation of dicts as ordered was found to be more performant than its predecessor.

Finally, even if they were unordered, there would be no need for ordering in this proposal, which depends on unique integer keys starting at 0.

Re:Python Proposal (Inspired by Lua) by necovek on Monday August 28, 2023 @02:46PM (#37298712)
The fact that they are definitionally ordered does not really matter for this discussion: for all intents and purposes of this proposal, they are unordered (insertion order is pretty much arbitrary order when it comes to their use as positional arguments or kwargs). And my intent was a bit more specific: I was thinking of "sorted", so definitely my bad.

For the proposed use case, either **kwargs would have to sort a subset of the dict with integer keys to map positional arguments to their, ahem, positions, and then either leave the rest as-is (or sort it altogether), or a dict would have its definition modified to keep this order as items are added.

I dislike both, and I definitely hate implicit sorting happening with **.

Re:Python Proposal (Inspired by Lua) by ali_m on Sunday August 27, 2023 @03:52PM (#37286028)
Dicts already have confusing half-ordered, half-not semantics. As of 3.7 they are guaranteed to be insertion-ordered, but operators like == don't care about order.
Re:Python Proposal (Inspired by Lua) by pphysch on Sunday August 27, 2023 @04:27PM (#37286334)
I don't think it's confusing or even on the level of "semantics": looping over dict keys/elements is a common pattern and the 3.7 change makes that a little more predictable and consistent.

Two dicts with same elements not being equal would be surprising and confusing.

Re:Python Proposal (Inspired by Lua) by ali_m on Sunday August 27, 2023 @06:14PM (#37287143)
If we're treating them as ordered containers then it really ought to be surprising and confusing that two dicts with the same elements in a different order are considered equal. Other ordered containers such as lists or tuples don't behave this way.

I'd use an `OrderedDict` in situations where I have a mapping where the order of elements is significant. `OrderedDict` signals intent much more clearly, and its `__eq__` method cares about order.

Re:Python Proposal (Inspired by Lua) by necovek on Monday August 28, 2023 @03:09PM (#37299008)
Yes, but I hope nobody really relies on that ordering in their code, as, as you say, it is pretty inconsistent.

Eg. you can't rely on

  dict_b_keys = iter(dict_b)
  for key in dict_a:
      value_a = dict_a[key]
      value_b = dict_b[next(dict_b_keys)]
      print(f"{value_a} == {value_b}: ", value_a == value_b)
The problem with the proposal is that it will be even stronger: you either require '*' to sort in-place (I assumed that would be even worse and thus unacceptable, so I ignored that case — it seems others believe that to be a more realistic implementation), or require dicts to maintain a half-sorted map of integer keys.
Re:Python Proposal (Inspired by Lua) by Hamuko on Sunday August 27, 2023 @03:52PM (#37286029)
Dicts are ordered as of Python 3.7.

>the insertion-order preservation nature of dict objects has been declared to be an official part of the Python language spec.

https://docs.python.org/3/whatsnew/3.7.html

Re:Python Proposal (Inspired by Lua) by SushiHippie on Sunday August 27, 2023 @03:53PM (#37286037)
Python dictionaries are guaranteed to preserve insertion order since python 3.7
Re:Python Proposal (Inspired by Lua) by JulianWasTaken on Sunday August 27, 2023 @03:59PM (#37286084)
Besides the mistake about dicts being ordered in Python which was pointed out by others,

> Why wouldn't you simply switch to setting everything through kwargs instead?

Not all arguments in Python are passable both ways.

In

def f(a, /): print(a)

`a` may be passed only positionally.

Re:Python Proposal (Inspired by Lua) by kstrauser on Sunday August 27, 2023 @04:29PM (#37286349)
I'd argue that functions with only position arguments are a place where you'd never want to use this proposal, ever. A tuple or list would be the right data structure to pass in there.
Re:Python Proposal (Inspired by Lua) by JulianWasTaken on Sunday August 27, 2023 @07:36PM (#37287741)
Of course it would be irrelevant for a function that only took positional arguments.

I was demonstrating the existence of such things in general since OP seemed unaware. For the proposal, you'd of course only use it for functions that took more kinds of arguments.

Re:Python Proposal (Inspired by Lua) by necovek on Monday August 28, 2023 @02:40PM (#37298646)
Thanks for pointing it out. Still, it seems highly unlikely someone would decide to use such a function declaration yet want to use the proposed syntax to pass both positional and keyword arguments.
Re:Python Proposal (Inspired by Lua) by fbdab103 on Sunday August 27, 2023 @04:02PM (#37286108)
Just laughing at the Cunningham's Law response of everyone jumping in to let you know that dictionaries are now insertion ordered.
Re:Python Proposal (Inspired by Lua) by throwmeout123 on Sunday August 27, 2023 @04:56PM (#37286559)
The proposal in question is not that good so maybe the order of the dicts is more interesting :P
Re:Python Proposal (Inspired by Lua) by necovek on Monday August 28, 2023 @02:49PM (#37298749)
What I meant was "sorted", which is a bit more specific than "ordered", but it serves me right :)

The funniest part is that this does not really matter for this discussion: insertion order is pretty much arbitrary when what you need is for key 0 to come first, key 1 to come next...

Re:Python Proposal (Inspired by Lua) by joshuamorton on Sunday August 27, 2023 @04:13PM (#37286219)
> Not to mention that this would have a performance penalty for dicts as they would have to keep an ordered set of integer keys in case they are used with *.

...why?

I don't love this proposal, but `{1:"a", 2:"b", 0:"c"} == {0:"c", 1:"a", 2:"b"}`, and so presumably either would have the same behavior when passed to `def f(foo, bar baz): ...`.

That the runtime would correct the order as part of the splat operation seems implied?

Re:Python Proposal (Inspired by Lua) by necovek on Monday August 28, 2023 @03:13PM (#37299064)
Passing a huge dict and assuming the runtime would magically do the sorting was exactly an implementation I ignored because it sounded preposterous to me.

Just imagine passing in a 500k item dict to ** by accident: dicts maintaining sorted state appeared the only sensible implementation to me.

Re:Python Proposal (Inspired by Lua) by joshuamorton on Monday August 28, 2023 @03:51PM (#37299562)
But again: why do you need to care about the whole sorted state thing? "Sorting" a dictionary keyed by index is linear time.

Like the cost of doing that isn't going to be greater than the existing cost of keyword arguments, which can already be arbitrarily ordered!

Like, what about this is more "preposterous" than passing a massive dict to kwargs already?

Re:Python Proposal (Inspired by Lua) by necovek on Tuesday August 29, 2023 @04:14PM (#37313296)
Passing a dict is passing by reference, and if the called method reads 5 elements from that dict, it's tiny constant time.

Sorting of a large collection happening implicitly is too expensive for what would generally be a benign mistake.

Re:Python Proposal (Inspired by Lua) by joshuamorton on Tuesday August 29, 2023 @06:44PM (#37315125)
But again: you don't actually need to sort anything. Like that's really important you don't need to sort anything!

Passing kwargs requires resolving n references, one for each kwarg. Passing args by index requires passing n references. Like in code:

    def convert_dict_to_args(f, magic_kwargs):
        out_args = []
        out_kwargs = {}
        for k, v in magic_kwargs:
            if isinstance(k, int):
                out_args[k] = v
            else:
                out_kwargs[k] = v
        return f(*out_args, **out_kwargs)
There's no sorting necessary! There's potential particular optimizations that might need to happen to make this work with python's opcodes for fast function calls, since out_args isn't constructed as a tuple which is internally optimized in function calls, but afaik you're already not getting those optimizations/fast paths if you're passing by name and/or using kwargs, so this isn't a change.

Edit: for context,

    >>> from timeit import timeit as t
    >>> t(stmt='f(1,2,3)', setup='def f(a, b, c): pass', number=int(1e8))
    5.765844099999981
    >>> t(stmt='f(*(1,2,3))', setup='def f(a, b, c): pass', number=int(1e8))
    5.840746500000023
    >>> t(stmt='f(a=1,b=2,c=3)', setup='def f(a, b, c): pass', number=int(1e8))
    7.400104499999998
    >>> t(stmt='f(**d)', setup='d=dict(a=1,b=2,c=3)\ndef f(a, b, c): pass', number=int(1e8))
    20.779321399999958
    >>> t(stmt='f(**d)', setup='d=dict()\ndef f(a=None, b=None, c=None): pass', number=int(1e8))
    9.583819900000094
So yeah using kwargs at all has a significant performance penalty, so you're already hitting performance penalties for using kwargs, keeping a sorted view isn't going to help sinificantly.
Re:Python Proposal (Inspired by Lua) by necovek on Wednesday August 30, 2023 @03:29PM (#37327643)
Since "sorting is not necessary", why don't you try:

  >> out_args = []
  >> out_args[3] = 'foo'
?

And if it's still not clear, pass

  magic_kwargs = { 'arg1': 'bar', 2: 'foo', 'arg2': 'yeah', 1: 'not', 0: 'really'}
to that helper you wrote.
Re:Python Proposal (Inspired by Lua) by joshuamorton on Wednesday August 30, 2023 @06:02PM (#37329723)
> >> out_args = [] >> out_args[3] = 'foo'

Yes, this should be a typeerror. I elided any kind of error checking, but you still don't need to sort to check that. You run into the same issue if you pass `*range(5)` to a function of one argument today.

> magic_kwargs = { 'arg1': 'bar', 2: 'foo', 'arg2': 'yeah', 1: 'not', 0: 'really'}

Again, this would be a TypeError (assuming the underlying function is defined as `def (arg1, arg2, ...) ` or something. And yet again, there's no need to sort anything to detect that error.

I feel like the thing you're getting at here is that you get index out of range errors given the half-assed implementation I did, which is absolutely true. It's buggy, it's broken, it's demo code. But you solve that by finding the max value of the integers keys of the dict, but finding the maximum value at worst requires a second linear scan, not a sort. You never need to sort the keys, you can always place them or error in linear time.

For a fuller implementation:

        def convert_dict_to_args(f, magic_kwargs):
            _max = 0
            for k in magic_kwargs.keys():
                if isinstance(k, int):
                    if k < 0: raise TypeError
                    if k > _max: _max = k
                if not isinstance(k, str):
                    raise TypeError 
            _SENTINEL = object()
            out_args = [_SENTINEL for _ in range(_max)
            out_kwargs = {}
            for k, v in magic_kwargs.items():
                if isinstance(k, int):
                    out_args[k] = v
                else:
                    out_kwargs[k] = v
            if _SENTINEL in out_args:
                raise TypeError
            return f(*out_args, **out_kwargs)
Re:Python Proposal (Inspired by Lua) by necovek on Thursday August 31, 2023 @12:59AM (#37332640)
You've basically sorted those int-keyed elements into a list, only optimized by making a few assumptions (eg. that all indexes are present up to a max: if you want to error check that like a real implementation would have to, you lose some of that optimization since you now have to keep track of missing elements, which is at least another loop through the list).

All of these optimizations are applicable to sorting a sparse, bounded list of integers, but that's still sorting them.

Re:Python Proposal (Inspired by Lua) by joshuamorton on Thursday August 31, 2023 @02:15PM (#37341490)
> You've basically sorted those int-keyed elements into a list

No, I've copied them into a list. There was no sorting. I've never compared elements or done any nlogn operations. I've shuffled, perhaps, or ordered, but not sorted. That's a meaningful distinction.

> have to keep track of missing elements, which is at least another loop through the list).

The implementation I have is complete, it errors if the array is sparse. You need at most two loops through the dict, which if you wanted to you could avoid with a small (constant time and constant space) change to the underlying dict implementation to cache the max integer key stored and require only a single iteration.

You can technically do even faster than that by making the iteration over the number of positional args, not total args, but in practice that would probably be slower.

And it's all going to be just as fast as kwargs today.

Like jumping back a bit your argument is

> Passing a dict is passing by reference, and if the called method reads 5 elements from that dict, it's tiny constant time.

But if you pass **kwargs to a method, the dict size is unbounded, so it's O(n), not constant! Exactly the same as now. And if the method being called declares it's params explicitly, it's still constant since you can immediately error on the first unknown parameter!

If it was constant before, it's still constant. If it was linear before, it's still linear. And you never need to sort anything.

Just to further clarify, above you say

> and **kwargs has every element read in O(1) time or it gets passed along by reference.

Reading every element in O(1) time is O(n)! And is precisely the same as what's happening now.

Re:Python Proposal (Inspired by Lua) by necovek on Thursday August 31, 2023 @03:09PM (#37342232)
If you only need 5 kwargs, reading 5 elements from a 500k element dict is O(1). Just finding the max index is O(n). You might be right that even passing 500k element dict to a function **kwargs has terrible performance today anyway (I haven't checked but it does sound plausible from your timing tests).

It seems like we are now looking for imprecisions in each other's reply and discussion does not seem to be constructive: we are agreeing on rough potential implementations, it's just that we differ whether that should be acceptable or not.

That's fine, you are not convincing me and I am not convincing you, so we might as well drop it. It's not like this proposal is happening :)

Have a good day!

Re:Python Proposal (Inspired by Lua) by necovek on Tuesday August 29, 2023 @04:21PM (#37313407)
Note that we are comparing against existing `*args, **kwargs`: no sorting is needed at all, as *args is processed by order, and **kwargs has every element read in O(1) time or it gets passed along by reference.