34

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?

  • 1
    Is this a bad question? Is is off-topic? is it unanswerable? Am I stupid? People can DV but they don't want to help improve it. – cat Mar 10 '16 at 15:55
  • 1
    There some that are likely tired of having people ask "is this an (anti-)pattern" or "is this (un)pythonic". – user40980 Mar 10 '16 at 15:58
  • 9
    @MichaelT That's fine. They can stop reviewing questions then. – Katana314 Mar 10 '16 at 16:56
  • 2
    @tac people will downvote for various reasons, but are never required to say why - the downvote button has a tool-tip that says, "this question does not show any research effort; it is unclear or not useful" - a vague, if acceptable answer. 8 up and 1 down is actually pretty good, particularly on a question, where downvotes are free. Don't let it get you down. Cheers. – Aaron Hall Mar 10 '16 at 20:20
  • @AaronHall I'm aware people are allowed to do what they like with their votes, indeed I tell new users who complain the same thing. – cat Mar 10 '16 at 20:23
14

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.

  • 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 Mar 12 '16 at 3:55
15

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()
  • 2
    +1 for your answer, although the abstract other(self): pass thing trades the linter's confusion for mine – cat Aug 10 '17 at 23:47
  • It's even more confusing without the @abstractmethod ;-) I started with Java first so this is just fine for me. – Chris Huang-Leaver Apr 18 at 4:53
9

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.

6

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
    this reads more like a comment, see How to Answer – gnat Mar 4 '18 at 21:21
  • 2
    this is a valuable answer because i don't think i'd ever know about that hint otherwise – cat Sep 8 '18 at 22:29
1

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.

  • 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 Mar 10 '16 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 Mar 10 '16 at 18:07
  • @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 May 1 '18 at 2:02

protected by gnat Mar 4 '18 at 21:20

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

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