这种使用isinstance算是Pythonic/“好”的做法吗?
这段内容提到的一个副作用是,作者在回答一个问题时,发现了一个帖子,帖子里说:
每当使用
isinstance
时,程序的执行流程会分叉;一种类型的对象会走一条代码路径,而其他类型的对象则走另一条——即使它们实现了相同的接口!
这个帖子还建议,这种情况并不好。
不过,我之前也用过类似的代码,我认为这是面向对象编程(OO)的一种方式。像下面这样:
class MyTime(object):
def __init__(self, h=0, m=0, s=0):
self.h = 0
self.m = 0
self.s = 0
def __iadd__(self, other):
if isinstance(other, MyTime):
self.h += other.h
self.m += other.m
self.s += other.s
elif isinstance(other, int):
self.h += other/3600
other %= 3600
self.m += other/60
other %= 60
self.s += other
else:
raise TypeError('Addition not supported for ' + type(other).__name__)
所以我想问:
这种使用 isinstance
的方式算不算“符合Python风格”和“好的面向对象编程”?
4 个回答
第一次使用是没问题的,但第二次就不行了。你应该把参数传给 int()
,这样就可以使用类似数字的类型了。
从Python 2.6开始,isinstance
变得相当好用,只要你遵循一个“好的设计原则”,这个原则在经典的“设计模式”书中有提到:设计要面向接口,而不是具体实现。具体来说,2.6引入的新抽象基类(Abstract Base Classes)是你在使用isinstance
和issubclass
检查时唯一应该用的东西,而不是具体的“实现”类型。
可惜的是,在2.6的标准库中没有一个抽象类来总结“这个数字是整数”的概念,但你可以通过检查一个类是否有一个特殊的方法__index__
来创建这样一个抽象基类(不要使用__int__
,因为像float
和str
这样的类也有这个方法,它们并不是整数——__index__
是专门用来表明“这个类的实例可以无损地转化为整数”)。然后你可以在这个“接口”(抽象基类)上使用isinstance
,而不是具体的实现int
,因为后者限制太多了。
你还可以创建一个抽象基类来总结“拥有m、h和s属性”的概念(这可能对接受属性同义词很有用,比如可以容忍datetime.time
或者timedelta
的实例——不太确定你的MyTime
类是表示一个瞬间还是一段时间,名字暗示是前者,但加法的存在又暗示是后者),同样是为了避免使用具体实现类时isinstance
带来的限制。
一般来说不是这样。一个对象的接口应该定义它的行为。在你上面的例子中,如果other
使用一致的接口会更好:
def __iadd__(self, other):
self.h += other.h
self.m += other.m
self.s += other.s
虽然这看起来功能上少了一些,但从概念上讲要干净得多。现在,如果other
不符合接口,语言会自动抛出一个异常。你可以通过创建一个MyTime
的“构造函数”,来使用整数的“接口”来解决添加int
时间的问题。这样可以让代码更整洁,也能减少给下一个人带来的意外。
当然,其他人可能会有不同的看法,但我觉得在一些特殊情况下,比如实现插件架构时,使用isinstance
可能还是有用的。