How to properly incorporate duck-typing to return original type?
-
20-06-2021 - |
Question
I really hope this isn't a duplicate. I tried to search for my question and I couldn't seem to find it.
So I have a fairly simple function that converts feet to meters:
def feetToMeters(val):
return numpy.array(val) * 0.3048
This works nicely and accepts ints, floats, arrays, and lists. However, if I put in a list (instead of a numpy array), I'd like to have a list returned. So I wrote this:
def feetToMeters(val):
try:
return val * 0.3084
except TypeError:
return [0.3084 * v for v in val]
(Alternatively I could use return list(numpy.array(val) * 0.3084)
for the last line if I want to use numpy
here, which I don't know if that really matters.)
Is this the best way to incorporate duck-typing here so that I can avoid the use of the type
function? Originally I tried AttributeError
, but it didn't work. Still, I am weary about TypeError
even though it seems to work.
Would it be sacrilegious to use if type(val) is list
instead?
Solution
Would it be sacrilegious to use
if type(val) is list
instead?
Yes, because it doesn't work for subclasses of list. If you want to go this way, at least do isinstance(val, list)
. Here's a solution that treats lists specially, and convert everything else (including scalars and tuples) to NumPy arrays:
def feetToMeters(feet):
meters = np.asarray(feet) * 0.3048
return list(meters) if isinstance(feet, list) else meters
Note that:
- passing an instance of a subclass of
list
will cause a plainlist
to be returned; - passing a list of lists will cause a list of NumPy arrays to be returned.
You could extend this to handle more types specially, but in general, for each type to be handled, you need to write more code as you need to know how to construct that type. Therefore, this kind of type conversion is usually left to client code.
OTHER TIPS
Since you already use numpy I would avoid using exceptions here with something like
def feetToMeters(val):
if numpy.isscalar(val):
return val * 0.3084
else:
return numpy.array(val) * 0.3048
Catching TypeError is dangerous since it could be caused by many things besides a list. I would generally use isinstance(val, list) for this purpose.
The trick is that you are trying to support scalars and sequences - maybe something like this would work for you:
def feetToMeters(val):
try:
return type(val)(val * 0.3048)
except TypeError:
return type(val)(v*0.3048 for v in val)