Sei sulla pagina 1di 37

**************************

Apuntes de Python
**************************

/////////////////
0. ANTES DE
/////////////////

- Instalar Python 2.7.14 (Python 3+ trabaja de forma muy distinta) y tener en la


variable de entorno PATH la ruta a Python

- Agregar al PATH y verificar en cmd que se pueda ejecutar el comando python (para
ejecutar una script: python <script>)

- Python dispone de herramientas con interfaz grafica como IDLE (y trabajar por
ejemplo en File > New File) o la linea de comandos de Python

- En Eclipse (version "for Java Developers")

- Instalar pydev desde el Eclipse Marketplace (menu Help)

- Configurar en Window > Preferences > PyDev > Interpreters > Python
Interpreter uno nuevo llamado python con el ejecutable de la instalación de python

- Tener como variable de entorno a PYTHONPATH con las rutas de Python: raíz,
DLLs, include, Lib, libs, Scripts, tcl, Tools\Scripts, Lib\lib-tk, Lib\site-
packages

- Emplear la perspectiva PyDev: Window > (Perspective) > Open perspective >
(Other) > PyDev

- Ya para crear un proyecto: File > New > PyDev Project. Para crear módulos:
click derecho en el proyecto > New > Pydev Module

- Se dice que un código que se entrega al usuario final está "congelado" (Frozen
Binaries) cuando se compila todo el código fuente en un solo ejecutable

//////////////////
1. LO BASICO
//////////////////

- Lenguaje compilado que también actúa como interpretado, fuertemente tipado y


orientado a objetos

- Scripts de python tienen extensión *.py ... Compilados (código byte) de python
tienen extensión *.pyc

- En Linux/Unix recordar poner permisos de ejecución y usar el shebang


#!/usr/bin/python ó #!/usr/bin/env python

- Comentarios de línea empiezan por #. Comentarios de bloque: linea inicial y liena


final con tres comillas simples '''

- Salida estándar: print(<cadena>) ## Tiene variantes como print <cadena>;


print <cadena1>,...,<cadenaN>; print(<cadena1>,...,<cadenaN>)
También posee una variante como función predefinida que no necesita paréntesis:
print var1,...,varN (imprime variables separadas por espacio)

- Entrada estándar: input([promptAMostrar]) (requiere comillas para strings en la


entrada, raw_imput([prompt]) no; solo para versiones anteriores a Python 3)

- Cadena a número: int(<cadena>)

- Separador de instrucciones: Salto de línea

- Creación de variables: var1 = "cadena"


var2 = 42
var3 = len(var1)

- Comprobación de tipo: type(<variable>). Se puede emplear, por ejemplo:


type(<variable>) <igualdad|desigualdad> <tipo>

- Operadores aritméticos: + - * ** / // % ; lógicos: and or not == != < > <= >= ;


de secuencias: + (concatena) * (repite un numero de veces) ;
binarios: & | ^ ~ << >>

- Ojo, / es division de enteros si ambos numeros son enteros, a menos que se


ejecute antes: from __future__ import division

- División obligatoriamente entera (truncada al piso) es con //

- Tambien admite asignación += -= *= /= %= etc. (no admite ++ ni --)

- Cadenas de caracteres: 'cadena' ; "cadena" ; u"cadenaáñ\n" (indica que es


Unicode; interpreta especiales con \) ; r"asd\asd" (un bruto; no interpreta \)

- Convertir caracter a entero (valor ASCII) y viceversa:

num = ord('x')
letra = char(99)

- El nombre de variables o funciones indica el encapsulamiento. Nombres corrientes


son de acceso público y nombres que empiecen por guión bajo son de acceso privado.

- Importación de módulos: permite concatenar un módulo en otro.

- Por ejemplo para meter lo de arch2.py en arch1.py: import arch2 (esto


compilará arch2.py). En ese momento se puede acceder a los elementos de arch2
empleando un punto. Ej: arch2.objeto

- Para cargar una clase de un módulo: from <módulo> import <clase>. Recordar
que esto crea un archivo de código binario (compilado) del módulo

- Para acceder por referencia a cualquier módulo:

import sys
referencia = sys.modules['<modulo>']

- Al hacer print(arch2) se muestra algo de información respecto al módulo


cargado

- Al importar módulos, los objetos de los módulos importados son cargados

- Se puede listar los objetos públicos de algunos módulos por su elemento


__all__

- Para importar una clase de un módulo: from <módulo> import <clase> ; usar * como
clase para importar todas las clases en un módulo
- Cuando se crea un paquete en Python, se requiere que el paquete contenga un
módulo __init__.py, que es el módulo principal de ejecución (análogo a la clase que
tenga el main en Java). Para importar un módulo o una clase de un paquete, se
refiere a <paquete>.<modulo>

/////////////////////////////////////
2. ESTRUCTURAS DE DATOS BASICAS
/////////////////////////////////////

- Las listas son colleciones de números, cadenas, booleanos u otras listas.


Indizadas de 0 a n-1.

- Ej: lista = [False, 123, 4, "cinco 6", [7, "8, 9"]]

- Inicializar lista vacía: lista = [] o bien lista = list()

- Para crear una lista con todos los elementos de una secuencia: lista =
list(<secuencia>)

- Acceso a "cinco 6": lista[3] ; acceso a "8, 9": lista[4][1]

- Índices negativos indican la posición desde el último elemento en una


lista. Ej: lista[-1] retorna [7, "8, 9"], lista[-3] retorna 4, etc.

- Acceso particionado a intervalos o secuencias de índices de la lista


mediante [inicio:fin] o [inicio:fin:salto], respectivamente.

- Ej: lista[1:3] retorna [123, 4, "cinco 6"], lista[0:5:2], lista[::]


(por defecto maneja inicio al final, de uno en uno)

- Notar que las cadenas de caracteres también admiten el manejo como


listas: cadena = "hola" ; cadena[1] ; cadena[1:5] ; etcétera.

- Se puede usar la palabra reservada 'in' para verificar si un elemento hace


parte de una lista

- Las tuplas son listas inmutables (no modificables) y de tamaño fijo (útiles para
ahorrar memoria y usos simples).

- Inizializar tupla vacia: tupla = ()

- Creación de tuplas: tupla = (1, "2", 3, True) ; tupla = (1, ) #### Notar
que tuplas de un elemento requieren la coma al final

- Acceso a elementos: tupla[indice] ; etc.

- Tupla de asignación.

- Por ejemplo: tupla = (1,2) ## o bien variable1,variable2 = 1,2


variable1,variable2 = tupla

En este caso se mapean respectivamente los


elementos de la tupla a las variables

- Esto tambien se puede hacer con una funcion que retorne una tupla.
Ej: return elemento_a,elemento_b

- También se puede usar en un for. Por ejemplo: for uno,dos,tres in


lista: ...

- Para concatenar un numero a una cadena se puede convertir el numero en cadena con
str(<numero>)

- A las listas, tuplas y cadenas de caracteres se les generaliza como secuencias.


Todas pueden particionar (hacer slicing).

- Los diccionarios asocian claves a valores (como mapas).

- Creación: diccionario = { "Pepe": [a, b, c, d] , ##


diccionario.items() muestra todos los mapeos
"Martha": 5,
"Ñejo": "asdfg"}

- Acceso a elementos: diccionario["Pepe"] ; diccionario["Martha"] = "Nuevo


Valor"

- Otra forma puede ser en un for: for clave,valor in diccionario: ....

- Para crear un diccionario vacío: diccionario = dict()

- Se puede crear un diccionario a partir de una lista de tuplas.

- Por ejemplo:

lista = [('nombre','Pepe'),('edad',40)]

diccionario = dict(lista) ## Otra forma:


diccionario = dict(name='Pepe',edad=40)

- Lookup: Buscar a qué valor refiere una clave. Reverse lookup: Buscar qué
clave es la que refiere a un valor.

- Se usa 'in' o 'not in' para determinar si un elemento hace parte de una
estructura. Para diccionarios revisa en las claves, para ver en los valores se
busca en diccionario.values()

- Los conjuntos son colecciones desordenadas (no indizadas) de elementos únicos (no
repetidos). Son útiles para remover duplicados en las secuencias.

- Para crear un conjunto vacío: conjunto = set()

- Para crear un conjunto de una secuencia: conjunto = set(<secuencia>)

- Creación directa: conjunto = {0,1,2,3,4,5,6,7,8,9}

- Verificar si un elemento está o no en un conjunto: <elemento> [not] in


<conjunto>

- Verificar si un conjunto es subconjunto de otro:


<subconjunto>.issubset(<superconjunto>)

- Agregar un elemento: conjunto.add(<elemento>) ## Nota: no se


pueden agregar listas ni diccionarios como elementos del conjunto

- Agregar elementos en una secuencia: conjunto.update(<secuencia>)

- Borrar un elemento del conjunto: conjunto.discard(<elemento>) ## No


retorna error si el elemento no existe

conjunto.remove(<elemento>) ## Retorna TypeError si el elemento no existe

- Operaciones entre los conjuntos A y B:

Unión: U = A | B Intersección: I = A & B


Diferencia: D = A - B Diferencia Simétrica: S = A ^ B

///////////////////////////////
3. ESTRUCTURAS DE CONTROL
///////////////////////////////

- Condicionales (requieren ':' e indentación): Análogo al ? en


otros lenguajes de programación:

if <condición>:
<operacion si se cumple> if <condicion> else <parametro operacion si no se
cumple>
<instrucción 1>
...
Ej: print "Verdadero" if 8 < 2 else "Falso"
<instrucción N>
elif <otra condición>:
var asdf = 8 if 8 < 2 else 5
<instrucción 1>
...
<instrucción N>
Dado que en Python no existe el switch, habría que emularlo empleando
diccionarios
else:
<instrucción 1>
...
<instrucción N>

- Los bucles, igual que los condicionales, requieren ':' e indentación. Tambien
usan break y continue. Son:

while <condicion>: for <elemento> in <secuencia>:


<operaciones> <operaciones>

Para obtener una secuencia en un


intervalo (por ejemplo para un for): range([inicio,]fin+1[,salto])
(inicio defecto = 0; salto
defecto = 1)

//////////////////
4. FUNCIONES
//////////////////

- Al igual que las estructuras de control, éstas emplean ':' e indentación. Se


definen con la palabra clave 'def':

def NombreFuncion(param1, ..., paramN): ## Valor por defecto


para parámetro: param=<valor> ; puede no llevar parámetros
["docstring" (cadena de documentación)] ## Para numero
variable de parametros, por ejemplo: (param1, *multipleParam)
<operaciones>
## donde *multipleParam es una tupla que contiene los argumentos enviados
[retorno (return <elemento>)] ## desde la
segunda posicion

Ej: def MiFuncion(a):


"""Este es como un comentario de bloque que documenta la funcion.
Requiere triple comilla doble
La funcion retorna 5+a.
Precondicion: a es un numero
Postcondicion: ninguna
Retorna: 5+a"""

return 5+a

Se llamaría simplemente, por ejemplo: b = MiFuncion(5); otra forma es definir


qué valor se pasa a qué parámetro: b = MiFuncion(a = 6)

- OJO. Las funciones deben ser definidas ANTES DE poder ser empleadas

- En la consola de salida, un retorno vacío (void) se refleja como None

- Para definir el valor por defecto de un parámetro:

def Funcion(param_defecto = 5):


print param_defecto

Asi, si se usa Funcion(), imprime 5; y si se usa Funcion(10), imprime 10

- En Python se pueden definir funciones anidadas (una dentro de otra, que serán
reconocidas localmente)

def Func1(param1):
print("Esta es la funcion 1")
def Func2(param2):
print("Esta es la funcion 2")
Func2()

- Python admite recursión. Por ejemplo:

def Factorial(n):
if (n==0 or n==1):
return 1
else:
return n*Factorial(n-1);

- Para usar una tupla (o lista) para pasar argumentos, se añade un * al nombre de
la variable de la tupla a pasar como argumento; para diccionario, añadir **

Ejemplo:

def Funcion(a,b,c): def Funcion(a,b,c):


print a,b,c print a,b,c

tupla = (1,2,3) diccionario = {a:1, b:2, c:3}


## Notar que deben concordar las claves con los parametros

Funcion(*tupla) Funcion(**diccionario)
- Para definir una funcion con numero variable de argumentos se pueden usar tuplas
o diccionarios (que guardan de 0 a muchos parámetros):

Para usar una tupla:

def NombreFuncion(param1, *multiples):


<operaciones>

Para usar un diccionario:

def NombreFuncion(param1, **otrosMultiples):


<operaciones>

En este caso se pueden hacer llamados a la función, por ejemplo:


NombreFuncion(1) ; NombreFuncion(1,2) ; NombreFuncion(1,segundo = 2, tercero = 3)

Notar en el último caso que al interior de la función habría que referir, por
ejemplo: otrosMultiples['segundo']

Un ejemplo combinado:

def Funcion(param1, *multipleTupla, **multipleDiccionario):


print(param1)
print(multipleTupla)
print(multipleDiccionario)

Y se llama, por ejemplo:


funcion("a",1,2,3,4,5,nombre='sadf',apellido='asdalskd')

- Adicionalmente, las funciones pueden emplear retorno de varios elementos a la vez

Ej: def MultipleRetorno()

- En Python no es posible la sobrecarga de métodos, sólo la sobreecritura. La unica


forma de manejar varios metodos del mismo nombre y diferentes parámetros es
empleando decoradores que determinen el uso de cada forma del método, por ejemplo
en el manejo de getters y setters de propiedades. Sin embargo, hay un truco para
hacer posible la sobrecarga y es definir los parámetros del método que sean
opcionales con un valor por defecto vacío (None). Ejemplo:

def funcion_sobrecargada(param1, param2 = None):


if (param2 == None):
<acciones sin el parámetro>
else:
<acciones con el parámetro>

- Llamada directa e indirecta:

def funcion(param):
print(param)

## Llamada directa ## Llamada indirecta

funcion("hace algo") x = funcion ## x apunta a la


direccion en memoria del objeto que representa la función
x("hace algo")

def
otra_forma(funcion,param):
funcion(param)

otra_forma(x,"hace algo")

Mediante una referencia a una función se puede obtener información sobre


ésta. Por ejemplo, en el caso de arriba, se puede obtener el nombre de la función a
la que refiere x (funcion), empleando x.__name__

- Las funciones anónimas se crean empleando la palabra reservada lambda

Ejemplo: <variable> = (lambda <parametros>: <operación de retorno>)

En este caso la variable guarda la dirección en memoria de la función -del


objeto que la representa- y puede ser usada para llamarle a ésta, o bien para
definirse como el retorno de una función (es decir, llamanado a esta función
anónima de forma indirecta)

- - Dado que las funciones son manejadas a través de objetos, éstas pueden hacer
parte de estructuras de datos como sus elementos

Ejemplo:

def funcion_a(x): return x**2


def funcion_b(x): return x**3
def funcion_c(x): return x**x

lista = [funcion_a, funcion,b, funcion_c] ## O también: lista = [lambda


x: x**2, lambda x: x**3, lambda x: x**x]

for i in lista:
print(i(5))

Esto es muy útil para manejar funciones directamente en decisiones empleando


diccionarios:

calculadora = {'suma': lambda a,b: a+b,


'resta': lambda a,b: a-b}

print(calculadora['suma'](a,b))

- Las variables al interior de una funcion se manejan como locales, a menos que se
defina que éstas refieren a globales: global <variable>

//////////////////////
5. COMPRENSIONES
(COMPREHENSIONS)
//////////////////////

- Las comprensiones son un método rápido en Python para poblar de contenido listas,
conjuntos o diccionarios, usando una sintaxis clara

- Comprensiones de secuencia:

Para crear una secuencia nueva con los elementos de una operación y/o
condición sobre otra secuencia:

nueva_secuencia = [<elementoAIncluir><operacion> for <elemento> in


<secuencia> [if <condicion>]]
Para poblar de esta forma una secuencia nueva, pero con más de una secuencia
de base, por ejemplo:

nueva_secuencia = [<elemento1><operacion><elemento2> for <elemento1> in


<secuencia1> for <elemento2> in <secuencia2> [if <condicion>]]

Esto funcionará como dos for anidados, empezando por iterar en todos
los elementos de la primera secuencia, luego los de la segunda, etc.

Ejemplos:

secuencia = [1,2,3,4,5,6]

numeros_pares = [i for i in secuencia if i%2 == 0]

cuadrados = [i**2 for i in secuencia]

- Comprensiones de conjunto:

Funcionan igual que las comprensiones de secuencia. Van dentro de llaves: {}

- Comprensiones de diccionario:

Igual que las de conjunto, pero por ejemplo el elemento a añadir se trata de
un mapeo al estilo clave:valor

Ejemplo:

Para mezclar dos diccionarios se usaría normalmente:

diccionario1 = {'pepe':500, 'lola':800}


diccionario2 = {'lolo':400}
diccionario_mezcla = {}

for diccionario in (diccionario1, diccionario2):


for clave,valor in diccionario.items():
diccionario_mezcla[clave] = valor

Pero esto se puede resumir en:

diccionario_mezcla = {clave:valor for diccionario in


(diccionario1,diccionario2) for clave,valor in diccionario.items()}

Notar cómo se escriben los for anidados en una sola línea

///////////////////////////
6. MANEJO DE ARCHIVOS
///////////////////////////

- Requiere de un objeto manejador de archivos, que se crea así: manejador =


open(<ruta absoluta o relativa a archivo>[,<modo de acceso>])

- El modo de acceso por defecto es de lectura; lanza IOError si el archivo


especificado no existe

- Los modos de acceso que maneja Python son:


r: Lectura; r+: Lectura-Sobreescritura; w: Sobreescritura; w+: Lectura-
Sobreescritura; a: Escritura al final; a+: Lectura-Escritura al final
b: Modo binario; x: Creación exclusiva; t: Modo texto

- Escritura sobre archivo: manejador.write(<cadena>). Tambien puede incluir las


variables, ya sea concatenándolas (respectivamente convertidas a string si es
necesario), o incluyéndolas por formato de string:

manejador.write("Varios elementos" + cadena + " " + str(5))

manejador.write("Varios elementos: %s %d" % ("cadena",5))

manejador.write("Varios elementos: {} {}".format("cadena",5))

- Una forma altentativa de hacer

archivo = open("salida.txt","w")
archivo.write("Cosa")

Es empleando la palabra reservada with:

with open("salida.txt","w") as archivo:


archivo.write("Cosa")

Este proceso no requiere especificar la operación close(), ya que la realiza de


forma automática

- Lectura completa del contenido de un archivo: manejador.read()

- Lectura de una sola línea en un archivo (y mover el cursor de línea en el mismo):


manejador.readline()

- Obtención de todas las líneas de un archivo en formato de lista:


manejador.readlines()

- Se puede emplear el manejador para iterar en todas las lineas. Por ejemplo: for
linea in manejador

- Recordar cerrar el manejador una vez terminada la tarea pretendida con el archivo
(según su acceso): manejador.close()

- Para operaciones relativas a las turas de archivo, emplear el módulo os.path (ver
sección de módulos)

////////////////////
7. SUBPROCESOS
////////////////////

- Python puede gestionar subprocesos: crearlos, ejecutarlos, cerrarlos

proceso = popen("notepad")
datos = proceso.read()
proceso.close()

//////////////////////////////
8. ORIENTACION A OBJETOS
//////////////////////////////
- Recordar la necesidad de tener variables privadas y su manejo con getter y
setter: ofrece control sobre la variable y la lógica de retornarla o modificarla
(como por ejemplo establecer condiciones en un setter)

- Python ofrece una forma de automatizar el getter y el setter mediante


propiedades; esto personaliza la sintaxis de <objeto>.<atributo> a llamar
automáticamente al getter, y <objeto>.<atributo> = <valor> al setter. Para hacerlo
se crean el getter y el setter, ambos nombrados como el atributo, y se les pone
como decoradores @property y @<atributo>.setter, respectivamente

- Las clases de definen también con indentación y dos puntos (:):

class <NombreClase>:
[docstring]

<inicializador __init__ con parámetros self y atributos de la clase>

<funciones de la clase>

Ejemplo:

class Carro:
"""Esta clase corresponde a la abstracción de un carro"""

def __init__(self, placa, modelo, color): ## Notar self. Es la auto-


referencia al objeto. Necesario en toda funcion que quiera acceder
## a
los atributos del objeto.
"""Inicializador del carro con placa, modelo y color"""
self._placa = placa ## Esta sería una variable privada
solo accesible por getters y setters, o bien por propiedad
self.modelo = modelo
self.color = color
self.posicion = 0

print "Se ha creado un carro con placa "

def movimiento(self, velocidad):


"""Movimiento del carro segun la velocidad"""
self.posicion += velocidad

######## Esta forma es empleando getter y setter (ya obsoleta por


propiedades)

def get_placa(self):
return self._placa

def set_placa(self,placa):
self._placa = placa

######## Esta forma es empleando propiedades (la nueva solución que


ofrece Python).

@property ## Decorador que reconoce un getter


def placa(self):
return self._placa

@placa.setter ## Decorador que reconoce un setter


def placa(self,placa):
if(placa == ""):
raise ValueError("NO SE PUEDE AGREGAR UNA PLACA VACIA!!!")
else:
self._placa = placa

######## En este momento se puede acceder simplemente con self.placa (o


<instancia>.placa por fuera), y automaticamente se maneja get y set

Para crear un objeto Carro: objCarro = Carro("ASD123", "Nissan", "Amarillo")

Para eliminar el objeto se emplea del(<referencia a objeto>)

Para acceder a atributos y métodos -que no sean privados- se usa el punto.


Ej: objCarro.color ; objCarro.movimiento(12) ## También puede crearse un objeto con
el constructor por defecto, y a la instancia pasarle atributos

- La clase principal de un módulo se llama de alto nivel y es reconocida en Python


como __main__.<nombre de la clase>

- Borrado de una instancia: del <nombre referencia>

- Dado que la definición de la clase como tal es un objeto, se puede acceder de


forma estática a sus atributos y métodos. Esto es últil, por ejemplo, para contar
instancias de esta clase. En el ejemplo se presenta __del__, que es el método
llamado a la hora del borrado de una instancia (método destructor)

class Cosa:

conteo = 0

def __init__(self):
Cosa.conteo += 1

def __del__(self):
Cosa.conteo -= 1

a = Cosa()
b = Cosa()

Por tanto, Cosa.conteo deberá retornar 2. Ahora, si se hace un borrado de la


instancia, del a, Cosa.conteo deberá retornar 1

- Las clases poseen una forma adicional de acceso a sus atributos, esto es mediante
un diccionario __dict__. Por ejemplo:

class Cosa:
nombre = "asdf"
numero = 5

Cosa.__dict__['nombre'] ## Acceso al valor que guarda el atributo


nombre

- Los métodos corrientes son conocidos como métodos regulares y requieren de una
instancia (y por ello, su primer parámetro debe referir a ésta), no pueden ser
ejecutados de forma estática a menos que se les pase como parámetro una instancia
(self, en la misma clase, si es el caso).

- El manejo estático de métodos en Python se da en dos formas: como métodos de


clase o como métodos estáticos. Se recomienda poner el respectivo decorador

Métodos estáticos: poco usados porque desconocen de la clase a la que


pertenecen. Útiles para crear funciones de utilidad.

Métodos de clase: son los que se deben usar ya que tienen acceso a la clase
que los llama, y así, pueden referir a atributos de la clase, etc. Son útiles para
retornar objetos de la clase, y así, métodos de fárbica (factory methods).
Requieren como primer parámetro la palabra reservada cls.

class Cosa:

@staticmethod ## decorador
@classmethod
def estatico():
def declase(cls):
return "asdf"
return cls() ## Retorna una instancia de la clase que llama este método

Cosa.estatico()
Cosa.declase() ## Obtiene implícitamente como parámetro la clase
c = Cosa()
c = Cosa()
c.estatico()
c.declase() ## Solo obtiene la clase, no la instancia

- Para manejar constantes en una clase hay que tener presente que sus métodos
puedan acceder a las mismas dependiendo de su tipo:

Para métodos regulares Para métodos estáticos o


de clase:

Emplear self.<constante> Emplear


<clase>.<constante>

- Recordar que el operador == entre dos instancias revisa si ambas apuntan a la


misma dirección de memoria. Sin embargo este comportamiento puede sobreescribirse
al definir en una clase el mérodo __eq__ para ==, y __ne__ para != (opcional). Hay
que tener cuidado con el comportamiento sobreescrito de comparación para cuando hay
manejo de polimorfismo

Ejemplo:

class Punto:
"""Clase que modela un par ordenado"""

def __init__(self,x,y):
self.x = x
self.y = y

def __eq__(self,otro):
return self.x == otro.x and self.y == otro.y

- Se puede verificar la direccion en memoria a la que refiere una instancia


haciéndole un print

- Para copiar una instancia en otra (los elementos al interior), se emplean los
métodos del módulo copy (ver sección de módulos de Python)
/////////////////
9. HERENCIA
/////////////////

- La herencia se define con un paréntesis y la(s) superclase(s). Python admite


herencia múltiple

Ejemplo:

class Animal:
"""Clase que generaliza los animales"""

def __init__(self, genero, patas):


"""Metodo inicializador de animal"""
self.genero = genero
self.patas = patas

class Mamifero(Animal): ## Ejemplo de clase hija simple


pass ## Pass sirve para denotar que un bloque
de código está vacío (sino, error sintáctico)

class Reptil(Animal): ## Ejemplo de clase hija con especificaciones


"""Clase que representa un reptil"""

def __init__(self, genero, patas, escamas):


"""Metodo constructor de animal"""
Animal.__init__(self, genero, patas) ## Uso del
constructor de la superclase
self._escamas = escamas ## Recordar
el encapsulamiento por nombre

class Hibrido(Mamifero, Reptil):


pass

- En caso que en una herencia múltiple hayan métodos repetidos, la última clase
padre (la de más a la derecha) es la que sobreescribe el elemento repetido.

- Para acceder a métodos o atributos explícitamente de la superclase se usa


super(<nombre de esta clase>,self).<metodo o atributo>, y la superclase debe
heredar explícitamente de object (en Python 3 es más simple y solo se usa super()).
Útil también si hay sobreescritura en la clase hija

///////////////////
10. __slots__
///////////////////

- Dado que Python utiliza un diccionario para almacenar información y atributos de


los objetos que se hayan creado, esto en ocasiones es más de lo necesario y se
ofrece una forma de poder optimizar el manejo de memoria, esto es mediante el uso
de __slots__

- __slots__ es una variable especial (dunder) que permite predefinir los atributos
y manejarlos sin emplear el diccionario que se maneja por defecto para las
instancias de una clase. Simplemente requiere el nombre o lista de nombres de los
atributos que vaya a manejar la clase, y emplearlos de forma corriente, como si se
hubieran declarado. Por ejemplo:
class Cosa:
__slots__ = ['primer_atributo','segundo_atributo']

def __init__(self,primer_atributo,segundo_atributo):
self.primer_atributo = primer_atributo
self.segundo_atributo = segundo_atributo

- Tambien son heredados sin problema los atributos

////////////////////////////////
11. CLASES DE NUEVO ESTILO
////////////////////////////////

- Permiten manejar propiedades, métodos estáticos y descriptores

- Para designar una clase como "clase de nuevo estilo" se hace herencia a otra que
lo sea. Por ejemplo, se puede heredar de object.

////////////////////////////
12. METODOS ESPECIALES
(DUNDER METHODS)
////////////////////////////

- __init__(self, <args>) ## Llamado luego de la creación del objeto para


parametrizar el objeto

- __new__(cls, <args>) ## Para clases nuevo estilo: Constructor

- __del__(self) ## Para clases nuevo estilo: Destructor

- __str__(self) ## Retorna una cadena con info. del objeto de la


clase (análogo a toString). Usado en print <instancia> o str(<instancia>).
## Por ejemplo: El <objeto> tiene un
<atributo> de valor <valor>...

- __repr__(self) ## Igual que __str__ pero __str__ es más para dar


información más natural que __repr__ (este último da información de cómo está
## conformado el objeto, en términos de
desarrollador). Por ejemplo: <Objeto>(<valor>,<valor>,<valor>). __str__ tiene más
prioridad

- __cmp__(self, <otraInstancia>) ## Retorna comparación (mayor, menor o igual)


entre esta y otra instancia (análogo a compareTo).

- __len__(self) ## Retorna una medición de longitud del objeto


(según como se personalice)

- Entre otros (para slicing, operadores aritméticos, diccionarios, etc.)

- Se puede determinar mediante estos métodos cómo funcionan los operadores


aritméticos entre objetos. Los métodos para los operadores aritméticos son:

+ __add__(self,<otro>) - __sub__(self,<otro>) * __mul__(self,<otro>) //


__floordiv__(self,<otro>) / __div__(self,<otro>) % __mod__(self,<otro>)
** __pow__(self,<otro>[,<modulo>]) << __lshift__(self,<otro>) >>
__rshift__(self,<otro>) & __and__(self,<otro>) ^ __xor__(self,<otro>)
| __or__(self,<otro>)
////////////////////////
13. METODOS UTILES
////////////////////////

- En general para las secuencias:

min(<secuencia>) max(<secuencia>) ## Retorna el elemento menor y


mayor, respectivamente, de la secuencia

list(<secuencia>) ## Retorna una lista con todos los elementos de la


secuencia

zip(<secuencia_1>,<secuencia_2>) ## Crea una nueva secuencia con los


elementos de las ingresadas en cada posicion combinados como tuplas

enumerate(<secuencia>) ## Retorna un diccionario con los indices como


claves y los elementos como sus respectivos valores

- Para una cadena de caracteres S:

S.count(<subcadena>[,<inicio>[,<final>]]) ## Cuántas veces aparece una


subcadena. Opcionales: Posiciones inicio y final

S.find(<subcadena>[,<inicio>[,<final>]]) ## Busca posicion de la


subcadena (-1 si no está). Posiciones opcionales

S.join(<secuencia>) ## Usa S como separador de los elementos


concatenados de la secuencia

S.partition(<separador>) ## Devuelve una tupla con los elementos


de la cadena separados por el separador especificado

S.replace(<ocurrencia>,<reemplazo>[,<cantidad>]) ## Reemplaza toda


ocurrencia, a menos que se especifique la cantidad de veces

S.split([<separador>[,<cantidad>]]) ## Fracciona la cadena según un


separador, o por defecto por espacio. Puede especificar cuántas particiones

- Para la lista L:

L.append(<objeto>) ## Añade un objeto al final de la lista

L.count(<elemento>) ## Cuenta las ocurrencias de un elemento en la


lista

L.extend(<iterable>) ## Añade los elementos del iterable a la lista

L.index(<valor>[,<inicio>[,<parada>]]) ## Retorna el índice donde se


encuentra valor. Se pueden especificar índices de búsqueda

L.insert(<indice>,<elemento>) ## Inserta un objeto en el índice


especificado

L.pop([<índice>]) ## Quita y retorna un elemento de la lista en el


índice especificado. Por defecto: el último de la lista

L.remove(<elemento>) ## Quita la primera ocurrencia del elemento


especificado en la lista

L.reverse() ## Invierte la lista (por referencia)

L.sort([cmp=<funcion; None por defecto>, key=<funcion; None por defecto>,


reverse=<True|False; False por defecto>]) ## Ordena la lista.

> En cmp se especifica función de comparación que compare dos elementos


de la lista y retorne resultados tipo -1, 0 y 1
> En key se especifica una función que tome un elemento de la lista y
devuelva un valor que lo represente para realizar comparaciones
> En reverse se especifica si se debe ordenar en forma inversa

- Para el diccionario D:

D.has_key(<clave>) ## Verifica si existe la clave. Análogo a:


<clave> in D

D.items() ## Retorna una lista de tuplas. Cada tupla es


un mapeo del diccionario (clave,valor)

D.keys() ## Devuelve una lista de claves del diccionario

D.values() ## Devuelve una lista de valores del


diccionario

D.pop(<clave>[,<opcional>]) ## Retira el mapeo de la clave entrada y


retorna su valor. Si no existe se puede especificar retorno opcional

D.copy() ## Retorna una copia (por referencia) del diccionario con


sus elementos. Si se quiere copia sin referencias, realizar primero:
from copy import deepcopy y emplear el método
deepcopy(<objeto_a_copiar>)

D.get(<clave>[,<mensaje>]) ## Igual que D[<clave>]; opcional, un


mensaje por defecto si no existe la clave

/////////////////////////////////////////////
14. DECORADORES DE FUNCIONES Y CLASES
PROGRAMACION FUNCIONAL
/////////////////////////////////////////////

- Python es multiparadigma: maneja los paradigmas de orientación a objetos, de


programación estructurada y funcional.

- Funciones de nivel superior: son funciones que tienen como parámetro, incluso
como retorno, a otras funciones. Esto es posible dado que Python lo ve y maneja
todo mediante objetos.

Ejemplo 1:

def funcion_a(funcion):
print("Esta es la funcion a")
funcion()

def funcion_b():
print("Esta es la funcion b")
funcion_a(funcion_b)

Ejemplo 2:

def saludar(idioma):
def saludar_es():
print "Hola"

def saludar_en():
print "Hi"

def saludar_fr():
print "Salut"

idioma_funcion = {"es": saludar_es,


"en": saludar_en,
"fr": saludar_fr}

- Clases o funciones decorators son las que modifican el comportamiento de otras


clases o funciones, de modo que éstas se envuelven dentro de una funcionalidad
adicional.

- El esquema de una función decorator es el siguiente:

def funcion_base(argumento)
<hacer algo>

def funcion_decorator(funcion):
def funcion_nuevo_comportamiento(argumento):
<nuevo comportamiento>
return funcion(argumento)
return funcion_envolvente

funcion_base = funcion_decorator(funcion_base)

Se define la funcion base con un comportamiento, luego se pasa al decorator


esta función para retornarla con un nuevo comportamiento.

Una forma rápida de establecer un Decorator, para el ejemplo anterior:

@funcion_decorator
def funcion_base(argumento):
<hacer algo>

Para el caso que la función base tenga de cero a muchos argumentos, se define
manejo de múltiples argumentos en la función de nuevo comportamiento:

def funcion_decorator(funcion):
def funcion_nuevo_comportamiento(*args, **kwargs): ## kwargs =
keywords arguments. Es decir, por palabra clave (modo diccionario)
<nuevo comportamiento>
return funcion(*args, **kwargs) ## Estos asteriscos en
los argumentos son necesarios para que python los diferencie
return funcion_envolvente

@funcion_decorator
def funcion_base(arg1,arg2):
<hacer algo>
Un ejemplo que demuestra la utilidad de hacer esto es incluir como nuevo
comportamiento validaciones u otras operaciones previas a la función base:

#!/usr/bin/python

def funcion_decoradora(funcion_base):
def funcion_comprobada(numero):
if (numero < 0 or type(numero) != int):
raise Exception("El argumento no es cero o un entero
positivo!!!")
else:
return funcion_base(numero)
return funcion_comprobada

#@funcion_decoradora
def factorial(n):
if (n==0 or n==1):
return 1
else:
return n*factorial(n-1)

factorial = funcion_decoradora(factorial)

print(factorial(3))
print(factorial(3.3))

Otros casos de uso para emplear estas técnicas son: sincronización, conteo de
llamados a funciones, manejo de tiempos, entre otros.

- Para definir una clase como decorator se añade el método __call__. La idea es que
la clase almacene la referencia a la función que se quiere "decorar" como atributo
mediante el método constructor, y el método __call__ es la definición del nuevo
comportamiento para esta función (requiere como argumentos, por tanto, los que vaya
a manejar la función base)

Ejemplo:

class Decorador:

def __init__(self,funcion_base):
self._funcion_decorada = funcion_base

def __call__(self,*args,**kwargs):
<nuevo comportamiento>
return self.funcion_decorada(*args,**kwargs)

Se puede definir la decoración entonces:

def funcion([arg1,arg2,...,argN]):
<comportamiento base>

decorador = Decorador(funcion)
decorador.__call__([...])

O bien:

@Decorador
def funcion([...]):
<comportamiento base>

funcion([...])

//////////////////
15. ITERATOR
//////////////////

- Es un objeto que permite personalizar la forma en la cual se itera sobre una


colección de elementos (secuencia, diccionario, conjunto, etc.)

- Las ventajas de manejar iteradores, en especial en colecciones personalizadas, es


que se maneja un código limpio y se pueden ahorrar muchos recursos

- El iterador más básico es el elemento de iteración del foreach (for)

- El protocolo de iteración se define consiste en dos métodos de la clase de la


colección: __iter__() y __next__()

- La función __iter__() define la clase como iterable. Solo requiere retornar la


instancia de la clase (self), o bien puede incluir inicializaciones (como conteos,
etc.). La función __next__() define de qué manera se obtiene el próximo elemento de
la colección.

Ejemplo:

class CosaIteradora:

def __init__(self):
self.total = 8
self.cosas = [1,2,3,4,5,6,7,8]

def __iter__(self):
self.contador = 0
return self

def __next__(self):
if (self.contador < self.total):
self.contador += 1
return self.cosas[self.contador]
else:
raise StopIteration

coleccion = CosaIteradora()
iterador = iter(coleccion)
print(iterador.__next__)

En este caso se trata de una implementación que depende del iterador


para recorrer los elementos de la colección de forma personalizada

- Para crear un iterador se emplea la función iter(<colección>)

- La función <iterador>.__next__(<colección>) pasa al próximo elemento (según como


se haya personalizado para la colección). El cursor está ubicado antes del primer
elemento, así que el primer llamado a __next__() permite obtener este elemento. Si
se llega al final de la colección y se usa __next__() nuevamente, ocurre una
excepción StopIteration. Por defecto a las colecciones de Python ya se les define
este comportamiento y se pueden manejar con <iterador>.next()

- Ejemplo: ver lista_simple_iterator.py

///////////////////
16. GENERATOR
///////////////////

- Una función se establece como Generator para definir de forma rápida sobre qué
elementos permite iterar.

- Para volver una función un Generator, se escribe la palabra reservada yield y


luego la variable que se quiere retornar en las iteraciones.

- El retorno de la función es un Generator que tiene método next() para devolver


cada elemento definido como parte de la colección. Lanza un StopIteration cuando no
hay más elementos.

- Ejemplo:

def funcion_generator():
nombre = "pepe"
apellido = "veráz"
edad = 41
yield nombre
yield apellido
yield edad

print(str(funcion_generator().next()))
print(str(funcion_generator().next()))
print(str(funcion_generator().next()))

/////////////////////////
17. CONTEXT MANAGER
/////////////////////////

- Se usa principalmente para gestionar recursos en cuanto a apertura de archivos,


manejo de sócalos (sockets), commits en bases de datos, bloqueos en el sistema
operativo y manejo de excepciones.

- Para crear una clase que haga de Context Manager, se requiere agregar las
funciones __enter__ y __exit__, y la función __init__ debe poder inicializar el
objeto de manejo (manejador de archivos, excepción, conexión a base de datos, etc.)

- La función __enter__ inicializa, configura y retorna el objeto de manejo, y la


función __exit__ debe limpiar el manejador (cerrar conexiones, flujos, archivos,
etc.)

- Los parámetros de __exit__ deben ser: self, type, value, traceback (manejo
interno)

- Para usar el Context Manager personalizado se puede emplear la palabra reservada


with, como se mostró en la sección "Manejo de Archivos". De este modo, el objeto
Context Manager tiene acceso a las funciones del manejador que se haya
personalizado.

with <ClasePersonalizada> as <objeto>:


<objeto>.<funcion del manejador>([...])

Ejemplo de Context Manager para manejo de archivos:

class MiContextManager:

def __init__(self, nombre_archivo, modo_acceso):


self.nombre_archivo = nombre_archivo
self.modo_acceso = modo_acceso
self.manejador = None

def __enter__(self):
self.manejador = open(nombre_archivo, modo_acceso)
return self.manejador

def __exit__(self, type, value, traceback):


self.manejador.close()

with MiContextManager as manager:


manager.write("¡¡¡Contenido insertado en el archivo!!!")

//////////////////////////////////////
18. MANEJO DE ARCHIVOS DE OFFICE
//////////////////////////////////////

***ARHIVOS DOCX***

- Se debe instalar python-docx empleando en una línea de comandos la instrucción:


pip install python-docx

- Código básico para manejar un documento DOCX:

from docx import Document ## O bien, import docx y usar


docx.Document en el código

documento_vacio = Document() ## Inicializa un objeto sin


asociación a archivo (solo para crearlo)
documento_vacio.save("prueba.docx") ## Crea el archivo

documento = Document("prueba.docx")
documento.add_paragraph("Este es un ejemplo de párrrafo.")
documento.add_paragraph("Este es otro párrafo.")
documento.add_paragraph("Sigue una tabla:")
documento.add_table(rows=2,cols=3)
documento.save("prueba.docx") ## Notar que no requiere
cerrado, se maneja de forma automática

***ARCHIVOS XLSX***

- Requiere instalar xlsxwriter: pip install xlsxwriter en una línea de comandos

- Código básico para manejar un documento XLSX:

from xlsxwriter import Workbook

documento = Workbook("prueba.xlsx")
hoja_nueva = documento.add_worksheet("Nombre de la Hoja") ## Hoja<i>
por defecto
hoja_nueva.write("A1","Prueba de escritura en la celda A1")
hoja_nueva.close()

///////////////////////////////
19. EXPRESIONES REGULARES
///////////////////////////////

- Para buscar ocurrencias de patrones en las cadenas se pueden usar las expresiones
regulares en Python

- Se manejan con el módulo re

- Las cadenas que definen los patrones a buscar deben estar antecedidas por una r.
Ejemplo: r"patron"

- Sintaxis de patrones de expresiones regulares: concatencación de caracteres,


comodines, rangos, condiciones, etc.

Comodín de un solo caracter: .


Cero o una vez el elemento anterior: ?
Cero o más veces el elemento anterior: *
Una o más veces el elemento anterior: +
Rango alfabético: a-z A-Z
Rango numérico: 0-9
Palabras (alfabéticos): \w
Encerrar como un elemento: ()
Opciones: [<opcion1><...><opcionN>] ## (si tiene guión como
opción debe ir al final para que no se malinterprete como un rango)

- Código básico de manejo:

import re

busqueda = re.search(r<cadena del patrón a buscar>, <cadena a


analizar>[,<flags>]) ## Retorna un objeto con las ocurrencias, None si no
hay alguna
busqueda.group(<#ocurrencia>) ## De la ocurrencia 1 a la N

re.findall(r<cadena del patrón a buscar>, <cadena a analizar>[,<flags>])


## Retorna una lista de ocurrencias

Ejemplo:

import re

email = "correo@hotmail.com"
busqueda = re.search(r"([\w.-]+)@([\w.-]+)",email) ## El
elemento ([\w.-]+) se puede leer como: una a muchas palabras o puntos o guiones

print("El nombre de usuario es {} y el dominio es


{}".format(busqueda.group(1),busqueda.group(2)))

////////////////////////
20. BASES DE DATOS
////////////////////////
************
***SQLITE***
************

- Un aplicativo útil para trabajar con bases de datos tipo SQLITE: DB Browser for
SQLite http://sqlitebrowser.org/

- Emplea el módulo sqlite3

- Código básico:

import sqlite3

conexion = sqlite3.connect("baseDeDatos.db") ## Si el archivo no


existe, lo crea

operacion = conexion.execute('''<OPERACION DE BASES DE DATOS>''')


## Las operaciones de bases de datos (DML, DDL, etc.) van en triple comilla
sola

## El objeto guarda información del éxito, consultas,


etc.

conexion.commit() ## OJO, ES UN SISTEMA DE BASES DE DATOS


TRANSACCIONAL, HAY QUE RECORDAR CONFIRMAR TODA LA TRANSACCION SI SE REQUIERE

conexion.close() ## Para cerrar la conexión a la base de datos

## Sin embargo, se recomienda mejor realizar estas operaciones desde el


objeto cursor, obtenido de la función cursor() del objeto de conexión

cursor = conexion.cursor()
operacion = cursor.execute('''<OPERACIONES DE BASES DE DATOS>''')

- La inserción de parámetros en las operaciones de base de datos NO DEBEN ser


mediante operaciones de cadenas, porque esto hace el código propenso a inyecciones
SQL. Para entrar información externa a una operación se emplea un placeholder
(marcador de posición), en este caso ?, que indica dónde se ingresa esta
información

Ejemplo:

NO HACER:

nombre_busqueda = "pepa"
consulta = cursor.execute('''SELECT * FROM mascota WHERE
nombre="%s";''' % (nombre))

EN CAMBIO, HACER:

nombre_busqueda = ("pepa",) ## Manejar como una tupla de


entradas
consulta = cursor.execute('''SELECT * FROM mascota WHERE
nombre=?;''', nombre_busqueda)

Útil por ejemplo para inserciones:

registro = (0,"pepa","h","cerdo")
cursor.execute('''INSERT INTO mascota VALUES(?,?,?,?);''',
registro)

- El método executemany del objeto cursor permite ejecutar una misma instrucción
SQL sobre una lista de parámetros de entrada

Ejemplo:

inserciones = [(1,'tobias','m','perro'),(2,'pepito','m','perico'),
(3,'andrea','h','castor')]

cursor.executemany('''INSERT INTO mascota VALUES(?,?,?,?);''',


inserciones)

- Las operaciones de consulta almacenan en el objeto al que se asignen una lista de


registros, que a la vez tienen una lista de columnas. Basta usar for.

Ejemplo:

consulta = cursor.execute('''SELECT * FROM mascota;''')

print("La base de datos contiene:")

resultado = ""

for registro in consulta:


for columna in registro:
resultado += " "+str(columna)+" "
resultado += "\n"

print(resultado)

- Para obtener uno a uno los registros retornados por la operación de consulta, se
puede emplear el método fetchone() del objeto que de consulta

- El objeto cursor también dispone del atributo description, que tiene informacion
incluyendo los nombres de columnas. Se puede usar la siguiente comprensión para
obtener los nombres de las columnas:

columnas = [informacion[0] for informacion in cursor.description]

O empleando list, map y lambda: columnas = list(map(lambda informacion:


informacion[0], cursor.description))

- Ver el ejemplo: database_basico.py

****************
***POSTGRESQL***
****************

- https://www.postgresql.org/

- Aplicación de manejo: pgAdmin (la versión actual es una página sobre el localhost
que abre en el navegador por defecto)

- Poner atención a los detalles de instalación: puerto y contraseña.

- Se puede poner el servicio postgresql con inicio manual para evitar carga al
inicio del Sistema Operativo (en services.msc o Panel de Control > Herramientas
Administrativas > Servicios). Recordar iniciarlo para las operaciones sobre este
motor de bases de datos

- Se requiere instalar los paquetes de Python: pygresql y psycopg2 (con el comando


pip install en línea de comandos)

- Por defecto, la conexión a la base de datos maneja como login

- Para realizar la conexión se requiere de la base de datos, por tanto esta debe
haberse creado con anterioridad

- Las consultas requieren que se defina un objeto cursor que las gestione
(funcionan igual que en SQLITE)

- Código básico de manejo:

import psycopg2

try:
conexion = psycopg2.connect(database="mibasededatos", user="postgres",
password="sql12345", host="127.0.0.1", port="5432")

cursor = conexion.cursor()

cursor.execute("""<OPERACION DE BASE DE DATOS>""") ## OJO,


requiere triple comilla doble para manejar comilla simple para strings internos
except OperationalError as o:
print("Error de conexión " + str(o))
except Exception as e:
print("Problema con operaciones en la base de datos..." + str(e))
else
conexion.commit()
finally
conexion.close()

- A diferencia de sqlite3, psycopg2 no almacena consultas al ejecutarlas; se


requiere primero hacerlas, y luego emplear los métodos fetch para almacenar los
resultados

Ejemplo:

cursor.execute("""SELECT FROM mascota;""")

consulta = fetchone() ## Para retornar un solo


registro
consulta = fetchmany(<# registros>) ## Para retornar un numero
determinado de registros
consulta = fetchall() ## Para retornar todos
los registros

resultado = ""

for registro in consulta:


for columna in registro:
resultado += " "+str(columna)+" "
resultado += "\n"

- El placeholder para insercion de parametros en las instrucciones SQL es %s


Ejemplo:

inserciones = [(2,'pepa','h','cerdo'),(3,'tobias','m','perro')]

cursor.executemany("""INSERT INTO mascota VALUES(%s,%s,%s,%s);""",


inserciones)

////////////////////////////
21. MÓDULOS ESPECIALES
////////////////////////////

- math para operaciones matematicas en dominio real (ejemplo: math.sin(angulo)), y


en dominio complejo cmath

- random para generaciones aleatorias (ej: random.random() random.__all__)

- copy para copiado de objetos (por sus elementos internos)

Para una copia simple, en donde si un elemento interno es otro objeto solo se
requiere manejo a su referencia, se usa la funcion copy:

objeto1 = copy.copy(objeto2)

Pero si no se quiere una referencia, sino una copia de éste como tal, se usa
la funcion deepcopy:

objeto1 = copy.deepcopy(objeto2)

- os para operaciones del sistema operativo

Para ejecutar instrucciones del sistema operativo (linea de comandos):

os.system('cls')
os.system('color 4f')

Para obtener la ruta absoluta del directorio de trabajo actual:

directorio = os.getcwd()

Para obtener la ruta absoluta de un archivo en el directorio actual:

os.path.abspath(<nombre del archivo>) ## Retorna una cadena


con la ruta absoluta del archivo

Comprobar existencia de una ruta absoluta o relativa:

os.path.exists(<ruta>) ## Retorna un booleano que indica si la


ruta existe

Comprobar si una ruta dada corresponde a un directorio:

os.path.isdir(<ruta>) ## Retorna booleano que incica si la ruta


corresponde a un directorio
~ isfile ~ ~ ~
~ ~ ~ archivo

Listar todos los elementos en un directorio dado por una ruta:


os.listdir(<ruta>) ## Retorna una lista con los nombres de
los elementos en el directorio

Unir nombre de archivo con su ruta:

os.path.join(<ruta>,<nombre>)

///////////////////////////////
22. MANEJO DE EXCEPCIONES
///////////////////////////////

- Recordar que error es el problema que tiene un programa en tiempo de ejecución


que no es manejado y probablemente no recuperable, mientras excepción es un
problema o situación que puede ocurrir y se le puede dar manejo (hasta posible
recuperación) en tiempo de ejecución

- El bloque de manejo de excepciones en Python es el siguiente (siguiendo el


esquema de palabra reservada, dos puntos e indentación):

try:
<operaciones a ejecutar que pueden derivar en situaciones de excepción>
except <tipo de excepción(es); la más genérica es Exception> [as <nombre de
objeto de excepción>]:
<operaciones a realizar cuando haya surgido la excepción>
else:
<operaciones a ejecutar si no ocurre excepción alguna de la(s)
definida(s)>
finally:
<operaciones a ejecutar siempre, pasen o no excepciones>

- Se pueden poner varias excepciones en el bloque de manejo, sin embargo Python


sólo saltará al del respectivo tipo de excepción que haya surgido. Ejemplo:

try:
archivo = open("archivoquenoexiste.txt") ## Esta operacion usa el
modo por defecto (de lectura), lanza IOError si el archivo no existe
numero = 8/0
except IOError as ioerr: ## En este caso Python maneja esta al
ocurrir primero
print("Archivo no encontrado")
print(str(ioerr))
except ZeroDivisionError:
print("NO DIVIDIR POR CERO!!!")
else:
print("No ocurre excepcion alguna de las definidas")
finally:
archivo.close()
print("Se continua con el programa despues de manejar excepciones")

- Aunque también se pueden unir varios tipos en un solo except:

try:
archivo = open("archivoquenoexiste.txt")
numero = 8/0
except (IOError,ZeroDivisionError) as ex
print("Archivo no encontrado o Division por cero!")
print(str(ex))
- Se puede ver una lista de excepciones predefinidas (Built-in) en la documentación
de Python (docs.python.org/2/library/exceptions.html)

- Para lanzar (hacer surgir) una excepción si ocurre una condición, se emplea raise
<tipo de excepción>[(<mensaje>)]. Recordar que esta funciona como un retorno y sale
del bloque que la haya lanzado hasta un bloque de manejo

- Para crear una excepción personalizada se crea una clase (con sufijo Error;
convención de Python) que herede de RuntimeError. Ejemplo:

class MiError(RuntimeError):

def __init__(self,mensaje):
self.mensaje = mensaje

///////////////////////////
22. PRUEBAS DE UNIDAD
///////////////////////////

- Una forma sencilla y directa de hacer una prueba de unidad a una función es crear
los casos de prueba en su documentación y luego probar con la función testmod de la
librería doctest

- Los casos de prueba se definen al final de la documentación de la función y


contienen un llamado de la función (precedido por >>> y un espacio), luego un salto
de línea, y el valor que la función debe retornar

Ejemplo:

def multiplicacion(a,b):
"""Esta funcion multiplica dos numeros ingresados
Precondicion: a y b son numeros
Postcondicion: ninguna
Retorno: a*b

Casos de prueba:
>>> multiplicacion(6,9)
54
>>> multiplicacion(2,0)
0
>>> multiplicacion(-2,10)
-20
"""
return a*b

if __name__ == "__main__":
import doctest
doctest.testmod(verbose=True,optionflags=doctest.NORMALIZE_WHITESPACE)

//////////////////////////
23. INTERFAZ GRÁFICA
//////////////////////////

- Hay todo un conjunto de paquetes distintos con los que se puede trabajar la
interfaz gráfica: wxPython, Tkinter, PythonWin, JavaSwing, PyGTK

- Aqui se maneja el paquete wxPython en la version 3.0 (wxpython.org)


- Es requerido que wxPython tenga la misma arquitectura de Python, o no funcionará.
Se puede comprobar la arquitectura instalada con:

import platform
platform.architecture()

- Una vez instalado y compilado, se puede probar intentando importar el módulo wx

- Ejemplo básico de un código de interfaz gráfica (recordar que el manejo de una


ventana debe hacerse en una clase para la misma):

import wx

def funcion_evento_boton(evento):
print("El boton funciona")

aplicacion = wx.App() ## Define la base de la aplicación para poder


inicializar elementos de interfaz gráfica

ventana = wx.Frame(None,title="Titulo", size=(500,600)) ## Creación


de un objeto ventana

panel = wx.Panel(ventana) ## Crea y pone el panel en la ventana


boton = wx.Button(panel,label="Clickeame",pos=(100,100)) ## Crea y
pone un botón en el panel
boton.Bind(wx.EVT_BUTTON, funcion_evento_boton) ## Enlaza al
elemento botón una escucha a un evento y una función de manejo para el evento
etiqueta = wx.StaticText(panel,label="Ejemplo de label")

ventana.Show() ## Muestra la ventana (siempre al final, para que ya


se hayan configurado los elementos)

aplicacion.MainLoop() ## Ejecuta el bucle principal de la aplicación


(hilo de control)

- Los elementos tienen getters y setters para sus respectivas propiedades. Por
ejemplo boton.SetLabel("Otra Etiqueta")

- El constructor de una ventana tiene los siguientes parámetros:

- Los elementos de interfaz en wxPython son Panel (panel), Button (botón),


TextCtrl (area de texto de 1 a muchos renglones), StaticText (etiqueta de texto)

- Para manejar eventos, se enlaza una función manejadora al elemento de interés a


escuchar. Se incluye primero el tipo de evento y luego la referencia a la función
manejadora:

<elemento>.Bind(<tipo de evento>, <referencia a funcion manejadora>)


## Ojo: Solo el nombre de la funcion u objeto que le apunte a ésta

Ejemplo:

boton.Bind(wx.EVT_BUTTON, FuncionManejadora)

- Una aplicación de interfaz gráfica -el módulo principal, al menos- tiene esta
forma:

import wx
class Ventana(wx.Frame):

def __init__(self,padre,[,<otros parametros>]):


wx.Frame.__init__(self,padre,[,<otros parametros>])

self.panel = wx.Panel(self)

self.boton = wx.Button(self,pos(50,50),label="OK")
self.campo = wx.TextCtrl(pos(100,50))
<otros elementos en el panel>
<enlaces a evento> ## Recordar llamar la funcion
manejadora con self (self.<funcion>)

<funciones de manejo de eventos> ## Como es una clase, no


requiere que estén al principio

if __name__ == "__main__": ## Comprobando que se trata del módulo


principal del programa, ejecutar las acciones de programa principal
aplicacion = wx.App()

ventana = Ventana(None)

ventana.Show()

aplicacion.MainLoop()

- Para que los componentes de la interfaz cambien de tamaño con ésta, se puede
emplear BoxSizer. Se requiere uno para manejo horizontal y otro para vertical:

## Horizontal
sizerH = wx.BoxSizer()
sizerH.Add(<componente de interfaz>, proportion = 1, flag = wx.EXPAND [|
<otras>]) ## Para un componente que no se quiera redimensionar: proportion = 0

## Vertical
sizerV = wx.BoxSizer()
sizerV.Add(sizerH, proportion = 0, flag = wx.EXPAND | wx.ALL) ##
Necesario añadirle el horizontal
sizerV.Add(<componente de interfaz>, proportion = 1, flag = wx.EXPAND [|
<otras>])

## Se agrega al panel el sizer vertical


panel.SetSizer(sizerV)

////////////////////////
24. HILOS?
////////////////////////

///////////////////////////
25. JUEGOS CON PYGAME
///////////////////////////

- La librería Pygame se especializa en manejar los elementos que conforman


aplicaciones de juegos: manejo de hilos, eventos, objetos, interfaz gráfica,
sonido, hardware y todo lo demás que se requiera
- La instalación se puede realizar empleando pip en una línea de comandos: pip
install pygame

- La documentación oficial de esta librería, incluyendo ejemplos y tutoriales, se


puede encontrar en https://www.pygame.org/docs/

- Para definir una pantalla base (como variable de la clase principal del juego) se
usa:

self.pantalla = pygame.display.set_mode((<ancho>,<alto>))

Los elementos de display de pygame permiten configurar la pantalla del juego


(título, colores, etc.)

- Pygame dispone de un objeto Rect que define posición y dimensiones de los objetos
en términos de un rectángulo (ancho y alto). Este Rect es usado comunmente como
atributo (propiedad) de tales objetos, que normalmente es llamada cada que se va a
dibujar, retornando un objeto Rect actualizado

Ejemplo:

@property
def rectangulo(self):
return pygame.Rect(self.x,self.y,self.ancho,self.alto)

- Para dibujar formas, están los métodos derivados de draw. Por ejemplo:
pygame.draw.ellipse(screen, <tupla RGB color>, <Rect>)

~~~~.rect

- Pygame define varios eventos, sus tipos y características. Se puede comparar,


dado un evento, si corresponde a los que maneja Pygame:

Ejemplo:

def manejar_eventos(self,evento):
if evento.type == pygame.KEYDOWN:
print("SE HA OPRIMIDO UNA TECLA")
if evento.type == pygame.K_LEFT:
print("IZQUIERDA!")

- Para agregar música, cosa que le corresponde a la clase o módulo principal:

- Se requiere inicializar los parámetros de audio con pygame.mixer.pre_init

Ejemplo: pygame.mixer.pre_init(44100,16,2,4096)

- Con los métodos del objeto music de mixer: se carga el archivo load, se
define un volumen entre 0 y 1 con set_volume y se reproduce con

pygame.mixer.music.load("bgmusic.mp3")
pygame.mixer.music.set_volume(0.5)
pygame.mixer.music.play(-1) ## Parámetro es número de
repeticiones. 1 por defecto, -1 infinitas

- Para dibujar texto en pantalla:

objeto_fuente = pygame.font.Font(None,<tamaño de letra>)


texto_en_pantalla = objeto_fuente.render(<texto a mostrar>)
pantalla.blit(texto_en_pantalla,<tupla de posición>)

- El manejo de colisiones se realiza de acuerdo a la situación. Simplemente se


valida si ocurre una colisión entre el rectángulo (caja de colisión) de un objeto
con el de otro, empleando el método "colliderect" del rectángulo de uno de los dos
objetos (como parámetro requiere el rectángulo del otro objeto)

Ejemplo:

if self.raqueta.rectangulo.colliderect(self.bola.rectangulo):
<manejo para la colisión entre estos dos objetos>

- El bucle principal del programa se puede manejar en un método aparte. En este


bucle se maneja:

Demora de tiempo para la siguiente actualización, dada por tasa de cuadros


por segundo. Esto lo hace el método tick() de un objeto reloj de tipo
pygame.time.Clock()

reloj.tick(50) Crea una demora para 50 cuadros por segundo

Un bucle sobre todos los eventos recibidos en la actualización presente, de


modo que se gestionen qué acciones se llevan a cabo para cada evento

Como parte final de la actualización se gestionan colisiones y se refrescan


los dibujos del programa

def jugar(self):
reloj = pygame.time.Clock()

while(True):
clock.tick(50)

for event in pygame.event.get():


if event.type == pygame.QUIT:
self.detener()
pygame.quit()
sys.exit()
if event.type in (pygame.KEYUP, pygame.KEYDOWN):
self.raqueta.evento_tecla(event)

self.manejar_colisiones()
self.dibujar_figuras()

- Para dormir el programa N segundos es útil manejar time.sleep(N) (del módulo


time)

- Para finalizar la ejecución activa del juego, emplear pygame.quit()

- Primer ejemplo: pingpong.py

////////////////////////
TRUCOS ADICIONALES
////////////////////////

- Si se van a trabajar caracteres de otra codificación en las cadenas, agregar


#coding=utf-8 al principio de la script
- Una forma de revisar si una secuencia o diccionario está lleno o vacío, es
empleando la función bool(<estructura>). True: llena; False: vacía

- La palabra reservada pass es necesaria para los casos de bloques de código vacío
en condiciones, funciones, etc.

- Diccionario de diccionarios (para manejar varios registros). Ejemplo:

datos = {'1': {
'nombre' = 'jose',
'apellido' = 'pera'
},
'2': {
'nombre' = 'pepe',
'apellido' = 'naranjo'
}
}

- Diccionario tabla (para relacionar todos los elementos de varias colecciones).


Por ejemplo:

ids = [0,1,2,3,4,5]
nombres = ['pepe','paco','pica','papas','pico']
platas = [5000,125000,2354000,2000,1000]

tabla = {'id': ids,


'nombre': nombres,
'plata': platas}

- Busqueda recursiva en profundidad en un directorio:

import os

def busqueda(directorio):
ramas = os.listdir(directorio)
for elemento in ramas:
ruta_elemento = os.path.abspath(elemento) ## O bien tambien con
os.path.join(directorio,elemento)
if(os.path.isdir(ruta_elemento)):
busqueda(ruta_elemento)
else:
print()

def envolvente(ruta):
print("Se va a buscar en profundidad qué hay en %s" % ruta)
busqueda(ruta)

- Uso de una funcion para cada elemento en una o varias secuencias:

map(<nombre/referencia a funcion>,<secuencia1>,...,<secuenciaN>)

Para ver las tuplas de argumentos (por cada índice de cada secuencia) que se
pasará a una función, se pueden usar:

map(None,<sencuencia1>,...,<secuenciaN>)
zip(<secuencia1>,...,<secuenciaN>) ## Esta función crea tuplas
de cada elemento entre varias secuencias

Ejemplo: Suma de vectores


def suma(a, b, c):
return a + b + c

map(suma,[1,2,3,4,5],[0,0,0,0,0],[1,-1,5,1,2]) ## Retorna: [2,1,8,5,7]

O una forma rápida con lambda:

map((lambda a, b, c: a + b + c), [1,2,3,4,5],[0,0,0,0,0],[1,-1,5,1,2])


## Retorna: [2,1,8,5,7]

- Uso de una función para todos los elementos de una secuencia (secuencialmente en
resultados de pares del primero al ultimo):

reduce(<funcion>,<secuencia>)

Ejemplo: Sumatoria

reduce((lambda a, b: a + b), [1,2,3,4,5,6,7m8])

- Para filtrar los elementos de una secuencia por una condición se puede usar la
función filter. Requiere: funcion que retorne la evaluación de condición para un
elemento de la secuencia y la secuencia a filtrar

Ejemplo 1: Para filtrar solo los numeros pares en una secuencia

secuencia = [1,2,3,4,5,6,7,8,9,10]

def validar_par(numero):
return numero % 2 == 0

filter(validar_par, secuencia) ## Retorna [2,4,6,8,10]

O bien, con lambda: filter((lambda numero: numero % 2 == 0),


secuencia)

Ejemplo 2: Filtrar las letras que están en una cadena

letras = "a b c d e f g h j i k l m n ñ o p q r s t u v w x y
z".split() ## ["a","b",...,"y","z"]
cadena = "esta es una cadena de ejemplo"

filter((lambda letra: letra in cadena), letras)

- Al ser Python un lenguage de programación dinámico éste permite realizar Monkey


Patching. La técnica de programación Monkey Patching consiste en modificar el
código de una instancia de un programa, es decir, permite modificar en tiempo de
ejecución el código de funciones, atributos y clases.

Ejemplo:

class Cosa:

def __init__(self):
pass

def hacer_algo(a,b):
print("{} {}".format(a,b))
def nueva_funcion(a,b): ## Debe tener la misma cantidad de
parametros que la que va a ser reemplazada
print("Nueva funcionalidad")

Cosa.hacer_algo = nueva_funcion

- Creación de un Singleton (clase que permite una única instancia existiendo a la


vez)

Recordando el Singleton básico, se recuerda su esquema:

clase Singleton

privado estático instancia = NULL

privado Constructor(<params>)
<inicializar y configurar>

público ObtenerInstancia()
si instancia == NULL:
instancia = Constructor(<params>)
retornar instancia

De forma corriente empleando el __init__:

class Singleton:

_instancia = None

def __init__(self[,params]):
"""Este es un init privado solo en esencia, no se debe
poder llamar"""
<inicializar y configurar>
if (Singleton._instancia != None):
raise Exception("No se puede crear otro objeto porque
esto es un Singleton")
else
Singleton._instancia = self ## Guardar esta
instancia que ya se debe haber inicializado y configurado

@staticmethod
def get_instancia():
if(Singleton._instancia == None):
Singleton._instancia = Singleton() ## Aqui
implica el llamado de init
return Singleton._instancia

Aunque el método especial en Python que se encarga como tal de crear la


instancia, el constructor verdadero, es __new__(cls[,<params>]), por tanto, el
Singleton adaptado a Python queda:

class Singleton(object): ## Hacerla que herede de object


para poder acceder a la definición base de __new__

_instancia = None ## Para acceso estático y privado


(Singleton._instancia, o bien, cls._instancia, para el método __new__)

def __new__(cls,*args,**kwargs):
if cls._instancia == None: ## Tambien se puede
decir, por ejemplo: if cls._instancia not _instancia (None es como False)
cls._instancia =
super(Singleton,cls).__new__(cls,*args,**kwargs)
reruen cls._instancia

De este modo se resume todo en el constructor de Singleton: toda variable que


lo use va a apuntar al mismo único objeto. Esto se evidencia haciendo:

puntero1 = Singleton()
puntero2 = Singleton()

print(id(puntero1))
print(id(puntero2))

Que deben dar iguales id

/////////////////////
RECOMENDACIONES
/////////////////////

- Usar variables globales solo si es estrictamente necesario

- No modificar parámetros mutables en las funciones si no se ha definido que ésto


deba ocurrir

- Crear funciones con propósitos únicos y sencillos; por tanto, evitar funciones
grandes y que hagan más de una tarea

- Evitar modificar las variables directamente sobre otro archivo de módulo

- Recordar que al importar los módulos se concatena, compila y ejecuta su código


fuente. Por ello es importante usar if __name__ == "__main__" para establecer
únicamente las operaciones que le conciernen al programa principal (el módulo que
ejecuta el programa), y no a uno importado.

- Revisar las guías de estilo de escritura de código python para seguir un estándar
de nombramiento de variables, funciones, etc.

Potrebbero piacerti anche