Thanks for saving some of the fun for me ![]()
I didn’t expect this to inspire as much discussion as it has, and I’m finding the conversation really interesting. Looking through the thread, though, it does seem like many of the arguments come down to personal preference; and perhaps to no one’s surprise, this kind of subjective value judgement is a place where multiple reasonable people can reasonably disagree.
Here’s my attempt to briefly summarize my opinion:
- 
It seems like we all value “simplicity,” but from going through the thread, I think we may disagree about what “simplicity” means, or at least about the relative importance of various kinds of simplicity. One primary way I interact with Python is through teaching, and so one kind of simplicity I value super highly is simplicity for someone new learning the language. To this end, internal consistency is very important to me.
 - 
Given PEP 448, we have (among other things) multiple ways to create/merge dictionaries:
{**x}anddict(x)andd.update(x). Once one has a feeling for what kinds ofxare appropriate for one of these forms, it would be nice if that feeling generalized to all of them rather than requiring memorization of which types are allowed in which case. - 
The change proposed here is (AFAICT) fully backwards-compatible, and it can be implemented with no little-to-no impact on performance of existing code.
 - 
I have not personally been convinced by any of the examples in this thread that this change would make anything harder to read/understand.
 
Happy to continue the conversation!
I agree with this in principle, and I would agree with it here, too, were it not for the feeling that in this case, keeping this interface limited makes it inconsistent with other existing ways of doing the same thing. The way I see it, implementing this idea wouldn’t be adding a whole new permissive interface; it would be making one existing interface more permissive to make it more similar to existing, similar interfaces.
I think about this the same way. I agree in principle, but given that that decision has been made, I favor making things internally consistent over other considerations.
Yes, and intentionally so. The proposal, as I see it, is allowing pairs in all the different ways we can create/update dictionaries, rather than only in some, and maintaining the same semantics in all of those cases, with the intention that once you know the quirks with one of these things, you know the quirks of all of them.
I’m not sure I agree, but even granting that, it would only be as leaky as dict.update is, and in the same way.
I also think it’s not quite right to say that the behavior of **mapping doesn’t already depend on the contents of mapping; in some cases, like keyword argument unpacking, the contents very much matter (keys must be strings and match the signature of the function being called).  So the user still has to be careful.
It does improve performance if what you’re starting with is not a dictionary but rather an iterable of pairs, which is the case I’m thinking about here. Here’s a little test case (of course, I know I actually have a dict to start with in this code, but let’s imagine I didn’t):
Quick Test Code
import timeit
import math
y = list(math.__dict__.items())
N = 1_000_000
t = timeit.timeit(lambda: {**dict(y)}, number=N)
t2 = timeit.timeit(lambda: {**y}, number=N)
print(f'{{**dict(y)}} {t:.04f} seconds')
print(f'{{**y}} {t2:.04f} seconds')
adj = "faster" if t < t2 else "slower"
print(f'wrapping in dict is {adj} ({t / t2:.02f}x)')
t3 = timeit.timeit(lambda: {}.update(y), number=N)
print()
print(f'{{}}.update(y) {t3:.04f} seconds')
print(f'{{**y}} {t2:.04f} seconds')
adj = "faster" if t3 < t2 else "slower"
print(f'calling update is {adj} ({t3 / t2:.02f}x)')
def func(**kwargs):
    return kwargs.keys()
t4 = timeit.timeit(lambda: func(**dict(y)), number=N)
t5 = timeit.timeit(lambda: func(**y), number=N)
print()
print(f'func(**dict(y)) {t4:.04f} seconds')
print(f'func(**y) {t5:.04f} seconds')
adj = "faster" if t4 < t5 else "slower"
print(f'wrapping in dict is {adj} ({t4 / t5:.02f}x)')
Running this test through my quick hacky implementation, a fairly typical output looks like:
{**dict(y)} 1.9673 seconds
{**y} 1.6413 seconds
wrapping in dict is slower (1.20x)
{}.update(y) 1.7020 seconds
{**y} 1.6413 seconds
calling update is slower (1.04x)
func(**dict(y)) 3.8099 seconds
func(**y) 3.4797 seconds
wrapping in dict is slower (1.09x)
This happens with no slowdown of the pure-dict case since we never hit the new branching point in that case, though it would be slightly slower than the current implementation for custom types that support .keys and .__getitem__, due to the extra check for the existence of .keys.  We also obviously save on memory by avoiding the creation of an intermediate dict object.
Of course, this is going to be way slower than the fast path have for dict/dict merges, but IMO that’s beside the point.
I also put together an Emscripten-based demo for this idea in case anyone wants to fiddle with it without needing to compile yourself: