ChatGPT解决这个技术问题 Extra ChatGPT

我知道 Python 不支持方法重载,但我遇到了一个问题,我似乎无法以一种好的 Python 方式解决。

我正在制作一个角色需要射击各种子弹的游戏,但是我如何编写不同的函数来创建这些子弹?例如,假设我有一个函数可以创建一个以给定速度从 A 点行进到 B 点的子弹。我会写一个这样的函数:

def add_bullet(sprite, start, headto, speed):
    # Code ...

但我想编写其他函数来创建项目符号,例如:

def add_bullet(sprite, start, direction, speed):
def add_bullet(sprite, start, headto, spead, acceleration):
def add_bullet(sprite, script): # For bullets that are controlled by a script
def add_bullet(sprite, curve, speed): # for bullets with curved paths
# And so on ...

等等有很多变化。有没有更好的方法可以在不使用这么多关键字参数的情况下做到这一点,导致它变得有点难看。重命名每个函数也很糟糕,因为您会得到 add_bullet1add_bullet2add_bullet_with_really_long_name

要解决一些答案:

不,我无法创建 Bullet 类层次结构,因为那太慢了。管理项目符号的实际代码是用 C 语言编写的,我的函数是 C API 的包装器。我知道关键字参数,但检查各种参数组合变得很烦人,但默认参数有助于分配如加速 = 0

仅适用于一个参数,但在这里(适用于从搜索引擎来到这里的人):docs.python.org/3/library/…
这似乎是默认值的好地方。您可以将一些设置为无,然后检查它们。额外的布尔影响似乎可以忽略不计
必须使用 default value + if + else 来执行与 C++ 相同的操作。这是 C++ 比 Python 具有更好可读性的为数不多的几件事之一......
我对为什么 kwargs 不是一个有效的答案感到困惑。您说您不想使用许多关键字参数,因为它很快就会变得丑陋……这就是问题的本质。如果您有很多争论并且因为您有很多争论而不是您的预期而变得一团糟?你想使用许多参数而不在任何地方指定它们吗??? Python 不是读心术。
我们不知道 script, curve 是什么类型的对象,它们是否有共同的祖先,它们支持什么方法。使用duck-typing,类设计由你决定它们需要支持哪些方法。大概 Script 支持某种基于时间步的回调(但它应该返回什么对象?那个时间步的位置?那个时间步的轨迹?)。大概 start, direction, speedstart, headto, spead, acceleration 都描述了轨迹的类型,但同样由您来设计接收类以了解如何解包和处理它们。

A
Andriy Drozdyuk

您所要求的称为多次调度。请参阅演示不同类型调度的 Julia 语言示例。

然而,在看之前,我们将首先解决为什么重载不是你在 Python 中真正想要的。

为什么不超载?

首先,需要了解重载的概念以及为什么它不适用于 Python。

当使用可以在编译时区分数据类型的语言时,可以在编译时进行选择。为编译时选择创建此类替代函数的行为通常称为重载函数。 (维基百科)

Python 是一种 dynamically 类型语言,因此重载的概念根本不适用于它。然而,一切都没有丢失,因为我们可以在运行时创建这样的替代函数

在将数据类型识别推迟到运行时的编程语言中,替代函数的选择必须在运行时根据函数参数的动态确定类型进行。以这种方式选择替代实现的函数通常被称为多方法。 (维基百科)

所以我们应该能够在 Python 中执行多方法——或者,也可以称为:多分派。

多次派送

多方法也称为多分派:

多分派或多方法是一些面向对象编程语言的特性,其中一个函数或方法可以基于其多个参数的运行时(动态)类型进行动态分派。 (维基百科)

Python 不支持开箱即用1,但是碰巧有一个名为 multipledispatch 的优秀 Python 包可以做到这一点。

解决方案

以下是我们如何使用 multipledispatch2 包来实现您的方法:

>>> from multipledispatch import dispatch
>>> from collections import namedtuple
>>> from types import *  # we can test for lambda type, e.g.:
>>> type(lambda a: 1) == LambdaType
True

>>> Sprite = namedtuple('Sprite', ['name'])
>>> Point = namedtuple('Point', ['x', 'y'])
>>> Curve = namedtuple('Curve', ['x', 'y', 'z'])
>>> Vector = namedtuple('Vector', ['x','y','z'])

>>> @dispatch(Sprite, Point, Vector, int)
... def add_bullet(sprite, start, direction, speed):
...     print("Called Version 1")
...
>>> @dispatch(Sprite, Point, Point, int, float)
... def add_bullet(sprite, start, headto, speed, acceleration):
...     print("Called version 2")
...
>>> @dispatch(Sprite, LambdaType)
... def add_bullet(sprite, script):
...     print("Called version 3")
...
>>> @dispatch(Sprite, Curve, int)
... def add_bullet(sprite, curve, speed):
...     print("Called version 4")
...

>>> sprite = Sprite('Turtle')
>>> start = Point(1,2)
>>> direction = Vector(1,1,1)
>>> speed = 100 #km/h
>>> acceleration = 5.0 #m/s**2
>>> script = lambda sprite: sprite.x * 2
>>> curve = Curve(3, 1, 4)
>>> headto = Point(100, 100) # somewhere far away

>>> add_bullet(sprite, start, direction, speed)
Called Version 1

>>> add_bullet(sprite, start, headto, speed, acceleration)
Called version 2

>>> add_bullet(sprite, script)
Called version 3

>>> add_bullet(sprite, curve, speed)
Called version 4

<子> 1。 Python 3 当前支持 single dispatch 2。注意不要在多线程环境中使用 multipledispatch,否则会出现奇怪的行为。


多线程环境中的“multipledispatch”有什么问题?由于服务器端的代码通常处于多线程环境中!只是想把它挖出来!
@danzeer 这不是线程安全的。我看到参数被两个不同的线程修改(即当另一个线程设置它自己的 speed 值时,speed 的值可能会在函数中间发生变化)!!!我花了很长时间才意识到罪魁祸首是图书馆。
single_dispatch 相比,multipledispatch 的优势在于它也适用于 python<3.8 中的类方法。
如何以 self 作为参数调度类方法?它是什么类型的?谢谢
@Iqigai + 没有过载。它只是 __add__(self, other) 的糖,它是在特定类上定义的方法。如果该类未定义此方法,则会出现错误。例如 {} + {} 给出 TypeError: unsupported operand type(s) for +: 'dict' and 'dict'
E
Escualo

当你展示它时,Python 确实支持“方法重载”。实际上,您刚刚描述的内容在 Python 中以多种不同的方式实现是微不足道的,但我会选择:

class Character(object):
    # your character __init__ and other methods go here

    def add_bullet(self, sprite=default, start=default, 
                 direction=default, speed=default, accel=default, 
                  curve=default):
        # do stuff with your arguments

在上面的代码中,default 是这些参数的合理默认值,即 None。然后,您可以只使用您感兴趣的参数调用该方法,Python 将使用默认值。

你也可以这样做:

class Character(object):
    # your character __init__ and other methods go here

    def add_bullet(self, **kwargs):
        # here you can unpack kwargs as (key, values) and
        # do stuff with them, and use some global dictionary
        # to provide default values and ensure that ``key``
        # is a valid argument...

        # do stuff with your arguments

另一种选择是将所需的函数直接挂钩到类或实例:

def some_implementation(self, arg1, arg2, arg3):
  # implementation
my_class.add_bullet = some_implementation_of_add_bullet

另一种方法是使用抽象工厂模式:

class Character(object):
   def __init__(self, bfactory, *args, **kwargs):
       self.bfactory = bfactory
   def add_bullet(self):
       sprite = self.bfactory.sprite()
       speed = self.bfactory.speed()
       # do stuff with your sprite and speed

class pretty_and_fast_factory(object):
    def sprite(self):
       return pretty_sprite
    def speed(self):
       return 10000000000.0

my_character = Character(pretty_and_fast_factory(), a1, a2, kw1=v1, kw2=v2)
my_character.add_bullet() # uses pretty_and_fast_factory

# now, if you have another factory called "ugly_and_slow_factory" 
# you can change it at runtime in python by issuing
my_character.bfactory = ugly_and_slow_factory()

# In the last example you can see abstract factory and "method
# overloading" (as you call it) in action 

所有这些看起来都是变量参数的例子,而不是重载。由于重载允许您使用相同的函数,但将不同的类型作为参数。例如: sum(real_num1, real_num2) 和 sum(imaginary_num1, imaginary_num2) 都具有相同的调用语法,但实际上期望两种不同的类型作为输入,并且实现也必须在内部进行更改
使用您将使用的答案,您将如何向调用者展示哪些参数一起有意义?只需放置一堆参数,每个参数都有一个默认值,就可以提供相同的功能,但就 API 而言,它的优雅程度要低得多
以上都不是重载,实现将必须检查参数输入(或忽略参数)的所有组合,如:if sprite and script and not start and not direction and not speed... 只是为了知道它在特定操作中。因为调用者可以调用提供所有可用参数的函数。在重载时为您定义确切的相关参数集。
当人们说python支持方法重载时,这是非常令人沮丧的。它不是。您将“方法重载”放在引号中的事实表明您意识到了这一事实。您可以使用多种技术获得类似的功能,例如此处提到的一种。但是方法重载有一个非常具体的定义。
我认为目的是虽然方法重载不是python的特性,但可以使用上述机制来达到等效的效果。
P
Peter Mortensen

您可以使用“roll-your-own”解决方案进行函数重载。这个是从 Guido van Rossum's article 中复制的关于多方法的(因为 Python 中的多方法和重载几乎没有区别):

registry = {}

class MultiMethod(object):
    def __init__(self, name):
        self.name = name
        self.typemap = {}
    def __call__(self, *args):
        types = tuple(arg.__class__ for arg in args) # a generator expression!
        function = self.typemap.get(types)
        if function is None:
            raise TypeError("no match")
        return function(*args)
    def register(self, types, function):
        if types in self.typemap:
            raise TypeError("duplicate registration")
        self.typemap[types] = function


def multimethod(*types):
    def register(function):
        name = function.__name__
        mm = registry.get(name)
        if mm is None:
            mm = registry[name] = MultiMethod(name)
        mm.register(types, function)
        return mm
    return register

用法是

from multimethods import multimethod
import unittest

# 'overload' makes more sense in this case
overload = multimethod

class Sprite(object):
    pass

class Point(object):
    pass

class Curve(object):
    pass

@overload(Sprite, Point, Direction, int)
def add_bullet(sprite, start, direction, speed):
    # ...

@overload(Sprite, Point, Point, int, int)
def add_bullet(sprite, start, headto, speed, acceleration):
    # ...

@overload(Sprite, str)
def add_bullet(sprite, script):
    # ...

@overload(Sprite, Curve, speed)
def add_bullet(sprite, curve, speed):
    # ...

目前最严格的限制是:

不支持方法,只支持非类成员的函数;

不处理继承;

不支持 kwargs;

注册新函数应该在导入时完成 事情不是线程安全的


+1 用于在此用例中扩展语言的装饰器。
+1,因为这是一个好主意(可能是 OP 应该采用的)---我从未见过 Python 中的多方法实现。
这与多调度库相比如何?
a
anonymoose

一个可能的选项是使用这里详述的多调度模块:http://matthewrocklin.com/blog/work/2014/02/25/Multiple-Dispatch

而不是这样做:

def add(self, other):
    if isinstance(other, Foo):
        ...
    elif isinstance(other, Bar):
        ...
    else:
        raise NotImplementedError()

你可以这样做:

from multipledispatch import dispatch
@dispatch(int, int)
def add(x, y):
    return x + y    

@dispatch(object, object)
def add(x, y):
    return "%s + %s" % (x, y)

使用结果:

>>> add(1, 2)
3

>>> add(1, 'hello')
'1 + hello'

为什么这没有得到更多的选票?我猜是因为缺少示例...我已经创建了一个答案,其中包含一个示例,说明如何使用 multipledispatch 包实现 OP 问题的解决方案。
P
Peter Mortensen

在 Python 3.4 中添加了 PEP-0443. Single-dispatch generic functions

这是 PEP 的简短 API 描述。

要定义通用函数,请使用 @singledispatch 装饰器对其进行装饰。请注意,调度发生在第一个参数的类型上。相应地创建您的函数:

from functools import singledispatch
@singledispatch
def fun(arg, verbose=False):
    if verbose:
        print("Let me just say,", end=" ")
    print(arg)

要向函数添加重载实现,请使用泛型函数的 register() 属性。这是一个装饰器,接受一个类型参数并装饰一个实现该类型操作的函数:

@fun.register(int)
def _(arg, verbose=False):
    if verbose:
        print("Strength in numbers, eh?", end=" ")
    print(arg)

@fun.register(list)
def _(arg, verbose=False):
    if verbose:
        print("Enumerate this:")
    for i, elem in enumerate(arg):
        print(i, elem)

+1,但是使用单个调度来实现问题的示例用例的示例(换句话说,如何在单个调度之上实现多个调度)将使这个答案更好。如果有人不考虑如何通过单次调度来解决多次调度问题,那么对于正在查看问题中的问题的人来说,这个答案可能会觉得无关紧要或无用。
P
Peter Mortensen

@overload 装饰器添加了类型提示 (PEP 484)。

虽然这不会改变 Python 的行为,但它确实更容易理解正在发生的事情,并让 mypy 检测错误。

请参阅:Type hintsPEP 484


你能添加一些例子吗?
我同意这里有一个很好的例子,因为这是很好的语法糖,而不是在单独的函数中分离逻辑。这是关于 mypy 页面工作的更好的详细信息:mypy.readthedocs.io/en/stable/…
P
Peter Mortensen

此类行为通常使用 polymorphism 解决(在 OOP 语言中)。每种类型的子弹都将负责了解它的行进方式。例如:

class Bullet(object):
    def __init__(self):
        self.curve = None
        self.speed = None
        self.acceleration = None
        self.sprite_image = None

class RegularBullet(Bullet):
    def __init__(self):
        super(RegularBullet, self).__init__()
        self.speed = 10

class Grenade(Bullet):
    def __init__(self):
        super(Grenade, self).__init__()
        self.speed = 4
        self.curve = 3.5

add_bullet(Grendade())

def add_bullet(bullet):
    c_function(bullet.speed, bullet.curve, bullet.acceleration, bullet.sprite, bullet.x, bullet.y)


void c_function(double speed, double curve, double accel, char[] sprite, ...) {
    if (speed != null && ...) regular_bullet(...)
    else if (...) curved_bullet(...)
    //..etc..
}

将尽可能多的参数传递给存在的 c_function,然后根据初始 c 函数中的值确定要调用哪个 c 函数。因此,Python 应该只调用一个 c 函数。一个 c 函数查看参数,然后可以适当地委托给其他 c 函数。

您实际上只是将每个子类用作不同的数据容器,但是通过在基类上定义所有潜在参数,子类可以自由地忽略它们不做的那些。

当出现一种新类型的项目符号时,您可以简单地在基础上再定义一个属性,更改一个 python 函数以便它传递额外的属性,以及一个检查参数和适当委托的 c_function。我想这听起来还不错。


那是我最初的方法,但出于性能原因,我不得不用 C 重写该代码。
@Bullets,我建议可能有许多不同的选项可用于提高性能,而不是编写一大堆可能不会做很多事情的 c 函数。例如:创建一个实例可能很昂贵,所以维护一个对象池。尽管我在不知道您发现什么太慢的情况下这么说。出于兴趣,这种方法到底有什么慢?除非在边界的 C 端花费大量时间,否则我不能认为 Python(本身)是真正的问题。
也许还有其他方法可以提高性能,但我使用 C 比使用 Python 要好得多。问题是计算子弹的运动并检测它们何时超出屏幕边界。我有一种方法可以计算子弹 pos+v*t 的位置,然后与屏幕边界 if x > 800 进行比较等等。每帧调用这些函数数百次被证明速度慢得令人无法接受。在 C 中完成时,使用纯 python 在 100% cpu 时为 40 fps,在 5%-10% 时为 60 fps。
@Bullets,那么公平。我仍然会使用封装数据的方法。将项目符号实例传递给 add_bullet,并提取您需要的所有字段。我会编辑我的答案。
@Bullets:您可以使用 Cython 将您的 C 函数和 Josh 建议的 OOP 方法结合起来。它允许提前绑定,因此不应该有速度损失。
b
blue_note

根据定义,在 python 中重载函数是不可能的(请继续阅读以了解详细信息),但您可以使用简单的装饰器实现类似的功能

class overload:
    def __init__(self, f):
        self.cases = {}

    def args(self, *args):
        def store_function(f):
            self.cases[tuple(args)] = f
            return self
        return store_function

    def __call__(self, *args):
        function = self.cases[tuple(type(arg) for arg in args)]
        return function(*args)

你可以像这样使用它

@overload
def f():
    pass

@f.args(int, int)
def f(x, y):
    print('two integers')

@f.args(float)
def f(x):
    print('one float')


f(5.5)
f(1, 2)

修改它以使其适应您的用例。

概念的澄清

函数调度:有多个同名函数。应该叫哪一个?两种策略

静态/编译时调度(又名“重载”)。根据参数的编译时类型决定调用哪个函数。在所有动态语言中,没有编译时类型,因此根据定义,重载是不可能的

动态/运行时调度:根据参数的运行时类型决定调用哪个函数。这是所有 OOP 语言所做的:多个类具有相同的方法,语言根据 self/this 参数的类型决定调用哪一个。但是,大多数语言仅针对此参数执行此操作。上面的装饰器将想法扩展到多个参数。

澄清一下,假设我们用假设的静态语言定义函数

void f(Integer x):
    print('integer called')

void f(Float x):
    print('float called')

void f(Number x):
    print('number called')


Number x = new Integer('5')
f(x)
x = new Number('3.14')
f(x)

使用静态调度(重载),您将看到两次“调用号码”,因为 x 已被声明为 Number,而这就是重载所关心的。使用动态调度,您将看到“整数调用,浮点调用”,因为这些是调用函数时 x 的实际类型。


此示例关键没有说明在 x 上调用了 哪个 方法以进行动态调度,也没有说明以何种顺序 两个方法被调用以进行静态调度。建议您将打印语句编辑为 print('number called for Integer') 等。
T
Tshilidzi Mudau

通过 passing keyword args

def add_bullet(**kwargs):
    #check for the arguments listed above and do the proper things

重新“传递关键字 args”:您的意思是“传递关键字 kwargs”吗?
@PeterMortensen 我不这么认为,因为 kwarg 是关键字参数的缩写。
P
Peter Mortensen

我认为您的基本要求是在 Python 中使用类似 C/C++ 的语法,并且尽可能减少头痛。虽然我喜欢 Alexander Poluektov's answer,但它不适用于课程。

以下应该适用于类。它通过按非关键字参数的数量来区分(但它不支持按类型区分):

class TestOverloading(object):
    def overloaded_function(self, *args, **kwargs):
        # Call the function that has the same number of non-keyword arguments.
        getattr(self, "_overloaded_function_impl_" + str(len(args)))(*args, **kwargs)

    def _overloaded_function_impl_3(self, sprite, start, direction, **kwargs):
        print "This is overload 3"
        print "Sprite: %s" % str(sprite)
        print "Start: %s" % str(start)
        print "Direction: %s" % str(direction)

    def _overloaded_function_impl_2(self, sprite, script):
        print "This is overload 2"
        print "Sprite: %s" % str(sprite)
        print "Script: "
        print script

它可以像这样简单地使用:

test = TestOverloading()

test.overloaded_function("I'm a Sprite", 0, "Right")
print
test.overloaded_function("I'm another Sprite", "while x == True: print 'hi'")

输出:

这是重载 3 Sprite:我是 Sprite 开始:0 方向:右 这是重载 2 Sprite:我是另一个 Sprite 脚本:while x == True: print 'hi'


我喜欢这个回应。它还可以用于检测类型,然后根据 arg 计数和类型进行重载
最大的缺点是解析器无法“看到”或提示参数名称或方法接受的类型。必须使用文档字符串,否则仅使用您的代码的人必须阅读一次。
V
Vlad Bezden

Python 3.8 添加了 functools.singledispatchmethod

将方法转换为单调度泛型函数。要定义泛型方法,请使用 @singledispatchmethod 装饰器对其进行装饰。请注意,调度发生在第一个非自我或非 cls 参数的类型上,相应地创建您的函数:

from functools import singledispatchmethod


class Negator:
    @singledispatchmethod
    def neg(self, arg):
        raise NotImplementedError("Cannot negate a")

    @neg.register
    def _(self, arg: int):
        return -arg

    @neg.register
    def _(self, arg: bool):
        return not arg


negator = Negator()
for v in [42, True, "Overloading"]:
    neg = negator.neg(v)
    print(f"{v=}, {neg=}")

输出

v=42, neg=-42
v=True, neg=False
NotImplementedError: Cannot negate a

@singledispatchmethod 支持与其他装饰器嵌套,例如 @classmethod。请注意,为了允许 dispatcher.register,singledispatchmethod 必须是最外层的装饰器。这是 Negator 类,其中 neg 方法被类绑定:

from functools import singledispatchmethod


class Negator:
    @singledispatchmethod
    @staticmethod
    def neg(arg):
        raise NotImplementedError("Cannot negate a")

    @neg.register
    def _(arg: int) -> int:
        return -arg

    @neg.register
    def _(arg: bool) -> bool:
        return not arg


for v in [42, True, "Overloading"]:
    neg = Negator.neg(v)
    print(f"{v=}, {neg=}")

输出:

v=42, neg=-42
v=True, neg=False
NotImplementedError: Cannot negate a

相同的模式可用于其他类似的装饰器:staticmethod、abstractmethod 等。


I
Ignacio Vazquez-Abrams

要么在定义中使用多个关键字参数,要么创建一个 Bullet 层次结构,其实例被传递给函数。


我打算建议第二种方法:制作一些 BulletParams... 类来指定项目符号的详细信息。
你能详细说明一下吗?我试图用不同的项目符号创建一个类层次结构,但这不起作用,因为 Python 太慢了。它不能足够快地计算所需数量的子弹的运动,所以我不得不用 C 编写这部分。所有的 add_bullet 变体只是调用相应的 C 函数。
H
Heewoon

您可以使用以下 Python 代码实现此目的:

@overload
def test(message: str):
    return message

@overload
def test(number: int):
    return number + 1

需要补充一点,你引用的是一个重载的 pip 包,你需要先导入装饰器
m
martineau

我认为具有相关多态性的 Bullet 类层次结构是可行的方法。您可以通过使用元类有效地重载基类构造函数,以便调用基类导致创建适当的子类对象。下面是一些示例代码来说明我的意思的本质。

更新

代码已修改为在 Python 2 和 3 下运行以保持相关性。这是以一种避免使用 Python 的显式元类语法的方式完成的,该语法在两个版本之间有所不同。

为了实现该目标,通过在创建 Bullet 基类时显式调用元类来创建 BulletMeta 类的 BulletMetaBase 实例(而不是使用 __metaclass__= 类属性或通过 metaclass 关键字参数,具体取决于Python 版本)。

class BulletMeta(type):
    def __new__(cls, classname, bases, classdict):
        """ Create Bullet class or a subclass of it. """
        classobj = type.__new__(cls, classname, bases, classdict)
        if classname != 'BulletMetaBase':
            if classname == 'Bullet':  # Base class definition?
                classobj.registry = {}  # Initialize subclass registry.
            else:
                try:
                    alias = classdict['alias']
                except KeyError:
                    raise TypeError("Bullet subclass %s has no 'alias'" %
                                    classname)
                if alias in Bullet.registry: # unique?
                    raise TypeError("Bullet subclass %s's alias attribute "
                                    "%r already in use" % (classname, alias))
                # Register subclass under the specified alias.
                classobj.registry[alias] = classobj

        return classobj

    def __call__(cls, alias, *args, **kwargs):
        """ Bullet subclasses instance factory.

            Subclasses should only be instantiated by calls to the base
            class with their subclass' alias as the first arg.
        """
        if cls != Bullet:
            raise TypeError("Bullet subclass %r objects should not to "
                            "be explicitly constructed." % cls.__name__)
        elif alias not in cls.registry: # Bullet subclass?
            raise NotImplementedError("Unknown Bullet subclass %r" %
                                      str(alias))
        # Create designated subclass object (call its __init__ method).
        subclass = cls.registry[alias]
        return type.__call__(subclass, *args, **kwargs)


class Bullet(BulletMeta('BulletMetaBase', (object,), {})):
    # Presumably you'd define some abstract methods that all here
    # that would be supported by all subclasses.
    # These definitions could just raise NotImplementedError() or
    # implement the functionality is some sub-optimal generic way.
    # For example:
    def fire(self, *args, **kwargs):
        raise NotImplementedError(self.__class__.__name__ + ".fire() method")

    # Abstract base class's __init__ should never be called.
    # If subclasses need to call super class's __init__() for some
    # reason then it would need to be implemented.
    def __init__(self, *args, **kwargs):
        raise NotImplementedError("Bullet is an abstract base class")


# Subclass definitions.
class Bullet1(Bullet):
    alias = 'B1'
    def __init__(self, sprite, start, direction, speed):
        print('creating %s object' % self.__class__.__name__)
    def fire(self, trajectory):
        print('Bullet1 object fired with %s trajectory' % trajectory)


class Bullet2(Bullet):
    alias = 'B2'
    def __init__(self, sprite, start, headto, spead, acceleration):
        print('creating %s object' % self.__class__.__name__)


class Bullet3(Bullet):
    alias = 'B3'
    def __init__(self, sprite, script): # script controlled bullets
        print('creating %s object' % self.__class__.__name__)


class Bullet4(Bullet):
    alias = 'B4'
    def __init__(self, sprite, curve, speed): # for bullets with curved paths
        print('creating %s object' % self.__class__.__name__)


class Sprite: pass
class Curve: pass

b1 = Bullet('B1', Sprite(), (10,20,30), 90, 600)
b2 = Bullet('B2', Sprite(), (-30,17,94), (1,-1,-1), 600, 10)
b3 = Bullet('B3', Sprite(), 'bullet42.script')
b4 = Bullet('B4', Sprite(), Curve(), 720)
b1.fire('uniform gravity')
b2.fire('uniform gravity')

输出:

creating Bullet1 object
creating Bullet2 object
creating Bullet3 object
creating Bullet4 object
Bullet1 object fired with uniform gravity trajectory
Traceback (most recent call last):
  File "python-function-overloading.py", line 93, in <module>
    b2.fire('uniform gravity') # NotImplementedError: Bullet2.fire() method
  File "python-function-overloading.py", line 49, in fire
    raise NotImplementedError(self.__class__.__name__ + ".fire() method")
NotImplementedError: Bullet2.fire() method

嗯,这仍然只是将函数命名为 add_bullet1、add_bullet2 等的一种奇特方式。
@Bullets:也许是,或者它只是创建工厂函数的一种稍微复杂的方式。它的一个好处是它支持 Bullet 子类的层次结构,而无需在每次添加另一个子类型时修改基类或工厂函数。 (当然,如果您使用的是 C 而不是 C++,我猜您没有类。)您还可以创建一个更智能的元类,根据类型和/或编号自行确定要创建的子类传递的参数数量(就像 C++ 支持重载一样)。
这个继承的想法也是我的第一选择。
C
C-3PO

您可以轻松地在 Python 中实现函数重载。以下是使用 floatsintegers 的示例:

class OverloadedFunction:
    def __init__(self):
        self.D = {int: self.f_int, float: self.f_float}
    
    def __call__(self, x):
        return self.D[type(x)](x)
    
    def f_int(self, x):
        print('Integer Function')
        return x**2
    
    def f_float(self, x):
        print('Float Function (Overloaded)')
        return x**3

# f is our overloaded function
f = OverloadedFunction()

print(f(3 ))
print(f(3.))

# Output:
# Integer Function
# 9
# Float Function (Overloaded)
# 27.0

代码背后的主要思想是,一个类包含您想要实现的不同(重载)函数,而 Dictionary 用作 router,根据输入 type(x) 将您的代码指向正确的函数。

PS1。对于自定义类,例如 Bullet1,您可以按照类似的模式初始化内部字典,例如 self.D = {Bullet1: self.f_Bullet1, ...}。其余代码相同。

PS2。所提出的解决方案的时间/空间复杂度也相当不错,每次操作的平均成本为 O(1)


R
Rafe Kettler

使用带有默认值的关键字参数。例如

def add_bullet(sprite, start=default, direction=default, script=default, speed=default):

在直子弹与弯曲子弹的情况下,我将添加两个函数:add_bullet_straightadd_bullet_curved


P
Peter Mortensen

在 Python 中重载方法很棘手。但是,可以使用传递字典、列表或原始变量。

我已经为我的用例尝试了一些东西,这可以帮助理解人们重载方法。

让我们举个例子:

一个类重载方法,调用不同类的方法。

def add_bullet(sprite=None, start=None, headto=None, spead=None, acceleration=None):

从远程类传递参数:

add_bullet(sprite = 'test', start=Yes,headto={'lat':10.6666,'long':10.6666},accelaration=10.6}

或者

add_bullet(sprite = 'test', start=Yes, headto={'lat':10.6666,'long':10.6666},speed=['10','20,'30']}

因此,正在处理来自方法重载的列表、字典或原始变量。

试试你的代码。


Z
Zeel B Patel

This library 以直接的 Python 方式支持它。从下面的自述文件中复制一个示例。

from plum import dispatch

@dispatch
def f(x: str):
    return "This is a string!"
    

@dispatch
def f(x: int):
    return "This is an integer!"

>>> f("1")
'This is a string!'

>>> f(1)
'This is an integer!'

s
sj95126

你也可以试试这个代码。我们可以尝试任意数量的参数

# Finding the average of given number of arguments
def avg(*args):   # args is the argument name we give
    sum = 0
    for i in args:
        sum += i
        average = sum/len(args)   # Will find length of arguments we given
    print("Avg: ", average)

# call function with different number of arguments
avg(1,2)
avg(5,6,4,7)
avg(11,23,54,111,76)