networkx/networkx — pull request #990 by chebee7i (2013-10-22 19:25:04+00:00)
Title: Make make_str() always return unicode, no matter the Python version.

After seeing #989, I noticed something about `make_str` that bothered me. This pull request is built off @hagberg's branch for #989, so merging this will merge both tickets.

Consider the following example:

# -*- coding: utf-8 -*-
# Example 1
from networkx.utils import make_str
x = "qualité"
y = make_str(x)

For Python 2, `y` is of type `str`, but as an encoded string. For Python 3, `y` is of type `str`, but as a unicode string. This is consistent output type, but the interpretation is very different. Now, consider:

# -*- coding: utf-8 -*-
# Example 2
from networkx.utils import make_str
try:
# 2.x
x = unicode("qualité")
except NameError:
# 3.x
x = "qualité"
y = make_str(x)

For Python 2, `y` is of type `unicode`, whereas in Python 3, `y` is of type `str` (which is unicode). Now, the types are different, but the interpretation is the same.

Overall, for Python 2, `make_str` returns either a `str` or a `unicode` object. On one hand, this makes sense since the input was different in each case.

On the other hand, its more confusing because it means that our code is possibly working with encoded strings or unicode strings. Always working with unicode would make things easier to maintain, I think. Additionally, this would be similar in spirit to how we handle `map`, `range` and `zip`---we always treat them as iterators so that our code treatment is uniform across Python versions. Right now, internal NetworkX code handles "strings" differently depending on the Python version.

Below is a new proposal for `make_str`.

PY2 = sys.version_info[0] == 2
if PY2:
def make_str(x):
if isinstance(x, unicode):
return x
else:
# Note, this will not work unless x is ascii-encoded.
# That is good, since we should be working with unicode anyway.
# Essentially, unless we are reading a file, we demand that users convert
# any encoded strings to unicode before using the library.
#
# Also, the str() is necessary to convert integers, etc.
# unicode(3) works, but unicode(3, 'unicode-escape') demands a buffer.
#
return unicode(str(x), 'unicode-escape')
else:
def make_str(x):
if isinstance(x, str):
return x
else:
return str(x)

The important difference is that the `str` in name `make_str` is now interpreted to mean 'unicode', always. Its forward looking to Python 3 just as map, range, zip, etc. The benefit now is that all NetworkX code can be confident that make_str returns a unicode string. Note: the type will be different depending on the Python version, (e.g. unicode vs str) but we shouldn't care about the type as much as we care about what the type represents (e.g. encoded string vs unicode string).

In Python 3, examples 1 and 2 from above are the same. unicode strings in and unicode strings out.

In Python 2, examples 1 and 2 differ but `y` represents a unicode string in both cases. In example 1, `y` is a unicode object of length 8. In example 2, 'y' is a unicode object of length 7. The length 8 unicode string is probably not what the use wanted, but the user should also not expect us (NetworkX) to know how the string is encoded. So the user should first decode to unicode and then pass it into `make_str`---in which case, the output would be the same as in example 2. If `x` were an ascii encoded string, then examples 1 and 2 would be the same in Python 2 and `y` would be unicode strings in both cases.