pythonoopoverloadingclass-methodinstance-methods

How to define one method as both instance method and class method (sharing the same name), each with different arguments?


In essence, I'm trying to accomplish the below but where bar and baz have the same handle (e.g. just bar) rather than being two differently-named functions.

Definitions
import numpy as np
foo = np.add # For example; real function will be different in every instance

class MyClass(object):
    def __init__(self, arg1, arg2):
        self.arg1 = arg1
        self.arg2 = arg2

    def bar(self):
        return foo(self.arg1, self.arg2)

    @classmethod
    def baz(cls, arg1, arg2):
        return foo(arg1, arg2)
Example Usage
a, b = 1, 2
mine = MyClass(a, b)
print(mine.bar())
>>> 3
x, y = 3, 4
print(MyClass.baz(x, y))
>>> 7

I'm trying to do this for intelligibility's sake. The real function names are long with many underscores, and making two slightly differently-named functions (e.g. prepending one function name with a _) for every function to which I want to do this is only going to confuse an already complicated situation.

The function will mostly be used internally, but I'd like the ability to call the function in a static context with ad hoc parameters that might not necessarily match the instance variables of a given object of MyClass (in fact, I would only call it this way if they didn't match). I'm using @classmethod rather than @staticmethod because the real functions use some internal class variables.

I've already tried simply implementing the above with bar for both function names, but as expected, the instance method has been overridden by the class method.

I saw some answers to similar SO posts that use Descriptors, but I was hoping there might be a more elegant solution.

Any wise Python wizards out here have advice?


Solution

  • You can let self take a default argument as well, so that you can distinguish between mine.bar() and MyClass.bar(). The price is that the other two arguments must be keyword arguments.

    class MyClass:
    
        _sentinel = object()
    
        def bar(self=None, *, arg1=_sentinel, arg2=_sentinel):
            if self is not None:
                if arg1 is _sentinel:
                    arg1 = self.arg1
                if arg2 is _sentinel:
                    arg2 = self.arg2
            else:
                if arg1 is _sentinel:
                    raise ValueError("Missing required arg1")
                if arg2 is _sentinel:
                    raise ValueError("Missing required arg2")
    
            return foo(arg1, arg2)
    
    
    mine.bar()  # self is mine, arg1 is _sentinel, arg2 is _sentinel
    
    MyClass.bar(arg1=3, arg2=4)