18

我最近阅读了建议不要在支持它的语言中使用 switch-case 语句的问题。就 Python 而言,我已经看到了许多 switch case 替换,例如:

  1. 使用字典(许多变体)
  2. 使用元组
  3. 使用函数装饰器(http://code.activestate.com/recipes/440499/
  4. 使用多态(推荐方法而不是类型检查对象)
  5. 使用 if-elif-else 阶梯
  6. 甚至有人推荐了访客模式(可能是外部的)

鉴于有多种选择,我在决定对特定代码段执行什么操作时有些困难。我想了解在一般情况下选择其中一种方法而不是另一种方法的标准。此外,如果我无法做出决定(附上选择的解释),我将不胜感激有关在特定情况下该怎么做的建议。

这是具体问题:
(1)

def _setCurrentCurve(self, curve):
        if curve == "sine":
            self.currentCurve =  SineCurve(startAngle = 0, endAngle = 14,
            lineColor = (0.0, 0.0, 0.0), expansionFactor = 1,
            centerPos = (0.0, 0.0))
        elif curve == "quadratic":
            self.currentCurve = QuadraticCurve(lineColor = (0.0, 0.0, 0.0))

此方法由qt-slot调用,以响应选择从菜单中绘制曲线。一旦申请完成,上述方法将包含总共 4-7 条曲线。在这种情况下使用一次性字典是否合理?由于最明显的方法是 if-elif-else,我应该坚持吗?我还考虑在这里使用 **kargs(在朋友的帮助下),因为所有曲线类都使用 **kargs...

(2)
第二段代码是一个qt-slot,当用户更改曲线的属性时会调用它。基本上,插槽从 gui (spinBox) 中获取数据并将其放入适当曲线类的实例变量中。在这种情况下,我又遇到了同样的问题——我应该使用字典吗?

这是前面提到的插槽-

def propertyChanged(self, name, value):
    """A Qt slot, to react to changes of SineCurve's properties."""
    if name == "amplitude":
        self.amplitude = value
    elif name == "expansionFactor":
        self.expansionFactor = value
    elif name == "startAngle":
        self.startAngle = value
    elif name == "endAngle":
        self.endAngle = value  

作为参考,这里是连接到上述插槽的代码 -

def _connectToPage(self, page):
    for connectionData in page.getConnectibles():
        self.connect(connectionData["object"],
                    SIGNAL(connectionData["signal"]),
                    lambda value, name = connectionData["property"]:\
                        self.currentCurve.propertyChanged(name, value))
        self.connect(connectionData["object"],
                    SIGNAL(connectionData["signal"]),
                    self.hackedDisplayArea.update) 

注意- self.endAngle 等在构造函数中初始化。

据我所知,选择 dict 的原因是为了快速查找。什么时候有保证?当我有 100 个或更多案例时?每次调用函数时继续构建和丢弃字典是个好主意吗?如果我在函数之外为此目的构建一个字典,我应该检查是否在其他地方需要它?如果其他地方不需要它会发生什么?

我的问题是如果有最好的做法是什么?做事最好/最优雅的方式是什么?换句话说,何时使用if-elif-else,何时使用其他选项?

4

7 回答 7

24

叹。对问题的错误部分过多地绞尽脑汁。switch 语句不是问题。有很多表达“另类”的方式不增加意义

问题是意义——而不是技术声明的选择。

共有三种常见模式。

  • 将键映射到对象。如果字典几乎是完全静态的,并且您在一个简单的键和另一个更复杂的东西之间有一个映射,请使用它。每次需要时都在运行中构建字典是愚蠢的。如果您的意思是这样,您可以使用它:您的“条件”是映射到对象的简单静态键值。

  • 子类之间的变体行为。使用多态而不是类型检查对象。正确的。如果您在多个具有不同行为的类中有类似的对象,它们应该是多态的。尽可能多地使用它。

  • 其他变体行为。使用 if-elif-else 阶梯。当您没有大量静态的键值映射时使用它。当条件复杂时使用它,或者你的意思是过程,而不是对象。

其他一切都只是可以实现类似结果的棘手代码。

使用元组。这只是没有映射的字典。这需要搜索,并且应尽可能避免搜索。不要这样做,效率低下。使用字典。

使用函数装饰器(http://code.activestate.com/recipes/440499/)。恶心。这隐藏了您正在解决的问题的 if-elif-elif 本质。不要这样做,选择是排他性的并不明显。使用其他任何东西。

甚至有人推荐了访客模式。当您有一个遵循复合设计模式的对象时使用它。这取决于多态性的工作,所以它并不是一个真正不同的解决方案。

于 2009-02-27T12:51:21.440 回答
8

在第一个示例中,我当然会坚持使用 if-else 语句。事实上,我没有理由不使用 if-else,除非

  1. 您发现(例如使用 profile 模块)if 语句是一个瓶颈(IMO 不太可能,除非您有大量的情况很少做)

  2. 使用字典的代码更清晰/重复更少。

你的第二个例子我实际上会重写

setattr(self, name, value)

(可能添加一个断言语句来捕获无效名称)。

于 2009-02-27T12:21:10.560 回答
2

考虑到这是为了响应用户操作(从菜单中选择某些东西)而完成的,并且您预期的选择数量非常少,我肯定会选择一个简单的 if-elif-else 阶梯。

选择速度是没有意义的,因为它只会在用户可以做出选择的情况下发生,这不是“光线追踪器的内部循环” - 领土。当然,给用户快速反馈很重要,但由于案例数量很少,也不存在这种危险。

优化简洁是没有意义的,因为(imo 更清晰,零可读性开销)if-ladder 无论如何都会非常短。

于 2009-02-27T12:27:13.353 回答
2

关于您的字典问题:

据我所知,选择 dict 的原因是为了快速查找。什么时候有保证?当我有 100 个或更多案例时?每次调用函数时继续构建和丢弃字典是个好主意吗?如果我在函数之外为此目的构建一个字典,我应该检查是否在其他地方需要它?如果其他地方不需要它会发生什么?

  1. 另一个问题是可维护性。拥有 string->curveFunction 字典允许您对菜单进行数据驱动。然后添加另一个选项只需将另一个字符串->函数条目放入字典中(它位于专用于配置的代码的一部分中。

  2. 即使您只有几个条目,它也会“分离关注点”;_setCurrentCurve负责在运行时进行接线,而不是定义组件盒。

  3. 建立字典并坚持下去。

  4. 即使它没有在其他地方使用,您也可以获得上述好处(可定位性、可维护性)。

我的经验法则是问“这里发生了什么?” 对于我的代码的每个组件。如果答案是形式

………………

(如“定义函数库并将每个函数菜单中的值相关联”)然后有一些问题需要分开。

于 2009-02-27T12:35:44.800 回答
1

每个公开的选项都非常适合某些场景:

  1. if-elif-else:简单、清晰
  2. 字典:当你动态配置它时很有用(想象你需要在一个分支上执行一个特定的功能)
  3. 元组:每个分支的多个选择的 if-else 案例的简单性。
  4. 多态性:自动面向对象分支
  5. 等等

Python 是关于可读性和一致性的,即使您的决定始终是主观的并且取决于您的风格,您也应该始终考虑 Python 口头禅。

./亚历克斯

于 2009-02-27T12:23:10.493 回答
1

关于第二个例子,我同意 df 的观点。第一个示例我可能会尝试使用字典重写,特别是如果所有曲线构造函数都具有相同的类型签名(可能使用 *args 和/或 **kwargs)。就像是

def _setCurrentCurve(self, new_curve):
    self.currentCurve = self.preset_curves[new_curve](options_here)

或者甚至

def _setCurrentCurve(self, new_curve):
    self.currentCurve = self.preset_curves[new_curve](**preset_curve_defaults[new_curve])
于 2009-02-27T12:27:03.713 回答
1

在 Python 中,不要考虑如何替换 switch 语句。

请改用类和多态性。尝试将有关每个可用选择以及如何实现它的信息保存在一个地方(即实现它的类)。

否则,您最终将拥有很多地方,每个地方都包含每个选择的一小部分,并且更新/扩展将是维护的噩梦。

这正是OOD试图通过抽象、信息隐藏、多态性和大量来解决的问题。

考虑一下您拥有哪些对象类及其属性,然后围绕它们创建一个 OO 架构。这样你就再也不用担心缺少“switch”语句了。

于 2009-02-27T13:23:21.550 回答