3

Why can stream.write output pretty much anything other than zero?

from StringIO import StringIO
v0 = 0
v1 = 1
a = [1, 0, v1, v0, "string", 0.0, 2.0]
stream = StringIO()
for v in a:
    stream.write(v)
    stream.write('\n')
print stream.getvalue()

With Python 2.7.6, running this code produces:

1

1

string

2.0
Peter Krnjevic
  • 1,070
  • 15
  • 20
  • It turns out that `.write(0)` means `don't write` in `StringIO`'s context. My guess is, in its internal implementation, it probably checks the input first like `if 0:` which always equals to `False`. Is there any reason you didn't convert items to `string` first using `str()`? – Ozgur Vatansever Feb 14 '15 at 08:29
  • By the way, I would prefer using `cStringIO` over `StringIO`. It is way faster. – Ozgur Vatansever Feb 14 '15 at 08:34
  • As a matter of interest, Python 3 `io` module (which replaces the StringIO module) produces a TypeError on `stream.write(v)` when `v` is not a string. – cdarke Feb 14 '15 at 08:36
  • so as `cStringIO.StringIO` in Python 2.7. – Ozgur Vatansever Feb 14 '15 at 08:45
  • @ozgur Actually I did end up wrapping numbers with str(), but was curious why this should be necessary. – Peter Krnjevic Feb 14 '15 at 08:56

2 Answers2

3

It looks like it's at StringIO.py, line 214 (function write):

if not s: return

(s being what you are passing to write).

In other words, 'falsy' values (such as None, [], 0, etc) will be discarded.

Daniel
  • 1,410
  • 12
  • 17
3

The interface for fileobj.write() requires that you write a string, always:

file.write(str)
Write a string to the file.

Emphasis mine.

It is an implementation detail that for StringIO() non-strings just happen to work. The code optimises the 'empty string' case by using:

if not s: return

to avoid doing unnecessary string concatenation. This means that if you pass in any falsey value, such as numeric 0 or None or an empty container, writing doesn't take place.

Convert your objects to a string before writing:

for v in a:
    stream.write(str(v))
    stream.write('\n')

If you used the C-optimised version here you'd have had an error:

>>> from cStringIO import StringIO
>>> f = StringIO()
>>> f.write(0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: must be string or buffer, not int
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Maybe it's just me, but the "write a string" is ambiguous - it could be interpreted as "file.write will output a string to the file", rather than "you must provide a string as input". – Peter Krnjevic Feb 14 '15 at 09:02
  • @PeterKrnjevic: the signature of the method is `file.write(str)`. No other file(like) object accepts other types. – Martijn Pieters Feb 14 '15 at 09:07
  • So it would seem. I tried with regular file.write() and it explodes as expected. However, I'm a bit surprised a dynamic language like Python that wouldn't "stringify" types when necessary. – Peter Krnjevic Feb 14 '15 at 09:15
  • @PeterKrnjevic: use `print >> fobj, value` or with the Python 3 syntax (after `from __future__ import print_function`) `print(value, file=fobj)` if you want stringification. – Martijn Pieters Feb 14 '15 at 09:43
  • @PeterKrnjevic: that `StringIO.write()` accepts other types at all is really a mis-feature that should have been removed long ago. The new Python 3 I/O infrastructure (available as the `io` module in Python 2) has in-memory file objects that correct the mistake. – Martijn Pieters Feb 14 '15 at 09:45