I'm trying to call an external function via a class variable. The following is a simplification of my real code:
def func(arg):
print(arg)
class MyClass(object):
func_ref = None
@classmethod
def setUpClass(cls):
#MyClass.func_ref = func
cls.func_ref = func
@staticmethod
def func_override(arg):
print("override printing arg...")
MyClass.func_ref(arg)
if __name__ == "__main__":
print(type(func))
print(type(MyClass.func_ref))
MyClass.setUpClass()
print(type(MyClass.func_ref))
MyClass.func_override("hello!")
The above code produces the following output:
[~]$ python tmp.py
<type 'function'>
<type 'NoneType'>
<type 'instancemethod'>
override printing arg...
Traceback (most recent call last):
File "tmp.py", line 20, in <module>
MyClass.func_override("hello!")
TypeError: func_override() takes exactly 2 arguments (1 given)
The situation seems to be unchanged if I use MyClass
in place of cls
within the classmethod setUpClass()
.
I would expect the type of MyClass.func_ref
to be function
after the assignment in setUpClass()
which explains the TypeError
I get when I try to call it. Why is the type of func_ref
being changed to instancemethod
when the value I assigned to it is of type function
?
This only seems to be an issue in Python 2. Python 3 behaves as I would expect.
How do I get calls to the static method MyClass.func_override()
to call func()
?
UPDATE
I was able to get the above to work by applying the following patch:
@@ -14,7 +14,7 @@ class MyClass(object):
def func_override(arg):
print("override printing arg...")
func(arg)
- MyClass.func_ref.__func__(arg)
+ MyClass.func_ref(arg)
if __name__ == "__main__":
print(type(func))
While the above works, its not at all clear to me why I needed to do this. I still don't understand why the type of func_ref
ends up an instancemethod
when I assigned to it a value of type function
.
Just put the function through a staticmethod
as follows:
@classmethod
def setUpClass(cls):
#MyClass.func_ref = func
cls.func_ref = staticmethod(func)
There's no need to play with @-based decorators in this case as you want to modify how the method is bound to MyClass, not the general definition of func
.
Why is this necessary? Because, when you assign a method to class, Python assumes you'll want to refer to an instance (via self
) or the class (via cls
). self
, unlike this
in JS, is only a naming convention, so when it sees arg
it assumes it got an instance, but you passed a string in your call.
So, as as Python cares, you might have as well have written def func(self):
. Which is why the message says unbound method func() must be called with MyClass 👉instance👈 as first argument
.
staticmethod
means, "please leave this alone and don't assume an instance or a class in the first variable".
You can even dispense with the setUpClass entirely
:
class MyClass(object):
func_ref = staticmethod(func)
BTW: In 2021, 16 months past EOL, Python 2.7 has all the subtle fagrance of moldy gym socks. Except less safe, virologically-speaking.