From 6ab78cd0c055462af4f5a6c59b9f310a83734c45 Mon Sep 17 00:00:00 2001
From: Raymond Hettinger <python@rcn.com>
Date: Sun, 29 Aug 2004 07:50:43 +0000
Subject: SF feature request #992967:  array.array objects should support
 sequences.

Made the constructor accept general iterables.
---
 Doc/lib/libarray.tex   |  8 ++++++--
 Lib/test/test_array.py | 38 +++++++++++++++++++++++++++++++++++---
 Misc/NEWS              |  4 ++--
 Modules/arraymodule.c  | 26 ++++++++++++++++++++------
 4 files changed, 63 insertions(+), 13 deletions(-)

diff --git a/Doc/lib/libarray.tex b/Doc/lib/libarray.tex
index 6fd8b0c..e168e54 100644
--- a/Doc/lib/libarray.tex
+++ b/Doc/lib/libarray.tex
@@ -41,10 +41,14 @@ The module defines the following type:
 \begin{funcdesc}{array}{typecode\optional{, initializer}}
 Return a new array whose items are restricted by \var{typecode},
 and initialized from the optional \var{initializer} value, which
-must be a list or a string.  The list or string is passed to the
+must be a list, string, or iterable over elements of the
+appropriate type.
+\versionchanged[Formerly, only lists or strings were accepted]{2.4}
+If given a list or string, the initializer is passed to the
 new array's \method{fromlist()}, \method{fromstring()}, or
 \method{fromunicode()} method (see below) to add initial items to
-the array.
+the array.  Otherwise, the iterable initializer is passed to the
+\method{extend()} method.
 \end{funcdesc}
 
 \begin{datadesc}{ArrayType}
diff --git a/Lib/test/test_array.py b/Lib/test/test_array.py
index d03618d..b15298f 100755
--- a/Lib/test/test_array.py
+++ b/Lib/test/test_array.py
@@ -600,6 +600,26 @@ class BaseTest(unittest.TestCase):
             array.array(self.typecode, self.example+self.example[::-1])
         )
 
+    def test_constructor_with_iterable_argument(self):
+        a = array.array(self.typecode, iter(self.example))
+        b = array.array(self.typecode, self.example)
+        self.assertEqual(a, b)
+
+        # non-iterable argument
+        self.assertRaises(TypeError, array.array, self.typecode, 10)
+
+        # pass through errors raised in __iter__
+        class A:
+            def __iter__(self):
+                raise UnicodeError
+        self.assertRaises(UnicodeError, array.array, self.typecode, A())
+
+        # pass through errors raised in next()
+        def B():
+            raise UnicodeError
+            yield None
+        self.assertRaises(UnicodeError, array.array, self.typecode, B())
+
     def test_coveritertraverse(self):
         try:
             import gc
@@ -904,8 +924,20 @@ class DoubleTest(FPTest):
     minitemsize = 8
 tests.append(DoubleTest)
 
-def test_main():
+def test_main(verbose=None):
+    import sys
+
     test_support.run_unittest(*tests)
 
-if __name__=="__main__":
-    test_main()
+    # verify reference counting
+    if verbose and hasattr(sys, "gettotalrefcount"):
+        import gc
+        counts = [None] * 5
+        for i in xrange(len(counts)):
+            test_support.run_unittest(*tests)
+            gc.collect()
+            counts[i] = sys.gettotalrefcount()
+        print counts
+
+if __name__ == "__main__":
+    test_main(verbose=True)
diff --git a/Misc/NEWS b/Misc/NEWS
index c7478d4..d294440 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -732,8 +732,8 @@ Extension modules
 - array objects now support the copy module.  Also, their resizing
   scheme has been updated to match that used for list objects.  This improves
   the performance (speed and memory usage) of append() operations.
-  Also, array.extend() now accepts any iterable argument for repeated
-  appends without needing to create another temporary array.
+  Also, array.array() and array.extend() now accept any iterable argument
+  for repeated appends without needing to create another temporary array.
 
 - cStringIO.writelines() now accepts any iterable argument and writes
   the lines one at a time rather than joining them and writing once.
diff --git a/Modules/arraymodule.c b/Modules/arraymodule.c
index ab904bd..7ed3b73 100644
--- a/Modules/arraymodule.c
+++ b/Modules/arraymodule.c
@@ -1772,7 +1772,7 @@ static PyObject *
 array_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
 {
 	char c;
-	PyObject *initial = NULL;
+	PyObject *initial = NULL, *it = NULL;
 	struct arraydescr *descr;
 
 	if (kwds != NULL) {
@@ -1793,9 +1793,15 @@ array_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
 	if (!(initial == NULL || PyList_Check(initial)
 	      || PyString_Check(initial) || PyTuple_Check(initial)
 	      || (c == 'u' && PyUnicode_Check(initial)))) {
-		PyErr_SetString(PyExc_TypeError,
-		    "array initializer must be list or string");
-		return NULL;
+		it = PyObject_GetIter(initial);
+		if (it == NULL)
+			return NULL;
+		/* We set initial to NULL so that the subsequent code
+		   will create an empty array of the appropriate type
+		   and afterwards we can use array_iter_extend to populate
+		   the array.
+		*/
+		initial = NULL;
 	}
 	for (descr = descriptors; descr->typecode != '\0'; descr++) {
 		if (descr->typecode == c) {
@@ -1859,6 +1865,14 @@ array_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
 				}
 #endif
 			}
+			if (it != NULL) {
+				if (array_iter_extend((arrayobject *)a, it) == -1) {
+					Py_DECREF(it);
+					Py_DECREF(a);
+					return NULL;
+				}
+				Py_DECREF(it);
+			}
 			return a;
 		}
 	}
@@ -1899,8 +1913,8 @@ PyDoc_STRVAR(arraytype_doc,
 "array(typecode [, initializer]) -> array\n\
 \n\
 Return a new array whose items are restricted by typecode, and\n\
-initialized from the optional initializer value, which must be a list\n\
-or a string.\n\
+initialized from the optional initializer value, which must be a list,\n\
+string. or iterable over elements of the appropriate type.\n\
 \n\
 Arrays represent basic values and behave very much like lists, except\n\
 the type of objects stored in them is constrained.\n\
-- 
cgit v0.12

