python3 utility: convert safely to int I made this utility function to check for parameters.
All it does is checking whether a given variable is an int, or a float/str that represent exactly an int (ie. no decimal part or NaN), and casting to that int (raising a ValueError exception on failure).
I'm not very experienced with Python (especially Python 3+), so I'm still learning and trying to improve my programming skills.
I'm open to any critics, improvements, and bad/good practices tips.
Also I'm trying to learn how to properly document Python code (my IDE is PyCharm and hints me to use reStructuredText docblocks, and default Python3 typing hints).
I'm using Python3.6 for the following code:
util.py
from typing import Union


def int_exact_cast(n: Union[int, float, str]) -&gt; int:
    &quot;&quot;&quot;
    Tries to cast parameter to int without losing decimal part or raise an exception
    :param Union[int, float] n: The value to be converted
    :type n: Union[int, float]
    :return: The converted int value
    :rtype: int
    :raises ValueError: raises ValueError exception if the parameter can't be casted exactly to int
    &quot;&quot;&quot;

    t = type(n)

    if t is int:
        return n  # return immediately if is already int (optimization)

    if not (t is str or t is float):
        raise ValueError('Parameter must be an int, str or float')

    f = float(n)

    if not f.is_integer():  # not checking for math.nan because in that case is_integer() returns False
        raise ValueError('Parameter contains a decimal part')

    return int(f)
I know this question/post is old, but...



Use isinstance rather than directly storing and then checking the type() output.

We can get rid of t and two of your if statements to check type by using isinstance instead, and check against all three acceptable types:

if not (isinstance(n, (float, str, int))):
    raise ValueError('Parameter must be an int, str or float')


Just a thought on this, though: If something provided is not an int, str, or float, then it's an invalid type, and should raise a TypeError, rather than a ValueError, because TypeError is more generally used when you're passing an invalid object type into a function that can't handle it.  Therefore, your raise ValueError("...") for cases where the parameter is not an int, str, or float should probably be a TypeError with the same error message.

As was mentioned, isinstance will not behave right if you are using inherited classes which are subclasses of other primary classes.  However, consider that subclasses building upon other immutable classes are rare and as long as you aren't testing for instances of a bare object, this should suit your purposes.



The final code is this:

from typing import Union


def int_exact_cast(n: Union[int, float, str]) -&gt; int:
    """
    Tries to cast parameter to int without losing decimal part or raise an exception
    :param Union[int, float] n: The value to be converted
    :type n: Union[int, float]
    :return: The converted int value
    :rtype: int
    :raises ValueError: raises ValueError exception if the parameter can't be casted exactly to int
    """

    if not (isinstance(n, (float, str, int))):
        raise ValueError('Parameter must be an int, str or float')

    f = float(n)

    if not f.is_integer():
        raise ValueError('Parameter contains a decimal part')

    return int(f)




Just keep in mind that if the string can't be converted to a number (such as if the string is 'I Am A Teapot'), then you get a different ValueError from the conversion system, because it's Not a Number.  (Which is why you don't need the NaN check with this method, but you have to be careful with what you're working with)