开发者

How can I augment the method of a Python object?

开发者 https://www.devze.com 2023-01-26 01:35 出处:网络
I have a list of Spam objec开发者_运维百科ts: class Spam: def update(self): print(\'updating spam!\')

I have a list of Spam objec开发者_运维百科ts:

class Spam:
    def update(self):
        print('updating spam!')

some of them might be SpamLite objects:

class SpamLite(Spam):
    def update(self):
        print('this spam is lite!')
        Spam.update(self)

I would like to be able to take an arbitrary object from the list, and add something to it's update method, something like:

def poison(spam):
    tmp = spam.update 
    def newUpdate(self):
        print 'this spam has been poisoned!'
        tmp(self)
    spam.update = newUpdate

I want spam.update() to now either print:

this spam has been poisoned!
updating spam!

or

this spam has been poisoned!
this spam is lite!
updating spam!

depending on whether it was a SpamLite or just a Spam.

But that doesn't work, because spam.update() won't pass in the self argument automatically, and because if tmp leaves scope or changes then it won't call the old update. Is there a way I can do this?


def poison(spam):
    tmp = spam.update
    def newUpdate():
        print 'this spam has been poisoned!'
        tmp()
    spam.update = newUpdate

Full Script:

class Spam:
    def update(self):
        print('updating spam!')

class SpamLite(Spam):
    def update(self):
        print('this spam is lite!')
        Spam.update(self)

def poison(spam):
    tmp = spam.update # it is a bound method that doesn't take any arguments
    def newUpdate():
        print 'this spam has been poisoned!'
        tmp()
    spam.update = newUpdate


from operator import methodcaller    
L = [Spam(), SpamLite()]
map(methodcaller('update'), L)
map(poison, L)
print "*"*79
map(methodcaller('update'), L)

Output:

updating spam!
this spam is lite!
updating spam!
*******************************************************************************
this spam has been poisoned!
updating spam!
this spam has been poisoned!
this spam is lite!
updating spam!


Another approach, with MethodType:

class Spam:
    def update(self):
        print('updating spam!')

class SpamLite(Spam):
    def update(self):
        print('this spam is lite!')
        Spam.update(self)

def poison(spam):
    import types
    tmp = spam.update 
    def newUpdate(self):
        print 'this spam has been poisoned!'
        tmp()
    newUpdate = types.MethodType(newUpdate, spam, Spam)
    spam.update = newUpdate

spam = Spam()
spam_lite = SpamLite()
poison(spam)
poison(spam_lite)
spam.update()
print
spam_lite.update()


MonkeyPatching is frowned upon, in the python world.

You should really use the Mixin approach and use Multiple inheritance.

You can then dynamically replace (update) the parents to achieve the desired effect.


Use decorators like this:

def method_decorator(f):
    def wrapper(self, *args, **kwargs):
        print('this spam has been poisoned!')
        return f(self)
    return wrapper

class Spam:
    def update(self):
        print('updating spam!')

    @method_decorator
    def update2(self):
        print('updating spam!')

Spam().update()
Spam().update2()

This prints:

updating spam!
this spam has been poisoned!
updating spam!

If you want to know more about decorators read this: http://www.drdobbs.com/web-development/184406073

The above is not a "good citizen" decorator, read the article to know how to write one. Be sure to check also decorator library: http://pypi.python.org/pypi/decorator

HTH

0

精彩评论

暂无评论...
验证码 换一张
取 消