在 python 中进行多重调度的简单方法?(没有外部库或类构建?)

dyl*_*njm 6 python function multiple-dispatch

我正在编写一个一次性脚本来计算我正在运行的一些模拟的一些分析解决方案。

我想以一种基于其输入计算正确答案的方式实现一个函数。举例来说,假设我有以下数学方程:

tmax = (s1 - s2) / 2 = q * (a^2 / (a^2 - b^2))
Run Code Online (Sandbox Code Playgroud)

对我来说,我应该能够做类似的事情,这似乎很简单:

tmax = (s1 - s2) / 2 = q * (a^2 / (a^2 - b^2))
Run Code Online (Sandbox Code Playgroud)

我可能已经习惯了用 编写julia,但我真的不想让这个脚本变得比我需要的更复杂。

Ed *_*ton 5

只是想我会提供另外两个选择。

多重调度类型检查

Python 本身就支持@overload注释

不会影响运行时,但如果您选择使用它们,它会通知您的 IDE 和静态分析工具。

从根本上讲,您的实现将是 Python 希望您使用的一系列令人讨厌的黑客,但您将获得更好的调试支持。

这篇文章解释得更好,我修改了代码示例以显示多个参数:

# << Beginning of additional stuff >>
from typing import overload


@overload
def hello(s: int) -> str:
    ...


@overload
def hello(s: str) -> str:
    ...


@overload
def hello(s: int, b: int | float | str) -> str:
    ...
# << End of additional stuff >>

# Janky python overload
def hello(s, b=None):
    if b is None:
        if isinstance(s, int):
            return "s is an integer!"
        if isinstance(s, str):
            return "s is a string!"
    if b is not None:
        if isinstance(s, int) and isinstance(b, int | float | str):
            return "s is an integer & b is an int / float / string!"

    raise ValueError('You must pass either int or str')
Run Code Online (Sandbox Code Playgroud)
print(hello(1))           # s is an integer!
print(hello("Blah"))      # s is a string!
print(hello(11, 1))       # s is an integer & b is an int / float / string!
print(hello(11, "Blah"))  # s is an integer & b is an int / float / string!
Run Code Online (Sandbox Code Playgroud)

我的 IDE 在有问题的参数下放置了一条正常的错误行。

print(hello("Blah", "Blah"))  
# >> ValueError: You must pass either int or str
# PyCharm warns "Blah" w/ "Expected type 'int', got 'str' instead"

print(hello(1, [0, 1]))  
# >> ValueError: You must pass either int or str
# PyCharm warns [0, 1] w/ "Expected type 'int | float | str', got 'list[int]' instead"

print(hello(1, 1) + 1)  
# >> TypeError: can only concatenate str (not "int") to str
# PyCharm warns "+ 1" w/ "Expected type 'str', got 'int' instead"
Run Code Online (Sandbox Code Playgroud)

这是对这个帖子最直接的回答。

单次发货:

如果您只需要对函数或类方法进行单次调度,请查看最近的内容@singledispatch@singledispatchmethod注释。

功能:

from functools import singledispatch


@singledispatch
def coolAdd(a, b):
    raise NotImplementedError('Unsupported type')

@coolAdd.register(int)
@coolAdd.register(float)
def _(a, b):
    print(a + b)

@coolAdd.register(str)
def _(a, b):
    print((a + " " + b).upper())
Run Code Online (Sandbox Code Playgroud)
coolAdd(1, 2)                     # 3
coolAdd(0.1, 0.2)                 # 0.30000000000000004
coolAdd('Python', 'Programming')  # PYTHON PROGRAMMING
coolAdd(b"hi", b"hello")          # NotImplementedError: Unsupported type
Run Code Online (Sandbox Code Playgroud)

Python 3.11 应该包含联合运算符,以便于阅读(现在您只需将每种类型作为单独的装饰器)。

方法:

class CoolClassAdd:

    @singledispatchmethod
    def addMethod(self, arg1, arg2):
        raise NotImplementedError('Unsupported type')

    @addMethod.register(int)
    @addMethod.register(float)
    def _(self, arg1, arg2):
        print(f"Numbers = %s" % (arg1 + arg2))

    @addMethod.register(str)
    def _(self, arg1, arg2):
        print(f"Strings = %s %s" % (arg1, (arg2).upper()))
Run Code Online (Sandbox Code Playgroud)
c = CoolClassAdd()
c.addMethod(1, 2)           # Numbers = 3
c.addMethod(0.1, 0.2)       # Numbers = 0.30000000000000004
c.addMethod(0.1, 2)         # Numbers = 2.1
c.addMethod("hi", "hello")  # hi HELLO
Run Code Online (Sandbox Code Playgroud)

还支持静态和类方法(大多数错误从 3.9.7 开始已得到解决)。

但是,请注意!self在评估要使用的函数/方法时,Dispatch 似乎仅检查第一个(非)参数类型。

c.addMethod(1, "hello")     
# >> TypeError: unsupported operand type(s) for +: 'int' and 'str'
Run Code Online (Sandbox Code Playgroud)

当然,这通常需要高级错误处理或实现多重分派,现在我们回到了开始的地方!


pax*_*blo 4

在 C++ 等静态类型语言中,您可以根据输入参数类型(和数量)重载函数,但这在 Python 中实际上是不可能的。任何给定名称只能有一个函数。

可以做的是使用默认参数功能来选择该函数中的两个路径之一,例如:

def tmax(p1, p2, p3 = None):
    # Two-argument variant has p3 as None.

    if p3 is None:
        return (p1 - p2) / 2

    # Otherwise, we have three arguments.

    return (p1 * p1 / (p1 * p1 - p2 * p2)) * p3
Run Code Online (Sandbox Code Playgroud)

如果您想知道为什么我将平方运算从 更改n ** 2n * n,那是因为后者更快(或者在过去的某个时刻至少对于像这样的小积分幂来说2- 这可能仍然是这种情况,但您可能想确认)。

一个可能的情况是,它可能g1 ** 2g1 * g1全局更快g1,而不是本地(Python VM 需要更长的时间LOAD_GLOBALLOAD_FAST。发布的代码并非如此,因为该参数本质上是非全局的。