Python: Regular Method And Static Method With Same Name
Solution 1:
While it's not strictly possible to do, as rightly pointed out, you could always "fake" it by redefining the method on instantiation, like this:
classYourClass(object):def__init__(self):
self.foo = self._instance_foo
@staticmethoddeffoo():
print "Static!"def_instance_foo(self):
print "Instance!"
which would produce the desired result:
>>>YourClass.foo()
Static!
>>>your_instance = YourClass()>>>your_instance.foo()
Instance!
Solution 2:
A similar question is here: override methods with same name in python programming
functions are looked up by name, so you are just redefining foo with an instance method. There is no such thing as an overloaded function in Python. You either write a new function with a separate name, or you provide the arguments in such a way that it can handle the logic for both.
In other words, you can't have a static version and an instance version of the same name. If you look at its vars
you'll see one foo
.
In [1]: classTest:
...: @staticmethod
...: deffoo():
...: print'static'
...: deffoo(self):
...: print'instance'
...:
In [2]: t = Test()
In [3]: t.foo()
instance
In [6]: vars(Test)
Out[6]: {'__doc__': None, '__module__': '__main__', 'foo': <function __main__.foo>}
Solution 3:
Because attribute lookup in Python is something within the programmer's control, this sort of thing is technically possible. If you put any value into writing code in a "pythonic" way (using the preferred conventions and idioms of the python community), it is very likely the wrong way to frame a problem / design. But if you know how descriptors can allow you to control attribute lookup, and how functions become bound functions (hint: functions are descriptors), you can accomplish code that is roughly what you want.
For a given name, there is only one object that will be looked up on a class, regardless of whether you are looking the name up on an instance of the class, or the class itself. Thus, the thing that you're looking up has to deal with the two cases, and dispatch appropriately.
(Note: this isn't exactly true; if an instance has a name in its attribute namespace that collides with one in the namespace of its class, the value on the instance will win in some circumstances. But even in those circumstances, it won't become a "bound method" in the way that you probably would wish it to.)
I don't recommend designing your program using a technique such as this, but the following will do roughly what you asked. Understanding how this works requires a relatively deep understanding of python as a language.
classStaticOrInstanceDescriptor(object):
def__get__(self, cls, inst):
if cls isNone:
return self.instance.__get__(self)
else:
return self.static
def__init__(self, static):
self.static = static
definstance(self, instance):
self.instance = instance
return self
classMyClass(object):
@StaticOrInstanceDescriptordeffoo():
return'static method' @foo.instancedeffoo(self):
return'public method'
obj = MyClass()
print(obj.foo())
print(MyClass.foo())
which does print out:
% python /tmp/sandbox.py
staticmethod
public method
Solution 4:
Ended up here from google so thought I would post my solution to this "problem"...
classTest():
deftest_method(self=None):
if self isNone:
print("static bit")
else:
print("instance bit")
This way you can use the method like a static method or like an instance method.
Solution 5:
When you try to call MyClass.foo()
, Python will complain since you did not pass the one required self
argument. @coderpatros's answer has the right idea, where we provide a default value for self
, so its no longer required. However, that won't work if there are additional arguments besides self
. Here's a function that can handle almost all types of method signatures:
import inspect
from functools import wraps
defclass_overload(cls, methods):
""" Add classmethod overloads to one or more instance methods """for name in methods:
func = getattr(cls, name)
# required positional arguments
pos_args = 1# start at one, as we assume "self" is positional_only
kwd_args = [] # [name:str, ...]
sig = iter(inspect.signature(func).parameters.values())
next(sig)
for s in sig:
if s.default is s.empty:
if s.kind == s.POSITIONAL_ONLY:
pos_args += 1continueelif s.kind == s.POSITIONAL_OR_KEYWORD:
kwd_args.append(s.name)
continuebreak @wraps(func)defoverloaded(*args, **kwargs):
# most common case: missing positional arg or 1st arg is not a cls instance
isclass = len(args) < pos_args ornotisinstance(args[0], cls)
# handle ambiguous signatures, func(self, arg:cls, *args, **kwargs);# check if missing required positional_or_keyword argifnot isclass:
for i inrange(len(args)-pos_args,len(kwd_args)):
if kwd_args[i] notin kwargs:
isclass = Truebreak# class methodif isclass:
return func(cls, *args, **kwargs)
# instance methodreturn func(*args, **kwargs)
setattr(cls, name, overloaded)
classFoo:
deffoo(self, *args, **kwargs):
isclass = self is Foo
print("foo {} method called".format(["instance","class"][isclass]))
class_overload(Foo, ["foo"])
Foo.foo() # "foo class method called"
Foo().foo() # "foo instance method called"
You can use the isclass
bool to implement the different logic for class vs instance method.
The class_overload
function is a bit beefy and will need to inspect the signature when the class is declared. But the actual logic in the runtime decorator (overloaded
) should be quite fast.
There's one signature that this solution won't work for: a method with an optional, first, positional argument of type Foo
. It's impossible to tell if we are calling the static or instance method just by the signature in this case. For example:
defbad_foo(self, other:Foo=None):
...
bad_foo(f) # f.bad_foo(None) or Foo.bad_foo(f) ???
Note, this solution may also report an incorrect isclass
value if you pass in incorrect arguments to the method (a programmer error, so may not be important to you).
We can get a possibly more robust solution by doing the reverse of this: first start with a classmethod, and then create an instance method overload of it. This is essentially the same idea as @Dologan's answer, though I think mine is a little less boilerplatey if you need to do this on several methods:
from types import MethodType
definstance_overload(self, methods):
""" Adds instance overloads for one or more classmethods"""for name in methods:
setattr(self, name, MethodType(getattr(self, name).__func__, self))
classFoo:
def__init__(self):
instance_overload(self, ["foo"])
@classmethoddeffoo(self, *args, **kwargs):
isclass = self is Foo
print("foo {} method called:".format(["instance","class"][isclass]))
Foo.foo() # "foo class method called"
Foo().foo() # "foo instance method called"
Not counting the code for class_overload
or instance_overload
, the code is equally succinct. Often signature introspection is touted as the "pythonic" way to do these kinds of things. But I think I'd recommend using the instance_method
solution instead; isclass
will be correct for any method signature, including cases where you call with incorrect arguments (a programmer error).
Post a Comment for "Python: Regular Method And Static Method With Same Name"