From e0734e7dc0dcccc91ed657191b804b3a846ad3f6 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Fri, 4 Jan 2008 03:22:53 +0000 Subject: Minor fix-ups to named tuples: * Make the _replace() method respect subclassing. * Using property() to make _fields read-only wasn't a good idea. It caused len(Point._fields) to fail. * Add note to _cast() about length checking and alternative with the star-operator. --- Doc/library/collections.rst | 23 +++++++++++++---------- Lib/collections.py | 6 ++---- Lib/test/test_collections.py | 9 +-------- 3 files changed, 16 insertions(+), 22 deletions(-) diff --git a/Doc/library/collections.rst b/Doc/library/collections.rst index 717731a..628dbf5 100644 --- a/Doc/library/collections.rst +++ b/Doc/library/collections.rst @@ -388,6 +388,8 @@ Example:: __slots__ = () + _fields = ('x', 'y') + def __new__(cls, x, y): return tuple.__new__(cls, (x, y)) @@ -402,11 +404,7 @@ Example:: def _replace(self, **kwds): 'Return a new Point object replacing specified fields with new values' - return Point._cast(map(kwds.get, ('x', 'y'), self)) - - @property - def _fields(self): - return ('x', 'y') + return self.__class__._cast(map(kwds.get, ('x', 'y'), self)) x = property(itemgetter(0)) y = property(itemgetter(1)) @@ -439,17 +437,22 @@ by the :mod:`csv` or :mod:`sqlite3` modules:: print emp.name, emp.title In addition to the methods inherited from tuples, named tuples support -three additonal methods and a read-only attribute. +three additonal methods and one attribute. .. method:: namedtuple._cast(iterable) - Class method returning a new instance taking the positional arguments from the *iterable*. - Useful for casting existing sequences and iterables to named tuples: + Class method returning a new instance taking the positional arguments from the + *iterable*. Useful for casting existing sequences and iterables to named tuples. + + This fast constructor does not check the length of the inputs. To achieve the + same effect with length checking, use the star-operator instead. :: >>> t = [11, 22] - >>> Point._cast(t) + >>> Point._cast(t) # fast conversion + Point(x=11, y=22) + >>> Point(*t) # slow conversion with length checking Point(x=11, y=22) .. method:: somenamedtuple._asdict() @@ -476,7 +479,7 @@ three additonal methods and a read-only attribute. .. attribute:: somenamedtuple._fields - Return a tuple of strings listing the field names. This is useful for introspection + Tuple of strings listing the field names. This is useful for introspection and for creating new named tuple types from existing named tuples. :: diff --git a/Lib/collections.py b/Lib/collections.py index c6d0d0f..487b119 100644 --- a/Lib/collections.py +++ b/Lib/collections.py @@ -60,6 +60,7 @@ def namedtuple(typename, field_names, verbose=False): template = '''class %(typename)s(tuple): '%(typename)s(%(argtxt)s)' \n __slots__ = () \n + _fields = %(field_names)r \n def __new__(cls, %(argtxt)s): return tuple.__new__(cls, (%(argtxt)s)) \n _cast = classmethod(tuple.__new__) \n @@ -70,10 +71,7 @@ def namedtuple(typename, field_names, verbose=False): return {%(dicttxt)s} \n def _replace(self, **kwds): 'Return a new %(typename)s object replacing specified fields with new values' - return %(typename)s._cast(map(kwds.get, %(field_names)r, self)) \n - @property - def _fields(self): - return %(field_names)r \n\n''' % locals() + return self.__class__._cast(map(kwds.get, %(field_names)r, self)) \n\n''' % locals() for i, name in enumerate(field_names): template += ' %s = property(itemgetter(%d))\n' % (name, i) if verbose: diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index edffbbe..5e71399 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -17,6 +17,7 @@ class TestNamedTuple(unittest.TestCase): self.assertEqual(Point.__slots__, ()) self.assertEqual(Point.__module__, __name__) self.assertEqual(Point.__getitem__, tuple.__getitem__) + self.assertEqual(Point._fields, ('x', 'y')) self.assertRaises(ValueError, namedtuple, 'abc%', 'efg ghi') # type has non-alpha char self.assertRaises(ValueError, namedtuple, 'class', 'efg ghi') # type has keyword @@ -51,14 +52,6 @@ class TestNamedTuple(unittest.TestCase): self.assertEqual(p._replace(x=1), (1, 22)) # test _replace method self.assertEqual(p._asdict(), dict(x=11, y=22)) # test _asdict method - # Verify that _fields is read-only - try: - p._fields = ('F1' ,'F2') - except AttributeError: - pass - else: - self.fail('The _fields attribute needs to be read-only') - # verify that field string can have commas Point = namedtuple('Point', 'x, y') p = Point(x=11, y=22) -- cgit v0.12