9

I want to make a class that uses a strategy design pattern similar to this:

class C:

    @staticmethod
    def default_concrete_strategy():
        print("default")

    @staticmethod
    def other_concrete_strategy():
        print("other")

    def __init__(self, strategy=C.default_concrete_strategy):
        self.strategy = strategy

    def execute(self):
        self.strategy()

This gives the error:

NameError: name 'C' is not defined

Replacing strategy=C.default_concrete_strategy with strategy=default_concrete_strategy will work but, left as default, the strategy instance variable will be a static method object rather than a callable method.

TypeError: 'staticmethod' object is not callable

It will work if I remove the @staticmethod decorator, but is there some other way? I want the default parameter to be self documented so that others will immediately see an example of how to include a strategy.

Also, is there a better way to expose strategies rather than as static methods? I don't think that implementing full classes makes sense here.

martineau
  • 119,623
  • 25
  • 170
  • 301
mtanti
  • 794
  • 9
  • 25
  • 1
    The strategy pattern is mostly useless in python. Since you have functions as first class objects you can just pass functions around. – Bakuriu Feb 10 '14 at 08:27
  • @Bakuriu Well as you can see the strategy is a first class object function. I think this is still called a strategy pattern no? – mtanti Feb 10 '14 at 08:31
  • Yes, but the strategy pattern was mostly invented in languages that do not allow functions to be passed around. What I mean is that in 99% of the use cases of your class you can simply pass the function directly and obtain the same results with less complexity. – Bakuriu Feb 10 '14 at 08:34
  • @Bakuriu can you please give an example of how this can be done? – mtanti Feb 10 '14 at 08:35
  • Also related to question [_Calling class staticmethod within the class body?_](http://stackoverflow.com/questions/12718187/calling-class-staticmethod-within-the-class-body) – martineau Feb 10 '14 at 10:55

1 Answers1

16

No, you cannot, because the class definition has not yet completed running so the class name doesn't exist yet in the current namespace.

You can use the function object directly:

class C:    
    @staticmethod
    def default_concrete_strategy():
        print("default")

    @staticmethod
    def other_concrete_strategy():
        print("other")

    def __init__(self, strategy=default_concrete_strategy.__func__):
        self.strategy = strategy

C doesn't exist yet when the methods are being defined, so you refer to default_concrete_strategy by the local name. .__func__ unwraps the staticmethod descriptor to access the underlying original function (a staticmethod descriptor is not itself callable).

Another approach would be to use a sentinel default; None would work fine here since all normal values for strategy are static functions:

class C:    
    @staticmethod
    def default_concrete_strategy():
        print("default")

    @staticmethod
    def other_concrete_strategy():
        print("other")

    def __init__(self, strategy=None):
        if strategy is None:
            strategy = self.default_concrete_strategy
        self.strategy = strategy

Since this retrieves default_concrete_strategy from self the descriptor protocol is invoked and the (unbound) function is returned by the staticmethod descriptor itself, well after the class definition has completed.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Or he could first define `default_concrete_strategy` *without* decorating it, and then decorate it after the definition of `__init__` (as decorators were used before 2.4), although this would be quite cumbersome. – Bakuriu Feb 10 '14 at 08:29
  • @Bakuriu: exactly; much easier to just unwrap. – Martijn Pieters Feb 10 '14 at 08:30
  • It's a shame if this is the only way. It would have been nice to see an example directly in the code of how to pass a parameter. @Bakuriu would decorating a method in the constructor be helpful? – mtanti Feb 10 '14 at 08:30
  • 1
    @mtanti No. you have to put the `@staticmethod` in the class. I meant something like `def method():pass; def __init__(self, func=method):pass; method = staticmethod(method)`. However I must say that I'd prefer simply use `None` as default value and check for it in the `__init__`. – Bakuriu Feb 10 '14 at 08:33
  • @mtanti: no, because descriptors need to be set on objects **on the class** not on the instance. – Martijn Pieters Feb 10 '14 at 08:33
  • 1
    @mtanti: it is not the only way, but it is the most readable way. The alternative is to apply the decorator 'by hand'; define `default_concrete_strategy` **without** the `@staticmethod` line, then after you defined the `__init__` method, use `default_concrete_strategy = staticmethod(default_concrete_strategy)`. – Martijn Pieters Feb 10 '14 at 08:34
  • @Bakuriu I didn't know you could decorate a method like that. Interesting. – mtanti Feb 10 '14 at 08:38
  • @mtanti: `@expression` followed by a class or function definition is nothing more than syntactic sugar for `name_of_obj = expression(name_of_obj)` *after* the class or function definition has completed. – Martijn Pieters Feb 10 '14 at 08:40
  • I don't think it's made clear in the answer but you have to **place the default_concrete_strategy staticmethod ABOVE the function you would like to execute the method in as parameter**, in this case the `__init__` function. Great tip otherwise thanks! Used the `.__func__` solution for a non-init method for a class I was creating. – decoder247 Oct 01 '20 at 10:50
  • @decoder247 that’s because function defaults are executed when the function is created, not when it is being called. So the name `default_concrete_strategy` must exist in the class scope when `def __init__(...):` is being executed to create the function object. – Martijn Pieters Oct 01 '20 at 22:50