我知道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_bullet1、add_bullet2就是add_bullet_with_really_long_name。

以下是一些问题的答案:

不,我不能创建一个子弹类层次结构,因为那太慢了。管理项目符号的实际代码是用C编写的,我的函数是围绕C API的包装器。 我知道关键字参数,但检查各种参数组合是令人讨厌的,但默认参数帮助分配,如加速度=0


当前回答

这种类型的行为通常是通过多态性(在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,以适当地检查参数和委托。我想听起来还不算太糟。

其他回答

在默认值中使用关键字参数。如。

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

在直线子弹和曲线子弹的情况下,我将添加两个函数:add_bullet_straight和add_bullet_curved。

Python 3.8增加了functools.singledispatchmethod

将方法转换为单分派泛型函数。 要定义一个泛型方法,请使用@singledispatchmethod装饰它 装饰。注意,调度发生在第一个的类型上 非self或非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必须是最外层的装饰器。这是 带有negg方法的否定类:

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等。

对于函数重载,可以使用“自己动手”的解决方案。下面这个摘自Guido van Rossum关于多方法的文章(因为在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; 注册新函数应该在导入时完成,这是不线程安全的

一个可能的选项是使用multipledispatch模块,如下所示: 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'

这种类型的行为通常是通过多态性(在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,以适当地检查参数和委托。我想听起来还不算太糟。