129 votos

¿Cómo se diseña una clase en Python?

He recibido una ayuda realmente impresionante en mis preguntas anteriores para la detección de patas y dedos de los pies dentro de una pata Pero todas estas soluciones sólo funcionan para una medición a la vez.

Ahora tengo datos que consiste en apagar:

  • unos 30 perros;
  • cada uno de ellos 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 el tiempo de contacto, la ubicación, la fuerza total, etc.

alt text

Obviamente, meter todo en un gran objeto no va a ser suficiente, así que pensé que tenía que usar clases en lugar de la actual serie de funciones. Pero aunque he leído el capítulo de Learning Python sobre las clases, no consigo aplicarlo a mi propio código ( Enlace a GitHub )

También siento que es bastante extraño procesar todos los datos cada tiempo quiero sacar algo de información. Una vez que conozco la ubicación de cada pata, no hay razón para que vuelva a calcularla. Además, quiero comparar todas las patas de un mismo perro para determinar qué contacto pertenece a cada pata (delantera/trasera, izquierda/derecha). Esto se convertiría en un lío si sigo utilizando sólo funciones.

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

416voto

S.Lott Puntos 207588

Cómo diseñar una clase.

  1. Escribe las palabras. Has empezado a hacerlo. Algunas personas no lo hacen y se preguntan por qué tienen problemas.

  2. Amplía tu conjunto de palabras en afirmaciones sencillas sobre lo que harán estos objetos. Es decir, escribe los distintos cálculos que harás con estas cosas. Tu breve lista de 30 perros, 24 medidas, 4 contactos y varios "parámetros" por contacto es interesante, pero sólo es una parte de la historia. Tus "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 del objeto.

  3. Subraya los sustantivos. En serio. Algunas personas discuten el valor de esto, pero yo encuentro que para los desarrolladores OO de primera vez ayuda. Subraya los nombres.

  4. Repasa los sustantivos. Los nombres genéricos como "parámetro" y "medida" deben sustituirse por nombres específicos y concretos que se apliquen al problema en su ámbito. Los específicos ayudan a aclarar el problema. Los genéricos simplemente eluden los detalles.

  5. Para cada sustantivo ("contacto", "pata", "perro", etc.) escribe los atributos de ese sustantivo y las acciones que realiza ese objeto. No lo acortes. 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 nombre definido, o algún otro tipo de dato "primitivo" o "atómico" como una cadena o un flotador o algo irreductible.

  7. Para cada acción u operación, hay que 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 tener la responsabilidad total de sus mutaciones.

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

No olvides probar cada clase de forma aislada utilizando unittest.

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

20voto

mitchelllc Puntos 519

Los siguientes consejos (similares a los de @S.Lott) están sacados del libro, Iniciación a Python: De principiante a profesional

  1. Escribe una descripción de tu problema (¿qué debería hacer el problema?). Subraya todos los sustantivos, verbos y adjetivos.

  2. Revisa los sustantivos, buscando posibles clases.

  3. Repasa los verbos, buscando posibles métodos.

  4. Repasa los adjetivos, buscando posibles atributos

  5. Asigne métodos y atributos a sus clases

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

  1. Escriba (o sueñe) un conjunto de casos de uso -Escenarios de cómo se puede utilizar su programa. Intente cubrir todas las funciones.

  2. Piensa en cada caso de uso paso a paso, asegurándote de que todo lo que necesitamos está cubierto.

13voto

Les Nightingill Puntos 375

Me gusta el enfoque TDD... Así que empieza escribiendo pruebas para lo que quieres que sea el comportamiento. Y escribe código que pase. En este punto, no te preocupes demasiado por el diseño, sólo consigue un conjunto de pruebas y un software que pase. No te preocupes si terminas con una clase grande y fea, con métodos complejos.

A veces, durante este proceso inicial, encontrarás un comportamiento que es difícil de probar y que necesita ser descompuesto, sólo para la testabilidad. Esto puede ser un indicio de que se justifica una clase separada.

Luego, la parte divertida... la refactorización. Después de tener un software que funciona, puedes ver las piezas complejas. A menudo, los pequeños focos de comportamiento se harán evidentes, sugiriendo una nueva clase, pero si no es así, sólo hay que buscar formas de simplificar el código. Extraiga los objetos de servicio y los objetos de valor. Simplifique sus métodos.

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

Al escribir primero el código de trabajo probado, debería obtener una visión íntima del dominio del problema que no podría conseguir fácilmente con el enfoque del diseño primero. Escribir pruebas y código te empuja a superar esa parálisis de "por dónde empiezo".

3voto

Spacedman Puntos 33792

La idea del diseño OO es hacer que tu código se adapte a tu problema, así que cuando, por ejemplo, quieres la primera pisada de un perro, haces algo como

dog.footstep(0)

Ahora bien, puede ser que para tu caso necesites leer tu archivo de datos en bruto y calcular las ubicaciones de las pisadas. Todo esto podría ocultarse en la función footstep() para que sólo ocurra una vez. Algo así 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 caché. La primera vez va y lee los datos de los pasos, las siguientes veces sólo los obtiene de self._footsteps].

Pero sí, conseguir un diseño OO correcto puede ser complicado. Piensa más en las cosas que quieres hacer con tus datos, y eso te informará de qué métodos necesitarás aplicar a qué clases.

2voto

Evan Moran Puntos 1420

Escribir los nombres, los verbos y los 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 Query y un objeto Database objeto:

El Query le ayudará a crear y almacenar una consulta -- almacenar, es la clave aquí, ya que una función podría ayudarle 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 ayude esconder algo , en este caso los datos necesarios para almacenar y dar salida a una consulta. El poder del objeto proviene de la sintaxis de su uso (en este caso un encadenamiento inteligente) y de no necesitar saber lo que almacena para hacerlo funcionar. Si se hace bien, el Query puede generar consultas para más de una base de datos. Internamente almacenaría un formato específico, pero podría convertir fácilmente a otros formatos cuando la salida (Postgres, MySQL, MongoDB).

Ahora pensemos en la Database objeto. ¿Qué esconde y almacena esto? Bueno, claramente no puede almacenar todo el contenido de la base de datos, ¡ya que para eso tenemos una base de datos! Entonces, ¿cuál es el objetivo? El objetivo es ocultar el funcionamiento de la base de datos de las personas que utilizan el Database objeto. Las buenas clases simplificarán el razonamiento al manipular el estado interno. Para ello Database podría ocultar cómo funcionan las llamadas de red, o las consultas o actualizaciones por lotes, o proporcionar una capa de almacenamiento en caché.

El problema es el siguiente Database objeto es ENORME. Representa la forma de acceder a una base de datos, por lo que bajo las cubiertas podría hacer cualquier cosa y todo. Claramente, la conexión en red, el almacenamiento en caché y la creación de lotes son bastante difíciles de manejar dependiendo de su sistema, por lo que ocultarlos sería muy útil. Pero, como mucha gente notará, una base de datos es increíblemente compleja, y cuanto más lejos de las llamadas a la base de datos en bruto te encuentres, más difícil es ajustar el rendimiento y entender cómo funcionan las cosas.

Este es el compromiso fundamental de la POO. Si eliges la abstracción correcta, hace que la codificación sea más sencilla (String, Array, Dictionary), si eliges una abstracción demasiado grande (Database, EmailManager, NetworkingManager), puede llegar a ser demasiado compleja para entender realmente cómo funciona, o qué esperar. El objetivo es ocultar la complejidad pero es necesaria una cierta complejidad. Una buena regla general es empezar evitando Manager y en su lugar crear clases que sean como structs -- todo lo que hacen es mantener los datos, con algunos métodos de ayuda para crear/manipular los datos para hacer su vida más fácil. Por ejemplo, en el caso de EmailManager comienzan con una función llamada sendEmail que toma un Email objeto. Este es un punto de partida sencillo y el código es muy fácil de entender.

En cuanto a tu ejemplo, piensa en los datos que deben estar juntos para calcular lo que buscas. Si quisieras saber qué distancia recorre un animal, por ejemplo, podrías tener AnimalStep y AnimalTrip (colección de AnimalSteps). Ahora que cada Viaje tiene todos los datos de los Pasos, entonces debería ser capaz de averiguar cosas sobre él, quizás AnimalTrip.calculateDistance() tiene sentido.

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X