Search code examples
pythondynamic-typing

Best place to coerce/convert to the right type in Python


I'm still fairly new to Python and I'm trying to get used to its dynamic typing. Sometimes I have a function or a class that expects a parameter of a certain type, but could get a value of another type that's coercible to it. For example, it might expect a float but instead receive an int or a decimal. Or it might expect a string, but instead receive an object that defines the __str__ special method.

What is the best practice for coercing the argument to the right type (and the reason for it)? Do I do it in the function/class or in the caller? If in the caller, do I also check for it in the function? Eg.

Alternative 1:

def myfunc(takes_float):
    myval = float(takes_float)

myfunc(5)

Alternative 2:

def myfunc(takes_float):
    myval = takes_float

myfunc(float(5))

Alternative 3:

def myfunc(takes_float):
    assert isinstance(takes_float, float)
    myval = takes_float

myfunc(float(5))

I've already read this answer and this one and they say that checking types in Python is "bad", but I don't want to waste time tracking down very simple bugs which would be instantly picked up by the compiler in a statically typed language.


Solution

  • You "coerce" (perhaps -- it could be a noop) when it's indispensable for you to do so, and no earlier. For example, say you have a function that takes a float and returns the sum of its sine and cosine:

    import math
    def spc(x):
      math.sin(x) + math.cos(x)
    

    Where should you "coerce" x to float? Answer: nowhere at all -- sin and cos do that job for you, e.g.:

    >>> spc(decimal.Decimal('1.9'))
    0.62301052082391117
    

    So when it is indispensable to coerce (as late as possible)? For example, if you want to call string methods on an argument, you do have to make sure it's a string -- trying to call e.g. .lower on a non-string won't work, len might work but do something different than you expect if the arg is e.g. a list (give you the number of items in the list, not the number of characters its representation as a string will take up), and so forth.

    As for catching errors -- think unit tests -- semidecent unit tests will catch all errors static typing would, and then some. But, that's a different subject.