Overloaded functions in Python
Asked Answered
B

6

107

Is it possible to have overloaded functions in Python?

In C# I would do something like

void myfunction (int first, string second)
{
    # Some code
}

void myfunction (int first, string second, float third)
{
    # Some different code
}

And then when I call the function it would differentiate between the two based on the number of arguments. Is it possible to do something similar in Python?

Bunde answered 18/8, 2011 at 19:31 Comment(4)
This seems to be a possible duplicate post. Also please don't flag as C# as the question doesn't have to do with C#. function-overloading-in-python-missingMedrano
possible duplicate of Function overloading in Python: MissingSordid
possible duplicate of Python function overloadingSonyasoo
Related (not duplicate): How can I detect duplicate method names in a Python class?Cystectomy
E
117

EDIT For the new single dispatch generic functions in Python 3.4, see http://www.python.org/dev/peps/pep-0443/

You generally don't need to overload functions in Python. Python is dynamically typed, and supports optional arguments to functions.

def myfunction(first, second, third = None):
    if third is None:
        #just use first and second
    else:
        #use all three

myfunction(1, 2) # third will be None, so enter the 'if' clause
myfunction(3, 4, 5) # third isn't None, it's 5, so enter the 'else' clause
Eneidaenema answered 18/8, 2011 at 19:33 Comment(14)
But note that calling different functions based on the type of the arguments is much more difficult (although not impossible).Glycoside
Well, there is a difference between overloading/polymorphism and conditionals. Do you mean that we do not need the polymorphysm since we have the conditionals?Eldenelder
@Eldenelder I'm saying, basically, that in a dynamically typed language you don't need overloading as a language feature because you can trivially emulate it in code, and so can get polymorphism that way.Eneidaenema
I do not see how dynamic linking answers my question. Now, since java is statically typed, you say that in cannot have the optional arguments. I do not understand this also.Eldenelder
@Eldenelder I'm saying that between optional arguments and dynamic types the way you have them in Python, you don't need Java style method overloading to achieve polymorphism.Eneidaenema
What if I want one version of the method to return a generator, and the other version "return" a single object, depending on the parameters? Yielding and returning from the same method is not permitted in python 2.x.Amaryl
@Amaryl I would argue that isn't a good idea -- a function shoudn't return completely different types of things. However, you can always define a local generator function inside the main function, call it, and return the resulting generator -- from the outside it will be the same as if the main function was a generator. Example here.Eneidaenema
@Eneidaenema What about the case where you need def searchItem(self, item_key) and searchItem(self, item_description)?Neckar
@Neckar Generally, this is what keyword arguments are for. def search_item(self, item_key = None, item_description = None). On Python 3, you can make them keyword only to avoid the confusion of them being specified positionally -- def search_item(self, *, item_key = None, item_description = None). There are also many other ways to solve this type of problem, like having two subclasses to handle the different cases, just two different methods, etc. It depends on the structure of the rest of the code.Eneidaenema
This isn't a pythonic solution. It's clever, but being pythonic means being explicit where possible. An execution path that should occur for the presence of A & B will run only if C is not present (e.g. None, or not C). That's okay for three arguments, but for a larger number this will rapidly become unpleasant. It'd be frustrating to trace which execution path to take for not A & not C & not D & not E to take the execution path for B & F. It'd also be an eyesore to for cases where the same execution path should be taken for (A & B) and (B & C) but not if D is present.Leucomaine
@Leucomaine I wouldn't describe this as clever at all; it's the basic way to handle this in Python, as it was over a decade ago when I wrote this. I agree that as the function signature gets more complex you can run into situations where it's not a good fit -- I don't think that's a good reason to not use it in the usual, pretty simple case. Is there a specific approach you'd advocate for? I don't see any suggestions in your comment.Eneidaenema
@Eneidaenema For cases where you're going to have have do be performing multiple operations each of need different implementations based on different target data (e.g. our would-be parameters), I think it really make more sense to create a class for each type of target data with its operations as the respective class methods. That way in your code you only need to have one decision function that decides which class to invoke based on the would-be parameter types. After that the methods can be named the same. Additionally overlapping code can be moved into shared inter-class support files for DRY.Leucomaine
@Eneidaenema If you were so inclined you could even name the classes similarly and group them neatly together in a folder with the shared function file(s). To instantiate the desired class in your initial decision function you could do something like my_class: Any; if type(data) == some_type then my_class = Class_type(). There's also some really neat ways to directly call class names that are very fast, pretty self-documenting, and reduce code reuse to the maximum extent possible.Leucomaine
@Leucomaine That approach is a standard one and certainly common when the amount of code and complexity merits that level of structure. I would argue, based on the codebases I've worked on and the people I've worked with over the years, that most people who find this answer aren't in that situation, but instead in the much simpler case where what I've described above is adequate and far simpler and lower overhead.Eneidaenema
B
61

In normal Python you can't do what you want. There are two close approximations:

def myfunction(first, second, *args):
    # 'args' is a tuple of extra arguments

def myfunction(first, second, third=None):
    # 'third' is optional

However, if you really want to do this, you can certainly make it work (at the risk of offending the traditionalists ;o). In short, you would write a wrapper(*args) function that checks the number of arguments and delegates as appropriate. This kind of "hack" is usually done via decorators. In this case, you could achieve something like:

from typing import overload

@overload
def myfunction(first):
    ....

@myfunction.overload
def myfunction(first, second):
    ....

@myfunction.overload
def myfunction(first, second, third):
    ....

And you'd implement this by making the overload(first_fn) function (or constructor) return a callable object where the __call__(*args) method does the delegation explained above and the overload(another_fn) method adds extra functions that can be delegated to.

You can see an example of something similar here http://acooke.org/pytyp/pytyp.spec.dispatch.html, but that is overloading methods by type. It's a very similar approach...

And something similar (using argument types) is being added to Python 3 - PEP 443 -- Single-dispatch generic functions

Billbillabong answered 18/8, 2011 at 19:40 Comment(3)
The range builtin uses overload, so is it really a hack? github.com/python/typeshed/blob/master/stdlib/2and3/…Lucilius
Is this a mistake: @myfunction.overload? Should not just all of them have @overload, and you would then need another function without the @overload on top of this? I have just tested @overload in quite a few settings. I wonder whether this answer is outdated. typing does not allow you to overload in the code, it is just to show what the type hints would give you if you overloaded the function. Which you do not, since typing is nothing about the working code, instead, it is about making code quickly readable.Smirch
I dared to answer at How can I type-hint a function where the return type depends on the input type of an argument?. Correct me if I am wrong, but typing, in my humble opinion, does not change the working of the code, it is just to quickly show what is going on. There is no overloading with typing, the typing overload functions are not run at all, as far as I can see. I could be wrong.Smirch
S
11

Yes, it's possible. I wrote the code below in Python 3.2.1:

def overload(*functions):
    return lambda *args, **kwargs: functions[len(args)](*args, **kwargs)

Usage:

myfunction=overload(no_arg_func, one_arg_func, two_arg_func)

Note that the lambda returned by the overload functions choose a function to call depending on the number of unnamed arguments.

The solution isn't perfect, but at the moment I can't write anything better.

Seeger answered 24/10, 2014 at 19:44 Comment(1)
Great way to refresh the args and kwargs thingy... Yet, is it pythonic to make it to everyday code? I doubt it. I guess in Python, they prefer try and except or if and else or optional (=None) over this small overload overhead, so that your good code was avoided from the start of Python.Smirch
C
1

It is not possible directly. You can use explicit type checks on the arguments given though, although this is generally frowned upon.

Python is dynamic. If you are unsure what an object can do, just try: and call a method on it, then except: errors.

If you don't need to overload based on types, but just on the number of arguments, use keyword arguments.

Cypress answered 18/8, 2011 at 19:33 Comment(1)
I don't think he knew about optional arguments.Eneidaenema
S
1

Here is the way to overload python functions with default arguments as well as keyword arguments

from multipledispatch import dispatch

# FOR hi(a: int, b: int = 3)

@dispatch(int, int)
def _hi(a: int, b: int):
    print(a, b)


@dispatch(int, int)
def hi(a: int, b: int = 3):
    _hi(a, b)


@dispatch(int, b=int)
def hi(a: int, *, b: int = 3):
    _hi(a, b)


# FOR hi(a: str, b: int = 3)

@dispatch(str, int)
def _hi(a: str, b: int):
    print(a, b, 'str!')


@dispatch(str, int)
def hi(a: str, b: int = 3):
    _hi(a, b)


@dispatch(str, b=int)
def hi(a: str, *, b: int = 3):
    _hi(a, b)

hi(2)
hi(2, 3)
hi(2, b=3)
hi('2')
hi('2', 3)
hi('2', b=3)

Output

2 3
2 3
2 3
2 3 str!
2 3 str!
2 3 str!
Singlebreasted answered 24/8, 2022 at 18:59 Comment(0)
T
0

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 the example use in one of the Stack Overflow questions:

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

Trictrac answered 30/12, 2016 at 6:44 Comment(1)
Probably not accelaration. More like acceleration (an ae).Cystectomy

© 2022 - 2024 — McMap. All rights reserved.