[Python-es] Pregunta sobre diseño de una biblioteca

Chema Cortes pych3m4 en gmail.com
Mie Feb 13 23:15:20 CET 2013


El día 13 de febrero de 2013 17:12, Juan Luis Cano
<juanlu001 en gmail.com> escribió:
> No suelo preguntar nada en esta lista, pero hoy me gustaría plantearos una
> cuestión un poco abierta a la que llevo dando vueltas algunos meses. No
> tengo una formación reglada en programación así que me gustaría recabar
> algunas ideas sobre cómo llevar a cabo este diseño DeLaManeraCorrecta™.

Tampoco me considero ortodoxo en ciencias de la computación, así que
también perdón si no soy demasiado purista.

Empezaré por el contexto del final:

> Estoy muy interesado en los principios SOLID y en intentar escribir código
> POO de buena calidad pero no parece haber mucho material disponible en la
> red y por otro lado sin una orientación a objetos «purista» no me resulta
> tan sencillo. Cuando programaba en ActionScript 3 hacía y deshacía con
> interfaces, encapsulación, herencia... ahora Python y su «duck typing» me
> despistan un poco en este sentido.

IMHO, es un error que los lenguajes de programación se centren en POO
en detrimento de otros paradigmas como la programación funcional. Todo
lo que veo en tus preguntas son conceptos de la programación funcional
que no sabes encajar dentro de una jerarquía de objetos. Tú tienes
"fórmulas" y la mejor representación de una fórmula es como una
"función".

Los principios SOLID los veo difíciles de cumplir sin contar con un
potente sistema de tipado de datos (y de anotaciones). Supongo que de
ahí tu comentario de que andas despistado con el "duck typing" de
python. Reconozco tener cierto escepticismo con este tipo de
principios. La teoría de tipos está evolucionado tanto últimamente con
los tipos genéricos y tipos cotravariantes que dejarían a algunos de
los principios SOLID, si no desfasados, al menos necesitados de
revisión.

Volviendo a python, el problema no es el "duck typing" si no que la
unificación de tipos y clases está incompleta. Existe un modelo de
tipos y existe un conjunto de clases abstractas (ABC) a falta de
completar.

Pero no quiero alargarme más y me voy a centrar en tus preguntas.
Antes una aclaración:

Una forma de reconciliar la OOP y la programación funcional es
considerar las funciones como si fueran "clases" y la ejecución de una
función como si fuera una "instancia" de la clase (lo que se conoce
como "clausura"). De hecho, para instanciar una clase se usa el mismo
interface que las funciones (mirar help(callable)).


> Me surgen dos preguntas:
>
> 1. (Menos importante) Cada vez que quiero una propiedad de uno de estos
> objetos la calculo con la fórmula matemática y la devuelvo. Por ejemplo,
> defino un `IsentropicFlow` y puedo obtener de él ciertas propiedades en
> función de otro argumento `M`, el número de Mach incidente.
>
> Pero aquí tengo otro ejemplo, los objetos `NormalShock`, que representan
> ondas de choque:
>
> https://github.com/Pybonacci/scikit-aero/blob/master/skaero/gasdynamics/shocks.py#L22
>
> donde las propiedades vienen determinadas por las variables que definen el
> `NormalShock`, nada más.
>
> *Pregunta*: Si no dependen de argumentos extra, ¿tal vez sería mejor
> calcularlas en `__init__`, almacenarlas y simplemente devolver los valores
> cuando se pidan? Si es así, ¿hasta qué punto tiene sentido definir una
> clase, cuando prácticamente podría conseguir lo mismo utilizando un
> diccionario, o un `namedtuple`?

En lugar de pensar primero en orientarlo a objeto, piensa antes en
cómo será reutilizado. Si estas clases no van a ser modificadas y su
única variación de comportamiento se regula con atributos o
parámetros, entonces puede que no necesites clases. Una función
"factoría" haría lo mismo, o sea, una función que devuelva el objeto
adecuado a partir de los argumentos. Si es preciso, usa funciones y
clases como parámetros. Los diccionarios son estupendos para
simplificar la toma de decisiones. Las propiedades que dependen de un
argumento podrían quedar mejor como "funciones currificadas"
(funciones 'partial' para el módulo 'functools').


> 2. (Más importante) Digamos que otro tipo de objetos, `ObliqueShock` (ondas
> de choque oblicuas) vienen caracterizados por dos variables: `M` (número de
> Mach incidente) y `beta` (ángulo de la onda de choque). Definido un
> `ObliqueShock`, puedo obtener su ángulo de deflexión correspondiente,
> `theta`, a través de una relación matemática. Sin embargo si conozco `M` y
> `theta`, esa relación se tiene que resolver iterativamente para hallar
> `beta`, y además hay dos valores posibles entre los que tengo que
> discriminar.
>
> Como me interesa, por motivos prácticos, disponer de una manera de
> instanciar `ObliqueShock` dados `M` y `theta`, mi aproximación al problema
> ha sido crear una función que:
>
> * Recibe como argumentos `M`, `theta` y un argumento booleano que discrimina
> entre las dos soluciones posibles.
> * Comienza un proceso iterativo: construye un `ObliqueShock` con `M` dado y
> `beta` cualquiera; si el `theta` resultante es el dado, devuelvo ese
> `ObliqueShock`, en caso contrario sigo iterando.
>
> Una idea parecida está recogida en la función `mach_from_area_ratio`:
>
> https://github.com/Pybonacci/scikit-aero/blob/master/skaero/gasdynamics/isentropic.py#L25
>
> que itera sobre un `IsentropicFlow`.
>
> *Pregunta*: ¿esto va bien? Supongo que sería una idea tipo «factoría» de
> clases. La razón del embrollo es no escribir la misma ecuación «fuera» y
> «dentro» de la clase, teniendo así que repetir código.

Es justo lo que te sugería. Una vez encapsulada la ecuación como una
función, pásala como argumento donde sea necesario. Si no te gusta
polucionar el espacio de nombres, agrupa las funciones en módulos y
paquetes. Cambiando un import basta para probar un nuevo conjunto de
ecuaciones.


Más información sobre la lista de distribución Python-es