Os mixins Python são um anti-padrão?


34

Estou plenamente ciente de que pylintoutras ferramentas de análise estática não são oniscientes e, às vezes, seus conselhos devem ser desobedecidos. (Isso se aplica a várias classes de mensagens, não apenas a conventions.)


Se eu tiver aulas como

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()

Obviamente, isso é artificial. Aqui está um exemplo melhor, se você quiser .

Eu acredito que esse estilo é chamado usando "mixins".

Como outras ferramentas, pylintclassifica esse código em -21.67 / 10, principalmente porque ele pensa more_methodse related_methodsnão tem selfou atributos otherfunc, stacke my_varporque sem executar o código, ele aparentemente não pode ver related_methodse more_methodsestá misturado implement_methods.

Compiladores e ferramentas de análise estática nem sempre podem resolver o Problema da Parada , mas acho que esse é certamente um caso em que analisar o que é herdado por implement_methodsmostraria que isso é perfeitamente válido e que seria uma coisa muito fácil de fazer.

Por que as ferramentas de análise estática rejeitam esse padrão OOP válido (eu acho)?

Ou:

  1. Eles nem tentam verificar a herança ou

  2. mixins são desencorajados em Python idiomático e legível


O número 1 está obviamente incorreto, porque se eu pedir pylintpara me contar sobre uma classe minha que herda unittest.TestCaseesse uso self.assertEqual(algo definido apenas em unittest.TestCase), ela não reclama.

Os mixins são antitônicos ou desencorajados?


1
Esta é uma pergunta ruim? Isso é fora de tópico? é irrespondível? Eu sou estúpido? As pessoas podem DV, mas não querem ajudar a melhorá-lo.
cat

1
Há alguns que provavelmente estão cansados ​​de pedir que as pessoas perguntem "esse é um (anti) padrão" ou "é este (des) pitônico".

9
@MichaelT Isso é bom. Eles podem parar de revisar as perguntas então.
Katana314

2
O pessoal do @tac votará por várias razões, mas nunca precisará dizer o porquê - o botão downvote tem uma dica de ferramenta que diz: "esta pergunta não mostra nenhum esforço de pesquisa; não é clara ou não é útil" - uma vaga, se resposta aceitável. 8 para cima e 1 para baixo é realmente muito bom, especialmente em uma pergunta, onde os votos negativos são gratuitos. Não deixe isso te derrubar. Felicidades.
Aaron Hall

@AaronHall Estou ciente de que as pessoas podem fazer o que quiserem com seus votos, na verdade digo aos novos usuários que reclamam da mesma coisa.
gato

Respostas:


16

Os mixins simplesmente não são um caso de uso considerado pela ferramenta. Isso não significa que seja necessariamente um caso de uso ruim, apenas um caso incomum para python.

Se mixins são usados ​​adequadamente em uma instância específica é outra questão. O anti-padrão de mixina que eu vejo com mais frequência é o uso de mixinas quando existe a intenção de haver apenas uma combinação. Essa é apenas uma maneira indireta de esconder uma classe divina. Se você não consegue pensar em um motivo agora para trocar ou deixar de fora um dos mixins, não deve ser um mixin.


Sim, é um objeto de Deus, eu admito. Nesse caso, eu diria que não há problema em a CPU ser um objeto divino.
cat

15

Eu acredito que os Mixins podem, absolutamente, ser pitonicos. No entanto, a maneira idiomática de silenciar seu interlocutor - e melhorar a legibilidade de seus Mixins - é: (1) definir métodos abstratos que definem explicitamente os métodos que os filhos de um Mixin devem implementar, e (2) pré-definir Noneavaliados para membros de dados Mixin que os filhos devem inicializar.

Aplicando esse padrão ao seu exemplo:

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 para sua resposta, embora a abstract other(self): passcoisa troque a confusão do linter pela minha #
cat

É ainda mais confuso sem o @abstractmethod ;-) Comecei com Java primeiro, então isso é bom para mim.
Chris Huang-Leaver 18/04

9

Eu acho que mixins podem ser bons, mas também acho que o pylint está certo neste caso. Isenção de responsabilidade: material baseado em opinião a seguir.

Uma boa classe, incluindo os mixins, tem uma responsabilidade clara. Idealmente, um mixin deve conter todo o estado que ele acessará e a lógica para lidar com isso. Por exemplo, um bom mixin pode adicionar um last_updatedcampo a uma classe de modelo ORM, fornecer lógica para defini-lo e métodos para procurar o registro mais antigo / mais novo.

Referir-se a membros da instância não declarados (variáveis ​​e métodos) parece um pouco estranho.

A abordagem correta depende muito da tarefa em questão.

Pode ser uma mistura com o bit relevante de estado mantido nele.

Pode ser uma hierarquia de classe diferente, na qual os métodos que você distribui atualmente por meio de um mixin estão em uma classe base, enquanto as diferenças de implementação de nível inferior pertencem às subclasses. Isso parece mais adequado ao seu caso com as operações de pilha.

Pode ser um decorador de classe que adicione um método ou dois; isso geralmente faz sentido quando você precisa passar alguns argumentos para o decorador para afetar a geração do método.

Declare o seu problema, explique suas preocupações maiores de design e poderíamos argumentar se algo é um antipadrão no seu caso.


6

O linter não está ciente de que você usa uma classe como um mixin. Pylint está ciente de que você usa um mixin se adicionar o sufixo 'mixin' ou 'Mixin' no final do nome da classe, e o linter para de reclamar.

linter_without_mixins linter2_with_mixin

Mixins não são ruins ou bons por si só, são apenas uma ferramenta. Você faz deles um bom uso ou um mau uso.


2
este lê mais como um comentário, consulte Como responder
mosquito

2
esta é uma resposta valiosa, porque eu não acho que eu nunca saberia que dica de outra forma
cat

1

Os mixins estão ok?

Os mixins Python são um anti-padrão?

Os mixins não são desencorajados - eles são um bom caso de uso para herança múltipla.

Por que seu linter está reclamando?

Pylint é, obviamente, reclamando porque ele não sabe onde o otherfunc, stacke my_var está vindo.

Trivial?

Não há motivo aparente imediatamente bom para você separar esses dois métodos em classes pai separadas, seja no seu exemplo na pergunta ou no seu exemplo vinculado mais trivial, mostrado aqui.

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" 

Custos de mixins e ruído

Os custos do que você está fazendo é fazer com que o seu relatório seja interrompido. Esse ruído pode ocultar problemas mais importantes no seu código. Este é um custo importante para pesar. Outro custo é que você está separando seu código inter-relacionado em diferentes namespaces, o que pode dificultar a descoberta do significado pelos programadores.

Conclusão

A herança permite a reutilização de código. Se você conseguir reutilizar o código com seus mixins, ótimo, eles criaram valor para você que provavelmente supera os outros custos possíveis. Se você não está recebendo reutilização / deduplicação de linhas de código, provavelmente não está obtendo muito valor para seus mixins e, nesse ponto, acho que o custo do ruído é maior do que os benefícios.


Há um bom motivo para separá-los no exemplo vinculado e não trivial - é mais fácil organizar meu código quando tenho uma fração de uma classe completa que implementa matemática, outra que implementa seqüências de caracteres e assim por diante, e você as mistura. obter a Forth Soup.
cat

"Outro custo é que você está separando seu código em diferentes namespaces, o que pode tornar mais difícil para os programadores descobrirem o significado de" - não, esse não é o caso. Os namespaces classe torná-lo mais fácil de dizer o que um grupo de métodos de fazer, e quando alguém quer usar a pilha, eles devem chamar / implementar o Forth Soup, e não os ingredientes individuais
gato

@cat provavelmente porque eles são inventados e não são usados ​​em um projeto muito grande, nenhum dos exemplos realmente suporta seus pontos de uso de mixins. Ambos mostram apenas uma vez que o mixin é usado; nesse caso, apenas colocar a implementação em um local é melhor estilo e organização. Agora, se você pudesse mostrar um caso em que deseja recursos comuns entre classes, mas não quer uma classe base comum, é nesse local que você usaria um mixin. O que está ao lado de sua pergunta de fiapos (o UltraBird respondeu bem). Mas isso responde à sua pergunta geralmente sobre a aplicabilidade do mixin.
Dlamblin
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.