¿Cómo diseño una clase en Python?


143

He recibido una ayuda realmente increíble en mis preguntas anteriores para detectar patas y dedos de los pies dentro de una pata , pero todas estas soluciones solo funcionan para una medición a la vez.

Ahora tengo datos que consisten en:

  • unos 30 perros;
  • cada uno tiene 24 medidas (divididas en varios subgrupos);
  • cada medida tiene al menos 4 contactos (uno para cada pata) y
    • cada contacto se divide en 5 partes y
    • tiene varios parámetros, como tiempo de contacto, ubicación, fuerza total, etc.

texto alternativo

Obviamente, pegar todo en un gran objeto no lo va a cortar, así que pensé que necesitaba usar clases en lugar de las funciones actuales. Pero a pesar de que he leído el capítulo de Learning Python sobre las clases, no puedo aplicarlo a mi propio código ( enlace de GitHub )

También siento que es bastante extraño procesar todos los datos cada vez que quiero obtener alguna información. Una vez que conozco la ubicación de cada pata, no hay razón para que calcule esto nuevamente. Además, quiero comparar todas las patas del mismo perro para determinar qué contacto pertenece a cada pata (delantera / trasera, izquierda / derecha). Esto se convertiría en un desastre si continúo usando solo funciones.

Así que ahora estoy buscando consejos sobre cómo crear clases que me permitan procesar mis datos ( enlace a los datos comprimidos de un perro ) de una manera sensata.


44
También puede considerar usar una base de datos (como sqlite: docs.python.org/library/sqlite3.html ). Podría escribir un programa que lea sus enormes archivos de datos y los convierta en filas en las tablas de la base de datos. Luego, como segunda etapa, puede escribir programas que extraigan datos de la base de datos para realizar un análisis más detallado.
unutbu

¿Quieres decir algo como lo pregunté aquí @ubutbu? Estoy planeando hacerlo, pero primero me gustaría poder procesar todos los datos de una manera más organizada
Ivo Flipse el

Respuestas:


434

Cómo diseñar una clase.

  1. Escribe las palabras. Empezaste a hacer esto. Algunas personas no lo hacen y se preguntan por qué tienen problemas.

  2. Expanda su conjunto de palabras en declaraciones simples sobre lo que harán estos objetos. Es decir, escriba los diversos cálculos que hará sobre estas cosas. Su breve lista de 30 perros, 24 medidas, 4 contactos y varios "parámetros" por contacto es interesante, pero solo es parte de la historia. Sus "ubicaciones de cada pata" y "comparar todas las patas del mismo perro para determinar qué contacto pertenece a cada pata" son el siguiente paso en el diseño de objetos.

  3. Subraya los sustantivos. Seriamente. Algunas personas debaten el valor de esto, pero creo que para los desarrolladores de OO por primera vez ayuda. Subraya los sustantivos.

  4. Revisa los sustantivos. Los sustantivos genéricos como "parámetro" y "medición" deben reemplazarse por sustantivos concretos específicos que se apliquen a su problema en su dominio del problema. Los detalles ayudan a aclarar el problema. Los genéricos simplemente eluden detalles.

  5. Para cada sustantivo ("contacto", "pata", "perro", etc.) escriba los atributos de ese sustantivo y las acciones en las que se involucra ese objeto. No acortes esto. Cada atributo "El conjunto de datos contiene 30 perros", por ejemplo, es importante.

  6. Para cada atributo, identifique si se trata de una relación con un sustantivo definido o algún otro tipo de datos "primitivos" o "atómicos" como una cadena o un flotador o algo irreducible.

  7. Para cada acción u operación, debe identificar qué sustantivo tiene la responsabilidad y qué sustantivos simplemente participan. Es una cuestión de "mutabilidad". Algunos objetos se actualizan, otros no. Los objetos mutables deben ser totalmente responsables de sus mutaciones.

  8. En este punto, puede comenzar a transformar los sustantivos en definiciones de clase. Algunos sustantivos colectivos son listas, diccionarios, tuplas, conjuntos o tuplas con nombre, y no necesita hacer mucho trabajo. Otras clases son más complejas, ya sea por datos complejos derivados o por alguna actualización / mutación que se realiza.

No olvide probar cada clase de forma aislada utilizando unittest.

Además, no hay una ley que diga que las clases deben ser mutables. En su caso, por ejemplo, casi no tiene datos mutables. Lo que tiene son datos derivados, creados por funciones de transformación del conjunto de datos de origen.


24

Los siguientes consejos (similares a los consejos de @ S.Lott) son del libro, Beginning Python: De principiante a profesional

  1. Escriba una descripción de su problema (¿qué debe hacer el problema?). Subraye todos los sustantivos, verbos y adjetivos.

  2. Revisa los sustantivos y busca clases potenciales.

  3. Revisa los verbos, busca métodos potenciales.

  4. Revisa los adjetivos, busca atributos potenciales

  5. Asigna métodos y atributos a tus clases

Para refinar la clase, el libro también aconseja que podamos hacer lo siguiente:

  1. Escriba (o invente ) un conjunto de casos de uso, escenarios de cómo se puede usar su programa. Intenta cubrir todo funcionalmente.

  2. Piense en cada caso de uso paso a paso, asegurándose de que todo lo que necesitamos esté cubierto.


Sería bueno tener algunos ejemplos del tipo de oraciones que se supone que debemos escribir.
endolito el

14

Me gusta el enfoque TDD ... Así que comience escribiendo pruebas de lo que quiere que sea el comportamiento. Y escribe el código que pasa. En este punto, no se preocupe demasiado por el diseño, solo obtenga un conjunto de pruebas y un software que pase. No se preocupe si termina con una sola clase grande y fea, con métodos complejos.

A veces, durante este proceso inicial, encontrará un comportamiento que es difícil de probar y necesita descomponerse, solo para comprobarlo. Esto puede ser una pista de que una clase separada está garantizada.

Luego la parte divertida ... refactorizando. Después de tener el software en funcionamiento, puede ver las piezas complejas. A menudo, pequeños grupos de comportamiento se harán aparentes, sugiriendo una nueva clase, pero si no, simplemente busque formas de simplificar el código. Extraer objetos de servicio y objetos de valor. Simplifica tus métodos.

Si está usando git correctamente (está usando git, ¿no es así?), Puede experimentar muy rápidamente con alguna descomposición particular durante la refactorización, y luego abandonarla y volver atrás si no simplifica las cosas.

Al escribir el código de trabajo probado primero, debe obtener una visión íntima del dominio del problema que no podría obtener fácilmente con el enfoque de diseño primero. Las pruebas de escritura y el código lo empujan más allá de esa parálisis de "dónde empiezo".


1
También estoy de acuerdo con esta respuesta, aunque desglosar el problema e identificar posibles clases (es decir, hacer una arquitectura de software "lo suficiente") puede ser muy útil si varios miembros del equipo van a trabajar en paralelo.
Ben Smith

3

La idea general del diseño OO es hacer que su código de mapa corresponda a su problema, de modo que cuando, por ejemplo, desee el primer paso de un perro, haga algo como:

dog.footstep(0)

Ahora, puede ser que para su caso necesite leer su archivo de datos sin procesar y calcular las ubicaciones de los pasos. Todo esto podría estar oculto en la función footstep () para que solo ocurra una 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]

[Esto es ahora una especie de patrón de almacenamiento en caché. La primera vez que va y lee los datos de los pasos, las veces posteriores solo los obtiene de self._footsteps.]

Pero sí, hacer bien el diseño de OO puede ser complicado. Piense más sobre las cosas que quiere hacer con sus datos, y eso le informará qué métodos necesitará aplicar a qué clases.


2

Escribir sus sustantivos, verbos, adjetivos es un gran enfoque, pero prefiero pensar en el diseño de la clase como una pregunta ¿ qué datos deben ocultarse ?

Imagina que tienes un Queryobjeto y un Databaseobjeto:

El Queryobjeto lo ayudará a crear y almacenar una consulta: almacenar es la clave aquí, ya que una función podría ayudarlo a crear una con la misma facilidad. Tal vez usted podría quedarse: Query().select('Country').from_table('User').where('Country == "Brazil"'). No importa exactamente la sintaxis, ¡ese es su trabajo! - la clave es que el objeto te ayuda a ocultar algo , en este caso los datos necesarios para almacenar y generar una consulta. El poder del objeto proviene de la sintaxis de usarlo (en este caso, un encadenamiento inteligente) y no necesitar saber qué almacena para que funcione. Si se hace correctamente, el Queryobjeto podría generar consultas para más de una base de datos. Internamente almacenaría un formato específico, pero podría convertirse fácilmente a otros formatos al generar (Postgres, MySQL, MongoDB).

Ahora pensemos en el Databaseobjeto. ¿Qué esconde y almacena esto? ¡Claramente, no puede almacenar el contenido completo de la base de datos, ya que es por eso que tenemos una base de datos! Entonces, ¿cuál es el punto? El objetivo es ocultar cómo funciona la base de datos de las personas que usan el Databaseobjeto. Las buenas clases simplificarán el razonamiento al manipular el estado interno. Para este Databaseobjeto, puede ocultar cómo funcionan las llamadas de red, consultas por lotes o actualizaciones, o proporcionar una capa de almacenamiento en caché.

El problema es este Database objeto es ENORME. Representa cómo acceder a una base de datos, por lo que bajo las cubiertas podría hacer cualquier cosa. Claramente, las redes, el almacenamiento en caché y el procesamiento por lotes son bastante difíciles de manejar dependiendo de su sistema, por lo que ocultarlos sería muy útil. Pero, como muchas personas notarán, una base de datos es increíblemente compleja, y cuanto más lejos de las llamadas de DB sin procesar que recibe, más difícil es ajustar el rendimiento y comprender cómo funcionan las cosas.

Esta es la compensación fundamental de OOP. Si elige la abstracción correcta, simplifica la codificación (String, Array, Dictionary), si elige una abstracción que es demasiado grande (Base de datos, EmailManager, NetworkingManager), puede volverse demasiado complejo para comprender realmente cómo funciona o qué esperar. El objetivo es ocultar la complejidad , pero es necesaria cierta complejidad. Una buena regla general es comenzar evitando Managerobjetos y, en su lugar, crear clases similares structs: todo lo que hacen es mantener los datos, con algunos métodos auxiliares para crear / manipular los datos para facilitarle la vida. Por ejemplo, en el caso de EmailManagercomenzar con una función llamada sendEmailque toma un Emailobjeto. Este es un punto de partida simple y el código es muy fácil de entender.

En cuanto a su ejemplo, piense qué datos deben estar juntos para calcular lo que está buscando. Si usted quería saber hasta qué punto un animal caminaba, por ejemplo, usted podría tener AnimalStepy AnimalTrip(colección de AnimalSteps) clases. Ahora que cada Viaje tiene todos los datos de Paso, entonces debería ser capaz de resolver las cosas al respecto, tal vez AnimalTrip.calculateDistance()tenga sentido.


2

Después de hojear su código vinculado, me parece que es mejor no diseñar una clase de perro en este momento. Por el contrario, debe usar pandas y marcos de datos . Un marco de datos es una tabla con columnas. Usted trama de datos tendría columnas como: dog_id, contact_part, contact_time, contact_location, etc. pandas utiliza matrices numpy detrás de las escenas, y tiene muchos métodos de conveniencia para usted:

  • Seleccione un perro, por ejemplo: my_measurements['dog_id']=='Charly'
  • guardar los datos: my_measurements.save('filename.pickle')
  • Considere usar en pandas.read_csv()lugar de leer manualmente los archivos de texto.
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.