54

I'm fully aware that pylint and other static analysis tools are not all-knowing, and sometimes their advice must be disobeyed. (This applies for various classes of messages, not just conventions.)


If I have classes like

class related_methods():

    def a_method(self):
        self.stack.function(self.my_var)

class more_methods():

    def b_method(self):
        self.otherfunc()

class implement_methods(related_methods, more_methods):

    def __init__(self):
        self.stack  = some()
        self.my_var = other()

    def otherfunc(self):
        self.a_method()

Obviously, that's contrived. Here's a better example, if you like.

I believe this style is called using "mixins".

Like other tools, pylint rates this code at -21.67 / 10, primarily because it thinks more_methods and related_methods don't have self or attributes otherfunc, stack, annd my_var because without running the code, it apparently can't see related_methods and more_methods are mixed-in to implement_methods.

Compilers and static analysis tools can't always solve the Halting Problem, but I feel this is certainly a case in which looking at what's inherited by implement_methods would show this is perfectly valid, and that would be a very easy thing to do.

Why do static analysis tools reject this valid (I think) OOP pattern?

Either:

  1. They don't even try to check inheritance or

  2. mixins are discouraged in idiomatic, readable Python


#1 is obviously incorrect because if I ask pylint to tell me about a class of mine that inherits unittest.TestCase that uses self.assertEqual, (something defined only in unittest.TestCase), it does not complain.

Are mixins unpythonic or discouraged?

0

5 Answers 5

33

Mixins just aren't a use case that was considered by the tool. That doesn't mean it's necessarily a bad use case, just an uncommon one for python.

Whether mixins are used appropriately in a particular instance is another matter. The mixin anti-pattern I see most frequently is using mixins when there is only ever intended to be one combination mixed. That's just a roundabout way to hide a god class. If you can't think of a reason right now to swap out or leave out one of the mixins, it shouldn't be a mixin.

1
  • yeah, it's a god object, I admit. In this case I'd argue it's OK for the CPU to be a god object.
    – cat
    Commented Mar 12, 2016 at 3:55
23

I believe that Mixins can, absolutely, be Pythonic. However, the idiomatic way to silence your linter--and improve your Mixins' readability--is to both (1) define abstract methods that explicitly define the methods that a Mixin's children are required to implement, and to (2) pre-define None-valued fields for Mixin data members that children must initialize.

Applying this pattern to your example:

from abc import ABC, abstractmethod


class related_methods():
    my_var = None
    stack = None

    def a_method(self):
        self.stack.function(self.my_var)


class more_methods(ABC):
    @abstractmethod
    def otherfunc(self):
        pass

    def b_method(self):
        self.otherfunc()


class implement_methods(related_methods, more_methods):
    def __init__(self):
        self.stack  = some()
        self.my_var = other()

    def otherfunc(self):
        self.a_method()
5
  • 2
    +1 for your answer, although the abstract other(self): pass thing trades the linter's confusion for mine
    – cat
    Commented Aug 10, 2017 at 23:47
  • It's even more confusing without the @abstractmethod ;-) I started with Java first so this is just fine for me. Commented Apr 18, 2019 at 4:53
  • 1
    One important fact to allow mixin be Pythonic (from the maintenable facet) is to refuse inheritance in mixins. Initialization with mixins is MRO dependant, and if a root ancestor don't call super().__init__ the chain is broken.
    – gdoumenc
    Commented Jan 23, 2020 at 15:16
  • 1
    @gdoumenc Could you elaborate that a bit?
    – ruohola
    Commented Jun 13, 2020 at 12:26
  • I'd expect Mixins to be self-contained. They might add some functionality, but they should not depend on others mixins implicitly (ie. by referencing other methods as you're proposing). What they could do is to mixin other Mixins. Commented Jul 9 at 14:15
16

The linter is not aware that you use a class as a mixin. Pylint is aware that you use a mixin if you add the suffix 'mixin' or 'Mixin' at the end of the class name, then the linter stops complaining.

linter_without_mixins linter2_with_mixin

Mixins aren't bad or good per se, are just a tool. You make them a good use or a bad use.

2
  • Would it be better to declare the fields from subclasses that are needed by the mixin?
    – dawid
    Commented Jun 5, 2020 at 7:14
  • Actually, is there a way to declare abstract fields? This would properly force their implementation.
    – dawid
    Commented Jun 5, 2020 at 7:15
12

I think mixins can be good, but I also think pylint is right in this case. Disclaimer: opinion-based stuff follows.

A good class, mixins included, has one clear responsibility. Ideally, a mixin should carry all the state it is going to access, and the logic to handle it. E.g. a good mixin could add a last_updated field to an ORM model class, provide logic for setting it, and methods to search for the oldest / newest record.

Referring to undeclared instance members (variables and methods) does look a bit weird.

The right approach very much depends on the task at hand.

It may be a mixin with the relevant bit of state kept in it.

It may be a different class hierarchy where the methods that you currently distribute via a mixin are in a base class, while lower-level implementation differences belong to subclasses. This looks most fit for your case with the stack operations.

It may be a class decorator that adds a method or two; this usually makes sense when you have to pass some arguments to the decorator to affect the method generation.

State your problem, explain your bigger design concerns, then we could argue if something is an anti-pattern in your case.

2

Are mixins ok?

Are Python mixins an anti-pattern?

Mixins are not discouraged - they are a good use-case for multiple inheritance.

Why is your linter complaining?

Pylint is obviously complaining because it doesn't know where the otherfunc, stack, and my_var is coming from.

Trivial?

There is no immediately apparent good reason for you separate these two methods into separate parent classes, either in your example in the question, or in your more trivial linked example, shown here.

201
202 class OpCore():
203     # nothing runs without these
204
205 class OpLogik(): 
206     # logic methods... 
207 
208 class OpString(): 
209     # string things
210 
211 
212 class Stack(OpCore, OpLogik, OpString): 
213 
214     "the mixin mixer of the above mixins" 

Costs of mixins and noise

The costs of what you are doing is making your linter report noise. That noise may obscure more important problems with you code. This is an important cost to weigh. Another cost is that you're separating your inter-related code into different namespaces that may make it harder for programmers to discover the meaning of.

Conclusion

Inheritance allows for code reuse. If you get code reuse with your mixins, great, they've created value for you that probably outweighs the possible other costs. If you're not getting code reuse/deduplication of lines of code, you're probably not getting much value for your mixins, and at that point, I think the cost of the noise is greater than the benefits.

3
  • 1
    There's a good reason for seprating them in the linked, nontrivial example -- it's easier for me to organise my code when I have a fraction of a full class that implements math, another that implements strings, and so on, and you mix them together to get Forth Soup.
    – cat
    Commented Mar 10, 2016 at 17:54
  • "Another cost is that you're separating your code into different namespaces that may make it harder for programmers to discover the meaning of" -- no, that's not the case at all. The class namespaces make it easier to tell what a group of methods do, and when someone wants to use the stack, they should call / implement the Forth Soup, not the individual ingredients
    – cat
    Commented Mar 10, 2016 at 18:07
  • 1
    @cat probably because they're contrived and not used in a real large project, neither example really supports your points of using mixins. Both show only one time the mixin is used, in which case just putting the implementation in one place is better style and organization. Now if you could show a case where you want common features across classes but do not want a common base class, that's where you'd use a mixin. Which is beside your linting question (UltraBird answered that well). But this answers your question generally regarding mixin applicability.
    – dlamblin
    Commented May 1, 2018 at 2:02

Not the answer you're looking for? Browse other questions tagged or ask your own question.