83

How can I declare a few methods with the same name, but with different numbers of parameters or different types in one class?

What must I change in the following class?

class MyClass:
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
    def my_method(self,parameter_A_that_Must_Be_String):
        print parameter_A_that_Must_Be_String

    def my_method(self,parameter_A_that_Must_Be_String,parameter_B_that_Must_Be_String):
        print parameter_A_that_Must_Be_String
        print parameter_B_that_Must_Be_String

    def my_method(self,parameter_A_that_Must_Be_String,parameter_A_that_Must_Be_Int):
        print parameter_A_that_Must_Be_String * parameter_A_that_Must_Be_Int
5
  • 5
    you may want to read this. dirtsimple.org/2004/12/python-is-not-java.html and this dirtsimple.org/2004/12/java-is-not-python-either.html Commented Feb 22, 2011 at 14:52
  • 15
    After experimenting with this for a little while, I have found that it appears to be valid to write multiple functions with the same name in Python, but that each time you write another function with the same name, the interpreter completely forgets about the prior functions with that name. Commented Oct 14, 2013 at 13:51
  • 2
    Related: functools.singledispatch Commented Jan 5, 2021 at 21:47
  • @TomWillis I think the titles you linked should say "Python is not an OOP language." Polymorphism is not a Java concept. It is one of the 4~5 core principles of OOP. There was a time when these were considered the minimum api that a language needed to expose in order to call itself object oriented. Half-implemented feature sets are just asking for anti-patterns via forced workarounds. I still love the language. It just needs to pick a lane and hold the wheel steady. Commented Feb 5, 2022 at 20:24
  • @wim's comment is the best option when you need a reliable solution to use consistently, as decorators require you to declare a nested wrapper function. But kefeizhou's answer, below, is best when you need a one-time solution, for a few replications; otherwise, you would end up with lots of repeated code anyway. Commented Aug 10, 2024 at 2:25

13 Answers 13

97

You can have a function that takes in a variable number of arguments.

def my_method(*args, **kwds):
    # Do something

# When you call the method
my_method(a1, a2, k1=a3, k2=a4)

# You get:
args = (a1, a2)
kwds = {'k1':a3, 'k2':a4}

So you can modify your function as follows:

def my_method(*args):
    if len(args) == 1 and isinstance(args[0], str):
        # Case 1
    elif len(args) == 2 and isinstance(args[1], int):
        # Case 2
    elif len(args) == 2 and isinstance(args[1], str):
        # Case 3
Sign up to request clarification or add additional context in comments.

3 Comments

This is the right way of doing this, except that you should raise an exception in the failsafe case. Something like else: raise TypeError('Parameter 1 should be a string and parameter 2 should be a string, int or omitted entirely'). And, as @delnan says, use basestring instead of str unless you have a good reason for not wanting unicode.
Thanks! I'm years late, but this solves my headache about different signatures for same method (child class to override parent class).
As of Python 3.10 a more elegant solution would be to use Structural Pattern Matching. You can find my implementation below. stackoverflow.com/a/74745600/4508848
33

You can't. There are not overloads or multimethods or similar things. One name refers to one thing. As far as the language is concerned anyway, you can always emulate them yourself... You could check types with isinstance (but please do it properly - e.g. in Python 2, use basestring to detect both strings and unicode), but it's ugly, generally discouraged and rarely useful. If the methods do different things, give them different names. Consider polymorphism as well.

3 Comments

If you consider polymorphism in python then ur back at using isinstance() and other type() checking, no? How do you mean polymorphism in python?
@RetroCode I mean polymorphism as in object-oriented programming, i.e., a bunch of classes (probably with some inheriting from others, but not necessarily in Python due to duck typing) that have methods of the same name with compatible signatures.
I do not feel any need to do something like: get_user_by_id(id) and get_user_by_name(name) when I could just do get_user(id) and get_user(name) overload. The parameter list is there to distinguish the two, it's clear and concise. Without overloading you can get ridiculous function names. I hope Python adds overloading in future.
33

Using Python 3.5 or higher, you can use @typing.overload to provide type annotations for overloaded functions/methods.

From the docs:

@overload
def process(response: None) -> None:
    ...
@overload
def process(response: int) -> tuple[int, str]:
    ...
@overload
def process(response: bytes) -> str:
    ...
def process(response):
    <actual implementation>

1 Comment

Thank you! Also, from typing import overload is required.
7

Short answer: you can't (see this previous discussion). Typically you'd use something like (you could add more type checking and reorder):

def my_method(self,parameter_A, parameter_B=None):
  if isinstance(parameter_B, int):
    print parameter_A * parameter_B
  else:
    print parameter_A
    if parameter_B is not None:
      print parameter_B

Comments

7

As of Python 3.10 a more elegant solution would be to use Structural Pattern Matching.

def my_method(parameters):
    match parameters:
        case str():
            # Case 1
        case (str(), str()):
            # Case 2
        case (str(), int()):
            # Case 3
        case _:
            print('no match')

Comments

3

This cannot work. No matter how many arguments you have, the name m will be overriden with the second m method.

class C:
    def m(self):
        print('m first')
    def m(self, x):
        print(f'm second {x}')


ci=C();
#ci.m() # will not work TypeError: m() missing 1 required positional argument: 'x'
ci.m(1) # works

The output will simple be:

m second 1

Comments

2

You can try multimethods in Python:

http://www.artima.com/weblogs/viewpost.jsp?thread=101605

But I don't believe multimethod is a way to go. Rather objects that you pass to a method should have common interface. You are trying to achieve method overloading similar to the one in C++, but it is very rarely required in Python. One way to do this is a cascade of ifs using isinstance, but that's ugly.

1 Comment

This is the best solution IMO (and the only real solution). A package is available pypi.org/project/multimethod.
2

Python is nothing like Java.

There are not really types, just objects with methods.

There is a way to test if a passed object is from a class, but it is mainly bad practices.

However, the code you want to produce for the two first methods should be something like

class MyClass(object):
    def my_method(self, str1, str2=None):
        print str1
        if str2: print str2

For the third, well... Use a different name...

1 Comment

(1) There are types, you just don't set the types of names in stone via static typing. (2) if x is a horrible way to check for None. It really checks if x is "falsy". (3) Optional parameters don't really work for the example, nor for overloading in general.
0

You probably want a pattern similar to the following: Note that adding '_' to the beginning of a method name is convention for marking a private method.

class MyClass:
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
    def my_method(self,parameter_A_that_Must_Be_String, param2=None):
        if type(param2) == str:
            return self._my_method_extra_string_version(parameter_A_that_Must_Be_String, param2)
        elif type(param2) == int:
            return self._my_method_extra_int_version(parameter_A_that_Must_Be_String, param2)
        else:
            pass # use the default behavior in this function
        print parameter_A_that_Must_Be_String

    def _my_method_extra_string_version(self,parameter_A_that_Must_Be_String, parameter_B_that_Must_Be_String):
        print parameter_A_that_Must_Be_String
        print parameter_B_that_Must_Be_String

    def _my_method_extra_int_version(self,parameter_A_that_Must_Be_String, parameter_A_that_Must_Be_Int):
        print parameter_A_that_Must_Be_String * parameter_A_that_Must_Be_Int

1 Comment

This is probably a bit over-engineered for python. You probably want to start the method be setting param2 to a default and then changing it to a string or an int as needed at the beginning of the method.
0
class MyClass:
    def __init__(this, foo_str, bar_int):
        this.__foo = foo_str
        this.__bar = bar_int

    def foo(this, new=None):
        if new != None:
            try:
                this.__foo = str(new)
            except ValueError:
                print("Illegal value. foo unchanged.")

        return this.__foo

    def bar(this, new=None):
        if new != None:
            try:
                this.__bar = int(new)
            except ValueError:
                print("Illegal value. bar unchanged.")

        return this.__bar

obj = MyClass("test", 42)
print(obj.foo(), obj.bar())

print(obj.foo("tset"), obj.bar(24))

print(obj.foo(42), obj.bar("test"))

Output:
    test 42
    tset 24
    Illegal value. bar unchanged.
    42 24

1 Comment

An explanation would be in order.
0

I think one very simple example is missing from all the answers, and that is: what to do when the only difference between variations on the method is the number of arguments. The answer still is to use a method with variable number of arguments.

Say, you start with a method that requires use of two arguments

def method(int_a, str_b):
    print("Got arguments: '{0}' and '{1}'".format(int_a, str_b)

then you need to add a variant with just the second argument (say, because the integer is redundant), the solution is very simple:

def _method_2_param(int_a, str_b):
    print("Got arguments: '{0}' and '{1}'".format(int_a, str_b))

def _method_1_param(str_b):
    print("Got argument: '{0}'".format(str_b))

def method(*args, **kwargs):
    if len(args) + len(kwargs) == 2:
        return _method_2_param(args, kwargs)
    elif len(args) + len(kwargs) == 1:
        return _method_1_param(args, kwargs)
    else:
        raise TypeError("Method requires one or two arguments")

The nice thing about this solution is that no matter if the calling code used keyword arguments or positional arguments before, it will still continue to work.

Comments

0

I think @overload solves the thing: VS Code -> f12 on open() -> it shows a bunch of duplicates of the open() function:

@overload
def open(...):
@overload
def open(...):
...

So, here's the result...

from typing import overload

class MyClass:
""""""

#----------------------------------------------------------------------
def __init__(self):
    """Constructor"""
@overload
def my_method(self,parameter_A_that_Must_Be_String,parameter_B_that_Must_Be_String):
    print parameter_A_that_Must_Be_String
    print parameter_B_that_Must_Be_String
@overload
def my_method(self,parameter_A_that_Must_Be_String,parameter_A_that_Must_Be_Int):
    print parameter_A_that_Must_Be_String * parameter_A_that_Must_Be_Int
def my_method(self,parameter_A_that_Must_Be_String):
    print parameter_A_that_Must_Be_String

I'm sorry for being nervous...

2 Comments

Hi, @overload is just for static type checkers. It doesn't bring overloading behavior like what you see in other languages.
The files end with .pyi are stub files. They only contain type hints. You don't see any implementation there.
0

You can use multipledispatch.dispatch and typing.overload to do that ellegantly instead of using ugly isinstance(b, str) or *args.

Install: pip3 install multipledispatch

from typing import overload
from multipledispatch import dispatch

# Annotations of mulitple input options for better visual.
@overload
def func(a:str): pass
@overload
def func(a:str, b:str): pass
@overload
def func(a:str, b:int): pass

# Actuall implementations for different sets of input.
@dispatch(str)
def func(a):
    print("ha")
@dispatch(str, str)
def func(a,b):
    print("haha")
@dispatch(str, int)
def func(a,b):
    print("hahaha")

func("a")
func("a","b")
func("a",1)

You get:

ha
haha
hahaha

With @overload, you will see the hints of input options: enter image description here

and

enter image description here

Without @overload, it still works, but no hints: enter image description here

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.