diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 6d8cc5319fb121..11ef0a49c71e41 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -2414,6 +2414,25 @@ def test_basic(self): self.assertIsInstance(UserName('Joe'), str) self.assertEqual(UserId(5) + 1, 6) + def test_definitions(self): + UserId = NewType('UserId', int) + UserName = NewType('UserName', str) + self.assertEqual(UserId.__name__, 'UserId') + self.assertEqual(UserId.__qualname__, 'UserId') + self.assertIs(UserId.__supertype__, int) + self.assertIsInstance(UserId, NewType) + self.assertEqual(UserName.__name__, 'UserName') + self.assertEqual(UserName.__qualname__, 'UserName') + self.assertIs(UserName.__supertype__, str) + self.assertIsInstance(UserName, NewType) + self.assertNotEqual(hash(UserId), hash(UserName)) + + def test_repr(self): + UserId = NewType('UserId', int) + UserName = NewType('UserName', str) + self.assertEqual(repr(UserId), "NewType") + self.assertEqual(repr(UserName), "NewType") + def test_errors(self): UserId = NewType('UserId', int) UserName = NewType('UserName', str) diff --git a/Lib/typing.py b/Lib/typing.py index cfcbb3b7632842..7973a7c4aafc9d 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1409,12 +1409,13 @@ def __new__(self, typename, fields=None, **kwargs): " can be provided to NamedTuple, not both") return _make_nmtuple(typename, fields) +class NewType: -def NewType(name, tp): - """NewType creates simple unique types with almost zero - runtime overhead. NewType(name, tp) is considered a subtype of tp - by static type checkers. At runtime, NewType(name, tp) returns - a dummy function that simply returns its argument. Usage:: + """NewType creates simple unique types with almost zero runtime + overhead. `NewType(name, tp)` is considered a subtype of `tp` + by static type checkers. At runtime, NewType(name, tp) creates + a callable instance that simply returns its argument when called. + Usage:: UserId = NewType('UserId', int) @@ -1429,18 +1430,31 @@ def name_by_id(user_id: UserId) -> str: num = UserId(5) + 1 # type: int """ - def new_type(x): - return x + __slots__ = ('__name__', '__qualname__', '__supertype__') + + def __init__(self, name, tp): + self.__name__ = self.__qualname__ = name + self.__supertype__ = tp + + @staticmethod + def __call__(arg): + return arg - new_type.__name__ = name - new_type.__supertype__ = tp - return new_type + def __repr__(self): + """ NewType reprs are in the form: + “NewTClassName”, e.g.: + `NewType` + """ + return f"{type(self).__name__}<" \ + f"{self.__qualname__}:" \ + f"{self.__supertype__.__name__}>" + def __hash__(self): + return hash((self.__name__, self.__supertype__)) # Python-version-specific alias (Python 2: unicode; Python 3: str) Text = str - # Constant that's True when type checking, but False here. TYPE_CHECKING = False diff --git a/Misc/NEWS.d/next/Library/2018-10-11-23-14-13.bpo-34963.fNy13b.rst b/Misc/NEWS.d/next/Library/2018-10-11-23-14-13.bpo-34963.fNy13b.rst new file mode 100644 index 00000000000000..eed1b450fbdbc9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-10-11-23-14-13.bpo-34963.fNy13b.rst @@ -0,0 +1,3 @@ +Updated the `typing.NewType(…)` function to create real callable types – +This allows the generated types to furnish a real __repr__ function, while +retaining the same behavior (specifically, the near-zero runtime overhead).