From 9e191fb2056cef7a7cb1d338b584bc303ccdc268 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 11 Sep 2016 14:40:20 +0200 Subject: [PATCH 1/7] New backward-compatible NamedTuple --- src/test_typing.py | 14 ++++++++++++++ src/typing.py | 44 ++++++++++++++++++++++++++++++++------------ 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/src/test_typing.py b/src/test_typing.py index c5cfd154..1bd2ffdc 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -1169,6 +1169,9 @@ class CSub(B): z: ClassVar['CSub'] = B() class G(Generic[T]): lst: ClassVar[List[T]] = [] +class CoolEmployee(NamedTuple): + name: str + cool: int """ if PY36: @@ -1586,6 +1589,17 @@ def test_basics(self): self.assertEqual(Emp._fields, ('name', 'id')) self.assertEqual(Emp._field_types, dict(name=str, id=int)) + @skipUnless(PY36, 'Python 3.6 required') + def test_annotation_usage(self): + tim = CoolEmployee('Tim', 9000) + self.assertIsInstance(tim, CoolEmployee) + self.assertIsInstance(tim, tuple) + self.assertEqual(tim.name, 'Tim') + self.assertEqual(tim.cool, 9000) + self.assertEqual(CoolEmployee.__name__, 'CoolEmployee') + self.assertEqual(CoolEmployee._fields, ('name', 'cool')) + self.assertEqual(CoolEmployee._field_types, dict(name=str, cool=int)) + def test_pickle(self): global Emp # pickle wants to reference the class by name Emp = NamedTuple('Emp', [('name', str), ('id', int)]) diff --git a/src/typing.py b/src/typing.py index fa2db48e..d91664b9 100644 --- a/src/typing.py +++ b/src/typing.py @@ -1801,12 +1801,29 @@ def new_user(user_class: Type[U]) -> U: """ -def NamedTuple(typename, fields): +class NamedTupleMeta(type): + + def __new__(cls, name, bases, ns, *, _root=False): + if _root: + return super().__new__(cls, name, bases, ns) + types = ns.get('__annotations__', {}) + nm_tpl = collections.namedtuple(name, [n for n in types]) + nm_tpl._field_types = types + try: + nm_tpl.__module__ = sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + pass + return nm_tpl + + +class NamedTuple(metaclass=NamedTupleMeta, _root=True): """Typed version of namedtuple. Usage:: - Employee = typing.NamedTuple('Employee', [('name', str), 'id', int)]) + class Employee(NamedTuple): + name: str + id: int This is equivalent to:: @@ -1815,17 +1832,20 @@ def NamedTuple(typename, fields): The resulting class has one extra attribute: _field_types, giving a dict mapping field names to types. (The field names are in the _fields attribute, which is part of the namedtuple - API.) + API.) Backward-compatible usage:: + + Employee = NamedTuple('Employee', [('name', str), ('id', int)]) """ - fields = [(n, t) for n, t in fields] - cls = collections.namedtuple(typename, [n for n, t in fields]) - cls._field_types = dict(fields) - # Set the module to the caller's module (otherwise it'd be 'typing'). - try: - cls.__module__ = sys._getframe(1).f_globals.get('__name__', '__main__') - except (AttributeError, ValueError): - pass - return cls + + def __new__(self, typename, fields): + cls = collections.namedtuple(typename, [n for n, t in fields]) + cls._field_types = dict(fields) + # Set the module to the caller's module (otherwise it'd be 'typing'). + try: + cls.__module__ = sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + pass + return cls def NewType(name, tp): From e2dad0f689742a2784d8c5f0596f21a6d38ca671 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 11 Sep 2016 15:04:00 +0200 Subject: [PATCH 2/7] Minor refactoring --- src/test_typing.py | 1 + src/typing.py | 32 +++++++++++++++----------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/test_typing.py b/src/test_typing.py index 1bd2ffdc..3b99060f 100644 --- a/src/test_typing.py +++ b/src/test_typing.py @@ -1169,6 +1169,7 @@ class CSub(B): z: ClassVar['CSub'] = B() class G(Generic[T]): lst: ClassVar[List[T]] = [] + class CoolEmployee(NamedTuple): name: str cool: int diff --git a/src/typing.py b/src/typing.py index d91664b9..9b533ba3 100644 --- a/src/typing.py +++ b/src/typing.py @@ -1801,19 +1801,23 @@ def new_user(user_class: Type[U]) -> U: """ +def _make_nmtuple(name, types): + nm_tpl = collections.namedtuple(name, [n for n in types]) + nm_tpl._field_types = types + try: + nm_tpl.__module__ = sys._getframe(2).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + pass + return nm_tpl + + class NamedTupleMeta(type): - def __new__(cls, name, bases, ns, *, _root=False): + def __new__(cls, typename, bases, ns, *, _root=False): if _root: - return super().__new__(cls, name, bases, ns) + return super().__new__(cls, typename, bases, ns) types = ns.get('__annotations__', {}) - nm_tpl = collections.namedtuple(name, [n for n in types]) - nm_tpl._field_types = types - try: - nm_tpl.__module__ = sys._getframe(1).f_globals.get('__name__', '__main__') - except (AttributeError, ValueError): - pass - return nm_tpl + return _make_nmtuple(typename, types) class NamedTuple(metaclass=NamedTupleMeta, _root=True): @@ -1838,14 +1842,8 @@ class Employee(NamedTuple): """ def __new__(self, typename, fields): - cls = collections.namedtuple(typename, [n for n, t in fields]) - cls._field_types = dict(fields) - # Set the module to the caller's module (otherwise it'd be 'typing'). - try: - cls.__module__ = sys._getframe(1).f_globals.get('__name__', '__main__') - except (AttributeError, ValueError): - pass - return cls + types = dict(fields) + return _make_nmtuple(typename, types) def NewType(name, tp): From 6e4525d81f99cb02a7e5e5e5fcd15e89032e01d3 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 11 Sep 2016 15:19:14 +0200 Subject: [PATCH 3/7] Fixed for older Python 3 versions --- src/typing.py | 64 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 23 deletions(-) diff --git a/src/typing.py b/src/typing.py index 9b533ba3..f18d5856 100644 --- a/src/typing.py +++ b/src/typing.py @@ -1811,39 +1811,57 @@ def _make_nmtuple(name, types): return nm_tpl -class NamedTupleMeta(type): +if sys.version[:2] >= (3, 6): + class NamedTupleMeta(type): - def __new__(cls, typename, bases, ns, *, _root=False): - if _root: - return super().__new__(cls, typename, bases, ns) - types = ns.get('__annotations__', {}) - return _make_nmtuple(typename, types) + def __new__(cls, typename, bases, ns, *, _root=False): + if _root: + return super().__new__(cls, typename, bases, ns) + types = ns.get('__annotations__', {}) + return _make_nmtuple(typename, types) + class NamedTuple(metaclass=NamedTupleMeta, _root=True): + """Typed version of namedtuple. -class NamedTuple(metaclass=NamedTupleMeta, _root=True): - """Typed version of namedtuple. + Usage:: - Usage:: + class Employee(NamedTuple): + name: str + id: int - class Employee(NamedTuple): - name: str - id: int + This is equivalent to:: - This is equivalent to:: + Employee = collections.namedtuple('Employee', ['name', 'id']) - Employee = collections.namedtuple('Employee', ['name', 'id']) + The resulting class has one extra attribute: _field_types, + giving a dict mapping field names to types. (The field names + are in the _fields attribute, which is part of the namedtuple + API.) Backward-compatible usage:: - The resulting class has one extra attribute: _field_types, - giving a dict mapping field names to types. (The field names - are in the _fields attribute, which is part of the namedtuple - API.) Backward-compatible usage:: + Employee = NamedTuple('Employee', [('name', str), ('id', int)]) + """ - Employee = NamedTuple('Employee', [('name', str), ('id', int)]) - """ + def __new__(self, typename, fields): + types = dict(fields) + return _make_nmtuple(typename, types) +else: + def NamedTuple(typename, fields): + """Typed version of namedtuple. - def __new__(self, typename, fields): - types = dict(fields) - return _make_nmtuple(typename, types) + Usage:: + + Employee = typing.NamedTuple('Employee', [('name', str), 'id', int)]) + + This is equivalent to:: + + Employee = collections.namedtuple('Employee', ['name', 'id']) + + The resulting class has one extra attribute: _field_types, + giving a dict mapping field names to types. (The field names + are in the _fields attribute, which is part of the namedtuple + API.) + """ + return _make_nmtuple(typename, dict(fields)) def NewType(name, tp): From 6352eaec68b676ea7c8d543012a3df34fcb5fdc7 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 11 Sep 2016 15:20:52 +0200 Subject: [PATCH 4/7] Fixed typo --- src/typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/typing.py b/src/typing.py index f18d5856..c0910795 100644 --- a/src/typing.py +++ b/src/typing.py @@ -1811,7 +1811,7 @@ def _make_nmtuple(name, types): return nm_tpl -if sys.version[:2] >= (3, 6): +if sys.version_info[:2] >= (3, 6): class NamedTupleMeta(type): def __new__(cls, typename, bases, ns, *, _root=False): From ce6aa0da7e2de1f1ae4dfd4d064a47eeca5ce79a Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 11 Sep 2016 15:24:24 +0200 Subject: [PATCH 5/7] Fixed unordered dict --- src/typing.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/typing.py b/src/typing.py index c0910795..75b34b71 100644 --- a/src/typing.py +++ b/src/typing.py @@ -1802,7 +1802,7 @@ def new_user(user_class: Type[U]) -> U: def _make_nmtuple(name, types): - nm_tpl = collections.namedtuple(name, [n for n in types]) + nm_tpl = collections.namedtuple(name, [n for n, t in types]) nm_tpl._field_types = types try: nm_tpl.__module__ = sys._getframe(2).f_globals.get('__name__', '__main__') @@ -1818,7 +1818,7 @@ def __new__(cls, typename, bases, ns, *, _root=False): if _root: return super().__new__(cls, typename, bases, ns) types = ns.get('__annotations__', {}) - return _make_nmtuple(typename, types) + return _make_nmtuple(typename, types.items()) class NamedTuple(metaclass=NamedTupleMeta, _root=True): """Typed version of namedtuple. @@ -1842,8 +1842,7 @@ class Employee(NamedTuple): """ def __new__(self, typename, fields): - types = dict(fields) - return _make_nmtuple(typename, types) + return _make_nmtuple(typename, fileds) else: def NamedTuple(typename, fields): """Typed version of namedtuple. @@ -1861,7 +1860,7 @@ def NamedTuple(typename, fields): are in the _fields attribute, which is part of the namedtuple API.) """ - return _make_nmtuple(typename, dict(fields)) + return _make_nmtuple(typename, fields) def NewType(name, tp): From 170408d603b32388b8f739801edec5071b014ab4 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 11 Sep 2016 15:25:56 +0200 Subject: [PATCH 6/7] Fixed one more typo --- src/typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/typing.py b/src/typing.py index 75b34b71..3b529991 100644 --- a/src/typing.py +++ b/src/typing.py @@ -1803,7 +1803,7 @@ def new_user(user_class: Type[U]) -> U: def _make_nmtuple(name, types): nm_tpl = collections.namedtuple(name, [n for n, t in types]) - nm_tpl._field_types = types + nm_tpl._field_types = dict(types) try: nm_tpl.__module__ = sys._getframe(2).f_globals.get('__name__', '__main__') except (AttributeError, ValueError): From 11b69402653beae8ee870ea7d63365921a988454 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 11 Sep 2016 15:27:40 +0200 Subject: [PATCH 7/7] Fixed one more typo --- src/typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/typing.py b/src/typing.py index 3b529991..4676d28c 100644 --- a/src/typing.py +++ b/src/typing.py @@ -1842,7 +1842,7 @@ class Employee(NamedTuple): """ def __new__(self, typename, fields): - return _make_nmtuple(typename, fileds) + return _make_nmtuple(typename, fields) else: def NamedTuple(typename, fields): """Typed version of namedtuple.