]> granicus.if.org Git - python/commitdiff
Issue #27842: The csv.DictReader now returns rows of type OrderedDict.
authorRaymond Hettinger <python@rcn.com>
Tue, 30 Aug 2016 19:35:50 +0000 (12:35 -0700)
committerRaymond Hettinger <python@rcn.com>
Tue, 30 Aug 2016 19:35:50 +0000 (12:35 -0700)
Doc/library/csv.rst
Lib/csv.py
Lib/test/test_csv.py
Misc/NEWS

index 7fb4fc8256aca3b5a4c301534f1864c67d33ee26..6341bc31262b7d85fa07a202ab9530e2bae46521 100644 (file)
@@ -149,18 +149,25 @@ The :mod:`csv` module defines the following classes:
 .. class:: DictReader(csvfile, fieldnames=None, restkey=None, restval=None, \
                       dialect='excel', *args, **kwds)
 
-   Create an object which operates like a regular reader but maps the
-   information read into a dict whose keys are given by the optional
-   *fieldnames* parameter.  The *fieldnames* parameter is a :mod:`sequence
-   <collections.abc>` whose elements are associated with the fields of the
-   input data in order. These elements become the keys of the resulting
-   dictionary.  If the *fieldnames* parameter is omitted, the values in the
-   first row of the *csvfile* will be used as the fieldnames.  If the row read
-   has more fields than the fieldnames sequence, the remaining data is added as
-   a sequence keyed by the value of *restkey*.  If the row read has fewer
-   fields than the fieldnames sequence, the remaining keys take the value of
-   the optional *restval* parameter.  Any other optional or keyword arguments
-   are passed to the underlying :class:`reader` instance.
+   Create an object that operates like a regular reader but maps the
+   information in each row to an :mod:`OrderedDict <collections.OrderedDict>`
+   whose keys are given by the optional *fieldnames* parameter.
+
+   The *fieldnames* parameter is a :term:`sequence`.  If *fieldnames* is
+   omitted, the values in the first row of the *csvfile* will be used as the
+   fieldnames.  Regardless of how the fieldnames are determined, the ordered
+   dictionary preserves their original ordering.
+
+   If a row has more fields than fieldnames, the remaining data is put in a
+   list and stored with the fieldname specified by *restkey* (which defaults
+   to ``None``).  If a non-blank row has fewer fields than fieldnames, the
+   missing values are filled-in with ``None``.
+
+   All other optional or keyword arguments are passed to the underlying
+   :class:`reader` instance.
+
+   .. versionchanged:: 3.6
+      Returned rows are now of type :class:`OrderedDict`.
 
    A short usage example::
 
@@ -170,9 +177,11 @@ The :mod:`csv` module defines the following classes:
        ...     for row in reader:
        ...         print(row['first_name'], row['last_name'])
        ...
-       Baked Beans
-       Lovely Spam
-       Wonderful Spam
+       Eric Idle
+       John Cleese
+
+       >>> print(row)
+       OrderedDict([('first_name', 'John'), ('last_name', 'Cleese')])
 
 
 .. class:: DictWriter(csvfile, fieldnames, restval='', extrasaction='raise', \
index 90461dbe1e43c2b224d98ed7c705e612bd82e70b..2e2303a28ee383800db796eab1f863d7685f8f11 100644 (file)
@@ -11,6 +11,7 @@ from _csv import Error, __version__, writer, reader, register_dialect, \
                  __doc__
 from _csv import Dialect as _Dialect
 
+from collections import OrderedDict
 from io import StringIO
 
 __all__ = ["QUOTE_MINIMAL", "QUOTE_ALL", "QUOTE_NONNUMERIC", "QUOTE_NONE",
@@ -116,7 +117,7 @@ class DictReader:
         # values
         while row == []:
             row = next(self.reader)
-        d = dict(zip(self.fieldnames, row))
+        d = OrderedDict(zip(self.fieldnames, row))
         lf = len(self.fieldnames)
         lr = len(row)
         if lf < lr:
index e97c9f366e53d566dfa6e8da65f62ce1c9e5c0ea..9df408048ba594a01cb7d26c027f4b390cf13173 100644 (file)
@@ -10,6 +10,7 @@ import csv
 import gc
 import pickle
 from test import support
+from itertools import permutations
 
 class Test_Csv(unittest.TestCase):
     """
@@ -1092,6 +1093,21 @@ class TestUnicode(unittest.TestCase):
             fileobj.seek(0)
             self.assertEqual(fileobj.read(), expected)
 
+class KeyOrderingTest(unittest.TestCase):
+
+    def test_ordering_for_the_dict_reader_and_writer(self):
+        resultset = set()
+        for keys in permutations("abcde"):
+            with TemporaryFile('w+', newline='', encoding="utf-8") as fileobject:
+                dw = csv.DictWriter(fileobject, keys)
+                dw.writeheader()
+                fileobject.seek(0)
+                dr = csv.DictReader(fileobject)
+                kt = tuple(dr.fieldnames)
+                self.assertEqual(keys, kt)
+                resultset.add(kt)
+        # Final sanity check: were all permutations unique?
+        self.assertEqual(len(resultset), 120, "Key ordering: some key permutations not collected (expected 120)")
 
 class MiscTestCase(unittest.TestCase):
     def test__all__(self):
index 00b6686bd4e4b2525cbb394843e3bef3247adfe3..699026dfca57dc2654add94328410ab69e9d8f18 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -64,6 +64,9 @@ Library
   match ``math.inf`` and ``math.nan``, and also ``cmath.infj`` and
   ``cmath.nanj`` to match the format used by complex repr.
 
+- Issue #27842: The csv.DictReader now returns rows of type OrderedDict.
+  (Contributed by Steve Holden.)
+
 - Issue #27861: Fixed a crash in sqlite3.Connection.cursor() when a factory
   creates not a cursor.  Patch by Xiang Zhang.