Python function overloading

2019-01-01 14:35发布

I know that Python does not support method overloading, but I've run into a problem that I can't seem to solve in a nice Pythonic way.

I am making a game where a character needs to shoot a variety of bullets, but how do I write different functions for creating these bullets? For example suppose I have a function that creates a bullet travelling from point A to B with a given speed. I would write a function like this:

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

But I want to write other functions for creating bullets like:

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

And so on with many variations. Is there a better way to do it without using so many keyword arguments cause its getting kinda ugly fast. Renaming each function is pretty bad too because you get either add_bullet1, add_bullet2, or add_bullet_with_really_long_name.

To address some answers:

  1. No I can't create a Bullet class hierarchy because thats too slow. The actual code for managing bullets is in C and my functions are wrappers around C API.

  2. I know about the keyword arguments but checking for all sorts of combinations of parameters is getting annoying, but default arguments help allot like acceleration=0

12条回答
君临天下
2楼-- · 2019-01-01 14:42

You can use "roll-your-own" solution for function overloading. This one is copied from Guido van Rossum's article about multimethods (because there is little difference between mm and overloading in 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

The usage would be

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):
    # ...

Most restrictive limitations at the moment are:

  • methods are not supported, only functions that are not class members;
  • inheritance is not handled;
  • kwargs are not supported;
  • registering new functions should be done at import time thing is not thread-safe
查看更多
余生请多指教
3楼-- · 2019-01-01 14:43

I think your basic requirement is to have a C/C++ like syntax in python with the least headache possible. Although I liked Alexander Poluektov's answer it doesn't work for classes.

The following should work for classes. It works by distinguishing by the number of non keyword arguments (but doesn't support distinguishing by type):

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

And it can be used simply like this:

test = TestOverloading()

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

Output:

This is overload 3
Sprite: I'm a Sprite
Start: 0
Direction: Right

This is overload 2
Sprite: I'm another Sprite
Script:
while x == True: print 'hi'

查看更多
余生请多指教
4楼-- · 2019-01-01 14:44

By passing keyword args.

def add_bullet(**kwargs):
    #check for the arguments listed above and do the proper things
查看更多
人气声优
5楼-- · 2019-01-01 14:47

overloading methods is tricky in python. However, there could be usage of passing the dict, list or primitive variables.

I have tried something for my use cases, this could help here to understand people to overload the methods.

Let's take your example:

a class overload method with call the methods from different class.

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

pass the arguments from remote class:

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

OR

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

So, handling is being achieved for list, Dictionary or primitive variables from method overloading.

try it out for your codes.

查看更多
时光乱了年华
6楼-- · 2019-01-01 14:48

In Python 3.4 was added PEP-0443. Single-dispatch generic functions.

Here is short API description from PEP.

To define a generic function, decorate it with the @singledispatch decorator. Note that the dispatch happens on the type of the first argument. Create your function accordingly:

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

To add overloaded implementations to the function, use the register() attribute of the generic function. This is a decorator, taking a type parameter and decorating a function implementing the operation for that type:

@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)
查看更多
春风洒进眼中
7楼-- · 2019-01-01 14:50

This type of behaviour is typically solved (in OOP languages) using Polymorphism. Each type of bullet would be responsible for knowing how it travels. For instance:

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

Pass as many arguments to the c_function that exist, then do the job of determining which c function to call based on the values in the initial c function. So, python should only ever be calling the one c function. That one c function looks at the arguments, and then can delegate to other c functions appropriately.

You're essentially just using each subclass as a different data container, but by defining all the potential arguments on the base class, the subclasses are free to ignore the ones they do nothing with.

When a new type of bullet comes along, you can simply define one more property on the base, change the one python function so that it passes the extra property, and the one c_function that examines the arguments and delegates appropriately. Doesn't sound too bad I guess.

查看更多
登录 后发表回答