===== Programación con Python =====

{{ apuntes:python.png?300 }}

Este anexo no proporciona todo lo necesario para aprender a programar desde cero sino proporcionar algunas explicaciones y ejemplos para comprender las diferencias que puede haber con respecto a Java, que es el lenguaje que ya conocéis. Así, en los diferentes apartados se encuentran, sobre todo, ejemplos y estructuras muy utilizadas. Estos ejemplos y estructuras nos permitirán, con las explicaciones y ejercicios de clase, empezar a programar en este lenguaje para abordar más adelante el desarrollo de aplicaciones con el framework **Django**.

Si se busca algo más de detalle sobre algún aspecto del lenguaje se pueden consultas los materiales de [[extra:referencias|referencia]] donde hay tutoriales y libros mucho más completos donde se explica como programar en Python con todo detalle.

===== Estructura de un programa en Python =====

Antes de comenzar a ver los diferentes aspectos del lenguaje Python vamos a analizar algunos fragmentos de código de forma que nos ayudarán a echar un vistazo rápido a todo el lenguaje, aunque luego nos adentremos con más detalle en cada parte.

Teniendo ya nociones de programacíón y además de un lenguaje Orientado a Objetos como Java será bastante sencillo hacerse una idea de como es Python puesto que también se trata de un lenguaje de Programación Orientado a Objetos y posee muchas características similares, por lo que la transición a este nuevo lenguaje será muy rápida.

Python dispone de una [[https://www.python.org/dev/peps/pep-0008/|Guia de estilo]] que conviene leerse para adecuarse a las convenciones que todo el mundo tiene que adoptar.

<code python>
# Programa de ejemplo que lee el contenido de un directorio 
# y lo imprime por pantalla
import os

PATH = '.'

def leer_ficheros(directorio=None):
    '''
    Lee un directorio y devuelve el listado de ficheros del mismo
    :return: El listado de ficheros
    '''
    if directorio:
        ruta = directorio
    else:
        ruta = PATH
    
    print("Leyendo los ficheros de %s" % ruta)
    ficheros = os.listdir(ruta)
    print("Se ha terminado de leer", ruta)

    return ficheros

def listar_ficheros(ficheros):
    '''
    Imprime por pantalla un listado de ficheros
    :param ficheros: 
    '''
    for fichero in ficheros:
        print(fichero)

def otro_metodo():
    pass        # Pendiente de implementar

listar_ficheros(leer_ficheros())
</code>

Viendo el ejemplo anterior podemos hacernos una idea de cómo es la sintaxis más básica de Python. Conviene además tener en cuenta los siguientes aspectos:

  * Sólo se permite una instrucción por línea y no hay que marcar el final con ningún caracter
  * La indentación del código marca el ámbito por lo que no es simple adorno
  * Puesto que la indentación del código marca el ámbito no hay caracter de inicio y fin del mismo
  * No hay que declarar las variable. Python decidirá el tipo de las mismas según el valor asignado
  * Se puede comprobar si existe una variable comprobando si está definida en el diccionario ''locals()''
<code python>
>>> nombre = 'Santi'
>>> 'nombre' in locals()
True 
</code>
  * Para los nombres compuestos de elementos del código se usa el estilo ''listar_ficheros'', palabras en minúscula y separadas por un guión bajo
  * El objeto nulo viene representado por la palabra reservada ''None''. Hay que tener en cuenta que no es lo mismo que una variable contenga el valor nulo a que esa variable no haya sido declarada
<code python>
>>> nombre = None
>>> nombre
>>>
</code>
Asi, si la variable no se ha declarado y se intenta hacer referencia a ella este será el error que aparecerá
<code python>
>>> nombre
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'nombre' is not defined
</code>
  * Técnicamente no hay diferencia entre comillas dobles (''"'') y simples (''''') pero normalmente las primeras se usan para hacer referencia a contenido que es visible para el usuario y las segundas para el resto de casos
<code python>
cadena = 'Texto de la cadena'
print("La cadena es %s", cadena)
</code>
  * Podemos definir métodos con parámetros opcionales simplemente dándoles un valor por defecto en la propia definición del método
<code python>
. . .
def sumar_numeros(a, b=0, c=0):
    return a + b + c
. . .
</code>

<code bash>
>>> sumar_numeros(1)
1
>>> sumar_numeros(1, 4)
5
>>> sumar_numeros(1, 5, 7)
13
</code>

===== Cómo ejecutar un programa =====

Puesto que Python es un lenguaje interpretado, por un lado no tenemos que compilar el código y por otro lado tampoco podemos ejecutarlos directamente. Es por eso que tenemos que utilizar el intérprete de comandos y para como argumento el fichero de código que queramos que se ejecute.

<code bash>
santi@zenbook:$ python3 programa.py
</code>

También es posible ejecutar los scripts de //Python// directamente desde la línea de comandos. Simplemente tenemos que poner la línea ''#!/usr/bin/python3'' al inicio del script para que podamos ejecutarlo sin utilizar el intérprete explicitamente. Por ejemplo, si tenemos el siguiente script de ejemplo:

<file python hello.py>
#!/usr/bin/python3

print("Hello!")
</file>

Dándole permisos de ejecución (en el caso de sistemas Linux/OS X), podremos ejecutarlo directamente.

<code bash>
santi@zenbook:$ chmod u+x hello.py
santi@zenbook:$ ./hello.py
Hello!
</code>

===== Entrada por teclado =====

Podemos solicitar al usuario la entrada de información por teclado mediante la instrucción ''input(mensaje)''

<code python>
>>> nombre = input("Introduce tu nombre")
Introduce tu nombre Santiago
>>> apellidos = input("Introduce tu apellido")
Introduce tu apellido Faci
>>> print("Te llamas", nombre, apellidos)
Te llamas Santiago Faci
</code>

También es posible hacer que la entrada del usuario se evalúe como una expresión más y no tal cual la introduzca.

<code python>
>>> suma = input("Introduce una suma")
Introduce una suma 3 + 5
>>> print(suma)
3 + 5
>>> print(eval(suma))
8
</code>
===== Operadores =====

^ Operador     ^ Descripción                          ^ Ejemplo               ^
| +, - ,       | Suma, Resta                          | 3 + 5, 3 - 2          |
| * ,/, %      | Multiplicación, División, Módulo     | 12 * 3, 5 / 6, 12 % 3 |
| <nowiki>//</nowiki>           | División truncada (resultado entero) | 9 <nowiki>//</nowiki> 2 = 4            |
| <nowiki>**</nowiki>           | Exponente                            | 5<nowiki>**</nowiki>2 = 25             |
| +=, -=       | Suma/Resta y asignación              | x += 2 --> x = x + 2  |
| *=, /=       | Multiplicación/División y asignación | x *= 2 --> x = x * 2  |
| <nowiki>**</nowiki>=, <nowiki>//</nowiki>=     | Exponente/División truncada y asignación | x <nowiki>//</nowiki>= 2 --> x = x <nowiki>//</nowiki> 2  |
| or, and, not | Operadores lógicos                   | if not terminado:     |
| in           | Está en la lista                     | if 2 in [1, 2, 3]:    |
| <, <=, >, >=, !=, == | Operadores de comparación    | if x != y:            |

===== Sentencias de control =====

<code python>
if <condicion1>:
    <sentencias1>
elif <condicion2>
    <sentencias2>
. . .
else:
    <sentencias_else>
</code>

<code python>
</code>

<code python>
while <condicion>:
    <sentencias>
    # En cualquier momento podemos romper el bucle
    [break]
[else:
    # Sólo irá aqui si el bucle termina de forma natural
    <sentencias_finales>]
</code>

<code python>
</code>

<code python>
for <variable> in <lista_valores>:
    <sentencias>
    # Opcionalmente podemos romper el bucle
    [break]
    # Opcionalmente podemos continuar con la siguiente iteración
    [continue]
[else:
    <sentencias_finales>]
</code>

<code python>
</code>
===== Tipos de Datos =====

Al no tener que declarar las variables tampoco tenemos que definir manualmente el tipo de las mismas ya que Python decidirá el tipo que convenga en función del valor que le asignemos. Incluso si reusamos una variable asignándole valores de diferentes tipos Python modificará el tipo de dicha variable a medida que sea necesario.

Aún asi, conviene conocer los tipos que existen en el lenguaje puesto que alguna vez nos será útil conocer los valores posibles que puedan tomar e incluso tener que convertir alguna variable a otro tipo para representarla más adelante.

También es muy importante conocer los tipos complejos con los que contamos en Python como listas, diccionarios y otros.

=== Booleano ===

El tipo booleano que ya conocemos y que podrá tomar los valores ''True'' o ''False''

=== Número ===

=== Fechas ===

Para las operaciones con fechas es necesario importar los módulos correspondientes

<code python>
from datetime import datetime, date, time, timedelta
import calendar
. . .
</code>

^ Tipo      ^ Descripción                            ^
| date      | Almacena una fecha(año-mes-dia)        |          
| datetime  | Almacena una fecha con hora (hora-minuto-segundo-milésimas            |          
| time      | Almacena una hora            |           
| calendar  | 

Tanto con tipo //date// como //datetime// y //time// podemos acceder a cada parte de la fecha a través de la propiedad correspondiente y a una serie de métodos que serán de utilidad para trabajar con fechas. A continuación se muestran algunos ejemplos:

<code python>
# Este ejemplo es válido también para date y time
# siempre y cuando accedamos a una propiedad válida
from datetime import datetime
. . .
>>> fecha = date.today()
>>> fecha_con_hora = datetime.today()
>>> fecha_hora.day
6
>>> fecha_hora.month
9
>>> fecha_hora.year
2017
>>> fecha_hora.hour
18
>>> fecha_hora.minute
19
>>> fecha_hora.second
46
>>> fecha_hora.microsecond
211714
</code>

<code python>
>>> from datetime import time, date, timedelta
>>> hora1 = time(14, 1, 0) # 14:01h
>>> hora2 = time(22, 34, 5) # 22:34h y 5 segundos
>>> hora1 < hora2)
True
>>> fecha1 = date.today()
>>> fecha1
datetime.date(2017, 9, 6)
>>> # Con timedelta podemos sumar days, seconds, hours, . . .
>>> fecha2 = fecha1 + timedelta(days=2) # Suma 2 dias a fecha1
>>> fecha2
datetime.date(2017, 9, 8)
>>> fecha1 < fecha2
True
</code>

=== Datos secuenciales ===

En Python se consideran datos secuenciales todos aquellos que están compuestos de una secuencia de elementos siempre y cuando éstos estén ordenados y puedan ser accedidos mediante indices.
  * Cadenas de texto
  * Listas
  * Tuplas

== Cadenas de texto ==

Las cadenas de texto se consideran datos secuenciales porque pueden ser tratados como una lista de caracteres y, por tanto, es posible acceder a cada unos de los caracteres por separado

<code python>
>>> cadena = 'Esto es una cadena de texto'
>>> cadena[1]
's'
>>> cadena[1:4]
'sto'
>>> 'cad' in cadena
True
>>> 'coche' in cadena
False
>>> 'hola' * 4
'holaholaholahola'
>>> 'hola' + 'hola'
'holahola'
</code>

== Lista ==

Una lista en Python es un tipo de datos que almacena una serie de elementos bajo las siguientes condiciones:

  * Están ordenadas
  * Pueden contener objetos arbitrarios
  * Se accede a cada elemento por su posición
  * Un elemento podría ser a su vez otra lista
  * Son de tamaño variable
  * Son mutables, los elementos de la lista pueden cambiar

Las listas se representan en Python con una serie de valores entre corchetes ''[ ]'' separados por comas (''[1, 2, 'Esto es una cadena', 4]'' es una lista).

Veamos algunos ejemplos de operaciones que podemos realizar con listas:

<code python>
>>> lista = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> len(lista)
10
>>> lista[0]  # Primer elemento
1
>>> lista[-1]  # Último elemento
10
>>> lista[0:3]  # Tres primeros elementos
[1, 2, 3]
>>> lista[:3]  # Tres primeros elementos
[1, 2, 3]
>>> lista[:-3]  # Todos los elementos menos los 3 últimos
[1, 2, 3, 4, 5, 6, 7]
>>> lista[0:10:3]  # lista[inicio:fin:paso]
[1, 4, 7, 10]
>>> otra_lista = ['un texto', [1, 2, 3], 50]
>>> otra_lista[1][0]
[1]
>>> 2 in lista  # Comprueba si el elemento está en la lista
True
>>> 2 not in lista
False
>>> 2 * lista  # duplica los elementos de la lista
[1, 2, 'Esto es una cadena', 4, 1, 2, 'Esto es una cadena', 4]
</code>

Además, también es posible manipular las listas para añadir, eliminar y buscar elementos en la lista
  * ''append(elemento)'': Añade un elemento a una lista
  * ''pop([indice])'': Elimina un elemento de una lista. No devuelve nada y lanza una excepción ''IndexError'' si la lista está vacía o no hay un elemento en la posición indicada. Si no se pasa ningún parámetro se elimina el último elemento de la lista
  * ''extend(lista)'': Añade los elementos de una lista a otra
  * ''remove(elemento)'': Elimina la primera aparición de un elemento dado. Devuelve una excepción ''ValueError'' en caso de que el elemento no exista
  * ''index(elemento, [indice_inicial])'': Devuelve la posición de un elemento determinado 
  * ''insert(indice, elemento)'': Inserta un elemento en una posición determinada 

<code python>
>>> lista = [1, 2, 3] 
>>> lista.append(4)  # No devuelve nada
>>> lista
[1, 2, 3, 4]
>>> lista.pop(0) # Elimina y devuelve el primer elemento
1
>>> lista
[2, 3, 4]
>>> lista.pop() # Elimina y devuelve el último elemento
4
>>> lista
[2, 3]
>>> lista2 = [4, 5, 6]
>>> lista.extend(lista2) # Concatena la segunda lista
>>> lista
[2, 3, 4, 5, 6]
>>> lista.extend("hola")
>>> lista
[2, 3, 4, 5, 6, 'h', 'o', 'l', 'a']
>>> lista.remove('h')
>>> lista
[2, 3, 4, 5, 6, 'o', 'l', 'a']
>>> lista.index('o')
5
>>> lista.insert(2, 3.5)
>>> lista
[2, 3, 3.5, 4, 5, 6, 'o', 'l', 'a']
</code>

También puede ser útil comenzar definiendo una lista vacía e ir añadiendo elementos durante la ejecución del programa.

<code python>
>>> lista = []
>>> lista.append(1)
>>> lista
[1]
>>> lista.append(2)
>>> lista
[1, 2]
</code>

== Tupla ==

Una tupla es una lista inmutable, es decir, una vez que se crea no se puede modificar y tampoco es posible añadir o eliminar elementos. Por ello presenta algunos beneficios:
  * Son más rapidas que las listas
  * También se utilizan para proteger los datos de cambios accidentales, siempre que estés seguro de que no te interesa que se puedan modificar sus elementos
  * A diferencia de la listas, las tuplas pueden utilizar como claves para los diccionarios

Se diferencia de la lista en que se utilizan los paréntesis (''('' '')'') para delimitar sus elementos.

<code python>
>>> una_tupla = (1, 2, 3)
>>> una_tupla[0]
1
>>> una_tupla[0] = 23
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
</code>

=== Diccionario ===

Un diccionario es una tipo de datos que contiene parejas clave-valor de forma que cada elemento (valor) tiene asociada una clave. Los elementos no están ordenados ya que la forma de hacer referencia a ellos es a través de su clave. Por eso, las claves no se pueden repetir.

Hay que tener en cuenta que cualquier valor puede ser utilizado como elemento pero para las claves sólo pueden ser utilizados tipos de datos inmutables, como por ejemplo las listas o diccionarios. Si que podría usarse, por ejemplo, una tupla como clave en un diccionario puesto que se trata de un tipo de datos inmutable.

En otros lenguajes, los diccionarios de Python, son conocidos como //Arrays Asociativos// o //Mapas//

<code python>
>>> habitantes = {'Huesca':52000, 'Zaragoza':661000, 'Teruel':35000}
>>> habitantes['Huesca']
52000
>>> 'Huesca' in habitantes
True
>>> habitantes['Madrid']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'Madrid'
</code>

Como ocurre con las lista, también es posible crear un diccionario vacío e ir añadiendo parejas clave-valor

<code python>
>>> stock = {}
>>> stock
{}
>>> stock['patatas'] = 50
>>> stock
{'patatas':50}
>>> stock['carne'] = 340
>>> stock
{'patatas':50, 'carne':340}
</code>

A continuación, algunas de las operaciones que se pueden llevar a cabo sobre un diccionario:
  * ''len(diccionario)'': Devuelve el número de parejas clave-valor
  * ''del diccionario[clave]'': Elimina el elemento (junto con su clave) cuya valor se para como parámetro
  * ''popitem()'': Elimina y devuelve una pareja clave-valor aleatoriamente

<code python>
>>> habitantes = {'Huesca':52000, 'Zaragoza':661000, 'Teruel':35000}
>>> len(habitantes)
3
>>> del habitantes['Huesca']
>>> habitantes
{'Zaragoza':661000, 'Teruel':35000}
>>> habitantes.popitem()
('Teruel', 35000)
</code>

También es posible recorrer secuencialmente todas las claves o todos los elementos del diccionario

<code python>
>>> habitantes = {'Huesca':52000, 'Zaragoza':661000, 'Teruel':35000}
>>> # Recorre todas las claves del diccionario
>>> for ciudad in habitantes:
...     print(ciudad)
...
Huesca
Zaragoza
Teruel
>>> # Recorre todas las claves del diccionario
>>> for ciudad in habitantes.keys():
...     print(ciudad)
...
Huesca
Zaragoza
Teruel
>>> # Recorre todos los elementos del diccionario
>>> for numero in habitantes.values():
...     print(numero)
...
52000
661000
35000
</code>

=== Conversión de tipos ===

^ Función ^ Descripción ^ Ejemplo ^ 
| bool()  | | |              
| float() | | |
| int()   | | |
| long()  | | |
===== Cadenas de texto =====

===== Control de excepciones =====

La estructura del código para capturar excepciones en Python sigue un esquema similar al de Java. La principal diferencia es que dispondremos de una claúsula ''else'' para indicar qué queremos hacer en caso de que la excepción no salte.

<code python>
try:
    # Código que puede producir la excepción
except <NombreException> [as objeto_excepcion]:
    # Código para ejecutar en caso de que se produzca
else:
    # Código que se ejecuta si no salta la excepcion
finally:
    # Código que se ejecuta en cualquier caso
</code>

Y a continuación un par de ejemplos de código:

<code python>
. . .
while True:
    try:
        n = input("Introduce un número: ")
        n = int(n)
        break
    except ValueError:
        print("El número no es válido. Prueba otra vez")
print("El número es correcto")
. . .
</code>

<code python>
. . .
try:
    fichero = open(nombre_fichero, 'r')
except IOError:
    print("No puedo abrir el fichero ", nombre_fichero)
else:
    contenido = fh.readlines()
    fichero.close()
. . .
</code>

<code python>
. . .
if algopasa:
    raise Exception('Ha pasado algo')
. . .
</code>

<code python>
class MiExcepcion(Exception):
    def __init__(self, valor):
        self.valor = valor
    def __str__(self):
        return repr(self.valor)
</code>

<code python>
. . .
try:
    raise MiExcepcion('algopasa ha pasado')
except MiExcepcion as me:
    print("Ha ocurrido un error", e.valor)
. . .
</code>
===== Expresiones Regulares =====

===== Programación Orientada a Objetos =====

Python es un lenguaje de Programación Orientado a Objetos y, aunque sigue ese paradigma, puede presentar ligeras diferencias con respecto a otros lenguajes también orientados a objetos.

A continuación se exponen algunas diferencias con respecto a Java o aspectos a tener a cuenta a la hora de escribir programas utilizando las características de orientación a objetos de Python:
 
  * Se permita la herencia múltiple, es decir, una clase puede tener varias clases base
  * No existen los modificadores de accesibilidad (public, protected, private) aunque hay una convención de código ampliamente adoptada por la que cualquier atributo prefijado con un guión bajo debería tratarse como un parte no pública (''_velocidad''), sea un atributo o un método) de forma que:
    * Si un atributo comienza con un guión bajo (''_velocidad'') se considera como protegido (//protected//)
    * Si el atributo comienza con dos guiones bajos (''<nowiki>__</nowiki>velocidad'') se le considera como privado (//private//) 
  * No existe la sobrecarga de métodos como tal por lo que tendremos que optar por crear un método con parámetros opcionales y decidir qué hacer dentro del mismo:
<code python>
# Método con dos casos (simula la sobrecarga)
def anadir_temporada(self, cantidad=None):
    if cantidad:
        self.temporadas += 1
    else:
        self.temporadas += cantidad
</code>

==== Declarar clases ====

<code python>
class Pelicula:

    # Atributo de clase
    productora = 'Netflix'

    # Constructor de la clase
    def __init__(self, titulo, director, presupuesto):
        # Atributos de instancia
        self.titulo = titulo
        self.director = director
        self.presupuesto = presupuesto

    # Obtiene su representación como cadena de texto
    def __str__(self):
        return self.titulo
        
    def aumentar_presupuesto(self, cantidad):
        self.presupuesto += cantidad

class Serie(Pelicula):
    # Esta clase hereda de la clase Pelicula
    
    def __init__(self, titulo, director, presupuesto, temporadas):
        # Invoca al constructor de la clase base
        super().__init__(titulo, director, presupuesto)
        self.temporadas = temporadas

    # Método con dos casos (simula la sobrecarga)
    def anadir_temporada(self, cantidad=None):
        if cantidad is None:
            self.temporadas += 1
        else:
            self.temporadas += cantidad
    
    # Método sobreescrito
    def aumentar_presupuesto(self, cantidad):
        # Invoca al método de la clase base con super()
        super().aumentar_presupuesto(cantidad * self.temporadas)
</code>

==== Instanciar objetos ====

A continuación se muestra un breve ejemplo de cómo instanciar objetos, cómo acceder a sus atributos o métodos y cómo tratar con las referencias entre objetos (en este caso es similar a como ocurre en Java)

<code python>
>>> serie = Serie('Breaking Bad', 'Ni idea', 5)
>>> miniserie = MiniSerie('El coche fantastico', 'Tampoco', 1250000)
>>> miniserie.incrementar_presupuesto(100)
>>> serie.titulo
'Breaking Bad'
>>> miniserie.presupuesto
12500100
>>> otraserie = serie
>>> otraserie.titulo = 'Dexter'
>>> serie.titulo
'Dexter'
</code>

==== Métodos Estáticos vs Métodos de Clase ====

De haber trabajado con Java ya sabemos que los métodos que no se asocian a las instancias de una clase (a sus objetos) se llaman método estáticos (''static'') pero aqui en Python nos encontramos dos tipos de estos métodos, los conocidos como métodos estáticos (''staticmethod'') y lo que se conoce como método de clase (''classmethod'').

En Python la idea es la siguiente, los métodos estáticos no quedan vinculados ni a una instancia ni a una clase mientras que los métodos de clase siguen sin vincularse a ninguna instancia pero si a la clase.

Para comprender mejor la diferencia veamos un ejemplo de código con el mismo método declarado como estático y como de clase actuando sobre un atributo de la misma.

Primero el ejemplo de código donde definimos el método de la clase como estático:

<code python personajes.py>
class Personaje:
    tipo = 'personaje'
    
    @staticmethod
    def descripcion(self):
        print("Este objeto es de tipo ", Personaje.tipo);
        
class SuperHeroe(Personaje):
    tipo = 'superheroe'
    
class Malo(Personaje):
    tipo = "malo"
    
personaje = Personaje()
personaje.descripcion()

superheroe = SuperHeroe()
superheroe.descripcion()

malo = Malo()
malo.descripcion()
</code>

Si ejecutamos el código anterior esta es la salida que obtenemos

<code bash>
santi@zenbook:$ python personajes.py
Este objeto es de tipo personaje
Este objeto es de tipo personaje
Este objeto es de tipo personaje
</code>

No queda vinculado a ninguna clase y muestra siempre el valor que inicialmente se le ha dado sin importar desde cual de las subclases se está ejecutando realmente.

Si ahora reescribimos el mismo código pero hacemos que el método estático sea un método de clase (''classmethod'') veremos que al ejecutar hay una ligera diferencia y es que el método queda vinculado a cada clase (subclases en este caso)

<code python personajes.py>
class Personaje:
    tipo = 'personaje'

    @classmethod
    def descripcion(cls):
        print("Este objeto es de tipo", cls.tipo)

class SuperHeroe(Personaje):
    tipo = 'superheroe'


class Malo(Personaje):
    tipo = "malo"

personaje = Personaje()
personaje.descripcion()

superheroe = SuperHeroe()
superheroe.descripcion()

malo = Malo()
malo.descripcion()
</code>

Y al ejecutar:

<code bash>
santi@zenbook:$ python personajes.py
Este objeto es de tipo personaje
Este objeto es de tipo superheroe
Este objeto es de tipo malo
</code>

Podemos observar como el valor de la variable de clase varía en función de la clase que ejecuta el método puesto que éste mantiene un vínculo con la clase que lo invoca.

==== Definir propiedades ====

En lenguajes como Java, tras definir algún atributo como privado o protegido, es habitual programar los habituales métodos conocidos como //getters// y //setters// para definir cómo puede el programador acceder a dichos atributos o bien para definir que no puede (si no implementamos el correspondiente //get//). En Python existe lo que se conoce como //propiedades// (que también existen en otras plataformas como .NET) que permite crear los métodos de acceso a dichos atributos y al utilizarlos hacerlo como si de atributos se trataran. Con ello conseguimos que el código quede algo más limpio sin prescindir de algo tan importante en la POO como es la encapsulación.

Veamos un ejemplo:

<code python>
class Pelicula:
    def __init__(self, titulo):
        # Hemos definido el atributo como privado
        self.__titulo = titulo
        
    # getter
    @property
    def titulo(self):
        return self.__titulo
       
    # setter
    @titulo.setter
    def titulo(self, titulo)
        self.__titulo = titulo
        
    . . .
</code>

De forma que podemos hacer uso de las propiedades como si estuvieramos accediendo a los propios atributos directamente, y lo que estamos haciendo realmente es ejecutar los métodos que hemos definido como tal:

<code python>
>>> pelicula = Pelicula('Gran Torino')
>>> pelicula.titulo = 'El lobo de Wall Street'
>>> pelicula.titulo
'El lobo de Wall Street'
</code>

==== Clases Abstractas ====

Como ocurre en la //POO//, una clase abstracta es una clase que cumple una serie de características:
  * No se pueden instanciar objetos de ella
  * Puede definir métodos abstractos, que son aquellos que pueden dejarse en blanco (en Python con la palabra reservada ''pass'', para que sean definidos por las clases derivadas.

En Python no existe una palabra reservada para definir una clase abstracta sino que tenemos que importar el paquete ''abc'' (Abstract Base Class) y hacer que la clase herede de ''ABC'', y definir como ''@abstractmethod'' los métodos de esa clase que también queremos que sean abstractos.

<code python>
from abc import ABC, abstractmethod

class Inmueble(ABC):
    def __init__(self, direccion, salario):
        self._nombre = nombre
        self._salario = salario
        super().__init__()
        
    @abstractmethod
    def obtener_salario(self):
        pass
        
     # También se pueden definir métodos 
     # que no sean abstractos
     
     . . .
</code>

Asi, si queremos crear una clase derivada de la anterio clase abtracta, tenemos que tener en cuenta que deberemos implementar los métodos declarados como abstractos en ella (si queremos que la clase derivada deje de considerarse abstracta).

<code python>
class Vendedor(Empleado):
    
    def __init__(self, direccion, salario, comision):
        super().__init__(direccion, salario, comision)
        self._comision = comision

    # Si no definimos todos los métodos abstractos
    # la clase también será abstracta
    def obtener_salario(self):
        return self._salario + self._comision
</code>

También podemos encontrarnos con el caso de métodos abstractos que se hayan implementado en la clase base. En ese caso, en las clases que hereden de esta clase abstractos estaremos obligados a sobreescribir dichos métodos.

<code python>
from abc import ABC, abstractmethod

class Empleado(ABC):
    def __init__(self, nombre, salario):
        self.__nombre = nombre
        self.__salario = salario
        super().__init__()
        
    @abstractmethod
    def obtener_salario(self):
        return self.__salario
        
class Vendedor(Empleado):
    def __init__(self, nombre, salario, comision):
        super().__init__(nombre, salario)
        self.__comision = comision

    def obtener_salario(self):
        return super().obtener_salario() + self.__comision
</code>

===== Ficheros =====

==== Leer ficheros de texto ====

En este apartado se muestran algunos ejemplos de operaciones habituales sobre ficheros de texto.

Extraer algo de información útil sobre el propio fichero (no el contenido):

<code python>
>>> # Muestra información útil sobre el fichero abierto
>>> fichero = open('fichero.txt')
>>> print(fichero.name)
'estructura.py'
>>> print(fichero.encoding)
'UTF-8'
>>> print(fichero.mode)
'r'
>>> fichero.close()
</code>

Cómo leer un fichero de texto completo, con una sola línea de texto:

<code python>
>>> # Lee un fichero de texto completo
>>> fichero = open('fichero.txt')
>>> contenido = fichero.read()
>>> fichero.close()
>>> contenido
'Este es el contenido del fichero'
</code>

En este caso podemos ver cómo es posible (y recomendable) utilizar la instrucción ''with'' para trabajar con ficheros, puesto que se encarga de cerrarlo automáticamente cuando terminamos de leerlo:

<code python>
# Lee el fichero y lo cierra automáticamente
with open('fichero.txt') as fichero:
    contenido = fichero.read()

print(contenido)
</code>

También podemos, si la ocasión lo requiere, leer el fichero de texto línea a línea:

<code python>
# Lee el fichero línea a línea
with open('fichero.txt') as fichero:
    for linea in fichero:
        print(linea.rstrip())
</code>

==== Escribir ficheros de texto ====

Aqui encontramos un par de ejemplos a la hora de escribir ficheros de texto.

Cómo escribir un fichero en modo escritura (**w**ritable), de forma que el nuevo contenido sobrescribe completamente todo el contenido anterior del fichero:

<code python>
with open('fichero.txt', mode='w') as fichero:
    fichero.write('este texto sobreescribe el fichero')
</code>

En este otro caso en modo añadir (**a**ppend), el contenido escrito se añadirá justamente al final del contenido que ya tuviera el fichero:

<code python>
with open('fichero.txt', mode='a') as fichero:
    fichero.write('este texto se añade al fichero')
</code>

===== XML =====

===== Conexión con Bases de Datos =====

==== MySQL ====

{{ mysql.jpg?300 }}

[[http://www.mysql.com|MySQL]] es un gestor de Bases de Datos Relacional diseñado según la arquitectura cliente-servidor. Actualmente es uno de los 3 motores que más se utilizan (y con mucha diferencia con respecto al resto) ((https://db-engines.com/en/ranking)) (los otros 2 son Oracle y Microsoft SQL Server) 

A continuación veremos como realizar las 4 operaciones básicas (CRUD((https://en.wikipedia.org/wiki/Create,_read,_update_and_delete))) sobre una Base de Datos MySQL.

En este caso hemos utilizado el módulo [[https://github.com/PyMySQL/PyMySQL|pymysql]] 

=== Lanzar una consulta ===

<code python>
# Ejemplo que muestra como conectar con una Base de Datos MySQL
# Usa el módulo pymysql que hay que instalar
# Lista todos los valores de un campo determinado por la posición
# También comprueba los errores de conexión más comunes
import pymysql

try:
    conexion = pymysql.connect(host='localhost', port=3306, user='santi', 
                               passwd='supercontraseña', db='basededatos',
                               cursorclass=pymysql.cursors.DictCursor)
    sql = "SELECT * FROM prueba"
    cursor = conexion.cursor()
    cursor.execute(sql)

    for fila in cursor:
        print(fila[texto])  # Se debe indicar el campo a mostrar
        # print(fila[0])  # La posición determina el campo a mostrar

except pymysql.err.OperationalError:
    print("Se ha producido un error al conectar con la Base de Datos")
    print("Comprueba que MySQL está iniciado o que los datos de conexión son correctos")
except pymysql.err.InternalError:
    print("Se ha producido un error al conectar con la Base de Datos")
    print("Comprueba que la Base de Datos existe")
finally:
    if 'cursor' in locals():
        cursor.close()
    if 'conexion' in locals():
        conexion.close()
</code>

=== Insertar un registro ===

<code python>
. . .
sql = "INSERT INTO prueba (texto) VALUES (%s)"
cursor = conexion.cursor()
cursor.execute(sql, ('Texto de prueba'))
conexion.commit()
. . .
</code>

=== Modificar un registro ===

<code python>
. . .
sql = "UPDATE prueba SET texto = %s WHERE id = %s"
cursor = conexion.cursor()
cursor.execute(sql, ('nuevo texto', 1))
conexion.commit()
. . .
</code>

=== Eliminar un registro ===

<code python>
. . .
sql = "DELETE FROM prueba WHERE id = %s"
cursor = conexion.cursor()
cursor.execute(sql, (2))
conexion.commit()
. . .
</code>

==== PostgreSQL ====

{{ postgresql.png?300 }}

=== Realizar una consulta ===

<code python>
</code>

=== Insertar un registro ===

<code python>
</code>

=== Modificar un registro ===

<code python>
</code>

=== Eliminar un registro ===

<code python>
</code>

==== SQLite ====

{{ sqlite.png?300 }}

[[http://www.sqlite.org|SQLite]] es un motor de Bases de Datos Relacional que, a diferencia de los anteriores que hemos visto, no sigue la arquitectura cliente-servidor sino que la Base de Datos se almacena en un sólo fichero y es la propia librería la que hace de motor.

SQLite se usa principalmente como almacenamiento local de cualquier tipo de aplicación siempre que necesitemos almacenar una cantidad no demasiado grande de información. Presenta como ventajas que siempre será mucho más sencillo de implantar la solución desarrollada ya que no tenemos que instalar ni configurar ningún software adicional. Actualmente es uno de los motores más usados ya que, por ejemplo, Android lo utiliza (viene integrado como parte de Android) para el almacenamiento local de las aplicaciones instaladas.

Conviene tener en cuenta que al tratarse de un motor tan sencillo apenas hay aplicaciones de gestión por lo que lo más habitual es incluir el código de creación de la Base de Datos como parte del código de la aplicación teniendo en cuenta que sólo deberá ejecutarse la primera vez. Es por eso que en este caso se incluye el ejemplo de cómo crear las tablas de la Base de Datos desde Python. Hay que pensar que nunca encontraremos Bases de Datos complejas para ser gestionadas por SQLite, por lo que esta práctica tampoco supone ningún problema. En el resto de ejemplos siempre se asume que la Base de Datos ya ha sido diseñada y creada con las aplicaciones correspondientes.

Para trabajar con Python en este caso hemos utilizado el módulo [[https://github.com/ghaering/pysqlite|pysqlite]] que además dispone de amplia [[https://pysqlite.readthedocs.io/en/latest/sqlite3.html|documentación]]

=== Conectar y crear la Base de Datos ===

En el siguiente ejemplo se muestra el código de como crear una Base de Datos ((https://sqlite.org/autoinc.html))

<code python>
# Ejemplo que muestra como conectar y crear 
# una Base de Datos SQLite
# Usa el módulo pysqlite que hay que instalar
import sqlite3

try:
    # Este primer bloque sólo habría que ejecutarlo
    # la primera vez
    conexion = sqlite3.connect("datos.db")
    sql_create = "CREATE TABLE prueba" \
                 "(id INTEGER PRIMARY KEY, texto VARCHAR(255))"
    cursor = conexion.cursor()
    cursor.execute(sql_create)
    conexion.commit()

except sqlite3.OperationalError:
    print("Se ha producido un error al conectar con la Base de Datos")
except sqlite3.InternalError:
    print("Se ha producido un error al conectar con la Base de Datos")
    print("Comprueba que la Base de Datos existe")
finally:
    if 'cursor' in locals():
        cursor.close()
    if 'conexion' in locals():
        conexion.close()
</code>

=== Realizar una consulta ===

<code python>
. . .
sql_select = "SELECT * FROM prueba"
cursor.execute(sql_select)

for fila in cursor:
    print(fila[1])  # La posición determina el campo a mostrar
. . .
</code>

=== Insertar un registro ===

<code python>
. . .
sql_insert = "INSERT INTO prueba (texto) VALUES (?)"
parametros = ('texto de prueba',)
cursor.execute(sql_insert, parametros)
conexion.commit()
. . .
</code>

=== Modificar un registro ===

<code python>
</code>

=== Eliminar un registro ===

<code python>
</code>

==== MongoDB ====

{{ mongodb.png }}

=== Realizar una consulta ===

<code python>
</code>

=== Insertar un registro ===

<code python>
</code>

=== Modificar un registro ===

<code python>
</code>

=== Eliminar un registro ===

<code python>
</code>

===== Cómo organizar un proyecto =====

==== Módulos ====

La programación modular o basada en módulos consiste en dividir una aplicación en varias partes independientes (módulos), capaces de funcionar por separados, pero que juntas funcionan como una sola. La principal idea sobre la programación modular es la de poder crear un código legible, ordenado y que se puedan mantener.

Además, disponer de nuestra aplicación programada en módulos, permite que podamos reutilizar esos módulos para construir otras, reutilizando de esa manera nuestro código. Incluso podemos desarrollar librerías simplemente para que otros puedan apoyarse (importando los módulos) en ellas para construir sus aplicaciones.

En Python, un módulo es cualquer fichero con extensión .py (fichero de código) con código que pueda ser reutilizado (clases, funciones, . . .).

La forma de utilizar esos módulos es utilizando la palabra reservada ''import'' acompañada del nombre del módulo que queremos utilizar en nuestra aplicación.

Por ejemplo, supongamos que tengo el módulo 'operaciones.py':

<file python operaciones.py>
def siguiente(numero):
    return numero + 1
    
def anterior(numero):
    if (numero > 0):
        numero -= 1
    return numero
</file>

Y ahora quiero importar ese módulo para utilizar algunas de sus funciones. Hay varias posibilidades:

Importar el módulo sin más, de forma que tendremos que escribir su nombre cuando queramos utilizar sus funciones:

<code bash>
>>> import operaciones
>>> operaciones.siguiente(3)
4
</code>

Podemos importarlo y renombrarlo, de forma que podremos utilizar el nuevo nombre cuando queramos utilizar sus funciones:

<code bash>
>>> import operaciones as ops
>>> ops.siguiente(3)
4
</code>

También podemos importar todas las funciones del módulo y evitar tener que utilizar su nombre como prefjo antes de invocarlas:

<code bash>
>>> from operaciones import *
>>> anterior(0)
0
</code>

También podemos importar sólo algunas de sus funciones, y utilizarlas entonces sin usar el nombre del módulo como prefijo:

<code bash>
>>> from operaciones import siguiente
>>> siguiente(3)
4
>>> anterior(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'anterior' is not defined
</code>

Realmente la opción mas aconsejable es importar el paquete tal cual aunque ello nos obligue a escribir su nombre a modo de prefijo cada vez que invoquemos a alguna de sus funciones. En proyectos muy grandes a veces interesa saber de donde salen las funciones y de esa manera podremos distinguir claramente nuestro código del de los módulos que hayamos importado. En ocasiones puede ser recomendable un renombrado del módulo para aquellos que tengan un nombre largo o incómodo de escribir, pero siempre utilizando un segundo nombre que sea lo suficientemente descriptivo como para que sepamos a que módulo se refiere.
==== Paquetes ====

En el punto anterior hemos visto que podemos organizar el código en módulos. En Python un módulo es un fichero que contiene código que podremos reutilizar importándolo. En el caso de que tengamos varios módulos y queramos organizarlos todos juntos estaremos hablando de un **paquete**.

Utilizar paquetes es la forma en la que podremos organizar nuestros proyectos más grandes en Python. Puesto que podemos almacenar varios módulos en cada paquete, podemos estructurar más todavía nuestro código evitando asi tener módulos demasiado grandes.

En Python un paquete es cualquier carpeta que contenga en su interior un fichero vacío con el nombre ''<nowiki>__init__</nowiki>.py''. Todos los módulos que aparezcan en esa carpeta formarán parte de ese paquete, cuyo nombre será el nombre de la carpeta.

Al final, la forma de importar los módulos de cada paquete es la misma que la que hemos usado en el apartado anterior a la hora de importar módulos por separado. Ahora, simplemente tendremos que utiliar el nombre del paquete delante del módulo de la forma ''import paquete.modulo''.

Imaginemos que tenemos el siguiente paquete, compuesto a su vez de dos paquetes. Cada uno de estos dos paquetes tiene un módulo con varias funciones cada uno (podrían contener más de un módulo y la forma de trabajar con ellos sería la misma).

<figure>
{{ python_package.png }}
<caption>Estructura del paquete calculadora</caption></figure>

Cada uno de los ficheros ''operaciones.py'' (se llaman igual pero no es necesario) contiene una serie de funciones, //basicas// por un lado:

<file python operaciones.py>
def sumar(numero1, numero2):
    return numero1 + numero2

def restar(numero1, numero2):
    return numero1 - numero2

def multiplicar(numero1, numero2):
    return numero1 * numero2

def dividir(numero1, numero2):
    return numero1 / numero2

def potencia(numero, exponente):
    return numero ** exponente
</file>

y de //funciones// por otro:

<file python operaciones.py>
import math

def seno(valor):
    return math.sin(valor)

def coseno(valor):
    return math.cos(valor)

def tangente(valor):
    return math.tan(valor)
</file>

Así, si desde fuera del paquete principal ''calculadora'', queremos importar cualquiera de los módulos, procederemos de la siguiente forma:

<code python>
>>> import calculadora.basicas.operaciones as operaciones
>>> print(operaciones.sumar(3, 4))
7
>>> |
</code>

Y a partir de ahi podemos importar los módulos de las diferentes formas que hemos visto en el apartado anterior sobre los mismos.
===== GUI =====

[[https://docs.python.org/3/library/tkinter.html|Tkinter]] es la librería estándar para el desarrollo de GUIs en Python y viene incluida en la instalación de //Python//, por lo que para usarla sólo es necesario importar el paquete ''tkinter''.

A continuación se muestra un ejemplo muy sencillo creado con esta librería. Para más información, en la sección [[extra:referencias|Referencias]] hay un par de enlaces interesantes.

<code python>
#!/usr/bin/python3
import tkinter as tk

class Ventana(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master)
        self.grid()
        self.__crearVentana()

    def __crearVentana(self):
        self.__tfTexto = tk.Text(self, width=20, height=1)
        self.__tfTexto.grid()

        self.__btBorrar = tk.Button(self, text='Borrar', command=self.__borrar)
        self.__btBorrar.grid(row=0, column=1)

        self.__btSalir = tk.Button(self, text='Salir', command=self.quit)
        self.__btSalir.grid(row=1, column=0)

    def __borrar(self):
        self.__tfTexto.delete(1.0, tk.END)

if __name__ == '__main__':
    ventana = Ventana()
    ventana.master.title('¡Hola GUI!')
    ventana.mainloop()
</code>

<figure>
{{ tkinter.png }}
<caption>Ejemplo GUI creada con tkinter</caption></figure>