我知道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


当前回答

根据定义,在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)

修改它以适应您的用例。

概念的澄清

function dispatch: there are multiple functions with the same name. Which one should be called? two strategies static/compile-time dispatch (aka. "overloading"). decide which function to call based on the compile-time type of the arguments. In all dynamic languages, there is no compile-time type, so overloading is impossible by definition dynamic/run-time dispatch: decide which function to call based on the runtime type of the arguments. This is what all OOP languages do: multiple classes have the same methods, and the language decides which one to call based on the type of self/this argument. However, most languages only do it for the this argument only. The above decorator extends the idea to multiple parameters.

为了澄清这一点,假设我们用一种假想的静态语言定义函数

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)

使用静态分派(重载),您将看到“number被调用”两次,因为x已被声明为number,这就是重载所关心的。在动态分派中,你会看到“integer called, float called”,因为它们是函数被调用时x的实际类型。

其他回答

你要求的是所谓的多重调度。参见Julia语言示例,其中演示了不同类型的分派。

然而,在讨论这个问题之前,我们首先要解决为什么在Python中重载并不是你真正想要的。

为什么不超载?

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

在使用可以区分数据类型的语言时 编译时,可以在 编译时。为…创造这样的替代功能的行为 编译时选择通常称为重载 函数。(维基百科)

Python是一种动态类型语言,因此重载的概念并不适用于它。然而,并不是所有的都失去了,因为我们可以在运行时创建这样的替代函数:

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

因此,我们应该能够在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目前支持单分派 2. 注意不要在多线程环境中使用multipledispatch,否则会出现奇怪的行为。

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

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 

我认为你的基本要求是在Python中有一个类似C/ c++的语法,并且尽可能不让人头疼。尽管我喜欢Alexander Poluektov的回答,但它并不适用于课堂。

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

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 精灵:我是精灵 开始:0 方向:对 这是过载2 精灵:我是另一个精灵 脚本: 当x == True:打印'hi'

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

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

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