Skip to content
Merged
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
20 changes: 20 additions & 0 deletions python2/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1264,6 +1264,9 @@ def test_list(self):
def test_deque(self):
self.assertIsSubclass(collections.deque, typing.Deque)

def test_counter(self):
self.assertIsSubclass(collections.Counter, typing.Counter)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a test checking that a subclass of counter could be instantiated, as you do for ChainMap in test_chainmap_subclass. (And also for Python 3)

def test_set(self):
self.assertIsSubclass(set, typing.Set)
self.assertNotIsSubclass(frozenset, typing.Set)
Expand Down Expand Up @@ -1338,6 +1341,23 @@ def test_deque_instantiation(self):
class D(typing.Deque[T]): pass
self.assertIs(type(D[int]()), D)

def test_counter_instantiation(self):
self.assertIs(type(typing.Counter()), collections.Counter)
self.assertIs(type(typing.Counter[T]()), collections.Counter)
self.assertIs(type(typing.Counter[int]()), collections.Counter)
class C(typing.Counter[T]): pass
self.assertIs(type(C[int]()), C)

def test_counter_subclass_instantiation(self):

class MyCounter(typing.Counter[int]):
pass

d = MyCounter()
self.assertIsInstance(d, MyCounter)
self.assertIsInstance(d, typing.Counter)
self.assertIsInstance(d, collections.Counter)

def test_no_set_instantiation(self):
with self.assertRaises(TypeError):
typing.Set()
Expand Down
11 changes: 11 additions & 0 deletions python2/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
'SupportsInt',

# Concrete collection types.
'Counter',
'Deque',
'Dict',
'DefaultDict',
Expand Down Expand Up @@ -1783,6 +1784,16 @@ def __new__(cls, *args, **kwds):
return _generic_new(collections.defaultdict, cls, *args, **kwds)


class Counter(collections.Counter, Dict[T, int]):
__slots__ = ()
__extra__ = collections.Counter

def __new__(cls, *args, **kwds):
if _geqv(cls, Counter):
return collections.Counter(*args, **kwds)
return _generic_new(collections.Counter, cls, *args, **kwds)


# Determine what base class to use for Generator.
if hasattr(collections_abc, 'Generator'):
# Sufficiently recent versions of 3.5 have a Generator ABC.
Expand Down
45 changes: 45 additions & 0 deletions src/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -892,6 +892,11 @@ class MyDict(typing.Dict[T, T]): ...
class MyDef(typing.DefaultDict[str, T]): ...
self.assertIs(MyDef[int]().__class__, MyDef)
self.assertIs(MyDef[int]().__orig_class__, MyDef[int])
# ChainMap was added in 3.3
if sys.version_info >= (3, 3):
class MyChain(typing.ChainMap[str, T]): ...
self.assertIs(MyChain[int]().__class__, MyChain)
self.assertIs(MyChain[int]().__orig_class__, MyChain[int])

def test_all_repr_eq_any(self):
objs = (getattr(typing, el) for el in typing.__all__)
Expand Down Expand Up @@ -1662,6 +1667,9 @@ def test_list(self):
def test_deque(self):
self.assertIsSubclass(collections.deque, typing.Deque)

def test_counter(self):
self.assertIsSubclass(collections.Counter, typing.Counter)

def test_set(self):
self.assertIsSubclass(set, typing.Set)
self.assertNotIsSubclass(frozenset, typing.Set)
Expand Down Expand Up @@ -1729,13 +1737,50 @@ class MyDefDict(typing.DefaultDict[str, int]):
self.assertIsSubclass(MyDefDict, collections.defaultdict)
self.assertNotIsSubclass(collections.defaultdict, MyDefDict)

@skipUnless(sys.version_info >= (3, 3), 'ChainMap was added in 3.3')
def test_chainmap_instantiation(self):
self.assertIs(type(typing.ChainMap()), collections.ChainMap)
self.assertIs(type(typing.ChainMap[KT, VT]()), collections.ChainMap)
self.assertIs(type(typing.ChainMap[str, int]()), collections.ChainMap)
class CM(typing.ChainMap[KT, VT]): ...
self.assertIs(type(CM[int, str]()), CM)

@skipUnless(sys.version_info >= (3, 3), 'ChainMap was added in 3.3')
def test_chainmap_subclass(self):

class MyChainMap(typing.ChainMap[str, int]):
pass

cm = MyChainMap()
self.assertIsInstance(cm, MyChainMap)

self.assertIsSubclass(MyChainMap, collections.ChainMap)
self.assertNotIsSubclass(collections.ChainMap, MyChainMap)

def test_deque_instantiation(self):
self.assertIs(type(typing.Deque()), collections.deque)
self.assertIs(type(typing.Deque[T]()), collections.deque)
self.assertIs(type(typing.Deque[int]()), collections.deque)
class D(typing.Deque[T]): ...
self.assertIs(type(D[int]()), D)

def test_counter_instantiation(self):
self.assertIs(type(typing.Counter()), collections.Counter)
self.assertIs(type(typing.Counter[T]()), collections.Counter)
self.assertIs(type(typing.Counter[int]()), collections.Counter)
class C(typing.Counter[T]): ...
self.assertIs(type(C[int]()), C)

def test_counter_subclass_instantiation(self):

class MyCounter(typing.Counter[int]):
pass

d = MyCounter()
self.assertIsInstance(d, MyCounter)
self.assertIsInstance(d, typing.Counter)
self.assertIsInstance(d, collections.Counter)

def test_no_set_instantiation(self):
with self.assertRaises(TypeError):
typing.Set()
Expand Down
26 changes: 26 additions & 0 deletions src/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
'SupportsRound',

# Concrete collection types.
'Counter',
'Deque',
'Dict',
'DefaultDict',
Expand Down Expand Up @@ -1898,6 +1899,31 @@ def __new__(cls, *args, **kwds):
return _generic_new(collections.defaultdict, cls, *args, **kwds)


class Counter(collections.Counter, Dict[T, int], extra=collections.Counter):

__slots__ = ()

def __new__(cls, *args, **kwds):
if _geqv(cls, Counter):
return collections.Counter(*args, **kwds)
return _generic_new(collections.Counter, cls, *args, **kwds)


if hasattr(collections, 'ChainMap'):
# ChainMap only exists in 3.3+
__all__.append('ChainMap')

class ChainMap(collections.ChainMap, MutableMapping[KT, VT],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typeshed defines this as Dict[KT, VT], I am not sure this is right, but typing and typeshed should be in sync for sure.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think MutableMapping is right; ChainMap is not a subclass of dict. I'll submit that change to typeshed.

extra=collections.ChainMap):

__slots__ = ()

def __new__(cls, *args, **kwds):
if _geqv(cls, ChainMap):
return collections.ChainMap(*args, **kwds)
return _generic_new(collections.ChainMap, cls, *args, **kwds)


# Determine what base class to use for Generator.
if hasattr(collections_abc, 'Generator'):
# Sufficiently recent versions of 3.5 have a Generator ABC.
Expand Down