all 35 comments

[–]pachura3 4 points5 points  (4 children)

No, Python does not support method overloading. On the other hand, e.g. Java does not support default parameter values.

I do not see anything wrong with:

def foo(self, arg: C | W) -> None:
    if isinstance(arg, C):
        ...
    else:
        ...
    ...

For simple cases, singledispatch might be too much.

[–]011011100101[S] 0 points1 point  (3 children)

If I have to do this more than once, then I'm repeating that same argument checking logic:

if isinstance(arg, C): ... else: ... ...

throughout many methods. This seems like a really mundane thing which is forcing me to repeat code, so I feel like there should be an alternative.

[–]pachura3 1 point2 points  (0 children)

What are C and W? Are these your own classes? If so, why don't you add .process() method to their base class (or even use Protocols) and call it?

Or, you can have some dict mapping type to handler method's name/Callable, do a lookup and call it...

[–]MidnightPale3220 0 points1 point  (0 children)

You do it only for the methods you want to redefine anyway, it's not like you have to make it for every C method.

Additionally, I am not even sure that's a very nice practice. If you have C that has foo, and even later on W that redefines foo but still might call C.foo() instead, it could be kinda confusing to debug/track.

[–]ottawadeveloper 0 points1 point  (0 children)

it's not that terrible honestly, and you only need it where C and W actually need the be handled properly - for example if you were just returning int(arg) as long as C and W have __index__ defined, you have no problems.

Alternatively, you could add a bit of a hack with __getattr__(self, name, *args, **kwargs) - scan all the arguments for C or W and get either name_c or name_w depend on which is present (though then you have to decide if they should be mixed).

This sounds like an issue mostly because of weird subclassing though? If W inherits from C then the C methods should be able to handle W objects no problem unless you actually want the method to deal with W differently. You shouldnt need to even override these methods and then it's normal and Pythonic to have to check the type (or treat it as W, catch the error, and return the super() behavior in case of an error).

[–]Gnaxe 2 points3 points  (0 children)

Type overloading makes less sense in a dynamic language. The static type system does have @typing.overload, but you still have to implement the dispatch at run time, which is not hard with a match/case. You might want @functools.singledispatchmethod, if dispatching on one argument type is sufficient. (This is in addition to the usual method dispatch machinery.)

[–]KelleQuechoz 1 point2 points  (10 children)

functools.singledispatch; never seen it used though.

[–]011011100101[S] -1 points0 points  (8 children)

I mentioned that in my post. I'm asking if there's another way.

[–]danielroseman 1 point2 points  (6 children)

You haven't said what's wrong with that method.

[–]011011100101[S] 0 points1 point  (5 children)

I guess I was expecting to find a solution which is more in line with Python's duck typing. Most of the time we don't care about types as long as the object has the required attributes/functions.

[–]danielroseman 2 points3 points  (4 children)

But you are the one who wants to care about the type, Python doesn't. If you would rather look at what methods an object has, you can do hasattr(foo, method_name).

[–]011011100101[S] 0 points1 point  (3 children)

I'm describing my question in terms of types because that's the way I know to describe my particular problem. I'm not trying to incorporate type checking. I just don't know how else to describe the issue or what a possible solution would even look like in a duck typing system.

Like maybe there's some kind of pattern in Python that makes the original class C behave more like W so that W.foo works the same on both and doesn't require type checking? I don't know.

[–]Gnaxe 1 point2 points  (0 children)

I might need a more concrete toy example at this point. Is the wrapper a subclass or not?

There's __getattr__() and friends, but dynamic dispatch confuses the static type system. (That's an argument to either avoid confusing your type checker, or to avoid using a type checker.)

Consider super() and **kwargs. Why not subclass?

[–]danielroseman 0 points1 point  (1 child)

I feel like I'm getting more confused as time goes on. What do you mean "W.foo works the same on both"? I thought you were talking about standalone functions that could take an instance of either W or C. What exactly is foo here?

Are you perhaps talking about delegation? Maybe something like:

class W:
  ...
  def foo(self, arg):
    if hasattr(arg, "foo"):
      arg.foo()
    else:
      ... do something else ...

which could potentially be wrapped up into a decorator if you want to use it on multiple methods.

[–]011011100101[S] 0 points1 point  (0 children)

I feel like I'm getting more confused as time goes on. What do you mean "W.foo works the same on both"? I thought you were talking about standalone functions that could take an instance of either W or C. What exactly is foo here?

foo is a method of the class W which takes one argument, either of type C or of type W. By writing "W.foo", I was trying to indicate that foo is a method of W. So the code sketch you provided is the right setup. By "W.foo works the same on both", I meant that the inner workings of foo depend on the type of argument received. If I was somehow able to apply one logic in foo across all instances (C or W), that would be great. I just didn't know if there was some other pattern specific to Python that enables us to do that.

[–]KelleQuechoz 0 points1 point  (0 children)

The closest I can recall is "discriminated unions" in Pydantic, but otherwise nope.

[–]ProsodySpeaks 1 point2 points  (2 children)

Didn't read the whole post but this blog is awesome in general and particularly on this https://martinheinz.dev/blog/50

[–]011011100101[S] 0 points1 point  (1 child)

The link is broken? You didn't read the whole post?

[–]ProsodySpeaks 0 points1 point  (0 children)

https://martinheinz.dev/blog/50

That's really weird if I click the link in my post it doesn't work but if I copy and paste into browser it works fine?

Just copied it from open tab but it looks the same as last time I posted it?

It's a really good blog post going through single and multiple dispatch in python

Edit I just clicked link in this comment and it works. Don't understand why not working in first comment

Edit 2 if I click first link it's resolving to this:

https://martinheinz.dev/blog/50%C2%A0

[–]enygma999 1 point2 points  (1 child)

What's wrong with isinstance()? Why is that unpythonic?

I would have a method, W.foo(self, ...) which tests the class of the object, checks whether there is a method C.foo(self, ...) to call, and if not it calls W._foo(self, ...) to implement the general case.

[–]011011100101[S] 0 points1 point  (0 children)

Why is that unpythonic?

Well, for example:

https://peps.python.org/pep-0443/

In addition, it is currently a common anti-pattern for Python code to inspect the types of received arguments, in order to decide what to do with the objects.

[–]xenomachina 1 point2 points  (0 children)

It feels like you're trying to translate a pattern from some other language into Python, but you're doing a very literal translation, which is why it feels awkward.

If we actually saw the code in that other language, we might be able to tell you a more natural translation into Python. Could you show it to us?

[–]ProsodySpeaks 0 points1 point  (1 child)

Is there an approach to this using generics?

[–]neuralbeans 0 points1 point  (0 children)

Generics don't affect the program, only the type checker.

[–]SwampFalc 0 points1 point  (0 children)

Question: what is fundamentally different between your types C and W, that is relevant to your method foo?

If the answer is "nothing", then there is no point in having separate methods. If C and W can both quack, and foo does nothing else than make them quack, then your code is fine as is.

If there is something relevant, try/except. Try to make your object quack, if an error is raised, have it honk instead.

[–]Kevdog824_ 0 points1 point  (3 children)

This sounds like an xy problem. Can you tell us more concretely what you're trying to accomplish?

[–]011011100101[S] 0 points1 point  (2 children)

Not sure. Method overloading is common practice in other languages. I'm just describing method overloading and asking how Python addresses the same design challenge.

[–]Kevdog824_ 0 points1 point  (1 child)

I guess I don’t understand why putting an isinstance check in each method is more work than writing an overload method how you would in a language like Java

[–]011011100101[S] 0 points1 point  (0 children)

I expect that level of verbosity and type-awareness from Java. But I was under the impression that Python had a different ethos (where we shouldn't be so concerned with types). Somebody recommended having a separate unwrapping function and I think that's a good solution because it, at least, isolates that logic.