How can I use a static method as a default paramet

2020-04-05 09:08发布

问题:

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.

回答1:

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.