diff --git a/pep-0584.txt b/pep-0584.rst old mode 100755 new mode 100644 similarity index 57% rename from pep-0584.txt rename to pep-0584.rst index e40c4f06e..d31ab48d9 --- a/pep-0584.txt +++ b/pep-0584.rst @@ -18,15 +18,19 @@ PEP-584 Dict addition and subtraction Abstract -------- -This PEP suggests adding merge ``+`` and difference ``-`` operators to the built-in ``dict`` class. +This PEP suggests adding merge ``+`` and difference ``-`` operators to +the built-in ``dict`` class. -The merge operator will have the same relationship to the ``dict.update`` method as the list concatenation operator has to ``list.extend``, with dict difference being defined analogously. +The merge operator will have the same relationship to the +``dict.update`` method as the list concatenation operator has to +``list.extend``, with dict difference being defined analogously. Examples -------- -Dict addition will return a new dict containing the left operand merged with the right operand. +Dict addition will return a new dict containing the left operand +merged with the right operand. >>> d = {'spam': 1, 'eggs': 2, 'cheese': 3} >>> e = {'cheese': 'cheddar', 'aardvark': 'Ethel'} @@ -41,7 +45,10 @@ The augmented assignment version operates in-place. >>> print(d) {'spam': 1, 'eggs': 2, 'cheese': 'cheddar', 'aardvark': 'Ethel'} -Analogously with list addition, the operator version is more restrictive, and requires that both arguments are dicts, while the augmented assignment version allows anything the ``update`` method allows, such as iterables of key/value pairs. +Analogously with list addition, the operator version is more +restrictive, and requires that both arguments are dicts, while the +augmented assignment version allows anything the ``update`` method +allows, such as iterables of key/value pairs. >>> d + [('spam', 999)] Traceback (most recent call last): @@ -53,7 +60,8 @@ Analogously with list addition, the operator version is more restrictive, and re {'spam': 999, 'eggs': 2, 'cheese': 'cheddar', 'aardvark': 'Ethel'} -Dict difference ``-`` will return a new dict containing the items from the left operand which are not in the right operand. +Dict difference ``-`` will return a new dict containing the items from +the left operand which are not in the right operand. >>> d = {'spam': 1, 'eggs': 2, 'cheese': 3} >>> e = {'cheese': 'cheddar', 'aardvark': 'Ethel'} @@ -69,7 +77,9 @@ Augmented assignment will operate in place. {'spam': 1, 'eggs': 2} -Like the merge operator and list concatenation, the difference operator requires both operands to be dicts, while the augmented version allows any iterable of keys. +Like the merge operator and list concatenation, the difference +operator requires both operands to be dicts, while the augmented +version allows any iterable of keys. >>> d - {'spam', 'parrot'} Traceback (most recent call last): @@ -88,11 +98,19 @@ Like the merge operator and list concatenation, the difference operator requires Semantics --------- -For the merge operator, if a key appears in both operands, the last-seen value (i.e. that from the right-hand operand) wins. This shows that dict addition is not commutative, in general ``d + e`` will not equal ``e + d``. This joins a number of other non-commutative addition operators among the builtins, including lists, tuples, strings and bytes. +For the merge operator, if a key appears in both operands, the +last-seen value (i.e. that from the right-hand operand) wins. This +shows that dict addition is not commutative, in general ``d + e`` will +not equal ``e + d``. This joins a number of other non-commutative +addition operators among the builtins, including lists, tuples, +strings and bytes. -Having the last-seen value wins makes the merge operator match the semantics of the ``update`` method, so that ``d + e`` is an operator version of ``d.update(e)``. +Having the last-seen value wins makes the merge operator match the +semantics of the ``update`` method, so that ``d + e`` is an operator +version of ``d.update(e)``. -The error messages shown above are not part of the API, and may change at any time. +The error messages shown above are not part of the API, and may change +at any time. Rejected semantics @@ -100,43 +118,64 @@ Rejected semantics Rejected alternatives semantics for ``d + e`` include: -- Add only new keys from ``e``, without overwriting existing keys in ``d``. This may be done by reversing the operands ``e + d``, or using dict difference first, ``d + (e - d)``. The later is especially useful for the in-place version ``d += (e - d)``. +- Add only new keys from ``e``, without overwriting existing keys in + ``d``. This may be done by reversing the operands ``e + d``, or + using dict difference first, ``d + (e - d)``. The later is + especially useful for the in-place version ``d += (e - d)``. -- Raise an exception if there are duplicate keys. This seems unnecessarily restrictive and is not likely to be useful in practice. For example, updating default configuration values with user-supplied values would most often fail under the requirement that keys are unique:: +- Raise an exception if there are duplicate keys. This seems + unnecessarily restrictive and is not likely to be useful in + practice. For example, updating default configuration values with + user-supplied values would most often fail under the requirement + that keys are unique:: prefs = site_defaults + user_defaults + document_prefs -- Add the values of d2 to the corresponding values of d1. This is the behaviour implemented by ``collections.Counter``. +- Add the values of d2 to the corresponding values of d1. This is the + behaviour implemented by ``collections.Counter``. Syntax ------ -An alternative to the ``+`` operator is the pipe ``|`` operator, which is used for set union. This suggestion did not receive much support on Python-Ideas. +An alternative to the ``+`` operator is the pipe ``|`` operator, which +is used for set union. This suggestion did not receive much support +on Python-Ideas. -The ``+`` operator was strongly preferred on Python-Ideas.[1] It is more familiar than the pipe operator, matches nicely with ``-`` as a pair, and the Counter subclass already uses ``+`` for merging. +The ``+`` operator was strongly preferred on Python-Ideas.[1] It is +more familiar than the pipe operator, matches nicely with ``-`` as a +pair, and the Counter subclass already uses ``+`` for merging. Current Alternatives -------------------- -To create a new dict containing the merged items of two (or more) dicts, one can currently write:: +To create a new dict containing the merged items of two (or more) +dicts, one can currently write:: {**d1, **d2} -but this is neither obvious nor easily discoverable. It is only guaranteed to work if the keys are all strings. If the keys are not strings, it currently works in CPython, but it may not work with other implementations, or future versions of CPython[2]. +but this is neither obvious nor easily discoverable. It is only +guaranteed to work if the keys are all strings. If the keys are not +strings, it currently works in CPython, but it may not work with other +implementations, or future versions of CPython[2]. -It is also limited to returning a built-in dict, not a subclass, unless re-written as ``MyDict(**d1, **d2)``, in which case non-string keys will raise TypeError. +It is also limited to returning a built-in dict, not a subclass, +unless re-written as ``MyDict(**d1, **d2)``, in which case non-string +keys will raise TypeError. -There is currently no way to perform dict subtraction except through a manual loop. +There is currently no way to perform dict subtraction except through a +manual loop. Implementation -------------- -The implementation will be in C. (The author of this PEP would like to make it known that he is not able to write the implemention.) +The implementation will be in C. (The author of this PEP would like +to make it known that he is not able to write the implemention.) -An approximate pure-Python implementation of the merge operator will be:: +An approximate pure-Python implementation of the merge operator will +be:: def __add__(self, other): if isinstance(other, dict): @@ -153,9 +192,12 @@ An approximate pure-Python implementation of the merge operator will be:: return new return NotImplemented -Note that the result type will be the type of the left operand; in the event of matching keys, the winner is the right operand. +Note that the result type will be the type of the left operand; in the +event of matching keys, the winner is the right operand. -Augmented assignment will just call the ``update`` method. This is analogous to the way ``list +=`` calls the ``extend`` method, which accepts any iterable, not just lists. +Augmented assignment will just call the ``update`` method. This is +analogous to the way ``list +=`` calls the ``extend`` method, which +accepts any iterable, not just lists. def __iadd__(self, other): self.update(other) @@ -180,7 +222,9 @@ An approximate pure-Python implementation of the difference operator will be:: return new return NotImplemented -Augmented assignment will operate on equivalent terms to ``update``. If the operand has a key method, it will be used, otherwise the operand will be iterated over:: +Augmented assignment will operate on equivalent terms to ``update``. +If the operand has a key method, it will be used, otherwise the +operand will be iterated over:: def __isub__(self, other): if hasattr(other, 'keys'): @@ -193,7 +237,11 @@ Augmented assignment will operate on equivalent terms to ``update``. If the ope del self[k] -These semantics are intended to match those of ``update`` as closely as possible. For the dict built-in itself, calling ``keys`` is redundant as iteration over a dict iterates over its keys; but for subclasses or other mappings, ``update`` prefers to use the keys method. +These semantics are intended to match those of ``update`` as closely +as possible. For the dict built-in itself, calling ``keys`` is +redundant as iteration over a dict iterates over its keys; but for +subclasses or other mappings, ``update`` prefers to use the keys +method. .. attention:: The above paragraph may be inaccurate. Although the dict docstring states that ``keys`` @@ -206,15 +254,25 @@ Contra-indications (Or when to avoid using these new operators.) -For merging multiple dicts, the ``d1 + d2 + d3 + d4 + ...`` idiom will suffer from the same unfortunate O(N\*\*2) Big Oh performance as does list and tuple addition, and for similar reasons. If one expects to be merging a large number of dicts where performance is an issue, it may be better to use an explicit loop and in-place merging:: +For merging multiple dicts, the ``d1 + d2 + d3 + d4 + ...`` idiom will +suffer from the same unfortunate O(N\*\*2) Big Oh performance as does +list and tuple addition, and for similar reasons. If one expects to +be merging a large number of dicts where performance is an issue, it +may be better to use an explicit loop and in-place merging:: new = {} for d in many_dicts: new += d -This is unlikely to be a problem in practice as most uses of the merge operator are expected to only involve a small number of dicts. Similarly, most uses of list and tuple concatenation only use a few objects. +This is unlikely to be a problem in practice as most uses of the merge +operator are expected to only involve a small number of dicts. +Similarly, most uses of list and tuple concatenation only use a few +objects. -Using the dict augmented assignment operators on a dict inside a tuple (or other immutable data structure) will lead to the same problem that occurs with list concatenation[3], namely the in-place addition will succeed, but the operation will raise an exception. +Using the dict augmented assignment operators on a dict inside a tuple +(or other immutable data structure) will lead to the same problem that +occurs with list concatenation[3], namely the in-place addition will +succeed, but the operation will raise an exception. >>> a_tuple = ({'spam': 1, 'eggs': 2}, None) >>> a_tuple[0] += {'spam': 999} @@ -230,13 +288,19 @@ Similar remarks apply to the ``-`` operator. Other discussions ----------------- -`Latest discussion which motivated this PEP `_ +`Latest discussion which motivated this PEP +`_ `Ticket on the bug tracker `_ -`A previous discussion `_ and `commentary on it `_. Note that the author of this PEP was skeptical of this proposal at the time. +`A previous discussion +`_ +and `commentary on it `_. Note that +the author of this PEP was skeptical of this proposal at the time. -`How to merge dictionaries `_ in idiomatic Python. +`How to merge dictionaries +`_ +in idiomatic Python. Open questions @@ -248,11 +312,14 @@ Should these operators be part of the ABC ``Mapping`` API? References ---------- -[1] Guido's declaration that plus wins over pipe: https://mail.python.org/pipermail/python-ideas/2019-February/055519.html +[1] Guido's declaration that plus wins over pipe: +https://mail.python.org/pipermail/python-ideas/2019-February/055519.html -[2] Non-string keys: https://bugs.python.org/issue35105 and https://mail.python.org/pipermail/python-dev/2018-October/155435.html +[2] Non-string keys: https://bugs.python.org/issue35105 and +https://mail.python.org/pipermail/python-dev/2018-October/155435.html -[3] Behaviour in tuples: https://docs.python.org/3/faq/programming.html#why-does-a-tuple-i-item-raise-an-exception-when-the-addition-works +[3] Behaviour in tuples: +https://docs.python.org/3/faq/programming.html#why-does-a-tuple-i-item-raise-an-exception-when-the-addition-works Copyright