Python函数重载

2024-09-24 04:32:08 发布

您现在位置:Python中文网/ 问答频道 /正文

我知道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_bullet2,或者add_bullet_with_really_long_name

要回答一些问题:

  1. 不,我不能创建项目符号类层次结构,因为这太慢了。管理项目符号的实际代码在C中,我的函数是C API的包装器。

  2. 我知道关键字参数,但是检查参数的各种组合越来越烦人,但是默认参数有助于分配,比如acceleration=0


Tags: 项目方法函数add参数def符号start
3条回答

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 

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

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

为什么不超载呢?

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

When working with languages that can discriminate data types at compile-time, selecting among the alternatives can occur at compile-time. The act of creating such alternative functions for compile-time selection is usually referred to as overloading a function. (Wikipedia)

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

In programming languages that defer data type identification until run-time the selection among alternative functions must occur at run-time, based on the dynamically determined types of function arguments. Functions whose alternative implementations are selected in this manner are referred to most generally as multimethods. (Wikipedia)

因此,我们应该能够在python中执行多方法,或者,也就是我们所说的:多分派

多次调度

多方法也称为多分派:

Multiple dispatch or multimethods is the feature of some object-oriented programming languages in which a function or method can be dynamically dispatched based on the run time (dynamic) type of more than one of its arguments. (Wikipedia)

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

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

您可以使用“roll your own”解决方案来重载函数。这个是从Guido van Rossum's article关于多方法复制的(因为mm和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
  • 应该在导入时注册新函数,但这不是线程安全的

相关问题 更多 >