Skip to content

Commit 6ab3abc

Browse files
bintorogvanrossum
authored andcommitted
Stop generic subclasses from inheriting __extra__ (#223)
Fixes several issues related to subclass checks against custom subclasses of generic collections.
1 parent 111d8f4 commit 6ab3abc

File tree

4 files changed

+61
-23
lines changed

4 files changed

+61
-23
lines changed

python2/test_typing.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import absolute_import, unicode_literals
22

3+
import collections
34
import pickle
45
import re
56
import sys
@@ -970,13 +971,17 @@ def test_no_list_instantiation(self):
970971
with self.assertRaises(TypeError):
971972
typing.List[int]()
972973

973-
def test_list_subclass_instantiation(self):
974+
def test_list_subclass(self):
974975

975976
class MyList(typing.List[int]):
976977
pass
977978

978979
a = MyList()
979980
self.assertIsInstance(a, MyList)
981+
self.assertIsInstance(a, typing.Sequence)
982+
983+
self.assertIsSubclass(MyList, list)
984+
self.assertNotIsSubclass(list, MyList)
980985

981986
def test_no_dict_instantiation(self):
982987
with self.assertRaises(TypeError):
@@ -986,13 +991,17 @@ def test_no_dict_instantiation(self):
986991
with self.assertRaises(TypeError):
987992
typing.Dict[str, int]()
988993

989-
def test_dict_subclass_instantiation(self):
994+
def test_dict_subclass(self):
990995

991996
class MyDict(typing.Dict[str, int]):
992997
pass
993998

994999
d = MyDict()
9951000
self.assertIsInstance(d, MyDict)
1001+
self.assertIsInstance(d, typing.MutableMapping)
1002+
1003+
self.assertIsSubclass(MyDict, dict)
1004+
self.assertNotIsSubclass(dict, MyDict)
9961005

9971006
def test_no_defaultdict_instantiation(self):
9981007
with self.assertRaises(TypeError):
@@ -1002,14 +1011,17 @@ def test_no_defaultdict_instantiation(self):
10021011
with self.assertRaises(TypeError):
10031012
typing.DefaultDict[str, int]()
10041013

1005-
def test_defaultdict_subclass_instantiation(self):
1014+
def test_defaultdict_subclass(self):
10061015

10071016
class MyDefDict(typing.DefaultDict[str, int]):
10081017
pass
10091018

10101019
dd = MyDefDict()
10111020
self.assertIsInstance(dd, MyDefDict)
10121021

1022+
self.assertIsSubclass(MyDefDict, collections.defaultdict)
1023+
self.assertNotIsSubclass(collections.defaultdict, MyDefDict)
1024+
10131025
def test_no_set_instantiation(self):
10141026
with self.assertRaises(TypeError):
10151027
typing.Set()
@@ -1090,6 +1102,13 @@ def __len__(self):
10901102
self.assertEqual(len(MMB[str, str]()), 0)
10911103
self.assertEqual(len(MMB[KT, VT]()), 0)
10921104

1105+
self.assertNotIsSubclass(dict, MMA)
1106+
self.assertNotIsSubclass(dict, MMB)
1107+
1108+
self.assertIsSubclass(MMA, typing.Mapping)
1109+
self.assertIsSubclass(MMB, typing.Mapping)
1110+
self.assertIsSubclass(MMC, typing.Mapping)
1111+
10931112

10941113
class NamedTupleTests(BaseTestCase):
10951114

python2/typing.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -911,8 +911,6 @@ def _next_in_mro(cls):
911911
class GenericMeta(TypingMeta, abc.ABCMeta):
912912
"""Metaclass for generic types."""
913913

914-
__extra__ = None
915-
916914
def __new__(cls, name, bases, namespace,
917915
tvars=None, args=None, origin=None, extra=None):
918916
self = super(GenericMeta, cls).__new__(cls, name, bases, namespace)
@@ -960,10 +958,7 @@ def __new__(cls, name, bases, namespace,
960958
self.__parameters__ = tvars
961959
self.__args__ = args
962960
self.__origin__ = origin
963-
if extra is not None:
964-
self.__extra__ = extra
965-
# Else __extra__ is inherited, eventually from the
966-
# (meta-)class default above.
961+
self.__extra__ = namespace.get('__extra__')
967962
# Speed hack (https://github.com/python/typing/issues/196).
968963
self.__next_in_mro__ = _next_in_mro(self)
969964
return self
@@ -1289,6 +1284,7 @@ def _get_protocol_attrs(self):
12891284
attr != '__next_in_mro__' and
12901285
attr != '__parameters__' and
12911286
attr != '__origin__' and
1287+
attr != '__extra__' and
12921288
attr != '__module__'):
12931289
attrs.add(attr)
12941290

@@ -1414,10 +1410,12 @@ class ByteString(Sequence[int]):
14141410
pass
14151411

14161412

1413+
ByteString.register(str)
14171414
ByteString.register(bytearray)
14181415

14191416

14201417
class List(list, MutableSequence[T]):
1418+
__extra__ = list
14211419

14221420
def __new__(cls, *args, **kwds):
14231421
if _geqv(cls, List):
@@ -1427,6 +1425,7 @@ def __new__(cls, *args, **kwds):
14271425

14281426

14291427
class Set(set, MutableSet[T]):
1428+
__extra__ = set
14301429

14311430
def __new__(cls, *args, **kwds):
14321431
if _geqv(cls, Set):
@@ -1452,6 +1451,7 @@ def __subclasscheck__(self, cls):
14521451
class FrozenSet(frozenset, AbstractSet[T_co]):
14531452
__metaclass__ = _FrozenSetMeta
14541453
__slots__ = ()
1454+
__extra__ = frozenset
14551455

14561456
def __new__(cls, *args, **kwds):
14571457
if _geqv(cls, FrozenSet):
@@ -1479,6 +1479,7 @@ class ValuesView(MappingView[VT_co]):
14791479

14801480

14811481
class Dict(dict, MutableMapping[KT, VT]):
1482+
__extra__ = dict
14821483

14831484
def __new__(cls, *args, **kwds):
14841485
if _geqv(cls, Dict):
@@ -1488,6 +1489,7 @@ def __new__(cls, *args, **kwds):
14881489

14891490

14901491
class DefaultDict(collections.defaultdict, MutableMapping[KT, VT]):
1492+
__extra__ = collections.defaultdict
14911493

14921494
def __new__(cls, *args, **kwds):
14931495
if _geqv(cls, DefaultDict):

src/test_typing.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import contextlib
2+
import collections
23
import pickle
34
import re
45
import sys
@@ -1218,13 +1219,17 @@ def test_no_list_instantiation(self):
12181219
with self.assertRaises(TypeError):
12191220
typing.List[int]()
12201221

1221-
def test_list_subclass_instantiation(self):
1222+
def test_list_subclass(self):
12221223

12231224
class MyList(typing.List[int]):
12241225
pass
12251226

12261227
a = MyList()
12271228
self.assertIsInstance(a, MyList)
1229+
self.assertIsInstance(a, typing.Sequence)
1230+
1231+
self.assertIsSubclass(MyList, list)
1232+
self.assertNotIsSubclass(list, MyList)
12281233

12291234
def test_no_dict_instantiation(self):
12301235
with self.assertRaises(TypeError):
@@ -1234,13 +1239,17 @@ def test_no_dict_instantiation(self):
12341239
with self.assertRaises(TypeError):
12351240
typing.Dict[str, int]()
12361241

1237-
def test_dict_subclass_instantiation(self):
1242+
def test_dict_subclass(self):
12381243

12391244
class MyDict(typing.Dict[str, int]):
12401245
pass
12411246

12421247
d = MyDict()
12431248
self.assertIsInstance(d, MyDict)
1249+
self.assertIsInstance(d, typing.MutableMapping)
1250+
1251+
self.assertIsSubclass(MyDict, dict)
1252+
self.assertNotIsSubclass(dict, MyDict)
12441253

12451254
def test_no_defaultdict_instantiation(self):
12461255
with self.assertRaises(TypeError):
@@ -1250,14 +1259,17 @@ def test_no_defaultdict_instantiation(self):
12501259
with self.assertRaises(TypeError):
12511260
typing.DefaultDict[str, int]()
12521261

1253-
def test_defaultdict_subclass_instantiation(self):
1262+
def test_defaultdict_subclass(self):
12541263

12551264
class MyDefDict(typing.DefaultDict[str, int]):
12561265
pass
12571266

12581267
dd = MyDefDict()
12591268
self.assertIsInstance(dd, MyDefDict)
12601269

1270+
self.assertIsSubclass(MyDefDict, collections.defaultdict)
1271+
self.assertNotIsSubclass(collections.defaultdict, MyDefDict)
1272+
12611273
def test_no_set_instantiation(self):
12621274
with self.assertRaises(TypeError):
12631275
typing.Set()
@@ -1338,6 +1350,13 @@ def __len__(self):
13381350
self.assertEqual(len(MMB[str, str]()), 0)
13391351
self.assertEqual(len(MMB[KT, VT]()), 0)
13401352

1353+
self.assertNotIsSubclass(dict, MMA)
1354+
self.assertNotIsSubclass(dict, MMB)
1355+
1356+
self.assertIsSubclass(MMA, typing.Mapping)
1357+
self.assertIsSubclass(MMB, typing.Mapping)
1358+
self.assertIsSubclass(MMC, typing.Mapping)
1359+
13411360

13421361
class OtherABCTests(BaseTestCase):
13431362

src/typing.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -894,8 +894,6 @@ def _next_in_mro(cls):
894894
class GenericMeta(TypingMeta, abc.ABCMeta):
895895
"""Metaclass for generic types."""
896896

897-
__extra__ = None
898-
899897
def __new__(cls, name, bases, namespace,
900898
tvars=None, args=None, origin=None, extra=None):
901899
self = super().__new__(cls, name, bases, namespace, _root=True)
@@ -943,10 +941,7 @@ def __new__(cls, name, bases, namespace,
943941
self.__parameters__ = tvars
944942
self.__args__ = args
945943
self.__origin__ = origin
946-
if extra is not None:
947-
self.__extra__ = extra
948-
# Else __extra__ is inherited, eventually from the
949-
# (meta-)class default above.
944+
self.__extra__ = extra
950945
# Speed hack (https://github.com/python/typing/issues/196).
951946
self.__next_in_mro__ = _next_in_mro(self)
952947
return self
@@ -1307,6 +1302,7 @@ def _get_protocol_attrs(self):
13071302
attr != '__next_in_mro__' and
13081303
attr != '__parameters__' and
13091304
attr != '__origin__' and
1305+
attr != '__extra__' and
13101306
attr != '__module__'):
13111307
attrs.add(attr)
13121308

@@ -1470,7 +1466,7 @@ class ByteString(Sequence[int], extra=collections_abc.ByteString):
14701466
ByteString.register(type(memoryview(b'')))
14711467

14721468

1473-
class List(list, MutableSequence[T]):
1469+
class List(list, MutableSequence[T], extra=list):
14741470

14751471
def __new__(cls, *args, **kwds):
14761472
if _geqv(cls, List):
@@ -1479,7 +1475,7 @@ def __new__(cls, *args, **kwds):
14791475
return list.__new__(cls, *args, **kwds)
14801476

14811477

1482-
class Set(set, MutableSet[T]):
1478+
class Set(set, MutableSet[T], extra=set):
14831479

14841480
def __new__(cls, *args, **kwds):
14851481
if _geqv(cls, Set):
@@ -1502,7 +1498,8 @@ def __subclasscheck__(self, cls):
15021498
return super().__subclasscheck__(cls)
15031499

15041500

1505-
class FrozenSet(frozenset, AbstractSet[T_co], metaclass=_FrozenSetMeta):
1501+
class FrozenSet(frozenset, AbstractSet[T_co], metaclass=_FrozenSetMeta,
1502+
extra=frozenset):
15061503
__slots__ = ()
15071504

15081505
def __new__(cls, *args, **kwds):
@@ -1538,15 +1535,16 @@ class ContextManager(Generic[T_co], extra=contextlib.AbstractContextManager):
15381535
__all__.append('ContextManager')
15391536

15401537

1541-
class Dict(dict, MutableMapping[KT, VT]):
1538+
class Dict(dict, MutableMapping[KT, VT], extra=dict):
15421539

15431540
def __new__(cls, *args, **kwds):
15441541
if _geqv(cls, Dict):
15451542
raise TypeError("Type Dict cannot be instantiated; "
15461543
"use dict() instead")
15471544
return dict.__new__(cls, *args, **kwds)
15481545

1549-
class DefaultDict(collections.defaultdict, MutableMapping[KT, VT]):
1546+
class DefaultDict(collections.defaultdict, MutableMapping[KT, VT],
1547+
extra=collections.defaultdict):
15501548

15511549
def __new__(cls, *args, **kwds):
15521550
if _geqv(cls, DefaultDict):

0 commit comments

Comments
 (0)