In Python can one implement mixin behavior without using inheritance?

前端 未结 8 1484
醉梦人生
醉梦人生 2021-02-06 13:10

Is there a reasonable way in Python to implement mixin behavior similar to that found in Ruby -- that is, without using inheritance?

class Mixin(object):
    def         


        
相关标签:
8条回答
  • 2021-02-06 13:31
    def mixer(*args):
        """Decorator for mixing mixins"""
        def inner(cls):
            for a,k in ((a,k) for a in args for k,v in vars(a).items() if callable(v)):
                setattr(cls, k, getattr(a, k).im_func)
            return cls
        return inner
    
    class Mixin(object):
        def b(self): print "b()"
        def c(self): print "c()"
    
    class Mixin2(object):
        def d(self): print "d()"
        def e(self): print "e()"
    
    
    @mixer(Mixin, Mixin2)
    class Foo(object):
        # Somehow mix in the behavior of the Mixin class,
        # so that all of the methods below will run and
        # the issubclass() test will be False.
    
        def a(self): print "a()"
    
    f = Foo()
    f.a()
    f.b()
    f.c()
    f.d()
    f.e()
    print issubclass(Foo, Mixin)
    

    output:

    a()
    b()
    c()
    d()
    e()
    False
    
    0 讨论(0)
  • 2021-02-06 13:38

    Composition? It seems like that would be the simplest way to handle this: either wrap your object in a decorator or just import the methods as an object into your class definition itself. This is what I usually do: put the methods that I want to share between classes in a file and then import the file. If I want to override some behavior I import a modified file with the same method names as the same object name. It's a little sloppy, but it works.

    For example, if I want the init_covers behavior from this file (bedg.py)

    import cove as cov
    
    
    def init_covers(n):
        n.covers.append(cov.Cover((set([n.id]))))
        id_list = []
        for a in n.neighbors:
            id_list.append(a.id)
        n.covers.append(cov.Cover((set(id_list))))
    
    def update_degree(n):
        for a in n.covers:
            a.degree = 0
            for b in n.covers:
                if  a != b:
                    a.degree += len(a.node_list.intersection(b.node_list))    
    

    In my bar class file I would do: import bedg as foo

    and then if I want to change my foo behaviors in another class that inherited bar, I write

    import bild as foo

    Like I say, it is sloppy.

    0 讨论(0)
  • 2021-02-06 13:39

    You can add the methods as functions:

    Foo.b = Mixin.b.im_func
    Foo.c = Mixin.c.im_func
    
    0 讨论(0)
  • 2021-02-06 13:39

    This one is based on the way it's done in ruby as explained by Jörg W Mittag. All of the wall of code after if __name__=='__main__' is test/demo code. There's actually only 13 lines of real code to it.

    import inspect
    
    def add_mixins(*mixins):
        Dummy = type('Dummy', mixins, {})
        d = {}
    
        # Now get all the class attributes. Use reversed so that conflicts
        # are resolved with the proper priority. This rules out the possibility
        # of the mixins calling methods from their base classes that get overridden
        # using super but is necessary for the subclass check to fail. If that wasn't a
        # requirement, we would just use Dummy above (or use MI directly and
        # forget all the metaclass stuff).
    
        for base in reversed(inspect.getmro(Dummy)):
            d.update(base.__dict__)
    
        # Create the mixin class. This should be equivalent to creating the
        # anonymous class in Ruby.
        Mixin = type('Mixin', (object,), d)
    
        class WithMixins(type):
            def __new__(meta, classname, bases, classdict):
                # The check below prevents an inheritance cycle from forming which
                # leads to a TypeError when trying to inherit from the resulting
                # class.
                if not any(issubclass(base, Mixin) for base in bases):
                    # This should be the the equivalent of setting the superclass 
                    # pointers in Ruby.
                    bases = (Mixin,) + bases
                return super(WithMixins, meta).__new__(meta, classname, bases,
                                                       classdict)
    
        return WithMixins 
    
    
    if __name__ == '__main__':
    
        class Mixin1(object):
            def b(self): print "b()"
            def c(self): print "c()"
    
        class Mixin2(object):
            def d(self): print "d()"
            def e(self): print "e()"
    
        class Mixin3Base(object):
            def f(self): print "f()"
    
        class Mixin3(Mixin3Base): pass
    
        class Foo(object):
            __metaclass__ = add_mixins(Mixin1, Mixin2, Mixin3)
    
            def a(self): print "a()"
    
        class Bar(Foo):
            def f(self): print "Bar.f()"
    
        def test_class(cls):
            print "Testing {0}".format(cls.__name__)
            f = cls()
            f.a()
            f.b()
            f.c()
            f.d()
            f.e()
            f.f()
            print (issubclass(cls, Mixin1) or 
                   issubclass(cls, Mixin2) or
                   issubclass(cls, Mixin3))
    
        test_class(Foo)
        test_class(Bar)
    
    0 讨论(0)
  • 2021-02-06 13:43

    I am not that familiar with Python, but from what I know about Python metaprogramming, you could actually do it pretty much the same way it is done in Ruby.

    In Ruby, a module basically consists of two things: a pointer to a method dictionary and a pointer to a constant dictionary. A class consists of three things: a pointer to a method dictionary, a pointer to a constant dictionary and a pointer to the superclass.

    When you mix in a module M into a class C, the following happens:

    1. an anonymous class α is created (this is called an include class)
    2. α's method dictionary and constant dictionary pointers are set equal to M's
    3. α's superclass pointer is set equal to C's
    4. C's superclass pointer is set to α

    In other words: a fake class which shares its behavior with the mixin is injected into the inheritance hierarchy. So, Ruby actually does use inheritance for mixin composition.

    I left out a couple of subleties above: first off, the module doesn't actually get inserted as C's superclass, it gets inserted as C's superclasses' (which is C's singleton class) superclass. And secondly, if the mixin itself has mixed in other mixins, then those also get wrapped into fake classes which get inserted directly above α, and this process is applied recursively, in case the mixed in mixins in turn have mixins.

    Basically, the whole mixin hierarchy gets flattened into a straight line and spliced into the inheritance chain.

    AFAIK, Python actually allows you to change a class's superclass(es) after the fact (something which Ruby does not allow you to do), and it also gives you access to a class's dict (again, something that is impossible in Ruby), so you should be able to implement this yourself.

    0 讨论(0)
  • 2021-02-06 13:43
    from functools import partial
    class Mixin(object):
        @staticmethod
        def b(self): print "b()"
        @staticmethod
        def c(self): print "c()"
    
    class Foo(object):
        def __init__(self, mixin_cls):
            self.delegate_cls = mixin_cls
    
        def __getattr__(self, attr):
            if hasattr(self.delegate_cls, attr):
                return partial(getattr(self.delegate_cls, attr), self)
    
        def a(self): print "a()"
    
    f = Foo(Mixin)
    f.a()
    f.b()
    f.c()
    print issubclass(Foo, Mixin)
    

    This basically uses the Mixin class as a container to hold ad-hoc functions (not methods) that behave like methods by taking an object instance (self) as the first argument. __getattr__ will redirect missing calls to these methods-alike functions.

    This passes your simple tests as shown below. But I cannot guarantee it will do all the things you want. Make more thorough test to make sure.

    $ python mixin.py 
    a()
    b()
    c()
    False
    
    0 讨论(0)
提交回复
热议问题