amigcamel
amigcamel

Reputation: 2009

Override the value of a static variable while using decorator

I've created a class like the following:

class Simon:

    name = 'Simon'

    @classmethod
    def says(cls, sentence):
        return '%s says: %s' % (cls.name, sentence)

If I want Simon say sit down, I can do this:

>>> Simon.says('sit down')
'Simon says: sit down'

To substitute Simon with another name, say, Eva, I can subclass it like this:

class Eva(Simon):

    name = 'Eva'

And the result:

>>> Eva.says('stand up')
'Eva says: stand up'

Now I want to change says to said by creating a decorator called to_past_tense:

class Eva(Simon):

    name = 'Eva'

    def to_past_tense(self, func):
        def wrapper(*arg, **kw):
            present_tense = func(*arg, **kw)
            past_tense = present_tense.replace('says', 'said')
            return past_tense
        return wrapper

    @classmethod
    def says(cls, *arg, **kw):
        return cls.to_past_tense(cls, Simon.says)(*arg, **kw)

If I do this:

>>> Eva.says('stand up')

what I'm expecting is this:

'Eva said: stand up'

but in fact I got this

'Simon said: stand up'

How can I override the value?

And, please, help me improve the title if it isn't precise and clear, thank you!

Upvotes: 0

Views: 2420

Answers (2)

Martijn Pieters
Martijn Pieters

Reputation: 1123400

You are using Simon.says, retrieving a bound class.

If you wanted to get the overridden class method but have it bind to the current class, use a super() proxy object:

@classmethod
def says(cls, *arg, **kw):
    return cls.to_past_tense(cls, super().says)(*arg, **kw)

The super() object will search the MRO of the class, find the says method on Simon, then bind it to the cls object for you, so the cls.name value is still looked up from Eva:

>>> class Simon:
...     name = 'Simon'
...     @classmethod
...     def says(cls, sentence):
...         return '%s says: %s' % (cls.name, sentence)
... 
>>> class Eva(Simon):
...     name = 'Eva'
...     def to_past_tense(self, func):
...         def wrapper(*arg, **kw):
...             present_tense = func(*arg, **kw)
...             past_tense = present_tense.replace('says', 'said')
...             return past_tense
...         return wrapper
...     @classmethod
...     def says(cls, *arg, **kw):
...         return cls.to_past_tense(cls, super().says)(*arg, **kw)
... 
>>> Eva.says('Hello')
'Eva said: Hello'

Upvotes: 2

Daniel Roseman
Daniel Roseman

Reputation: 599788

I'm not entirely sure what you are trying to achieve here, but I suppose you need to pass cls.says to the method:

return cls.to_past_tense(cls, cls.says)(*arg, **kw)

Upvotes: 0

Related Questions