321

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

Neuron
  • 5,141
  • 5
  • 38
  • 59
Bullets
  • 3,251
  • 3
  • 15
  • 6
  • 12
    Works for only one parameter, but here (for people coming here from a search engine): https://docs.python.org/3/library/functools.html#functools.singledispatch – leewz Apr 15 '14 at 00:41
  • 2
    this seems like a good place for default values. you can set some to None and just check for them. the extra boolean impact seems negligable – Andrew Scott Evans May 07 '15 at 17:17
  • Have to use `default value + if + else` to do the same as C++ do. This is one of the very few things that C++ has better readability than Python... – Deqing Nov 17 '15 at 04:26
  • 1
    I'm confused on why kwargs is not a valid answer. You say that you don't want to use many keyword arguments because it gets ugly fast... well that's just the nature of the problem. If you have many arguments and it's messy because you have many arguments than what did you expect? Do you want to use many arguments without specifying them anywhere??? Python is not a mind reader. – Calculus Aug 30 '19 at 12:38
  • We don't know what sort of objects `script, curve` are, do they have a common ancestor, what methods they support. With duck-typing, it's up to you for class design to figure out what methods they need to support. Presumably `Script` supports some sort of timestep-based callback (but what object should it return? the position at that timestep? the trajectory at that timestep?). Presumably `start, direction, speed` and `start, headto, spead, acceleration` both describe types of trajectories, but again it's up to you to design the receiving class to know how to unpack them and process them. – smci Jan 05 '20 at 14:21
  • Related (not duplicate): *[How can I detect duplicate method names in a Python class?](https://stackoverflow.com/questions/10761988)* – Peter Mortensen Jan 06 '21 at 16:13

20 Answers20

254

What you are asking for is called multiple dispatch. See Julia language examples which demonstrates different types of dispatches.

However, before looking at that, we'll first tackle why overloading is not really what you want in Python.

Why Not Overloading?

First, one needs to understand the concept of overloading and why it's not applicable to 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 is a dynamically typed language, so the concept of overloading simply does not apply to it. However, all is not lost, since we can create such alternative functions at run-time:

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)

So we should be able to do multimethods in Python—or, as it is alternatively called: multiple dispatch.

Multiple dispatch

The multimethods are also called multiple dispatch:

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 does not support this out of the box1, but, as it happens, there is an excellent Python package called multipledispatch that does exactly that.

Solution

Here is how we might use multipledispatch2 package to implement your methods:

>>> 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 currently supports single dispatch 2. Take care not to use multipledispatch in a multi-threaded environment or you will get weird behavior.

Andriy Drozdyuk
  • 58,435
  • 50
  • 171
  • 272
  • 12
    What is the problem with 'multipledispatch' in a multi-threaded environment ? Since server-side's code is usually in multi-threaded environment! Just trying to dig it out! – danzeer Jan 21 '17 at 03:34
  • 16
    @danzeer It was not thread-safe. I saw the argument being modified by two different threads (i.e. the value of `speed` might change in the middle of the function when another thread sets it's own value of `speed`)!!! It took me long time to realize that it was the library that was the culprit. – Andriy Drozdyuk Jan 22 '17 at 06:24
  • 2
    The advantage of `multipledispatch` as compared to `single_dispatch` is that it also works with class methods in python<3.8. – Dr_Zaszuś Dec 23 '20 at 16:25
  • how to dispatch the class method with self as argument? what's the type of it? thanks – SKSKSKSK Jan 17 '22 at 04:47
  • Isn't the `+` overloaded in python: we can write `a = 1 + 2` and `my_string = 'abc' + 'def'` as far as I know. So saying that overloading does not exist in python is not accurate. – Iqigai Jan 29 '22 at 09:41
  • 1
    Is there not scenarios where overloading in other languages also entails a different number of arguments for each function definition, and not simply a different input type? – IntegrateThis Mar 08 '22 at 19:57
  • 1
    @Iqigai The `+` is not overloaded. It is simply sugar for `__add__(self, other)`, which is a method defined on specific class. If the class does not define this method then you get an error. For example `{} + {}` gives `TypeError: unsupported operand type(s) for +: 'dict' and 'dict'`. – Andriy Drozdyuk Mar 09 '22 at 03:29
  • 1
    What about [typing.overload](https://docs.python.org/3/library/typing.html?highlight=typing#typing.overload)? – darda Mar 17 '22 at 00:15
  • That's just for type checker. You have to check for each specific type inside the final non-decorated function. Those functions have `...` as their body. – Andriy Drozdyuk Mar 17 '22 at 06:15
  • Not working for class methods in python@>3.8 Pylance throws `Method declaration "XXX" is obscured by a declaration of the same name` – Pablo LION Sep 26 '22 at 08:01
  • Dynamic typing doesn't explain why overloading is not supported for the case of different number of arguments. – user626528 Mar 08 '23 at 16:15
124

Python does support "method overloading" as you present it. In fact, what you just describe is trivial to implement in Python, in so many different ways, but I would go with:

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

In the above code, default is a plausible default value for those arguments, or None. You can then call the method with only the arguments you are interested in, and Python will use the default values.

You could also do something like this:

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

Another alternative is to directly hook the desired function directly to the class or instance:

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

Yet another way is to use an abstract factory pattern:

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 
Escualo
  • 40,844
  • 23
  • 87
  • 135
  • 131
    All these look as examples of variable arguments, rather than overloading. Since overloading allows you to have the same function, for different types as arguments. eg: sum(real_num1, real_num2) and sum(imaginary_num1, imaginary_num2) Will both have same calling syntax, but are actually expecting 2 different types as input, and the implementation has to change also internally – Efren Sep 17 '13 at 06:21
  • 21
    Using the answer you would go with, how would you present to the caller which arguments make sense together? Just putting a bunch of arguments each with a default value may provide the same functionality but in terms of an API it is much less elegant – Greg Ennis May 12 '14 at 14:43
  • 10
    Non of the above is overloading, the implementation will have to check all combinations of parameters inputs (or ignore parameters) like: `if sprite and script and not start and not direction and not speed...` just to know it is in a specific action. because a caller can call the function providing all the parameters available. While overloading define for you the exact sets of relevant parameters. – Roee Gavirel Sep 27 '17 at 06:06
  • 7
    It is very upsetting when people say that python supports method overloading. It does not. The fact that you put "method overloading" in quotations indicates that you aware of this fact. You can get similar functionality with several techniques, like the one mentioned here. But method overloading has a very specific definition. – Howard Swope Jan 03 '19 at 21:23
  • 6
    I think the intended point is while method overloading is not a feature of python, the above mechanisms can be used to achieve the equivalent effect. – rawr rang Jun 25 '19 at 14:59
  • In many situations, I think the **first** is the best approach. It is very USER FRIENDLY because I can type `add_bullet(` in VS code and it tells me what the choices are. Yes I can specify too many / conflicting parameters, BUT the implementation can easily check that using, for example, `assert headto is None or direction is None, "invalid parameters"` – John Henckel Jul 11 '22 at 16:33
106

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 multimethods 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
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Alexander Poluektov
  • 7,844
  • 1
  • 28
  • 32
58

A possible option is to use the multipledispatch module as detailed here: http://matthewrocklin.com/blog/work/2014/02/25/Multiple-Dispatch

Instead of doing this:

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

You can do this:

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)

With the resulting usage:

>>> add(1, 2)
3

>>> add(1, 'hello')
'1 + hello'
anonymoose
  • 819
  • 2
  • 11
  • 25
Dave C
  • 1,344
  • 14
  • 14
  • 7
    Why doesn't this get more votes? I'm guessing due to lack of examples... I've created an answer with an example of how to implement a solution to OP's problem with *multipledispatch* package. – Andriy Drozdyuk Mar 17 '15 at 05:37
38

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

Here is a 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)
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
eirenikos
  • 2,296
  • 25
  • 25
  • 1
    +1, but an example which uses single dispatch to implement the question's example use case (in other words, how to implement multiple dispatch on top of single dispatch) would make this answer a lot better. If someone doesn't think of how to solve multiple dispatch with single dispatch, this answer might feel irrelevant or useless to people looking at problems like the one in the question. – mtraceur Sep 30 '20 at 17:34
20

The @overload decorator was added with type hints (PEP 484).

While this doesn't change the behaviour of Python, it does make it easier to understand what is going on, and for mypy to detect errors.

See: Type hints and PEP 484

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • 14
    Can you add some examples? – gerrit Feb 14 '20 at 15:30
  • 3
    I agree an example would be nice here as this is nice syntactic sugar rather than separating logic in separate functions. This are better details of the workings on mypy page: https://mypy.readthedocs.io/en/stable/more_types.html#function-overloading – Cas Jul 14 '21 at 21:04
13

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, and 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. It doesn't sound too bad I guess.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Josh Smeaton
  • 47,939
  • 24
  • 129
  • 164
  • 1
    That was my initial approach, but for performance reasons I had to rewrite that code in C. – Bullets Jun 22 '11 at 03:38
  • @Bullets, I would suggest that there may be a number of different options available to improve performance rather than writing a whole lot of c functions that probably won't be doing a whole lot. For example: creating an instance may be expensive, so maintain an object pool. Though I say this without knowing what you found to be too slow. Out of interest, what exactly was slow about this approach? Unless significant time is going to be spent in the C side of the boundary, I can't think that Python (itself) is the real problem. – Josh Smeaton Jun 22 '11 at 03:51
  • Maybe there are other ways to improve the performance, but I am much better with C than with Python. The problem was calculating the motions of the bullets and detecting when they go out of screen bounds. I had a methods for calculating position of the bullet `pos+v*t` and then comparing to screen boundaries `if x > 800` and so on. Calling these functions several hundred times per frame turned out to be unacceptably slow. It was something like 40 fps at 100% cpu with pure python to 60 fps with 5%-10% when done in C. – Bullets Jun 22 '11 at 04:03
  • @Bullets, fair enough then. I'd still use the approach I went with for encapsulating data. Pass an instance of bullet to `add_bullet`, and extract all the fields that you need. I'll edit my answer. – Josh Smeaton Jun 22 '11 at 04:13
  • @Bullets: You can combine your C functions and the OOP approach suggested by Josh using [Cython](http://docs.cython.org/src/quickstart/overview.html). It allows early binding so there should not be a speed penalty. – jfs Sep 05 '11 at 13:25
12

It is impossible by definition to overload a function in python (read on for details), but you can achieve something similar with a simple decorator

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)

You can use it like this

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

Modify it to adapt it to your use case.

A clarification of concepts

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

To clear up, assume that we define, in a hypothetical static language, the functions

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)

With static dispatch (overloading) you will see "number called" twice, because x has been declared as Number, and that's all overloading cares about. With dynamic dispatch you will see "integer called, float called", because those are the actual types of x at the time the function is called.

blue_note
  • 27,712
  • 9
  • 72
  • 90
  • This example crucially doesn't illustrate **which** method got called on `x` for dynamic dispatch, nor **in which order** both methods got called in for static dispatch. Recommend you edit the print statements to `print('number called for Integer')` etc. – smci Jan 05 '20 at 14:14
8

By passing keyword args.

def add_bullet(**kwargs):
    #check for the arguments listed above and do the proper things
Tshilidzi Mudau
  • 7,373
  • 6
  • 36
  • 49
Nick Radford
  • 1,038
  • 6
  • 13
6

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

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Tim Ludwinski
  • 2,704
  • 30
  • 34
  • 1
    I like this response. It could be used also to detect types and then make overloads based on arg count and types – Andrew Jul 08 '20 at 00:32
  • Biggest downside is the parser cannot "see" or hint at the parameter names nor the types the method accepts anymore. Making it necessary to use a docstring, or else someone just using your code is having to read it once. –  Dec 23 '20 at 14:14
6

Python 3.8 added functools.singledispatchmethod

Transform a method into a single-dispatch generic function.

To define a generic method, decorate it with the @singledispatchmethod decorator. Note that the dispatch happens on the type of the first non-self or non-cls argument, create your function accordingly:

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=}")

Output

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

@singledispatchmethod supports nesting with other decorators such as @classmethod. Note that to allow for dispatcher.register, singledispatchmethod must be the outer most decorator. Here is the Negator class with the neg methods being class bound:

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=}")

Output:

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

The same pattern can be used for other similar decorators: staticmethod, abstractmethod, and others.

Vlad Bezden
  • 83,883
  • 25
  • 248
  • 179
6

You can achieve this with the following Python code:

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

@overload
def test(number: int):
    return number + 1
Andrew
  • 375
  • 3
  • 10
4

Either use multiple keyword arguments in the definition, or create a Bullet hierarchy whose instances are passed to the function.

Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • I was going to suggest the second approach: make some BulletParams... classes to specify the bullet details. – John Zwinck Jun 22 '11 at 03:15
  • 1
    Can you elaborate on this? I tried to create a class hierarchy with different bullets but this does not work, because Python is too slow. It can't calculate the motions of the required number of bullets fast enough, so I had to write that part in C. All the add_bullet variants just call the corresponding C function. – Bullets Jun 22 '11 at 03:24
4

You can easily implement function overloading in Python. Here is an example using floats and integers:

class OverloadedFunction:
    def __init__(self):
        self.router = {int : self.f_int   ,
                       float: self.f_float}
    
    def __call__(self, x):
        return self.router[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

The main idea behind the code is that a class holds the different (overloaded) functions that you would like to implement, and a Dictionary works as a router, directing your code towards the right function depending on the input type(x).

PS1. In case of custom classes, like Bullet1, you can initialize the internal dictionary following a similar pattern, such as self.D = {Bullet1: self.f_Bullet1, ...}. The rest of the code is the same.

PS2. The time/space complexity of the proposed solution is fairly good as well, with an average cost of O(1) per operation.

C-3PO
  • 1,181
  • 9
  • 17
3

I think a Bullet class hierarchy with the associated polymorphism is the way to go. You can effectively overload the base class constructor by using a metaclass so that calling the base class results in the creation of the appropriate subclass object. Below is some sample code to illustrate the essence of what I mean.

Updated

The code has been modified to run under both Python 2 and 3 to keep it relevant. This was done in a way that avoids the use Python's explicit metaclass syntax, which varies between the two versions.

To accomplish that objective, a BulletMetaBase instance of the BulletMeta class is created by explicitly calling the metaclass when creating the Bullet baseclass (rather than using the __metaclass__= class attribute or via a metaclass keyword argument depending on the Python version).

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

Output:

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
martineau
  • 119,623
  • 25
  • 170
  • 301
  • Hmm this is still just a fancy way to name the functions as add_bullet1, add_bullet2 and so on. – Bullets Jun 22 '11 at 16:57
  • 1
    @Bullets: Perhaps it is, or maybe it's just a slightly elaborate way to create a factory function. A nice thing about it is that it supports a hierarchy of `Bullet` subclasses without having to modify the base class or factory function every time you add another subtype. (Of course, if you're using C rather than C++, I guess you don't have classes.) You could also make a smarter metaclass that figured-out on its own what subclass to create based on the type and/or number of arguments passed (like C++ does to support overloading). – martineau Jun 22 '11 at 19:13
  • 1
    This inheritance idea would be my first option as well. – Daniel Möller Aug 22 '17 at 18:23
2

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, and 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 the 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 code.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
shashankS
  • 1,043
  • 1
  • 11
  • 21
2

Use keyword arguments with defaults. E.g.

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

In the case of a straight bullet versus a curved bullet, I'd add two functions: add_bullet_straight and add_bullet_curved.

Rafe Kettler
  • 75,757
  • 21
  • 156
  • 151
2

Plum supports it in a straightforward pythonic way. Copying an example from the README below.

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!'
Zeel B Patel
  • 691
  • 5
  • 16
2

How to overload in python?

I know this is an old question, but the topic is still very relevant, and I haven't read a single clear and concise answer, so I thought I'd provide one myself.


First, install the package:

pip3 install overloading
pip3 install typing

Then, use the overload decorator from the package to define multiple implementations of the function:

from typing import Set, List
from overloading import overload

@overload
def process_data(data: List[str], join_string: str) -> str:
    return join_string.join(data)

@overload
def process_data(data: Set[str], join_string: str) -> str:
    return join_string.join(data)

@overload
def process_data(data: List[float], round_to: int) -> List[float]:
    return [round(x, round_to) for x in data]

# And so on ...

The number and the types of the arguments determine which version of the function is called.

  • Unfortunately, dispatch based on argument names is not supported.

Here you can find the complete documentation.

ciurlaro
  • 742
  • 10
  • 22
0

My solution

def _either(**kwargs):
    return len([True for _, v in kwargs.items() if v is not None]) == 1

def overload(func, _overloaded_args=None):
    """enable overloading toward all params
    Usage:
        @overload
        def overloaded_func(either1=None, either2=None, either3=None):
            pass
        @overload
        def overloaded_func(must1, must2, either1=None, either2=None):
            pass
    """
    def inner(*func_args, **func_kwargs):
        nonlocal _overloaded_args
        __overloaded_args = _overloaded_args
        if __overloaded_args is None:
            __overloaded_args = list(func_kwargs.keys())
        if __overloaded_args:
            __overloaded_kwargs = {k: v for k, v in func_kwargs.items()
                                  if k in __overloaded_args}
            assert _either(**__overloaded_kwargs), (
                'function overloading should contain a single overloaded param.'
                f' overloaded params: {__overloaded_args}. inputs: {func_kwargs}')
        return func(*func_args, **func_kwargs)
    return inner

def overload_args(*_overloaded_args):
    """enable overloading toward specified params
    Usage:
        @overload_args('either1', 'either2')
        def overloaded_func(either1=None, either2=None):
            pass
        @overload_args('either1', 'either2')
        def overloaded_func(either1=None, either2=None, optional1=None, optional2=123):
            pass
    """
    def inner(func):
        return overload(func, _overloaded_args=_overloaded_args)
    return inner
Jay Yang
  • 384
  • 5
  • 6