mtanti mtanti - 26 days ago 8
Python Question

In Python, how can I use a static method as a default parameter for the strategy design pattern?

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.

Answer

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.

Comments