Skip to content

Commit d6390f3

Browse files
JelleZijlstrailevkivskyi
authored andcommitted
Add typing.Counter and typing.ChainMap (#366)
* Add typing.Counter Pretty much copied from deque. * also add ChainMap Only to the Python 3 version because ChainMap was added in 3.3. * fix for 3.2 Apparently typing still supports 3.2 * fix flake8 * fix base class of Counter * move counter below Dict * clarify error message, add test * allow instantiation * fix reliance on type cache
1 parent 33560c8 commit d6390f3

File tree

4 files changed

+102
-0
lines changed

4 files changed

+102
-0
lines changed

python2/test_typing.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1284,6 +1284,9 @@ def test_deque(self):
12841284
class MyDeque(typing.Deque[int]): pass
12851285
self.assertIsInstance(MyDeque(), collections.deque)
12861286

1287+
def test_counter(self):
1288+
self.assertIsSubclass(collections.Counter, typing.Counter)
1289+
12871290
def test_set(self):
12881291
self.assertIsSubclass(set, typing.Set)
12891292
self.assertNotIsSubclass(frozenset, typing.Set)
@@ -1358,6 +1361,23 @@ def test_deque_instantiation(self):
13581361
class D(typing.Deque[T]): pass
13591362
self.assertIs(type(D[int]()), D)
13601363

1364+
def test_counter_instantiation(self):
1365+
self.assertIs(type(typing.Counter()), collections.Counter)
1366+
self.assertIs(type(typing.Counter[T]()), collections.Counter)
1367+
self.assertIs(type(typing.Counter[int]()), collections.Counter)
1368+
class C(typing.Counter[T]): pass
1369+
self.assertIs(type(C[int]()), C)
1370+
1371+
def test_counter_subclass_instantiation(self):
1372+
1373+
class MyCounter(typing.Counter[int]):
1374+
pass
1375+
1376+
d = MyCounter()
1377+
self.assertIsInstance(d, MyCounter)
1378+
self.assertIsInstance(d, typing.Counter)
1379+
self.assertIsInstance(d, collections.Counter)
1380+
13611381
def test_no_set_instantiation(self):
13621382
with self.assertRaises(TypeError):
13631383
typing.Set()

python2/typing.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
'SupportsInt',
5454

5555
# Concrete collection types.
56+
'Counter',
5657
'Deque',
5758
'Dict',
5859
'DefaultDict',
@@ -1785,6 +1786,16 @@ def __new__(cls, *args, **kwds):
17851786
return _generic_new(collections.defaultdict, cls, *args, **kwds)
17861787

17871788

1789+
class Counter(collections.Counter, Dict[T, int]):
1790+
__slots__ = ()
1791+
__extra__ = collections.Counter
1792+
1793+
def __new__(cls, *args, **kwds):
1794+
if _geqv(cls, Counter):
1795+
return collections.Counter(*args, **kwds)
1796+
return _generic_new(collections.Counter, cls, *args, **kwds)
1797+
1798+
17881799
# Determine what base class to use for Generator.
17891800
if hasattr(collections_abc, 'Generator'):
17901801
# Sufficiently recent versions of 3.5 have a Generator ABC.

src/test_typing.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -910,6 +910,11 @@ class MyDict(typing.Dict[T, T]): ...
910910
class MyDef(typing.DefaultDict[str, T]): ...
911911
self.assertIs(MyDef[int]().__class__, MyDef)
912912
self.assertIs(MyDef[int]().__orig_class__, MyDef[int])
913+
# ChainMap was added in 3.3
914+
if sys.version_info >= (3, 3):
915+
class MyChain(typing.ChainMap[str, T]): ...
916+
self.assertIs(MyChain[int]().__class__, MyChain)
917+
self.assertIs(MyChain[int]().__orig_class__, MyChain[int])
913918

914919
def test_all_repr_eq_any(self):
915920
objs = (getattr(typing, el) for el in typing.__all__)
@@ -1705,6 +1710,9 @@ def test_deque(self):
17051710
class MyDeque(typing.Deque[int]): ...
17061711
self.assertIsInstance(MyDeque(), collections.deque)
17071712

1713+
def test_counter(self):
1714+
self.assertIsSubclass(collections.Counter, typing.Counter)
1715+
17081716
def test_set(self):
17091717
self.assertIsSubclass(set, typing.Set)
17101718
self.assertNotIsSubclass(frozenset, typing.Set)
@@ -1772,13 +1780,50 @@ class MyDefDict(typing.DefaultDict[str, int]):
17721780
self.assertIsSubclass(MyDefDict, collections.defaultdict)
17731781
self.assertNotIsSubclass(collections.defaultdict, MyDefDict)
17741782

1783+
@skipUnless(sys.version_info >= (3, 3), 'ChainMap was added in 3.3')
1784+
def test_chainmap_instantiation(self):
1785+
self.assertIs(type(typing.ChainMap()), collections.ChainMap)
1786+
self.assertIs(type(typing.ChainMap[KT, VT]()), collections.ChainMap)
1787+
self.assertIs(type(typing.ChainMap[str, int]()), collections.ChainMap)
1788+
class CM(typing.ChainMap[KT, VT]): ...
1789+
self.assertIs(type(CM[int, str]()), CM)
1790+
1791+
@skipUnless(sys.version_info >= (3, 3), 'ChainMap was added in 3.3')
1792+
def test_chainmap_subclass(self):
1793+
1794+
class MyChainMap(typing.ChainMap[str, int]):
1795+
pass
1796+
1797+
cm = MyChainMap()
1798+
self.assertIsInstance(cm, MyChainMap)
1799+
1800+
self.assertIsSubclass(MyChainMap, collections.ChainMap)
1801+
self.assertNotIsSubclass(collections.ChainMap, MyChainMap)
1802+
17751803
def test_deque_instantiation(self):
17761804
self.assertIs(type(typing.Deque()), collections.deque)
17771805
self.assertIs(type(typing.Deque[T]()), collections.deque)
17781806
self.assertIs(type(typing.Deque[int]()), collections.deque)
17791807
class D(typing.Deque[T]): ...
17801808
self.assertIs(type(D[int]()), D)
17811809

1810+
def test_counter_instantiation(self):
1811+
self.assertIs(type(typing.Counter()), collections.Counter)
1812+
self.assertIs(type(typing.Counter[T]()), collections.Counter)
1813+
self.assertIs(type(typing.Counter[int]()), collections.Counter)
1814+
class C(typing.Counter[T]): ...
1815+
self.assertIs(type(C[int]()), C)
1816+
1817+
def test_counter_subclass_instantiation(self):
1818+
1819+
class MyCounter(typing.Counter[int]):
1820+
pass
1821+
1822+
d = MyCounter()
1823+
self.assertIsInstance(d, MyCounter)
1824+
self.assertIsInstance(d, typing.Counter)
1825+
self.assertIsInstance(d, collections.Counter)
1826+
17821827
def test_no_set_instantiation(self):
17831828
with self.assertRaises(TypeError):
17841829
typing.Set()

src/typing.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
'SupportsRound',
6969

7070
# Concrete collection types.
71+
'Counter',
7172
'Deque',
7273
'Dict',
7374
'DefaultDict',
@@ -1906,6 +1907,31 @@ def __new__(cls, *args, **kwds):
19061907
return _generic_new(collections.defaultdict, cls, *args, **kwds)
19071908

19081909

1910+
class Counter(collections.Counter, Dict[T, int], extra=collections.Counter):
1911+
1912+
__slots__ = ()
1913+
1914+
def __new__(cls, *args, **kwds):
1915+
if _geqv(cls, Counter):
1916+
return collections.Counter(*args, **kwds)
1917+
return _generic_new(collections.Counter, cls, *args, **kwds)
1918+
1919+
1920+
if hasattr(collections, 'ChainMap'):
1921+
# ChainMap only exists in 3.3+
1922+
__all__.append('ChainMap')
1923+
1924+
class ChainMap(collections.ChainMap, MutableMapping[KT, VT],
1925+
extra=collections.ChainMap):
1926+
1927+
__slots__ = ()
1928+
1929+
def __new__(cls, *args, **kwds):
1930+
if _geqv(cls, ChainMap):
1931+
return collections.ChainMap(*args, **kwds)
1932+
return _generic_new(collections.ChainMap, cls, *args, **kwds)
1933+
1934+
19091935
# Determine what base class to use for Generator.
19101936
if hasattr(collections_abc, 'Generator'):
19111937
# Sufficiently recent versions of 3.5 have a Generator ABC.

0 commit comments

Comments
 (0)