¡Acceso ilimitado 24/7 a todos nuestros libros y vídeos! Descubra la Biblioteca Online ENI. Pulse aquí
¡Acceso ilimitado 24/7 a todos nuestros libros y vídeos! Descubra la Biblioteca Online ENI. Pulse aquí
  1. Libros
  2. Python y Raspberry Pi
  3. Conceptos avanzados del lenguaje Python
Extrait - Python y Raspberry Pi Aprenda a desarrollar en su nano-ordenador
Extractos del libro
Python y Raspberry Pi Aprenda a desarrollar en su nano-ordenador Volver a la página de compra del libro

Conceptos avanzados del lenguaje Python

Las clases: definición con la palabra clave class

Es extremadamente fácil con Python escribir un script de 10 líneas para resolver un problema sin tener que preocuparse por la reutilización de los componentes en el código. Para remediar este problema, los diseñadores del lenguaje se han preocupado de añadir todos los ingredientes necesarios para transformar Python en un lenguaje moderno. Entre estos ingredientes, figuran la programación orientada a objetos y el concepto de clase. Sin embargo, preste atención: el objetivo de este capítulo no es enseñar las bases de la programación orientada a objetos, sino explicar de manera clara y precisa cómo funciona una clase en Python, aprender a escribir clases y alguno de los aspectos más sutiles que tienen. Es necesario conocer algunos conceptos básicos para poder programar con la Raspberry Pi, principalmente para la manipulación de librerías gráficas tales como tkinter.

Este capítulo aborda muy brevemente el tema de las clases que es, recordémoslo, muy amplio en Python. En este sentido, si queremos profundizar al máximo, le aconsejamos el libro de Sébastien Chazallet - Python 3 disponible en Ediciones ENI y, principalmente, el capítulo dedicado al modelo de objetos que explica en detalle la programación orientada a objetos en Python y todo lo que las clases pueden lograr: la herencia múltiple, la sobrecarga de operadores, el polimorfismo, los atributos dinámicos, etc.

Otro aspecto importante: la programación orientada a objetos es totalmente opcional en Python. Es perfectamente posible escribir programas de varias docenas de líneas sin tener que recurrir a la creación y manipulación de una sola clase.

Pero en primer lugar, ¿qué es una clase? En el lenguaje habitual, una clase es un medio de representar un objeto del mundo real, en el dominio de la programación: una persona, un coche, un almácen y prácticamente cualquier objeto se puede representar con ayuda de una clase. El ejemplo utilizado habitualmente para explicar la programación orientada a objetos y el concepto de clase es el del coche. Un coche generalmente tiene una marca, un color, un motor, asientos (que son sus atributos) y también puede realizar acciones, como arrancar, acelerar, reducir la velocidad...

Las funciones: las palabras clave def y lambda

Habitualmente, en programación hablamos de función o rutina cuando se trata de factorizar una tarea repetitiva. En efecto, llamar a una función permite reutilizar un bloque de código varias veces en un programa. En Python, existen dos palabras clave para ayudar al desarrollador a definir una o varias funciones dentro de un programa: def y lambda. Examinemos sus diferencias.

1. Definir una función

La palabra clave def crea una función y asigna esta función a un nombre. A continuación se muestra la sintaxis asociada a def:


1 def nombre_de_la_funcion(argumento1, argumento2,... argumentoX): 
2        cuerpo de la función 
3        return resultado
 

Una función def (como una función lambda, que se explica justo después y casi como cualquier cosa en Python) es, ante todo, un objeto con un tipo: function. Por ejemplo:


>>> def dos_veces(x):  
...   return x * 2   
...   
>>> dos_veces  
<function dos_veces at 0x7fcc13a66950>  
>>> type(dos_veces)  
<class 'function'>  
>>> dir(dos_veces)  
['__annotations__', '__call__', '__class__', '__closure__',  
 '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__' 
 '__doc__', '__eq__', '__format__', '__ge__', '__get__',  
 '__getatributoe__', '__globals__', '__gt__', '__hash__',  
 '__init__', '__kwdefaults__', '__le__', '__lt__', '__module__', 
 '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', 
 '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',  
 '__str__', '__subclasshook__']
 

La declaración de una función def empieza por el nombre de la función y, entre paréntesis, sus argumentos. El cuerpo de la función está indentado, como la mayor parte de las declaraciones en Python. El objetivo de una función es, en primer lugar, tratar los argumentos...

La sintaxis comprensible

Normalmente se alaba mucho la elegancia de la sintaxis del lenguaje Python, que principalmente se centra en su escritura la cual, debido a los entusiastas de las matemáticas y del álgebra, habitualmente se parece más a fórmulas que a código. Guido van Rossum ha estudiado y obtenido un master en matemáticas en la universidad de Ámsterdam, antes de orientar su carrera al desarrollo de software. ¿Por qué es importante remarcar este hecho? Porque tiene gran influencia en la sintaxis del lenguaje, principalmente en la sintaxis comprensible. Esta sintaxis permite principalmente declarar una lista filtrando sus elementos, con el objetivo de extraer únicamente el contenido deseado. Se extiende a los diccionarios, los sets, las tuplas y a los generadores. 

Explicar la esencia de una lista comprensible y poder escribirla uno mismo, hace necesario la explicación de un ejemplo sencillo. Una de las tareas más recurrentes en programación es la ordenación de datos y fundamentalmente de estructuras de datos: iteración, asignación y condición. Supongamos una lista de 10 cifras:


>>> mi_lista = list(range(1, 11))   
>>> mi_lista  
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
 

Su tarea es la siguiente: ordenar esta lista con el objetivo de recuperar solo los valores pares y añadir estos valores a otra lista. La mayor...

Iterador y generador: las palabras clave iter y yield

Habitualmente tendrá que manipular listas que la mayor parte de las veces tendrán un tamaño muy reducido, es decir que contendrán muy pocos elementos, entre 10 y 100. En estos casos, el sistema operativo asigna memoria con el objetivo de almacenar en ella los n elementos contenidos. Esto no representa ningún problema para los tratamientos que necesitan poco espacio de memoria. Pero cuando la lista alcanza un tamaño gigantesco, digamos más de un millón de elementos, ¿cómo solucionar el problema de espacio en memoria o al menos evitarlo? Además de que la Raspberry Pi generalmente no es el entorno ideal para realizar tratamientos que consuman mucha memoria virtual, el tamaño de esta va de 256 MB a más de 4 GB en los últimos modelos.

Es en este tipo de situaciones cuando el uso de un iterador parece indispensable. En la práctica, el uso de un iterador es muy sencillo. A continuación se muestra lo que aparece en REPL:


>>> 2**50  
1073741824  
>>> list(range(0, 2**30))   
Traceback (most recent call last):  
  File "<stdin>", line 1, in <module>  
MemoryError 

La creación de una lista de 1.073.741.824 elementos consumiría demasiada memoria. Python devuelve una excepción MemoryError cuando el tamaño de la estructura de datos a crear es demasiado importante para el sistema.

En Python, un iterador es como una envoltura que sirve de interfaz entre una estructura de datos y el programa. Para crear esta envoltura, se utiliza la palabra clave iter o la función __iter__(). En ambos casos, el objeto devuelto será de tipo iterator:


>>> range(0, 2**30).__iter__()  
<range_iterator object at 0x7f74e73feb10>  
>>> iter(range(0, 2**30))  
<range_iterator object at 0x7f74e73fe510>
 

Según la estructura de datos manipulada, este tipo varía: list_iterator para una lista, tuple_iterator para una tupla, set_iterator para un set y así sucesivamente. Cada uno de estos iteradores tiene un nombre distinto pero...

La gestión de excepciones con las palabras clave try, except, raise y finally

Generalmente, se produce una excepción cuando un programa intenta realizar una operación no permitida o peligrosa. En programación, desafortunadamente, hay una larga lista de operaciones peligrosas que pueden provocar que un programa se detenga repentinamente: lectura de un archivo que no existe, lectura de un índice demasiado grande en una tabla demasiado pequeña, recuperación de una clave en un diccionario cuyo valor no existe, etc.

Para paliar este problema, Python ofrece una gestión de excepciones nativa al lenguaje y las gestiona a través de cuatro palabras clave:

  • try que permite intentar ejecutar un fragmento de código que puede generar una excepción.

  • except que funciona junto con try y que, cuando se produce una excepción, la intercepta y continua o no la ejecución del programa.

  • raise que devuelve una excepción durante la ejecución del programa.

  • finally que se utiliza después de una instrucción try / except, con el objetivo de ejecutar siempre un bloque de código, independientemente de que se genere una excepción o no.

En la práctica ¿cómo funciona esto? Desde REPL, intentamos leer un archivo que no existe en la Rasbperry Pi. Como se explica un poco más adelante, la función open() abre un archivo para leer su contenido:


>>> open('/etc/passwd', 'r') 
<_io.TextIOWrapper name='/etc/passwd' mode='r' encoding='UTF-8'>
 

Cuando el archivo está en disco, Python devuelve un descriptor de archivo para poder manipular el archivo en cuestión. El resultado es diferente cuando el recurso no está accesible:


>>> open('noxiste', 'r') 
Traceback (most recent call last): 
  File "<stdin>", line 1, in <module> 
FileNotFoundError:...

Fragmentar y manipular una estructura de datos

Python ofrece la posibilidad de manipular el contenido de una estructura de datos con el objetivo de recuperar toda o una parte de ella. La sintaxis se basa en el uso de los índices de una estructura de datos y existen diferentes operaciones. La estructura de datos elegida en este ejemplo es la lista, porque es la que se utiliza más a menudo, pero esta sintaxis se aplica a tuplas, sets y cadenas de caracteres. Tomemos la variable mi_lista, una lista de 10 elementos inicializada de la siguiente manera:


>>> mi_lista = list('abcdefghijk')  
>>> mi_lista  
['a', 'b', 'c', 'de' , 'e', 'f', 'g', 'h', 'i', 'j', 'k']
 

Para recuperar los valores de mi_lista yendo del índice x hasta el índice y, se utilizará la sintaxis mi_lista[x:y] donde x es el valor de inicio e y el valor de fin del tramo de valores. De esta manera, estos valores se devuelven en una nueva lista:


>>> mi_lista[2:7]  
['c', 'de' , 'e', 'f', 'g']  
>>> mi_lista[1:3]  
['b', 'c']
 

Para recuperar los valores de mi_lista partiendo del inicio de la lista hasta el índice x, se utilizará la sintaxis mi_lista[:x], donde x es el valor final del tramo de valores:


>>>...

La importación de los módulos con la palabra clave import

Seguramente ha observado a lo largo de las páginas anteriores el uso de la palabra clave import. La primera función de esta palabra clave es importar uno o varios módulos. En sus programas, así como en REPL, import hace accesibles las funciones o variables contenidas en el módulo importado:


>>> import random   
>>> random.random()  
0.3751871473556778  
>>> import sys   
>>> sys.maxsize 
9223372036854775807
 

La importación de varios módulos es posible separando por una coma los nombres de los módulos a importar:


>>> import os, re, cmd 
>>> os; re; cmd; 
<module 'os' from '/usr/lib64/python3.3/os.py'> 
<module 're' from '/usr/lib64/python3.3/re.py'> 
<module 'cmd' from '/usr/lib64/python3.3/cmd.py'>
 

Para listar las funciones expuestas y ofrecidas por un módulo, se utilizará la función dir() pasándole como argumento el nombre del módulo a inspeccionar:


>>> import string  
>>> dir(string)   
['ChainMap', 'Formatter', 'Template', '_TemplateMetaclass ,  
 '__builtins__', '__cached__', '__doc__', '__file__',  ...

La gestión de contexto con las palabras clave with y as

Desde la versión 2.6, Python implementa la gestión de contexto en forma de dos palabras clave, with y as. Esta gestión de contexto también se acompaña de un protocolo, con el objetivo de poder aprovechar al máximo estas dos palabras clave dentro de sus clases.

Sin embargo, ¿para qué sirve la gestión de contexto? Una posible definición sería la gestión de un recurso, ya se trate de un archivo, un flujo IO, un socket de red, sin que sea necesario explícitamente abrir y cerrar el recurso manipulado. La gestión de contexto simplifica mucho el código, lo hace más corto y menos complejo de escribir.

Un ejemplo siempre es mejor que mil palabras. En Python, la apertura y cierre de un archivo son operaciones muy habituales. En este caso, la función open() se utiliza con el objetivo de leer o escribir en un archivo. Recibe como argumentos dos valores:

  • La primera es la ruta al archivo;

  • Después, el modo de apertura del archivo, que puede ser múltiple: ’r’ para la lectura, ’w’ para la escritura (que también tiene el efecto de eliminar el contenido existente del archivo) y ’a’ para añadir contenido a un archivo.

El archivo normalmente se vuelve a cerrar cuando la operación termina a través de una llamada a la función close()...

Conclusión

Este capítulo resume las funciones avanzadas del lenguaje, tales como la programación orientada a objetos, las listas comprensibles o los iteradores. Es importante conocer estas nociones con el objetivo de poder elaborar programas más complejos y leer el código escrito por los programadores más experimentados.