aprende python

Principios SOLID en Python

El objetivo de este artículo es que el lector aprenda aplicar los principios SOLID con el lenguaje Python. SOLID es un acrónimo creado por Michael Feathers para los principios publicados por Robert C. Martin, en su libro Agile Software Development: Principles, Patterns, and Practices.

Se trata de cinco principios de diseño orientado a objetos que nos ayudarán a crear mejor código, más estructurado, con clases de responsabilidad más definida y más desacopladas entre sí:

  • Single Responsibility: Responsabilidad única.
  • Open/Closed: Abierto/Cerrado.
  • Liskov substitution: Sustitución de Liskov.
  • Interface segregation: Segregación de interfaz.
  • Dependency Inversion: Inversión de dependencia.

Es importante resaltar que se trata de principios, no de reglas. Una regla es de obligatorio cumplimiento, en cambio, los principios son recomendaciones que pueden ayudar a hacer las cosas mejor. Además, siempre puedes encontrar algún contexto en el que te los puedas saltar, lo importante es hacerlo de forma consciente.

Single Responsibility Principle (SRP)

Nunca debería haber más de un motivo por el cual cambiar una clase Click Para Twittear

El primero de los cinco principios, single responsibility principle o en castellano, principio de responsabilidad única, viene a decir que una clase debe tener tan solo una única responsabilidad. A finales de los 80, Kent Beck y Ward Cunningham, ya aplicaban este principio mediante tarjetas CRC (Class, Responsibility, Collaboration) con las que detectaban responsabilidades y colaboraciones entre clases.

El principio responsabilidad única no se basa en diseñar clases con un sólo método, sino éstas tan sólo deberían tener una fuente de cambio. En otras palabras, aquellas clases que se vieran obligadas a cambiar ante una modificación en la base de datos y a la vez ante un cambio en la lógica de negocio, tendría más de un motivo para cambiar, es decir, más de una responsabilidad.

Este principio se suele incumplir  cuando en una misma clase están involucradas varias capas de la arquitectura. Veamos un ejemplo:

class Vehicle(object):
    def __init__(self, name):
        self._name = name
	self._persistence = MySQLdb.connect()
        self._engine = Engine()

    def getName():
        return self._name()

    def getEngineRPM():
        return self._engine.getRPM()

    def getMaxSpeed():
        return self._speed

    def print():
        return print ‘Vehicle: {}, Max Speed: {}, RMP: {}’.format(self._name, self._speed, self._engine.getRPM())

A primera vista se puede detectar que estamos mezclando tres capas muy diferenciadas: la lógica de negocio,  la lógica de presentación y la lógica de persistencia. Además estamos instanciando la clase engine dentro de vehicle, así que también nos estamos saltando el principio de inversión de dependencias.

Una solución para el problema de las múltiples responsabilidades de la clase anterior, podría pasar por abstraer dos clases, como por ejemplo VehicleRepository para manejar la persistencia y VehiclePrinter para gestionar la capa de presentación.

class Vehicle(object):
    def __init__(self, name, engine):
        self._name = name
        self._engine = engine

    def getName(self):
        return self._name()

    def getEngineRPM(self):
        return self._engine.getRPM()

    def getMaxSpeed(self):
        return self._speed


class VehicleRepository(object):
    def __init__(self, vehicle, db)
        self._persistence = db
        self._vehicle = vehicle


class VehiclePrinter(object):
    def __init__(self, vehicle, db)
        self._persistence = db
        self._vehicle = vehicle
    
    def print(self):
        return print ‘Vehicle: {}, Max Speed: {}, RMP: {}’.format(self._vehicle.getName(), self._vehicle.getMaxSpeed(), self._vehicle.getRPM())

En este caso se veía muy claro lo que teníamos que hacer para aplicar correctamente el SRP, pero muchas veces los detalles serán más sutiles y probablemente no los detectarás a la primera. No te obseciones simplemente aplica el sentido común.

Open-Closed Principle (OCP)

Todas las entidades software deberían estar abiertas a extensión, pero cerradas a modificación Click Para Twittear

El segundo de los principios,  Open-Closed (Abierto/Cerrado), cuyo nombre se lo debemos a Bertrand Meyer. Básicamente nos recomienda que cuando se pretende introducir un nuevo comportamiento en un sistema existente, en lugar de modificar las clases antiguas, se deben crear nuevas mediante herencia y redefinición de los métodos de la clase padre (polimorfismo), o inyectando dependencias que implementen el mismo contrato.

Este principio promete mejoras en la estabilidad de tu aplicación al evitar que los objetos existentes cambien con frecuencia, lo que también hace que las cadenas de dependencia sean un poco menos frágiles ya que habría menos partes móviles de las que preocuparse. Este principio aplica bien a la hora trabajar con un framework o con código legacy, evidentemente si el código lo has hecho tu o tu equipo, refactoriza.

Un buen ejemplo en el que se aplica este principio es el que veíamos en este artículo a la hora de extender el user de django desde la clase AbstractUser

from django.db import models

from django.db import models
from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    bio = models.TextField(max_length=500, blank=True)
    location = models.CharField(max_length=30, blank=True)
    birth_date = models.DateField(null=True, blank=True)

Liskov Substitution Principle (LSP)

Las funciones que utilicen punteros o referencias a clases base deben ser capaces de usar… Click Para Twittear

El principio de Sustitución de Liskov, obtiene su nombre de Barbara Liskov. Este principio está relacionado con el anterior en lo que a la extensibilidad de las clases se refiere, y viene a decir que dada una instancia de una clase B, siendo esta un subtipo de una clase A, debemos poder sustituirla por una instancia de la clase A sin mayor problema.

En los lenguajes orientados a objetos de tipado estático, este principio describe principalmente una regla sobre una relación entre una subclase y una superclase. Cuando hablamos de lenguajes de tipado dinámico como Python, nos interesa qué mensajes responde ese objeto en lugar de a qué clase pertenece.

Un ejemplo que ilustra bastante bien la importancia de este principio es el de tratar de modelar un cuadrado como la concreción de un rectángulo:

class Rectangle(object):

    def getWidth(self)
        return _width
 
    def setWidth(self, width) 
        self._width = width
    
    def getHeight(self)
        return _height
 
    def setHeight(self, height) 
        self._height = height
 
    def calculateArea(self) 
        return self._width * self._height;
 
class Square(Rectangle):
    def setWidth(self, width) 
        self._width = width
        self._height = height

    def setWidth(self, height) 
        self._height = height
        self._width = width

class TestRectangle(unittest.TestCase):
 
    def setUp(self):
        pass
 
    def test_calculateArea(self):
        r = Rectangle()
        r.setWidth(5);
        r.setHeight(4);
        self.assertEqual(r.calculateArea(), 20)

Si tratamos de sustituir en el test el rectángulo por un cuadrado, el test no se cumple, ya que el resultado sería 16 en lugar de 20. Estaríamos por tanto violando el principio de sustitución de Liskov.

La regla principal para no violar este principio es básicamente tratar de heredar lo menos posible o  no usar los mixins a menos que se esté bastante seguro de que el comportamiento que está implementando no interferirá con las operaciones internas de sus ancestros.

Interface Segregation Principle (ISP)

Los clientes no deberían estar obligados a depender de interfaces que no utilicen. Click Para Twittear

El principio de segregación de la interfaz nos indica que ninguna clase debería depender de métodos que no usa. Cuando creemos interfaces (clases en lenguajes interpretados como Python) que definan comportamientos, es importante estar seguros de que todas los objetos que implementen esas interfaces/clases se vayan a necesitar, de lo contrario, es mejor tener varias interfaces/clases pequeñas.

Este principio está íntimamente relacionado al de responsabilidad única, ya que cuando usamos SRP estamos a la vez aplicando este principio, podría decirse que el principio de segregación de la interfaz nos ayuda a mantener el principio de responsabilidad única. Cuando se da el caso de que una clase o interfaz tiene más métodos de los que una necesita para sí mismo, es muy probable que sirva a objetos con responsabilidades diferentes.

Una forma de no violar este principio en Python es aplicando duck typing. Este concepto viene decir que los métodos y propiedades de un objeto determinan su validez semántica, en vez de su jerarquía de clases o la implementación de una interfaz específica.

Dependency Inversión Principle (DIP)

Los módulos de alto nivel no deben depender de módulos de bajo nivel. Ambos deben depender de abstracciones. Click Para Twittear Las abstracciones no deben depender de detalles. Los detalles deben depender de abstracciones. Click Para Twittear

Quinto y último de los principios, la inversión de dependencia, cuyo objetivo principal es desacoplar nuestro código de sus dependencias directas.  Este principio viene a decir que las clases de las capas superiores no deberían depender de las clases de las capas inferiores, sino que ambas deberían depender de abstracciones. A su vez, dichas abstracciones no deberían depender de los detalles, sino que son los detalles los que deberían depender de las mismas.

La inversión de dependencias da origen a la inyección de dependencias. Este concepto se basa en hacer que una clase A inyecte objetos en una clase B en lugar de dejar que sea la propia clase B la que se encargue de instanciar el objeto. Veamoslo con el ejemplo del vehiculo:

class Engine(object):
    def __init__(self):
        pass
      
    def accelerate(self):
        pass
 
    def getRPM(self):
        currentRPM = 0
        #...
        return currentRPM
 
class Vehicle(object):
    def __init__(self):
        self._engine = Engine()
        
    def getEngineRPM(self)
        return self._engine.getRPM();

El código anterior ilustra la manera “habitual” de definir la colaboración entre clases. Como podemos observar, existe una clase Vehicle que contiene un objeto de la clase Engine. La clase Vehicle obtiene las revoluciones del motor invocando el método getEngineRPM del objeto Motor y devolviendo su resultado. Este caso se corresponde con una dependencia, el módulo superior Vehicle depende del módulo inferior Engine, lo cual genera un código tremendamente acoplado y dificil de testear.

Para desacoplar la dependencia Engine de Vehicle debemos hacer que la clase Vehicle deje de responsabilizarse de instanciar el objeto Engine, inyectándolo como parámetro al constructor, evitando así que la responsabilidad recaiga sobre la propia clase. De este modo desacoplamos ambos objetos, quedando la clase tal que así:

class Vehicle(object):
    def __init__(self, engine):
        self._engine = engine
        
    def getEngineRPM(self)
        return self._engine.getRPM();

Ahora la responsabilidad de instanciar la clase engine ya no corresponde a la clase vehicule. Además, en Python, el parámetro engine no tiene porqué ser una instancia de la clase engine, podría ser cualquier objeto siempre y cuando tuviera un método getRPM(). Esta es una ventaja inherente a los lenguajes dinámicos, ya que nos permiten aprovechar el duck typing y evitar así tener que definir el tipo de la dependencia.

Hasta ahora no he comentado nada de sobre los contenedores de inversión de control, aunque no es necesario para hacer inyección de dependencias, puede ser interesante su uso, sobre todo en los lenguajes de tipado estático. En los lenguajes dinámicos los contenedores de inversión de control pierden su interés ya que en los constructores de las clases no está especificado el tipo de las dependencias y si quieren usar estarás obligado a definir de forma un tanto forzada las dependencias entre los objetos para que el contenedor pueda componer unos con otros.

Como hemos podido ver, la inyección de dependencias por si misma nos ayuda a crear clases con responsabilidad más definida, más estructuradas y desacopladas entre sí.

Resumen

Los prinpicios SOLID, pese al abuso que se hace últimamente de ellos, son una herramienta que nos ayudan a comprender mejor el diseño de software orientado a objetos. Si los aplicas con sentido común, sin dogmatizarlos, estarás en mejores condiciones para encontrar optimas soluciones a los problemas software.

Si te ha gustado el artículo, valora y comparte en tus redes sociales. No dudes en comentar dudas, aportes o sugerencias, estaré encantado de responder.
Este artículo se distribuye bajo una Licencia Creative Commons Reconocimiento-CompartirIgual 4.0 Internacional (CC BY-SA 4.0)

licencia-cc

aprende python

Aprender Python en 10 minutos

Antes de empezar tengo que advertirte que ningún lenguaje de programación, por simple que sea, puede aprenderse en profundidad en tan poco tiempo, a no ser que se requiera de experiencia previa en otros lenguajes. Dominar la programación precisa de experiencia, lo cual a su vez requiere de un tiempo mínimo que permita afianzar las estructuras mentales necesarias para entender la secuencia lógica a seguir para desarrollar un programa o proyecto de software.

El objetivo de este artículo no es enseñar a programar, sino tratar de exponer en 10 minutos los elementos más importantes del lenguaje Python, sería algo así como una mezcla entre un tutorial y una cheatsheet. Haré uso de una REPL para exponer los ejemplos, con lo que podrás modificar y jugar con ellos sin tener que salir de la entrada. Es totalmente recomendable seguir el artículo en un ordenador o tablet, ya que la REPL por limitaciones de espacio no es tan cómoda de utilizar en un móvil.

Características de Python

Python un lenguaje multiparadigma, que soporta orientación a objetos, programación imperativa y, en menor medida, programación funcional. Es interpretado, de tipado dinámico y multiplataforma.

Lo básico de Python

La sintaxis de Python es extremadamente “limpia“, no se requiere de ningún caracter que indique el final de una secuencia y los bloques se definen a través de la indentación del código. Los comentarios de una sola línea se definen con el carácter almohadilla (#) y las cadenas de multiples líneas (“””) se suelen emplear para escribir comentarios multilínea.

Los operadores aritméticos +, y / significan en Python lo mismo que en matemáticas. El asterisco (*) es el símbolo para la multiplicación, el % se usa para obtener el módulo// para división entera y ** es el símbolo para las potencias. También disponemos de los operadores incrementar (+=) y decrementar (-=).

Existen tres operadores lógicos: and, or, y not. La semántica (significado) de estos operadores es similar a sus significados en inglés (en español “y”, “o” y “no”).

La asignación de valores, como en la mayoría de lenguajes, se realiza con el símbolo “igual” (=). La doble igualdad (==) se usa para comprobar que dos valores son iguales, además disponemos de estos operadores de comparación: distinto que (!=), mayor que (>), menor que (<), mayor o igual (>=) y menor o igual (<=).

Tipos de datos

Los tipos de datos básicos en Python son los numéricos formados por enteros, los reales y los complejos; las cadenas de texto y los booleanos. La función type nos devuelve el tipo:

Estructuras de control de flujo

Las sentencias de control de flujo, son bloques de código en los que se agrupan instrucciones de manera controlada. Por un lado tenemos las estruturas condicionales (if, if .. else, if ..elif ..else) y por otro las estructuras iterativas (for, while).

Funciones

Una función no es más que un bloque de código reutilizable encargado de realizar una determinada tarea. Para definir una función en Python debemos utilizar la palabra reservada “def” seguido del nombre de la función y los parámetros los indicamos entre parentesis. Veamos varios ejemplos:

Funciones, parametros *args y **kwargs

En el ejemplo anterior hemos visto varias formas de definir y ejecutar a las funciones en Python, ya sea con parámetros por con valores por defecto o keywords como argumentos. Nos faltaría ver como llamarlas con colecciones como argumentos, una forma muy común de pasar valores a las funciones en Python, y que en muchas ocasiones cuesta entender. Para ello debemos tener claro que las tupla como colección de argumentos se define con *args y el diccionario con **kwargs. Veamos un ejemplo en la REPL:

Manipulación de cadenas

Las cadenas en Python pueden definirse entre comillas simples o dobles. En la REPL se muestra un ejemplo de la mayoría de las operaciones que podemos realizar ellas:

Estructuras de datos (colecciones)

Las colecciones son un tipo de datos diseñados específicamente para agrupar objetos y llevar a cabo tareas con ellos.  Las estructuras de datos más utilizadas en Python son las listas, tuplas (listas inmutables) y los diccionarios, aunque personalmente también suelo usar mucho los conjuntos (sets).

No todas las estructuras de datos exponen exactamente las mismas operaciones, por ejemplo una lista no deja buscar por clave (si bien sí por índice) y un conjunto no deja buscar ni por una cosa ni por otra.

El siguiente playground está dividido en tres ficheros, main.py desde el que se importa listas.py y diccionarios.py, para ver los resultados de uno u otro, elimina o comenta su respectivo import.

Clases y objetos

En programación orientada a objetos (POO), un objeto es una entidad que agrupa un estado y una funcionalidad relacionada. El estado se define a través de las variables denominadas atributos y la funcionalidad a través de funciones denominadas en POO cómo métodos. Por otro lado, una clase, no es más que una plantilla genérica a partir de la cuál instanciamos los objetos. Dicho de otra manera, una clase es una abstracción en la que se define el comportamiento que va a tener el objeto.

En Python, las clases se declaran mediante la palabra reservada class seguida del nombre de la clase, la clase base de la cual hereda, si no extiende de ninguna debe hacerlo de object; a continuación dos puntos (:), luego el indentado y el cuerpo de la clase.

El método constructor de la clase se define con la palabra clave __init__ y al igual que el resto de métodos definidos en la clase recibe como primer parámetro self, el interprete de python requiere de este argumento para referenciarlos como métodos de instancia.

Excepciones

No soy muy fan de usar excepciones, pero es cierto que con Python hay momentos en los que su uso no se puede obviar. Las excepciones en Python se manejan esencialmente con try-except:

Depurando con PDB

El depurador por defecto de python es pdb , nos permite inspeccionar nuestro código de forma interactiva. La forma más simple de utilizarlo es ejecutando pdb.set_trace():

import pdb
a = "aaa"
pdb.set_trace()
b = "bbb"
c = "ccc"
final = a + b + c
print final

Los comandos básicos del depurador son: “n“(next) para ejecutar la siguiente sentencia, para mostrar el valor de las variables “p“(print), si queremos ir paso a paso dentro de las subrutinas utilizariamos “s“(step), y por último “q”(quit) para salir.

Guia de estilo – PEP8

En Python existen las denominadas PEP’s (Python Enhancement Proposals), en concreto la PEP 8 hace referencia a las convenciones de estilo de programación en Python.
Entre las convenciones principales, destacan:

  • Usar 4 espacios para indentar.
  • Tamaños de línea con un máximo de 79 caracteres.
  • Las funciones y las clases se deben separar con dos lineas en blanco, mientras que los métodos de clase solo con uno.
  • Los import deben de estar separados uno en cada línea. Se permite: from urllib2 import urlopen, Request
  • Las sentencias import deben de estar siempre en la parte superior del archivo agrupadas de la siguiente manera:
    • Librería estándar
    • Librerías de terceros
    • import’s de la aplicación local
  • Usar espacios alrededor de los operadores aritméticos, a excepción de cuando forman parte de los argumentos de una función.
  • No se deben de realizar comentarios obvios
  • No se deben comparar booleanos mediante ==

Resumen

Este tutorial no pretende ser una guía exhaustiva sobre Python, aunque he condensado bastante información en poco más de 1000 palabras, me he dejado muchísimos elementos en el tintero, cómo expresiones regulares, ficheros, testing, POO, elementos de programación funcional. Aunque continuaré escribiendo sobre Python en el futuro. En el caso de que quieras seguir profundizando en este magnifico lenguaje te recomiendo este excelente libro: Python 3. Los fundamentos del lenguaje.

Espero haber facilitado tu transición a Python, y si ya lo conocías espero que el artículo te sirva como referencia. Si te ha gustado la entrada valora y comparte en tus redes sociales. No dudes en comentar dudas, aportes o sugerencias, estaré encantado de responder.
Este artículo se distribuye bajo una Licencia Creative Commons Reconocimiento-CompartirIgual 4.0 Internacional (CC BY-SA 4.0)

licencia-cc