2010-11-17 14 views
133

He tenido una ayuda realmente increíble en mis preguntas anteriores for detecting paws y toes within a paw, pero todas estas soluciones solo funcionan para una medición a la vez.¿Cómo diseño una clase en Python?

Now I have data que consta de:

  • unos 30 perros;
  • cada uno tiene 24 mediciones (divididas en varios subgrupos);
  • cada medición 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, la ubicación, etc. fuerza total

alt text

Obviamente todo lo que se pega en un objeto grande no se va a cortar, por lo que pensé que necesito ed para usar clases en lugar de la cantidad actual de funciones. Sin embargo, a pesar de que he leído el capítulo de aprendizaje de Python sobre las clases, no llego a aplicarlo a mi propio código (GitHub link)

también me siento como que es bastante extraño para procesar todos los datos cada vez que quiero salir alguna información. Una vez que conozco la ubicación de cada pata, no hay ninguna razón para que vuelva a calcular esto. Además, quiero comparar todas las patas del mismo perro para determinar qué contacto pertenece a cada pata (frontal/posterior, 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 (link to the zipped data of one dog) de una manera sensata.

+4

También es posible que desee considerar el uso de una base de datos (como sqlite: http://docs.python.org/library/sqlite3.html). Puede 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 análisis adicionales. – unutbu

+0

[¿Quiere decir algo como lo que pregunté aquí] (http://stackoverflow.com/questions/3738269/how-to-insert-arrays-into-a-database) @ubutbu? Estoy planeando hacerlo, pero primero me gustaría poder procesar todos los datos de una manera más organizada. –

Respuesta

423

Cómo diseñar una clase.

  1. Escriba las palabras. Usted comenzó a hacer esto. Algunas personas no lo hacen y se preguntan por qué tienen problemas.

  2. Amplíe su conjunto de palabras en declaraciones simples sobre lo que harán estos objetos. Es decir, anote los diversos cálculos que hará sobre estas cosas.Su lista corta de 30 perros, 24 medidas, 4 contactos y varios "parámetros" por contacto es interesante, pero solo una 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 nombres. Seriamente. Algunas personas debaten el valor de esto, pero me parece que para los desarrolladores primerizos de OO ayuda. Subraye los sustantivos.

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

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

  6. Para cada atributo, identifique si esto es 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 irreductible.

  7. Para cada acción u operación, debe identificar qué nombre 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 los únicos responsables de sus mutaciones.

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

No olvide probar cada clase de forma aislada utilizando unittest.

Además, no hay ninguna 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.

3

La idea de diseño orientado a objetos es hacer que su código del mapa a su problema, por lo que cuando, por ejemplo, desea que el primer paso de un perro, que hacer algo como:

dog.footstep(0) 

Ahora, puede ser que para su caso necesite leer en su archivo de datos brutos y calcular las ubicaciones de los pasos. Todo esto podría ocultarse 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 un tipo de patrón de almacenamiento en caché. La primera vez que va y lee los datos de las pisadas, las veces posteriores solo lo obtiene de self._footsteps.]

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

22

Los siguientes consejos (similares a los consejos de @ S. Lott) son del libro, Beginning Python: From Novice to Professional

  1. escribir una descripción de su problema (lo que debe hacer el problema?). Subraye todos los sustantivos, verbos y adjetivos.

  2. Repase los nombres, busque las posibles clases.

  3. Repase los verbos en busca de posibles métodos.

  4. Ir a través de los adjetivos, en busca de posibles atributos

  5. Asignar métodos y atributos a sus clases

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

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

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

13

Me gusta el enfoque TDD ... así que empieza por escribir pruebas para lo que desea el comportamiento sea. Y escribe el código que pasa. En este punto, no se preocupe demasiado por el diseño, solo obtenga un paquete de prueba y software que pase. No te preocupes si terminas con una sola gran clase 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 probarlo. Esto puede ser una pista de que una clase separada está garantizada.

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

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

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

+1

También estoy de acuerdo con esta respuesta, aunque desglosando el problema e identificando posibles clases (es decir, haciendo " solo la "arquitectura de software" suficiente puede ser muy útil si varios miembros del equipo van a trabajar en paralelo en varios aspectos. –

2

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

Imagine que tiene un objeto y un objeto QueryDatabase:

El objeto Query le ayudará a crear y almacenar una consulta - tienda, es la clave aquí, como una función podría ayudarle a crear uno con la misma facilidad . Tal vez podrías quedarte: 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 ocultar algo, en este caso los datos necesarios para almacenar y generar una consulta. El poder del objeto proviene de la sintaxis de su uso (en este caso, un encadenamiento inteligente) y no necesita saber qué almacena para que funcione. Si se hace bien, el objeto Query podría generar consultas para más de una base de datos. Internamente almacenaría un formato específico, pero podría convertirlo fácilmente a otros formatos al generar resultados (Postgres, MySQL, MongoDB).

Ahora, pensemos en el objeto Database. ¿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 personas que usan el objeto Database. Las buenas clases simplificarán el razonamiento al manipular el estado interno. Para este objeto Database, podría ocultar cómo funcionan las llamadas de red, o realizar consultas o actualizaciones por lotes, o proporcionar una capa de almacenamiento en caché.

El problema es que este objeto Database es ENORME. Representa cómo acceder a una base de datos, por lo que bajo las coberturas podría hacer cualquier cosa y todo. Evidentemente, la conexión en red, 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 base de datos que obtienes, 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 (Cadena, Matriz, Diccionario), si selecciona una abstracción que es demasiado grande (Base de datos, Administrador de correo electrónico, Administrador de redes), puede ser demasiado compleja 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 los objetos Manager, y en su lugar crear clases que son como structs; todo lo que hacen es retener datos, con algunos métodos auxiliares para crear/manipular los datos para hacerte la vida más fácil. Por ejemplo, en el caso de EmailManager comience con una función llamada sendEmail que toma un objeto Email. 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 quisiera saber qué tan lejos estaba caminando un animal, por ejemplo, podría tener las clases AnimalStep y AnimalTrip (colección de AnimalSteps). Ahora que cada viaje tiene todos los datos del paso, entonces debería ser capaz de resolver las cosas al respecto, quizás AnimalTrip.calculateDistance() tiene sentido.

2

Después de rozar su código vinculado, me parece que está mejor que no diseñando una clase de perro en este punto. En su lugar, debe usar Pandas y dataframes. 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:

  • seleccionar un perro por ejemplo, : my_measurements['dog_id']=='Charly'
  • guardar los datos: my_measurements.save('filename.pickle')
  • considerar el uso de pandas.read_csv() en lugar de leer manualmente los archivos de texto.