4

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.

2 Answers 2

1

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you! The "Python assumes you'll want..." is exactly what I was looking for. I'd up 2.7's fragrance to moldy gym socks left in a hot car. But even with the stench, 2.7 is still mission critical for some large and expensive systems. Thanks again!
0

When func_ref is called, it's expecting a self argument, just like any other normal (instance) class method (see this question and answers for discussions why). You can either add a self argument to func or make func a static method:

@staticmethod
def func(arg):
    print(arg)

>>> MyClass.setUpClass()
>>> MyClass.func_override("hello!")
override printing arg...
hello!

Note that in either case func is now not normally callable as a regular function:

>>> func('what does this do?')
TypeError: 'staticmethod' object is not callable

If you need func to be usable as a regular function, you can wrap it with another, qualifying function and use the wrapper in MyClass:

def func(arg):
    print(arg)

@staticmethod
def func_wrapper(arg):
    func(arg)

class MyClass(object):
    @classmethod
    def setUpClass(cls):
        cls.func_ref = func_wrapper  # use wrapper function

>>> MyClass.setUpClass()
>>> MyClass.func_override("success!")
override printing arg...
success!

3 Comments

Thanks! I understand that func_ref is now expecting a self argument, but why? I assigned it the value of a "normal" function - not a method. I want func to remain a "norrmal' function un-associated with a class (as opposed to any kind of method, static or otherwise)
@awm129 Updated to address your comment
Thanks again. The wrapper could work, but I really want to call func via my class variable. I figured out something that works, but it isn't at all clear to me why it works.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.