Type Conversion in Python

Python converts numeric types automatically inside an expression and refuses everything else. Explicit conversion is a set of constructor functions, each with rules worth knowing.

Two kinds of conversion

  • Implicit - Python does it for you, silently, and only when no information is lost.
  • Explicit - you call a conversion function and take responsibility for the result.

Implicit conversion

Implicit conversion in Python is limited almost entirely to numbers. Inside one arithmetic expression, the narrower type is promoted to the wider one.

bool  ->  int  ->  float  ->  complex
print(True + 1)          # 2      bool promoted to int
print(3 + 2.5)           # 5.5    int promoted to float
print(2.0 + 3j)          # (2+3j) float promoted to complex

print(type(4 + 0.0))     # <class 'float'>

That is the whole list. Python does not convert between text and numbers implicitly, and that refusal is deliberate:

print("3" + 4)      # TypeError: can only concatenate str (not "int") to str

In a weakly typed language this might produce "34" or 7, and you would not know which until it went wrong in production. Python makes you say what you meant.

Explicit conversion

int()

print(int("42"))        # 42
print(int("  42  "))    # 42, surrounding whitespace is ignored
print(int(9.99))        # 9,  truncates towards zero, never rounds
print(int(-9.99))       # -9
print(int(True))        # 1
print(int("1010", 2))   # 10, parsed as binary
print(int("ff", 16))    # 255

# print(int("42.0"))    # ValueError: it is not an integer literal
# print(int("abc"))     # ValueError
# print(int(None))      # TypeError
Note the difference between ValueError and TypeError. The right type with unusable contents gives ValueError; the wrong type entirely gives TypeError. Reading that distinction saves debugging time.

float()

print(float("3.14"))    # 3.14
print(float("42"))      # 42.0
print(float(7))         # 7.0
print(float("1e3"))     # 1000.0
print(float("inf"))     # inf
# print(float("abc"))   # ValueError

str()

print(str(42))              # '42'
print(str(3.14))            # '3.14'
print(str([1, 2]))          # '[1, 2]'
print(str(None))            # 'None'

str() never fails; every object can produce some text form. Its partner is repr(), which aims at an unambiguous developer facing form:

value = "10"
print(str(value))     # 10
print(repr(value))    # '10'   - the quotes tell you it is text

bool()

Every object converts to a boolean. The falsy values are a short, closed list; everything else is truthy.

FalsyTruthy
False, NoneAny non zero number
0, 0.0, 0jAny non empty string, including "0" and "False"
""Any non empty container
[], (), {}, set()Objects with no rule saying otherwise
print(bool(0), bool(1))            # False True
print(bool(""), bool("False"))     # False True   <- note the second one
print(bool([]), bool([0]))         # False True   <- a list holding a falsy value is not empty

Container conversions

print(list("abc"))                 # ['a', 'b', 'c']
print(tuple([1, 2, 3]))            # (1, 2, 3)
print(set([1, 2, 2, 3]))           # {1, 2, 3} - duplicates removed
print(list({"a": 1, "b": 2}))      # ['a', 'b'] - the keys

pairs = [("a", 1), ("b", 2)]
print(dict(pairs))                 # {'a': 1, 'b': 2}

print(dict(zip(["a", "b"], [1, 2])))   # {'a': 1, 'b': 2}

dict() needs pairs. Passing a flat list raises an error, and zip is the usual way to build the pairs from two parallel sequences.

Converting user input safely

input() always returns a string, so conversion is unavoidable, and it can always fail.

raw = input("Enter your age: ")

try:
    age = int(raw)
except ValueError:
    print("That is not a whole number.")
else:
    print("Next year you will be", age + 1)

Never assume the conversion succeeds. A user typing twenty or pressing Enter on an empty line will crash an unguarded program.

Conversion that loses information

print(int(3.99))                 # 3    the fraction is gone
print(list({3, 1, 2}))           # order is arbitrary, not the insertion order
print(set([1, 1, 2]))            # duplicates are gone permanently
print(int(float("3.7")))         # 3    two conversions, one of them lossy

The last line is the standard way to accept "3.7" as an integer: convert to float first, because int("3.7") raises.

Common mistakes

  • Calling int("3.7"). Convert through float first.
  • Assuming int() rounds. It truncates. Use round() if rounding is wanted.
  • Trusting bool("False") to be False. Any non empty string is truthy.
  • Converting user input without a try block.
  • Using str() where repr() would have shown the problem, particularly when debugging whitespace.
  • Converting a set to a list and expecting a predictable order.

Best practices

  • Convert at the boundary: turn input into the right type as soon as it enters the program, and keep it that way.
  • Wrap every conversion of external data in try and except ValueError.
  • Prefer f-strings to explicit str() calls when building messages.
  • Use repr() in debug output so quotes and whitespace are visible.

Practice

  1. Explain why int("42 ") works but int("4 2") does not.
  2. Write a loop that keeps asking for a number until a valid integer is entered.
  3. Predict the truthiness of each: "", "0", [], [[]], 0.0, "None", {}, {0: 0}.
  4. Convert ["a", "b"] and [1, 2] into a single dictionary in one expression.
  5. Explain the difference between str(x) and repr(x) for the value "5", and when each is the right choice.

Conclusion

Python converts numbers implicitly and nothing else. Every other conversion is a function call you write on purpose, it may lose information, and on external data it may fail. Convert once, at the edge of your program, and check the result.

Written by Lorens Mishra

Default administrator account created by the installer.

Continue reading

All Python notes →

Discussion

0 comments
Sign in to join the discussion.

No comments yet. Be the first to say something.