tags:

views:

175

answers:

2

I need to find an elegant way to do 2 kinds of MixIns.

First:

class A(object):
    def method1(self):
        do_something()

Now, a MixInClass should make method1 do this: do_other() -> A.method1() -> do_smth_else() - i.e. basically "wrap" the older function. I'm pretty sure there must exist a good solution to this.

Second:

class B(object):
    def method1(self):
        do_something()
        do_more()

In this case, I want MixInClass2 to be able to inject itself between do_something() and do_more(), i.e.: do_something() -> MixIn.method1 -> do_more(). I understand that probably this would require modifying class B - that's ok, just looking for simplest ways to achieve this.

These are pretty trivial problems and I actually solved them, but my solution is tainted.

Fisrt one by using self._old_method1 = self.method1(); self.method1() = self._new_method1(); and writing _new_method1() that calls to _old_method1().

Problem: multiple MixIns will all rename to _old_method1 and it is inelegant.

Second MixIn one was solved by creating a dummy method call_mixin(self): pass and injecting it between calls and defining self.call_mixin(). Again inelegant and will break on multiple MixIns..

Any ideas?


Thanks to Boldewyn, I've found elegant solution to first one (I've forgot you can create decorators on-the-fly, without modifying original code):

class MixIn_for_1(object):
    def __init__(self):
        self.method1 = self.wrap1(self.method1)
        super(MixIn_for_1, self).__init__()

    def wrap1(self, old):
        def method1():
            print "do_other()"
            old()
            print "do_smth_else()"
        return method1

Still searching for ideas for second one (this idea won't fit, since I need to inject inside of old method, not outside, like in this case).


Solution for second is below, replacing "pass_func" with lambda:0.

+2  A: 

I think, that can be handled in quite a Pythonic way using decorators. (PEP 318, too)

Boldewyn
Thanks, that solves my first problem. I did in fact think of decorators, but I had forgotten that I could construct decorators as deco(method), instead of @deco. The latter required modifying original code.
Slava N
+2  A: 

Here is another way to implement MixInClass1, MixinClass2:

Decorators are useful when you need to wrap many functions. Since MixinClass1 needs to wrap only one function, I think it is clearer to monkey-patch:

Using double underscores for __old_method1 and __method1 plays a useful role in MixInClass1. Because of Python's name-mangling convention, using the double underscores localizes these attributes to MixinClass1 and allows you to use the very same attribute names for other mix-in classes without causing unwanted name-collisions.

class MixInClass1(object):
    def __init__(self):
        self.__old_method1,self.method1=self.method1,self.__method1
        super(MixInClass1, self).__init__()        
    def __method1(self):
        print "pre1()"
        self.__old_method1()
        print "post1()"

class MixInClass2(object):
    def __init__(self):
        super(MixInClass2, self).__init__()        
    def method1_hook(self):
        print('MixIn method1')

class Foo(MixInClass2,MixInClass1):
    def method1(self):
        print "do_something()"
        getattr(self,'method1_hook',lambda *args,**kw: None)()
        print "do_more()"

foo=Foo()
foo.method1()
unutbu
The first solution is exactly the same I described in question.The second one is clever, assuming `Foo` is `B` and pass_func is `lambda:0`.
Slava N
@Slava: If you use `pass_func`, then you will not have to change `Foo` if you later decide to pass arguments to `method1_hook`. If you use `lambda: 0` then you would have to change it to something like `lambda x,y,z: 0`.
unutbu
1. What is `pass_func`? 2. Can't we just do `lambda *args, **kwargs:0` ?
Slava N
Oops, you're right. I'm editing my answer to use that...
unutbu