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(开发者_如何学C5))
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.
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.
It really depends. Why do you need a float
? Would an int
break the function? If so, why?
If you need the parameter to support a function/property that a float
has but an int
does not you should check for that function/property, not that the parameter happens to be a float
. Check that the object can do what you need it to do, not that it happens to be a particular type that you're familiar with.
Who knows, maybe someone will find some major problem with Python's implementation of float
and create a notbrokenfloat
library. It might support everything a float does while fixing some exotic bug, but its objects wouldn't be of type float
. Manually casting it to a float
might remove all the benefits of this nifty new class (or could break outright).
Yes, that's an unlikely example, but I think that's the right mindset to get into when working with a dynamically typed language.
There is exactly one time when integer vs. float will be a problem. This is the only time when you will find a "simple" bug that's weird and a challenge to debug.
Division.
Everything else does the conversion you need when you need it.
If you are using Python 2.x and casually throwing around /
operators without thinking, you can -- under some common circumstances -- wind up doing the wrong thing.
You have several choices.
from __future__ import division
will give you Python 3 semantics for division.Run with the
-Qnew
option at all times to get the new division semantics.Use
float
near/
operations.
Division is the only place where type can matter. It's the only time that integers behave differently from floats in a way that silently affects your results.
All other type mismatch problems will fail spectacularly with a TypeError
exception. All others. You won't waste time debugging. You'll know immediately what's wrong.
To be more specific.
There's no debugging of "expect a string but didn't get a string". This will crash immediately with a traceback. No confusion. No time lost thinking. If a function expects a string, then the caller must provide the string -- that's the rule.
Alternative 2 above is used RARELY to correct the problem where you have a function that expects a string AND you got confused and forgot to provide a string. This mistake happens RARELY and it leads to an immediate type exception.
精彩评论