Implementing Function Overloading in Python

(arpitbhayani.me)

34 points | by arpitbbhayani 179 days ago

17 comments

  • anentropic 178 days ago

    This is pretty horrible.

    And you could achieve the same thing with built-in functools.singledispatch by designing your API better

    e.g. the example given is:

        int area(int length, int breadth) {
          return length * breadth;
        }
    
        float area(int radius) {
          return 3.14 * radius * radius;
        }
    
    What if you need to calculate the area of another shape that uses a single integer value as the parameter?

    With singledispatch and some types:

        import math
        from functools import singledispatch
        from typing import NamedTuple
    
        class Rectangle(NamedTuple):
            length: int
            breadth: int
    
        class Circle(NamedTuple):
            radius: int
    
        @singledispatch
        def area(val) -> None:
            raise TypeError(val)
    
        @area.register
        def _(rectangle: Rectangle) -> int:
            return rectangle.length * rectangle.breadth
        
        @area.register
        def _(circle: Circle) -> float:
            return math.pi * math.pow(circle.radius, 2)
    • BiteCode_dev 178 days ago

      Again this is over-engineering. Put functions in modules to namespace them and be done with it.

      If you really want types, use classes. It removes the need for single dispatch, creates a self documenting namespace and saves you an import when you want to do the calculation.

          import math
          from dataclasses import dataclass
      
          @dataclass
          class Rectangle:
              length: int
              breadth: int
              def area(self) -> int:
                  return self.length * self.breadth
      
          @dataclass
          class Circle:
              radius: int
              def area(self) -> float:
                  return math.pi * self.radius ** 2
      
      
      @dataclass is of course not mandatory, and just make the code shorter.

      We are talking about very simple things here. No need to add complexity to them.

      • anentropic 178 days ago

        Yes I agree, if I was writing this for my own project I'm pretty sure I would do what you have done and use classes with an `area` method. (FWIW I think the dataclass approach is nicer for this example than namespacing separate `area` functions using modules)

        You could define it as a typing.Protocol too.

        My point was just that, even if for some reason you prefer function overloading, the restrictions needed to fit within the constraints of functool.singledispatch arguably lead to cleaner code than the complicated mess in the OP article.

        This would look messy if you implemented as just a single function with variable no of kwargs that you sniffed in the body, and I don't think applying function overloading alone as OP has done solves the underlying problem.

        • BiteCode_dev 178 days ago

          > My point was just that, even if for some reason you prefer function overloading, the restrictions needed to fit within the constraints of functool.singledispatch arguably lead to cleaner code than the complicated mess in the OP article.

          Indeed. Less is more sometimes.

        • jakearmitage 178 days ago

          Didn't know about @dataclass until now. Thank you.

      • harel 178 days ago

        Came here to say the same thing - so take my upvote sir.

      • Waterluvian 178 days ago

        I love Python as a "hack it into a Frankenstein monster for fun" language because of how much power you have to get under the hood. For that reason I love articles like this.

        But I'm not sure you'd ever want to implement overloading for practical uses. Just utilize optional arguments and some branching logic within the function. But even then, rethink how much you're trying to pack into a function.

        • lordgrenville 178 days ago

          Yes, my reaction to this was "this is horrifying Python, never do this in real life!" But also fascination at how malleable and subvertible it is once you go deep enough into the internals.

          • iovrthoughtthis 178 days ago

            You might enjoy ruby as well then!

          • BiteCode_dev 178 days ago

            Remember that duck typing + default values + args + kwargs is usually more idiomatic than function overloading in python.

            Don't try to code in python like you code in another language.

            Also, think about what your API is conveying. Overloading may make it less clear.

            The snippet from this article is a good example of when not to do it:

                def area (l, b): 
                  return length * breadth;
            
            
                def area(r):
                  return 3.14 * radius * radius
            
            Here, you have 2 functions that do 2 fundamentally different things. One is calculating an area for a circle. The other one is for a rectangle. When you have 2 different signatures, it's often a sign the process is very different, and you should signal that in your API.

            If you scan code using those, your brain cannot quickly parse it. It needs to process "area(), ok but of what, well, it has only x param, so I guess this is for z". The overloading gimmick has little value for the reader, but a real cost (not to mention in python it actually slows the run time)

            The better solution is simply to make it explicitly 2 functions with good names:

                def rectangle_area (l, b): 
                  return length * breadth;
            
            
            Or, most likely, put them in separated namespaces (using a module or a class), which does the same.

                import circle
            
                circle.area(r)       
            
            It's very easy to abuse overloading because it tickles our need for refactoring. But it's a tool to solve a problem, not an end by itself.
            • oefrha 178 days ago

              There's really nothing wrong with having two different signatures, it's a pretty pythonic way to do things. It's harder to document for sure, but other than that hardly a problem in many cases.

              This post unfortunately reads like one of those Java programmer discovers Python (except not really) posts, but for C++. In general if you find yourself inventing a new paradigm for a 30-year old language you just started working with, it's reason enough to doubt you're doing something suboptimal. However, in this case the author is actually very familiar with Python, so I'm kinda confused.

              • throwaway_pdp09 178 days ago

                The first part seems the wrong way to do it (treating it as an example). You would presumably overload area() on the object type, not on individual parameters representing the object's geometry. 'circle.area(r)' is even weirder - extracting radius just doesn't make sense here.

                <SomeGeometricObject>.area() is right AFAICS.

                Which I think is what you're getting at anyway.

                But IMO overloading used right is cleaner than not having overloading.

                • BiteCode_dev 178 days ago

                  > 'circle.area(r)' is even weirder

                  It's just putting the original area() function in a module, not making it a method of a class. It's the simplest solution you can have, ever.

                  Adding an object is already more complex, and complexity needs to be justified. Just KISS if you can.

              • arunix 178 days ago

                This seems like a lot of work for a result that isn't even very self documenting compared to some earlier efforts e.g.

                Guido's post on multimethods: https://www.artima.com/weblogs/viewpost.jsp?thread=101605

                Clojure style multimethods: https://adambard.com/blog/implementing-multimethods-in-pytho...

              • goodside 178 days ago

                This borders on spam. This is the third time you’ve submitted this post in the past two months and the feedback is no more positive this time.

                Yes, you can do just about anything in Python. But single dispatch is usually a bad idea, and implementing it yourself instead of using the Python stdlib is always a bad idea.

                There’s nothing wrong with showing off programming curiosities, but this isn’t presented in that context. The more often you post it, especially without heeding or engaging with feedback, the more annoying it becomes.

                • enricozb 178 days ago

                  If you want to "overload" a function. Do

                      def area(shape):
                        return shape.area()
                  
                  and implement it appropriately in your type.
                  • trymas 178 days ago

                    https://pypi.org/project/multidispatch/

                    IMHO, multiple dispatch is unpythonic and you should use it only if you really need it. Also OP's implementation is missing typing information and differentiates functions only by length of arguments. Though probably that's the reason I do not like this idea in python because it's not a statically typed language.

                    • lervag 178 days ago
                      • whalesalad 178 days ago

                        Singledispatch from functools will often work in a lot of situations where you might want to do something like this: https://docs.python.org/3/library/functools.html

                        This is a cool post from the author, though. I interpret this as more of an exploration of Python under the hood versus being a recommended production solution.

                        Python certainly could use a facility to do that based on an arbitrary predicate fn versus type comparison, though.

                        Python is well suited for this.. maybe it is time to throw a PEP in the ring.

                        • jstrong 178 days ago

                          "Yeah, but your scientists were so preoccupied with whether or not they could, they didn’t stop to think if they should." -Dr. Ian Malcolm

                          • sleavey 178 days ago

                            Isn't this behavior in Python already via the functools module? Can't look it up right now but I'm pretty sure it's there.

                            • joshuamorton 178 days ago

                              Functions.singledispatch allows overloading based on the type of the first argument, but doesn't allow complex overloads, like arg-count based or multi-type based.

                              • emidln 178 days ago

                                This is not hard to write if you care about it (dispatching on data in addition to dispatching on type). Clojure-style multimethods with python-style type dispatch or data result dispatch can be had for under 150 lines. It's not particularly useful unless you are porting code or otherwise writing very unpythonic code.

                            • mrkeen 178 days ago

                              If you allow both

                                int area(int length, int breadth)
                              
                              and

                                float area(int radius)
                              
                              to exist, then in the future you won't be able ask what the type of 'area' is. (If that's your kind of thing)
                              • zomglings 178 days ago

                                You will, the type could be Union[int, float].

                                Alternatively, you could also use the abstract base classes in the numbers module - https://docs.python.org/3/library/numbers.html

                                numbers.Number may be a good choice.

                                Still, you are right, overloading like this may be okay as a thought exercise, but would raise some serious questions during a code review. :)

                              • klhugo 178 days ago

                                Using fancy features does not make you a better programmer. It is the other way around.

                                • mrkeen 178 days ago

                                  Being a better programmer makes you use fancy features?

                                  • modo_ 177 days ago

                                    Charitably interpreted: A better programmer knows using a fancy feature _can_ result in better code, but, also knows that fancy features usually aren't required to write good code (and that fancy features can turn good code into bad code when reached for too quickly)

                                • detaro 178 days ago

                                  duplicate, please don't repost so quickly: https://news.ycombinator.com/item?id=22340720

                                  • jl2718 178 days ago

                                    I would have expected polymorphism with type hints. Oh well.

                                    • agumonkey 176 days ago

                                      next: type based dispatch

                                      nzxt^2: predicate based dispatch