【问题标题】:Is this use of isinstance pythonic/"good"?这是 isinstance pythonic/"good" 的使用吗?
【发布时间】:2010-06-24 15:50:25
【问题描述】:

this 问题的副作用是我被引导到this post,它指出:

每当使用 isinstance 时,控制流分叉;一种类型的对象在一个代码路径下,而其他类型的对象在另一个上——即使它们实现了相同的接口!

并暗示这是一件坏事。

但是,我以前使用过这样的代码,我认为这是一种面向对象的方式。类似于以下内容:

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 的用法吗?

【问题讨论】:

    标签: oop python


    【解决方案1】:

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

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

    尽管这看起来功能较少,但从概念上讲它更干净。现在,如果other 与接口不匹配,则将其留给语言抛出异常。您可以通过使用整数的“接口”创建MyTime“构造函数”来解决添加int 次的问题。这样可以使代码更简洁,给下一个人留下更少的惊喜。

    其他人可能不同意,但我觉得如果您在特殊情况下(例如在实现插件架构时)使用反射,isinstance 可能会有一席之地。

    【讨论】:

    • 我同意。 Justin 提倡只能将 MyTime 对象添加到其他 MyTime 对象中,这是最好的方法。可能有点模糊的部分是,这种技术可能看起来只是在构造函数上推动 isinstance 调用。但是,还有其他 Python 方式可以通过工厂方法或关键字参数以多种方式构造对象——我最喜欢的是后者。见stackoverflow.com/questions/356718/…
    • 如何使用整数的“接口”实现构造函数?您会在__iadd__ 中添加调用还是其他方式?
    【解决方案2】:

    为了进一步详细说明我在贾斯汀的回答下所做的评论,我将保留他的代码 __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)
    

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

    【讨论】:

    • 那么这是更好的 OOP/更 Python 的吗?另外,有什么理由(除了偏好等)不只做__init__(self, h=0, m=0, s=0),然后只做self.h = h + s/3600,等等?
    • 这符合 Python 的显式和可读方式,此外,它还允许您在需要时轻松添加新参数。它对调用代码有一个额外的好处,因为我不必查看实际的构造函数代码来理解传递的参数;解释由关键字提供。如果您要使用提到的 3 个参数进行设计,请将 MyTime(2,5,20) 与 MyTime(hr=2,min=5,sec=20) 的可读性作为示例进行比较。
    • P.S.就像我在另一条评论中提到的那样,使对象构造明确且可读的另一种方法是使用工厂方法。两者都是实现同一目标的不同方式;使用任何你喜欢的。工厂方法使构造更容易(每种类型一个方法)但非标准,因为您使用的是类方法而不是__init__。关键字参数更标准(一种__init__ 方法),但处理所有可能的参数或报告错误可能更加困难。
    【解决方案3】:

    isinstance,从 Python 2.6 开始,只要你遵循经典的“4 人帮”书中解释的“良好设计的关键规则”:设计到一个界面,而不是一个实现。具体来说,2.6 的新抽象基类是您应该用于isinstanceissubclass 检查的唯一东西,不是具体的“实现”类型。

    遗憾的是2.6的标准库中没有抽象类来总结“这个数是整数”的概念,但是你可以通过检查类是否有特殊方法__index__不要使用__int__,它也由诸如floatstr这样的绝对非整数类提供——__index__被专门引入以断言“此类的实例可以制成没有丢失重要信息的整数”)并在该“接口”(抽象基类)上使用isinstance,而不是具体实现int,这太严格了。

    您还可以制作一个 ABC 来总结“具有 m、h 和 s 属性”的概念(可能有助于接受属性同义词以便容忍 datetime.timetimedelta 实例,例如 - 不确定您是用 MyTime 类表示瞬间还是时间流逝,名称暗示前者,但添加的存在暗示后者),再次避免 isinstance 具有非常限制性的含义 具体实现 cass.

    【讨论】:

    • 我从中提取示例的情况是两者兼而有之。我正在创建一个可视计时器,创建一个可以“打印”的对象比编写一些函数来处理日期时间要容易得多。当然,我写这篇文章已经过去了大约 2 年,所以我想我正在养成更好的习惯(例如询问更有经验的人),并且可能不会以完全相同的方式实现它。
    【解决方案4】:

    第一次用没问题,第二次不行。将参数传递给int(),以便您可以使用类似数字的类型。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-05-21
      • 2010-09-26
      • 1970-01-01
      • 1970-01-01
      • 2019-09-15
      • 2010-09-25
      • 1970-01-01
      • 2011-07-14
      相关资源
      最近更新 更多