Descarga la aplicación para disfrutar aún más
Vista previa del material en texto
Programación funcional. Funciones de orden superior Python tiene algunas características de la programación funcional, un paradigma de programación basado en un concepto de función más cercano al matemático que al que habitualmente estamos habituados en la programación imperativa. En el siguiente ejemplo vamos a construir un conversor de números anidando varias funciones en una función de orden superior. def conversor(sis): def sis_bin(numero): print('dec:', numero, 'bin:', bin(numero)) def sis_oct(numero): print('dec:', numero, 'oct:', oct(numero)) def sis_hex(numero): print('dec:', numero, 'hex:', hex(numero)) sis_func = {'bin': sis_bin, 'oct': sis_oct, 'hex': sis_hex} return sis_func[sis] conversorhex = conversor('hex') # crea una instancia del conversor hexadecimal conversorhex(100) # convierte el número 100 dec a hex print() conversor('bin')(9) # otra forma de usar el conversor. La función map() La función de orden superior map() aplica una función a una lista de datos y devuelve un iterador que contiene todos los resultados para los elementos de la lista. En el siguiente ejemplo la función cuadrado calcula el cuadrado de un número. La lista1 contiene una lista de datos numéricos. Con map(cuadrado, lista1) se aplica la función cuadrado a cada elemento de la lista. def cuadrado(numero): return numero ** 2 lista1 = [-2, 4, -6, 8] lista2= list(map(cuadrado, lista1)) # devuelve un iterador que convertimos a lista print(lista2) # resultado: 4, 16, 36, 64 Para convertir el iterador a una lista hemos empleado la función list(). import math def area_circulo(radio): return math.pi * radio ** 2 lista3 = [1, 2, 3] lista4 = list(map(area_circulo, lista3)) # devuelve un iterador que convertimos a lista print(lista4) La función filter() La función filter() aplica un filtro a una lista de datos y devuelve un iterador con los elementos que superan el filtro o den el valor lógico True en un predicado. squares = map(lambda x: x**2, range(10)) special_squares = filter(lambda x: x > 5 and x < 50, squares) print special_squares La función lambda (función anónima) La función lambda se utiliza para declarar funciones que sólo ocupan un línea. Son objetos que pueden asignarse a variables para usarse con posterioridad. cuadrado = lambda x: x*x lista = [1,2,3,5,8,13] for elemento in lista: print(cuadrado(elemento)) print (lambda x, y: x*y)(3, 4) Comprensión de listas Es un tipo de construcción que consta de una expresión que determina cómo modificar los elementos de una lista, seguida de una o varias clausulas for y, opcionalmente, una o varias clausulas if. El resultado que se obtiene es una lista. El formato general es: [ expresión a evaluar for loop-variable in secuencia ] squares = [ x**2 for x in range(10) ] print squares def funcion(x): # define función return 1/x # devuelve inverso de un número L = [1, 2, 3] # declara lista print([funcion(float(i)) for i in L]) # muestra lista con inversos de cada número Generadores Los generadores funcionan de forma parecida a la comprensión de listas pero no devuelven listas sino generadores. Un generador es una clase especial de función que genera valores sobre los que iterar. La sintaxis usada es como la usada en la comprensión de listas pero en vez de usar corchetes se utilizan paréntesis. Para devolver los valores se utiliza yield, esta orden devolverá un valor (igual que hace return) pero, además, congelará la ejecución de la función hasta la próxima vez que le pidamos un valor. La mejor forma de entenderlo es creando uno. Por ejemplo, un generador que nos devuelva múltiplos del número pasado como argumento: def multiplos_de(n): index = 1 while True: yield index*n index = index + 1 if __name__ == '__main__': # En este caso genera múltiplos de 7 for i in multiplos_de(7): print i Usando los generadores vamos a desarrollar una función que pasado un número binario lo irá “desgranando” y devolviendo los valores decimales por cada bit. ### EJEMPLO SIN USAR GENERADORES def binToDecClassic(val): n = 0 res = [] while val > 0: res += [(val%2) * (2**n)] val /= 10 n+=1 return res sum = 0 out = binToDecClassic(10011) for val in out: print "+", val, sum += val print "=", sum ### EJEMPLO USANDO GENERADORES def binToDecUsingGenerators(val): n = 0 while val > 0: yield (val % 2) * (2**n) val = val / 10 n += 1 sum = 0 for val in binToDecUsingGenerators(10011): print "+", val , sum += val print "=", sum Como vemos, el código es muy similar, de hecho, la sintaxis puede llevar a confusión, ya que aunque vemos dos bucles, realmente sólo hay uno, en cada parada del for se corresponde con la instruccion “yield” dentro de la función. Esta palabra reservada, “yield” es la clave de los generadores. Decorador Es una función que recibe una función como parámetro y devuelve otra función como valor de retorno. El formato de un decorador es: def decorador(a): def r(*args, **kwargs): # comportamiento previo a la ejecución de a a(*args, **kwargs) # comportamiento posterior a la ejecución de a return r Un ejemplo trivial es: def decorador1(funcion): def funciondecorada(*param1, **param2): print('Inicio', funcion.__name__) funcion(*param1, **param2) print('Fin', funcion.__name__) return funciondecorada def suma(a, b): print(a + b) suma2 = decorador1(suma) suma2(1,2) suma3 = decorador1(suma) suma3(2,2) Otra forma más elegante, usando @: @decorador1 def producto(arg1, arg2): print(arg1 * arg2) producto(5,5) Programación Orientada a Objetos Un Programa Orientado a Objetos (POO) se basa en una agrupación de objetos de distintas clases que interactúan entre sí y que, en conjunto, consiguen que un programa cumpla su propósito. En este paradigma de programación se intenta emular el funcionamiento de los objetos que nos rodean en la vida real. En Python cualquier elemento del lenguaje pertenece a una clase y todas las clases tienen el mismo rango y se utilizan del mismo modo. con la función type() se muestra a qué clase pertenecen cada uno: In [1]: lenguaje = "Python" In [2]: print(type(lenguaje)) <type 'str'> In [3]: def mitad(x): ...: return float(x)/2 ...: In [4]: print(type(mitad)) <type 'function'> In [5]: import math In [6]: print(type(math)) <type 'module'> In [7]: lista=[1,2,3] In [8]: print(type(lista)) <type 'list'> Clases, atríbutos y métodos Las clases en este contexto permiten definir los atributos y el comportamiento, mediante métodos, de los objetos de un programa. Una clase es una estructura de datos que se utiliza para crear instancias individuales del mismo tipo de objeto. Los atributos definen las características propias del objeto y modifican su estado. Son datos asociados a las clases y a los objetos creados a partir de ellas. De ello, se deducen los dos tipos de atributos o de variables existentes: variables de clase y variables de instancia (objetos). Los métodos son bloques de código (o funciones) de una clase que se utilizan para definir el comportamiento de los objetos. Tanto para acceder a los atributos como para llamar a los métodos se utiliza el método denominado de notación de punto que se basa en escribir el nombre del objeto o de la clase seguido de un punto y el nombre del atributo o del método con los argumentos que procedan: clase.atributo, objeto.atributo, objeto.método([argumentos]). Ejemplo: In [9]: class A1: ...: nombre="cosa" ...: apellido="tio" ...: def telefono(usuario): ...: if usuario=="Roberto": TE=4370 ...: else: TE="desconocido" ...: In [10]: a=A1() In [11]: print(type(a)) <type 'instance'> In [12]: print(type(A1)) <type 'classobj'> In [13]: print(type(a.apellido))<type 'str'> In [14]: a.telefono("pepe") --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-14-7ab387672808> in <module>() ----> 1 a.telefono("pepe") TypeError: telefono() takes exactly 1 argument (2 given) En la instrucción última ocurrió una excepción debido a que los métodos son privados de la clase a menos que se los haga públicos de manera explícita. Para hacer público el método se usa la llamada self y el método constructor __init__ (luego se retomará el tema) In [16]: class D1: nombre="cosa" apellido="tio" def __init__(self, usuario): if usuario=="Roberto": self.TE=4382 else: self.TE="desconocido" ....: In [17]: d1=D1("Roberto") In [18]: d2=D1("pepe") In [19]: print d1.TE, d2.TE 4382 desconocido Variables de Clases y Variables de Instancias En lenguajes que crean objetos a partir de clases, un objeto es una instancia de una clase. Y de una misma clase se pueden mantener activas en un programa más de una instancia al mismo tiempo. • Una variable de clase es compartida por todas las instancias de una clase. Se definen dentro de la clase (después del encabezado de la clase) pero nunca dentro de un método. Este tipo de variables no se utilizan con tanta frecuencia como las variables de instancia. • Una variable de instancia se define dentro de un método y pertenece a un objeto determinado de la clase instanciada. Crear clases Una clase consta de dos partes: un encabezado que comienza con el término class seguido del nombre de la clase (en singular) y dos puntos (:) y un cuerpo donde se declaran los atributos y los métodos: class NombreClase: 'Texto para documentar la clase (opcional)' varclase1 = "variable de clase1" def nombremetodo1(self, var1): self.var1 = var1 def nombremetodo2(self): self.var1 += 1 La documentación de una clase debe situarse después del encabezado y justo antes del lugar donde se declaren las variables y los métodos de la clase. Desde cualquier lugar de un programa se puede acceder a la cadena de documentación de una clase accediendo al atributo especial: NombreClase.__doc__. Todo lo que se incluye en una clase es opcional. De hecho, la clase más elemental aunque no tenga mucha utilidad puede estar vacía: class Clase: pass En el siguiente ejemplo se define una clase mucho más completa: class Alumno: 'Clase para alumnos' numalumnos = 0 sumanotas = 0 def __init__(self, nombre, nota): self.nombre = nombre self.nota = nota Alumno.numalumnos += 1 Alumno.sumanotas += nota def mostrarNombreNota(self): return(self.nombre, self.nota) def mostrarNumAlumnos(self): return(Alumno.numalumnos) def mostrarSumaNotas(self): return(Alumno.sumanotas) def mostrarNotaMedia(self): if Alumno.numalumnos > 0: return(Alumno.sumanotas/Alumno.numalumnos) else: return("Sin alumnos") La clase Alumno consta de dos variables de clase (Alumno.numalumnos y Alumno.sumanotas) que son accesibles desde los métodos de la clase. Además, sus valores son compartidos por todas las instancias que existan de esta clase. A continuación, se declaran varios métodos (funciones) que incluyen como primer argumento a self que contiene la referencia del objeto especifico que llama al método en un momento dado. Como su valor es implícito cuando se llama a un método no es necesario pasar este argumento. El método __init__() es especial porque se ejecuta automáticamente cada vez que se crea una nuevo objeto. Este método, que es opcional, se llama constructor y se suele utilizar para inicializar las variables de las instancias (en este caso para inicializar las variables self.nombre y self.nota). El resto de métodos se utilizan para acceder y mostrar el valor de las variables de clase y de instancia. Por último, el método mostrarNotaMedia() realiza un cálculo y después muestra su resultado. Accediendo a los atributos y llamando a los métodos Para crear instancias de una clase se llama a la clase por su propio nombre pasando los argumentos que requiera el método constructor __init__ si existe. alumno1 = Alumno("Maria", 8) alumno2 = Alumno("Carlos", 6) Todos los argumentos se pasan escribiéndolos entre paréntesis y separados por comas (","). El primer argumento self se omite porque su valor es una referencia al objeto y es implícito para todos los métodos. Para modificar la variable de un objeto se utiliza la misma notación para referirse al atributo y después del signo igual (=) se indica la nueva asignación: alumno1.nombre = "Carmela" Para suprimir un atributo: del alumno1.nombre Funciones para atributos: getattr(), hasattr(), setattr() y delattr() La función getattr() se utiliza para acceder al valor del atributo de un objeto Si un atributo no existe retorna el valor del tercer argumento (es opcional). nota_alumno2 = getattr(alumno2, 'nota', 0) edad_alumno2 = getattr(alumno2, 'edad', 0) print(nota_alumno2) # 6 print(edad_alumno2) # 0 La función hasattr() devuelve True o False dependiendo si existe o no el atributo indicado if not hasattr(alumno2, 'edad'): print("El atributo 'edad' no existe") setattr() se utiliza para asignar un valor a un atributo. La función delattr() es para borrar el atributo de un objeto. Si el atributo no existe se producirá una excepción del tipo AttributeError. Atributos de clase (Built-In) Todas las clases Python incorporan los siguientes atributos especiales: __dict__ Devuelve un diccionario que contiene el espacio de nombres de la clase (o de un objeto instanciado). In [20]: print(D1.__dict__) {'nombre': 'cosa', '__module__': '__main__', '__init__': <function __init__ at 0x7f048484ab90>, 'apellido': 'tio', '__doc__': None} In [21]: print(d1.__dict__) {'TE': 4382} In [22]: print(d2.__dict__) {'TE': 'desconocido'} __name__ Devuelve el nombre de la clase. __doc__ Devuelve la cadena de documentación de la clase o None si no se ha definido. __module__ Devuelve el nombre del módulo donde se encuentra la clase definida. __bases__ Devuelve una tupla (posiblemente vacía) que contiene las clases base, en orden de aparición. In [24]: print(D1.__name__) D1 In [25]: print(D1.__module__) __main__ In [26]: print(D1.__bases__) () In [27]: print(D1.__doc__) None Destrucción de objetos (Recolección de basura) Python elimina objetos que no son necesarios (del tipo Built-In o instancias de clase) automáticamente para liberar espacio de memoria. El proceso por el cual Python reclama periódicamente bloques de memoria que ya no están en uso se denomina Recolección de Basura. La Recolección de Basura de Python se ejecuta durante la ejecución de un programa y se activa cuando el contador de referencia de un objeto llega al valor cero. Este contador va cambiando su valor en función del uso que se haga del objeto. Si se asigna un nuevo nombre o se utiliza en una lista, tupla o diccionario el contador irá en aumento y si se borra el objeto con del, su referencia es reasignada o bien se queda fuera de ámbito, va disminuyendo. En el momento que se alcanza el valor cero el recolector comienza con la tarea de "rescate". pizza1 = "Margarita" # Crear objeto margarita pizza2 = pizza1 # Incrementa el contador de referencia de margarita cena['lunes'] = pizza2 # Incrementa el contador de referencia de margarita del pizza1 # Decrementa el contador de referencia de margarita pizza2 = "Cuatro Estaciones" # Decrementa el contador de referencia de margarita cena['lunes'] = pizza2 # Decrementa el contador de referencia de margarita No obstante, una clase puede incluir el método __del__(), llamado destructor, que se invoca cuando una instancia está a punto de ser destruida con del, o si no ha sido destruida con esta sentencia, cuando el programa finalice su ejecución.Ejemplo: class Robot: 'Clase Robot' def __init__(self, nombre): self.nombre = nombre def saludo(self): print("Hola, mi nombre es", self.nombre) def __del__(self): print("Se han terminado mis baterias. Me voy a dormir.") robot1 = Robot("C7PQ") robot1.saludo() # Hola, mi nombre es C7PQ print(id(robot1)) # Muestra el ID del objeto, Ejem.: 3070861004 del robot1 # Se han terminado mis baterias. Me voy a dormir. Herencia La herencia es una de las características más importantes de la Programación Orientada a Objetos. Consiste en la posibilidad de crear una nueva clase a partir de una o más clases existentes, heredando de ellas sus atributos y métodos que podrán utilizarse como si estuvieran definidos en la clase hija. Las clases derivadas se declaran como cualquier clase con la diferencia de incluir después de su nombre el nombre de la clase superior (entre paréntesis) de la que heredará sus características: class NombreSubClase (NombreClaseSuperior): 'Cadena de documentación opcional' Declaración de atributos y métodos... Ejemplo: >>> # subclases >>> # clase padre o raiz >>> class Empleados: def computarSueldo(self): self.sueldo=2000 def darBonos(self): self.bonos='SI' def promocionar(self): self.promociones='NO' def retiro(self): self.jubilaciom='82%' >>> Pepe=Empleados() >>> Pepe.computarSueldo() >>> print Pepe.sueldo 2000 >>> # Se crea una subclase Ingenieros con otro sueldo, que hereda el resto >>> class Ingenieros (Empleados): def computarSueldo(self): self.sueldo=3000 >>> Jose=Ingenieros() >>> Jose.computarSueldo() >>> print "jose sueldo", Jose.sueldo jose sueldo 3000 >>> # pero el resto de las instancias se heredan y no cambian >>> Jose.promocionar() >>> print "jose promocion", Jose.promociones jose promocion NO Herencia múltiple La herencia múltiple se refiere a la posibilidad de crear una clase a partir de múltiples clases superiores. Es importante nombrar adecuadamente los atributos y los métodos en cada clase para no crear conflictos: class Telefono: "Clase teléfono" def __init__(self): pass def telefonear(self): print('llamando') def colgar(self): print('colgando') class Camara: "Clase camara fotográfica" def __init__(self): pass def fotografiar(self): print('fotografiando') class Reproductor: "Clase Reproductor Mp3" def __init__(self): pass def reproducirmp3(self): print('reproduciendo mp3') def reproducirvideo(self): print('reproduciendo video') class Movil(Telefono, Camara, Reproductor): def __del__(self): print('Móvil apagado') movil1 = Movil() print(dir(movil1)) movil1.reproducirmp3() movil1.telefonear() movil1.fotografiar() del movil1 Funciones issubclass() y isinstance() La función issubclass(SubClase, ClaseSup) se utiliza para comprobar si una clase (SubClase) es hija de otra superior (ClaseSup), devolviendo True o False según sea el caso. La función booleana isinstance(Objeto, Clase) se utiliza para comprobar si un objeto pertenece a una clase o clase superior. Polimorfismo: Sobrecarga de métodos (Overriding Methods) La sobrecarga de métodos se refiere a la posibilidad de que una subclase cuente con métodos con el mismo nombre que los de una clase superior pero que definan comportamientos diferentes. Ejemplo, del anterior: class Movil(Telefono, Camara, Reproductor): def __init__(self): print('Móvil encendido') def reproducirmp3(self): print('Reproduciendo lista mp3') def __del__(self): print('Móvil apagado') movil3 = Movil() # Móvil encendido movil3.reproducirmp3() # Reproduciendo lista mp3 del movil3 # Móvil apagado Polimorfismo: Sobrecarga de Operadores (Overloading Operators) La sobrecarga de operadores trata básicamente de lo mismo que la sobrecarga de métodos pero pertenece en esencia al ámbito de los operadores aritméticos, binarios, de comparación y lógicos. Ejemplo: class Punto: def __init__(self,x = 0,y = 0): self.x = x self.y = y def __add__(self,other): x = self.x + other.x y = self.y + other.y return x, y punto1 = Punto(4,6) punto2 = Punto(1,-2) print(punto1 + punto2) # (5, 4) Ocultación de datos (Encapsulación) Los atributos de un objeto pueden ocultarse (superficialmente) para que no sean accedidos desde fuera de la definición de una clase. Para ello, es necesario nombrar los atributos con un prefijo de doble subrayado: __atributo Ejemplo: class Factura: __tasa = 19 def __init__(self, unidad, precio): self.unidad = unidad self.precio = precio def a_pagar(self): total = self.unidad * self.precio impuesto = total * Factura.__tasa / 100 return(total + impuesto) compra1 = Factura(12, 110) print(compra1.unidad) print(compra1.precio) print(compra1.a_pagar(), "euros") print(Factura.__tasa) # Error: Programación funcional. Funciones de orden superior La función map() La función filter() La función lambda (función anónima) Comprensión de listas Generadores Decorador Programación Orientada a Objetos Clases, atríbutos y métodos Variables de Clases y Variables de Instancias Crear clases Accediendo a los atributos y llamando a los métodos Funciones para atributos: getattr(), hasattr(), setattr() y delattr() Atributos de clase (Built-In) Destrucción de objetos (Recolección de basura) Herencia Herencia múltiple Funciones issubclass() y isinstance() Polimorfismo: Sobrecarga de métodos (Overriding Methods) Polimorfismo: Sobrecarga de Operadores (Overloading Operators) Ocultación de datos (Encapsulación)
Compartir