2

I hoped this would work:

class A(object):

    @classmethod
    def do_it(cls, spam, eggs):
        if spam in A.ways_to_do_it:
            A.ways_to_do_it[spam](eggs)
        super(A, cls).do_it(spam, eggs)

    @staticmethod
    def do_it_somehow(eggs):
        ...

    @staticmethod
    def do_it_another_way(eggs):
        ...

    ways_to_do_it = {
        'somehow': do_it_somehow,
        'another_way': do_it_another_way,
    }

But it raises TypeError: 'staticmethod' object is not callable. I wanted to inspect staticmethod to find out something, but it's a built in. I hope that it's clear what I want to achieve here.

Do you have any ideas how to do it nicely? I know that making these @staticmethods global would solve the problem, but that would be a mess in my module.

P. S. do_it_somehow and do_it_another_way will be called from A.do_it only.

Bartosz Marcinkowski
  • 6,651
  • 4
  • 39
  • 69
  • 1
    As for those trying to mark this question as duplicate: I think this question is a more general case than the referenced one - which is a corner case depicting the same problem the OP had here. – jsbueno Feb 11 '14 at 12:55
  • @jsbueno: The more general case is also covered in duplicates. – Martijn Pieters Feb 11 '14 at 15:46

2 Answers2

4

Python has a concept of descriptor objects, which are objects having at least the __get__ method. These objects behave differently when retrieved from a class, or an instance, as attributes (their __get__ method is called.)

The @staticmethod decorator transforms the subsequent function declaration in a descriptor that has the static method behavior - but said behavior will only be available when retrieving the object as a class attribute. The code above makes a direct reference to the object as it is, instead.

Since it is you also have other (class) methods for your dictionary to work, you'd better retrieve yoiur desired methods after class creation, so that each method is retrieved via descriptor protocol:

class A(object):

    @classmethod
    def do_it(cls, spam, eggs):
        if spam in A.ways_to_do_it:
            A.ways_to_do_it[spam](eggs)
        super(A, cls).do_it(spam, eggs)

    @staticmetod
    def do_it_somehow(eggs):
        ...

    @staticmetod
    def do_it_another_way(eggs):
        ...

A.ways_to_do_it = {
        'somehow': A.do_it_somehow,
        'another_way': A.do_it_another_way,
    }

You could retrieve your static method before class creation, calling do_it_another_way.__get__(object, None) - since it does not need the class reference (but needs a valid class as first parameter nonetheless). But if you want your dictionary to also point to the classmethods defined, they definitely have to be fetched after class creation: there is no way Python can create you a "bound class method" before class creation.

Creating other direct references to the class/static methods inside the class body works:

class A(object):
   @staticmethod
   def foo(): ...

   bar = foo

because in this way, bar would also be fetched through the descriptor protocol. But since you have an indirect dictionary, you have to take care of the descriptor __get__ call yourself.

Check http://docs.python.org/2/reference/datamodel.html#implementing-descriptors for more information. (This is what the classmethod, staticmethod decorators do, since you also wanted to know).

jsbueno
  • 99,910
  • 10
  • 151
  • 209
  • 1
    And in Python 3, _all_ (non-built-in) functions are descriptor objects. `class A: pass; a = lambda self: id(self); A.a = a` will result in `a` being a function on the class object and a bound method on any instances (even ones created before the function was set as an attribute of the class, assuming they didn't shadow the function already), and you can even get the equivalent to JavaScript's `bind` with explicit usage of `__get__`: `anA = A(); aa = a.__get__(anA); aa()`. – JAB Feb 11 '14 at 12:48
2

Try it like this:

class A(object):

    @classmethod
    def do_it(cls, spam, eggs):
        if spam in A.ways_to_do_it:
            A.ways_to_do_it[spam](eggs)
        super(A, cls).do_it(spam, eggs)

    @staticmethod
    def do_it_somehow(eggs):
        ...

    @staticmethod
    def do_it_another_way(eggs):
        ...

A.ways_to_do_it = {
    'somehow': A.do_it_somehow,
    'another_way': A.do_it_another_way,
}

It gets tricky to reference a class before you've completed construction of the class, so it's simplest to add stuff to it right after the end of the class definition.

JAB
  • 20,783
  • 6
  • 71
  • 80
  • Your answer makes it work, and describes the highlevel of why it works for classmethods. But staticmethods would not need a class reference, so they could be referenced before the class is ready. Check my answer to see what goes on. – jsbueno Feb 11 '14 at 12:45