3

I am trying to do something like this in python:

def foo(x, y):
    # do something at position (x, y)

def foo(pos):
    foo(pos.x, pos.y)

So I want to call a different version of foo depending on the number of parameters I provide. This of course doesn't work because I redefined foo.

What would be the most elegant way of achieving this? I would prefer not to use named parameters.

chw
  • 393
  • 4
  • 11
  • In Python there are default parameters... – nbro Jun 07 '15 at 11:52
  • You'd need to work against the language to do that. I'd rather provide only one overload. – Kos Jun 07 '15 at 11:55
  • see [overload](https://pypi.python.org/pypi/overload/1.1) package on pypi – behzad.nouri Jun 07 '15 at 11:55
  • @behzad.nouri: boy, that decorator sure runs [a lot of code](https://bitbucket.org/r1chardj0n3s/overload/src/d05dc40af8352ab1c668ffd628733a0191efd63c/overload.py?at=default#cl-113) each time you call one of the functions. That's hardly an efficient way to handle this. – Martijn Pieters Jun 07 '15 at 12:04
  • @MartijnPieters maybe, but it looks cool : ) – behzad.nouri Jun 07 '15 at 12:17
  • @behzad.nouri: yeah, that's always a great reason to use a library. It slows down your code execution speed by a huge factor, but *it looks so cool!* Ahem. :-P – Martijn Pieters Jun 07 '15 at 12:20
  • @Martijn: I don't think it's necessarily obvious how this is a dupe of that other question. The answers over there are all about overloading by type, whereas here we "just" have to overload by number of arguments, with only two possibilities. It's not clear to me how to apply them to this case, other than the generic "this indicates a design problem", meaning "don't ask this question" ;-) – Steve Jessop Jun 07 '15 at 12:22
  • @SteveJessop: I'm open to suggestions of better dupes, but there are a whole series of questions on overloading (e.g. there is no overloading, plus various approaches to designing better options). I'm not sure we have to re-hash this all again. – Martijn Pieters Jun 07 '15 at 12:24
  • @MartijnPieters well if we *only* cared about runtime performance, then dynamically typed languages would have never got traction. – behzad.nouri Jun 07 '15 at 12:28
  • @behzad.nouri: that's never an excuse to just drain out the performance you do have. – Martijn Pieters Jun 07 '15 at 12:29
  • @MartijnPieters: I have seen a lot of similar questions including the one you linked before and was asking this question hoping there would be a better way. Apparently, there isn't. – chw Jun 07 '15 at 12:29

5 Answers5

5

Usually you'd either define two different functions, or do something like:

def foo(x, y = None):
    if y is None:
        x, y = x.x, x.y
    # do something at position (x, y)

The option to define two different functions seems unwieldy if you're used to languages that have overloading, but if you're used to languages like Python or C that don't, then it's just what you do. The main problem with the above code in Python is that it's a bit awkward to document the first parameter, it doesn't mean the same in the two cases.

Another option is to only define the version of foo that takes a pos, but also supply a type for users:

Pos = collections.namedtuple('Pos', 'x y')

Then anyone who would have written foo(x,y) can instead write foo(Pos(x,y)). Naturally there's a slight performance cost, since an object has to be created.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
5

You can use default parameters, but if you want use overloading in python, the best way to do this is:

from pytyp.spec.dispatch import overload

@overload
def foo(x,y):

@foo.overload
def foo(x, y,z):

Pytyp is a python package (you can download it from https://pypi.python.org/pypi/pytyp). It includes module pytyp.spec.dispatch, which contains decorators, like overload. If it is palced on method, this method will be called for all the overloaded methods..

szarad
  • 81
  • 1
  • 7
2

What you're trying to achieve looks like a bad design, maybe consider sticking with different function names:

def do_something_by_coordinates(x, y):
    pass

def do_something_by_position(pos):
    do_something_by_coordinates(pos.x, pos.y)

Or you can use kwargs if you really need to:
Understanding kwargs in Python

Community
  • 1
  • 1
matino
  • 17,199
  • 8
  • 49
  • 58
  • If it's not a good design, then why does Python have `min` instead of `min_of_iterable` and `min_of_arguments` or so? – Stefan Pochmann Jun 07 '15 at 12:12
  • Because with `min` it actually makes sense to use such design. Maybe I shouldn't use words "is not a good design" but "smells like bad design", since whatever this really is a bad design depends on a larger context which we don't know. – matino Jun 07 '15 at 12:16
2

There are a lot of things you could do, like named default parameters.

However, what it looks like you want is "multiple dispatch" and there's a number of implementations out of there of decorators to help you do that sort of thing. Here's one implementation that looks like:

>>> from multipledispatch import dispatch

>>> @dispatch(int, int)
... def add(x, y):
...     return x + y

>>> @dispatch(Position)
... def add(pos):
...     return "%s + %s" % (pos.x, pos.y)

>>> add(1, 2)
3

>>> pos = Position(3, 4)
>>> add(pos)
7

While @functools.singledispatch is coming to Python 3.4 I don't think that will work for your example as in one case you have multiple arguments.

stderr
  • 8,567
  • 1
  • 34
  • 50
  • I guess this means you lose the duck-typing of the one-arg version, is that correct? It has to take `Position`, not any other type that has `.x` and `.y`? I suppose you can still use an ABC for dispatch. – Steve Jessop Jun 07 '15 at 12:11
  • You can do "@dispatch(object, object)" if you want to do duck type inside that implementation. I just assumed that if you really wanted multiple dispatch you didn't want to duck type ;-) – stderr Jun 07 '15 at 12:12
0

One way by setting a default value:

def foo(x, y=None):    
    # now for example, you can call foo(1) and foo(1,2)     

Inside foo you can check if y == None and have a different logic for the two cases.

Note that you can have a better design if you separate the functions, don't attempt to have the same function that accept both coordinates and position.

Maroun
  • 94,125
  • 30
  • 188
  • 241