在Python中强制转换为正确类型的最佳位置
我刚开始学习Python,正在适应它的动态类型。有时候,我会写一个函数或者类,它需要一个特定类型的参数,但可能会收到其他类型的值,这些值可以转换成所需的类型。例如,它可能需要一个float
类型的值,但却收到了一个整数或者小数。又或者,它可能需要一个字符串,但却收到了一个定义了__str__
特殊方法的对象。
那么,怎样才能把参数转换成正确的类型是最好的做法呢?我应该在函数或类里面处理,还是在调用者那里处理?如果在调用者那里处理,我在函数里还需要检查吗?比如:
替代方案 1:
def myfunc(takes_float):
myval = float(takes_float)
myfunc(5)
替代方案 2:
def myfunc(takes_float):
myval = takes_float
myfunc(float(5))
替代方案 3:
def myfunc(takes_float):
assert isinstance(takes_float, float)
myval = takes_float
myfunc(float(5))
我已经看过这个回答和这个回答,他们说在Python中检查类型是“坏”的做法,但我不想浪费时间去追踪那些在静态类型语言中编译器能立刻发现的简单错误。
3 个回答
整数和浮点数之间的问题其实只有一个特定的情况,这也是你可能会遇到的一个“简单”的bug,奇怪而且难以调试。
那就是除法运算。
其他情况下,系统会在需要的时候自动帮你转换数据类型。
如果你在使用Python 2.x的时候,随便用 /
这个符号而不去想后果,在某些常见情况下,你可能会做出错误的操作。
你有几个选择:
使用
from __future__ import division
可以让你的除法运算像Python 3那样工作。始终使用
-Qnew
选项来获得新的除法运算规则。在进行
/
运算时,使用float
类型。
除法是唯一一个数据类型可能会影响结果的地方。只有在这里,整数和浮点数的表现会有所不同,且这种差异会悄悄地影响你的结果。
其他类型不匹配的问题通常会引发一个 TypeError
异常,表现得非常明显。所有其他情况都是如此。你不会浪费时间去调试,立刻就能知道哪里出错了。
更具体一点来说。
没有“期待一个字符串但没有得到字符串”的调试过程。这种情况会立即崩溃,并显示错误追踪信息。没有混淆,也不会浪费时间去思考。如果一个函数期待一个字符串,那么调用者必须提供这个字符串——这是规则。
上面提到的第二个选择很少用来解决你有一个函数期待字符串但你忘记提供字符串的情况。这种错误发生得很少,而且会立即引发类型异常。
这真的要看情况。你为什么需要一个float
?如果用int
会让功能出问题吗?如果会,那是为什么呢?
如果你需要这个参数支持某个float
特有的功能,但int
没有,那你应该检查这个功能,而不是单纯看参数是不是float
。要确认这个对象能否完成你需要的事情,而不是它恰好是你熟悉的某种类型。
谁知道呢,也许有人会发现Python的float
实现有大问题,然后创建一个notbrokenfloat
库。这个库可能支持float
的所有功能,同时修复一些奇怪的bug,但它的对象就不会是float
类型。手动把它转成float
可能会失去这个新类的所有好处(甚至可能直接出错)。
是的,这个例子不太可能,但我觉得在使用动态类型语言时,保持这样的思维方式是很重要的。
你“强制转换”(也许——这可能是个无效操作)只有在必要的时候才需要这样做,而不是提前。例如,假设你有一个函数,它接收一个浮点数并返回它的正弦和余弦的和:
import math
def spc(x):
math.sin(x) + math.cos(x)
那么,你应该在哪里“强制转换”x为浮点数呢?答案是:根本不需要——正弦和余弦函数会帮你处理这个问题,比如:
>>> spc(decimal.Decimal('1.9'))
0.62301052082391117
那么,什么时候必须要强制转换(尽量晚一点做)呢?例如,如果你想对一个参数调用字符串的方法,你就必须确保这个参数是字符串——如果你试图对一个非字符串调用比如 .lower
,那是行不通的;而 len
可能会有效,但如果参数是一个列表,它的结果会和你预期的不一样(它会给你列表中项的数量,而不是字符串表示形式所占的字符数),等等。
至于捕捉错误——可以考虑一下 单元测试——合格的单元测试会捕捉到所有静态类型检查能捕捉到的错误,还有更多。但这又是另一个话题。