Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should mixins use parent attributes?

I'm working on a python project where I need to use about 20 different classes implementing a list of functionalities such as : "download", "parse", "update", etc.

Several functionalities can easily be factorized by using a superclass since the required code is the same for all of them.

But sometimes, particularly for the "parse" method, I have 10 classes that must implement the same algorithm and 10 others that need a specific algorithm.

Based on what I know about python, this behavior can be easily factorized with the use of mixins.

But here is the problem even if the "parse" algorithm is the same, I need to apply a tag to parsed entries, and this tag is specific for each class. I wonder if this is a proper way to use a class attribute that will be used only by the mixin to achieve this goal.

This chunk of code give an example of how attributes are used :

class MyMixin():
    def parse(self):
        print(self.tag)
        ...

class MyClass(MyMixin):
    tag = 'mytag'

I already saw it in some frameworks (http://www.django-rest-framework.org/api-guide/generic-views/), but I'm interested to know what's the opinion of the community.

==========================

EDIT

To summarize with a concrete example, should I write this :

class MyMixin():
    def do_something(self):
        print(self.tag)

class MyClass(MyMixin):
    tag = 'mytag'

if __name__ == '__main__':
    c = MyClass()
    c.do_something()

or that :

class MyMixin():
    def do_something(self, tag):
        print(tag)

class MyClass(MyMixin):
    tag = 'mytag'

if __name__ == '__main__':
    c = MyClass()
    c.do_something(c.tag)
like image 621
J1bz Avatar asked Apr 18 '16 09:04

J1bz


2 Answers

You use mixins to implement composition in other classes. This way you can delegate functionality to a mixin and reuse the mixin in other classes. Therefore with the use of python, you have two rules for mixins:

  • Mixin classes should not derive from any base class (except object)
  • But they may assume that the classes they will be mixed in with do derive from a certain base class, so they may assume the base class is there of they assume that the class has certain attributes.

These two rules set a mixin apart from inheritance.

So to answer you're question, yes you may use parent attributes, if you make sure the parent has these attributes.

A small example (from: https://mail.python.org/pipermail/tutor/2008-May/062005.html):

class Base(object):
    """Base class for mixer classes. All mixin classes
    require the classes they are mixed in with to be
    instances of this class (or a subclass)."""

    def __init__(self,b):
        self.b = b # Mixin classes assume this attribute will be present

class MixinBPlusOne(object):
    """A mixin class that implements the print_b_plus_one
    method."""

    def __init__(self):
        print 'MixinBPlusOne initialising'

    def print_b_plus_one(self):
        print self.b+1

class MixinBMinusOne(object):
    """A mixin class that implements the print_b_minus_one
    method."""

    def __init__(self):
        print 'MixinBMinusOne initialising'

    def print_b_minus_one(self):
        print self.b-1

class Mixer(Base,MixinBPlusOne,MixinBMinusOne):
    """A mixer class (class that inherits some mixins), this
    will work because it also inherits Base, which the
    mixins expect."""

    def __init__(self,b):
        # I feel like I should be using super here because 
        # I'm using new-style classes and multiple
        # inheritance, but the argument list of
        # Base.__init__ differs from those of the Mixin
        # classes, and this seems to work anyway.
        Base.__init__(self,b)
        MixinBPlusOne.__init__(self)
        MixinBMinusOne.__init__(self)

class BrokenMixer(MixinBPlusOne,MixinBMinusOne):
    """This will not work because it does not inherit Base,
    which the mixins expect it to do."""

    pass

m = Mixer(9)
m.print_b_plus_one()
m.print_b_minus_one()

m = BrokenMixer(9)
m.print_b_plus_one() # It'll crash here.

Also the django restframework example is very good: django rest framework mixins

like image 145
Marc Avatar answered Oct 19 '22 10:10

Marc


You can get some extra safety with the help of the abc module:

from abc import abstractproperty, ABCMeta

class Parser(object):
    __metaclass__ = ABCMeta

    @abstractproperty
    def tag(self):
        pass

class Concrete(Parser):

    @property
    def tag(self):
        return 'concrete'

print Concrete().tag  # => prints 'concrete'

class Incomplete(Parser):
    pass

# Raises error:
# TypeError: Can't instantiate abstract class Incomplete with abstract methods tag
Incomplete()

(the code for Python 3 may be slightly different)

This way the error is caught nice and early instead of when the attribute is accessed.

In addition PyCharm warns that the class is incomplete at the definition. Other static analysis tools can probably also pick this up.

enter image description here

like image 22
Alex Hall Avatar answered Oct 19 '22 12:10

Alex Hall