And the performance is pretty much the same, just a lot nicer syntax
In [1]: x = {1:2, 3:4}
In [2]: %timeit x[3] = 5
48.6 ns ± 1.18 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
In [3]: %timeit y = x.copy(); y[3] = 5
189 ns ± 3.23 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
In [4]: %timeit {**x, 3: 5}
182 ns ± 3.3 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
Edit: It also seems to be pretty constant time if you're just merging:
In [16]: %timeit {**x, **y, **z}
180 ns ± 1.29 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
In [17]: %timeit {**x, **y, **z, 3: 5}
278 ns ± 18.2 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [19]: dis.dis(lambda: {**x, **y, **z, 3: 5})
0 LOAD_GLOBAL 0 (x)
2 LOAD_GLOBAL 1 (y)
4 LOAD_GLOBAL 2 (z)
6 LOAD_CONST 1 (3)
8 LOAD_CONST 2 (5)
10 BUILD_MAP 1
12 BUILD_MAP_UNPACK 4
14 RETURN_VALUE
In [20]: dis.dis(lambda: {**x, **y, **z})
0 LOAD_GLOBAL 0 (x)
2 LOAD_GLOBAL 1 (y)
4 LOAD_GLOBAL 2 (z)
6 BUILD_MAP_UNPACK 3
8 RETURN_VALUE