
Overload functions in Python - arpitbbhayani
https://arpitbhayani.me/blogs/function-overloading
======
nickserv
This is pretty neat, and shows off some relatively advanced features of the
language.

And while it's fine for a personal project or in a specific context, please
don't do this in a regular Python project, especially in a professional
setting. You'll just confuse new hires, slow down execution time, and make the
code more difficult to reason about and debug.

~~~
pwdisswordfish2
It's a funny thing:

Advocates of dynamic languages tend to claim that the flexibility they offer —
dynamic duck typing, dynamic dispatch, runtime reflection, eval — is a major
advantage.

And yet every time someone actually tries to meaningfully use those features,
they say ‘why would you do that, it's too confusing’ and tell people to stick
to writing code that's just as easily expressed in a statically-typed,
statically-dispatched, AOT-compiled language, while still paying the costs of
their environment supporting those features.

If you're going to write Python like C, why even bother?

~~~
missosoup
Just because you can, doesn't mean you should.

A large part of Python's popularity is due to the fact that there's a
reasonably well defined 'pythonic' way to do things, that everyone can learn
and then have a decent experience using and reading code produced by others.

You _can_ implement fancy operators, overloading, entire DSLs in Python; but
by doing so you break the pythonic contract and make your creation stand alone
with a separate learning curve. There are some valid reasons to do this,
especially for bespoke in-house tooling, but open source modules intended for
mass use have virtually no justification to deviate from the primitives which
the entire community is used to.

~~~
2T1Qka0rEiPr
> Just because you can, doesn't mean you should.

I think this is very much true, but actually I disagree with you when it comes
to OSS. For example, Django makes heavy use of metaclasses in order to
simplify its API, and I think _that 's fine_, because no junior developer
realistically needs to contribute to such a project. They can work on a
project _which uses_ Django without needing to understand the internals.

Having said that, I was only introduced to SQLAlchemy a couple of years ago,
when already pretty competent at Python. Their filter syntax (ab)uses __eq__
to allow you to write expressions such as `MyModel.my_field == 'query'` which
return an expression which can be evaluated dynamically when applied to a SQL
query. I did a double take when first looking at this, assuming it at first to
be a typo. I then ended up digging into the internals of SQLAlchemy to find
out how it all fits together. The upshot was that I explored the SQLA API in
great detail. The downside is I spent a few hours doing it :D

------
skrause
The standard library has something similar:
[https://docs.python.org/3/library/functools.html#functools.s...](https://docs.python.org/3/library/functools.html#functools.singledispatch)

~~~
mlthoughts2018
Cython also has had a mechanism for this for a long time. In fact if you
wanted multiple dispatch in a new pure C program in 2020, just write it in
Cython with no use of the CPython API and have Cython generate the pure C
library for you.

[https://cython.readthedocs.io/en/latest/src/userguide/fusedt...](https://cython.readthedocs.io/en/latest/src/userguide/fusedtypes.html)

------
rusk
I thought a consensus had emerged that function overloading was a bad idea for
a while now? Even in strongly-typed languages, it pushes that extra bit of
cognitive load onto the human reader. It also complicates things for tooling.
In loosely typed languages it's hard to see the need. As somebody else
mentioned here, variable _args and_ _kwargs are the more_ pythonic* way to
address such concerns. If you want to have different behaviour for different
args you can do this explicitly.

I guess this article is a fun discussion and a nice comparison of language
features, maybe I'm taking it too seriously.

~~~
testuser66
On one hand - I agree, overloading isn't great. On the other hand having a
`def func(args, kwargs)` is a pain the ass for everyone involved (people &
IDEs): you have no idea what the args or kwargs could be without reading the
source.

If you can get away with just a bunch of named kwargs after the arguments that
is fine, but I'd take overloading over the `args, kwargs` garbage any day,
even if that is the more "pythonic" way.

~~~
rusk
I think the kwargs approach is fine for when you’re reading your code. When
you’re writing I think you’ll always have to consult the docs, or headers. In
a strongly typed language IDE can pick up the hint but in a more dynamic
language like python it can get confused.

~~~
testuser66
My bad - I meant this but didn't know how to format code so that is showed the
stars

    
    
        def func(*args, **kwargs)
    

I am in full support of actual kwargs with names, it's the wildcard ones that
I don't like.

------
josh_fyi
Python has idiomatic ways of implementing something like overloading. \-
Default parameters, so variable numbers of arguments can be passed. \- Run-
time type identification. Though not generally recommended, you can use it for
slightly different implementations for different argument types.

------
mumblemumble
This is a neat article, and a nice dive into Python.

I think it also taught me, by prompting an immediate negative gut reaction to
the basic idea, about an opinion about language features that I didn't know I
had, let alone that I didn't know I had so strongly: I think that I officially
believe that function overloading should only be used for two reasons: First,
you can overload functions of the same arity to mimic dynamic typing in a
static language. A print function that takes many types of argument, for
example. Second, you can provide multiple arities to mimic optional arguments
in a language that doesn't have them.

But the example in the argument, where the overload is for providing two
different versions that do different things with their arguments, is not
something I'd want to see in real code. There's just too much opportunity for
confusion. For example, if I were familiar with `float area(int)` as a
function that calculates the area of a circle, and and then encountered
`area(int, int)`, I would guess that the return value is a float, and that the
two ints are now the lengths of the semi-major and semi-minor axes of an
ellipse.

And I'm having a hard time coming up with a better example for the article.
Perhaps because function overloading just isn't a desirable feature in a
language like Python.

~~~
cuchoi
I don't miss it a lot in Python except for some cases such as:

    
    
        area(Circle circle):
            ...
    
        area(Square square):
            ...
    

You could say "Ah! But that could be something that you define in each class,
like square.calculate_area()". Yes, but sometimes you don't have access to the
class. You could monkey-patch it but that's not something that I like to do.

~~~
mumblemumble
The Pythonic way to deal with that would be to just remember that Python is a
dynamic language:

    
    
      def area(shape):
          if isinstance(shape, Circle):
              ...
          if isinstance(shape, Square):
              ...
    

Or, if you want better coverage from your type checker,

    
    
      from typing import overload
    
      @overload
      def area(shape: Circle): ...
    
      @overload
      def area(shape: Square): ...
    
      def area(shape):
          if isinstance(shape, Circle):
              ...
          if isinstance(shape, Square):
              ...
    

This still isn't real overloading. The last one is the one and only function.
All the other two bits do is tell the static type checker what kinds of
arguments it's prepared to support.

edit: Scratch that, I think what I'd really go for in a simpler case like this
would just be

    
    
      def area(shape: Union[Circle, Square]):
        ...

~~~
closed
Python including functools.singledispatch I think is a strong indicator that
function overloading IS pythonic (or at least pythonic enough to core python
developers)

~~~
joshuamorton
I don't know that I've seen singledispatch used anywhere, and the standard
library includes a lot of unpythonic code (and entire modules).

As a simple example, the unittest module is entirely unpythonic.

Singledispatch was mostly included to handle specific cases where
singledispatch is very clearly useful (the PEP mentions pprint and copy), but
not as a generic tool for common end-user code.

------
bakery2k
The Wren scripting language supports this kind of "overloading by arity" [0].

Wren therefore allows overloads such as `range(stop)` and `range(start,
stop)`. This is more intuitive than Python's `range(start=0, stop)`, which
might be the only function in the language that has an optional parameter
_before_ a required one.

[0] [http://wren.io/method-calls.html](http://wren.io/method-calls.html)

~~~
mlonkibjuyhv
I could have sworn I have written range(1,stop) many times in Python. Did I
misunderstand your argument, or has my memory gone all sideways?

~~~
bakery2k
`range(stop)` and `range(1, stop)` are both supported, but without
overloading, the implementation of `range` is messy as it has to work out the
meaning of each argument manually.

~~~
skrebbel
Why is that a problem? I _want_ the standard library to contain all messy
stuff so my code doesn't have to.

From the call site there's no difference between Python's optional-first-
argument range() function and a hypothetical overloaded one. Any perceived
complexity in usage, therefore, can be fixed with better documentation.

~~~
bakery2k
`range` is an example. Lack of support for overloading makes it harder to
replicate its API in our own functions.

~~~
skrebbel
Ah right, totally misunderstood.

Yep, true. Overloading is nice.

------
techdragon
Its weird an article written about this _now_ , has no mention of
[https://www.python.org/dev/peps/pep-0443/](https://www.python.org/dev/peps/pep-0443/)
(skrause mentioned the standard library docs for this
[https://docs.python.org/3/library/functools.html#functools.s...](https://docs.python.org/3/library/functools.html#functools.singledispatch)
a few minutes ago
[https://news.ycombinator.com/item?id=22346433](https://news.ycombinator.com/item?id=22346433)
)

It's especially weird to me, since single dispatch generic functions would do
quite a lot of what he shows in the article, without having to build it all
from scratch. I mean if you need more than what the standard library tools for
multiple dispatch will let you have, then sure build your own, but I
definitely echo nickserv's sentiments
[https://news.ycombinator.com/item?id=22346235](https://news.ycombinator.com/item?id=22346235)
... This kind of hand rolled alternative to something in the standard library
is not something you should end up doing as a last resort, its usually more
trouble than it's worth. When you do need it, you should be documenting the
_hell_ out of not just what it does, but why you had to do it yourself.

Edit for general knowledge sharing reasons: I just noticed the nice update to
the built in functools.singledispatchmethod
([https://docs.python.org/3/library/functools.html#functools.s...](https://docs.python.org/3/library/functools.html#functools.singledispatchmethod))
that came with Python 3.7, it now supports registering arguments using type
annotations. I can already think of a few places where I could go back and
clean up some code by removing a bunch of now unnecessary code doing "if
isinstance(foo, str):" checks.

------
jupake
This is very clever. But function overloading is something out of static
language territory. It feels un-Pythonic and needlessly complicated.
Especially when compared to _args and_ *kwargs.

~~~
closed
It feels pythonic to me. One thing that I think brings perspective here is the
PEP on singledispatch, which is essentially on function overloading, and is
implemented in functools!

[https://www.python.org/dev/peps/pep-0443/](https://www.python.org/dev/peps/pep-0443/)

------
fredley
Got to the Namespace portion and couldn't help thinking "Namespaces are one
honking great idea—let's do more of those!"

------
keymone
meh.. that kind of code would be on a shortlist for refactoring at first
sight.

    
    
        area(radius=1)
        area(width=1, height=1)

