17 de mayo de 2016

35. Mis propios módulos.

Generando un proyecto


Vamos a generar un "proyecto". Un proyecto es una serie de programas, cada uno en un fichero .py, que actúan de manera coordinada. Cada fichero o programa .py se denomina en Python módulo.
Empezamos por crear una carpeta en cualquier lugar denominada proyecto. Dentro de esta carpeta, a su vez, creamos una subcarpeta denominada clases.
Dentro de la carpeta proyecto se encuentra mi módulo principal, denominado miprograma.py
 
La carpeta clases es lo que se conoce como paquete en Python. Dentro de ella, se pueden organizar varios módulos que podré llamar desde otros módulos y, en particular, desde el programa principal. Dentro de la carpeta clases se encuentra...:
  1. El módulo misclases.py, que contendrá la clase "fraccion"
  2. El módulo vacío __init__.py ; este módulo no sirve para nada, está vacío, pero indica a Python que la carpeta donde se encuentra está formando un paquete.
  3. Archivos .pyc , que no necesitamos tocar, ya que los crea el mismo programa.
  4. La carpeta funciones, una subcarpeta que se comportará como paquete para el módulo misclases.py. Dentro de ella tenemos:
    1. El módulo vacío __init__.py que indicará que funciones es un paquete.
    2. El módulo misfunciones.py
    3. Archivos .pyc
= = =
¿Qué programa contiene cada módulo? Veámoslos por partes
1.- Módulo misfunciones
En este módulo sólo hay una función, la que descompone un número en sus factores devolviendo una lista. Del programa "fraccion" del tema anterior, mejora el que es capaz de detectar si un número es negativo y calcularle sus factores, incluyendo el factor del signo (-1)
# -*- coding: utf-8 -*-

#  =========================
# Funciones
# =========================
    
# Devuelve lista con los factores descompuestos
def descomponer(num):
    lista = []
    if num<0:
        lista.append(-1)
        num = -1 * num
    pivote = 2
    while pivote<num:
        if num % pivote != 0:
            pivote = pivote + 1
        else:
            #introduce el pivote en la lista
            lista.append(pivote)
            #divide num entre el pivote
            num = num / pivote
            # print num, pivote
    #Cuando acaba introduce el número
    lista.append(num)
    return lista
= = =
2.- Módulo misclases
Este módulo, en un nivel superior, define la clase fraccion. Al principio observamos la línea import funciones.misfunciones as fun , como novedad. Esta línea importa el módulo misfunciones del paquete funciones y se referirá a él como fun. De hecho, en las líneas 30 y 31 se utiliza la función descomponer como fun.descomponer(...); se está llamando a la función descomponer del paquete funciones.
# -*- coding: utf-8 -*-

import funciones.misfunciones as fun 

# =========================
# Clases
# =========================

class fraccion(object):
    
    def __init__(self, x0 = 1, y0 = 1):
        try:             
            self.numerador = x0
            self.denominador = y0
            test = x0 / y0
            return 
        except ZeroDivisionError:
            raise 
        
    def __str__(self):
        if self.denominador >1:
            return str(self.numerador) + "/" + str(self.denominador)
        elif self.denominador == 1:
            return str(self.numerador)
        else:
            return "No es posible representar"
        
    def reducir(self):
        # Obtiene componentes
        listanumerador = fun.descomponer(self.numerador)
        listadenominador = fun.descomponer(self.denominador)
        # bucle que empieza a reducir        
        listaIntermedia = []
        for i in listanumerador:
            if i in listadenominador:
                listadenominador.remove(i)
                listaIntermedia.append(i)
        # print "Numerador: ",listanumerador
        # print "Intermedia: ",listaIntermedia
        # print "Denominador: ",listanumerador
        for i in listaIntermedia:
            listanumerador.remove(i)
        # convierte la fracción a la reducida
        self.numerador = 1
        for i in listanumerador:
            self.numerador *= i
        self.denominador = 1
        for i in listadenominador:
            self.denominador *= i
        # print listanumerador, listadenominador, listaIntermedia
        return 
        
    def __add__(self, otra):
        numerador = self.numerador*otra.denominador + self.denominador*otra.numerador
        denominador = self.denominador * otra.denominador
        suma = fraccion(numerador, denominador)
        suma.reducir()
        return suma
        
    def __mul__(self, otra):
        numerador = self.numerador * otra.numerador
        denominador = self.denominador * otra.denominador
        multiplicar = fraccion(numerador, denominador)
        multiplicar.reducir()
        return multiplicar
        
    def __sub__(self,otra):
        resta = fraccion(1,1)
        resta.numerador = -1 * otra.numerador
        resta.denominador = otra.denominador
        restar = self + resta
        restar.reducir()
        return restar
        
    def inversa(self):
        a = fraccion(self.denominador,self.numerador)
        a.reducir()
        return a
        
    def __div__(self, otra):
        dividir = otra.inversa()
        division = self * dividir
        division.reducir()
        return division
= = =
3.- Módulo o programa principal miprograma
Por último, analizo el programa principal. En él, la primera línea (3) escrita sería import clases.misclases as miclase , llamando al módulo misclases del paquete clases y llamándolo por el alias miclase. En la línea 16, dentro de la función introducir() se llama a la clase fraccion que estaría en miclase. Es suficiente con esta sola llamada ya que declarando los objetos, los métodos están implícitos a ellos.
# -*- coding: utf-8 -*-

import clases.misclases as miclase

# =========================
# Funciones
# =========================

def introducir():
    t = 5
    while t>0: 
        try:
            print ("Tienes %d oportunidades de introducir un dato correcto: ") % (t)
            numerador = int(raw_input("Introduce numerador: "))
            denominador = int(raw_input("Introduce numerador: "))
            a = miclase.fraccion(numerador, denominador)
            return a
        except ZeroDivisionError:
            print ("El denominador no puede ser cero\n")
            t = t - 1
        except TypeError:
            print ("No has introducido un tipo correcto. Debe ser entero\n")
            t = t - 1
        except ValueError:
            print ("No has introducido un valor entero\n")
            t = t - 1
        except Exception, ex:
            print ("Error producido :") + str(ex)
            raise
    raise ValueError, "Has tenido cinco oportunidades de introducir un dato correcto"


# =========================
# Programa principal
# =========================


print "Introduce fracción primera: "
f1 = introducir()
print "primera fracción:", f1, "\n"

print "Introduce fracción segunda: "
f2 = introducir()
print "segunda fracción:", f2, "\n"
        
f1.reducir()
print "primera fracción reducida:", f1, "\n"
f2.reducir()
print "segunda fracción reducida:", f2, "\n"

print "Inversa 1ª fracción reducida:", f1.inversa(), "\n"
print "Inversa 2ª fracción reducida:", f2.inversa(), "\n"

print "Suma de las dos: ", f1 + f2, "\n"
print "Resta de las dos: ", f1 - f2, "\n"
print "Multiplicar las dos: ", f1 * f2, "\n"
print "Dividir las dos: ", f1 / f2, "\n"
= = =
4.- Esquema de árbol
 arbol_programa_python
= = = 

Hablando de módulos

Si nos paramos un momento a pesar, nos daremos cuenta que escribir cualquier tipo de programa en un sólo fichero puede ser largo y sumamente tedioso. Hasta ahora nuestros programas han sido "cortos". Ocupaban relativamente poco espacio pero... ¿y si se complican? Necesariamente tenemos que dividirlos por partes y ordenarlos. Para ello se implementan los paquetes y los módulos; los primeros para mantener una jerarquía o un orden y, los segundos para contener código de programación específico.
El ejemplo anterior nos ha servido para introducirnos en la estructura de un proyecto en Python. Vamos ahora con algo de "teoría" que nos ayude a comprender mejor el potencial de estos conceptos.
Imagina que en mi programa principal tengo el siguiente código
. . . 
import moduloA          # importar un módulo que no pertenece a un paquete 
import paquete.moduloB # importar un módulo que está dentro de un paquete
import paquete.subpaquete.moduloC
. . . 
Si simplemente escribo import moduloA, el módulo se deberá encontrar en la misma carpeta que nuestro programa principal y no pertenece a un paquete concreto. Si escribo import paquete.moduloB , importaré el moduloB que está dentro de la carpeta paquete y si escribo import paquete.subpaquete.moduloC , estoy accediendo al moduloC que se encuentra dentro del subpaquete que a su vez está dentro del paquete.
Lo importante es, que, encontrándome donde me encuentro, cada vez que accedo dentro de un paquete o carpeta, tengo que ir poniendo la ruta al módulo separando los paquetes por puntos. En el ejemplo anterior, desde el módulo misclases sólo necesitaba indicar funciones.misfunciones para acceder al módulo misfunciones. Es ir buscando la ruta...
Dentro del programa principal... ¿cómo llamo a una función determinada de un módulo importado? Pues por ejemplo, si es la función contar del moduloB
cuenta = paquete.moduloB.contar()
La parte de la ruta a la función paquete.moduloB se denomina namespace. Un namespace es el nombre que se ha indicado tras la palabra import.
Los namespaces pueden denominarse o acortarse por alias...
. . . 
import paquete.moduloB as modB
. . . 
cuenta = modB.contar()
Esto favorece las llamadas a las distintas funciones / clases / variables de los módulos. En el ejemplo, modB es un alias.
= = =
También es posible llamar solamente una función o una clase por separado, con el método from. Del ejemplo anterior de fracciones, en el programa principal podría haber escrito
# antes estaba: import clases.misclases as miclase
from clases.misclases import fraccion
Lo cual hubiera simplemente importado la clase "fraccion". En la línea 16 del código, ya sólo hubiese tenido que escribir a = fraccion(numerador, denominador) sin la referencia del alias anterior.
También podría haber usado un alias en este caso...
from clases.misclases import fraccion as fra
Lo cual es conveniente si desde varios módulos llamamos elementos que podrían tener el mismo nombre. Por ejemplo...
from clases.misclases import fraccion as fra, sumar as sum1
from operaciones.misoperaciones import logaritmo as log, sumar as sum2
Muy poco recomendado, pero posible, es llamar a todos los elementos de un módulo con la expresión "*"
from paquete.modulo1 import *
Recuperándose cada uno con su nombre original, ya que no hay posibilidad de alias.

No hay comentarios:

Publicar un comentario