Question

In a package, I have isolated a set of functions/variables that work altogether in a class. As these functions did not require a specific context, they became class methods. They may use some external functions, here represented by the save_c function. But I can't get the following code to work:

def save_c(v):
    print("c :" + v)


class Writer(object):

    @classmethod
    def save(cls, t, v):
        cls.writers[t](v)


    @classmethod
    def save_a(cls, v):
        print("a :" + v)


    @classmethod
    def save_b(cls, v):
        print("b :" + v)

    writers = [save_a, save_b, save_c]


if __name__ == "__main__":
    Writer.save(1, [1, 2, 3])

Previous code results into the following error:

TypeError: 'classmethod' object is not callable

Does anybody know if what I'm trying to do is reachable? Or am I obliged to use regular methods and instanciate a Writer wherever I need to?

Was it helpful?

Solution

You could also make it work by adding a wrapper class that is smarter about what calling whatever it's dealing with.

Since you mentioned in a comment that you're going to need to make the call to one of thewritersmultiple places, I've updated my answer to address this by adding a nested wrapper class to encapsulate the logic of calling the various callable types in one spot. This is a classic example of using OOP to apply the DRY principle.

def save_c(v):
    print("c :" + v)

class Writer(object):
    @classmethod
    def save(cls, t, v):
        cls.writers[t](cls, v)

    @classmethod
    def save_a(cls, v):
        print("a :" + v)

    @staticmethod
    def save_b(v):
        print("b :" + v)

    def _wrapper(writer):
        if isinstance(writer, classmethod):
            return type('ClassmethodWrapper', (object,), {'__call__':
                        lambda self, cls, v: writer.__func__(cls, str(v))})()
        elif isinstance(writer, staticmethod):
            return type('StaticmethodWrapper', (object,), {'__call__':
                        lambda self, cls, v: writer.__func__(str(v))})()
        else:
            return type('VanillaWrapper', (object,), {'__call__':
                        lambda self, cls, v: writer(str(v))})()

    writers = list(map(_wrapper, (save_a, save_b, save_c)))

if __name__ == "__main__":
    Writer.save(0, [1, 2, 3])  # -> a :[1, 2, 3]
    Writer.save(1, [4, 5, 6])  # -> b :[4, 5, 6]
    Writer.save(2, [7, 8, 9])  # -> c :[7, 8, 9]

OTHER TIPS

In fact, the inner writers attribute has to reference the __func__ attribute of the class methods. And yes, we need here to use staticmethod decorators instead of classmethod ones in order to respect the same signature used for external calls. The following code works well:

def save_c(v):
    print("c :" + v)


class Writer(object):

    @staticmethod
    def save(t, v):
        Writer.writers[t](str(v))


    @staticmethod
    def save_a(v):
        print("a :" + v)


    @staticmethod
    def save_b(v):
        print("b :" + v)

    writers = [save_a.__func__, save_b.__func__, save_c]


if __name__ == "__main__":
    Writer.save(0, [1, 2, 3])
    Writer.save(1, [1, 2, 3])
    Writer.save(2, [1, 2, 3])

It sounds like you meant to use @staticmethod rather than @classmethod.

For more information, see What is the difference between @staticmethod and @classmethod in Python?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top