Opened 6 years ago

Closed 6 years ago

Last modified 6 years ago

#28909 closed Cleanup/optimization (fixed)

Use unpacking generalizations added in Python 3.5

Reported by: Nick Pope Owned by: Nick Pope
Component: Core (Other) Version: dev
Severity: Normal Keywords: unpacking
Cc: Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Now that master is Python 3.5+ we can look to using unpacking generalizations for dict, list, set and tuple.

https://docs.python.org/3.5/whatsnew/3.5.html#whatsnew-pep-448

One benefit to this is that slow function/method calls can avoided and specific operations for unpacking are used instead:

$ python -c "import dis; dis.dis(\"d0 = {'a': 1}; d1 = {'b': 2}; x = {**d0, **d1}\")"
  1           0 LOAD_CONST               0 ('a')
              2 LOAD_CONST               1 (1)
              4 BUILD_MAP                1
              6 STORE_NAME               0 (d0)
              8 LOAD_CONST               2 ('b')
             10 LOAD_CONST               3 (2)
             12 BUILD_MAP                1
             14 STORE_NAME               1 (d1)
             16 LOAD_NAME                0 (d0)
             18 LOAD_NAME                1 (d1)
             20 BUILD_MAP_UNPACK         2
             22 STORE_NAME               2 (x)
             24 LOAD_CONST               4 (None)
             26 RETURN_VALUE
    
$ python -c "import dis; dis.dis(\"d0 = {'a': 1}; d1 = {'b': 2}; x = dict(d0, **d1)\")"
  1           0 LOAD_CONST               0 ('a')
              2 LOAD_CONST               1 (1)
              4 BUILD_MAP                1
              6 STORE_NAME               0 (d0)
              8 LOAD_CONST               2 ('b')
             10 LOAD_CONST               3 (2)
             12 BUILD_MAP                1
             14 STORE_NAME               1 (d1)
             16 LOAD_NAME                2 (dict)
             18 LOAD_NAME                0 (d0)
             20 BUILD_TUPLE              1
             22 LOAD_NAME                1 (d1)
             24 CALL_FUNCTION_EX         1
             26 STORE_NAME               3 (x)
             28 LOAD_CONST               4 (None)
             30 RETURN_VALUE
    
$ python -c "import dis; dis.dis(\"d0 = {'a': 1}; d1 = {'b': 2}; x = d0.copy(); x.update(d1)\")"
  1           0 LOAD_CONST               0 ('a')
              2 LOAD_CONST               1 (1)
              4 BUILD_MAP                1
              6 STORE_NAME               0 (d0)
              8 LOAD_CONST               2 ('b')
             10 LOAD_CONST               3 (2)
             12 BUILD_MAP                1
             14 STORE_NAME               1 (d1)
             16 LOAD_NAME                0 (d0)
             18 LOAD_ATTR                2 (copy)
             20 CALL_FUNCTION            0
             22 STORE_NAME               3 (x)
             24 LOAD_NAME                3 (x)
             26 LOAD_ATTR                4 (update)
             28 LOAD_NAME                1 (d1)
             30 CALL_FUNCTION            1
             32 POP_TOP
             34 LOAD_CONST               4 (None)
             36 RETURN_VALUE
    
$ python -m timeit "d0 = {'a': 1}; d1 = {'b': 2}; x = {**d0, **d1}"
1000000 loops, best of 3: 0.24 usec per loop
    
$ python -m timeit "d0 = {'a': 1}; d1 = {'b': 2}; x = dict(d0, **d1)"
1000000 loops, best of 3: 0.39 usec per loop
    
$ python -m timeit "d0 = {'a': 1}; d1 = {'b': 2}; x = d0.copy(); x.update(d1)"
1000000 loops, best of 3: 0.45 usec per loop

Obviously these are fairly contrived examples, and in many cases there can be multiple calls to methods such as dict.update() or list.extend().

Here are examples of a number of changes that could be made:

# Unpack directly into initial definition:
-d = {...}
-d.update(extra)
+d = {..., **extra}

# Unpack instead of using dict() to combine (#1):
-d = dict(original, **extra)
+d = {**original, **extra}

# Unpack instead of using dict() to combine (#2):
-d = dict(original, ...)
+d = {**original, ...}

# Unpacking peforms a shallow copy like dict():
-d = dict(original)
-d.update(extra)
+d = {**original, **extra}

# Unpacking peforms a shallow copy like dict.copy():
-d = original.copy()
-d.update(extra)
+d = {**original, **extra}

# Unpacking peforms a shallow copy like copy.copy():
-import copy
-d = copy.copy(original)
-d.update(extra)
+d = {**original, **extra}

# More complex examples that become simple:
-d = dict(original, ...)
-d.update(extra or {})
+d = {**original, ... **(extra or {})}

# Unpacking for sets also makes things simple:
-s = {...}
-s.update(extra)
-s.update(override)
+s = {..., *extra, *override}

# Unpacking for lists also makes things simple:
-l = [...]
-l.extend(extra)
-l.extend(override)
+l = [..., *extra, *override]

Change History (5)

comment:1 by Tim Graham, 6 years ago

Component: UncategorizedCore (Other)
Triage Stage: UnreviewedReady for checkin
Last edited 6 years ago by Tim Graham (previous) (diff)

comment:2 by Tim Graham <timograham@…>, 6 years ago

Resolution: fixed
Status: assignedclosed

In d13a9e4:

Fixed #28909 -- Simplified code using tuple/list/set/dict unpacking.

comment:3 by Tim Graham <timograham@…>, 6 years ago

In f3a98224:

Refs #28909 -- Simplifed code using unpacking generalizations.

comment:4 by GitHub <noreply@…>, 6 years ago

In 074a2f7f:

Refs #28909 -- Simplifed code using unpacking generalizations.

comment:5 by Tim Graham <timograham@…>, 6 years ago

In 8ef8bc0f:

Refs #28909 -- Simplifed code using unpacking generalizations.

Note: See TracTickets for help on using tickets.
Back to Top