]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.7] bpo-38191: Accept arbitrary keyword names in NamedTuple(). (GH-16222) (GH-16239)
authorSerhiy Storchaka <storchaka@gmail.com>
Tue, 17 Sep 2019 19:09:09 +0000 (22:09 +0300)
committerGitHub <noreply@github.com>
Tue, 17 Sep 2019 19:09:09 +0000 (22:09 +0300)
This includes such names as "cls", "self", "typename" and "fields".
(cherry picked from commit 2bf31ccab3d17f3f35b42dca97f99576dfe2fc7d)

Lib/test/test_typing.py
Lib/typing.py
Misc/NEWS.d/next/Library/2019-09-17-12-28-27.bpo-38191.1TU0HV.rst [new file with mode: 0644]

index b396283a02ee32a41031a9fe5695f9d362c77801..4871fb7c4ddc3f3db6f6a13353299ee6ac94391a 100644 (file)
@@ -2536,6 +2536,34 @@ class XMethBad2(NamedTuple):
         with self.assertRaises(TypeError):
             NamedTuple('Name', x=1, y='a')
 
+    def test_namedtuple_special_keyword_names(self):
+        NT = NamedTuple("NT", cls=type, self=object, typename=str, fields=list)
+        self.assertEqual(NT.__name__, 'NT')
+        self.assertEqual(NT._fields, ('cls', 'self', 'typename', 'fields'))
+        a = NT(cls=str, self=42, typename='foo', fields=[('bar', tuple)])
+        self.assertEqual(a.cls, str)
+        self.assertEqual(a.self, 42)
+        self.assertEqual(a.typename, 'foo')
+        self.assertEqual(a.fields, [('bar', tuple)])
+
+    def test_namedtuple_errors(self):
+        with self.assertRaises(TypeError):
+            NamedTuple.__new__()
+        with self.assertRaises(TypeError):
+            NamedTuple()
+        with self.assertRaises(TypeError):
+            NamedTuple('Emp', [('name', str)], None)
+        with self.assertRaises(ValueError):
+            NamedTuple('Emp', [('_name', str)])
+
+        Emp = NamedTuple(typename='Emp', name=str, id=int)
+        self.assertEqual(Emp.__name__, 'Emp')
+        self.assertEqual(Emp._fields, ('name', 'id'))
+
+        Emp = NamedTuple('Emp', fields=[('name', str), ('id', int)])
+        self.assertEqual(Emp.__name__, 'Emp')
+        self.assertEqual(Emp._fields, ('name', 'id'))
+
     def test_pickle(self):
         global Emp  # pickle wants to reference the class by name
         Emp = NamedTuple('Emp', [('name', str), ('id', int)])
index 9851cb4c7ebd6f8a21a856090f9a87c482e8c531..021ef9552e6529bbd0082611b4b3d3f5dcb3fd44 100644 (file)
@@ -1405,13 +1405,36 @@ class NamedTuple(metaclass=NamedTupleMeta):
     """
     _root = True
 
-    def __new__(self, typename, fields=None, **kwargs):
+    def __new__(*args, **kwargs):
+        if not args:
+            raise TypeError('NamedTuple.__new__(): not enough arguments')
+        cls, *args = args  # allow the "cls" keyword be passed
+        if args:
+            typename, *args = args # allow the "typename" keyword be passed
+        elif 'typename' in kwargs:
+            typename = kwargs.pop('typename')
+        else:
+            raise TypeError("NamedTuple.__new__() missing 1 required positional "
+                            "argument: 'typename'")
+        if args:
+            try:
+                fields, = args # allow the "fields" keyword be passed
+            except ValueError:
+                raise TypeError(f'NamedTuple.__new__() takes from 2 to 3 '
+                                f'positional arguments but {len(args) + 2} '
+                                f'were given') from None
+        elif 'fields' in kwargs and len(kwargs) == 1:
+            fields = kwargs.pop('fields')
+        else:
+            fields = None
+
         if fields is None:
             fields = kwargs.items()
         elif kwargs:
             raise TypeError("Either list of fields or keywords"
                             " can be provided to NamedTuple, not both")
         return _make_nmtuple(typename, fields)
+    __new__.__text_signature__ = '($cls, typename, fields=None, /, **kwargs)'
 
 
 def NewType(name, tp):
diff --git a/Misc/NEWS.d/next/Library/2019-09-17-12-28-27.bpo-38191.1TU0HV.rst b/Misc/NEWS.d/next/Library/2019-09-17-12-28-27.bpo-38191.1TU0HV.rst
new file mode 100644 (file)
index 0000000..15e8fbb
--- /dev/null
@@ -0,0 +1,2 @@
+Constructor of :class:`~typing.NamedTuple` type now accepts arbitrary keyword
+argument names, including "cls", "self", "typename" and "fields".