Paradigmas de La Programación Con Python
Paradigmas de La Programación Con Python
Paradigmas de La Programación Con Python
22 de marzo de 2019
Resumen
Este documento fue preparado para servir de apoyo a la asignatura “Paradigmas de la Programación” de
la carrera de Licenciatura en Ciencias Informáticas de la Facultad Politécnica de la Universidad Nacional de
Asunción. En él, se examinan los principales modelos de programación usando como ejemplo código escrito en
Python, un lenguaje de programación multiparadigma.
1
ÍNDICE ÍNDICE
Índice
1. Introducción 3
1.1. ¿Qué es un paradigma? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2. ¿Qué es un lenguaje? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.3. ¿Qué es un programa? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.4. Código binario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.5. Arquitectura del conjunto de instrucciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.6. Lenguaje de programación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.7. Lenguaje de máquina . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.8. Ensamblador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.9. Máquina virtual . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.10. Tipado de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.10.1. Strong typing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.10.2. Weak typing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.10.3. Static typing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.10.4. Dynamic typing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.10.5. Duck typing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
4. El Paradigma Estructurado 9
4.1. Estructuras jerárquicas de flujo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
5. El Paradigma Modular 10
5.1. ¿Qué es un módulo? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
5.2. Entrada-Proceso-Salida . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
5.3. Divide y vencerás (top-down) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
5.4. Procedimientos y funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1. Introducción
1.1. ¿Qué es un paradigma?
La palabra “paradigma” proviene del griego pará (junto) y deigma (modelo), y significa algo así como “demostración
de un modelo”.
Un paradigma es un esquema formal de organización, un marco teórico, un conjunto de teorías alrededor de un
tema determinado.
El código binario puede representar cualquier tipo de información, por ejemplo la letra minúscula “a” en la tabla
ASCII (American Standard Code for Information Interchange) está representada por la cadena de bits “01100001”
que tiene un valor decimal de 97.
1.8. Ensamblador
El término “ensamblador” puede referirse a un programa especial o a un lenguaje de programación (Assembly). El
Assembly o Ensamblador es un tipo de lenguaje de bajo nivel (cercano al hardware) que permite una representación
un poco más abstracta de las instrucciones en código máquina.
El programa ensamblador es capaz de leer un archivo con instrucciones escritas en lenguaje Assembly y traducirlas
a código máquina, para posteriormente almacenarlas en un nuevo archivo, denominado objeto o ejecutable.
Los ensambladores fueron creados para facilitar la escritura de programas en código máquina, ya que hacerlo en
código binario resulta muy complicado para el ser humano.
4. El Paradigma Estructurado
4.1. Estructuras jerárquicas de flujo
En la programación estructurada se utilizan únicamente tres tipos de estructuras: secuencia, selección e iteración,
siendo innecesario el uso de instrucciones de transferencia incondicional como GOTO, GOSUB o EXIT que
actualmente están casi en desuso.
Estructura secuencial: Las instrucciones se ejecutan una tras otra, a modo de secuencia lineal. Una instrucción
no se ejecuta hasta que finaliza la anterior, ni se bifurca el flujo del programa.
nombre = input('¿Cuál es tu nombre? ')
print('Hola,', nombre)
Estructura selectiva: La ejecución del programa se bifurca a una instrucción (o conjunto) u otra(s) según un
criterio o condición lógica establecida. Sólo uno de los caminos en la bifurcación será el tomado para ejecutarse.
Palabras reservadas de Python: if-else-elif
nombre = input('¿Cuál es tu nombre? ')
if nombre.isdigit():
print('Dije nombre, no edad...')
elif nombre:
print('Hola,',nombre)
else:
print('No seas timido...')
Estructura iterativa: Dada una secuencia de instrucciones, un bucle iterativo o iteración hace que se repita su
ejecución mientras se cumpla una condición. El número de iteraciones normalmente está determinado por el cambio
en la condición dentro del mismo bucle, aunque puede ser forzado o explícito por otra condición.
Palabras reservadas de Python: for, while
nombre = 'x'
while nombre:
nombre = input('¿Cuál es tu nombre? ')
if nombre.isdigit():
print('Dije nombre, no edad...')
elif nombre:
print('Hola,', nombre)
vocales = 0
for letra in nombre:
if letra in 'aeiou':
vocales += 1
print('Tu nombre tiene',vocales,'vocales')
else:
print('No seas timido...')
5. El Paradigma Modular
5.1. ¿Qué es un módulo?
Un módulo es cada una de las partes de un programa que resuelve uno de los subproblemas en que se divide el
problema complejo original. Cada uno de estos módulos tiene una tarea bien definida y algunos necesitan de otros
para poder operar.
5.2. Entrada-Proceso-Salida
Cada módulo puede o no aceptar valores de entrada y retornar valores de salida. El proceso interno puede o no estar
relacionado con dichos valores. Los valores de entrada se denominan indistintamente parámetros, argumentos o
atributos.
Cada objeto es capaz de recibir mensajes, procesar datos, y enviar mensajes a otros objetos, como si se tratara de
una “máquina” o caja negra, independiente, con un papel distinto o una responsabilidad única. Tanto los datos
(llamados propiedades) como las acciones sobre esos datos (llamadas métodos) están estrechamente asociados con
el objeto correspondiente.
6.2. Clase
Una clase es un modelo o plantilla que describe un tipo de objeto. El intérprete o compilador utiliza la clase cada
vez que se tenga que crear un nuevo objeto. Este proceso se llama instanciación. Cada objeto es una instancia de
alguna clase.
6.4. Propiedades
Las propiedades pueden ser de clase o de instancia:
class OtraClase:
PropiedadClase = 0
def __init__(self, n): # Método constructor de la clase
self.PropiedadObjeto = n # Propiedad de instancia
print(Objeto1.PropiedadClase) # Imprime: 0
print(Objeto1.PropiedadObjeto) # Imprime: 1
print(Objeto2.PropiedadClase) # Imprime: 0
print(Objeto2.PropiedadObjeto) # Imprime: 2
print(Objeto1.PropiedadClase) # Imprime: 3
print(Objeto2.PropiedadClase) # Imprime: 3
de Clase: Una propiedad de clase es aquella cuyo estado es compartido por todas las instancias de la clase.
de Instancia: Una propiedad de instancia (o de objeto) es aquella cuyo estado puede ser invocado o
modificado únicamente por la instancia a la que pertenece.
6.5. Métodos
Un método es una función, procedimiento o subrutina que forma parte de la definición de una clase. Los métodos,
mediante sus parámetros de entrada, definen el comportamiento de las instancias de la clase asociada.
cantidad = 0
def saludar(self):
print("Hola, mi nombre es", self.nombre)
print("Somos", Persona.cantidad)
def __del__(self):
print("{} dice adiós.".format(self.nombre))
sobrino_donald = dict()
for nombre in ["Hugo", "Paco", "Luis"]:
sobrino_donald[nombre] = Persona(nombre)
sobrino_donald[nombre].saludar()
En el ejemplo, nombre es un atributo o parámetro del método __init__, mientras que self.nombre es una
propiedad de instancia y cantidad es una propiedad de clase.
class Archivo:
def __init__(self, nombre='lista.txt'):
self.items = open(nombre).readlines() if os.path.exists(nombre) else []
self.items = [ item.strip() for item in self.items ] # Quita espacios sobrantes
self.nombre = nombre
def __del__(self):
open(self.nombre,'w').write('\n'.join(self.items))
lista = ListArch()
lista.vaciar()
lista.agregar('Hola')
lista.agregar(2)
lista.agregar((3, 4))
lista.agregar('Chau')
En el código anterior, __init__, __repr__, __str__ y __del__ son ejemplos de redefiniciones de métodos
predefinidos que modifican parte del comportamiento estándar de los objetos, en este caso la construcción y la
impresión del objeto, respectivamente. A esta capacidad del lenguaje se le denomina sobrecarga.
__repr__ devuelve una representación del objeto en forma de cadena de caracteres, que puede volver a
generar el objeto si es evaluada nuevamente, por ejemplo con la función eval().
__str__ devuelve una cadena de caracteres cuando se imprime el objeto con la sentencia print().
class Ejemplo1(object):
def __init__(self):
self._x = 1
def getx(self):
return self._x
def setx(self, valor):
self._x = valor
def delx(self):
del self._x
x = property(getx, setx, delx, "Soy la propiedad 'x'.")
class Ejemplo2(object):
def __init__(self):
self._x = 2
@property
def x(self):
"Soy la propiedad 'x'."
return self._x
@x.setter
def x(self, valor):
self._x = valor
@x.deleter
def x(self):
del self._x
Ambas sintaxis cumplen el mismo propósito y dan exactamente los mismos resultados, pero la segunda es la más
nueva incorporación al lenguaje Python.
También contamos con los métodos __getitem__ y __setitem__ para manipular listas y/o diccionarios usando
únicamente el nombre del objeto. De esta manera, en vez de escribir objeto.lista[indice] simplemente podemos
escribir objeto[indice].
class Dic:
def __init__(self):
self.dic = {}
def __repr__(self):
return repr(self.dic)
def __getitem__(self, clave):
De esta manera, una instancia de la clase Dic puede usarse como si se tratase de un diccionario:
d = Dic()
d['Nombre'] = 'Carlos'
print(d['Nombre'])
6.6.1. Encapsulamiento
Consiste en colocar al mismo nivel de abstracción a todos los elementos (estado y comportamiento) que pueden
considerarse pertenecientes a una misma entidad (identidad). Esto permite aumentar la cohesión de los componentes
del sistema.
class Persona(object):
def getNombre(self):
return ' '.join(self.__nombre)
Las propiedades pertenecen al espacio de nombres del objeto (namespace) y pueden estar ocultas, es decir, sólo
accesibles para el objeto. En Python, esto último se logra superficialmente anteponiendo dos guiones bajos ( __ )
al nombre de la propiedad.
carlos = Persona("Carlos Zayas")
print(carlos.nombre.upper())
Decimos que en Python se logra sólo superficialmente el ocultamiento de las propiedades porque, si bien no
podemos invocar la propiedad nombre de manera tradicional, sí podemos hacerlo de la siguiente manera:
print(carlos._Persona__nombre)
6.6.2. Herencia
Las clases se relacionan entre sí dentro de una jerarquía de clasificación que permite definir nuevas clases basadas
en clases preexistentes, y así poder crear objetos especializados.
class Empleado(Persona):
self.cargo = cargo
print("{} es {}".format(self.nombre, self.cargo))
La herencia es el mecanismo por excelencia de la programación orientada a objetos que permite lograr la reutilización
de código. Mediante ella, podemos crear nuevas clases modificando clases ya existentes.
En el caso de Python, la evolución del lenguaje dio como resultado dos algoritmos MRO distintos, uno simple para
las clases de estilo antiguo y otro más sofisticado para las clases de estilo nuevo (las que heredan de object).
Ambos algoritmos provienen de la teoría de grafos, y son los siguientes:
DFS – Depth First Search (Búsqueda en profundidad): Recorre los nodos del grafo (árbol) de izquierda a
derecha empezando de la raíz pero hasta el nodo más lejano. En el diagrama, empezando desde la clase D, el
orden sería D, B, A, C.
BFS – Breadth First Search (Búsqueda en anchura): Recorre los nodos del grafo efectuando barridos de
izquierda a derecha. En el diagrama, empezando desde la clase D, el orden sería D, B, C, A.
# Este ejemplo es para Python 2
6.6.3. Polimorfismo
Consiste en definir comportamientos diferentes basados en una misma denominación, pero asociados a objetos
distintos entre sí. Al llamarlos por ese nombre común, se utilizará el adecuado al objeto que se esté invocando.
a = Persona("Pablo") # Nace Pablo
b = Persona("Juan") # Nace Juan
print(b) # Juan dice "Hola"
c = Empleado("Esteban","Chofer") # Nace Esteban
# Esteban es Chofer
En el siguiente ejemplo, las dos clases derivadas de Animal comparten el método sonido, pero cada una le agrega
su particularidad.
class Animal:
cantidad = 0
def __init__(self):
print("Hola, soy un animal.")
self.nombre = ""
Animal.cantidad += 1
print("Hay", Animal.cantidad, "animales.")
def sonido(self):
print("Este es mi sonido:")
def __del__(self):
print(self.nombre, "dice: Adios!")
class Perro(Animal):
def sonido(self):
Animal.sonido(self)
print("Guau!")
class Gato(Animal):
def sonido(self):
Animal.sonido(self)
print("Miau!")
fido = Perro("Fido")
fido.sonido()
tom = Gato("Tom")
tom.sonido()
Los objetos fido y tom descienden de Animal pero cada uno “emite su propio sonido”.
Una subclase suele necesitar llamar al constructor de la superclase:
class Subclase(Superclase):
def __init__(self):
# Aquí la subclase hace sus cosas
Superclase.__init__(self)
# Aquí la subclase sigue haciendo sus cosas
class A(object):
def __init__(self):
print "Mundo"
class B(A):
def __init__(self):
print "Hola"
super(B, self).__init__()
# Python 3.x
class A:
def __init__(self):
print("Mundo")
class B(A):
def __init__(self):
print("Hola")
super().__init__()