25

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.

miradulo
  • 28,857
  • 6
  • 80
  • 93
user1126425
  • 2,385
  • 4
  • 18
  • 17
  • 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 Jun 15 '12 at 22:01
  • 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 Jun 15 '12 at 22:04
  • 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 Jun 15 '12 at 22:08
  • 1
    @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 Jun 15 '12 at 22:12
  • 1
    @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 Jun 15 '12 at 22:59
  • The code in the question does not produce that error, and the answers all address different problems (there's at least 3 different issues here), so this question is beyond redemption. Voting to close, since it's not possible to edit the question into shape without invalidating any of the existing answers. – Aran-Fey Mar 23 '19 at 19:41

6 Answers6

19

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')
Jeff Mercado
  • 129,526
  • 32
  • 251
  • 272
Hugh Bothwell
  • 55,315
  • 8
  • 84
  • 99
  • 3
    Thanks for the reply Hugh. 1. This is really confusing to me. Why doesn't python stop me right at that spot when I have refrenced them in dict ? It only complained when I was trying to call it. 2. This global funcList was created by reading a configuration file (reading configuration is being done in a different module), so I couldn't think of another way... 3. I am trying to get an input configuration file from the user which would tell the program what functions to run and while doing so want to maintain clear separation between different parts of code (so as to make it extensible easily) – user1126425 Jun 15 '12 at 22:36
15

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."

kevinarpe
  • 20,319
  • 26
  • 127
  • 154
  • 5
    Those are just stubs created for pycharm so that code completion and introspection work. In reality, those decorators are builtins that are written in C – Brendan Abel May 11 '16 at 22:43
  • 4
    You can use `func.__func__` directly, no need to `getattr(func, '__func__')` – MestreLion Feb 11 '21 at 02:43
5

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

lesnik
  • 2,507
  • 2
  • 25
  • 24
  • Thanks. Although I don't completely understand why, but I think your answer, as well this [similar conversation (about classmethod's cousin, staticmethod, though)](https://groups.google.com/forum/#!topic/comp.lang.python/s2mqUdNnUgw), demonstrates that assigning a classmethod into a variable would somehow mess up with its `self` or `cls` handling magic. Just FYI. – RayLuo May 13 '19 at 20:42
-2

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.

ulu5
  • 439
  • 7
  • 11
  • 1
    these are all class methods not instance methods.. – user1126425 Jun 15 '12 at 21:59
  • I see. Why don't you just use switch statement instead of a map? Or you could use getattr to call the method. That is if you made it an instance method. – ulu5 Jun 15 '12 at 22:15
  • Was trying to be elegant in my code... and to write it so that it can be easily extended :) I don't want to use it as instance methods... because it makes changes to certain resources that are used by other modules that need to view them in a consistent manner.. – user1126425 Jun 15 '12 at 22:46
-2

You may need to try a static method.

@staticmethod
def function():...

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

awesoon
  • 32,469
  • 11
  • 74
  • 99
John
  • 1
  • 2
    Interesting, but it just leads to `TypeError: 'staticmethod' object is not callable` instead. – starfry Sep 28 '17 at 09:00
-2

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.

Rob Rose
  • 1,806
  • 22
  • 41