PythonMastery
reference 3 min read · lesson 1 of 60 in Errors

TypeError: unsupported operand type(s) for +: 'int' and 'str'

1 · The lesson

read

What this error means

Python refuses to join a number and a string with +. The + operator means "add" for numbers and "concatenate" for strings, and Python won't guess which one you meant. Unlike JavaScript or PHP, Python is strongly typed: it will not silently convert 25 into "25" (or the other way round) just because you used +.

When you see it

python
Traceback (most recent call last):
  File "main.py", line 2, in <module>
    print("I am " + age + " years old")
TypeError: can only concatenate str (not "int") to str

Or the reverse direction:

python
Traceback (most recent call last):
  File "main.py", line 2, in <module>
    total = 5 + "10"
TypeError: unsupported operand type(s) for +: 'int' and 'str'

Why it happens

+ is overloaded. For int + int it adds. For str + str it concatenates. For int + str there is no sensible default: should 5 + "10" be 15 or "510"? Python refuses to choose and raises TypeError instead. Any value coming from input(), a CSV cell, or a JSON field is almost always a str, even when it looks like a number.

How to fix it

Cleanest fix: use an f-string:

python
age = 25
print(f"I am {age} years old")

Convert explicitly with str() or int():

python
age = 25
print("I am " + str(age) + " years old")     # the int is turned into a str

total = 5 + int("10")                         # the str is turned into an int

Or use .format:

python
print("I am {} years old".format(age))
+ setup added so this can run · re-runs 2 earlier blocks on this page first
# Lightweight mock for objects whose attributes/methods aren't critical
class _AutoMock:
    def __init__(self, name='mock'): self._name = name
    def __getattr__(self, k): return _AutoMock(self._name + '.' + k)
    def __call__(self, *a, **kw):
        print('-> ' + self._name + '() called')
        return _AutoMock(self._name + '()')
    def __repr__(self): return '<mock ' + self._name + '>'
    def __str__(self): return '<mock ' + self._name + '>'
    def __bool__(self): return True
    def __iter__(self): return iter([])
    def __len__(self): return 0
    def __getitem__(self, k): return _AutoMock(self._name + '[...]')
    def __setitem__(self, k, v): pass
    def __enter__(self): return self
    def __exit__(self, *a): return False
    async def __aenter__(self): return self
    async def __aexit__(self, *a): return False
    def __add__(self, o): return self
    def __radd__(self, o): return self
    def __sub__(self, o): return self
    def __mul__(self, o): return self
    def __rmul__(self, o): return self
    def __truediv__(self, o): return self
    def __eq__(self, o): return isinstance(o, _AutoMock)
    def __hash__(self): return hash(self._name)
    def __lt__(self, o): return True
    def __le__(self, o): return True
    def __gt__(self, o): return False
    def __ge__(self, o): return False
    def __mro_entries__(self, bases): return (object,)

age = _AutoMock('age')

# Earlier blocks on this page that this one builds on, re-run quietly first.
_pm_earlier = [
    'age = 25\nprint(f"I am {age} years old")',
    'age = 25\nprint("I am " + str(age) + " years old")     # the int is turned into a str\n\ntotal = 5 + int("10")                         # the str is turned into an int',
]
def _pm_run_earlier(_srcs, _g):
    import sys as _s, io as _io, warnings as _w
    for _src in _srcs:
        _o, _e = _s.stdout, _s.stderr
        _s.stdout = _s.stderr = _io.StringIO()
        try:
            with _w.catch_warnings():
                _w.simplefilter('ignore')
                exec(compile(_src, '<earlier block>', 'exec'), _g)
        except (Exception, SystemExit):
            pass
        finally:
            _s.stdout, _s.stderr = _o, _e
    if 'matplotlib.pyplot' in _s.modules:
        _s.modules['matplotlib.pyplot'].close('all')   # their plots belong to their own blocks
_pm_run_earlier(_pm_earlier, globals())
del _pm_earlier, _pm_run_earlier

Pick the conversion direction based on what the value means. If you need to do arithmetic, convert to int or float. If you just need to display it, convert to str (or use an f-string, so you don't need to convert it yourself).

When you'd see this in real code

  • Reading numeric input from input(). It always returns a str, so input("Age: ") + 1 raises this error.
  • Concatenating user IDs into a URL: "/users/" + user_id when user_id is an int from the database.
  • Building log lines: "Processed " + count + " rows" when count is a counter.
  • CSV columns are strings, so trying to sum a column without int() will fail at the first row.

See Also

Found this useful? Share it with a friend who’s learning Python.