Python function overloading Python function overloading python python

Python function overloading


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 atcompile-time, selecting among the alternatives can occur atcompile-time. The act of creating such alternative functions forcompile-time selection is usually referred to as overloading afunction. (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 untilrun-time the selection among alternativefunctions must occur at run-time, based on the dynamically determinedtypes of function arguments. Functions whose alternativeimplementations are selected in this manner are referred to mostgenerally 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 someobject-oriented programming languages in which a function or methodcan be dynamically dispatched based on the run time (dynamic) type ofmore 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) == LambdaTypeTrue>>> 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 dispatch2. Take care not to use multipledispatch in a multi-threaded environment or you will get weird behavior.


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):  # implementationmy_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 speedclass pretty_and_fast_factory(object):    def sprite(self):       return pretty_sprite    def speed(self):       return 10000000000.0my_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 issuingmy_character.bfactory = ugly_and_slow_factory()# In the last example you can see abstract factory and "method# overloading" (as you call it) in action 


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] = functiondef 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 multimethodimport unittest# 'overload' makes more sense in this caseoverload = multimethodclass Sprite(object):    passclass Point(object):    passclass 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