Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion Lib/_collections_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def _f(): pass
"Mapping", "MutableMapping",
"MappingView", "KeysView", "ItemsView", "ValuesView",
"Sequence", "MutableSequence",
"ByteString", "Buffer",
"Buffer",
]

# This module has been renamed from collections.abc to _collections_abc to
Expand Down Expand Up @@ -823,6 +823,7 @@ def __eq__(self, other):

__reversed__ = None

Mapping.register(frozendict)
Mapping.register(mappingproxy)
Mapping.register(framelocalsproxy)

Expand Down Expand Up @@ -1170,3 +1171,13 @@ def __iadd__(self, values):

MutableSequence.register(list)
MutableSequence.register(bytearray)

_deprecated_ByteString = globals().pop("ByteString")

def __getattr__(attr):
if attr == "ByteString":
import warnings
warnings._deprecated("collections.abc.ByteString", remove=(3, 17))
globals()["ByteString"] = _deprecated_ByteString
return _deprecated_ByteString
raise AttributeError(f"module 'collections.abc' has no attribute {attr!r}")
91 changes: 73 additions & 18 deletions Lib/collections/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
_sys.modules['collections.abc'] = _collections_abc
abc = _collections_abc

lazy from copy import copy as _copy
lazy from heapq import nlargest as _nlargest
from itertools import chain as _chain
from itertools import repeat as _repeat
from itertools import starmap as _starmap
Expand Down Expand Up @@ -59,8 +61,6 @@
except ImportError:
pass

heapq = None # Lazily imported


################################################################################
### OrderedDict
Expand Down Expand Up @@ -328,14 +328,14 @@ def __ior__(self, other):
return self

def __or__(self, other):
if not isinstance(other, dict):
if not isinstance(other, (dict, frozendict)):
return NotImplemented
new = self.__class__(self)
new.update(other)
return new

def __ror__(self, other):
if not isinstance(other, dict):
if not isinstance(other, (dict, frozendict)):
return NotImplemented
new = self.__class__(other)
new.update(self)
Expand Down Expand Up @@ -637,12 +637,7 @@ def most_common(self, n=None):
if n is None:
return sorted(self.items(), key=_itemgetter(1), reverse=True)

# Lazy import to speedup Python startup time
global heapq
if heapq is None:
import heapq

return heapq.nlargest(n, self.items(), key=_itemgetter(1))
return _nlargest(n, self.items(), key=_itemgetter(1))

def elements(self):
'''Iterator over elements repeating each as many times as its count.
Expand Down Expand Up @@ -779,23 +774,27 @@ def __repr__(self):
# When the multiplicities are all zero or one, multiset operations
# are guaranteed to be equivalent to the corresponding operations
# for regular sets.
#
# Given counter multisets such as:
# cp = Counter(a=1, b=0, c=1)
# cq = Counter(c=1, d=0, e=1)
#
# The corresponding regular sets would be:
# sp = {'a', 'c'}
# sq = {'c', 'e'}
#
# All of the following relations would hold:
# set(cp + cq) == sp | sq
# set(cp - cq) == sp - sq
# set(cp | cq) == sp | sq
# set(cp & cq) == sp & sq
# (cp == cq) == (sp == sq)
# (cp != cq) == (sp != sq)
# (cp <= cq) == (sp <= sq)
# (cp < cq) == (sp < sq)
# (cp >= cq) == (sp >= sq)
# (cp > cq) == (sp > sq)
# set(cp + cq) == sp | sq
# set(cp - cq) == sp - sq
# set(cp | cq) == sp | sq
# set(cp & cq) == sp & sq
# set(cp ^ cq) == sp ^ sq

def __eq__(self, other):
'True if all counts agree. Missing counts are treated as zero.'
Expand Down Expand Up @@ -908,6 +907,33 @@ def __and__(self, other):
result[elem] = newcount
return result

def __xor__(self, other):
'''Symmetric difference. Absolute value of count differences.

The symmetric difference p ^ q is equivalent to:

(p - q) | (q - p).

For each element, symmetric difference gives the same result as:

max(p[elem], q[elem]) - min(p[elem], q[elem])

>>> Counter(a=5, b=3, c=2, d=2) ^ Counter(a=1, b=3, c=5, e=1)
Counter({'a': 4, 'c': 3, 'd': 2, 'e': 1})

'''
if not isinstance(other, Counter):
return NotImplemented
result = Counter()
for elem, count in self.items():
newcount = abs(count - other[elem])
if newcount:
result[elem] = newcount
for elem, count in other.items():
if elem not in self and count:
result[elem] = abs(count)
return result

def __pos__(self):
'Adds an empty counter, effectively stripping negative and zero counts'
result = Counter()
Expand Down Expand Up @@ -990,6 +1016,22 @@ def __iand__(self, other):
self[elem] = other_count
return self._keep_positive()

def __ixor__(self, other):
'''Inplace symmetric difference. Absolute value of count differences.

>>> c = Counter(a=5, b=3, c=2, d=2)
>>> c ^= Counter(a=1, b=3, c=5, e=1)
>>> c
Counter({'a': 4, 'c': 3, 'd': 2, 'e': 1})

'''
for elem, count in self.items():
self[elem] = abs(count - other[elem])
for elem, count in other.items():
if elem not in self:
self[elem] = abs(count)
return self._keep_positive()


########################################################################
### ChainMap
Expand Down Expand Up @@ -1177,14 +1219,14 @@ def __repr__(self):
def __or__(self, other):
if isinstance(other, UserDict):
return self.__class__(self.data | other.data)
if isinstance(other, dict):
if isinstance(other, (dict, frozendict)):
return self.__class__(self.data | other)
return NotImplemented

def __ror__(self, other):
if isinstance(other, UserDict):
return self.__class__(other.data | self.data)
if isinstance(other, dict):
if isinstance(other, (dict, frozendict)):
return self.__class__(other | self.data)
return NotImplemented

Expand All @@ -1205,16 +1247,29 @@ def __copy__(self):
def copy(self):
if self.__class__ is UserDict:
return UserDict(self.data.copy())
import copy
data = self.data
try:
self.data = {}
c = copy.copy(self)
c = _copy(self)
finally:
self.data = data
c.update(self)
return c


# This method has a default implementation in MutableMapping, but dict's
# equivalent is last-in, first-out instead of first-in, first-out.
def popitem(self):
"""Remove and return a (key, value) pair as a 2-tuple.

Removes pairs in the same order as the wrapped mapping's popitem()
method. For dict objects (the default), that order is last-in,
first-out (LIFO).
Raises KeyError if the UserDict is empty.
"""
return self.data.popitem()


@classmethod
def fromkeys(cls, iterable, value=None):
d = cls()
Expand Down
Loading