How to code a function that accepts float, list or numpy.array?

You need numpy.asarray. This takes as its first argument:

Input data, in any form that can be converted to an array. This includes lists, lists of tuples, tuples, tuples of tuples, tuples of lists and ndarrays.

and it returns:

Array interpretation of a. No copy is performed if the input is already an ndarray.

So you can implement your function like this:

import numpy as np

def get_lerp_factor(a, x, b):
    a, x, b = np.asarray(a), np.asarray(x), np.asarray(b)
    return ((x - a) / (b - a)).clip(0, 1)

This works for scalars:

>>> get_lerp_factor(0, 9, 16)
0.5625

and also for iterables:

>>> get_lerp_factor(2, range(8), 6)
array([ 0.  ,  0.  ,  0.  ,  0.25,  0.5 ,  0.75,  1.  ,  1.  ])

Actually, as long as the numpy array has the semantics you want for the operators you're using (-, /, <=, >=), then it already works. This is called "duck typing", where you don't really care what type your arguments have, just that they behave in a specific way.

Of course, a list is not going to behave like that. And the numpy array might not behave entirely like that either (the <= operator works, but results in an array of booleans, the => operator is not defined). So, you will have to check the type at runtime. One way to do this is to check if the __len__ method is supported:

try:
    if len(a) == len(b) == len(x):
         # handle list / array case
except TypeError:
    # oops, was a float

Note that it is generally a very bad idea to check the type explicitly (with isinstance(o, t)) since you want to maintain as much duck type semantics as possible. But sometimes you need to do just that.

Note also, that this is only really acceptable when the "semantics" of the function remain the same, regardless of the input type. If you are changing the meening of the function based on input types, then you're going to have a bad time! Or worse: Your users (consumers of your function) are going to have a bad time.