Como faço para criar uma classe em Python?

143

Eu tive uma ajuda realmente incrível nas minhas perguntas anteriores para detectar patas e dedos dentro de uma pata , mas todas essas soluções funcionam apenas para uma medição de cada vez.

Agora eu tenho dados que consistem em:

  • cerca de 30 cães;
  • cada um possui 24 medições (divididas em vários subgrupos);
  • cada medição possui pelo menos 4 contatos (um para cada pata) e
    • cada contato é dividido em 5 partes e
    • possui vários parâmetros, como tempo de contato, localização, força total etc.

texto alternativo

Obviamente, colocar tudo em um grande objeto não vai ser suficiente, então imaginei que precisava usar classes em vez da atual quantidade de funções. Mas mesmo que eu tenha lido o capítulo sobre classes do Learning Python, não consigo aplicá-lo ao meu próprio código ( link do GitHub )

Também sinto que é bastante estranho processar todos os dados toda vez que quero obter algumas informações. Depois de conhecer os locais de cada pata, não há motivo para calcular isso novamente. Além disso, quero comparar todas as patas do mesmo cão para determinar qual contato pertence a qual pata (dianteira / traseira, esquerda / direita). Isso se tornaria uma bagunça se eu continuasse usando apenas funções.

Então, agora estou procurando conselhos sobre como criar classes que me permitam processar meus dados ( link para os dados compactados de um cão ) de maneira sensata.

Ivo Flipse
fonte
4
Você também pode considerar o uso de um banco de dados (como sqlite: docs.python.org/library/sqlite3.html ). Você pode escrever um programa que lê seus enormes arquivos de dados e os converte em linhas nas tabelas do banco de dados. Então, como um segundo estágio, você pode escrever programas que extraem dados do banco de dados para fazer análises adicionais.
unutbu
Você quer dizer algo como eu perguntei aqui @ubutbu? Estou pensando em fazê-lo fazer isso, mas primeiro eu gostaria de ser capaz de processar todos os dados de uma forma mais organizada
Ivo Flipse

Respostas:

434

Como projetar uma classe.

  1. Anote as palavras. Você começou a fazer isso. Algumas pessoas não sabem e se perguntam por que têm problemas.

  2. Expanda seu conjunto de palavras em declarações simples sobre o que esses objetos farão. Ou seja, anote os vários cálculos que você fará sobre essas coisas. Sua lista curta de 30 cães, 24 medições, 4 contatos e vários "parâmetros" por contato é interessante, mas apenas parte da história. Seus "locais de cada pata" e "comparam todas as patas do mesmo cão para determinar qual contato pertence a qual pata" são o próximo passo no design de objetos.

  3. Sublinhe os substantivos. Seriamente. Algumas pessoas debatem o valor disso, mas acho que para os desenvolvedores de OO pela primeira vez isso ajuda. Sublinhe os substantivos.

  4. Revise os substantivos. Substantivos genéricos como "parâmetro" e "medida" precisam ser substituídos por substantivos específicos e concretos que se aplicam ao seu problema no domínio do seu problema. Informações específicas ajudam a esclarecer o problema. Os genéricos simplesmente ignoram os detalhes.

  5. Para cada substantivo ("contato", "pata", "cachorro" etc.), anote os atributos desse substantivo e as ações nas quais esse objeto se envolve. Não atalho isso. Todo atributo. "O conjunto de dados contém 30 cães", por exemplo, é importante.

  6. Para cada atributo, identifique se esse é um relacionamento com um substantivo definido ou algum outro tipo de dado "primitivo" ou "atômico", como uma string ou um float ou algo irredutível.

  7. Para cada ação ou operação, você deve identificar qual substantivo tem a responsabilidade e quais substantivos apenas participam. É uma questão de "mutabilidade". Alguns objetos são atualizados, outros não. Objetos mutáveis ​​devem possuir total responsabilidade por suas mutações.

  8. Nesse ponto, você pode começar a transformar substantivos em definições de classe. Alguns substantivos coletivos são listas, dicionários, tuplas, conjuntos ou nomeados, e você não precisa fazer muito trabalho. Outras classes são mais complexas, devido a dados derivados complexos ou a alguma atualização / mutação realizada.

Não se esqueça de testar cada classe isoladamente usando o mais unido.

Além disso, não há lei que diga que as classes devem ser mutáveis. No seu caso, por exemplo, você quase não possui dados mutáveis. O que você tem são dados derivados, criados por funções de transformação do conjunto de dados de origem.

S.Lott
fonte
24

Os conselhos a seguir (semelhantes aos conselhos de @ S.Lott) são do livro Beginning Python: From Novice to Professional

  1. Anote uma descrição do seu problema (o que o problema deve fazer?). Sublinhe todos os substantivos, verbos e adjetivos.

  2. Percorra os substantivos, procurando por possíveis classes.

  3. Percorra os verbos, procurando métodos possíveis.

  4. Percorra os adjetivos, procurando atributos em potencial

  5. Aloque métodos e atributos para suas classes

Para refinar a turma, o livro também aconselha que possamos fazer o seguinte:

  1. Anote (ou invente ) um conjunto de casos de uso - cenários de como seu programa pode ser usado. Tente cobrir todo o funcionalmente.

  2. Pense em cada caso de uso passo a passo, certificando-se de que tudo o que precisamos seja coberto.

mitchelllc
fonte
Seria bom ter alguns exemplos do tipo de sentenças que devemos escrever.
endolith
14

Eu gosto da abordagem TDD ... Então comece escrevendo testes para o que você quer que seja o comportamento. E escreva o código que passa. Neste ponto, não se preocupe muito com o design, basta obter um conjunto de testes e software que passam. Não se preocupe se você terminar com uma única classe grande e feia, com métodos complexos.

Às vezes, durante esse processo inicial, você encontrará um comportamento difícil de testar e precisa ser decomposto, apenas para testabilidade. Isso pode ser uma dica de que uma classe separada é necessária.

Então a parte divertida ... refatoração. Depois de ter o software funcionando, você pode ver as peças complexas. Muitas vezes, pequenos focos de comportamento se tornam aparentes, sugerindo uma nova classe, mas, se não, procure maneiras de simplificar o código. Extraia objetos de serviço e objetos de valor. Simplifique seus métodos.

Se você estiver usando o git corretamente (você está usando o git, não é?), Poderá experimentar rapidamente uma decomposição específica durante a refatoração, abandoná-lo e reverter se isso não simplificar as coisas.

Ao escrever o código de trabalho testado primeiro, você deve obter uma visão íntima do domínio do problema que não conseguiu obter facilmente com a abordagem do design primeiro. Os testes de escrita e o código levam você a superar a paralisia "por onde começar".

Les Nightingill
fonte
1
Eu também concordo com esta resposta, apesar de dividir o problema e identificar possíveis classes (por exemplo, fazer arquitetura de software "apenas o suficiente") pode ser muito útil se o problema for trabalhado em paralelo por vários membros da equipe.
Ben Smith
3

Toda a idéia do design do OO é fazer com que seu código seja mapeado para o seu problema; portanto, quando, por exemplo, você desejar o primeiro passo de um cachorro, faça algo como:

dog.footstep(0)

Agora, pode ser que, para o seu caso, você precise ler seu arquivo de dados brutos e calcular os locais dos passos. Tudo isso pode estar oculto na função Passo a Passo () para que isso aconteça apenas uma vez. Algo como:

 class Dog:
   def __init__(self):
     self._footsteps=None 
   def footstep(self,n):
     if not self._footsteps:
        self.readInFootsteps(...)
     return self._footsteps[n]

[Agora, esse é um tipo de padrão de cache. Na primeira vez em que ele lê os dados dos passos, nas vezes subsequentes, obtém-os de self._footsteps.]

Mas sim, obter o design correto do OO pode ser complicado. Pense mais sobre o que você deseja fazer com seus dados e isso informará quais métodos você precisará aplicar a quais classes.

Spacedman
fonte
2

Escrever seus substantivos, verbos e adjetivos é uma ótima abordagem, mas eu prefiro pensar no design de classe como fazer a pergunta que dados devem ser ocultados ?

Imagine que você tinha um Queryobjeto e um Databaseobjeto:

O Queryobjeto ajudará você a criar e armazenar uma consulta - armazenar, é a chave aqui, pois uma função pode ajudá-lo a criar uma com a mesma facilidade. Talvez você poderia ficar: Query().select('Country').from_table('User').where('Country == "Brazil"'). Não importa exatamente a sintaxe - esse é o seu trabalho! - a chave é que o objeto está ajudando a ocultar algo , neste caso os dados necessários para armazenar e gerar uma consulta. O poder do objeto vem da sintaxe de usá-lo (neste caso, um encadeamento inteligente) e da necessidade de não saber o que ele armazena para fazê-lo funcionar. Se feito corretamente, o Queryobjeto pode gerar consultas para mais de um banco de dados. Internamente, ele armazenava um formato específico, mas poderia ser facilmente convertido para outros formatos ao produzir (Postgres, MySQL, MongoDB).

Agora vamos pensar no Databaseobjeto. O que isso esconde e armazena? Bem, claramente, ele não pode armazenar todo o conteúdo do banco de dados, pois é por isso que temos um banco de dados! Então qual é o ponto? O objetivo é ocultar como o banco de dados funciona das pessoas que usam o Databaseobjeto. Boas classes simplificarão o raciocínio ao manipular o estado interno. Para esse Databaseobjeto, você pode ocultar como as chamadas de rede funcionam, ou consultas em lote ou atualizações, ou fornecer uma camada de armazenamento em cache.

O problema é que esse Databaseobjeto é ENORME. Ele representa como acessar um banco de dados; portanto, por baixo das cobertas, ele pode fazer tudo e qualquer coisa. Claramente, é difícil lidar com redes, cache e lotes, dependendo do sistema, portanto, escondê-los seria muito útil. Mas, como muitas pessoas observam, um banco de dados é incrivelmente complexo e, quanto mais longe das chamadas brutas de banco de dados que você recebe, mais difícil é ajustar o desempenho e entender como as coisas funcionam.

Essa é a troca fundamental da OOP. Se você escolher a abstração correta, simplificará a codificação (String, Matriz, Dicionário), se você escolher uma abstração muito grande (Banco de Dados, EmailManager, NetworkingManager), poderá se tornar muito complexo para realmente entender como funciona ou o que Espero. O objetivo é ocultar a complexidade , mas é necessária alguma complexidade. Uma boa regra é começar a evitar Managerobjetos e, em vez disso, criar classes parecidas structs- tudo o que eles fazem é armazenar dados, com alguns métodos auxiliares para criar / manipular os dados para facilitar sua vida. Por exemplo, no caso de EmailManageriniciar com uma função chamada sendEmailque pega um Emailobjeto. Este é um ponto de partida simples e o código é muito fácil de entender.

Como no seu exemplo, pense em quais dados precisam estar juntos para calcular o que você está procurando. Se você quisesse saber a que distância um animal estava andando, por exemplo, você poderia ter AnimalStepe AnimalTrip(coleção de AnimalSteps) classes. Agora que cada viagem tem todos os dados da etapa, deve ser capaz de descobrir coisas sobre isso, talvez AnimalTrip.calculateDistance()faça sentido.

Evan Moran
fonte
2

Depois de analisar o código vinculado, parece-me que é melhor não criar uma classe Dog neste momento. Em vez disso, você deve usar Pandas e quadros de dados . Um quadro de dados é uma tabela com colunas. Você trama de dados teria colunas, tais como: dog_id, contact_part, contact_time, contact_location, etc. Pandas utiliza matrizes Numpy nos bastidores, e tem muitos métodos de conveniência para você:

  • Selecione um cão, por exemplo: my_measurements['dog_id']=='Charly'
  • salve os dados: my_measurements.save('filename.pickle')
  • Considere usar em pandas.read_csv()vez de ler manualmente os arquivos de texto.
cyborg
fonte