Generalize file.writelines() to allow iterable objects.

This commit is contained in:
Tim Peters 2001-09-23 04:06:05 +00:00
parent d31db7e939
commit 2c9aa5ea8d
5 changed files with 92 additions and 36 deletions

View File

@ -1312,8 +1312,10 @@ Files have the following methods:
the \method{flush()} or \method{close()} method is called. the \method{flush()} or \method{close()} method is called.
\end{methoddesc} \end{methoddesc}
\begin{methoddesc}[file]{writelines}{list} \begin{methoddesc}[file]{writelines}{sequence}
Write a list of strings to the file. There is no return value. Write a sequence of strings to the file. The sequence can be any
iterable object producing strings, typically a list of strings.
There is no return value.
(The name is intended to match \method{readlines()}; (The name is intended to match \method{readlines()};
\method{writelines()} does not add line separators.) \method{writelines()} does not add line separators.)
\end{methoddesc} \end{methoddesc}

View File

@ -641,6 +641,59 @@ class TestCase(unittest.TestCase):
self.assertEqual(indexOf(iclass, i), i) self.assertEqual(indexOf(iclass, i), i)
self.assertRaises(ValueError, indexOf, iclass, -1) self.assertRaises(ValueError, indexOf, iclass, -1)
# Test iterators with file.writelines().
def test_writelines(self):
f = file(TESTFN, "w")
try:
self.assertRaises(TypeError, f.writelines, None)
self.assertRaises(TypeError, f.writelines, 42)
f.writelines(["1\n", "2\n"])
f.writelines(("3\n", "4\n"))
f.writelines({'5\n': None})
f.writelines({})
# Try a big chunk too.
class Iterator:
def __init__(self, start, finish):
self.start = start
self.finish = finish
self.i = self.start
def next(self):
if self.i >= self.finish:
raise StopIteration
result = str(self.i) + '\n'
self.i += 1
return result
def __iter__(self):
return self
class Whatever:
def __init__(self, start, finish):
self.start = start
self.finish = finish
def __iter__(self):
return Iterator(self.start, self.finish)
f.writelines(Whatever(6, 6+2000))
f.close()
f = file(TESTFN)
expected = [str(i) + "\n" for i in range(1, 2006)]
self.assertEqual(list(f), expected)
finally:
f.close()
try:
unlink(TESTFN)
except OSError:
pass
# Test iterators on RHS of unpacking assignments. # Test iterators on RHS of unpacking assignments.
def test_unpack_iter(self): def test_unpack_iter(self):
a, b = 1, 2 a, b = 1, 2

View File

@ -3,6 +3,8 @@ What's New in Python 2.2a4?
Core Core
- file.writelines() now accepts any iterable object producing strings.
- PyUnicode_FromEncodedObject() now works very much like - PyUnicode_FromEncodedObject() now works very much like
PyObject_Str(obj) in that it tries to use __str__/tp_str PyObject_Str(obj) in that it tries to use __str__/tp_str
on the object if the object is not a string or buffer. This on the object if the object is not a string or buffer. This

View File

@ -1164,55 +1164,54 @@ file_write(PyFileObject *f, PyObject *args)
} }
static PyObject * static PyObject *
file_writelines(PyFileObject *f, PyObject *args) file_writelines(PyFileObject *f, PyObject *seq)
{ {
#define CHUNKSIZE 1000 #define CHUNKSIZE 1000
PyObject *list, *line; PyObject *list, *line;
PyObject *it; /* iter(seq) */
PyObject *result; PyObject *result;
int i, j, index, len, nwritten, islist; int i, j, index, len, nwritten, islist;
assert(seq != NULL);
if (f->f_fp == NULL) if (f->f_fp == NULL)
return err_closed(); return err_closed();
if (args == NULL || !PySequence_Check(args)) {
PyErr_SetString(PyExc_TypeError, result = NULL;
"writelines() argument must be a sequence of strings"); list = NULL;
return NULL; islist = PyList_Check(seq);
if (islist)
it = NULL;
else {
it = PyObject_GetIter(seq);
if (it == NULL) {
PyErr_SetString(PyExc_TypeError,
"writelines() requires an iterable argument");
return NULL;
}
/* From here on, fail by going to error, to reclaim "it". */
list = PyList_New(CHUNKSIZE);
if (list == NULL)
goto error;
} }
islist = PyList_Check(args);
/* Strategy: slurp CHUNKSIZE lines into a private list, /* Strategy: slurp CHUNKSIZE lines into a private list,
checking that they are all strings, then write that list checking that they are all strings, then write that list
without holding the interpreter lock, then come back for more. */ without holding the interpreter lock, then come back for more. */
index = 0; for (index = 0; ; index += CHUNKSIZE) {
if (islist)
list = NULL;
else {
list = PyList_New(CHUNKSIZE);
if (list == NULL)
return NULL;
}
result = NULL;
for (;;) {
if (islist) { if (islist) {
Py_XDECREF(list); Py_XDECREF(list);
list = PyList_GetSlice(args, index, index+CHUNKSIZE); list = PyList_GetSlice(seq, index, index+CHUNKSIZE);
if (list == NULL) if (list == NULL)
return NULL; goto error;
j = PyList_GET_SIZE(list); j = PyList_GET_SIZE(list);
} }
else { else {
for (j = 0; j < CHUNKSIZE; j++) { for (j = 0; j < CHUNKSIZE; j++) {
line = PySequence_GetItem(args, index+j); line = PyIter_Next(it);
if (line == NULL) { if (line == NULL) {
if (PyErr_ExceptionMatches( if (PyErr_Occurred())
PyExc_IndexError)) { goto error;
PyErr_Clear(); break;
break;
}
/* Some other error occurred.
XXX We may lose some output. */
goto error;
} }
PyList_SetItem(list, j, line); PyList_SetItem(list, j, line);
} }
@ -1271,14 +1270,15 @@ file_writelines(PyFileObject *f, PyObject *args)
if (j < CHUNKSIZE) if (j < CHUNKSIZE)
break; break;
index += CHUNKSIZE;
} }
Py_INCREF(Py_None); Py_INCREF(Py_None);
result = Py_None; result = Py_None;
error: error:
Py_XDECREF(list); Py_XDECREF(list);
Py_XDECREF(it);
return result; return result;
#undef CHUNKSIZE
} }
static char readline_doc[] = static char readline_doc[] =
@ -1342,10 +1342,10 @@ static char xreadlines_doc[] =
"often quicker, due to reading ahead internally."; "often quicker, due to reading ahead internally.";
static char writelines_doc[] = static char writelines_doc[] =
"writelines(list of strings) -> None. Write the strings to the file.\n" "writelines(sequence_of_strings) -> None. Write the strings to the file.\n"
"\n" "\n"
"Note that newlines are not added. This is equivalent to calling write()\n" "Note that newlines are not added. The sequence can be any iterable object\n"
"for each string in the list."; "producing strings. This is equivalent to calling write() for each string.";
static char flush_doc[] = static char flush_doc[] =
"flush() -> None. Flush the internal I/O buffer."; "flush() -> None. Flush the internal I/O buffer.";

View File

@ -118,8 +118,7 @@ def main(args):
def restore(which): def restore(which):
restored = difflib.restore(sys.stdin.readlines(), which) restored = difflib.restore(sys.stdin.readlines(), which)
for line in restored: sys.stdout.writelines(restored)
print line,
if __name__ == '__main__': if __name__ == '__main__':
args = sys.argv[1:] args = sys.argv[1:]