23
votes

I have this code:

class SomeClass:
    @classmethod
    def func1(cls,arg1):
        #---Do Something---
    @classmethod
    def func2(cls,arg1):
        #---Do Something---

    # A 'function map' that has function name as its keys and the above function
    # objects as values
    func_map={'func1':func1,'func2':func2}

    @classmethod
    def func3(cls,arg1):
        # following is a dict(created by reading a config file) that
        # contains func names as keys and boolean as values that tells
        # the program whether or not to run that function
        global funcList
        for func in funcList:
            if funcList[func]==True:
                cls.func_map[func](arg1)        #TROUBLING PART!!!

    if _name__='main'
        SomeClass.func3('Argumentus-Primus')

When I run this I keep getting the error:

Exception TypeError: "'classmethod' object is not callable"

I am unable to figure out what is wrong with this and would appreciate your help.

6
Sorry for not adding the error description. Edited the answer (and thats all I know about this error). And If I had the answer to the problem why would I even post this question ? :S I had looked around for this error and found that people were getting these errors as they weren't using the code correctly e.g. on a thread a guy was accessing an element of a dictionary like someDict(key) and was getting similar error.. but I believe that I am making a call to a function (which should be callable, right?). Correct me if I am wrong.. - user1126425
When you get an error, copy paste the whole stack trace. It will tell you useful things like what line the error occurs on. - Gareth Latty
Lattye, Exception TypeError: "'classmethod' object is not callable" in 'pyadecg.__wrap_py_func' ignored this is all I am able to see in the stacktrace.. Thanks for your help - user1126425
@Lattyware wants you to accept the best answers to your previous questions. An important aspect of stack overflow. Read he faq to learn more. - David Heffernan
@user1126425: It's helpful, when people ask you a question like "What is the error?" to update your question with the information rather than posting it in the comments. That way everyone can see it immmediately, and you'll be more likely to get a good answer. - Joel Cornett

6 Answers

17
votes

You can't create references to classmethods until the class has been defined. You'll have to move it out of the class definition. However using a global function map to decide what gets run is really awkward. If you described what you are trying to do with this, we could probably suggest a better solution.

class SomeClass(object):
    @classmethod
    def func1(cls, arg1):
        print("Called func1({})".format(arg1))

    @classmethod
    def func2(cls, arg1):
        print("Call func2({})".format(arg1))

    @classmethod
    def func3(cls, arg1):
        for fnName,do in funcList.iteritems():
            if do:
                try:
                    cls.func_map[fnName](arg1)
                except KeyError:
                    print("Don't know function '{}'".format(fnName))

# can't create function map until class has been created
SomeClass.func_map = {
    'func1': SomeClass.func1,
    'func2': SomeClass.func2
}

if __name__=='__main__':
    funcList = {'func1':True, 'func2':False}
    SomeClass.func3('Argumentus-Primus')
13
votes

I discovered something tonight that will be helpful here: We can unwrap magic staticmethod and classmethod objects via: getattr(func, '__func__')

How did I find this information? Using JetBrains' PyCharm (I don't know about other Python IDEs), I viewed the source code for @staticmethod and @classmethod. Both classes define the attribute __func__.

"The rest is left as an exercise for the reader."

2
votes

All other answers suggest to add some code outside the class SomeClass definition. It may be ok in some cases, but in my case it was very inconvenient. I really wanted to keep the func_map inside the class.

I suggest the following approach. Use not a class variable, but one more classmethod:

class SomeClass:
    # ...

    @classmethod
    def get_func_map(cls):
        return {'func1': cls.func1, 'func2': cls.func2}

    @classmethod
    def func3(cls, arg1):
        # .....
        cls.get_func_map()[func_name](arg1)

Of course you should modify this code so that a new dictionary not be constructed each time you call the get_func_map method. It's easy, I did not do myself it to keep the example small and clear.

Tested on python 3.6

-1
votes

Add self as an argument for each method within the class.

Also

    if _name__='main'
    SomeClass.func3('Argumentus-Primus')

should look like this:

if __name__=='__main__':
    SomeClass.func3('Argumentus-Primus')

and should not be within the body of the class.

-1
votes

Here's a bad way to do it:

def func3(cls,arg1):
    global funcList
    for func in funcList:
        if funcList[func]==True:
            eval(f'SomeClass.{func}')(arg1)

Only works if func is the name of the function. That being said, do not use this method, because you're taking user input. It would be very easy to inject nasty code in the call. That being said, this does work.

-2
votes

You may need to try a static method.

@staticmethod
def function():...

Static methods do not pass the class as an implicit first argument.