Please don’t get me wrong. I don’t want to convince you to use decorators. I consider this thread merely as a discussion of different solutions. I will throw in a third option at the bottom of this post. No solution is a “one-size-fits-all”.
–1--
You seem to be afraid that decorators can be used only for all instances of a class. This is not true. If you look at my second post you will see that the method Cat.meow dow NOT have a decorator; I add the decorator in the line after creating an instance of the class Cat.
The following two lines from your my example do almost the same. They replace the original Cat::meow-method with something else, for one instance and only one instance
aCat.meow = self.myMeow # wrapper
aCat.meow = intercept(cat.meow) # decorator
So far both techniques are equal. Yours is more lightweight, and thus prefered.
–2--
Let me show you some situations where decorators might be more handy. I pick up the sichness aspect you mentioned earlier. The player should get nausea when opening a door, when smashing a door, and when drinking a potion.
With your technique:
class Door:
def open(self): pass
def smash(self): pass
class Potion:
def drink(self): pass
# -----
def myOpen(self):
# globals.player.set_nausea()
self.openInDoor()
def mySmash(self):
# globals.player.set_nausea()
self.smashInDoor()
def myDrink(self):
# globals.player.set_nausea()
self.drinkInPotion()
# -----
aDoor = Door()
aDoor.openInDoor = aDoor.open
aDoor.open = myOpen
aDoor.smashInDoor = aDoor.smash
aDoor.smash = mySmash
aPotion = Potion()
aPotion.drinkInPotion = aPotion.drink
aPotion.drink = myDrink
The decorator is reusable:
class Door:
def open(self): pass
def smash(self): pass
class Potion:
def drink(self): pass
# ----
def nausea(f):
@wraps(f)
def wrapper(*args, **kwds):
# globals.player.set_nausea()
return f(*args, **kwds)
return wrapper
# ----
aDoor = Door()
aDoor.open = nausa(aDoor.open)
aDoor.smash = nausa(aDoor.smash)
aPotion = Potion()
aPotion.drink = nausa(aPotion.drink)
Less code, because of better re-usability. Re-usability comes from the fact that the decorator does not need to know the name of the method to be called - the method is kept inside the decorator, in a generic way.
–3--
Now let’s assume you have mushrooms, and eating a mushroom should cause nausea. But every mushroom should have a different nausea duration. The duration might be part of the level data.
from functools import wraps
class Mushroom:
def eat(self): pass
def nausea(f, duration):
@wraps(f)
def wrapper(*args, **kwds):
# globals.player.set_nausea(duration)
return f(*args, **kwds)
return wrapper
mushroom1 = Mushroom()
mushroom1.eat = nausea(mushroom1.eat, 30)
mushroom2 = Mushroom()
mushroom2.eat = nausea(mushroom2.eat, 90)
mushroom3 = Mushroom() # no nausea!
mushroom1.eat()
mushroom2.eat()
mushroom3.eat()
How would you implement this using your way? One option would be to make use of a global dictionary where you register objects with their corresponding duration. Possible, but a general rule is that data and the functions acting upon data should be kept close together.
–4--
I promised to throw in a third technique. It is not based on exchanging the methods on a class, but a fundamental pattern of de-coupling: events.
The method Cat::meow would emit an event each time it is called. It might pack additional data inside the event, but it does not need to.
You first need to have an Event class, for example:
class Event:
def __init__(self):
self.handlers = set()
def __iadd__(self, handler):
self.handlers.add(handler)
return self
def __isub__(self, handler):
try:
self.handlers.remove(handler)
except:
raise ValueError('not handling this event!')
return self
def __call__(self, *args, **kargs):
for handler in self.handlers:
handler(*args, **kargs)
def _(self):
return len(self.handlers)
This is effectively a Python implementation of the C# event framework. not many lines
Now the code for the Cat class and the creation of two Cat instance. We also need an handler which act upon the nausa event. the handler is registered for only one cat-instance.
class Cat:
def __init__(self):
self.nausea = Event()
def meow(self):
self.nausea()
print 'meow'
def handler():
print 'player got nausea!'
aCat = Cat()
aCat.nausea += handler
bCat = Cat()
aCat.meow() # player will get nausea
bCat.meow() # player won't get nausea