这是使用isinstance pythonic /“good”吗?

时间:2010-06-24 15:50:25

标签: oop python

this问题的一个副作用是我引导this post,其中指出:

  

每当使用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“pythonic”和“good”OOP?

4 个答案:

答案 0 :(得分:5)

不一般。对象的接口应该定义它的行为。在上面的示例中,如果other使用了一致的接口,那会更好:

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

即使这看起来功能较少,但从概念上来说它更清洁。现在,如果other与接口不匹配,则将其保留为语言以引发异常。您可以通过 - 例如 - 使用整数的“接口”创建int“构造函数”来解决添加MyTime次的问题。这样可以使代码更清晰,为下一个人留下更少的惊喜。

其他人可能不同意,但我觉得如果你在特殊情况下使用反射,例如在实现插件架构时,可能会有isinstance的位置。

答案 1 :(得分:4)

isinstance,自从Python 2.6以来,只要你遵循经典的“4人帮”书中所解释的“良好设计的关键规则”已经变得非常好:设计到界面,不是实施。具体来说,2.6的新抽象基类是您应该用于isinstanceissubclass检查的唯一内容,不是具体的“实现”类型。

不幸的是,在2.6的标准库中没有抽象类来概括“这个数字是积分”的概念,但你可以通过检查类是否有一个特殊的方法__index__来构建一个这样的ABC(< strong>不使用__int__,这也是由floatstr - __index__等明确的非整数类提供的断言“这个类的实例可以变成整数而不会丢失重要信息”)并在那个“接口”(抽象基类)而不是特定的实现isinstance上使用int,这是一种方式限制太多了。

您还可以制作一个ABC,总结“拥有m,h和s属性”的概念(可能有助于接受属性同义词以容忍datetime.timetimedelta个实例,例如 - 不确定你是代表你的MyTime班级的瞬间或时间流逝,名称暗示前者但存在加法表明后者),再次避免isinstance的限制性影响{ {1}}使用具体实现 cass。

答案 2 :(得分:2)

第一次使用很好,第二种不是。将参数传递给int(),以便您可以使用类似数字的类型。

答案 3 :(得分:1)

为了进一步阐述我在Justin的回答中所做的评论,我会保留他的代码__iadd__(即,所以MyTime对象只能添加到其他MyTime对象)并在此重写__init__方式:

def __init__(self, **params):
    if params.get('sec'):
        t = params['sec']
        self.h = t/3600
        t %= 3600
        self.m = t/60
        t %= 60
        self.s = t
    elif params.get('time'):
        t = params['time']
        self.h = t.h
        self.m = t.m
        self.s = t.s
    else:
        if params:
            raise TypeError("__init__() got unexpected keyword argument '%s'" % params.keys()[0])
        else:
            raise TypeError("__init__() expected keyword argument 'sec' or 'time'")

# example usage
t1 = MyTime(sec=30)
t2 = MyTime(sec=60)
t2 += t1 
t3 = MyTime(time=t1)

我只是尝试选择简短的关键字参数,但您可能希望获得比我更具描述性的信息。