Skip to content Skip to sidebar Skip to footer

What Would Be The Python Equivalent Of Type-wise Overloading?

To my knowledge, there are two types of overloading, one based on number of arguments, and one based on argument types While overloading based on number of arguments has been cover

Solution 1:

You could use functools.singledispatch. It will dispatch based on the type of first parameter and uses the default implementation if type doesn't match with any of the registered functions:

from functools import singledispatch

@singledispatchdefoverloaded_func(arg):
    print('Default impl', arg)

@overloaded_func.register(list)defdo_list_action(lst):
    print('List action', lst)

@overloaded_func.register(dict)defdo_list_action(dct):
    print('Dict action', dct)

overloaded_func(['foobar'])
overloaded_func({'foo':'bar'})
overloaded_func('foobar')

Output:

List action ['foobar']
Dict action {'foo': 'bar'}
Defaultimplfoobar

Solution 2:

Type checking with type() (or, better, isinstance()) isn't frowned upon in general in Python. What's frowned upon is using type as a proxy for behavior when you can get away with only checking the behavior. In other words, when you need an object to have some certain functionality, whereas in some other languages you would have to explicitly check whether its type supports that functionality, in Python you just assume the object does what you need it to do, and trust that an exception will be raised if that's not the case. But if you're choosing between different types of functionality, any of which will do the job for you, that's a different story.

For example, suppose you have some code that can use either lists or dicts to implement integer-indexed arrays.

classArray:def__init__(self, keys_and_values):
        self.store = ... # either a list or a dict

Perhaps it uses a dict if only a few, very large indices have values assigned, and a list otherwise. If you want to access the element at an index, if there is one, then you just write self.store[index].

def__getitem__(self, index):
        returnself.store[index]

You don't bother to check whether it's a list or a dict first, because the behavior you want - the ability to be indexed by an integer - exists either way.

But if you want to set the element at an index, if it's a list, you need to extend it to the proper length first. Now, proper duck typing would probably suggest you do this:

def__setitem__(self, index, value):
        if index >= len(self.store):
            try:
                self.store.extend([None] * (index - len(self.store) + 1))
            except AttributeError:
                pass
        self.store[index] = value

But I think most Python programmers would say isinstance() is better in this case. (No, really. It's okay.)

def__setitem__(self, index, value):
        ifisinstance(self.store, list) and index >= len(self.store):
            self.store.extend([None] * (index - len(self.store) + 1))
        self.store[index] = value

I would generally recommend this route when you've only got a few types to test.

If you have many more types to test, it's more practical to use a dispatcher pattern, which is a functional approach. You build a mapping of types to functions that handle that type, and choose which one to call based on the type of the object you get. In this example, that would play out like this:

def__setitem__dict(self, index, value):
        self.store[index] = value
    def__setitem__list(self, index, value):
        if index >= len(self.store):
            self.store.extend([None] * (index - len(self.store) + 1))
        self.store[index] = value
    __setitem__dispatch = {list: __setitem__list, dict: __setitem__dict}
    def__setitem__(self, index, value):
        self.__setitem__dispatch[type(self.store)](index, value)

It's pretty silly to do this in this simple example, but in more complicated scenarios it can come in very handy. The pattern in general is

dispatch = {list: handle_list, dict: handle_dict, ...}
def function(arg):
    return dispatch[type(arg)](arg)

It even lets you dynamically add handlers for new types later on. This is basically what functools.singledispatch does (as another answer mentioned). That way just looks complicated because it hides the dispatch dictionary as an attribute of the original function itself.

It's impossible to say in general whether to use duck typing, type checking, dispatching, or something else, because it's somewhat subjective and depends on the details of your situation: how different is the code you need to handle different types? How many types are you dealing with? Do you need to be able to handle new types easily? And so on. You haven't given enough information in the question to allow anyone else to tell you which way seems best, but they all have their uses.

Solution 3:

Use isinstance:

def overloaded_func(arg):
    if isinstance(arg, list):
        do_list_action(arg)
    elif isinstance(arg, dict):
        do_dict_action(arg)
    else:
        do_default_action(arg)

Alternatively, you might consider checking other ways such as for the presence of __getitem__ or __iter__ etc. This depends on the details of why you're overloading, which you haven't shared with us.

Post a Comment for "What Would Be The Python Equivalent Of Type-wise Overloading?"