1

I'm trying to write a Python program that works in both Python 2.7 and Python 3.*. I have a case where I use StringIO, and according to Python-Future's cheatsheet on StringIO, all I have to to is to use the Python 3-style io module.

The problem is that I'm printing floats to this StringIO:

from __future__ import print_function
from io import StringIO

with StringIO() as file:
    print(1.0, file=file)

This results in

TypeError: string argument expected, got 'str'

When I replace 1.0 by u"AAAA" (or "AAAA" with unicode_literals enabled), it works fine.

Alternatives I've tried:

  • BytesIO. I can't print anymore, because "unicode doesn't support the buffer interface".
  • "{:f}".format(...) every float. This is possible, but cumbersome.
  • file.write(...) instead of print(..., file=file). This works, but at this point, I don't see what the use of print() is anymore.

Are there any other options?

  • "*I don't see the use of `print()` is anymore*": why would that be an issue? Why not use `file.write()`? BTW: bad idea to have a variable called `file` in Python 2 because it masks a system supplied function (not related to your problem). – cdarke Apr 18 '16 at 15:28
  • 2
    Why not just `u"{}".format(whatever)` – Padraic Cunningham Apr 18 '16 at 15:30
  • @cdarke Isn't `print` supposed to be a convenience? Isn't there a way to recover its convenient use in conjunction with `StringIO`? I really dislike having more than one way of writing to a file, and `print` is currently the most readable way of doing so. –  Apr 18 '16 at 15:31
  • @Rhymoid: I think the expression is "TMTOWTDI" (Tim-toady) :-) – cdarke Apr 18 '16 at 15:32
  • @PadraicCunningham That'd become `print(one_field, another_field, *("{:f}".format(x) for x in [floaty_field, another_floaty_field, maybe_even_more]))`. Again, it's not exactly convenient, and it feels like I'll have to make a sensible wrapper around `csvwriter` at this point. –  Apr 18 '16 at 15:33
  • 1
    `map(u"{}".format, lst)` will at least make it a little more concise – Padraic Cunningham Apr 18 '16 at 15:36
  • @PadraicCunningham Oh wait, you mean for _every_ argument. That's an interesting idea, but then I'll have to either `sep.join(map(u"{}".format, lst))`, or pass the kwargs using a dict. After all, [parameter lists in Python 2 have a different syntax than those in Python 3](http://stackoverflow.com/questions/9872824/calling-a-python-function-with-args-kwargs-and-optional-default-arguments). Guess I'll just have to cut the knot and implement a writer object. –  Apr 18 '16 at 15:53

1 Answers1

1

This is what I do with this issue:

import sys

if sys.version_info[0] == 2:  # Not named on 2.6
    from __future__ import print_function
    from StringIO import StringIO
else:
    from io import StringIO

This breaks PEP008 by the way (imports should be at the top of the file), but personally I think it is justified.

cdarke
  • 42,728
  • 8
  • 80
  • 84
  • Another reason why I took `io.StringIO` is because [`StringIO.StringIO` doesn't play well with `with`](http://stackoverflow.com/questions/12028637/pythons-stringio-doesnt-do-well-with-with-statements): it doesn't implement `__exit__`. For that, I'll just have to `try`/`finally` explicitly. –  Apr 18 '16 at 15:38
  • @Rymoid: off the wall, but I wonder what py3to2.py makes of that? https://pypi.python.org/pypi/py3to2/0.2.0 – cdarke Apr 18 '16 at 15:44