Quando devo usar classes em Python?


176

Eu tenho programado em python por cerca de dois anos; principalmente dados (pandas, mpl, numpy), mas também scripts de automação e pequenos aplicativos da web. Estou tentando me tornar um programador melhor e aumentar meu conhecimento em python, e uma das coisas que me incomoda é que nunca usei uma classe (além de copiar o código do frasco aleatório para aplicativos da Web pequenos). Geralmente entendo o que são, mas não consigo entender por que eu precisaria deles para uma função simples.

Para adicionar especificidade à minha pergunta: escrevo toneladas de relatórios automatizados que sempre envolvem extrair dados de várias fontes de dados (mongo, sql, postgres, apis), realizando muita ou pouca movimentação e formatação de dados, gravando os dados no csv / excel / html, envie-o por email. Os scripts variam de ~ 250 linhas a ~ 600 linhas. Haveria alguma razão para eu usar classes para fazer isso e por quê?


15
não há nada errado em codificar sem classes, se você puder gerenciar seu código melhor. Os programadores de POO tendem a exagerar os problemas devido às restrições do design da linguagem ou à compreensão superficial de diferentes padrões.
21715 Jason Jason

Respostas:


133

As aulas são o pilar da programação orientada a objetos . OOP está muito preocupado com a organização do código, reutilização e encapsulamento.

Primeiro, um aviso: OOP contrasta parcialmente com a Programação Funcional , que é um paradigma diferente usado muito no Python. Nem todo mundo que programa em Python (ou certamente a maioria das linguagens) usa OOP. Você pode fazer muito no Java 8 que não é muito orientado a objetos. Se você não quiser usar o OOP, não o faça. Se você estiver apenas escrevendo scripts únicos para processar dados que nunca mais usará, continue escrevendo do jeito que está.

No entanto, existem várias razões para usar o OOP.

Algumas razões:

  • Organização: OOP define maneiras conhecidas e padrão de descrever e definir dados e procedimentos no código. Os dados e o procedimento podem ser armazenados em diferentes níveis de definição (em diferentes classes), e existem maneiras padrão de falar sobre essas definições. Ou seja, se você usar o OOP de maneira padrão, ele ajudará você e outras pessoas a entender, editar e usar seu código. Além disso, em vez de usar um mecanismo complexo e arbitrário de armazenamento de dados (dictos de dictos ou listas ou dictos ou listas de dictos de conjuntos ou qualquer outra coisa), você pode nomear partes das estruturas de dados e consultá-las convenientemente.

  • Estado: OOP ajuda a definir e acompanhar o estado. Por exemplo, em um exemplo clássico, se você estiver criando um programa que processa alunos (por exemplo, um programa de notas), você pode manter todas as informações necessárias sobre eles em um único local (nome, idade, sexo, nível da série, cursos, notas, professores, colegas, dieta, necessidades especiais etc.), e esses dados persistem enquanto o objeto estiver vivo e forem facilmente acessíveis.

  • Encapsulamento : com o encapsulamento, o procedimento e os dados são armazenados juntos. Os métodos (um termo OOP para funções) são definidos ao lado dos dados em que operam e produzem. Em uma linguagem como Java, que permite controle de acesso , ou em Python, dependendo de como você descreve sua API pública, isso significa que métodos e dados podem ser ocultados do usuário. O que isso significa é que, se você precisar ou quiser alterar o código, poderá fazer o que quiser com a implementação do código, mas manter as APIs públicas iguais.

  • Herança : a herança permite definir dados e procedimentos em um local (em uma classe) e, em seguida, substituir ou estender essa funcionalidade posteriormente. Por exemplo, no Python, muitas vezes vejo pessoas criando subclasses da dictclasse para adicionar funcionalidades adicionais. Uma alteração comum está substituindo o método que lança uma exceção quando uma chave é solicitada de um dicionário que não existe para fornecer um valor padrão com base em uma chave desconhecida. Isso permite que você estenda seu próprio código agora ou mais tarde, permita que outras pessoas estendam seu código e que você estenda o código de outras pessoas.

  • Reutilização: todos esses motivos e outros permitem maior reutilização do código. O código orientado a objetos permite que você escreva um código sólido (testado) uma vez e depois reutilize repetidamente. Se você precisar ajustar algo para o seu caso de uso específico, poderá herdar de uma classe existente e substituir o comportamento existente. Se você precisar alterar alguma coisa, poderá alterar tudo enquanto mantém as assinaturas de método público existentes, e ninguém é o mais sábio (espero).

Novamente, existem vários motivos para não usar o OOP, e você não precisa. Mas, felizmente, com uma linguagem como Python, você pode usar um pouco ou muito, depende de você.

Um exemplo do caso de uso do aluno (sem garantia de qualidade do código, apenas um exemplo):

Orientado a Objeto

class Student(object):
    def __init__(self, name, age, gender, level, grades=None):
        self.name = name
        self.age = age
        self.gender = gender
        self.level = level
        self.grades = grades or {}

    def setGrade(self, course, grade):
        self.grades[course] = grade

    def getGrade(self, course):
        return self.grades[course]

    def getGPA(self):
        return sum(self.grades.values())/len(self.grades)

# Define some students
john = Student("John", 12, "male", 6, {"math":3.3})
jane = Student("Jane", 12, "female", 6, {"math":3.5})

# Now we can get to the grades easily
print(john.getGPA())
print(jane.getGPA())

Ditado padrão

def calculateGPA(gradeDict):
    return sum(gradeDict.values())/len(gradeDict)

students = {}
# We can set the keys to variables so we might minimize typos
name, age, gender, level, grades = "name", "age", "gender", "level", "grades"
john, jane = "john", "jane"
math = "math"
students[john] = {}
students[john][age] = 12
students[john][gender] = "male"
students[john][level] = 6
students[john][grades] = {math:3.3}

students[jane] = {}
students[jane][age] = 12
students[jane][gender] = "female"
students[jane][level] = 6
students[jane][grades] = {math:3.5}

# At this point, we need to remember who the students are and where the grades are stored. Not a huge deal, but avoided by OOP.
print(calculateGPA(students[john][grades]))
print(calculateGPA(students[jane][grades]))

Por causa do "rendimento", o encapsulamento Python geralmente é mais limpo com geradores e gerenciadores de contexto do que com classes.
Dmitry Rubanovich

4
@meter Eu adicionei um exemplo. Espero que ajude. A observação aqui é que, em vez de ter que confiar nas chaves dos seus dictos com o nome correto, o interpretador Python cria essa restrição para você se você errar e forçá-lo a usar métodos definidos (embora campos não definidos (embora Java e outros Linguagens OOP não permitem definir campos fora das classes, como Python)).
dantiston

5
@meter também, como um exemplo de encapsulamento: digamos que hoje essa implementação seja boa porque eu só preciso obter o GPA para 50.000 estudantes na minha universidade uma vez por semestre. Agora, amanhã, recebemos uma concessão e precisamos fornecer o GPA atual de cada aluno a cada segundo (é claro, ninguém pediria isso, mas apenas para torná-lo um desafio computacional). Poderíamos então "memorizar" o GPA e calculá-lo apenas quando ele é alterado (por exemplo, definindo uma variável no método setGrade); outros retornam uma versão em cache. O usuário ainda usa getGPA (), mas a implementação foi alterada.
dantiston

4
@dantiston, este exemplo precisa de collections.namedtuple. Você pode criar um novo tipo Student = collections.namedtuple ("Student", "nome, idade, sexo, nível, notas"). E então você pode criar instâncias john = Student ("John", 12, "male", notas = {'math': 3.5}, nível = 6). Observe que você usa argumentos posicionais e nomeados da mesma forma que faria na criação de uma classe. Este é um tipo de dados que já está implementado para você no Python. Você pode consultar john [0] ou john.name para obter o 1º elemento da tupla. Você pode obter as notas de john como john.grades.values ​​() agora. E já está feito para você.
Dmitry Rubanovich

2
para mim, o encapsulamento é uma razão suficientemente boa para sempre usar OOP. Luto para ver que o valor NÃO está usando OOP para qualquer projeto de codificação de tamanho razoável. Eu acho que eu preciso de respostas para a pergunta inversa :)
San Jay

23

Sempre que você precisar manter um estado de suas funções, isso não poderá ser realizado com geradores (funções que produzem e não retornam). Geradores mantêm seu próprio estado.

Se você deseja substituir qualquer um dos operadores padrão , precisa de uma classe.

Sempre que você usar um padrão de visitante, precisará de classes. Todos os outros padrões de design podem ser realizados de maneira mais eficiente e limpa com geradores, gerenciadores de contexto (que também são melhor implementados como geradores do que como classes) e tipos de POD (dicionários, listas e tuplas, etc.).

Se você deseja escrever código "python", deve preferir gerenciadores de contexto e geradores do que classes. Será mais limpo.

Se você deseja estender a funcionalidade, quase sempre será capaz de realizá-la com contenção e não com herança.

Como toda regra, isso tem uma exceção. Se você quiser encapsular a funcionalidade rapidamente (por exemplo, escreva o código de teste em vez do código reutilizável no nível da biblioteca), poderá encapsular o estado em uma classe. Será simples e não precisará ser reutilizável.

Se você precisa de um destruidor de estilo C ++ (RIIA), definitivamente NÃO deseja usar classes. Você deseja gerenciadores de contexto.


1
Os fechamentos @DmitryRubanovich não são implementados por meio de geradores em Python.
Eli Korvigo

1
@DmitryRubanovich eu estava me referindo a "fechamentos implementados como geradores em Python", o que não é verdade. Os fechamentos são muito mais flexíveis. Os geradores são obrigados a retornar uma Generatorinstância (um iterador especial), enquanto os fechamentos podem ter qualquer assinatura. Basicamente, você pode evitar as aulas na maioria das vezes criando fechamentos. E fechamentos não são meramente "funções definidas no contexto de outras funções".
Eli Korvigo

3
@ Eli Korvigo, de fato, os geradores são um salto significativo sintaticamente. Eles criam uma abstração de uma fila da mesma maneira que as funções são abstrações de uma pilha. E a maioria dos fluxos de dados pode ser montada a partir das primitivas de pilha / fila.
Dmitry Rubanovich

1
@DmitryRubanovich estamos falando de maçãs e laranjas aqui. Estou dizendo que os geradores são úteis em um número muito limitado de casos e de forma alguma podem ser considerados uma substituição de chamadas de estado com finalidade geral. Você está me dizendo como são ótimas, sem contradizer meus argumentos.
Eli Korvigo

1
@ Eli Korvigo, e eu estou dizendo que callables são apenas generalizações de funções. Que são açúcar sintático sobre o processamento de pilhas. Enquanto os geradores são açúcar sintático sobre o processamento de filas. Mas é esse aprimoramento na sintaxe que permite que construções mais complicadas sejam construídas facilmente e com sintaxe mais clara. '.next ()' quase nunca é usado, btw.
Dmitry Rubanovich

11

Eu acho que você faz certo. As aulas são razoáveis ​​quando você precisa simular alguma lógica de negócios ou processos difíceis da vida real com relações difíceis. Como exemplo:

  • Várias funções com estado de compartilhamento
  • Mais de uma cópia das mesmas variáveis ​​de estado
  • Para estender o comportamento de uma funcionalidade existente

Eu também sugiro que você assista a este vídeo clássico


3
Não há necessidade de usar uma classe quando uma função de retorno de chamada precisa de um estado persistente no Python. Usar o rendimento do Python em vez de retornar torna a função reentrante.
Dmitry Rubanovich

4

Uma classe define uma entidade do mundo real. Se você estiver trabalhando em algo que existe individualmente e tem sua própria lógica separada dos outros, crie uma classe para ele. Por exemplo, uma classe que encapsula a conectividade do banco de dados.

Se não for esse o caso, não há necessidade de criar classe


0

Depende da sua ideia e design. se você é um bom designer, o POO sairá naturalmente na forma de vários padrões de design. Para um processamento simples no nível de script, os OOPs podem estar sobrecarregados. Simples, considere os benefícios básicos de OOPs, como reutilizáveis ​​e extensíveis, e verifique se eles são necessários ou não. OOPs tornam as coisas complexas mais simples e as coisas mais simples complexas. Simplesmente mantém as coisas simples de qualquer maneira, usando OOPs ou não. o que é mais simples, use isso.

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.