这种使用isinstance算是Pythonic/“好”的做法吗?

12 投票
4 回答
3097 浏览
提问于 2025-04-16 00:23

这段内容提到的一个副作用是,作者在回答一个问题时,发现了一个帖子,帖子里说:

每当使用 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 个回答

2

第一次使用是没问题的,但第二次就不行了。你应该把参数传给 int(),这样就可以使用类似数字的类型了。

4

从Python 2.6开始,isinstance变得相当好用,只要你遵循一个“好的设计原则”,这个原则在经典的“设计模式”书中有提到:设计要面向接口,而不是具体实现。具体来说,2.6引入的新抽象基类(Abstract Base Classes)是你在使用isinstanceissubclass检查时唯一应该用的东西,而不是具体的“实现”类型。

可惜的是,在2.6的标准库中没有一个抽象类来总结“这个数字是整数”的概念,但你可以通过检查一个类是否有一个特殊的方法__index__来创建这样一个抽象基类(不要使用__int__,因为像floatstr这样的类也有这个方法,它们并不是整数——__index__是专门用来表明“这个类的实例可以无损地转化为整数”)。然后你可以在这个“接口”(抽象基类)上使用isinstance,而不是具体的实现int,因为后者限制太多了。

你还可以创建一个抽象基类来总结“拥有m、h和s属性”的概念(这可能对接受属性同义词很有用,比如可以容忍datetime.time或者timedelta的实例——不太确定你的MyTime类是表示一个瞬间还是一段时间,名字暗示是前者,但加法的存在又暗示是后者),同样是为了避免使用具体实现类时isinstance带来的限制。

8

一般来说不是这样。一个对象的接口应该定义它的行为。在你上面的例子中,如果other使用一致的接口会更好:

def __iadd__(self, other):
    self.h += other.h
    self.m += other.m
    self.s += other.s

虽然这看起来功能上少了一些,但从概念上讲要干净得多。现在,如果other不符合接口,语言会自动抛出一个异常。你可以通过创建一个MyTime的“构造函数”,来使用整数的“接口”来解决添加int时间的问题。这样可以让代码更整洁,也能减少给下一个人带来的意外。

当然,其他人可能会有不同的看法,但我觉得在一些特殊情况下,比如实现插件架构时,使用isinstance可能还是有用的。

撰写回答