What Would Be The Python Equivalent Of Type-wise Overloading?
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?"