Last Updated: February 25, 2016
· scott2b

Python string templating vs. concatenation gotcha

Be aware of how Python treats None differently when concatenating strings vs. using string templates. Attempting to cat a None to a string (or vice versa) will raise a TypeError, whereas Python lets you pass None into a string formatting template ... and it gets rendered as 'None'.

As noted in the comments by @jswinarton, this happens because, whereas concatenation does not do any type coercion, the %s operator ensures strings by calling str on the value (and the string representation of None is 'None').

>>> 'ickleme' + 'pickleme'
>>> '%s%s' % ('ickleme', 'pickleme')
>>> 'pickleme' + None
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: cannot concatenate 'str' and 'NoneType' objects
>>> '%s%s' % ('pickleme', None)

Note the same behavior for named formatting strings also:

>>> '%(captain)s, %(crew)s, %(boatswain)s' % { 'captain':'ickleme', 'crew':'pickleme', 'boatswain':None }
'ickleme, pickleme, None'

2 Responses
Add your response

This is because the % operator performs implicit type coercion as well as formatting. Behind the scenes, python is converting the value you pass to it using str().

>>> None
>>> str(None)
>>> 'this is a %s' % None
'this is a None'

This works predictably with other Python types, as well.

>>> 'this is a %s' % 123
'this is a 123'
>>> 'this is a %s' % {'abc': 123}
"this is a {'abc': 123}"

over 1 year ago ·

You are right. I was forgetting that concatenation does not do any coercion, even for types that have obvious string representations.

I did not mean to imply that None is getting some special treatment here. The str call is the definition of using the %()s operator. One can also call repr with %()r. However, None seems particularly problematic in that, in most cases, it seems like you really don't want 'None' to sneak into your presentation code. Whereas, other types have available explicit replacement operators (%d, %f, etc.) which provide a certain kind of protection for those types, I think it is unfortunate that there is not an explicit replacement operator for the string type also.

There could just as well have been a design decision to call int() on subjects to the %d operator -- but it doesn't. So, there does seem to be some design inconsistency here worth being aware of.

over 1 year ago ·