基于输入类型 select 行为的大多数 Pythonic 方式?

Most Pythonic way to select behavior based on Type of input?

我有一些函数的实现细节取决于传递给它们的对象类型(具体来说,就是为 link Django 模型选择合适的方法来生成 QuerySet)。以下两个选项中哪一个是更 Pythonic 的实现方式?

如果梯子

def do_something(thing: SuperClass) -> "QuerySet[SomethingElse]":
    if isinstance(thing, SubClassA):
        return thing.property_set.all()
    if isinstance(thing, SubClassB):
        return thing.method()
    if isinstance(thing, SubClassC):
        return a_function(thing)
    if isinstance(thing, SubClassD):
        return SomethingElse.objects.filter(some_property__in=thing.another_property_set.all())
    return SomethingElse.objects.none()

词典

def do_something(thing: SuperClass) -> "QuerySet[SomethingElse]":
    return {
        SubClassA: thing.property_set.all(),
        SubClassB: thing.method(),
        SubClassC: a_function(thing),
        SubClassD: SomethingElse.objects.filter(some_property__in=thing.another_property_set.all()),
    }.get(type(thing), SomethingElse.objects.none())

字典选项的重复代码和行数较少,但 if 阶梯使 PyCharm 和 MyPy 更快乐(尤其是类型检查)。

我假设两者之间的任何性能差异都可以忽略不计,除非它在经常调用的例程的内部循环中(如 >>1 request/second)。

这正是多态要解决的问题类型,而"Pythonic"解决这个问题的方法就是使用多态。按照 "encapsulate what varies" 的概念,我建议创建一个所有 类 实现的基础 "interface",然后只需在所有 类.[= 上调用同名方法即可11=]

我将 "interface" 放在引号中,因为 Python 并不真正具有 OOP 中众所周知的接口。因此,您将不得不使用 sub类,并手动执行方法签名(即小心)。

演示:

class SuperClass:

    # define the method signature here (mostly for documentation purposes)
    def do_something(self):
        pass

class SubClassA(SuperClass):

    # Be careful to override this method with the same signature as shown in
    # SuperClass. (In this case, there aren't any arguments.)
    def do_something(self):
        print("Override A")

class SubClassB(SuperClass):

    def do_something(self):
        print("Override B")

if __name__ == '__main__':
    import random

    a = SubClassA()
    b = SubClassB()

    chosen = random.choice([a, b])

    # We don't have to worry about which subclass was chosen, because they
    # share the same interface. That is, we _know_ there will be a
    # `do_something` method on it that takes no arguments.
    chosen.do_something()