Index of content
Temas de Pregrado en Ciencias de la Computación Principiantes Guía de Python 3 Programación Juan caza
Temas de Pregrado en Ciencias de la Computación Editor de series Ian Mackie, Universidad de Sussex, Brighton, Reino Unido Editores asesores Samson Abramsky, Departamento de Ciencias de la Computación, Universidad de Oxford, Oxford, Reino Unido Chris Hankin, Departamento de Informática, Imperial College London, Londres, Reino Unido Dexter C. Kozen, Departamento de Ciencias de la Computación, Universidad de Cornell, Ithaca, NY, EE.UU Andrew Pitts, Universidad de Cambridge, Cambridge, Reino Unido Hanne Riis Nielson , Departamento de Matemática Aplicada e Informática, Universidad Técnica de Dinamarca, Kongens Lyngby, Dinamarca Steven S. Skiena, Departamento de Ciencias de la Computación, Universidad de Stony Brook, Stony Brook, Nueva York, EE. UU. Iain Stewart, Departamento de Ciencias de la Computación, Laboratorios de Ciencias, Universidad de Durham, Durham, Reino Unido Mike Hinchey, Universidad de Limerick, Limerick, Irlanda
‘Temas de pregrado en Ciencias de la Computación’ (UTiCS) ofrece alta calidad contenido instructivo para estudiantes universitarios que estudian en todas las áreas de computación y Ciencias de la Información. Desde el material fundamental y teórico básico hasta el último año temas y aplicaciones, los libros de UTiCS adoptan un enfoque fresco, conciso y moderno y son ideales para el autoaprendizaje o para un curso de uno o dos semestres. Los textos son todos escrito por expertos establecidos en sus campos, revisado por un asesor internacional tablero, y contienen numerosos ejemplos y problemas, muchos de los cuales incluyen completamente soluciones trabajadas. El concepto UTiCS se basa en libros concisos y de alta calidad en formato de tapa blanda, y generalmente un máximo de 275-300 páginas. Para libros de texto de pregrado que probablemente para ser más largo, más expositivo, Springer continúa ofreciendo los muy apreciados Textos en la serie Computer Science, a la que remitimos a los posibles autores. Más información sobre esta serie en http://www.springer.com/series/7592
Juan caza Una guía para principiantes de Python 3 Programación 123
Juan caza Tecnología Midmarsh Ltd Chippenham, Wiltshire, Reino Unido ISSN 1863-7310 ISSN 2197-1781 (electrónico) Temas de Pregrado en Ciencias de la Computación ISBN 978-3-030-20289-7 ISBN 978-3-030-20290-3 (libro electronico) https://doi.org/10.1007/978-3-030-20290-3 © Springer Nature Suiza AG 2019, publicación corregida 2020 Esta obra está sujeta a derechos de autor. Todos los derechos están reservados por el editor, ya sea en su totalidad o en parte del material se refiere, en concreto los derechos de traducción, reimpresión, reutilización de ilustraciones, recitación, radiodifusión, reproducción en micropelículas o de cualquier otra forma física, y transmisión o almacenamiento y recuperación de información, adaptación electrónica, software de computadora, o por medios similares o diferentes metodología ahora conocida o desarrollada en el futuro. El uso de nombres descriptivos generales, nombres registrados, marcas comerciales, marcas de servicio, etc. en este publicación no implica, incluso en ausencia de una declaración específica, que dichos nombres estén exentos de las leyes y reglamentos de protección pertinentes y, por lo tanto, gratis para uso general. El editor, los autores y los editores están seguros de asumir que el consejo y la información en este libro se cree que son verdaderos y precisos en la fecha de publicación. Ni el editor ni el los autores o los editores dan garantía, expresa o implícita, con respecto al material contenido en este documento o por cualquier error u omisión que se haya cometido. El editor se mantiene neutral con respecto a reclamos jurisdiccionales en mapas publicados y afiliaciones institucionales. Este pie de imprenta de Springer es publicado por la empresa registrada Springer Nature Switzerland AG La dirección de la empresa registrada es: Gewerbestrasse 11, 6330 Cham, Suiza
Este libro fue escrito y está dedicado a, mi hija Phoebe y mi hijo Adam; No podría estar más orgulloso de ninguno de ustedes.
Prefacio Actualmente existe un gran interés en el lenguaje de programación Python. esto es impulsado por varios factores; su uso en escuelas con la plataforma Raspberry Pi, su capacidad para ser utilizado para scripts DevOps, su uso en ciencia de datos y aprendizaje automático y, por supuesto el lenguaje mismo. Hay muchos libros sobre Python, sin embargo, la mayoría asume programación previa experiencia o se centran en aspectos particulares del uso de Python, como la ciencia de datos o el aprendizaje automático o tienen un sabor científico. El objetivo de este libro es presentar Python a aquellos con poca o muy poca conocimiento de programación, y luego llevarlos a través para convertirse en un experimentado Desarrollador Python. Como tal, las primeras partes del libro introducen conceptos fundamentales tales como qué es una variable y cómo funciona un bucle for. Por el contrario, los últimos capítulos introducen conceptos avanzados como programación funcional, orientación a objetos y manejo de excepciones. Entre una amplia gama de temas se introducen y discuten desde un Python punto de vista que incluye funciones, recursividad, operadores, propiedades de Python, módulos y paquetes, protocolos y monkey patching, etc. Una vez establecidos los elementos centrales de Python, cada nueva área temática se introducido a través de un capítulo introductorio que presenta el tema en general, pro- proporcionando antecedentes sobre ese tema, por qué es importante, etc. Estas introducciones cubrir el análisis estructurado, la programación funcional y la orientación a objetos. Algunos de los aspectos clave de este libro son:
Supone muy poco conocimiento o experiencia de Python o programación.
Proporciona una introducción básica a Python, así como temas avanzados como generadores y corrutinas.
Este libro proporciona una amplia cobertura de la orientación a objetos y las características en Python 3 admite clases, herencia y protocolos.
También se presenta el soporte de Pythons para la programación funcional. viii
Después de presentar las ideas básicas detrás de la programación funcional, el libro presenta cómo conceptos funcionales avanzados tales como cierres, curry, y las funciones de orden superior funcionan en Python.
El libro incluye ejercicios al final de la mayoría de los capítulos con soluciones en línea.
Hay varios estudios de casos distribuidos a lo largo del libro que amplían la comprensión posición de los temas precedentes.
Todos los ejemplos de código (y soluciones de ejercicios) se proporcionan en línea en un GitHub repositorio. Organización del Capítulo Cada capítulo tiene una breve introducción, el cuerpo principal del capítulo, seguido de una lista de (típicamente) referencias en línea que se pueden usar para lecturas adicionales. Después de esto, normalmente hay una sección de Ejercicios que enumera uno o más ejercicios que se basan en las habilidades que habrá aprendido en ese capítulo. Las soluciones de muestra para los ejercicios están disponibles en un repositorio en línea de GitHub que apoya este libro. Que necesitas Por supuesto, puedes simplemente leer este libro; sin embargo, siguiendo los ejemplos de este book se asegurará de que obtenga el mayor provecho posible del contenido. Para esto, necesitarás una computadora. Python es un lenguaje de programación multiplataforma y, como tal, puede usar Python en una PC con Windows, una caja de Linux o una Mac de Apple, etc. Así que no estás atado a un tipo particular de sistema operativo; puedes usar lo que tengas disponible. Sin embargo, deberá instalar algún software en esa computadora. en un mini- mamá, necesitarás Python. Este libro se enfoca en Python 3, así que lo necesitará. Algunas orientaciones sobre esto son proporcionado en el Cap. 2 sobre la configuración de su entorno. También necesitará algún tipo de editor en el que escribir sus programas. Allá hay numerosos editores de programación genéricos disponibles para diferentes sistemas operativos con Vim en Linux, Notepad++ en Windows y Sublime Text en Windows, y Las Mac son opciones populares. Sin embargo, el uso de un editor de entorno de desarrollo integrado (IDE) como PyCharm hará que escribir y ejecutar sus programas sea mucho más fácil. viii Prefacio
Usando un IDE El IDE que prefiero para Python es PyCharm, aunque no es el único IDE para Python de ninguna manera, pero es uno muy usado. Otros IDE disponibles para Python incluyen: • Rodeo, que es un IDE ligero y de código abierto, consulte https://rodeo.yhat.com. • Jupyter Notebook, que es un IDE basado en web y es particularmente bueno para datos científicos https://jupyter.org. • Código de Visual Studio. Este es un muy buen editor gratuito de Microsoft que tiene características realmente útiles https://code.visualstudio.com. • Sublime Text es más un editor de texto que codifica con colores Python; sin embargo, para un proyecto simple puede ser todo lo que necesita https://www.sublimetext.com. Descarga del IDE de PyCharm PyCharm es proporcionado por JetBrains, que fabrica herramientas para una variedad de lenguajes diferentes. calibres El PyCharm IDE se puede descargar desde su sitio; consulte https://www. jetbrains.com/. Busque el encabezado del menú ‘Herramientas’ y selecciónelo. Verás un larga lista de herramientas, que debería incluir PyCharm. Seleccione esta opción. La página resultante tiene mucha información; sin embargo, solo necesita seleccionar ‘DESCARGAR AHORA’. Asegúrese de seleccionar el sistema operativo que utiliza (hay opciones para Windows, Mac OS y Linux). Hay entonces dos opciones de descarga disponibles: Profesional y Comunidad. La versión profesional es la opción de pago, mientras que la versión comunitaria es Prefacio ix
gratis. Para la mayor parte del trabajo que hago en Python, la versión Community es más que adecuado y por lo tanto es la versión que puede descargar e instalar (nota con el Versión profesional, obtiene una prueba gratuita, pero deberá pagar la versión completa versión al final de la versión de prueba o vuelva a instalar la versión de la Comunidad en ese momento). Suponiendo que haya seleccionado la edición Community, el instalador se descargará ahora, y se le pedirá que lo ejecute. Tenga en cuenta que puede ignorar la solicitud de suscripción si quieres. Ahora puede ejecutar el instalador y seguir las instrucciones proporcionadas. Configuración del IDE Primero debe iniciar PyCharm IDE. Una vez iniciado, el primer cuadro de diálogo que se le muestra le pregunta si desea importar cualquier configuración que haya tenido para otra versión de PyCharm. En este punto, seleccione ‘No importar configuraciones’. Pase por el siguiente conjunto de cuadros de diálogo seleccionando la apariencia (Me gusta la luz versión del IDE), si desea compartir datos con JetBrains, etc. Una vez que ha completado esto, haga clic en la opción ‘Iniciar PyCharm’. Ahora debería aparecer la pantalla de inicio de PyCharm: Ahora crearemos un proyecto para que usted trabaje. Un proyecto en PyCharm es donde escribes tus programas y cómo configuras qué versión de Python estás usando y cualquier biblioteca que pueda necesitar (como bibliotecas de gráficos, etc.). Haga clic en la opción ‘Crear nuevo proyecto’ en el cuadro de diálogo de aterrizaje. Ahora se le preguntará dónde desea crear este nuevo proyecto. De nuevo puedes use la ubicación predeterminada, pero deberá darle un nombre, llamaré a mi proyecto python-introducción. X Prefacio
También vale la pena asegurarse de que el intérprete de Python que instaló haya sido recogido por el IDE. Puede hacer esto abriendo el ‘Proyecto Intérprete: Nuevo opción de entorno Virtualenv y asegurándose de que el campo del intérprete base esté poblado adecuadamente. Esto se muestra a continuación: Si todo está bien, seleccione ‘Crear’; si el intérprete base no está especificado o está incorrecto, luego haga clic en el botón ‘…’ a la derecha del campo y busque el ubicación apropiada. Al abrir el proyecto PyCharm, debería ver un mensaje de bienvenida; hacer clic ‘Cerrar’ y el proyecto se configurará para usted. Cuando abra el proyecto, se le mostrarán al menos dos vistas. La mano izquierda vista es la vista ‘Proyecto’ que le muestra todos los directorios y archivos en su proyecto. El área de la derecha es donde se presenta el editor que le permite escribir en tu programa Por ejemplo, La tercera área que puede mostrarse representa la salida de su programa. Si es la primera vez que abre el proyecto, entonces es posible que aún no esté visible. Sin embargo, si ejecuta un programa, se mostrará en la parte inferior del IDE. Para ejemplo: Prefacio xi
Convenciones A lo largo de este libro, encontrará una serie de convenciones utilizadas para los estilos de texto. Estos estilos de texto distinguen diferentes tipos de información. Palabras de código, valores variables de Sand Python, utilizados en el cuerpo principal del texto, se muestran usando una fuente Courier. Por ejemplo: Este programa crea una ventana de nivel superior (el wx.Frame) y le da un título. también crea una etiqueta (un objeto wx.StaticText) que se mostrará dentro del marco. En el párrafo anterior, wx.Frame y wx.StaticText son clases disponibles en un Biblioteca de interfaz gráfica de usuario de Python. Un bloque de código de Python se establece como se muestra aquí: num = int(input(‘Ingrese otro numero: ‘)) si numero > 0: imprimir(num,’es positivo’) imprimir (num, ‘cuadrado es’, num * num) imprimir(‘adiós’) Tenga en cuenta que las palabras clave y las cadenas se muestran en negrita. xi Prefacio
Cualquier línea de comando o entrada de usuario se muestra en cursiva:
python hola.py O Hola Mundo Ingrese su nombre: Juan Hola John Código de ejemplo y soluciones de muestra Los ejemplos utilizados en este libro (junto con soluciones de muestra para los ejercicios en el final de la mayoría de los capítulos) están disponibles en un repositorio de GitHub. GitHub proporciona tanto un entorno de servidor que aloja Git y una interfaz basada en web para ese entorno. Git es un sistema de control de versiones que normalmente se usa para administrar archivos de código fuente (como como los que se utilizan para crear sistemas en lenguajes de programación como Python, pero también Java, C#, C++, Scala, etc.). Sistemas como Git son muy útiles para la colaboración. ya que permiten que varias personas trabajen en una implementación y fusionar su trabajo juntos. También proporcionan una vista histórica útil del código. (que también permite a los desarrolladores revertir los cambios si las modificaciones resultan ser inadecuado). Si ya tiene Git instalado en su computadora, entonces puede clonar (obtener un copia de) el repositorio localmente usando: clon de git https://github.com/johnehunt/beginnerspython3. git Si no tiene Git, puede obtener un archivo zip de los ejemplos usando https://github.com/johnehunt/beginnerspython3/ archivo/maestro.zip Por supuesto, puede instalar Git usted mismo si lo desea. Para hacer esto, consulte https://git-scm. com/descargas. Versiones del cliente Git para Mac OS, Windows y Linux/Unix están disponibles aquí. Sin embargo, muchos IDE, como PyCharm, vienen con soporte Git y, por lo tanto, ofrecen otro enfoque para obtener un repositorio Git. Para obtener más información sobre Git, consulte http://git-scm.com/doc. Esta guía Git pro- Proporciona una muy buena imprimación y es muy recomendable. Baño, Reino Unido Juan caza Prefacio XIII
Contenido 1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.1 ¿Qué es Python? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.2 Versiones de Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 1.3 Programación Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 1.4 Bibliotecas de Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 1.5 Modelo de ejecución de Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.6 Ejecución de programas Python. . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.6.1 Uso interactivo del intérprete de Python. . . . . . . . . 5 1.6.2 Ejecución de un archivo de Python. . . . . . . . . . . . . . . . . . . . . . 6 1.6.3 Ejecución de un script de Python. . . . . . . . . . . . . . . . . . . . 7 1.6.4 Usando Python en un IDE. . . . . . . . . . . . . . . . . . . . . 9 1.7 Recursos útiles. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 2 Configuración del entorno de Python. . . . . . . . . . . . . . . . . . . . . . . . 13 2.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 2.2 Verifique si Python está instalado. . . . . . . . . . . . . . . . . . . . 13 2.3 Instalación de Python en una PC con Windows. . . . . . . . . . . . . . . . . . . . 15 2.4 Configuración en una Mac. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 2.5 Recursos en línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 3 Un primer programa en Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 3.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 3.2 Hola Mundo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 3.3 Hola mundo interactivo. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 3.4 variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 3.5 Convenciones de nombres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 3.6 Operador de asignación. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 3.7 Declaraciones de Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 3.8 Comentarios en Código. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 3.9 Guiones versus programas. . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 XV
3.10 Recursos en línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 3.11 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 4 Cadenas de pitón. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 4.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 4.2 ¿Qué son las cadenas?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 4.3 Cuerdas representativas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 4.4 ¿Qué tipo es una cadena? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 4.5 ¿Qué puedes hacer con las cuerdas? . . . . . . . . . . . . . . . . . . . . . . 35 4.5.1 Concatenación de cadenas. . . . . . . . . . . . . . . . . . . . . . . . 36 4.5.2 Longitud de una cadena. . . . . . . . . . . . . . . . . . . . . . . . . 36 4.5.3 Acceso a un personaje. . . . . . . . . . . . . . . . . . . . . . . 36 4.5.4 Acceso a un subconjunto de caracteres. . . . . . . . . . . . . . . 37 4.5.5 Cadenas repetitivas. . . . . . . . . . . . . . . . . . . . . . . . . . 37 4.5.6 División de cadenas. . . . . . . . . . . . . . . . . . . . . . . . . . . 38 4.5.7 Cuerdas de conteo. . . . . . . . . . . . . . . . . . . . . . . . . . . 38 4.5.8 Sustitución de cuerdas. . . . . . . . . . . . . . . . . . . . . . . . . . 39 4.5.9 Encontrar subcadenas. . . . . . . . . . . . . . . . . . . . . . . . 39 4.5.10 Conversión de otros tipos en cadenas. . . . . . . . . . . . 40 4.5.11 Comparación de cadenas. . . . . . . . . . . . . . . . . . . . . . . . . 40 4.5.12 Otras operaciones con cadenas. . . . . . . . . . . . . . . . . . . . . 40 4.6 Sugerencias sobre cadenas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 4.6.1 Las cadenas de Python distinguen entre mayúsculas y minúsculas. . . . . . . . . . . . . . 42 4.6.2 Nombres de funciones/métodos . . . . . . . . . . . . . . . . . . . . . 42 4.6.3 Invocaciones de función/método. . . . . . . . . . . . . . . . . . 42 4.7 Formateo de cadenas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 4.8 Plantillas de cadenas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 4.9 Recursos en línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 4.10 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 5 Números, Booleanos y Ninguno. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 5.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 5.2 Tipos de Números. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 5.3 enteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 5.3.1 Conversión a enteros. . . . . . . . . . . . . . . . . . . . . . . . . . 53 5.4 Números de punto flotante . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 5.4.1 Conversión a flotadores. . . . . . . . . . . . . . . . . . . . . . . . 54 5.4.2 Convertir una cadena de entrada en un punto flotante Número . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 5.5 Números complejos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 5.6 Valores booleanos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 5.7 Operadores aritméticos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 5.7.1 Operaciones con enteros. . . . . . . . . . . . . . . . . . . . . . . . . 57 5.7.2 División de números enteros negativos. . . . . . . . . . . . . . 59 xvi Contenido
5.7.3 Operadores de números de punto flotante. . . . . . . . . . . . . . . 60 5.7.4 Operaciones con enteros y punto flotante. . . . . . . . . . . 60 5.7.5 Operadores de números complejos. . . . . . . . . . . . . . . . . . 61 5.8 Operadores de Asignación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 5.9 Ninguno Valor. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 5.10 Recursos en línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 5.11 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 5.11.1 Ejercicio general. . . . . . . . . . . . . . . . . . . . . . . . . . . 64 5.11.2 Convertir Kilómetros a Millas. . . . . . . . . . . . . . . . . . 64 6 Flujo de control usando sentencias If. . . . . . . . . . . . . . . . . . . . . . . . sesenta y cinco 6.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . sesenta y cinco 6.2 Operadores de comparación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . sesenta y cinco 6.3 Operadores logicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 6.4 La sentencia If. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 6.4.1 Trabajar con una instrucción If. . . . . . . . . . . . . . . . . 67 6.4.2 Else en una instrucción If. . . . . . . . . . . . . . . . . . . . . . 68 6.4.3 El uso de elif. . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 6.5 Anidamiento de sentencias If. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 6.6 Si Expresiones. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 6.7 Una nota sobre verdadero y falso. . . . . . . . . . . . . . . . . . . . . . . . . . . 71 6.8 Sugerencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 6.9 Recursos en línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 6.10 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 6.10.1 Verifique que la entrada sea positiva o negativa. . . . . . . . . . . . . 72 6.10.2 Prueba si un número es par o impar. . . . . . . . . . . . . . 73 6.10.3 Conversor de kilómetros a millas. . . . . . . . . . . . . . . . . 73 7 Iteración/bucles. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 7.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 7.2 Mientras Bucle. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 7.3 En bucle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 7.4 Declaración de ruptura de bucle. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 7.5 Continuar declaración de bucle. . . . . . . . . . . . . . . . . . . . . . . . . . . 81 7.6 For Loop con Else . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82 7.7 Una nota sobre la denominación de variables de bucle. . . . . . . . . . . . . . . . . . . . . 83 7.8 Juego de tirada de dados. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 7.9 Recursos en línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 7.10 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 7.10.1 Calcular el Factorial de un Número. . . . . . . . . . . . . 84 7.10.2 Imprimir todos los números primos en un rango. . . . . . . . . 85 Contenido xvii
8 Juego de adivinanzas de números. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 8.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 8.2 Configuración del programa. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 8.2.1 Agregue un mensaje de bienvenida. . . . . . . . . . . . . . . . . . . . 87 8.2.2 Ejecutando el Programa. . . . . . . . . . . . . . . . . . . . . . . 88 8.3 ¿Qué hará el programa? . . . . . . . . . . . . . . . . . . . . . . . . . 89 8.4 Creando el Juego. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 8.4.1 Genere el número aleatorio. . . . . . . . . . . . . . . . . 90 8.4.2 Obtener una entrada del usuario. . . . . . . . . . . . . . . . . 91 8.4.3 Comprobar para ver si el jugador ha adivinado el Número . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 8.4.4 Comprobar que no han superado su máximo Número de adivinanzas. . . . . . . . . . . . . . . . . . . . . . . . . . 92 8.4.5 Notifique al jugador si es más alto o más bajo. . . . . . . 93 8.4.6 Estado del final del juego. . . . . . . . . . . . . . . . . . . . . . . . 94 8.5 El listado completo. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 8.6 Sugerencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 8.6.1 Inicializando Variables. . . . . . . . . . . . . . . . . . . . . . . . 96 8.6.2 Líneas en blanco dentro de un bloque de código. . . . . . . . . . . . 96 8.7 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 9 recursividad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 9.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 9.2 Comportamiento recursivo. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 9.3 Beneficios de la recursividad. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 9.4 Búsqueda recursiva de un árbol. . . . . . . . . . . . . . . . . . . . . . . . . 100 9.5 Recursividad en Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102 9.6 Cálculo factorial recursivamente. . . . . . . . . . . . . . . . . . . . . . 102 9.7 Desventajas de la recursividad. . . . . . . . . . . . . . . . . . . . . . . . . . 104 9.8 Recursos en línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105 9.9 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105 10 Introducción al Análisis Estructurado. . . . . . . . . . . . . . . . . . . . . . . . 107 10.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 10.2 Análisis Estructurado e Identificación de Funciones. . . . . . . . . . . . 107 10.3 Descomposición funcional . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 10.3.1 Terminología de descomposición funcional. . . . . . . . . . 109 10.3.2 Proceso de Descomposición Funcional. . . . . . . . . . . . . . 110 10.3.3 Ejemplo de descomposición funcional de calculadora. . . . . 110 10.4 Flujo Funcional. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 10.5 Diagramas de flujo de datos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 10.6 Diagramas de flujo. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 10.7 Diccionario de datos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 10.8 Recursos en línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116 xviii Contenido
11 Funciones en Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 11.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 11.2 ¿Qué son las funciones?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 11.3 Cómo funcionan las funciones. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118 11.4 Tipos de funciones. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118 11.5 Definición de funciones. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119 11.5.1 Una función de ejemplo. . . . . . . . . . . . . . . . . . . . . . . 120 11.6 Devolución de valores desde funciones. . . . . . . . . . . . . . . . . . . . . . 121 11.7 Cadena de documentos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123 11.8 Parámetros de función. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124 11.8.1 Funciones de parámetros múltiples. . . . . . . . . . . . . . . . . 124 11.8.2 Valores de parámetros predeterminados. . . . . . . . . . . . . . . . . . . . 125 11.8.3 Argumentos con nombre. . . . . . . . . . . . . . . . . . . . . . . . . 126 11.8.4 Argumentos arbitrarios. . . . . . . . . . . . . . . . . . . . . . . . 127 11.8.5 Argumentos posicionales y de palabras clave. . . . . . . . . . . . . 128 11.9 Funciones anónimas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 11.10 Recursos en línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131 11.11 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131 12 Alcance y vida útil de las variables. . . . . . . . . . . . . . . . . . . . . . . . . . . 133 12.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 12.2 Variables locales. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 12.3 La palabra clave global. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 12.4 Variables no locales. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136 12.5 Sugerencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137 12.6 Recursos en línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138 12.7 Ejercicio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138 13 Implementación de una calculadora usando funciones. . . . . . . . . . . . . . . . . . 139 13.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139 13.2 Qué hará la calculadora. . . . . . . . . . . . . . . . . . . . . . . . . 139 13.3 Empezando . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 13.4 Las operaciones de la calculadora. . . . . . . . . . . . . . . . . . . . . . . . . . . 140 13.5 Comportamiento de la Calculadora. . . . . . . . . . . . . . . . . . . . . . . . . . 141 13.6 Identificar si el usuario ha terminado. . . . . . . . . . . . . . . 142 13.7 Selección de la Operación. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144 13.8 Obtención de los Números de Entrada. . . . . . . . . . . . . . . . . . . . . . . . . 146 13.9 Determinación de la Operación a Ejecutar. . . . . . . . . . . . . . . . . . 147 13.10 Ejecutando la Calculadora. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147 13.11 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148 14 Introducción a la Programación Funcional. . . . . . . . . . . . . . . . . . . . 149 14.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149 14.2 ¿Qué es la programación funcional? . . . . . . . . . . . . . . . . . . . . . 149 Contenido xix
14.3 Ventajas de la Programación Funcional. . . . . . . . . . . . . . . . . 151 14.4 Desventajas de la Programación Funcional. . . . . . . . . . . . . . . 153 14.5 Transparencia referencial. . . . . . . . . . . . . . . . . . . . . . . . . . . . 153 14.6 Otras lecturas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155 15 Funciones de orden superior. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157 15.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157 15.2 Resumen de funciones en Python. . . . . . . . . . . . . . . . . . . . . . . . 157 15.3 Funciones como objetos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158 15.4 Conceptos de funciones de orden superior. . . . . . . . . . . . . . . . . . . . . . 160 15.4.1 Ejemplo de función de orden superior. . . . . . . . . . . . . . . 161 15.5 Funciones de orden superior de Python. . . . . . . . . . . . . . . . . . . . . . . 162 15.5.1 Uso de funciones de orden superior. . . . . . . . . . . . . . . . . 163 15.5.2 Funciones que devuelven funciones. . . . . . . . . . . . . . . . 164 15.6 Recursos en línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 15.7 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 dieciséis Funciones al curry. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167 16.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167 16.2 Conceptos de curry. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167 16.3 Funciones Python y Curry. . . . . . . . . . . . . . . . . . . . . . . . 168 16.4 Cierres. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170 16.5 Recursos en línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172 16.6 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173 17 Introducción a la Orientación a Objetos. . . . . . . . . . . . . . . . . . . . . . . . . 175 17.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175 17.2 clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175 17.3 ¿Para qué son las clases? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176 17.3.1 ¿Qué debe hacer una clase? . . . . . . . . . . . . . . . . . . . . 177 17.3.2 Terminología de la clase. . . . . . . . . . . . . . . . . . . . . . . . . 177 17.4 ¿Cómo se construye un sistema OO? . . . . . . . . . . . . . . . . . . . 178 17.4.1 ¿Donde empezamos? . . . . . . . . . . . . . . . . . . . . . . . . 179 17.4.2 Identificación de los objetos. . . . . . . . . . . . . . . . . . . . . . 180 17.4.3 Identificación de los Servicios o Métodos. . . . . . . . . . . . . 181 17.4.4 Refinando los Objetos. . . . . . . . . . . . . . . . . . . . . . . . 182 17.4.5 Reuniéndolo todo. . . . . . . . . . . . . . . . . . . . . 183 17.5 ¿Dónde está la estructura en un programa OO? . . . . . . . . . . . . . . . 186 17.6 Otras lecturas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188 18 Clases de Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189 18.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189 18.2 Definiciones de clase. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190 18.3 Creando Ejemplos de la Persona de Clase. . . . . . . . . . . . . . . . . . 191 18.4 Tenga cuidado con la asignación. . . . . . . . . . . . . . . . . . . . . . . . . 192 XX Contenido
18.5 Impresión de objetos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194 18.5.1 Acceso a atributos de objetos. . . . . . . . . . . . . . . . . . . 194 18.5.2 Definición de una representación de cadena predeterminada. . . . . . . . . 195 18.6 Proporcionar un comentario de clase. . . . . . . . . . . . . . . . . . . . . . . . . . 196 18.7 Adición de un método de cumpleaños. . . . . . . . . . . . . . . . . . . . . . . . . . 197 18.8 Definición de métodos de instancia. . . . . . . . . . . . . . . . . . . . . . . . . . 198 18.9 Resumen de la clase de persona. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199 18.10 La palabra clave del. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200 18.11 Gestión automática de memoria. . . . . . . . . . . . . . . . . . . . . . 201 18.12 Atributos intrínsecos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202 18.13 Recursos en línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203 18.14 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203 19 Lado de clase y comportamiento estático. . . . . . . . . . . . . . . . . . . . . . . . . . . 205 19.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205 19.2 Datos del lado de la clase. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205 19.3 Métodos del lado de la clase. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206 19.3.1 ¿Por qué métodos del lado de la clase?. . . . . . . . . . . . . . . . . . . . 207 19.4 Métodos estáticos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207 19.5 Sugerencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208 19.6 Recursos en línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208 19.7 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209 20 Herencia de clases. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211 20.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211 20.2 ¿Qué es la herencia? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211 20.3 Terminología en torno a la herencia. . . . . . . . . . . . . . . . . . . . . . 215 20.4 El objeto de clase y la herencia. . . . . . . . . . . . . . . . . . . . . . 217 20.5 La clase de objeto integrada. . . . . . . . . . . . . . . . . . . . . . . . . . . 218 20.6 Finalidad de las subclases. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218 20.7 Métodos de anulación. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219 20.8 Ampliación de métodos de superclase. . . . . . . . . . . . . . . . . . . . . . . . 221 20,9 Convenciones de nomenclatura orientadas a la herencia. . . . . . . . . . . . . . . 222 20.10 Python y la herencia múltiple. . . . . . . . . . . . . . . . . . . . . . 223 20.11 La Herencia Múltiple Considerada Dañina. . . . . . . . . . . . . . . . 225 20.12 Resumen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230 20.13 Recursos en línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230 20.14 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231 21 ¿Por qué molestarse con la orientación a objetos? . . . . . . . . . . . . . . . . . . . . . . 233 21.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233 21.2 El enfoque procesal. . . . . . . . . . . . . . . . . . . . . . . . . . . 233 21.2.1 Procedimientos para la Estructura de Datos. . . . . . . . . . . . . . 234 21.2.2 paquetes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235 Contenido xxx
21.3 ¿La orientación a objetos funciona mejor? . . . . . . . . . . . . . . . . 235 21.3.1 Paquetes Versus Clases. . . . . . . . . . . . . . . . . . . . . 235 21.3.2 Herencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237 21.4 Resumen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239 22 Sobrecarga del operador. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241 22.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241 22.2 Sobrecarga del operador. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241 22.2.1 ¿Por qué hay sobrecarga de operadores? . . . . . . . . . . . . . . 241 22.2.2 ¿Por qué no tener sobrecarga de operadores?. . . . . . . . . . . 242 22.2.3 Implementación de la sobrecarga de operadores. . . . . . . . . . . . 242 22.3 Operadores numéricos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244 22.4 Operadores de comparación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247 22.5 Operadores logicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249 22.6 Resumen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249 22.7 Recursos en línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250 22.8 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250 23 Propiedades de Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253 23.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253 23.2 Atributos de Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253 23.3 Métodos de estilo Setter y Getter. . . . . . . . . . . . . . . . . . . . . . . 255 23.4 Interfaz pública a las propiedades. . . . . . . . . . . . . . . . . . . . . . . . . 256 23.5 Definiciones de propiedad más concisas. . . . . . . . . . . . . . . . . . . . . 258 23.6 Recursos en línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 260 23.7 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 260 24 Manejo de errores y excepciones. . . . . . . . . . . . . . . . . . . . . . . . . . . . 263 24.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263 24.2 Errores y Excepciones. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263 24.3 ¿Qué es una excepción? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264 24.4 ¿Qué es el manejo de excepciones? . . . . . . . . . . . . . . . . . . . . . . . . 265 24.5 Manejo de una excepción. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267 24.5.1 Acceso al objeto de excepción. . . . . . . . . . . . . . . . 269 24.5.2 Saltar a los controladores de excepciones. . . . . . . . . . . . . . . . 270 24.5.3 Captura cualquier excepción. . . . . . . . . . . . . . . . . . . . . . . 272 24.5.4 La cláusula Else. . . . . . . . . . . . . . . . . . . . . . . . . . . 272 24.5.5 La cláusula final. . . . . . . . . . . . . . . . . . . . . . . . . 273 24.6 Generación de una excepción. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274 24.7 Definición de una excepción personalizada. . . . . . . . . . . . . . . . . . . . . . . . 275 24,8 Excepciones de encadenamiento. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277 24,9 Recursos en línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279 24.10 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279 XXII Contenido
25 Módulos y paquetes de Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . 281 25.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 281 25.2 Módulos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 281 25.3 Módulos de Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282 25.4 Importación de módulos de Python. . . . . . . . . . . . . . . . . . . . . . . . . . 284 25.4.1 Importación de un módulo. . . . . . . . . . . . . . . . . . . . . . . . 284 25.4.2 Importando desde un Módulo. . . . . . . . . . . . . . . . . . . . 285 25.4.3 Ocultar algunos elementos de un módulo. . . . . . . . . . . . 287 25.4.4 Importación dentro de una función. . . . . . . . . . . . . . . . . . 288 25,5 Propiedades del módulo. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 288 25.6 Módulos Estándar. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289 25.7 Ruta de búsqueda del módulo de Python. . . . . . . . . . . . . . . . . . . . . . . . . . 291 25,8 Módulos como Scripts. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292 25,9 Paquetes Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 294 25.9.1 Organización del paquete. . . . . . . . . . . . . . . . . . . . . . . 294 25.9.2 Subpaquetes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296 25.10 Recursos en línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296 25.11 Ejercicio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 297 26 Clases base abstractas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 299 26.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 299 26.2 Clases abstractas como concepto. . . . . . . . . . . . . . . . . . . . . . . . 299 26.3 Clases base abstractas en Python. . . . . . . . . . . . . . . . . . . . . . 300 26.3.1 Subclasificación de un ABC. . . . . . . . . . . . . . . . . . . . . . . . 300 26.3.2 Definición de una clase base abstracta. . . . . . . . . . . . . . . 302 26.4 Definición de una interfaz. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304 26.5 Subclases virtuales. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 305 26.6 mezclas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 306 26.7 Recursos en línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 308 26.8 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 308 27 Protocolos, polimorfismos y descriptores. . . . . . . . . . . . . . . . . . . . 311 27.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311 27.2 Contratos implícitos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311 27.3 Mecanografía de pato. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313 27.4 protocolos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314 27.5 Un ejemplo de protocolo. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315 27.6 El protocolo del administrador de contexto. . . . . . . . . . . . . . . . . . . . . . . 315 27.7 polimorfismo. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317 27,8 El Protocolo Descriptor. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319 27,9 Recursos en línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 322 27.10 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 322 Contenido XXIII
28 Monkey Patching y búsqueda de atributos. . . . . . . . . . . . . . . . . . . . . 325 28.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 325 28.2 ¿Qué es el parche de mono? . . . . . . . . . . . . . . . . . . . . . . . . . . 325 28.2.1 ¿Cómo funciona el parche de mono? . . . . . . . . . . . . 326 28.2.2 Ejemplo de parches de mono. . . . . . . . . . . . . . . . . . . 326 28.2.3 El autoparámetro. . . . . . . . . . . . . . . . . . . . . . . . . 327 28.2.4 Adición de nuevos datos a una clase. . . . . . . . . . . . . . . . . . 328 28.3 Consulta de atributos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 328 28.4 Manejo del acceso a atributos desconocidos. . . . . . . . . . . . . . . . . . . 331 28.5 Manejo de invocaciones de métodos desconocidos. . . . . . . . . . . . . . . . . 332 28.6 Búsqueda de atributos interceptores. . . . . . . . . . . . . . . . . . . . . . . . 333 28.7 Interceptar el establecimiento de un atributo. . . . . . . . . . . . . . . . . . . . . . . 334 28.8 Recursos en línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335 28,9 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335 29 Decoradores. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337 29.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337 29.2 ¿Qué son los decoradores? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337 29.3 Definición de un decorador. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338 29.4 Uso de decoradores. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 339 29.5 Funciones con parámetros. . . . . . . . . . . . . . . . . . . . . . . . . . . 340 29.6 Decoradores apilados. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 340 29.7 Decoradores parametrizados. . . . . . . . . . . . . . . . . . . . . . . . . . . . 342 29.8 Decoradores de métodos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 343 29.8.1 Métodos sin parámetros. . . . . . . . . . . . . . . . . . 343 29.8.2 Métodos con parámetros. . . . . . . . . . . . . . . . . . . . 344 29,9 Decoradores de clase. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 345 29.10 ¿Cuándo se ejecuta un decorador? . . . . . . . . . . . . . . . . . . . . . . . 347 29.11 Decoradores incorporados. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 348 29.12 Envoltura de herramientas de función. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 348 29.13 Recursos en línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349 29.14 Referencia del libro. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 350 29.15 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 350 30 Iterables, Iteradores, Generadores y Corrutinas. . . . . . . . . . . . . . . 353 30.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353 30.2 iteración . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353 30.2.1 Iterables. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353 30.2.2 iteradores. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354 30.2.3 Los métodos relacionados con la iteración. . . . . . . . . . . . . . . . . 354 30.2.4 La clase de pares iterables. . . . . . . . . . . . . . . . . . . . . 354 30.2.5 Uso de la clase Evens con un bucle for. . . . . . . . . . 355 30.3 El Módulo Itertools. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 356 30.4 Generadores. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 356 XXIV Contenido
30.4.1 Definición de una función generadora. . . . . . . . . . . . . . . . . 356 30.4.2 Uso de una función generadora en un bucle for. . . . . . . . 357 30.4.3 ¿Cuándo se ejecutan las declaraciones de rendimiento? . . . . . . . . . 357 30.4.4 Un generador de números pares. . . . . . . . . . . . . . . . . . 358 30.4.5 Funciones del generador de anidamiento. . . . . . . . . . . . . . . . . . 358 30.4.6 Uso de generadores fuera de un bucle for. . . . . . . . . . . . 359 30.5 Corrutinas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360 30.6 Recursos en línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 361 30.7 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 361 31 Colecciones, Tuplas y Listas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363 31.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363 31.2 Tipos de colecciones de Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . 363 31.3 tuplas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 364 31.3.1 Creación de Tuplas. . . . . . . . . . . . . . . . . . . . . . . . . . . 364 31.3.2 La función constructora tuple() . . . . . . . . . . . . . . . 364 31.3.3 Acceso a elementos de una tupla. . . . . . . . . . . . . . . . 365 31.3.4 Creación de nuevas tuplas a partir de tuplas existentes. . . . . . . 365 31.3.5 Las tuplas pueden contener diferentes tipos. . . . . . . . . . . . . . 366 31.3.6 Iterando sobre tuplas. . . . . . . . . . . . . . . . . . . . . . . 367 31.3.7 Funciones relacionadas con tuplas. . . . . . . . . . . . . . . . . . . . . 367 31.3.8 Comprobar si existe un elemento. . . . . . . . . . . . . . . . . 368 31.3.9 Tuplas anidadas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368 31.3.10 Cosas que no puedes hacer con las tuplas. . . . . . . . . . . . . . 369 31.4 Listas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 369 31.4.1 Creando Listas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 369 31.4.2 Función constructora de listas. . . . . . . . . . . . . . . . . . . . 371 31.4.3 Acceso a elementos desde una lista. . . . . . . . . . . . . . . . 372 31.4.4 Adición a una lista. . . . . . . . . . . . . . . . . . . . . . . . . . . 373 31.4.5 Inserción en una lista. . . . . . . . . . . . . . . . . . . . . . . . 374 31.4.6 Concatenación de listas. . . . . . . . . . . . . . . . . . . . . . . . . 374 31.4.7 Eliminación de una lista. . . . . . . . . . . . . . . . . . . . . . . 375 31.4.8 El método pop(). . . . . . . . . . . . . . . . . . . . . . . . . . 375 31.4.9 Eliminación de una lista. . . . . . . . . . . . . . . . . . . . . . . . 376 31.4.10 Métodos de lista. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 377 31.5 Recursos en línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 377 31.6 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 378 32 conjuntos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379 32.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379 32.2 Creación de un conjunto. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379 32.3 La función constructora Set(). . . . . . . . . . . . . . . . . . . . . . . 380 32.4 Acceso a los elementos de un conjunto. . . . . . . . . . . . . . . . . . . . . . . . . 380 32.5 Trabajando con Conjuntos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 380 Contenido xiv
32.5.1 Comprobación de la presencia de un elemento. . . . . . . . . . . . 380 32.5.2 Adición de elementos a un conjunto. . . . . . . . . . . . . . . . . . . . . . . 381 32.5.3 Cambio de elementos en un conjunto. . . . . . . . . . . . . . . . . . . . . 381 32.5.4 Obtención de la longitud de un conjunto. . . . . . . . . . . . . . . . . 382 32.5.5 Obtención de los valores máximo y mínimo de un conjunto. . . . . . . 382 32.5.6 Eliminación de un elemento. . . . . . . . . . . . . . . . . . . . . . . . . 382 32.5.7 Conjuntos de anidamiento. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383 32.6 Establecer operaciones. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 384 32.7 Establecer métodos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386 32.8 Recursos en línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386 32,9 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 387 33 diccionarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 389 33.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 389 33.2 Creación de un diccionario. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 389 33.2.1 La función constructora dict() . . . . . . . . . . . . . . . . 390 33.3 Trabajar con diccionarios. . . . . . . . . . . . . . . . . . . . . . . . . . . 391 33.3.1 Acceso a elementos a través de teclas. . . . . . . . . . . . . . . . . . . . 391 33.3.2 Adición de una nueva entrada. . . . . . . . . . . . . . . . . . . . . . . . 391 33.3.3 Cambiar el valor de una clave. . . . . . . . . . . . . . . . . . . . . 391 33.3.4 Eliminación de una entrada. . . . . . . . . . . . . . . . . . . . . . . . . 392 33.3.5 Iterando sobre Keys. . . . . . . . . . . . . . . . . . . . . . . . . 393 33.3.6 Valores, Claves y Items. . . . . . . . . . . . . . . . . . . . . . 394 33.3.7 Comprobación de membresía clave. . . . . . . . . . . . . . . . . . . 394 33.3.8 Obtención de la longitud de un diccionario. . . . . . . . . . . . 395 33.3.9 Diccionarios de anidamiento. . . . . . . . . . . . . . . . . . . . . . . . 395 33.4 Una nota sobre los objetos clave del diccionario. . . . . . . . . . . . . . . . . . . . 396 33.5 Métodos de diccionario. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 397 33.6 Recursos en línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 397 33.7 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 398 34 Módulos relacionados con la colección. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401 34.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401 34.2 Comprensión de listas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401 34.3 El Módulo de Colecciones. . . . . . . . . . . . . . . . . . . . . . . . . . . . 402 34.4 El Módulo Itertools. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 405 34.5 Recursos en línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 406 34.6 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 406 35 ADT, Colas y Pilas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 407 35.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 407 35.2 Tipos de datos abstractos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 407 35.3 Estructuras de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 408 xxi Contenido
35.4 colas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 408 35.4.1 Lista de Python como una cola. . . . . . . . . . . . . . . . . . . . . . 409 35.4.2 Definición de una clase de cola. . . . . . . . . . . . . . . . . . . . . . 409 35.5 pilas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 411 35.5.1 Lista de Python como una pila. . . . . . . . . . . . . . . . . . . . . . 412 35.6 Recursos en línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 413 35.7 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 413 36 Mapear, Filtrar y Reducir. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 415 36.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 415 36.2 filtro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 415 36.3 Mapa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 417 36.4 Reducir . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 419 36.5 Recursos en línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420 36.6 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420 37 Juego de tres en raya. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 423 37.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 423 37.2 Clases en el Juego. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 423 37.3 Clase de contador. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426 37.4 Mover Clase. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 427 37.5 La clase del jugador. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 427 37.6 La clase HumanPlayer. . . . . . . . . . . . . . . . . . . . . . . . . . . . 428 37.7 La clase ComputerPlayer. . . . . . . . . . . . . . . . . . . . . . . . . . . 429 37.8 La clase del tablero. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 430 37,9 La Clase de Juego. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 431 37.10 Ejecutando el Juego. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 432 Corrección a: Funciones en Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . C1 Contenido xvii
Capítulo 1 Introducción 1.1 ¿Qué es Python? Python es un lenguaje de programación de propósito general similar a otros lenguajes de programación de los que quizás hayas oído hablar, como C++, JavaScript o C# de Microsoft y Java de Oracle. Ha existido durante un tiempo considerable después de haber sido concebido originalmente en la década de 1980 por Guido van Rossum en Centrum Wiskunde & Informatica (CWI) en los Países Bajos. El idioma lleva el nombre de uno de los favoritos de Guido. programa “Monty Pythons Flying Circus”, un clásico y algo anárquico británico programa de sketches de comedia que se desarrolló originalmente entre 1969 y 1974 (pero que ha sido retransmitido en varias estaciones desde entonces) y con varias películas derivadas. Incluso lo harás encuentre varias referencias a este programa en la documentación disponible con Python. Como lengua ha ganado interés en los últimos años, particularmente dentro de la mundo comercial, con mucha gente queriendo aprender el idioma. Esto aumentó El interés en Python está impulsado por varios factores diferentes:
- Su flexibilidad y sencillez que facilita su aprendizaje.
- Su uso por parte de la comunidad de Data Science donde proporciona un estándar más lenguaje de programación que algunos rivales como R.
- Su idoneidad como lenguaje de secuencias de comandos para quienes trabajan en el campo DevOps donde proporciona un mayor nivel de abstracción que los lenguajes alternativos tra- usado tradicionalmente.
- Su capacidad para ejecutarse en (casi) cualquier sistema operativo, pero particularmente en los tres grandes sistemas operativos Windows, macOS y Linux.
- La disponibilidad de una amplia gama de bibliotecas (módulos) que se pueden utilizar para ampliar las características básicas de la lengua.
- ¡Es gratis! © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_1 1
Python en sí mismo ahora es administrado por Python Software Foundation, una organización sin fines de lucro. (ver https://en.wikipedia.org/wiki/Python_Software_Foundation) que fue lanzado ched en marzo de 2001. La misión de la fundación es fomentar el desarrollo de la comunidad de Python; también es responsable de varios procesos dentro de Python comunidad, incluido el desarrollo de la distribución central de Python, la gestión de derechos intelectuales y conferencias de desarrolladores de apoyo, incluida PyCon. 1.2 Versiones de Python Actualmente hay dos versiones principales de Python llamadas Python 2 y Python 3. • Python 2 se lanzó en octubre de 2000 y ha sido, y sigue siendo, ampliamente usado. • Python 3 se lanzó en diciembre de 2008 y es una revisión importante del lenguaje. calibre que no es compatible con versiones anteriores. El problema entre las dos versiones se puede resaltar mediante la impresión simple instalación: • En Python 2 esto se escribe como print ‘Hello World’ • En Python 3 esto se escribe como impresión (‘Hello World’) Puede que no parezca una gran diferencia, pero la inclusión de ‘()’ marca un cambio importante y significa que cualquier código escrito para una versión de Python probablemente no se ejecute en la otra versión. Hay herramientas disponibles, como el 2–3 utilidad, que (parcialmente) automatizará la traducción de Python 2 a Python 3 pero en En general, todavía le queda mucho trabajo por hacer. Esto entonces plantea la pregunta de qué versión usar. Aunque el interés en Python 3 aumenta constantemente, hay muchas organizaciones que todavía usan Python 2. Elegir qué versión usar es una preocupación constante para muchas empresas. Sin embargo, el plan de fin de vida de Python 2 se anunció inicialmente en 2015 y aunque se pospuso hasta 2020 debido a la preocupación de que una gran cantidad de El código no se pudo transferir fácilmente a Python 3, todavía está viviendo en préstamo. tiempo. Python 3 es el futuro del lenguaje Python y es esta versión la que tiene introdujo muchas de las características nuevas y mejoradas del lenguaje y la biblioteca (que han es cierto que ha sido portado a Python 2 en muchos casos). Este libro es únicamente centrado en Python 3. En el resto de este libro cuando nos referimos a Python siempre estaremos refiriéndose a Python 3. 2 1 Introducción
1.3 Programación Python Hay varios paradigmas de programación diferentes que un lenguaje de programación pueden permitir a los desarrolladores codificar, estos son: • Programación procedimental en la que un programa se representa como una secuencia de instrucciones que le dicen a la computadora lo que debe hacer explícitamente. Procedimientos y/ o funciones se utilizan para dar estructura al programa; con estructuras de control como declaraciones if y construcciones de bucle para administrar qué pasos se ejecutan y cuantas veces. Los lenguajes que tipifican este enfoque incluyen C y Pascal. • Lenguajes de programación declarativa, como Prolog, que permiten a los desarrolladores describir cómo se debe resolver un problema, con el lenguaje/entorno determinar cómo se debe implementar la solución. SQL (una consulta de base de datos lenguaje) es uno de los lenguajes declarativos más comunes que probablemente encontrar. • Enfoques de Programación Orientada a Objetos que representan un sistema en términos de los objetos que forman ese sistema. Cada objeto puede contener sus propios datos (también conocidos como estado), así como definir el comportamiento que define lo que el objeto puede hacer. Un programa de computadora se forma a partir de un conjunto de estos objetos que cooperan entre sí. Los lenguajes como Java y C# tipifican el enfoque orientado a objetos. • Los lenguajes de programación funcional descomponen un problema en un conjunto de funciones Cada función es independiente de cualquier estado externo, operando solo en los insumos que recibieron para generar sus productos. El lenguaje de programación Haskell es un ejemplo de un lenguaje de programación funcional. Algunos lenguajes de programación se consideran lenguajes híbridos; eso es permiten a los desarrolladores utilizar una combinación de enfoques diferentes dentro del mismo programa. Python es un ejemplo de un lenguaje de programación híbrido ya que permite le permite escribir código muy procedimental, usar objetos de una manera orientada a objetos y para escribir programas funcionales. Cada uno de estos enfoques se cubre en este libro. 1.4 Bibliotecas de Python Además del lenguaje central, hay muchas bibliotecas disponibles para Python. Estas bibliotecas amplían la funcionalidad del lenguaje y hacen que sea mucho más fácil desarrollar aplicaciones. Estas bibliotecas cubren • marcos web como Django/Flask, • clientes de correo electrónico como smtplib (un cliente de correo electrónico SMTP) e imaplib (un cliente IMAP4 Cliente de correo electronico), • operaciones de gestión de contenidos como la biblioteca de Zope, • concurrencia ligera (ejecutar múltiples operaciones al mismo tiempo) usando la biblioteca sin pilas, • la generación de archivos de Microsoft Excel utilizando la biblioteca Python Excel, • bibliotecas de gráficos como Matplotlib y PyOpenGL, • aprendizaje automático utilizando bibliotecas como SKLearn y TensorFlow. 1.3 Programación Python 3
Un recurso muy útil para mirar, que presenta muchas de estas bibliotecas (también conocidos como módulos), es el sitio web ‘Python 3 module of the Week’ que se puede encontrado en https://pymotw.com/3. Esto enumera muchas de las bibliotecas/módulos disponibles y proporciona una breve introducción a lo que hacen y cómo usarlos. 1.5 Modelo de ejecución de Python Python no es un lenguaje precompilado en la forma en que otros lenguajes puede haber encontrado son (como C++). En cambio, es lo que se conoce como un lenguaje interpretado (aunque incluso esto no es del todo exacto). un interpretado lenguaje es uno que no requiere una fase de compilación separada para convertir el formato legible por humanos en algo que puede ser ejecutado por una computadora. En cambio la versión de texto sin formato se introduce en otro programa (generalmente conocido como el intérprete) que luego ejecuta el programa por usted. Python en realidad usa un modelo intermedio en el sentido de que realmente convierte el modelo simple texto del programa Python de estilo inglés en un código de máquina ‘pseudo’ intermedio y es este formato intermedio el que se ejecuta. Esto se ilustra a continuación: La forma en que el intérprete de Python procesa un programa de Python está rota hacia abajo en varios pasos. Los pasos que se muestran aquí son ilustrativos (y simplificados), pero la idea general es correcta.
- Primero se verifica el programa para asegurarse de que sea Python válido. eso es un cheque se hace que el programa siga todas las reglas del lenguaje y que cada uno de los comandos y operaciones, etc. son entendidos por el entorno de Python.
- Luego traduce el texto sin formato, como comandos en inglés, a un formato más conciso. formato intermedio que es más fácil de ejecutar en una computadora. Python puede almacenar esto versión intermedia en un archivo que lleva el nombre del archivo original pero con un ‘. pyc’ en lugar de una extensión ‘.py’ (la ‘c’ en la extensión lo indica contiene la versión compilada del código).
- A continuación, el intérprete ejecuta la versión intermedia compilada. Cuando se vuelve a ejecutar este programa, el intérprete de Python verifica si un archivo ‘.pyc’ está presente. Si no se han realizado cambios en el archivo fuente desde que se instaló ‘.pyc’ 4 1 Introducción
creado, entonces el intérprete puede omitir los pasos 1 y 2 e inmediatamente ejecutar el ‘.pyc’ versión del programa. Un aspecto interesante del uso de Python es que puede usarse (y a menudo se usa) en una manera interactiva (a través de REPL), con comandos individuales que se ingresan y ejecuta uno a la vez, con la información de contexto que se construye. esto puede ser util en situaciones de depuración. 1.6 Ejecutar programas de Python Hay varias formas en las que puede ejecutar un programa de Python, incluyendo • Uso interactivo del intérprete de Python • Almacenado en un archivo y ejecutado usando el comando de Python • Ejecutar como un archivo de secuencia de comandos especificando el intérprete de Python para usar dentro del archivo de secuencia de comandos • Desde dentro de un IDE de Python (Entorno de desarrollo integrado) como PyCharm. 1.6.1 Uso interactivo del intérprete de Python Es bastante común encontrar que la gente usará Python en modo interactivo. Esto usa Python REPL (llamado así por el estilo de operación Read Evaluate Print Loop). Usando el REPL, las declaraciones y expresiones de Python se pueden escribir en el Indicación de Python y luego se ejecutará directamente. Los valores de las variables serán recordado y puede ser utilizado más adelante en la sesión. Para ejecutar Python REPL, Python debe haberse instalado en la computadora sistema que está utilizando. Una vez instalado, puede abrir una ventana del símbolo del sistema (Windows) o una ventana de Terminal (Mac) y escriba python en el indicador. Esto es se muestra para una máquina con Windows a continuación: 1.5 Modelo de ejecución de Python 5
En el ejemplo anterior, escribimos interactivamente varios comandos de Python y el intérprete de Python ‘Lee’ lo que hemos escrito, lo ‘Evalúa’ (resuelve qué debería funcionar), ‘Imprimió’ el resultado y luego ‘En bucle’ listo para más entradas. En este caso nosotros • Impreso la cadena ‘Hello World’. • Sumó 5 y 4 y obtuvo el resultado 9. • Almacenó la cadena ‘Juan’ en una variable llamada nombre. • Imprime el contenido del nombre de la variable. Para salir del shell interactivo (el REPL) y volver a la consola (el sistema shell), presione Ctrl-Z y luego Enter en Windows, o Ctrl-D en OS X o Linux. Alternativamente, también puede ejecutar el comando de Python exit() o quit(). 1.6.2 Ejecutar un archivo de Python Por supuesto, podemos almacenar los comandos de Python en un archivo. Esto crea un archivo de programa que luego se puede ejecutar como un argumento para el comando python. Por ejemplo, dado un archivo que contiene el siguiente archivo (llamado hello.py) con los 4 comandos en él: Para ejecutar el programa hello.py en una PC usando Windows podemos usar el python comando seguido del nombre del archivo: También podemos ejecutar el mismo programa en una Apple Mac usando MacOS a través de la intérprete de Python. Por ejemplo en un Mac podemos hacer lo siguiente: 6 1 Introducción
Esto hace que sea muy fácil crear programas en Python que se pueden almacenar en archivos y ejecutar cuando sea necesario en cualquier plataforma que se requiera (Windows, Linux o Mac). Este ilustra la naturaleza multiplataforma de Python y es solo una de las razones por las que Python es tan popular. 1.6.3 Ejecutar un script de Python También es posible transformar un archivo que contiene un programa de Python almacenado en un Guion. Un script es un archivo independiente que se puede ejecutar directamente sin necesidad de (explícitamente) use el comando python. Esto se hace agregando una línea especial al inicio del archivo de Python que indica el comando de Python (o intérprete) para usar con el resto del archivo. Esta línea debe Empezar con ‘#!’ y debe ir al principio del archivo. Para convertir el archivo de la sección anterior en un Script necesitaríamos agregar el ruta al intérprete de python. Aquí ruta se refiere a la ruta que la computadora debe tomar para encontrar el intérprete (o ejecutable) de Python especificado. La ubicación exacta del intérprete de Python en su computadora depende de qué las opciones se seleccionaron cuando usted (o quien instaló Python) lo configuró. Típicamente en una PC con Windows, Python se encontrará en el directorio ‘Archivos de programa’ o podría instalarse en su propio directorio ‘Python’. Sea cual sea la ubicación del intérprete de Python, para crear un script necesitaremos agregue una primera línea a nuestro archivo hello.py. Esta línea debe comenzar con un #!. esta combinación de caracteres se conoce como shebang e indica a Linux y otros Unix como sistemas operativos (como MacOS) cómo debe ser el resto del archivo ejecutado. Por ejemplo, en un Apple Mac podríamos agregar: /Biblioteca/Frameworks/Python.framework/Versions/3.7/bin/python3 Cuando se agrega al archivo hello.py ahora tenemos: 1.6 Ejecutar programas de Python 7
Sin embargo, no podemos simplemente ejecutar el archivo tal como está. Si tratamos de ejecutar el archivo sin ningún cambio, obtendremos un error que indica que el permiso para ejecutar el archivo ha sido denegado: $ ./hola.py -bash: ./hello.py: Permiso denegado ps Esto se debe a que, de forma predeterminada, no puede simplemente ejecutar un archivo. Tenemos que marcarlo como ejecutable. Hay varias formas de hacer esto, sin embargo, una de las más fáciles en una Mac o el cuadro de Linux es usar el comando chmod (que se puede usar para modificar el permisos asociados con el archivo). Para hacer el archivo ejecutable podemos cambiar el permisos de archivo para permitir que el archivo se ejecute utilizando el siguiente comando desde un ventana de terminal cuando estamos en el mismo directorio que el archivo hello.py: $ chmod +x hola.py Donde +x indica que queremos agregar el permiso ejecutable al archivo. Ahora, si intentamos ejecutar el archivo directamente, se ejecuta y los resultados de los comandos dentro del archivo se imprimen: Tenga en cuenta el uso de ‘./’ antes del nombre del archivo en lo anterior; esto se usa en Linux y Mac para indicarle al sistema operativo que busque en el directorio actual el archivo para ejecutar. Diferentes sistemas almacenarán Python en diferentes ubicaciones y, por lo tanto, podrían necesitar Primeras líneas diferentes, por ejemplo en Linux podríamos escribir: 8 1 Introducción
#!/usr/local/bin/python3 imprimir(‘Hola, mundo’) imprimir (5 + 4) nombre = ‘Juan’ imprimir (nombre) Por defecto Windows no tiene el mismo concepto. Sin embargo, para promover portabilidad multiplataforma, Python Launcher para Windows también puede admitir esto estilo de operación. Permite que los scripts indiquen una preferencia por un Python específico. versión usando el mismo #! (Shebang) como sistemas operativos de estilo Unix. Nosotros ahora puede indicar que el resto del archivo debe interpretarse como un script de Python; si se instalan múltiples versiones de Python, esto puede requerir que Python 3 sea explícitamente especificado. El lanzador también entiende cómo traducir la versión de Unix a Versiones de Windows para que /user/local/bin/python3 se interprete como indicando que se requiere python3. Se proporciona un ejemplo del script hello.py para una máquina con Windows o Linux a continuación usando Notepad++ en una caja de Windows. Cuando se instaló el iniciador, debería haberse asociado con los archivos de Python. (es decir, archivos que tienen una extensión ‘.py’). Esto significa que si hace doble clic en uno de estos archivos desde el Explorador de Windows, luego se usará el iniciador de Python para ejecutar el archivo 1.6.4 Usando Python en un IDE También podemos usar un IDE como PyCharm para escribir y ejecutar nuestro Python programa. El mismo programa se muestra usando PyCharm a continuación: 1.6 Ejecutar programas de Python 9
En la figura anterior, el conjunto simple de comandos se enumera nuevamente en un archivo llamado hola.py. Sin embargo, el programa se ha ejecutado desde dentro del IDE y el la salida se muestra en una consola de salida en la parte inferior de la pantalla. 1.7 Recursos útiles Hay una amplia gama de recursos en la web para Python; destacaremos algunos aquí que usted debe marcar. No seguiremos haciendo referencia a estos para evitar repetición, pero puede volver a consultar esta sección siempre que lo necesite: • https://en.wikipedia.org/wiki/Python_Software_Foundation Pitón Software Base. • https://docs.python.org/3/ El sitio principal de documentación de Python 3. Contiene tutoriales, referencias de bibliotecas, guías de configuración e instalación, así como Python cómo-tos • https://docs.python.org/3/library/index.html Una lista de todas las funciones integradas para el lenguaje Python—aquí es donde puede encontrar documentación en línea para el varias clases y funciones que usaremos a lo largo de este libro. • https://pymotw.com/3/ el módulo Python 3 del sitio de la semana. Este sitio contiene muchos, muchos módulos de Python con breves ejemplos y explicaciones de lo que los módulos lo hacen. Un módulo de python es una biblioteca de características que se construyen y expanden 10 1 Introducción
el lenguaje central de Python. Por ejemplo, si estás interesado en construir juegos usando Python, entonces pajama es un módulo específicamente diseñado para hacer esto más fácil. • https://www.fullstackpython.com/email.html es a mensual Boletin informativo eso se enfoca en un solo tema de Python cada mes, como una nueva biblioteca o módulo. • http://www.pythonweekly.com/ es un resumen semanal gratuito de las últimas novedades de Python artículos, proyectos, videos y próximos eventos. 1.7 Recursos útiles 11
Capitulo 2 Configuración del entorno de Python 2.1 Introducción En este capítulo comprobaremos si tiene Python instalado en su computadora. Si no tiene Python instalado, seguiremos el proceso de instalación. Pitón. Esto es necesario porque cuando ejecuta un programa de Python busca el intérprete de python que se utiliza para ejecutar su programa o script. Sin el intérprete de python instalado en su máquina ¡Los programas de Python son solo archivos de texto! 2.2 Comprobar para ver si Python está instalado Lo primero que debe hacer es ver si Python 3 ya está instalado en su computadora. Primero verifique que no tenga Python instalado. si es tu no necesita hacer nada a menos que sea una versión muy antigua de Python 3 como 3.1 o 3.2. En una máquina con Windows, puede comprobar la versión instalada abriendo un ventana del símbolo del sistema (esto se puede hacer buscando Cmd en el ‘Tipo aquí para buscar’ cuadro en Windows 10). Una vez que la ventana de comandos esté abierta, intente escribir python. esto se ilustra abajo: © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_2 13
Tenga en cuenta que lo anterior ha probado tanto python como python3 en caso de que la última versión tenga se ha instalado con ese nombre. En un sistema como Mac, puede usar la Terminal y hacer lo mismo. Tú probablemente encontrará que al menos python (2) está preinstalado para usted. Por ejemplo, si escribe python en una Mac obtendrá algo como esto: Esto indica que el usuario anterior tiene instalada la versión 2.7.15 (tenga en cuenta que puede tener otra versión 2.x instalada). Sin embargo, tenga cuidado si descubre que tiene Python 2 instalado en su máquina; este libro se centra únicamente en Python 3. Si ha iniciado un intérprete de Python, entonces • Utilice quit() o exit() para salir del intérprete de Python; exit() es un alias para quit() y se proporciona para que Python sea más fácil de usar. Si Python 3 no estaba disponible, los siguientes pasos lo ayudarán a instalarlo. Si la versión correcta de Python ya está disponible en su computadora, entonces puede pasar al siguiente capítulo. 14 2 Configuración del entorno de Python
2.3 Instalación de Python en una PC con Windows Paso 1: Descarga de Python Python está disponible para una amplia gama de plataformas, desde Windows hasta Mac OS y linux; deberá asegurarse de descargar la versión para su sistema operativo sistema. Python se puede descargar desde el sitio web principal de Python, que se puede encontrar en http://www.python.org Como verá, el enlace ‘Descargas’ es el segundo desde la izquierda en la parte grande barra de menú debajo del campo de búsqueda. Haga clic en esto y será llevado a la página de descarga; la versión actual de Python 3 en el momento de escribir este artículo es Python 3.7 cual es la versión que vamos a descargar. Haga clic en el enlace Descargar Python. Para ejemplo, para una máquina con Windows verá: Esto descargará un instalador para su sistema operativo. Incluso si es más nuevo la versión de Python está disponible (lo cual es probable ya que la versión se actualiza silenciosamente frecuentemente) los pasos deben ser fundamentalmente los mismos. 2.3 Instalación de Python en una PC con Windows 15
Paso 2: ejecutar el instalador Ahora se le pedirá la ubicación para instalar Python, por ejemplo: Tenga en cuenta que es más fácil si hace clic en la opción ‘Agregar Python 3.7 a PATH’ como este hará que esté disponible desde la línea de comandos. Si no haces esto, no te preocupes, podemos agregar Python a PATH más adelante (la variable de entorno PATH es utilizada por Windows para encontrar la ubicación de programas como el intérprete de Python). A continuación, seleccione la opción ‘Instalar ahora’ y siga los pasos de instalación. Si todo salió como se esperaba, ahora debería ver un cuadro de diálogo de confirmación. como: Si está en Windows ahora, cierre cualquier ventana de comando que tenga abierta (la variable PATH no se actualiza para las ventanas de comandos existentes). Esto puede ser se hace escribiendo ‘salir’ o cerrando la ventana. dieciséis 2 Configuración del entorno de Python
Paso 3: Configure la RUTA (opcional) Si no seleccionó ‘Agregar Python 3.7 a PATH’ al comienzo de la instalación, ahora deberá configurar la variable de entorno PATH. Si lo hiciste, salta a el siguiente paso. Puede configurar la variable de entorno PATH utilizando el entorno del sistema editor de variables La forma más fácil de encontrar esto es escribir ’envir’ en el cuadro de búsqueda de Windows, luego enumerará todas las aplicaciones que coincidan con este patrón, incluido el ‘Editar el sistema editor de variables de entorno. Si ha iniciado sesión en su máquina como un usuario con Los derechos de ‘administrador’ usan el primero de la lista, si ha iniciado sesión como usuario sin administrador. derechos seleccione ‘Editar variables de entorno para su cuenta;’ opción. En el cuadro de diálogo resultante, seleccione el botón ‘Variables de entorno…’ en la final de la página: 2.3 Instalación de Python en una PC con Windows 17
En el siguiente cuadro de diálogo, seleccione la variable de entorno PATH y seleccione Editar: Ahora agregue las ubicaciones en las que instaló Python, de forma predeterminada, esto será algo como Tenga en cuenta que Python37-32 debe reemplazarse por la versión de Python que está instalando si es diferente El resultado final debería ser algo como: 18 2 Configuración del entorno de Python
Ahora haga clic en Aceptar hasta que se cierren todas las ventanas. Paso 4: Verificar la instalación A continuación, abra una nueva ventana del símbolo del sistema y escriba python como se muestra a continuación: ¡Felicitaciones, ha instalado Python y ha ejecutado el intérprete de Python! Paso 5: ejecuta algo de Python Ahora, en el indicador ‘»>’, escriba Tenga cuidado de asegurarse de usar todas las letras minúsculas para la función ‘imprimir’; Python distingue entre mayúsculas y minúsculas, lo que significa que, en lo que respecta a Python print(‘Hello World’) e Print(‘Hello World’) son completamente cosas diferentes. También asegúrese de tener comillas simples alrededor del texto Hello World este lo convierte en una cadena. Si lo hace bien, entonces en Windows debería ver: Ya ha ejecutado su primer programa en Python. En este caso, su programa impreso el mensaje ‘Hello World’ (un primer programa tradicional en la mayoría de programas) lenguas mezcladas). Paso 6: Salga del intérprete de Python Para salir del intérprete de Python, use exit() o Ctrl-Z más Retorno. 2.3 Instalación de Python en una PC con Windows 19
2.4 Configuración en una Mac Instalar Python en una Mac es similar a instalarlo en una máquina Windows en que puede descargar el instalador de Python para Apple Mac desde el software de Python Sitio web de la Fundación (https://www.python.org). Por supuesto que esta vez necesitas asegúrese de seleccionar la versión de Mac OS de la descarga como se muestra a continuación: Desde aquí puede seleccionar el instalador de macOS de 64 bits (asegúrese de seleccionar el apropiado para su versión del sistema operativo). Esto descargará un Manzana paquete eso poder ser instalado (él voluntad tener a nombre similar a python-3.7.2-macos10.9.pkg aunque el número de versión de Python y el sistema operativo Mac OS puede ser diferente). Deberá ejecutar este instalador. Cuando ejecute este instalador, el cuadro de diálogo del asistente del instalador de Python se abrirá como mostrado a continuación: 20 2 Configuración del entorno de Python
Recorra los cuadros de diálogo que le presenta el asistente de instalación aceptando cada opción hasta que comience la instalación. Una vez completada la instalación, estará presentó una pantalla de resumen que confirma que la instalación fue exitosa. Tú Ahora puede cerrar el instalador. Esto habrá creado una nueva carpeta en su carpeta Aplicaciones para Python 3.7. Tenga en cuenta que en una Mac que ya tiene Python 2 instalado (que es instalado por predeterminado), Python 3 se puede instalar junto con él y se puede acceder a él a través del comando python3 (como se muestra a continuación). Debe confirmar que Python ha sido instalado correctamente abriendo una ventana de Terminal e ingresando Python 3 REEMPLAZAR: Ahora, en el indicador ‘»>’, escriba Tenga cuidado de asegurarse de usar todas las letras minúsculas para la función ‘imprimir’; como Python distingue entre mayúsculas y minúsculas, lo que significa que, en lo que respecta a Python print(‘Hello World’) e Print(‘Hello World’) son completamente cosas diferentes. El resultado debe ser como se muestra a continuación: 2.4 Configuración en una Mac 21
Ahora puede salir de REPL usando exit() o quit(). 2.5 Recursos en línea Consulte la documentación de la biblioteca estándar de Python para: • https://docs.python.org/3/using/index.html con documentación para la configuración de Python y uso • https://docs.python.org/3/faq/windows.html Preguntas frecuentes sobre Python en Windows. • https://www.jetbrains.com/pycharm/ La página de inicio de PyCharm IDE. 22 2 Configuración del entorno de Python
Capítulo 3 Un primer programa de Python 3.1 Introducción En este capítulo volveremos al programa Hola Mundo y veremos qué es haciendo. También lo modificaremos para que sea más interactivo y exploraremos las concepto de variables de Python. 3.2 Hola Mundo Como se mencionó en el capítulo anterior, es tradicional iniciarse en una nueva lenguaje de programación con la escritura de un programa de estilo Hello World. Esto es muy útil ya que asegura que su entorno, que es el intérprete, cualquier entorno configuración, su editor (o IDE), etc. están configurados correctamente y pueden procesar (o compile) y ejecute (o ejecute) su programa. Como el programa ‘Hello World’ se trata de el programa más simple en cualquier idioma, lo estás haciendo sin las complejidades del lenguaje real que se está utilizando. Nuestro programa ‘Hello World’ ya ha sido presentado en el primer capítulo de este libro, sin embargo volveremos a él aquí y echaremos un vistazo más de cerca a lo que está pasando. En Python, la versión más simple del programa Hello World simplemente imprime un cadena con el mensaje de bienvenida: imprimir(‘Hola Mundo’) Puede utilizar cualquier editor de texto o IDE (Editor de desarrollo integrado) para crear un Archivo Python. Los ejemplos de editores comúnmente utilizados con Python incluyen Emacs, Vim, Notepad++, Sublime Text o Visual Studio Code; ejemplos de IDE para Python incluyen PyCharm y Eclipse. Usando cualquiera de estas herramientas podemos crear un archivo con un © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_3 23
extensión .py. Dicho archivo puede contener una o más sentencias de Python que representan un programa Python o Script. Por ejemplo, podemos crear un archivo llamado hola.py que contenga lo anterior función print() en él. Una pregunta que esto plantea es ¿de dónde viene la función print()? De hecho, print() es una función predefinida que se puede usar para imprimir cosas, por ejemplo al usuario. La salida se imprime realmente en lo que se conoce como flujo de salida. Esto maneja un flujo (secuencia) de datos como letras y números. Este flujo de datos de salida se puede enviar a una ventana de salida como el terminal en una Mac o Command Window en una PC con Windows. En este caso estamos imprimiendo el cadena ‘Hola mundo’. Por predefinido aquí queremos decir que está integrado en el entorno de Python y es entendido por el intérprete de Python. Esto significa que el intérprete sabe dónde para encontrar la definición de la función print() que le dice qué hacer cuando encuentra la función print(). Por supuesto, puede escribir sus propias funciones y veremos cómo hacerlo. que más adelante en este libro. La función print() en realidad intenta imprimir lo que le des, • cuando se le da una cadena, imprimirá una cadena, • si se le da un número entero como 42 imprimirá 42 y • si se trata de un número de punto flotante dado, como 23,56, imprimirá 23,56. Por lo tanto, cuando ejecutamos este programa, la cadena ‘Hello World’ se imprime en el ventana de la consola También tenga en cuenta que el texto que forma la cadena Hello World está envuelto dentro de dos caracteres de comillas simples; estos caracteres delimitan el inicio y el final de la cadena; si te pierdes uno de ellos entonces habrá un error. Para ejecutar el programa, si está utilizando un IDE como PyCharm, puede seleccione el archivo en el árbol de la izquierda y, con el botón derecho del ratón, seleccione Ejecutar. Si usted desea volver a ejecutar este programa, simplemente haga clic en la flecha verde en la herramienta barra en la parte superior del IDE. Si lo está ejecutando desde la línea de comando, escriba python seguido del nombre del archivo, por ejemplo:
python hola.py Esto debe hacerse en el directorio donde creó el archivo. 3.3 Hola mundo interactivo Hagamos nuestro programa un poco más interesante; hagamos que nos pregunte nuestro nombre y saludanos personalmente. 24 3 Un primer programa de Python
El programa actualizado es: imprimir(‘Hola, mundo’) nombre_usuario = input(‘Ingrese su nombre:’) print(‘Hola’, nombre_usuario) Ahora, después de imprimir la cadena original ‘Hello World’, el programa tiene dos declaraciones adicionales. El resultado de ejecutar este programa es: Hola Mundo Escribe tu nombre: Juan Hola John Examinaremos cada una de las nuevas declaraciones por separado. La primera afirmación es: nombre_usuario = input(‘Ingrese su nombre:’) Esta declaración hace varias cosas. Primero ejecuta otra función llamada aporte(). A esta función se le pasa una cadena, que se conoce como argumento, a utilícelo cuando solicite al usuario una entrada. Esta función input (), es nuevamente una función incorporada que es parte de Python idioma. En este caso, mostrará la cadena que proporcione como un mensaje para el usuario. y espere hasta que el usuario escriba algo seguido de la tecla de retorno. Lo que sea que el usuario escriba se devuelve como resultado de ejecutar input() función. En este caso, ese resultado se almacena en la variable nombre_usuario. Una variable es un área con nombre de la memoria de las computadoras que se puede usar para almacenar cosas (a menudo denominadas datos) como cadenas, números, valores booleanos como True/ Falso, etc. En este caso, la variable nombre_usuario actúa como una etiqueta para un área de memoria que contendrá la cadena ingresada por el usuario. La idea básica se ilustra en el siguiente diagrama: 3.3 Hola mundo interactivo 25
Esta imagen simplificada ilustra cómo una variable, como nombre_usuario, puede hacer referencia a un área de la memoria que contiene datos reales. En este diagrama la memoria es se muestra como una cuadrícula bidimensional de ubicaciones de memoria. Cada lugar tiene una dirección. asociado a ello. Esta dirección es única dentro de la memoria y se puede utilizar para volver a los datos guardados en esa ubicación. Esta dirección a menudo se conoce como la dirección de memoria de los datos. Es esta dirección de memoria la que se encuentra realmente en el variable nombre_usuario; esta es la razón por la cual la variable nombre_usuario se muestra apuntando al área de la memoria que contiene la cadena ‘John’. Así la variable nombre_usuario nos permite acceder fácilmente a esta área de memoria y convenientemente. Por ejemplo, si queremos hacernos con el nombre introducido por el usuario en otro declaración, podemos hacerlo simplemente haciendo referencia a la variable nombre_usuario. En efecto, esto es exactamente lo que hacemos en la segunda declaración que agregamos a nuestro programa. Esto es mostrado a continuación: imprimir(‘Hola’, nombre_usuario) La última declaración nuevamente usa la función print() incorporada, pero esta vez toma dos argumentos Esto se debe a que la función print() en realidad puede tomar una variable número de argumentos (elementos de datos que le pasamos). Cada argumento está separado por un coma. En este caso, hemos pasado la cadena ‘Hola’ y cualquier valor que sea referenciado por (presente en la dirección de memoria indicada por) la variable nombre_usuario. 3.4 Variables Quizás se pregunte por qué el elemento que contiene el nombre del usuario arriba se conoce como variable. Se llama variable porque el valor al que hace referencia en la memoria puede variar durante la vigencia del programa. Por ejemplo, podemos modificar nuestro programa Hello World para preguntar al usuario por el nombre de su mejor amigo e imprima un mensaje de bienvenida para ese mejor amigo. Si nosotros queremos, podemos reutilizar la variable para contener el nombre de ese mejor amigo. Por ejemplo: imprimir(‘Hola, mundo’) nombre = entrada (‘Ingrese su nombre:’) imprimir(‘Hola’, nombre) nombre = input(’¿Cómo se llama tu mejor amigo?: ‘) print(‘Hola mejor amigo’, nombre) Cuando ejecutamos esta versión del programa y el usuario ingresa ‘John’ para su nombre y ‘Denise’ para el nombre de sus mejores amigos, veremos: 26 3 Un primer programa de Python
Hola Mundo Escribe tu nombre: Juan Hola John ¿Cómo se llama tu mejor amiga: Denise? Hola mejor amiga Denise Como puede ver en esto, cuando se imprime la cadena ‘Hello Best Friend’, es el nombre ‘Denise’ que está impreso al lado. Esto se debe a que el área de memoria que anteriormente contenía la cadena ‘John’ ahora sostiene la cadena ‘Denise’. De hecho, en Python, el nombre de la variable no se limita a contener una cadena como ‘Juan’ y ‘Denise’; también puede contener otros tipos de datos, como números o el valores Verdadero y Falso. Por ejemplo: mi_variable = ‘Juan’ imprimir (mi_variable) mi_variable = 42 imprimir (mi_variable) mi_variable = Verdadero imprimir (mi_variable) El resultado de ejecutar el ejemplo anterior es John 42 Verdadero Como puede ver, my_variable primero contiene (o hace referencia al área de memoria contiene) la cadena ‘John’, luego contiene el número 42 y finalmente contiene el Valor booleano True (los valores booleanos solo pueden ser True o False). Esto se conoce en Python como escritura dinámica. Ese es el tipo de datos retenidos. por una variable puede cambiar dinámicamente a medida que se ejecuta el programa. Aunque esto puede parecer la forma obvia de hacer las cosas; no es el enfoque utilizado por muchos lenguajes de programación como Java y C# donde las variables son estáticamente mecanografiado La palabra estática se usa aquí para indicar que el tipo de datos que una variable puede contener se determinará cuando el programa se procese (o compile) por primera vez. Más adelante no será posible cambiar el tipo de datos que puede contener; pues si un variable es contener un número que luego no puede contener una cadena. este es el enfoque adoptado por lenguajes como Java y C#. Ambos enfoques tienen sus pros y sus contras; pero para muchas personas la flexibilidad de Las variables de Python son una de sus principales ventajas. 3.4 Variables 27
3.5 Convenciones de nombres Es posible que haya notado algo sobre algunos de los nombres de variables que tenemos introducido anteriormente, como nombre_usuario y mi_variable. Ambas variables Los nombres están formados por un conjunto de caracteres con un guión bajo entre las ‘palabras’ en el nombre de la variable. Ambos nombres de variables resaltan una convención de nomenclatura muy utilizada en Python, que es que los nombres de las variables deberían: • estar todo en minúsculas, • ser en general más descriptivo que los nombres de variables como a o b (aunque hay algunas excepciones, como el uso de las variables i y j en bucles construcciones). • con palabras individuales separadas por guiones bajos según sea necesario para mejorar legibilidad. Este último punto es muy importante ya que en Python (y la mayoría de los programas de programación de computadoras) idiomas) los espacios se tratan como separadores que se pueden usar para indicar dónde se una cosa acaba y otra empieza. Por lo tanto, no es posible definir un nombre de variable tal como: • nombre de usuario Como Python trata el espacio como un separador y, por lo tanto, Python piensa que usted es definiendo dos cosas ‘usuario’ y ’nombre’. Cuando crea sus propias variables, debe intentar nombrarlas siguiendo el Python aceptó el estilo, por lo tanto, nombre como: • mi_nombre, su_nombre, nombre_usuario, nombre_cuenta • recuento, número_total_de_usuarios, porcentaje_aprobado, aprobado_ tasa • dónde_vivimos, número_de_casa, • está_bien, es_correcto, status_flag son todos aceptables pero • A, Aaaaa, aaAAAaa • Minombre, miNombre, MiNombre o MINombre • DONDE VIVIMOS No cumple con las convenciones aceptadas. Sin embargo, vale la pena mencionar que estos simplemente se adhieren comúnmente a convenciones e incluso el mismo Python no siempre cumple con estas convenciones. Por lo tanto, si define un nombre de variable que no se ajusta a la convención de Python no se quejará. 28 3 Un primer programa de Python
3.6 Operador de asignación Un aspecto final de la declaración que se muestra a continuación aún no se ha considerado. nombre_usuario = input(‘Ingrese su nombre:’) ¿Qué es exactamente este ‘=’ entre la variable nombre_usuario y la entrada() ¿función? Se llama operador de asignación. Se utiliza para asignar el valor devuelto por el entrada de función () a la variable nombre_usuario. Es probablemente el más ampliamente Operador usado en Python. Por supuesto, no solo se usa para asignar valores de funciona como se ilustra en los ejemplos anteriores. Por ejemplo, también lo usamos cuando almacenó una cadena en una variable directamente: mi_variable = ‘Jason’ 3.7 Declaraciones de Python A lo largo de este capítulo hemos usado la frase enunciado para describir una parte de un programa de Python, por ejemplo, la siguiente línea de código es una declaración que imprime una cadena ‘Hola’ y el valor contenido en nombre_usuario. imprimir(‘Hola’, nombre_usuario) Entonces, ¿qué queremos decir con una declaración? En Python, una declaración es una instrucción que el intérprete de Python puede ejecutar. Esta declaración puede estar formada por una serie de elementos como el anterior que incluye una llamada a una función y una asignación de un valor a una variable. En muchos casos, una declaración es una sola línea en su programa, pero también es posible que una declaración se extienda por varias líneas particularmente si esto ayuda a la legibilidad o diseño del código. por ejemplo, el Lo siguiente es una declaración única, pero está distribuida en 6 líneas de código para que sea más fácil. leer: print(‘La población total de’, ciudad, ’era’, número_de_personas_en_la_ciudad, ’en’, año) Además de declaraciones, también hay expresiones. Una expresión es esencialmente una cálculo que genera un valor, por ejemplo: 3.6 Operador de asignación 29
4 + 5 Esta es una expresión que suma 4 y 5 y genera el valor 9. 3.8 Comentarios en el código Es una práctica común (aunque no universalmente) agregar comentarios al código para ayudar cualquiera que lea el código para entender qué hace el código, cuál era su intención, cualquier decisiones de diseño que tomó el programador, etc. Los comentarios son secciones de un programa que el intérprete de Python ignora. no son código ejecutable. Un comentario se indica con el carácter ‘#’ en Python. Cualquier cosa después de eso El intérprete ignorará el carácter hasta el final de la línea, ya que será se supone que es un comentario, por ejemplo:
Esto es un comentario
nombre = entrada (‘Ingrese su nombre:’)
Este es otro comentario
print(nombre) # este es un comentario al final de la línea En lo anterior, las dos líneas que comienzan con # son comentarios, son para nuestro ojos humanos solamente. Curiosamente, la línea que contiene la función print() también tiene un comentario—está bien ya que el comentario comienza con el # y se extiende hasta el final del línea, cualquier cosa antes del carácter # no es parte del comentario. 3.9 Guiones Versus Programas Python se puede ejecutar de varias maneras: • A través del intérprete de Python ingresando el REPL; la sesión interactiva de Python. • Haciendo que el intérprete de Python ejecute un archivo que contiene comandos de Python almacenados. • Configurando una asociación a nivel del sistema operativo para que cualquier archivo que terminan en .py siempre los ejecuta el intérprete de Python. • Indicando el intérprete de Python a utilizar al inicio del archivo de Python. Esto es se hace incluyendo una primera línea en un archivo con algo similar a ‘#!/usr/bin/ env python’. Esto indica que el resto del archivo debe pasarse al Intérprete de Python. Todos estos se pueden definir como una forma de ejecutar un programa de Python o, de hecho, un programa de Python. guion. Sin embargo, para los propósitos de este libro, trataremos un archivo que contiene una primera línea especificando el intérprete de Python para usar como script. Todo el resto del código de Python que 30 3 Un primer programa de Python
representa algún código para ejecutar para un propósito específico se llamará Python programa. Sin embargo, esto es realmente sólo una distinción que se hace aquí para simplificar terminología. 3.10 Recursos en línea Consulte la documentación de la biblioteca estándar de Python para: • https://docs.python.org/3/reference/simple_stmts.html Para información en declaraciones en Python. 3.11 Ejercicios En este punto, debería intentar escribir su propio programa en Python. Es probable más fácil de empezar modificando el programa Hola Mundo que ya hemos estado estudiando. Los siguientes pasos lo guiarán a través de esto:
- Si aún no ha ejecutado el programa Hello World, hágalo ahora. para ejecutar su programa tienes varias opciones. Lo más fácil si ha configurado un IDE como PyCharm es usar la opción de menú ’ejecutar’. De lo contrario, si ha configurado el Intérprete de Python en su computadora, puede ejecutarlo desde un símbolo del sistema (en Windows) o una ventana de Terminal (en una caja de Mac/Linux).
- Ahora asegúrese de que se siente cómodo con lo que realmente hace el programa. Intentar comentando algunas líneas—lo que sucede; ¿Ese es el comportamiento que esperabas? compruebe que está satisfecho con lo que hace.
- Una vez que haya hecho eso, modifique el programa con sus propias indicaciones al usuario (el argumento de cadena dado a la función de entrada). Asegúrese de que cada cadena está rodeado por los caracteres de comillas simples (’’); recuerda que estos indican el comienzo y final de una cadena.
- Intente crear sus propias variables y almacenar valores en ellas en lugar del variable nombre_usuario.
- Agregue una función de impresión () al programa con su propio aviso.
- Incluya una tarea que sume dos números (por ejemplo, 4 + 5) y luego asignar el resultado a una variable.
- Ahora imprima el valor de las variables una vez que se le haya asignado un valor.
- Asegúrese de que puede ejecutar el programa después de cada uno de los cambios anteriores. Si hay un error informado intenta solucionar ese problema antes de continuar. También debe tener cuidado con la sangría de su programa: Python es muy sensible a cómo se presenta el código y en este punto todas las declaraciones deben comenzar en el comienzo de la línea. 3.9 Guiones Versus Programas 31
Capítulo 4 Cadenas de pitón 4.1 Introducción En el capítulo anterior usamos cadenas varias veces, tanto como indicaciones para el usuario y como salida de la función print(). Incluso hicimos que el usuario escribiera su nombre y guárdelo en una variable que podría usarse para acceder a este nombre en un punto posterior en tiempo. En este capítulo exploraremos qué es una cadena y cómo se puede trabajar con y manipularlos. 4.2 ¿Qué son las cadenas? Durante la descripción del programa Hello World nos referimos a las cadenas de Python varias veces, pero ¿qué es una cadena? En Python, una cadena es una serie o secuencia de caracteres en orden. En esta definición nición de un carácter es cualquier cosa que pueda escribir en el teclado con una pulsación de tecla, como como una letra ‘a’, ‘b’, ‘c’ o un número ‘1’, ‘2’, ‘3’ o caracteres especiales como ‘', ‘[’, ‘$’, etc. un espacio es también un carácter ’ ‘, aunque no tiene una representación visible. También se debe tener en cuenta que las cadenas son inmutables. Inmutable significa que una vez que Se ha creado una cadena, no se puede cambiar. Si intenta cambiar una cadena, de hecho, cree una nueva cadena, que contenga las modificaciones que haya realizado, no afectará la cadena original de ninguna manera. En su mayor parte, puede ignorar este hecho pero significa que si intenta obtener una subcadena o dividir una cadena, debe recordar almacenar el resultado—lo veremos más adelante en este capítulo. Para definir el inicio y el final de una cadena, hemos utilizado el carácter de comilla simple ‘, por lo tanto, todas las siguientes son cadenas válidas: © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_4 33
• ‘Hola’ • ‘Hola Mundo’ • ‘Hola Andrea2000’ • ‘¡Ser o no ser, esa es la cuestión!’ También podemos definir una cadena vacía que no tenga caracteres (se define como una comilla simple seguida inmediatamente por una segunda comilla simple sin espacio entre a ellos). Esto se usa a menudo para inicializar o restablecer una variable que contiene una referencia a un cadena, por ejemplo • alguna_cadena = ’’ 4.3 Representando cadenas Como se indicó anteriormente; hemos usado comillas simples para definir el inicio y el final de una cadena, sin embargo, en Python se pueden usar comillas simples o dobles para definir una cadena, por lo tanto de los siguientes son válidos: • ‘Hola Mundo’ • “Hola Mundo” En Python estas formas son exactamente iguales, aunque por convención por defecto al uso de comillas simples. Este enfoque a menudo se conoce como más pitónico. (lo que implica que es más la convención utilizada por los programadores experimentados de Python) pero el lenguaje no lo impone. Sin embargo, debe tener en cuenta que no puede mezclar los dos estilos de inicio y final. cadenas, es decir, no puede comenzar una cadena con una comilla simple y terminar una cadena con una comillas dobles, por lo que las siguientes son ilegales en Python: • ‘Hello World’ # Esto es ilegal • ‘Hola mundo’ # Así es esto Sin embargo, la capacidad de usar tanto ’’ como ’ es útil si su cadena necesita contener uno de los otros tipos de delimitadores de cadena. Esto se debe a que una comilla simple puede estar incrustado en una cadena definida usando comillas dobles y viceversa, por lo que podemos escribe lo siguiente: imprimir(“Es el dia”) print(‘Dijo “hola” a todos’) La salida de estas dos líneas es: es el dia Ella dijo “hola” a todos Una tercera alternativa es el uso de comillas triples, que a primera vista puede parecer un poco difíciles de manejar, pero permiten que una cadena admita cadenas de varias líneas, por ejemplo: 34 4 Cadenas de pitón
z = """ Hola Mundo """ imprimir (z) que imprimirá Hola Mundo 4.4 ¿Qué tipo es una cadena? A menudo se dice que Python no tiene tipo; pero esto no es estrictamente cierto, como se dijo en un capítulo anterior es un lenguaje de tipo dinámico con todos los datos que tienen un tipo asociado. El tipo de un elemento de datos (como una cadena) determina lo que es legal hacer con los datos y cuál será el efecto de varias acciones. Por ejemplo, lo que el el efecto de usar un operador ‘+’ dependerá de los tipos involucrados; si ellos estan números, entonces el operador más los sumará; si sin embargo las cadenas son involucrados, entonces las cadenas se concatenarán (combinarán), etc. Es posible averiguar qué tipo tiene actualmente una variable utilizando el función tipo(). Esta función toma un nombre de variable y devolverá el tipo de los datos que contiene esa variable, por ejemplo: mi_variable = ‘Bob’ imprimir(tipo(mi_variable)) El resultado de ejecutar estas dos líneas de código es el resultado: <clase ‘str’> Esta es una forma abreviada de decir que lo que está contenido en my_variable es actualmente un clase (tipo) de cadena (en realidad cadena es una clase y Python admite ideas de Programación Orientada a Objetos tales como clases y los encontraremos más adelante en el libro). 4.5 ¿Qué puedes hacer con las cuerdas? En términos de Python, esto significa qué operaciones o funciones están disponibles o incorporado que puede usar para trabajar con cadenas. La respuesta es que hay muy muchos. Algunas de ellas se describen en esta sección. 4.3 Representando cadenas 35
4.5.1 Concatenación de cadenas Puede concatenar dos cadenas usando el operador ‘+’ (un operador es un operación o comportamiento que se puede aplicar a los tipos involucrados). Eso es lo que puedes tome una cadena y agréguela a otra cadena para crear una nueva tercera cadena: string_1 = ‘Bien’ cadena_2 = “día” cadena_3 = cadena_1 + cadena_2 imprimir (cadena_3) print(‘Hola’ + ‘Mundo’) La salida de esto es Buen día Hola Mundo Observe que la forma en que se define la cadena no importa aquí, string_1 usó comillas simples pero string_2 usó comillas dobles; sin embargo ellos ambos son solo cadenas. 4.5.2 Longitud de una cadena A veces puede ser útil saber cuánto dura una cadena, por ejemplo, si está poner una cadena en una interfaz de usuario, es posible que necesite saber cuánto de la cadena se mostrará dentro de un campo. Para averiguar la longitud de una cadena en Python, usa la función len(), por ejemplo: imprimir (largo (cadena_3)) Esto imprimirá la longitud de la cadena que actualmente tiene la variable string_3 (en términos del número de caracteres contenidos en la cadena). 4.5.3 Acceso a un personaje Como una cadena es una secuencia fija de letras, es posible usar corchetes y una índice (o posición) para recuperar un carácter específico dentro de una cadena. Por ejemplo: my_string = ‘Hola mundo’ imprimir (mi_cadena [4]) 36 4 Cadenas de pitón
Sin embargo, debe tener en cuenta que las cadenas están indexadas desde cero. Esto significa que el primer carácter está en la posición 0, el segundo en la posición 1, etc. Así, [4] indica que queremos obtener el quinto carácter de la cadena, que en este caso es la letra ‘o’. Esta forma de elementos de indexación es bastante común en pro- lenguajes de gramática y se refiere a una indexación basada en cero. 4.5.4 Acceso a un subconjunto de caracteres También es posible obtener un subconjunto de la cadena original, a menudo denominado subcadena (de la cadena original). Esto se puede hacer nuevamente usando los corchetes notación pero usando un ‘:’ para indicar los puntos inicial y final de la subcadena. si uno de las posiciones se omite, entonces se asume el inicio o el final de la cadena (dependiendo por omisión), por ejemplo: my_string = ‘Hola mundo’ print(my_string[4]) # caracteres en la posición 4 print(my_string[1:5]) # de la posición 1 a la 5 print(my_string[:5]) # desde el inicio hasta la posición 5 print(my_string[2:]) # desde la posición 2 hasta el final Generará o ello Hola hola mundo Como tal, my_string[1:5] devuelve la subcadena que contiene del 2 al 6 letras (que es ’ello’). A su vez, my_string[:5] devolvió la subcadena con- que contiene las letras 1 a 6 y my_string[2:] la subcadena que contiene el 3 a las últimas letras. 4.5.5 Cadenas repetitivas También podemos usar el operador ‘’ con cadenas. En el caso de cadenas esto significa repetir la cadena dada un cierto número de veces. Esto genera una nueva cadena que contiene la cadena original se repitió n número de veces. Por ejemplo: imprimir(’’ * 10) imprimir(‘Hola’ * 10) Generará
Hola Hola Hola Hola Hola Hola Hola Hola Hola 4.5 ¿Qué puedes hacer con las cuerdas? 37
4.5.6 Dividir cadenas Un requisito muy común es la necesidad de dividir una cadena en múltiples cadenas basadas en un carácter específico, como un espacio o una coma. Esto se puede hacer con la función split(), que toma una cadena para usar en identificar cómo dividir la cadena de recepción. Por ejemplo: title = ‘El bueno, el malo y el feo’ print(‘Cadena fuente:’, titulo) print(‘Dividir usando un espacio’) imprimir (título. dividir (’ ‘)) print(‘Dividir usando una coma’) imprimir (título. dividir (’,’)) Esto produce como salida Cadena fuente: Lo bueno, lo malo y lo feo Dividir usando un espacio [‘Lo bueno, lo malo y lo feo’] Dividir usando una coma [‘Lo bueno, lo malo y lo feo’] Como se puede ver a partir de esto, el resultado generado es una lista de cada palabra en el cadena o tres cadenas según lo definido por la coma. Es posible que haya notado algo extraño en la forma en que escribimos la llamada. a la operación de división. No pasamos la cadena a split() sino que usamos el formato de la variable que contiene la cadena seguida de ‘.’ y luego dividir(). Esto se debe a que split() es en realidad lo que se conoce como método. Lo haremos Volveremos a este concepto más cuando exploremos clases y objetos. Por el momento simplemente recuerde que los métodos se aplican a cosas como cadenas usando el punto notación. Por ejemplo, dado el siguiente código title = ‘El bueno, el malo y el feo’ imprimir (título. dividir (’ ‘)) Esto significa tomar la cadena contenida por el título de la variable y dividirla según el espacio de caracteres 4.5.7 Contando cadenas Es posible averiguar cuántas veces se repite una cadena en otra cadena. Este se hace usando la operación count() por ejemplo my_string = ‘Cuenta, el número de espacios’ print(“mi_cadena.cuenta(’ ‘):”, mi_cadena.cuenta(’ ‘)) 38 4 Cadenas de pitón
que tiene la salida mi_cadena.cuenta(’ ‘): 8 indicando que hay 8 espacios en la cadena original. 4.5.8 Sustitución de cuerdas Una cadena puede reemplazar una subcadena en otra cadena. Esto se hace usando el método replace() en una cadena. Por ejemplo: welcome_message = ‘¡Hola Mundo!’ print(mensaje_de_bienvenida.replace(“Hola”, “Adiós”)) La salida producida por esto es por lo tanto ¡Adiós mundo! 4.5.9 Encontrar subcadenas Puede averiguar si una cadena es una subcadena de otra cadena usando el método find() método. Este método toma una segunda cadena como parámetro y verifica si eso string está en la cadena que recibe el método find(), por ejemplo: cadena.buscar(cadena_a_buscar) El método devuelve −1 si la cadena no está presente. De lo contrario, devuelve un índice. indicando el comienzo de la subcadena. Por ejemplo imprimir(‘Edward Alun Rawlings’.find(‘Alun’)) Esto imprime el valor 5 (el índice de la primera letra de la subcadena ‘Alun’ nota las cadenas se indexan desde cero; as, la primera letra est en la posicin Cero, la segunda en posición uno, etc En contraste, la siguiente llamada al método find() imprime −1 ya que ‘Alun’ no es parte más larga de la cadena de destino: imprimir(‘Edward John Rawlings’.find(‘Alun’)) 4.5 ¿Qué puedes hacer con las cuerdas? 39
4.5.10 Convertir otros tipos en cadenas Si intenta utilizar el operador de concatenación ‘+’ con una cadena y algún otro tipo como un número, obtendrá un error. Por ejemplo, si intenta lo siguiente: msg = ‘Hola Lloyd eres ’ + 21 imprimir (mensaje) Recibirá un mensaje de error que indica que solo puede concatenar cadenas con cadenas no enteros con cadenas. Para concatenar un número como 21 con un string, debe convertirlo en una cadena. Esto se puede hacer usando la función str(). Este concurso cualquier tipo en una representación de cadena de ese tipo. Por ejemplo: msg = ‘Hola Lloyd eres ’ + str(21) imprimir (mensaje) Este fragmento de código imprimirá el mensaje: hola lloyd tienes 21 4.5.11 Comparando cadenas Para comparar una cadena con otra, puede usar la igualdad ‘==’ y ‘!=’ no es igual operadores. Estos compararán dos cadenas y devolverán Verdadero o Falso indicando si las cadenas son iguales o no. Por ejemplo: print(‘James’ == ‘James’) # imprime True print(‘James’ == ‘Juan’) # imprime Falso print(‘James’ != ‘Juan’) # imprime Verdadero Debe tener en cuenta que las cadenas en Python distinguen entre mayúsculas y minúsculas, por lo que la cadena ‘James’ no es igual a la cadena ‘james’. De este modo: print(‘James’ == ‘james’) # imprime Falso 4.5.12 Otras operaciones con cadenas De hecho, hay muchas operaciones diferentes disponibles para cadenas, incluyendo comprobar que una cadena comienza o termina con otra cadena, es decir, mayúsculas o minúsculas etc. También es posible reemplazar parte de una cadena con otra cadena, convertir cadenas mayúsculas, minúsculas o título, etc. 40 4 Cadenas de pitón
A continuación se dan ejemplos de estos (tenga en cuenta que todas estas operaciones usan el punto notación): alguna_cadena = ‘Hola mundo’ print(‘Probando una cadena’) imprimir(’-’ * 20) print(‘alguna_cadena’, alguna_cadena) print(“alguna_cadena.comienza con(‘H’)”, alguna_cadena.comienza con(‘H’)) print(“alguna_cadena.comienza con(‘h’)”, alguna_cadena.comienza con(‘h’)) print(“alguna_cadena.termina con(’d’)”, alguna_cadena.termina con(’d’)) print(‘alguna_cadena.istitle()’, alguna_cadena.istitle()) print(‘alguna_cadena.issuperior()’, alguna_cadena.issuperior()) print(‘alguna_cadena.islower()’, alguna_cadena.islower()) print(‘alguna_cadena.isalpha()’, alguna_cadena.isalpha()) print(‘Conversiones de cadenas’) imprimir(’-’ * 20) print(‘alguna_cadena.superior()’, alguna_cadena.superior()) print(‘alguna_cadena.inferior()’, alguna_cadena.inferior()) print(‘alguna_cadena.titulo()’, alguna_cadena.titulo()) print(‘alguna_cadena.swapcase()’, alguna_cadena.swapcase()) print(‘Cadena de espacios al principio y al final’, " xyz “.strip()) La salida de esto es Probando una cadena
some_string Hola mundo alguna_cadena.comienza con(‘H’) True some_string.startswith(‘h’) Falso some_string.endswith(’d’) True alguna_cadena.istitle() Verdadero alguna_cadena.isupper() Falso some_string.islower() Falso alguna_cadena.isalpha() Falso Conversiones de cadenas
some_string.upper() HOLA MUNDO some_string.lower() hola mundo some_string.title() Hola Mundo some_string.swapcase() HOLA MUNDO Cadena de espacios iniciales y finales xyz 4.5 ¿Qué puedes hacer con las cuerdas? 41
4.6 Sugerencias sobre cadenas 4.6.1 Las cadenas de Python distinguen entre mayúsculas y minúsculas En Python, la cadena ’l’ no es lo mismo que la cadena ‘L’; uno contiene la minúscula letra ’l’ y uno la letra mayúscula ‘L’. Si las mayúsculas y minúsculas no te importan entonces debe convertir cualquier cadena que desee comparar en un caso común antes de hacer cualquier prueba; por ejemplo usando lower() como en alguna_cadena.inferior().empieza con(‘h’) 4.6.2 Nombres de funciones/métodos Tenga mucho cuidado con las mayúsculas de los nombres de funciones/métodos; en pitón isupper() es una operación completamente diferente a isUpper(). Si usas el caso incorrecto Python no podrá encontrar la función o el método requerido y generar un mensaje de error. No te preocupes por la terminología con respecto a las funciones. y métodos en este punto, por ahora pueden ser tratados como la misma cosa y solo difieren en la forma en que recuerdan o invocan. 4.6.3 Invocaciones de función/método También tenga cuidado de incluir siempre los corchetes cuando llame a una función o método; incluso si no toma parámetros/argumentos. Hay una diferencia significativa entre isupper y isupper(). El primero es el nombre de una operación en un string mientras que el segundo es una llamada a esa operación para que la operación se ejecute. Ambos formatos son Python legales pero el resultado es muy diferente, por ejemplo: imprimir (alguna_cadena.essuperior) imprimir (alguna_cadena.isupper()) produce la salida: <el método incorporado es la parte superior del objeto str en 0x105eb19b0> FALSO Observe que la primera impresión le indica que se está refiriendo a la función integrada método llamado isupper definido en el tipo String; mientras que el segundo en realidad corre isupper() para usted y devuelve True o False. 42 4 Cadenas de pitón
4.7 Formato de cadena Python proporciona un sofisticado sistema de formato para cadenas que puede ser útil para imprimir información o registrar información de un programa. El sistema de formato de cadena utiliza una cadena especial conocida como cadena de formato que actúa como un patrón que define cómo se distribuirá la cadena final. Esta cadena de formato puede contienen marcadores de posición que se reemplazarán con valores reales cuando se creado. Se puede aplicar un conjunto de valores a la cadena de formato para llenar los marcadores de posición utilizando el método format(). El ejemplo más simple de una cadena de formato es uno que proporciona un único marcador de posición indicado por dos llaves (por ejemplo, {}). Por ejemplo, el siguiente es un formato cadena con el patrón ‘Hola’ seguido de un marcador de posición: format_string = ‘¡Hola {}!’ Esto se puede usar con el método de cadena format() para proporcionar un valor (o rellenar) el marcador de posición, por ejemplo: imprimir(formato_cadena.formato(‘Phoebe’)) La salida de esto es: ¡Hola Phoebe! Una cadena de formato puede tener cualquier número de marcadores de posición que deben completarse, por ejemplo ejemplo, el siguiente ejemplo tiene dos marcadores de posición que se completan al proporcionar dos valores al método format():
Permite múltiples valores para completar la cadena
nombre = “Adán” edad = 20 print(”{} tiene {} años".format(nombre, edad)) En este caso la salida es: Adán tiene 20 años. También ilustra que las variables se pueden usar para proporcionar los valores para el formato. método, así como los valores literales. Un valor literal es un valor fijo como 42 o el cadena ‘Juan’. De forma predeterminada, los valores están vinculados a los marcadores de posición en función del orden en que aparecen. se proporcionan al método format(); sin embargo, esto puede ser anulado por pro- proporcionando un índice al marcador de posición para decirle qué valor debe vincularse, por ejemplo: 4.7 Formato de cadena 43
Puede especificar un índice para la sustitución
format_string = “Hola {1} {0}, tienes {2}%” imprimir(formato_cadena.formato(‘Smith’, ‘Carol’, 75)) En este caso, la segunda cadena ‘Carol’ se vinculará al primer marcador de posición; tenga en cuenta que los parámetros están numerados de cero, no de uno. La salida del ejemplo anterior es: Hola Carol Smith, obtuviste el 75% Por supuesto, al ordenar los valores, es bastante fácil equivocarse en algo. porque un desarrollador podría pensar que las cadenas están indexadas desde 1 o simplemente porque equivocarse en el pedido. Un enfoque alternativo es utilizar valores con nombre para los marcadores de posición. En esto acercarse a los corchetes que rodean el nombre del valor que se va a sustituir, por ejemplo {artista}. Luego, en el método format(), se proporciona un par clave=valor donde la clave es el nombre en la cadena de formato; esto se muestra a continuación:
Puede usar sustituciones con nombre, el orden no es significativo
format_string = “{artista} cantó {canción} en {año}” print(format_string.format(artista=‘Paloma Faith’, canción=‘Culpable’, año=2017)) En este ejemplo, el orden ya no importa ya que el nombre asociado con el El parámetro pasado al método format() se usa para obtener el valor a ser sustituido En este caso la salida es: Paloma Faith cantó Culpable en 2017 También es posible indicar la alineación y el ancho dentro de la cadena de formato. Para ejemplo, si desea indicar un ancho a dejar para un marcador de posición cualquiera que sea el valor real proporcionado, puede hacerlo usando dos puntos (’:’) seguidos del ancho para usar. Por ejemplo, para especificar un espacio de 25 caracteres que se puede llenar con un valor de sustitución que puede utilizar {: 25} como se muestra a continuación: print(’|{:25}|’.format(‘ancho de 25 caracteres’)) En lo anterior, las barras verticales simplemente se utilizan para indicar dónde se encuentra la cadena. comienza y termina como referencia, no tienen significado dentro del método de formato. Este produce la salida: |25 caracteres de ancho | 44 4 Cadenas de pitón
Dentro de este espacio también puede indicar una alineación donde: • < Indica alineación a la izquierda (la predeterminada), • > Indica alineación correcta, • ^ Indica centrado. Estos siguen a los dos puntos (’:’) y vienen antes del tamaño del espacio a usar, por ejemplo: print(’|{:<25}|’.format(‘alineado a la izquierda’)) # El valor predeterminado print(’|{:>25}|’.format(‘alineado a la derecha’)) imprimir(’|{:^25}|’.formato(‘centrado’)) Que produce: |alineado a la izquierda | | alineado a la derecha | | centrado | Otra opción de formato es indicar que un número debe formatearse con separadores (como una coma) para indicar miles:
Puede formatear números con coma como separador de miles
imprimir(’{:,}’.formato(1234567890)) imprimir(’{:,}’.formato(1234567890.0)) Lo que genera la salida: 1.234.567.890 1,234,567,890.0 De hecho, hay numerosas opciones disponibles para controlar el diseño de un valor dentro de la cadena de formato y se debe hacer referencia a la documentación de Python para más información. 4.8 Plantillas de cadena Una alternativa al formato de cadena es usar plantillas de cadena. Éstas eran introducido en Python 2.4 como una solución más simple y menos propensa a errores para la mayoría de las cadenas requisitos de formato. Una plantilla de cadena es una clase (tipo de cosa) que se crea a través de la cadena. Función de plantilla (). La plantilla contiene una o más variables con nombre pre- cedido con un símbolo $. La plantilla se puede utilizar con un conjunto de valores que reemplace las variables de plantilla con valores reales. 4.7 Formato de cadena 45
Por ejemplo: cadena de importación
Inicializar la plantilla con ¢variables que
se sustituirá por valores reales
template = string.Template(’$artista cantó $canción en $año’) Tenga en cuenta que es necesario incluir una declaración de importación al inicio del programa ya que las plantillas no se proporcionan de forma predeterminada en Python; deben cargarse desde un biblioteca de funciones de cadena adicionales. Esta biblioteca es parte de Python pero necesita dígale a Python que desea acceder a estas funciones de cadenas adicionales. Volveremos a la declaración de importación más adelante en el libro; por ahora solo excepto que es necesario para acceder a la funcionalidad Plantilla. La plantilla en sí se crea a través de la función string.Template(). El la cadena pasada a la función string.Template() puede contener cualquier carácter más las variables de la plantilla (que se indican con el carácter $ seguido del nombre de la variable como $artista arriba). Lo anterior es, por lo tanto, una plantilla para el patrón ‘algún artista cantó alguna canción en algún año’. Los valores reales se pueden sustituir en la plantilla utilizando el sustituto () función. La función de sustitución toma un conjunto de pares clave=valor, en los que la clave es el nombre de la variable de plantilla (menos el carácter $ inicial) y el valor es el valor a utilizar en la cadena. print(template.substitute(artist=‘Freddie Mercury’, song=‘The Gran pretendiente’, año=1987)) En este ejemplo, $artist será reemplazado por ‘Freddie Mercury’, $song por ‘The Great Pretender’ y $year en 1987. La función de sustitución devolverá entonces un nueva cadena que contiene ‘Freddie Mercury cantó El gran pretendiente en 1987’ Esto se ilustra en el siguiente código: cadena de importación
Inicializar la plantilla con $variables que
se sustituirá por valores reales
template = string.Template(’$artista cantó $canción en $año’)
Reemplazar / sustituir variables de plantilla con valores reales
Puede usar un par clave = valor donde la clave es el nombre de
la variable de plantilla y el valor es el valor a usar
en la cadena
print(template.substitute(artist=‘Freddie Mercury’, song=‘The Gran pretendiente’, año=1987)) 46 4 Cadenas de pitón
Esto produce: Freddie Mercury cantó El gran pretendiente en 1987 Por supuesto, podemos reutilizar la plantilla sustituyendo la plantilla por otros valores. variables, cada vez que llamamos al método replace() generará una nueva cadena con las variables de plantilla reemplazadas con los valores apropiados: print(template.substitute(artista=‘Ed Sheeran’, canción=‘Galway Chica’, año=2017)) print(template.substitute(artista=‘Camila Cabello’, cancion=‘La Habana’, año=2018)) Con lo anterior produciendo: Ed Sheeran cantó Galway Girl en 2017 Camila Cabello cantó La Habana en 2018 Alternativamente, puede crear lo que se conoce como un diccionario. Un diccionario es un estructura compuesta por pares clave:valor en los que la clave es única. Esto permite un estructura de datos que se va a crear que contiene los valores a utilizar y luego se aplica a la función de sustitución: d = dict(artista = ‘Billy Idol’, canción=‘Ojos sin rostro’, año = 1984) imprimir (plantilla.sustituir (d)) Esto produce una nueva cadena: Billy Idol cantó Eyes Without a Face en 1984 Discutiremos los diccionarios con mayor detalle más adelante en el libro. Plantilla instrumentos de cuerda poder contener plantilla Variables usando el formato $nombre-de-variable; sin embargo, hay algunas variaciones que vale la pena señalar: • $$ le permite incluir un carácter ‘$’ en la cadena sin que Python interprete como el comienzo de una variable de plantilla, este doble ‘$$’ se reemplaza con un solo $. Esto se conoce como escapar de un carácter de control. • ${template_variable} es equivalente a $template_variable. Es requerido cuando los caracteres de identificación válidos siguen al marcador de posición pero no son parte del marcador de posición, como ‘’${noun}ificación’’. Otro punto a tener en cuenta sobre la función template.substitute() es que si Si no proporciona un valor a todas las variables de la plantilla, aparecerá un error. generado. Por ejemplo: print(template.substitute(artista=‘David Bowie’, canción=‘Rebelde Rebelde’)) 4.8 Plantillas de cadena 47
Dará como resultado que el programa no se ejecute y se genere un mensaje de error. borrado: Rastreo (llamadas recientes más última): Archivo “/emplate_examples.py”, línea 16, en <módulo> print(template.substitute(artista=‘David Bowie’, canción=‘Rebelde Rebelde’)) Archivo “/Biblioteca/Frameworks/Python.framework/Versions/3.7/lib/ python3.7/string.py”, línea 132, en sustitución return self.pattern.sub(convertir, self.template) Archivo “/Biblioteca/Frameworks/Python.framework/Versions/3.7/lib/ python3.7/string.py”, línea 125, en convertir return str(mapeo[nombrado]) KeyError: ‘año’ Esto se debe a que no se ha proporcionado un valor a la variable de plantilla $año. Si no quiere tener que preocuparse por proporcionar todas las variables en un plantilla con un valor, entonces debería usar la función safe_substitute(): print(template.safe_substitute(artista=‘David Bowie’, canción = ‘Rebelde Rebelde’)) Esto llenará las variables de plantilla proporcionadas y dejará cualquier otra plantilla variables que se incorporarán en la cadena tal como son, por ejemplo: David Bowie cantó Rebel Rebel en $year 4.9 Recursos en línea Hay una gran cantidad de documentación en línea disponible sobre cadenas en Python incluido: • https://docs.python.org/3/library/string.html que presenta una cadena común operaciones. • https://docs.python.org/3/library/stdtypes.html#text-sequence-type-str este pro- proporciona información sobre cadenas y la clase str en Python. • https://pyformat.info tiene una introducción simple al formato de cadena de Python. • https://docs.python.org/3/library/string.html#format-string-syntax cual pre- envía documentación detallada sobre el formato de cadena de Python. • https://docs.python.org/3/library/string.html#template-strings para documentación en plantillas de cadena. 48 4 Cadenas de pitón
4.10 Ejercicios Vamos a probar algunas de las operaciones relacionadas con cadenas.
- Explore la sustitución de una cadena Cree una cadena con palabras separadas por ‘,’ y reemplace las comas con espacios; por ejemplo, reemplace todas las comas en ‘Denyse,Marie,Smith,21,London,UK’ con espacios Ahora imprime la cadena resultante.
- Manejar la entrada del usuario El objetivo de este ejercicio es escribir un programa para pedirle al usuario dos cadenas y concatenarlos juntos, con un espacio entre ellos y almacenarlos en un nuevo variable llamada nueva_cadena. Próximo: • Imprime el valor de new_string. • Imprime cuánto mide el contenido de new_string. • Ahora convierta el contenido de new_string a mayúsculas. • Ahora verifique si nueva_cadena contiene la cadena ‘Albus’ como una subcadena. 4.10 Ejercicios 49
Capítulo 5 Números, Booleanos y Ninguno 5.1 Introducción En este capítulo exploraremos las diferentes formas en que se pueden representar los números. por los tipos incorporados en Python. También presentaremos el tipo booleano utilizado para representar Verdadero y Falso. Como parte de esta discusión, también veremos ambos Operadores numéricos y de asignación en Python. Concluiremos presentando la valor especial conocido como Ninguno. 5.2 Tipos de números Hay tres tipos que se usan para representar números en Python; estos son números enteros (o integrales), números de coma flotante y números complejos. Esto plantea la pregunta ¿por qué? Por qué tienen diferentes formas de representar los números; después de todo, los humanos pueden trabajar fácilmente con el número 4 y el número 4.0 y no necesitan enfoques completamente diferentes para escribirlos (aparte del ‘.’, por supuesto). En realidad, esto se reduce a la eficiencia en términos tanto de la cantidad de memoria necesaria para representar un número y la cantidad de potencia de procesamiento necesaria para trabajar con ese numero En esencia, los números enteros son más fáciles de trabajar y pueden ocupar menos memoria que los números reales. Los números enteros son números enteros que no necesitan tener un elemento fraccionario. Cuando se suman, multiplican o restan dos números enteros, generar siempre otro número entero. En Python, los números reales se representan como números de punto flotante (o flotantes). Estos pueden contener una parte fraccionaria (el bit después del punto decimal). Escaneo de computador mejor trabajo con números enteros (en realidad, por supuesto, solo realmente 1 y 0). Ellos por lo tanto necesitan una forma de representar un punto flotante o un número real. Típicamente esto involucra representando los dígitos antes y después del punto decimal. © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_5 51
El término punto flotante se deriva del hecho de que no hay un número fijo de dígitos antes o después del punto decimal; es decir, el punto decimal puede flotar. Operaciones con números de punto flotante como suma, resta, multiplicación etc. generará nuevos números reales que también deben ser representados. también es mucho más difícil asegurar que los resultados sean correctos como potencialmente muy pequeños y muy grandes las partes fraccionarias pueden estar involucradas. De hecho, la mayoría de los números de punto flotante son en realidad representadas como aproximaciones. Esto significa que uno de los desafíos en el manejo números de coma flotante es asegurar que las aproximaciones conduzcan a resultados razonables. resultados. Si esto no se hace adecuadamente, pequeñas discrepancias en las aproximaciones puede crecer como una bola de nieve hasta el punto en que los resultados finales pierden sentido. Como resultado, la mayoría de los lenguajes de programación de computadoras tratan los números enteros como 4 como siendo diferente de los números reales como 4.000000004. Los números complejos son una extensión de los números reales en los que todos los números son expresada como la suma de una parte real y una parte imaginaria. los numeros imaginarios son reales múltiplos de la unidad imaginaria (la raíz cuadrada de −1), donde la parte imaginaria es a menudo se escribe en matemáticas usando una ‘i’ mientras que en ingeniería a menudo se escribe usando una ‘j’. Python tiene soporte incorporado para números complejos, que se escriben usando el notación de ingeniería; es decir, la parte imaginaria se escribe con un sufijo j, p. 3 + 1j. 5.3 enteros Todos los valores enteros, sin importar cuán grandes o pequeños, están representados por la integral (o int) escriba en Python 3. Por ejemplo: X = 1 imprimir (x) imprimir (tipo (x)) x = 1000000000000000000000000000000000000000000000000000000000001 imprimir (x) imprimir (tipo (x)) Si se ejecuta este código, la salida mostrará que ambos números son de tipo int: 1 <clase ‘int’> 1000000000000000000000000000000000000000000000000000000000001 <clase ‘int’> Esto hace que sea muy fácil trabajar con números enteros en Python. A diferencia de algunos Los lenguajes de programación como C# y Java tienen diferentes tipos de enteros dependiendo en el tamaño del número, los números pequeños tienen que ser convertidos en tipos más grandes en algunas situaciones 52 5 Números, Booleanos y Ninguno
5.3.1 Convertir a enteros Es posible convertir otro tipo en un número entero usando la función int(). Para ejemplo, si queremos convertir una cadena en un int (asumiendo que la cadena contiene un número entero), entonces podemos hacer esto usando la función int(). Por ejemplo total = int(‘100’) Esto puede ser útil cuando se usa con la función input(). La función input() siempre devuelve una cadena. Si queremos pedirle al usuario que ingrese un número entero, entonces tendremos que convertir la cadena devuelta desde el función input() en un int. Podemos hacer esto ajustando la llamada a la entrada () en una llamada a la función int(), por ejemplo: edad = int(input(‘Ingrese su edad:’)) imprimir (tipo (edad)) imprimir (edad) Ejecutar esto da: Por favor ingrese su edad: 21 <clase ‘int’> 21 La función int() también se puede usar para convertir un número de punto flotante en un int, por ejemplo: yo = int(1.0) 5.4 Números de punto flotante Los números reales, o números de punto flotante, se representan en Python usando el IEEE 754 formato de número de punto flotante binario de precisión doble; en su mayor parte lo haces No necesita saber esto, pero es algo que puede buscar y leer si desear. El tipo utilizado para representar un número de punto flotante se llama flotante. Python representa números de punto flotante usando un punto decimal para separar los parte entera de la parte fraccionaria del número, por ejemplo: tipo_de_cambio = 1,83 imprimir (tasa_de_cambio) imprimir (tipo (tipo de cambio)) 5.3 enteros 53
Esto produce una salida que indica que estamos almacenando el número 1.83 como un número flotante. número de punto: 1.83 <clase ‘flotador’> 5.4.1 Conversión a flotadores Al igual que con los números enteros, es posible convertir otros tipos, como un int o una cadena, en un flotar Esto se hace usando la función float(): valor_int = 1 cadena_valor = ‘1.5’ float_value = float(int_value) print(‘valor int como float:’, float_value) imprimir (tipo (valor_flotante)) float_value = float(string_value) print(‘valor de cadena como un flotante:’, float_value) imprimir (tipo (valor_flotante)) El resultado de este fragmento de código es: valor int como flotante: 1.0 <clase ‘flotador’> valor de cadena como un flotante: 1.5 <clase ‘flotador’> 5.4.2 Convertir una cadena de entrada en un punto flotante Número Como hemos visto, la función input() devuelve una cadena; que pasa si queremos el usuario para ingresar un número de coma flotante (o real)? Como hemos visto anteriormente, una cadena se puede convertir en un número de punto flotante usando la función float() y por lo tanto, podemos usar este enfoque para convertir una entrada del usuario en un flotante: tipo_de_cambio = float(input(“Ingrese el tipo de cambio para usar: “)) imprimir (tasa_de_cambio) imprimir (tipo (tipo de cambio)) Usando esto, podemos ingresar la cadena 1.83 y convertirla en un número de punto flotante: Por favor, introduzca el tipo de cambio a utilizar: 1,83 1.83 <clase ‘flotador’> 54 5 Números, Booleanos y Ninguno
5.5 Números complejos Los números complejos son el tercer tipo de tipo numérico incorporado de Python. Un complejo número está definido por una parte real y una parte imaginaria y tiene la forma a + bi (donde i es la parte imaginaria y a y b son números reales): La parte real del número (a) es el número real que se suma al puro número imaginario. La parte imaginaria del número, o b, es el número real coeficiente del número puro número imaginario. La letra ‘j’ se usa en Python para representar la parte imaginaria del número, por ejemplo: c1 = 1j c2 = 2j imprimir(‘c1:’, c1, ‘, c2:’, c2) imprimir (tipo (c1)) imprimir (c1.real) imprimir (c1.imagen) Podemos ejecutar este código y la salida será: c1: 1j , c2: 2j <clase ‘complejo’> 0.0 1.0 Como puede ver, el tipo de número es ‘complejo’ y cuando el número es impreso directamente se hace imprimiendo juntas las partes real e imaginaria. No se preocupe si esto es confuso; es poco probable que necesite usar complejos números a menos que esté haciendo una codificación muy específica, por ejemplo dentro de un campo científico. 5.6 Valores booleanos Python admite otro tipo llamado Boolean; un tipo booleano solo puede ser uno de Verdadero o Falso (y nada más). Tenga en cuenta que estos valores son verdaderos (con mayúscula) T) y Falso (con F mayúscula); verdadero y falso en Python no son lo mismo y no tienen significado por sí mismos. 5.5 Números complejos 55
El equivalente de la clase int o float para booleanos es bool. El siguiente ejemplo ilustra el almacenamiento de dos valores booleanos en una variable todo bien: all_ok = Verdadero imprimir (todo_ok) all_ok = Falso imprimir (todo_ok) imprimir (escribir (todo_ok)) La salida de esto es Verdadero FALSO <clase ‘bool’> El tipo booleano es en realidad un subtipo de entero (pero con solo los valores Verdadero y Falso) por lo que es fácil traducir entre los dos, usando las funciones int() y bool() para convertir booleanos a enteros y viceversa. Para ejemplo: imprimir (int (verdadero)) imprimir (int (falso)) imprimir(bool(1)) imprimir (bool (0)) que produce 1 0 Verdadero FALSO También puede convertir cadenas en booleanos siempre que las cadenas contengan Verdadero o Falso (y nada más). Por ejemplo: estado = bool(input(‘OK continuar: ‘)) imprimir (estado) imprimir (tipo (estado)) Cuando ejecutamos esto Aceptar para continuar: Verdadero Verdadero <clase ‘bool’> 56 5 Números, Booleanos y Ninguno
5.7 Operadores aritméticos Los operadores aritméticos se utilizan para realizar algún tipo de operación matemática como como suma, resta, multiplicación y división, etc. En Python se representan enviado por uno o dos personajes. La siguiente tabla resume el Python operadores aritméticos: Operador Descripción Ejemplo + Sume los valores izquierdo y derecho juntos 1 + 2 − Resta el valor de la derecha del valor de la izquierda 3 - 2 * Múltiple los valores izquierdo y derecho 3 * 4 / Divide el valor de la izquierda por el valor de la derecha 12/3 // División entera (ignorar cualquier resto) 12//3 % Módulo (también conocido como el operador de resto): solo devuelve cualquier resto 13%3 ** Operador exponente (o potencia de): con el valor de la izquierda elevado a la poder del derecho 3 ** 4 5.7.1 Operaciones con enteros Se pueden sumar dos números enteros usando +, por ejemplo 10 + 5. A su vez, dos los números enteros se pueden restar (10 − 5) y multiplicar (10 * 4). Operaciones como +, − y * entre enteros siempre producen resultados enteros. Esto se ilustra a continuación: casa = 10 lejos = 15 imprimir (local + visitante) imprimir (escribir (casa + fuera)) imprimir (10 * 4) imprimir (tipo (10 * 4)) goles_por = 10 goles_contra = 7 print(goles_a favor - goles_en contra) imprimir (tipo (objetivos_a favor - objetivos_en contra)) La salida de esto es 25 <clase ‘int’> 40 <clase ‘int’> 3 <clase ‘int’> 5.7 Operadores aritméticos 57
Sin embargo, puede notar que nos hemos perdido la división con respecto a números enteros, ¿por qué es esto? Es porque depende de qué operador de división uses como a cuál es realmente el tipo devuelto. Por ejemplo, si dividimos el entero 100 por 20, entonces el resultado que podrías esperar razonablemente producir podría ser 5; pero no lo es, en realidad es 5.0: imprimir (100 / 20) imprimir (tipo (100 / 20)) la salida es 5.0 <clase ‘flotador’> Y como puede ver a partir de esto, el tipo de resultado es flotante (es decir, flotante). número de punto). Entonces porqué es este el caso? La respuesta es que la división no sabe si los dos enteros involucrados se dividen entre sí exactamente o no (es decir, hay un resto). Por lo tanto, por defecto a producir un número de coma flotante (o real) que puede tener una parte fraccionaria. Este es por supuesto necesario en algunas situaciones, por ejemplo si dividimos 3 por 2: res1 = 3/2 imprimir (res1) imprimir (tipo (res1)) En este caso 3 no se puede dividir exactamente por 2, podríamos decir que 2 cabe en 3 una vez con resto. Esto es lo que muestra Python: 1.5 <clase ‘flotador’> El resultado es que 2 cabe en 3, 1,5 veces y el tipo de resultado es un flotar Si solo está interesado en la cantidad de veces que 2 entra en 3 y está feliz para ignorar la parte fraccionaria, entonces hay una versión alternativa del operador de división //. Este operador se conoce como operador de división de enteros: res1 = 3//2 imprimir (res1) imprimir (tipo (res1)) que produce 1 <clase ‘int’> Pero, ¿qué sucede si solo está interesado en la parte restante de una división, el número entero operador de división ha perdido eso? Bueno, en ese caso puedes usar la operación de módulo (’%’). Este operador devuelve el resto de una operación de división: por ejemplo: 58 5 Números, Booleanos y Ninguno
print(‘División de módulo 4 % 2:’, 4 % 2) print(‘División de módulo 3 % 2:’, 3 % 2) Que produce: Módulo división 4 % 2: 0 Módulo división 3 % 2: 1 Un último operador entero que veremos es el operador de potencia que se puede usar para elevar un número entero a una potencia dada, por ejemplo 5 a la potencia de 3. La potencia operador es ‘**’, esto se ilustra a continuación: un = 5 segundo = 3 imprimir (a ** b) Lo que genera el número 125. 5.7.2 División de números enteros negativos También vale la pena explorar lo que sucede en la división entera y verdadera cuando intervienen números negativos. Por ejemplo, print(‘División verdadera 3/2:’, 3/2) print(‘División verdadera 3//2:’, -3 / 2) print(‘División entera 3//2:’, 3 // 2) print(‘División entera 3//2:’, -3 // 2) La salida de esto es: División verdadera 3/2: 1.5 Verdadera división 3//2: -1.5 División entera 3//2: 1 División entera 3//2: -2 Los primeros tres de estos podrían ser exactamente lo que espera dado nuestro anterior discusión; sin embargo, el resultado del último ejemplo puede parecer un poco sorprendente, ¿por qué ¿3//2 genera 1 pero −3//2 genera −2? La respuesta es que Python siempre redondea el resultado de la división de enteros hacia menos infinito (que es el número negativo más pequeño posible). Esto significa que tira el resultado de la división entera al menor número posible, 1 es menor que 1,5 pero −2 es menor que −1,5. 5.7 Operadores aritméticos 59
5.7.3 Operadores de números de punto flotante También tenemos las operaciones de múltiplo, resta, suma y división disponibles para flotante números de puntos. Todos estos operadores producen nuevos números de coma flotante: imprimir (2.3 + 1.5) imprimir (1.5 / 2.3) imprimir (1.5 * 2.3) imprimir (2.3 - 1.5) imprimir (1.5 - 2.3) Estas declaraciones producen el resultado que se muestra a continuación: 3.8 0.6521739130434783 3.4499999999999997 0.7999999999999998 -0.7999999999999998 5.7.4 Operaciones con enteros y punto flotante Cualquier operación que involucre números enteros y números de coma flotante siempre producir un número de coma flotante. Es decir, si uno de los lados de una operación como sumar, restar, dividir o multiplicar es un número de punto flotante, entonces el resultado será un número de punto flotante. Por ejemplo, dado el entero 3 y el punto flotante número 0.1, si los multiplicamos juntos, obtenemos un número de coma flotante: yo = 3 * 0.1 imprimir (yo) Ejecutando esto obtenemos 0.30000000000000004 Que puede o no haber sido lo que esperabas (podrías haber esperado 0,3); sin embargo, esto destaca el comentario al comienzo de este capítulo relacionado con números de coma flotante (o reales) que se representan como una aproximación dentro de un sistema informático. Si esto fuera parte de un cálculo mayor (como el cálculo de la cantidad de interés a pagar sobre un préstamo muy grande durante un período de 10 años), entonces el el resultado final bien podría estar fuera por una cantidad significativa. Es posible superar este problema utilizando uno de los módulos (o bibliotecas) de Pythons. Por ejemplo, el módulo decimal proporciona la clase Decimal que apro- maneje con cuidado la multiplicación de 3 y 0,1. 60 5 Números, Booleanos y Ninguno
5.7.5 Operadores de números complejos Por supuesto, puede usar operadores como multiplicar, sumar, restar y dividir con números complejos. Por ejemplo: c1 = 1j c2 = 2j c3 = c1 * c2 imprimir (c3) Podemos ejecutar este código y la salida será: (-2+0j) También puede convertir otro número o una cadena en un número complejo usando la función compleja(). Por ejemplo: complex(1) # genera (1+0j) Además, el módulo de matemáticas proporciona funciones matemáticas para complejas números. 5.8 Operadores de Asignación En el cap. 3 presentamos brevemente el operador de asignación (’=’) que se utilizó para asignar un valor a una variable. De hecho, hay varios operadores de asignación diferentes que podría usarse con valores numéricos. Estos operadores de asignación en realidad se conocen como operadores compuestos como combinan una operación numérica (como sumar) con la asignación operador. Por ejemplo, el operador compuesto += es una combinación de la suma operador y el operador = tal que x = 0 x += 1 # tiene el mismo comportamiento que x = x + 1 A algunos desarrolladores les gusta usar estos operadores compuestos porque son más concisos. para escribir y puede ser interpretado de manera más eficiente por el intérprete de Python. La siguiente tabla proporciona una lista de los operadores compuestos disponibles 5.7 Operadores aritméticos 61
Operador Descripción Ejemplo Equivalente += Agregue el valor a la variable de la izquierda x + = 2 x = x + 2 −= Restar el valor de la variable de la izquierda x−= 2 x = x – 2 *= Multiplique la variable de la izquierda por el valor x * = 2 x = x * 2 /= Divide el valor de la variable por el valor de la derecha x/= 2 x = x/2 //= Use la división de enteros para dividir el valor de la variable por el valor de la mano derecha x //= 2 x = x//2 %= Utilice el operador de módulo (resto) para aplicar el valor de la derecha a la variable x %= 2 x = x % 2 = Aplicar la potencia del operador para elevar la variable valor por el valor ofrecido x= 3 x = x ** 3 5.9 Ninguno Valor Python tiene un tipo especial, el NoneType, con un solo valor, None. Esto se usa para representar valores nulos o la nada. No es lo mismo que False, o una cadena vacía o 0; es un no-valor. Puede ser se usa cuando necesita crear una variable pero no tiene un valor inicial para ella. Para ejemplo: ganador = Ninguno Luego puede probar la presencia de Ninguno usando ’es’ y ’no es’, por ejemplo: imprimir (el ganador es Ninguno) Esto imprimirá Verdadero si y solo si la variable ganador está configurada actualmente en Ninguno. Alternativamente, también puede escribir: imprimir (el ganador no es Ninguno) Que imprimirá Verdadero solo si el valor del ganador no es Ninguno. Se dan varios ejemplos usando el valor Ninguno y los operadores ’es’ y no es’ abajo: ganador = Ninguno imprimir(‘ganador:’, ganador) print(’el ganador es Ninguno:’, el ganador es Ninguno) print(’el ganador no es Ninguno:’, el ganador no es Ninguno) imprimir (tipo (ganador)) print(‘Establecer ganador en Verdadero’) ganador = Verdadero imprimir(‘ganador:’, ganador) print(’el ganador es Ninguno:’, el ganador es Ninguno) print(’el ganador no es Ninguno:’, el ganador no es Ninguno) imprimir (tipo (ganador)) 62 5 Números, Booleanos y Ninguno
El resultado de este fragmento de código es: ganador: ninguno el ganador es Ninguno: Cierto el ganador no es Ninguno: Falso <clase ‘NingunoTipo’> Establecer ganador en Verdadero ganador: cierto el ganador es Ninguno: Falso el ganador no es Ninguno: Cierto <clase ‘bool’> 5.10 Recursos en línea Consulte la documentación de la biblioteca estándar de Python para: • https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex Tipos numéricos. • https://docs.python.org/3/library/stdtypes.html#truth-value-testing Boolean val- ues y pruebas booleanas. • https://docs.python.org/3/library/decimal.html que proporciona información sobre el módulo decimal de Python. • https://docs.python.org/3/library/cmath.html cual discute matemático Funciones para números complejos. Si está interesado en cómo se representan los números de coma flotante, entonces una buena los puntos de partida son: • https://en.wikipedia.org/wiki/Double-precision_floating-point_format cual proporciona una descripción general de la representación de coma flotante. • https://en.wikipedia.org/wiki/IEEE_754 que es la página de Wikipedia en el IEEE 754 Formato de número de punto flotante de precisión doble. 5.11 Ejercicios El objetivo de los ejercicios de este capítulo es explorar los tipos numéricos que tenemos estado mirando 5.9 Ninguno Valor 63
5.11.1 ejercicio general Intente explorar los diferentes tipos de números disponibles en Python. Debería probar los diferentes operadores numéricos disponibles y mezclar los números que se utilizan, por ejemplo, 1 y también 1.0, etc. Comprueba que los resultados que obtienes son los que esperas. 5.11.2 Convertir Kilómetros a Millas El objetivo de este ejercicio es escribir un programa para convertir una distancia en Kilómetros en una distancia en millas.
- Tome la entrada del usuario para una distancia determinada en kilómetros. Esto puede hacerse utilizando la función de entrada().
- Convierta el valor devuelto por la función input() de una cadena en un entero utilizando la función int().
- Ahora convierta este valor en millas; esto se puede hacer dividiendo los kilómetros por 0.6214
- Imprima un mensaje que le diga al usuario cuántos kilómetros son en millas. 64 5 Números, Booleanos y Ninguno
Capítulo 6 Flujo de control usando sentencias If 6.1 Introducción En este capítulo vamos a ver la sentencia if en Python. Esta declaración es Se utiliza para controlar el flujo de ejecución dentro de un programa basado en alguna condición. Estas condiciones representan algún punto de elección que se evaluará como Verdadero o FALSO. Para realizar esta evaluación es común utilizar un operador de comparación (por ejemplo para comprobar si la temperatura es superior a algún umbral). En muchos casos estas comparaciones deben tener en cuenta varios valores y en estos Se pueden utilizar operadores lógicos de situaciones para combinar dos o más operaciones de comparación. expresiones juntas. Este capítulo introduce primero los operadores lógicos y de comparación antes de discutir la instrucción if en sí misma. 6.2 Operadores de comparación Antes de explorar las declaraciones if, necesitamos discutir los operadores de comparación. Estos son operadores que devuelven valores booleanos. Son clave para los elementos condicionales. de flujo de sentencias de control como si. Un operador de comparación es un operador que realiza algún tipo de prueba y devuelve Verdadero o falso. Estos son operadores que usamos en la vida cotidiana todo el tiempo. Por ejemplo, ¿yo tengo suficiente dinero para comprar el almuerzo, o este zapato es de mi talla, etc. © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_6 sesenta y cinco
En Python hay una variedad de operadores de comparación representados típicamente por uno o dos personajes. Estos son: Operador Descripción Ejemplo
Comprueba si dos valores son iguales 3 == 3 != Comprueba que dos valores no son iguales entre sí 2 != 3 < Pruebas para ver si el valor de la mano izquierda es menor que el valor de la mano derecha 2 < 3
Comprueba si el valor de la izquierda es mayor que el valor de la derecha 3 > 2 <= Comprueba si el valor de la izquierda es menor o igual que el valor de la derecha 3 <= 4
= Comprueba si el valor de la izquierda es mayor o igual que el valor de la derecha 5 >= 4 6.3 Operadores logicos Además de los operadores de comparación, Python también tiene operadores lógicos. Los operadores lógicos se pueden utilizar para combinar expresiones booleanas. Por lo general, se usan con operadores de comparación para crear relaciones más complejas. condiciones Nuevamente, los usamos todos los días, por ejemplo, podríamos considerar si podemos permitirnos un helado y si cenaremos pronto, etc. Hay tres operadores lógicos en Python, estos se enumeran a continuación: Operador Descripción Ejemplo y Devuelve True si tanto la izquierda como la derecha son verdaderas (3 < 4) y (5 > 4) o Devuelve dos si la izquierda o la derecha es tregua (3 < 4) o (3 > 5) no Devuelve verdadero si el valor que se está probando es falso no 3 < 2 6.4 La declaración si Una declaración if se usa como una forma de programación condicional; algo que tu probablemente lo haga todos los días en el mundo real. Es decir, debe decidir si vas a tomar té o café o decidir si vas a tener tostadas o un muffin para desayuno, etc. En cada uno de estos casos, está haciendo una elección, generalmente basada en alguna información como que tomé café ayer, así que tomaré té hoy. En Python, tales elecciones se representan programáticamente mediante la condición if declaración. En esta construcción, si alguna condición es verdadera, se realiza alguna acción, opcionalmente si no es cierto que se pueda realizar alguna otra acción en su lugar. 66 6 Flujo de control usando sentencias If
6.4.1 Trabajar con una instrucción If En su forma más básica, la instrucción if es if <condición-evaluando-a-booleano>: declaración Tenga en cuenta que la condición debe evaluarse como Verdadero o Falso (o un equivalente (ver más adelante en este capítulo). Si la condición es verdadera, ejecutaremos el declaración con sangría. Tenga en cuenta que la sangría es muy importante en Python; de hecho, el diseño del código es muy, muy importante en Python. La sangría se utiliza para determinar cómo una pieza de El código debe estar asociado con otra parte del código. Veamos un ejemplo sencillo, numero = int(input(‘Ingrese un numero: ‘)) si numero < 0: imprimir(num, ’es negativo’) En este ejemplo, el usuario ha ingresado un número; si es menor que cero un mensaje teniendo en cuenta que esto se imprimirá para el usuario. Si el número es positivo; entonces nada sera producción. Por ejemplo, Introduzca un número: -1 -1 es negativo Si deseamos ejecutar múltiples sentencias cuando nuestra condición es Verdadera, podemos sangrar varias líneas; de hecho, todas las líneas sangradas al mismo nivel después del if automáticamente formará parte de la declaración if. Por ejemplo; num = int(input(‘Ingrese otro numero: ‘)) si numero > 0: imprimir(num, ’es positivo’) print(num, ‘al cuadrado es’, num * num) imprimir(‘adiós’) Si ahora ejecutamos este programa e ingresamos 2, veremos Introduce otro número: 2 2 es positivo 2 al cuadrado es 4 Adiós 6.4 La declaración si 67
Sin embargo, si ingresamos el valor −1 entonces obtenemos Introduzca otro número: -1 Adiós Tenga en cuenta que ninguna de las líneas sangradas se ejecutó. Esto se debe a que las dos líneas sangradas están asociadas con la instrucción if y solo se ejecutará si la condición booleana evalúa (devuelve) True. Sin embargo, la sentencia print(‘Bye’) no forma parte de la sentencia if; es simplemente el siguiente sentencia a ejecutar después de la sentencia if (y su estado print() asociado) mentos) han terminado. 6.4.2 Else en una instrucción If También podemos definir una parte else de una sentencia if; este es un elemento opcional que se puede ejecutar si la parte condicional de la declaración if devuelve False. Por ejemplo: num = int(input(‘Ingrese otro número más: ‘)) si numero < 0: imprimir(‘Es negativo’) demás: print(‘No es negativo’) Ahora, cuando se ejecuta este código, si el número ingresado es menor que cero, entonces el La primera declaración de impresión () se ejecutará de lo contrario (de lo contrario) la segunda declaración de impresión () se ejecutará. Sin embargo, estamos garantizados que al menos uno (y como máximo uno) de se ejecutarán las sentencias print(). Por ejemplo, en la ejecución 1 si ingresamos el valor 1: Introduce otro número más: 1 no es negativo Y en la corrida 2 si ingresamos el valor −1: Introduzca otro número más: -1 es negativo 6.4.3 El uso de elif En algunos casos, puede haber varias condiciones que desee probar, con cada condición siendo probado si el anterior falló. Este escenario else-if es compatible con Python por el elemento elif de una sentencia if. 68 6 Flujo de control usando sentencias If
El elemento elif de una declaración if sigue a la parte if y viene antes de cualquier (opcional) otra parte. Tiene el formato: elif <condición-evaluando-a-booleano>: declaración Por ejemplo ahorros = float(input(“Ingrese cuanto tiene ahorrado: “)) si ahorro == 0: print(“Lo siento, no hay ahorros”) ahorro elif < 500: imprimir(‘Bien hecho’) ahorro elif < 1000: print(‘Esa es una suma ordenada’) ahorro elif < 10000: imprimir(’¡Bienvenido señor!’) demás: imprimir(‘Gracias’) Si ejecutamos esto: Ingresa cuanto tienes ahorrado: 500 Esa es una suma ordenada Aquí podemos ver que la primera condición if falló (ya que el ahorro no es igual a 0). Sin embargo, el próximo elif también debe haber regresado Falso ya que los ahorros fueron mayores. de 500. De hecho, fue la segunda declaración elif la que devolvió True y, por lo tanto, la Se ejecutó la declaración de impresión asociada (‘Esa es una suma ordenada’). Teniendo ejecutó esta declaración, la declaración if luego terminó (el resto de elif y otras partes fueron ignoradas). 6.5 Anidación de sentencias If Es posible anidar una sentencia if dentro de otra. Este término anidamiento indica que una declaración if se encuentra dentro de parte de la otra declaración if y puede ser se utiliza para refinar el comportamiento condicional del programa. A continuación se muestra un ejemplo. Tenga en cuenta que permite que se realice algún comportamiento. antes y después de ejecutar/ejecutar la instrucción if anidada. También tenga en cuenta que la sangría es clave aquí, ya que es cómo funciona Python si las declaraciones if están anidadas o no. 6.4 La declaración si 69
nevando = Cierto temperatura = -1 si temperatura < 0: imprimir(‘Hace mucho frio’) si nieva: print(‘Ponte las botas’) print(‘Hora del chocolate caliente’) imprimir(‘adiós’) En este ejemplo, si la temperatura es menor que cero, ingresaremos el si bloque de código. Si no es menor que cero, omitiremos la instrucción if completa y salta a la declaración print(‘Bye’) que está después de ambas declaraciones If. En este caso, la temperatura se establece en −1, por lo que ingresaremos la instrucción If. Nosotros luego imprimirá la cadena ‘Se está congelando’. En este punto, otra declaración if es anidado dentro de la primera instrucción if. Ahora se hará una verificación para ver si es nevando Tenga en cuenta que nevar ya es un valor booleano y también lo será True o False e ilustra que aquí se puede usar un valor booleano por sí solo. Como está nevando, imprimiremos ‘Ponte las botas’. Sin embargo, la declaración impresa ‘Time for Hot Chocolate’ no es parte del anidado si. Es parte del if externo (aquí es donde la sangría es importante). Si usted quería que solo se imprimiera si estaba nevando, entonces debe sangrarse al mismo nivel que la primera declaración en el bloque if anidado, por ejemplo: nevando = Cierto temperatura = -1 si temperatura < 0: imprimir(‘Hace mucho frio’) si nieva: print(‘Ponte las botas’) print(‘Hora del chocolate caliente’) imprimir(‘adiós’) Esto ahora cambia el if interno (o anidado) para tener dos declaraciones de impresión asociado a ello. Esto puede parecer sutil, pero es clave en cómo Python usa el diseño para vincularse. declaraciones individuales. 6.6 si las expresiones Una expresión if es una forma abreviada de una declaración if que devuelve un valor. En De hecho, la diferencia entre una expresión y una declaración en un lenguaje de programación el calibre es solo eso; las expresiones devuelven un valor; las declaraciones no. 70 6 Flujo de control usando sentencias If
Es bastante común querer asignar un valor específico a una variable dependiente de
alguna condición. Por ejemplo, si deseamos decidir si alguien es adolescente o no
entonces podríamos verificar si tienen más de 12 años y menos de 20. Podríamos escribir esto como:
edad = 15
estado = Ninguno
si (edad > 12) y edad < 20:
estado = ‘adolescente’
demás:
estado = ’no adolescente’
imprimir (estado)
Si ejecutamos esto, obtenemos la cadena ‘adolescente’ impresa.
Sin embargo, esto es bastante largo y puede que no sea obvio que la verdadera intención de este
El código era asignar un valor apropiado al estado.
Una alternativa es una expresión if. El formato de una expresión if es
6.8 Sugerencias Una cosa con la que hay que tener mucho cuidado en Python es el diseño. A diferencia de lenguajes como Java y C#, el diseño de su programa es parte de su programa. Determina cómo se asocian las declaraciones entre sí y cómo el flujo de los elementos de control, como las sentencias if, afectan a qué sentencias se ejecutan. Además, tenga cuidado con la instrucción if y su uso del carácter ‘:’. Este carácter es clave para separar la parte condicional de la instrucción if de la sentencias que se ejecutarán dependiendo de si la condición es Verdadera o FALSO. 6.9 Recursos en línea Consulte la documentación de la biblioteca estándar de Python para: • https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not Operaciones Booleanas. • https://docs.python.org/3/library/stdtypes.html#comparaciones Comparación operadores. • https://docs.python.org/3/tutorial/controlflow.html el flujo de Python en línea de tutorial de controles 6.10 Ejercicios Hay tres ejercicios diferentes en esta sección, puede seleccionar cuál interesado en o hacer los tres. 6.10.1 Verifique que la entrada sea positiva o negativa El objetivo de este ejercicio es escribir un pequeño programa para probar si un número entero es positivo o negativo. Su programa debe:
- Pida al usuario que ingrese un número (utilice la función input()). puedes asumir que la entrada será algún tipo de número.
- Convierta la cadena en un número entero usando la función int().
- Ahora comprueba si el número entero es un número positivo o negativo.
- También puede agregar una prueba para ver si el número es cero. 72 6 Flujo de control usando sentencias If
6.10.2 Prueba si un número es par o impar Los ejercicios requieren que escribas un programa para recibir información del usuario y determinar si el número es par o impar. Una vez más, puede suponer que el usuario introduzca un número entero válido. Imprima un mensaje para el usuario para informarle el resultado. Para probar si un número es par puedes usar (núm % 2) == 0 Que devolverá True si el número es par (tenga en cuenta que los paréntesis son opcionales pero que sea más fácil de leer). 6.10.3 Convertidor de kilómetros a millas En este ejercicio, debe volver al convertidor de kilómetros a millas que escribió en el último capítulo. Agregaremos varias pruebas nuevas a su programa:
- Modifique su programa para que verifique que el usuario ha ingresado un positivo distancia (es decir, no pueden ingresar un número negativo).
- Ahora modifique su programa para verificar que la entrada sea un número; si no es un número, entonces no hagas nada; de lo contrario, convierta la distancia a millas. Para verificar si una cadena contiene solo dígitos, use el método isnumeric() por ejemplo ‘42’.isnumeric(); que devuelve True si la cadena solo contiene contiene números. Tenga en cuenta que este método solo funciona para números enteros positivos; pero esto es suficiente suficiente para este ejemplo. 6.10 Ejercicios 73
Capítulo 7 Iteración/bucle 7.1 Introducción En esta sección veremos el bucle while y el bucle for disponibles en Python. Estos bucles se utilizan para controlar la ejecución repetida de sentencias seleccionadas. 7.2 Mientras bucle El bucle while existe en casi todos los lenguajes de programación y se utiliza para iterativo (o repetir) una o más declaraciones de código siempre que la condición de prueba (expresión) sea Verdadero. Esta construcción de iteración generalmente se usa cuando la cantidad de veces que necesitamos repetir el bloque de código a ejecutar no se conoce. Por ejemplo, puede que tenga que repetir hasta que se encuentra alguna solución o el usuario ingresa un valor particular. El comportamiento del bucle while se ilustra a continuación. © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_7 75
El ciclo while de Python tiene la forma básica: while <la condición-de-prueba-es-verdadera>: declaración o declaraciones Como se muestra en el diagrama y se puede inferir del código; mientras la prueba condición/expresión es verdadera, entonces la declaración o el bloque de declaraciones en el while se ejecutará el bucle. Tenga en cuenta que la prueba se realiza antes de cada iteración, incluida la primera iteración; por lo tanto, si la condición falla la primera vez en el ciclo, la declaración o el bloque de sentencias nunca se puede ejecutar en absoluto. Al igual que con la declaración if, la sangría es clave aquí ya que las declaraciones incluidas en la instrucción while está determinada por la sangría. Cada declaración que está sangrada al mismo nivel después de que la condición while sea parte del ciclo while. Sin embargo, tan pronto como una instrucción ya no siga al bloque while; Entonces eso ya no es parte del ciclo while y su ejecución ya no está bajo el control de la condición de prueba. A continuación se ilustra un ejemplo de ciclo while en Python: cuenta = 0 imprimir(‘Iniciando’) mientras cuenta < 10: print(count, ’ ‘, end=’’) # parte del ciclo while count += 1 # también forma parte del ciclo while print() # no es parte del ciclo while imprimir(‘Terminado’) En este ejemplo, mientras alguna variable cuenta es menor que el valor 10, mientras el bucle continuará iterando (se repetirá). El bucle while en sí contiene dos declaraciones; uno imprime el valor de la variable de conteo mientras que el otro incrementa contar (recuerde contar +=1 es equivalente a contar = contar + 1). Hemos usado la versión de print() que no imprime un retorno de carro cuando imprime un valor (esto se indica mediante la opción end=’’ pasada a print() función). El resultado de ejecutar este ejemplo es: A partir de 0 1 2 3 4 5 6 7 8 9 Hecho Como puede ver, las declaraciones que imprimen los mensajes de inicio y finalización son solo corre una vez. Sin embargo, la declaración que imprime la variable de conteo se ejecuta 10 veces (imprimiendo los valores 0–9). Una vez que el valor de count es igual a 10, el ciclo termina (o termina). Tenga en cuenta que necesitábamos inicializar la variable de conteo antes del ciclo. Esto es porque necesita tener un valor para la primera iteración del ciclo while. Eso es 76 7 Iteración/bucle
antes de que el ciclo while haga algo, el programa necesita saber el primero valor de count para que pueda realizar esa primera prueba. Esta es una característica del tiempo comportamiento de los bucles. 7.3 En bucle En muchos casos sabemos cuántas veces queremos iterar sobre uno o más declaraciones (como de hecho hicimos en la sección anterior). Aunque el ciclo while se puede usar para tales situaciones, el ciclo for es una forma mucho más concisa de hacerlo. Él por lo general, también es más claro para otro programador que el ciclo debe iterar para un programa específico. número de iteraciones. El bucle for se utiliza para pasar una variable a través de una serie de valores hasta que se se cumple la prueba. El comportamiento del bucle for se ilustra a continuación. Este diagrama de flujo muestra que alguna secuencia de valores (por ejemplo, todos los números enteros) valores entre 0 y 9) se usarán para iterar sobre un bloque de código para procesar. Cuando se haya llegado al último elemento de la secuencia, el ciclo terminará. Muchos lenguajes tienen un bucle for de la forma: para i = de 0 a 10 declaración o declaraciones En este caso una variable ‘i’ tomaría los valores 0, 1, 2, 3 etc. hasta 10. En Python, el enfoque es ligeramente diferente ya que los valores de 0 a 10 son representado por un rango. Esta es en realidad una función que generará el rango de valores que se utilizarán como secuencia en el bucle for. Esto se debe a que Python para loop es muy flexible y puede recorrer no solo un rango de valores enteros sino también un conjunto de valores contenidos en estructuras de datos, como una lista de enteros o cadenas. Lo haremos 7.2 Mientras bucle 77
volver a esta función del bucle for cuando miramos colecciones/contenedores de datos
en un capítulo posterior.
El formato del bucle for de Python cuando se usa un rango de valores es
para
Bucle sobre un conjunto de valores en un rango
print(‘Imprimir valores en un rango’) para i en el rango (0, 10): imprimir(i, ’ ‘, fin=’’) imprimir() imprimir(‘Terminado’) Cuando ejecutamos este código, la salida es: Imprimir valores en un rango 0 1 2 3 4 5 6 7 8 9 Hecho Como se puede ver en lo anterior; el resultado final es que hemos generado un for bucle que produce el mismo conjunto de valores que el bucle while anterior. Sin embargo, • el código es más conciso, • está claro que estamos procesando un rango de valores de 0 a 9 (tenga en cuenta que depende de pero sin incluir el valor final) y • no necesitábamos definir primero la variable de bucle. Por estas razones, los bucles for son más comunes en los programas en general que los while. bucles Sin embargo, una cosa que puede notar es que en el bucle while no estamos limitado a incrementar la variable de conteo en uno (acabamos de hacer este). Por ejemplo, podríamos haber decidido incrementar la cuenta en 2 cada vez alrededor del bucle (una idea muy común). De hecho, range nos permite hacer exactamente esto; a El tercer argumento que se puede proporcionar a la función de rango es el valor a incrementar. la variable de bucle cada vez que se redondea el bucle, por ejemplo:
Ahora use valores en un rango pero incremente en 2
print(‘Imprimir valores en un rango con un incremento de 2’) para i en el rango (0, 10, 2): imprimir(i, ’ ‘, fin=’’) imprimir() imprimir(‘Terminado’) 78 7 Iteración/bucle
Cuando ejecutamos este código, la salida es Imprime valores en un rango con un incremento de 2 0 2 4 6 8 Hecho Por lo tanto, el valor de la variable de bucle ha saltado en 2 a partir de 0. Una vez que su valor era 10 o más, entonces el ciclo termina. Por supuesto, no es sólo el valor 2 lo que podría utilizar; podríamos incrementar por cualquier número entero significativo, 3, 4 o 5, etc. Una variación interesante del ciclo for es el uso de un comodín (un ‘_’) en su lugar de una variable de bucle; esto puede ser útil si solo está interesado en hacer un bucle cierto número de veces y no en el valor del propio contador de bucle, por ejemplo:
Ahora use una variable de bucle ‘anónima’
para _ en el rango (0,10): imprimir(’.’, fin=’’) imprimir() En este caso no nos interesan los valores generados por el rango per se solo en bucle 10 veces por lo que no hay ningún beneficio en el registro de la variable de bucle. El lazo La variable está representada por el carácter de subrayado (’_’). Tenga en cuenta que, de hecho, esta es una variable válida y se puede hacer referencia a ella dentro del bucle; sin embargo, por convención se trata como anónimo. 7.4 Declaración de ruptura de bucle Python permite a los programadores decidir si quieren salir de un bucle temprano o no (ya sea que estemos usando un bucle for o un bucle while). Esto se hace usando la sentencia de ruptura. La instrucción break permite que un desarrollador altere el ciclo normal del bucle. basado en algunos criterios que pueden no ser predecibles de antemano (por ejemplo, puede basarse en alguna entrada del usuario). La instrucción break, cuando se ejecuta, terminará el ciclo actual y saltará el programa a la primera línea después del ciclo. El siguiente diagrama muestra cómo este funciona para un bucle for: 7.3 En bucle 79
Por lo general, se coloca una declaración de guardia (si la declaración) en el descanso para que el La instrucción break se aplica condicionalmente cuando corresponde. Esto se muestra a continuación para una aplicación simple donde se le pide al usuario que ingrese un número que puede o no estar en el rango definido por el bucle for. Si el el número está en el rango, luego haremos un bucle hasta que se alcance este valor y luego romperemos el bucle (que termina antes de tiempo sin procesar el resto de los valores en el bucle): print(‘Solo imprime el código si se completaron todas las iteraciones’) num = int(input(‘Ingrese un número para verificar: ‘)) para i en el rango (0, 6): si i == numero: romper imprimir(i, ’ ‘, fin=’’) imprimir(‘Terminado’) Si ejecutamos esto e ingresamos el valor 7 (que está fuera del rango), entonces todos los Los valores en el bucle deben imprimirse: Ingrese un número para verificar: 7 0 1 2 3 4 5 Listo Sin embargo, si ingresamos el valor 3, solo se imprimirán los valores 0, 1 y 2 antes de que el ciclo se interrumpa (termina) antes de tiempo: Ingrese un número para verificar: 3 0 1 2 Listo Tenga en cuenta que la cadena ‘Done’ se imprime en ambos casos, ya que es la primera línea después de el bucle for que no tiene sangría y, por lo tanto, no forma parte del bucle for. 80 7 Iteración/bucle
Tenga en cuenta que la declaración de ruptura puede venir en cualquier lugar dentro del bloque de código asociado con la construcción del bucle (ya sea un bucle for o un bucle while). Esto significa que puede haber declaraciones antes y después. 7.5 Continuar declaración de bucle La declaración de continuar también afecta el flujo de control dentro del ciclo. construcciones para y mientras. Sin embargo, no termina todo el ciclo; más bien solo termina la iteración actual alrededor del bucle. Esto le permite pasar por alto parte de la iteración de un bucle para un valor particular, pero luego continuar con el valores restantes en la secuencia. Se puede usar un guardián (sentencia if) para determinar cuándo continúa el estado- debe ejecutarse. Al igual que con la sentencia break, la sentencia continue puede venir en cualquier lugar dentro del cuerpo de la construcción de bucle. Esto significa que usted puede tener algunos declaraciones que se ejecutarán para cada valor en la secuencia y algunas que son solo se ejecuta cuando no se ejecuta la instrucción continuar. Esto se muestra a continuación. En este programa, la instrucción continuar se ejecuta solo para números impares y, por lo tanto, las dos declaraciones de impresión () solo se ejecutan si el valor de i es par: para i en el rango (0, 10): imprimir(i, ’ ‘, fin=’’) si yo % 2 == 1: continuar print(‘oye, es un número par’) print(’nos encantan los números pares’) imprimir(‘Terminado’) Cuando ejecutamos este código obtenemos 7.4 Declaración de ruptura de bucle 81
0 oye es un numero par amamos los números pares 1 2 hey es un número par amamos los números pares 3 4 hey es un numero par amamos los números pares 5 6 hey es un numero par amamos los números pares 7 8 hey es un numero par amamos los números pares 9 Listo Como puede ver, solo imprimimos los mensajes sobre un número par cuando los valores son 0, 2, 4, 6 y 8. 7.6 For Loop con Else Un bucle for puede tener un bloque else opcional al final del bucle. la otra parte se ejecuta si y solo si se procesan todos los elementos de la secuencia. El bucle for puede no puede procesar todos los elementos en el ciclo si por alguna razón ocurre un error en su programa (por ejemplo, si tiene un error de sintaxis) o si rompe el bucle. Aquí hay un ejemplo de un bucle for con una parte else:
Solo imprime el código si todas las iteraciones se completaron en una lista
print(‘Solo imprime el código si se completaron todas las iteraciones’) num = int(input(‘Ingrese un número para verificar: ‘)) para i en el rango (0, 6): si i == numero: romper imprimir(i, ’ ‘, fin=’’) demás: imprimir() print(‘Todas las iteraciones exitosas’) Si ejecutamos este código e ingresamos el valor entero 7 como el número a verificar; entonces el bloque else se ejecuta como la prueba if dentro de la instrucción for nunca es verdadera y así el ciclo nunca se rompe, y todos los valores se procesan: Solo imprima el código si se completaron todas las iteraciones Ingrese un número para verificar: 7 0 1 2 3 4 5 Todas las iteraciones exitosas Sin embargo, si ingresamos el valor 3 como el número a verificar; entonces el si será verdadera cuando la variable de ciclo ‘i’ tenga el valor 3; así sólo el los valores 0, 1 y 2 serán procesados por el bucle. En esta situación, la otra parte no se ejecutará porque no se procesaron todos los valores de la secuencia: 82 7 Iteración/bucle
Solo imprima el código si se completaron todas las iteraciones Ingrese un número para verificar: 3 0 1 2 7.7 Una nota sobre la denominación de variables de bucle Anteriormente en el libro dijimos que los nombres de las variables deben ser significativos y que nombres como ‘a’ y ‘b’ en general no eran una buena idea. La única excepción a esto La regla se relaciona con los nombres de variables de bucle que se usan con bucles for sobre rangos. Es muy Es común encontrar que estas variables de bucle se denominan ‘i’, ‘j’, etc. Es una convención tan común que si una variable se llama ‘i’ o ‘j’ la gente espera que ser una variable de bucle. Como tal • debe considerar el uso de estos nombres de variables en construcciones de bucle, • y evite utilizarlos en otros lugares. Pero esto plantea la pregunta de por qué ‘i’ y ‘j’; la respuesta es que todo vuelve a un lenguaje de programación llamado Fortran que se desarrolló por primera vez en la década de 1950. En este lenguaje de programación, las variables de bucle tenían que llamarse ‘i’ y ‘j’, etc. Fortran era tan omnipresente para la programación matemática y científica, donde los bucles son casi con rigor, que esto se ha convertido en la convención en otros idiomas que no no tener esta restricción. 7.8 Juego de tirada de dados El siguiente programa breve ilustra cómo se puede usar un bucle while para controlar la ejecución del cuerpo principal del código. En este juego seguiremos tirando un par de dados hasta que el usuario indique que no quiere volver a tirar (usamos el módulo aleatorio para esto que se discute en el próximo capítulo). cuando esto ocurre el ciclo while terminará: importar al azar MÍN = 1 MÁX = 6 roll_again = ‘y’ mientras roll_again == ‘y’: print(‘Lanzar los dados…’) imprimir(‘Los valores son….’) dado1 = random.randint(MIN, MAX) imprimir (dado1) dice2 = random.randint(MIN, MAX) imprimir (dados2) roll_again = input(’¿Tira los dados otra vez? (s/n): ‘) 7.6 For Loop con Else 83
Cuando ejecutamos este programa, se muestran los resultados de lanzar dos dados. El El programa seguirá repitiendo e imprimiendo los valores de los dos dados hasta que el usuario indica que ya no quiere tirar los dados: Tirando los dados… Los valores son…. 2 6 Tirar los dados de nuevo? (t/n): si Tirando los dados… Los valores son…. 4 1 Tirar los dados de nuevo? (t/n): si Tirando los dados… Los valores son…. 3 6 Tirar los dados de nuevo? (t/n): norte 7.9 Recursos en línea Consulte la documentación de la biblioteca estándar de Python para: • https://docs.python.org/3/tutorial/controlflow.html el flujo de Python en línea de tutorial de controles 7.10 Ejercicios Hay dos ejercicios para este capítulo. El primer ejercicio requerirá un simple para mientras que el segundo es más complicado y requiere bucles for anidados y un declaración de ruptura. 7.10.1 Calcular el factorial de un número Escriba un programa que pueda encontrar el factorial de cualquier número dado. Por ejemplo, encuentre el factorial del número 5 (¡a menudo escrito como 5!) que es 1 * 2 * 3 * 4 * 5 y es igual a 120 El factorial no está definido para números negativos y el factorial de Cero es 1; eso es 0! = 1. 84 7 Iteración/bucle
Su programa debe tomar como entrada un número entero del usuario (puede reutilizar su lógica del último capítulo para verificar que han ingresado un valor entero positivo usando isnumeric()). Debería
- Si el número es menor que cero, regresa con un mensaje de error.
- Verifique si el número es cero; si lo es, la respuesta es 1; imprímalo.
- De lo contrario, utilice un bucle para generar el resultado e imprimirlo. 7.10.2 Imprimir todos los números primos en un rango Un número primo es un número entero positivo, mayor que 1, que no tiene otra divisores excepto el número 1 y el número mismo. Es decir, solo se puede dividir entre sí mismo y el número 1, por ejemplo el los números 2, 3, 5 y 7 son números primos ya que no se pueden dividir por ningún otro número entero. Sin embargo, los números 4 y 6 no lo son porque ambos pueden ser dividido por el número 2 además el número 6 también se puede dividir por el numero 3. Debe escribir un programa para calcular números primos desde 1 hasta el valor ingresado por el usuario. Si el usuario ingresa un número inferior a 2, imprime un mensaje de error. Para cualquier número mayor que 2 bucle para cada entero de 2 a ese número y determinar si se puede dividir por otro número (probablemente necesitará dos para bucles para esto; uno anidado dentro del otro). Para cada número que no se puede dividir por ningún otro número (es decir, es primo) número) imprímelo. 7.10 Ejercicios 85
Capítulo 8 Juego de adivinanzas de números 8.1 Introducción En este capítulo vamos a reunir todo lo que hemos aprendido hasta ahora para crear un simple juego de adivinanzas de números. Esto implicará la creación de un nuevo programa de Python, el manejo de la entrada del usuario, el uso de la if declaración, así como el uso de construcciones de bucle. También usaremos una biblioteca o módulo adicional, que no está disponible de forma predeterminada por defecto a su programa; este será el módulo generador de números aleatorios. 8.2 Configuración del programa Queremos asegurarnos de no sobrescribir lo que haya hecho hasta ahora, y nos gustaría que este programa de Python esté separado de su otro trabajo. Como tal crearemos un nuevo archivo de Python para contener este programa. El primer paso será crear un nuevo archivo de Python. Si está usando PyCharm IDE, puede hacerlo usando el Opción de menú Nuevo>PythonFile. 8.2.1 Agregar un mensaje de bienvenida Para asegurarnos de que todo funciona correctamente, agregaremos un simple print() al archivo para que podamos probar su ejecución. Puede agregar lo que desee, incluida la impresión de ‘Hello World’, sin embargo el ejemplo que se muestra a continuación muestra un mensaje de bienvenida que se puede utilizar al comienzo de el juego en PyCharm IDE: © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_8 87
8.2.2 Ejecutar el programa Ahora podemos ejecutar nuestro programa embrionario. Si está utilizando PyCharm, entonces para hacer esto podemos seleccionar el archivo en la vista de la izquierda y usar el menú derecho del mouse para seleccionar la opción ‘Ejecutar’: Cuando hagamos esto, la consola de Python se abrirá en la parte inferior del IDE y la salida mostrada: 88 8 Juego de adivinanzas de números
A partir de ahora, puede volver a ejecutar el programa number_guess_game simplemente con haciendo clic en la pequeña flecha verde en la parte superior derecha de PyCharm (permite que vuelva a ejecutar el último programa que ejecutó PyCharm). 8.3 ¿Qué hará el programa? El objetivo de nuestro juego de adivinanzas de números es adivinar el número que tiene el programa proponer. El flujo principal del programa se muestra en el siguiente diagrama: 8.2 Configuración del programa 89
Esencialmente, la lógica del programa es • El programa selecciona aleatoriamente un número entre 1 y 10. • Luego le pedirá al jugador que ingrese su suposición. • Luego verificará si ese número es el mismo que el que la computadora generado aleatoriamente; si es así, el jugador ha ganado. • Si la suposición del jugador no es la misma, entonces verificará si el número es más alto o más bajo que la conjetura y dígaselo al jugador. • El jugador tendrá 4 intentos para adivinar el número correctamente; si no adivinan el número dentro de este número de intentos, entonces se les informará que han perdido el juego y se les dirá cuál fue el número real. 8.4 Creando el juego 8.4.1 Generar el número aleatorio Comenzaremos observando cómo podemos generar un número aleatorio. Hasta esto punto, solo hemos utilizado las funciones integradas que proporciona Python auto- maticamente De hecho, Python viene con muchos módulos proporcionados por el Organización de Python en sí, por proveedores externos y por el código abierto (típicamente libre) comunidad de software. El módulo o biblioteca aleatoria de Python es uno que se proporciona con Python como parte del entorno predeterminado; pero las funciones dentro de él no son automáticamente cargada o puesta a disposición del programador. Esto se debe en parte a que hay tantos muchas facilidades disponibles con Python que podrían volverse abrumadoras; así que para el en su mayor parte, Python solo pone a disposición de forma predeterminada las facilidades más utilizadas. dades. Los programadores pueden especificar explícitamente cuándo quieren usar algunas instalaciones de una de las otras bibliotecas o módulos. El módulo aleatorio proporciona implementaciones de números pseudoaleatorios generadores para su uso en programas de aplicación. Estos generadores de números aleatorios son denominado pseudo porque es muy difícil para una computadora generar realmente una serie de números aleatorios; en cambio, hace todo lo posible para imitar esto usando un algoritmo; cual por su propia naturaleza se basará en alguna lógica que significará que no es imposible predecir el siguiente número; por lo tanto, no es verdaderamente aleatorio. Para nuestro propósitos esto está bien, pero hay aplicaciones, como seguridad y encriptación, donde esto puede ser un problema real. Para acceder al módulo aleatorio en Python, debe importarlo; esto hace que el módulo visible en el resto del archivo de Python (en nuestro caso a nuestro programa). Esto es hecho usando 90 8 Juego de adivinanzas de números
importar al azar número_a_adivinar = aleatorio.randint(1,10) Una vez que lo hayamos importado, podemos usar las funciones dentro de este módulo, como randint Esta función devuelve un número entero aleatorio entre el primero y el segundo argumentos En el ejemplo anterior, significa que el número aleatorio generado será estar entre 1 y 10 inclusive. La variable number_to_guess ahora contendrá un número entero que el jugador de el juego debe adivinar. 8.4.2 Obtener una entrada del usuario Ahora necesitamos obtener la entrada del usuario que representa su suposición. Tenemos ya vimos cómo hacer esto en capítulos anteriores; podemos usar la función input() que devuelve una cadena y luego la función de número entero que convertirá esa cadena en un número entero (ignoraremos la verificación de errores para asegurarnos de que hayan ingresado un número en este punto). Por lo tanto, podemos escribir: adivinar = int(input(‘Adivine un número entre 1 y 10: ‘)) Esto almacenará el número que adivinaron en la variable adivinar. 8.4.3 Comprobar para ver si el jugador ha adivinado el número Ahora tenemos que comprobar si el jugador ha adivinado el número correcto. Podríamos usar una declaración if para esto, sin embargo, vamos a querer repetir esta prueba hasta que hayan adivinado correctamente o hasta que se hayan quedado sin va. Por lo tanto, usaremos un ciclo while y verificaremos si su suposición es igual a la Número a adivinar: adivinar = int(input(‘Adivine un número entre 1 y 10: ‘)) while number_to_guess != adivinar: El ciclo anterior se repetirá si el número que ingresaron no coincidió con el número a adivinar. Por lo tanto, podemos imprimir un mensaje que le diga al jugador que su suposición fue incorrecta: adivinar = int(input(‘Adivine un número entre 1 y 10: ‘)) while number_to_guess != adivinar: print(‘Lo siento número equivocado’) 8.4 Creando el juego 91
Ahora necesitamos que el jugador haga otra suposición; de lo contrario, el programa nunca terminar, por lo que podemos pedirles nuevamente que ingresen un número: adivinar = int(input(‘Adivine un número entre 1 y 10: ‘)) while number_to_guess != adivinar: print(‘Lo siento número equivocado’)
por determinar…
adivinar = int(input(‘Por favor adivina de nuevo:’)) 8.4.4 Comprobar que no han superado su máximo Número de adivinanzas También dijimos anteriormente que el jugador no puede jugar para siempre; tienen que adivinar la correcta número dentro de 4 va. Por lo tanto, necesitamos agregar alguna lógica que detenga el juego una vez que excedan este número. Por lo tanto, necesitaremos una variable para realizar un seguimiento del número de intentos que Han hecho. Deberíamos llamar a esta variable algo significativo para que sepamos qué es. representa, por ejemplo, podríamos llamarlo count_number_of_tries e iniciar marque con el valor 1: recuento_número_de_intentos = 1 Esto tiene que suceder antes de que entremos en el bucle while. Dentro del ciclo while necesitamos hacer dos cosas • compruebe si se ha excedido el número de intentos, • incrementar el número de intentos si todavía se les permite jugar el juego. Usaremos una declaración if para verificar si se ha alcanzado el número de intentos; si es así, queremos terminar el bucle; la forma más fácil de hacerlo es a través de un descanso declaración. si cuenta_número_de_intentos == 4: romper Si no salimos del ciclo, podemos incrementar el conteo usando el ‘+=’ operador. Así ahora tenemos: 92 8 Juego de adivinanzas de números
recuento_número_de_intentos = 1 adivinar = int(input(‘Adivine un número entre 1 y 10: ‘)) while number_to_guess != adivinar: print(‘Lo siento número equivocado’) si cuenta_número_de_intentos == 4: romper
por determinar…
adivinar = int(input(‘Por favor adivina de nuevo:’)) recuento_número_de_intentos += 1 8.4.5 Notificar al jugador si es más alto o más bajo También dijimos al principio que para que sea más fácil para el jugador adivinar el número; debemos indicar si su suposición fue mayor o menor que la real número. Para hacer esto, podemos usar nuevamente la instrucción if; si la conjetura es menor, imprimimos un mensaje pero si era mayor imprimimos otro. En este punto, tenemos la opción de tener un estado if separado. ment a la utilizada para decidir si se ha alcanzado el máximo va o extender ese uno con un elif. Cada enfoque puede funcionar, pero el último indica que estos todas las condiciones están relacionadas, así que esa es la que usaremos. El ciclo while ahora se ve así: recuento_número_de_intentos = 1 adivinar = int(input(‘Por favor, adivina un número entre 1 y 10: ‘)) while number_to_guess != adivinar: print(‘Lo siento número equivocado’) si cuenta_número_de_intentos == 4: romper elif conjetura < numero_a_conjetura: print(‘Su suposición fue menor que el número’) demás: print(‘Su suposición fue mayor que el número’) adivinar = int(input(‘Por favor adivina de nuevo:’)) recuento_número_de_intentos += 1 Observe que la declaración if tiene un else final que indica que la conjetura fue más alto; esto está bien ya que en este punto es la única opción que queda. 8.4 Creando el juego 93
8.4.6 Estado del final del juego Ya hemos cubierto todas las situaciones que pueden ocurrir mientras se desarrolla el juego. jugó; todo lo que nos queda por hacer es manejar los mensajes de fin de juego. Si el jugador ha acertado el número, queremos felicitarlo; si no adivinaron el número, queremos hacerles saber cuál es el número real era. Haremos esto usando otra instrucción if que verifica si el jugador adivinó el número de no. Después de esto imprimiremos un mensaje de fin de juego: 8.5 El listado completo Para facilitar la consulta, a continuación se proporciona la lista completa: print(‘Bienvenido al juego de adivinar números’)
Inicializar el número a adivinar
número_a_adivinar = aleatorio.randint(1,10)
Inicializar el número de intentos que ha hecho el jugador
recuento_número_de_intentos = 1
Obtener su suposición inicial
adivinar = int(input(‘Adivine un número entre 1 y 10: ‘)) while number_to_guess != adivinar: print(‘Lo siento número equivocado’)
Verifique que no hayan excedido el máximo
# número de intentos, si es así, salir del bucle de lo contrario
importar al azar ) 94 8 Juego de adivinanzas de números
dar al usuario retroalimentación
si cuenta_número_de_intentos == 4: romper elif conjetura < numero_a_conjetura: print(‘Su suposición fue menor que el número’) demás: print(‘Su suposición fue mayor que el número’)
Obtenga su próxima suposición e incremente el número de intentos
adivinar = int(input(‘Por favor adivina de nuevo:’)) recuento_número_de_intentos += 1
Comprobar para ver si adivinaron el número correcto
if number_to_guess == adivinar: print(’¡Bien hecho, ganaste!’) print(‘Tomaste’, count_number_of_tries , ‘va a completar el juego’) demás: print(“Lo siento - pierdes”) print(‘El número que necesitabas adivinar era’, número_a_adivinar) imprimir(‘Se acabó el juego’) Y aquí se muestra una ejecución de muestra del programa: Bienvenido al juego de adivinar números Adivina un número entre 1 y 10: 5 Perdón número equivocado Tu conjetura fue mayor que el número Adivina de nuevo: 3 Perdón número equivocado Su conjetura fue menor que el número Adivina de nuevo: 4 ¡Bien hecho, ganaste! Tomaste 3 intentos para completar el juego. Juego terminado 8.5 El listado completo 95
8.6 Sugerencias 8.6.1 Variables de inicialización En Python no es necesario declarar una variable antes de asignarla; De todos modos, eso es necesario darle un valor inicial antes de referenciarlo. Aquí referencialo se refiere a la obtención del valor que tiene una variable. Por ejemplo cuenta = cuenta + 1 lo que esto dice es obtener el valor retenido por count, agregarle 1 y luego almacenar el nuevo valor de nuevo en cuenta. Si count no tiene un valor antes de intentar hacer esto, entonces está tratando de agarra nada y súmale 1; que no puede hacer y, por lo tanto, se producirá un error generados en su programa, tales como: NameError: el nombre ‘count_number_of_tries’ no está definido Esto también es cierto para contar += 1 Recuerde que esto es solo una forma abreviada de contar = contar + 1 y, por lo tanto, todavía se basa en contar con un valor antes de esta declaración. Es por eso que necesitábamos inicializar la variable count_number_of_tries antes de que lo usáramos. 8.6.2 Líneas en blanco dentro de un bloque de código Es posible que haya notado que hemos utilizado líneas en blanco para agrupar ciertas líneas. de código en este ejemplo. Esto tiene la intención de facilitar la lectura del código y son perfectamente permitido en Python. De hecho, las pautas de diseño de Python lo fomentan. También es posible tener una línea en blanco dentro de un bloque de código sangrado; el bloque de declaraciones no se termina hasta que otra línea con Python válido en él es encontrado que no está sangrado al mismo nivel. 96 8 Juego de adivinanzas de números
8.7 Ejercicios Para este capítulo, todos los ejercicios se relacionan con agregar funciones adicionales al juego:
- Proporcione un modo de trucos, por ejemplo, si el usuario ingresa −1, imprima el número necesitan adivinar y luego hacer un bucle de nuevo. Esto no cuenta como uno de sus pases.
- Si su suposición está dentro de 1 del número real, dígaselo al jugador.
- Al final del juego, antes de imprimir ‘Game Over’, modifica tu programa para que que pregunta al usuario si quiere volver a jugar; si dicen que sí, entonces reinicie el toda la cosa. 8.7 Ejercicios 97
Capítulo 9 recursividad 9.1 Introducción La recursividad es una forma muy poderosa de implementar soluciones para una cierta clase de problemas. Esta clase de problemas es aquella en la que la solución general a un problema puede generarse al descomponer ese problema general en instancias más pequeñas del el mismo problema. El resultado general se genera luego combinando los resultados obtenidos para los problemas más pequeños. 9.2 Comportamiento recursivo Una solución recursiva en un lenguaje de programación como Python es aquella en la que un función se llama a sí misma una o más veces para resolver un problema en particular. En muchos casos el resultado de llamarse a sí mismo se combina con el estado actual de las funciones para devolver un resultado. En la mayoría de los casos, la llamada recursiva consiste en llamar a la función pero con un número menor problema a resolver. Por ejemplo, una función para recorrer una estructura de datos de árbol podría llamar a sí mismo pasando en un sub-árbol para procesar. Alternativamente, una función para generar un factorial el número podría llamarse a sí mismo pasando un número más pequeño para procesar, etc. La clave aquí es que un problema general puede resolverse dividiéndolo en pequeños ejemplos del mismo problema. Las funciones que resuelven problemas llamándose a sí mismas se denominan recursivas. funciones Sin embargo, si tal función no tiene un punto de terminación, entonces la función seguirá llamándose hasta el infinito (al menos en teoría). En la mayoría de los idiomas, tal la situación dará como resultado (eventualmente) la generación de un error. © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_9 99
Para que una función recursiva a lo útil, por lo tanto, debe tener una terminación condición. Esa es una condición bajo la cual no se llaman a sí mismos y en su lugar simplemente regrese (a menudo con algún resultado). La condición de terminación puede ser porque: • Se ha encontrado una solución (algunos datos de interés en una estructura de árbol). • El problema se ha vuelto tan pequeño que puede resolverse sin más recurrencia. Esto a menudo se denomina caso base. Es decir, un caso base es un problema que puede resolverse sin más recurrencia. • Se ha alcanzado algún nivel máximo de recursividad, posiblemente sin resultado siendo encontrado/generado. Por lo tanto, podemos decir que una función recursiva es una función definida en términos de sí mismo a través de expresiones autorreferenciales. La función continuará llamándose a sí misma con variaciones más pequeñas del problema general hasta que se cumpla alguna condición de terminación para detener la recursividad. Todas las funciones recursivas comparten un formato común; ellos tienen un parte recursiva y un punto de terminación que representa la parte del caso base. 9.3 Beneficios de la recursividad El beneficio clave de la recursión es que algunos algoritmos (soluciones a problemas informáticos) lemas) se expresan mucho más elegantemente y con mucho menos código cuando implementado recursivamente que cuando se usa un enfoque iterativo. Esto significa que el código resultante puede ser más fácil de escribir y de leer. Estas ideas gemelas son importantes tanto para los desarrolladores que crean inicialmente el software, sino también para aquellos desarrolladores que deben mantener ese software (potencialmente una cantidad significativa de tiempo después). El código que es más fácil de escribir tiende a ser menos propenso a errores. Del mismo modo código que es más fácil de leer tiende a ser más fácil de mantener, depurar, modificar y ampliar. La recursividad también es adecuada para producir soluciones funcionales a un problema como por su propia naturaleza, una función recursiva se basa en sus entradas y salidas y no mantener cualquier estado oculto. Volveremos a esto en un capítulo posterior sobre Funcional. Programación. 9.4 Búsqueda recursiva de un árbol Como ejemplo de un problema que se adapta bien a una solución recursiva, vamos a ejemplo de cómo podríamos implementar un programa para recorrer los datos de un árbol binario estructura. Un árbol binario es una estructura de datos de árbol formada por nodos en los que cada nodo tiene un valor, un puntero izquierdo y un puntero derecho. 100 9 recursividad
El nodo raíz es el nodo más alto del árbol. Este nodo raíz luego hace referencia a un subárbol izquierdo y derecho. Esta estructura se repite hasta un nodo hoja. Un nodo hoja es un nodo en el que los punteros derecho e izquierdo están vacíos (es decir, tienen el valor Ninguno). Esto se muestra a continuación para un árbol binario simple: Por lo tanto, un árbol binario está vacío (representado por un puntero nulo) o está hecho de un nodo único, donde los punteros izquierdo y derecho apuntan cada uno a un árbol binario. Si ahora queremos averiguar si un valor particular está en el árbol, entonces podemos comenzar en el nodo raíz. Si el nodo raíz tiene el valor, lo imprimimos; de lo contrario podemos llamar a la búsqueda función en los nodos secundarios del nodo actual. Si el nodo actual no tiene hijos simplemente regresamos sin un resultado. El pseudocódigo para esto podría verse así: buscar(valor_a_buscar, nodo_actual): Si current_node.value == value_to_find: print(‘valor encontrado:’, nodo_actual.valor) De lo contrario, si current.node.has_children: buscar (valor, nodo_actual.izquierda) buscar (nodo_actual.derecha) Esto ilustra lo fácil que es escribir una función recursiva que pueda resolver lo que a primera vista puede parecer un problema complejo. 9.4 Búsqueda recursiva de un árbol 101
9.5 Recursividad en Python La mayoría de los programas de computadora admiten la idea de la recursividad y Python no es una excepción. En Python es perfectamente legal tener una función que se llame a sí misma (es decir, dentro del cuerpo de la función se hace una llamada a la misma función). Cuando la función es ejecutado por lo tanto se llamará a sí mismo. En Python podemos escribir una función recursiva como: def función_recursiva(): print(’llamando a función_recursiva’) función recursiva() Aquí la función recursive_function() se define de tal manera que imprime un mensaje y luego se llama a sí mismo. Tenga en cuenta que no se requiere una sintaxis especial para esto como No es necesario que la función se haya definido por completo antes de utilizarla. Por supuesto, en el caso de recursive_function() esto resultará en infinitos recursividad ya que no hay condición de terminación. Sin embargo, esto sólo se convertirá evidente en tiempo de ejecución cuando Python eventualmente generará un error: Rastreo (llamadas recientes más última): Archivo “recursion_example.py”, línea 5, en <módulo> RecursionError: se excedió la profundidad máxima de recursión durante la llamada un objeto Python Sin embargo, como ya se mencionó, una función recursiva debe tener una parte recursiva y una terminación o parte del caso base. La condición de terminación se utiliza para identificar cuando se aplica el caso base. Por lo tanto, deberíamos agregar una condición para identificar el escenario de terminación y cuál es el comportamiento del caso base. Haremos esto en el siguiente sección mientras vemos cómo se puede generar un factorial para un número recursivamente. 9.6 Cálculo factorial recursivamente Ya hemos visto cómo crear un programa que pueda calcular el factorial de un número utilizando la iteración como uno de los ejercicios del último capítulo; ahora vamos a crear una implementación alternativa que usa la recursividad en su lugar. Recuerda que el factorial de un número es el resultado de multiplicar ese número por cada de los valores enteros hasta ese número, por ejemplo, para encontrar el factorial del número 5 (¡escrito como 5!) podemos multiplicar 1 * 2 * 3 * 4 * 5 lo que generará el número 120. Podemos crear una solución recursiva al problema Factorial definiendo un función que toma un número entero para generar el número factorial. Esta función devolverá el valor 1 si el número pasado es 1; este es el caso base. De lo contrario, multiplicará el valor que se le pasa con el resultado de llamarse a sí mismo (la función factorial()) con n − 1 que es la parte recursiva. 102 9 recursividad
La función se da a continuación: def factorial(n): if n == 1: # La condición de terminación return 1 # El caso base demás: res = n * factorial(n-1) # La llamada recursiva volver res imprimir(factorial(5)) La clave para entender esta función es que tiene:
- Una condición de terminación cuya ejecución está garantizada cuando el valor de n es 1. Este es el caso base; no podemos reducir el problema más abajo ya que el factorial de 1 es 1!
- La función se llama recursivamente a sí misma pero con n − 1 como argumento; esto significa cada vez que se llama a sí mismo el valor de n es menor. Así, el valor devuelto por esta llamada es el resultado de un cálculo más pequeño. Para aclarar cómo funciona esto, podemos agregar algunas declaraciones de impresión (y una profundidad indicador) a la función para indicar su comportamiento: def factorial(n, profundidad = 1): si n == 1: print(’\t’ * profundidad, ‘Retornando 1’) volver 1 demás: print(’\t’*profundidad,‘Llamando factorial recursivamente(’,n- 1,’)’) resultado = n * factorial(n-1, profundidad + 1) print(’\t’ * profundidad, ‘Retornando:’, resultado) resultado devuelto print(‘Llamando factorial( 5 )’) imprimir(factorial(5)) Cuando ejecutamos esta versión del programa, el resultado es: Factorial de llamadas( 5 ) Llamada factorial recursiva( 4 )
Llamada factorial recursiva( 3 )
Llamada recursiva factorial( 2 )
Llamada factorial recursiva( 1 )
regresando 1
Regreso: 2
Regreso: 6
Regreso: 24 Regreso: 120 120 9.6 Cálculo factorial recursivamente 103
Tenga en cuenta que el parámetro de profundidad se utiliza simplemente para proporcionar alguna sangría a la imprimir declaraciones. De la salida podemos ver que cada llamada al programa factorial da como resultado un cálculo más simple hasta el punto en que estamos pidiendo el valor de 1! cual es
- Esto se devuelve como resultado de llamar a factorial(1). Este resultado es multi- plied con el valor de n antes de eso; que era 2. Las causas factorial(2) a devuelve el valor 2 y así sucesivamente. 9.7 Desventajas de la recursividad Aunque la recursividad puede ser una forma muy expresiva de definir cómo se puede resolver un problema, resuelto, no es tan eficiente como la iteración. Esto se debe a que una llamada de función es más costoso para Python procesar ese bucle for. En parte esto se debe a la infraestructura que va junto con una llamada de función; esa es la necesidad de configurar la pila para cada invocación de función separada para que todas las variables locales sean independientes de cualquier otra llamada a esa función. También está relacionado con el desenrollado asociado de la pila. cuando una función regresa. Sin embargo, también se ve afectado por la creciente cantidad de memoria que cada llamada recursiva debe usar para almacenar todos los datos en la pila. En algunos idiomas, las optimizaciones son posibles para mejorar el rendimiento de un solución recursiva. Un ejemplo típico se relaciona con un tipo de recursividad conocida como cola. recursividad Una solución recursiva de cola es aquella en la que el cálculo se realiza antes de la llamada recursiva. El resultado se pasa luego al paso recursivo, que da como resultado que la última declaración en la función simplemente llame a la función recursiva. En tales situaciones, la solución recursiva se puede expresar (internamente a la sistema informático) como un problema iterativo. Ese es el programador escribe la solución. como un algoritmo recursivo pero el intérprete o compilador lo convierte en un iterativo solución. Esto permite a los programadores beneficiarse de la naturaleza expresiva de recur- mientras que también se beneficia del rendimiento de una solución iterativa. Se podría pensar que la función factorial presentada anteriormente es cola recur- sivo; sin embargo, no es porque la última declaración en la función realiza un cálculo cálculo que multiplica n por el resultado de la llamada recursiva. Sin embargo, podemos refactorizar la función factorial para que sea recursiva de cola. Este versión de la función factorial pasa el resultado evolutivo a través del parámetro del acumulador. Se da como referencia aquí: def tail_factorial(n, acumulador=1): si n == 0: acumulador de retorno demás: return tail_factorial(n - 1, acumulador * n) imprimir (cola_factorial (5)) 104 9 recursividad
Sin embargo, debe tenerse en cuenta que Python actualmente no realiza recurrencia de cola. optimización de la visión; así que este es un ejercicio puramente teórico. 9.8 Recursos en línea A continuación se proporcionan algunas referencias sobre recursividad disponibles en línea: • https://en.wikipedia.org/wiki/Recursion_(computer_science) Proporciona wikipe- dias introduccion a la recursividad. • https://www.sparknotes.com/cs/recursion/whatisrecursion/section1/ proporciona una Introducción al concepto de recursividad. 9.9 Ejercicios En este conjunto de ejercicios, tendrás la oportunidad de explorar cómo resolver problemas. utilizando la recursividad en Python.
- Escriba un programa para determinar si un número dado es un número primo o no. Usar recursión para implementar la solución. El siguiente fragmento de código ilustra cómo esto podría funcionar: imprimir(’es_principal(3):’, es_principal(3)) # Verdadero imprimir(’es_principal(7):’, es_principal(7)) # Verdadero imprimir(’es_principal(9):’, es_principal(9)) # Falso imprimir(’es_principal(31):’, es_principal(31)) # Verdadero
- Escriba una función que implemente el triángulo de Pascal para un número específico de filas El triángulo de Pascal es un triángulo de los coeficientes binomiales. Los valores sostenidos en el triángulo se generan de la siguiente manera: En la fila 0 (la fila superior), hay una única entrada distinta de cero 1. Cada entrada de cada fila subsiguiente se construye mediante sumando el número de arriba y a la izquierda con el número de arriba y a la derecha, tratar las entradas en blanco como 0. Por ejemplo, el número inicial en el primero (o cualquier otra) la fila es 1 (la suma de 0 y 1), mientras que los números 1 y 3 en la tercera fila se suman para generar el número 4 en la cuarta fila. Un ejemplo del triángulo de Pascal para 4 filas se da a continuación: 9.7 Desventajas de la recursividad 105
Por ejemplo, su función podría llamarse pascals_traingle() en la que caso, la siguiente aplicación ilustra cómo podría usarlo: triangulo = pascales_triangulo(5) para fila en triangulo: imprimir (fila) La salida de esto podría ser: [1] [1, 1] [1, 2, 1] [1, 3, 3, 1] [1, 4, 6, 4, 1] 106 9 recursividad
Capítulo 10 Introducción al análisis estructurado 10.1 Introducción En los capítulos anteriores lo que hemos visto es típico del enfoque procedimental programación. En el próximo capítulo comenzaremos a explorar la definición de función. ciones que permiten un estilo de programación más modular. En este capítulo introduciremos un enfoque para el análisis y diseño de sistemas de software llamados Análisis Estructurado/Diseño. Dentro de esta zona hay muchos métodos específicos y bien documentados, incluido SSADM (Structured Método de Análisis y Diseño de Sistemas) y el método estructurado de Yourden. Sin embargo, no nos centraremos en ningún enfoque específico; en su lugar vamos a esbozar las ideas clave y los dos elementos básicos de la mayoría de los métodos de Análisis Estructurado; Descomposición Funcional y Análisis de Flujo de Datos. Luego presentaremos Diagramas de flujo para el diseño de algoritmos. 10.2 Análisis estructurado e identificación de funciones Los métodos de análisis estructurado suelen emplear un enfoque basado en procesos (con un conjunto de pasos o etapas prescritos) que de una forma u otra consideran lo que el son las entradas y salidas del sistema y cómo esas entradas se transforman en salidas. Esta transformación implica la aplicación de una o más funciones. El Los pasos involucrados en el análisis estructurado identifican estas funciones y normalmente dividirlas iterativamente en funciones cada vez más pequeñas hasta que se se ha alcanzado el nivel de detalle. Este proceso se conoce como Funcional. Descomposición. Aunque los programas simples de Python solo pueden contener una secuencia de declaraciones y expresiones; cualquier programa de un tamaño significativo necesitará ser estructurado tal que puede ser: © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_10 107
• fácil de entender por otros desarrolladores, • probado para asegurar que hace lo que se pretende, • mantenido a medida que evolucionan los requisitos nuevos y existentes, • depurado para resolver comportamientos inesperados o no deseados. Dados estos requisitos es común querer organizar tu programa Python en términos de funciones y subfunciones. La descomposición funcional apoya el análisis y la identificación de estos funciones 10.3 Descomposición funcional La descomposición funcional es una forma en la que un sistema se puede descomponer en sus partes constituyentes. Por ejemplo, para un sistema de nómina computarizado para calcular cuánto cuánto debería recibir un empleado pagado por hora, podría ser necesario para:
- Cargue los detalles del empleado desde alguna forma de almacenamiento permanente (como un archivo o una base de datos).
- Cargue cuántas horas ha trabajado el empleado durante esa semana (posiblemente desde otro sistema que registra el número de horas trabajadas).
- Multiplique las horas trabajadas por la tarifa por hora del empleado.
- Registre cuánto se le pagará al empleado en una base de datos o archivo de nómina.
- Imprima el comprobante de pago del empleado.
- Transferir los fondos correspondientes de la cuenta bancaria de la empresa a la cuenta bancaria de los empleados.
- Registrar en la base de datos de nómina todo lo que se ha completado. Cada uno de los pasos anteriores podría representar una función realizada por el sistema. Estas funciones de nivel superior podrían dividirse en funciones de nivel inferior. funciones Por ejemplo, imprimir el recibo de nómina de los empleados puede implicar imprimiendo su nombre y dirección en un formato particular, imprimiendo el empleado número, número de seguro social, etc. Además de imprimir información histórica como como cuánto se ha pagado en el ejercicio en curso, cuánto impuesto han pagado, etc. Todo además de imprimir la cantidad real que se les está pagando. Este proceso de descomponer las funciones de nivel superior en funciones de nivel inferior ayuda con: • probar el sistema (las funciones se pueden probar de forma aislada), • entender el sistema como la organización de las funciones puede dar sentido al código, además de permitir que cada función se entienda de forma aislada el resto del sistema, • mantener el sistema ya que solo aquellas funciones que necesitan ser cambiadas pueden ser afectados por requisitos nuevos o modificados, 108 10 Introducción al análisis estructurado
• depurar el sistema, ya que los problemas pueden aislarse en funciones específicas que pueden examinarse independientemente del resto de la solicitud. También se conoce como un enfoque de refinamiento de arriba hacia abajo. El término superior- refinamiento hacia abajo (también conocido como diseño paso a paso) destaca la idea de que estamos descomponer un sistema en los subsistemas que lo componen. Es común representar las funciones identificadas en forma de un árbol que ilustra las relaciones entre las funciones de nivel superior y las funciones de nivel inferior. Este se ilustra a continuación: Este diagrama ilustra cómo se puede desglosar un proceso de aprobación de tarjetas de crédito en subfunciones. 10.3.1 Terminología de descomposición funcional Los términos clave utilizados en la descomposición funcional son: • Función. Esta es una tarea que es realizada por un dispositivo, sistema o proceso. • Descomposición. Este es el proceso por el cual se rompen las funciones de nivel superior. hacia abajo en funciones de nivel inferior donde cada función representa parte de la funcionalidad de la función de nivel superior. • Función de nivel superior. Esta es una función que tiene una o más subfunciones. Esta función de nivel superior depende de la funcionalidad del nivel inferior funciones para su comportamiento. • Sub función. Esta es una función que proporciona algún elemento del comportamiento de una función de nivel superior. Una función secundaria también se puede dividir en su propia función secundaria. funciones de forma jerárquica. En el diagrama anterior, la Revisión para La función de cumplimiento es a la vez una subfunción y tiene sus propias subfunciones. 10.3 Descomposición funcional 109
• Función básica. Una función básica es una función que no tiene subfunciones más pequeñas. Las funciones Realizar verificación de crédito y Revisión de reglas internas son ambas Funciones básicas. 10.3.2 Proceso de descomposición funcional En un nivel muy alto, la Descomposición Funcional consta de una serie de pasos tales como los que se detallan a continuación:
- Buscar/Identificar las entradas y salidas del sistema.
- Defina cómo se convierten las entradas en salidas. Esto ayudará a identificar el función(es) superior(es) de alto nivel.
- Mire las funciones actuales e intente dividirlas en una lista de sub funciones Identifique lo que debe hacer cada subfunción y cuáles son sus entradas y las salidas son.
- Repita el paso 2 para cada función identificada hasta que las funciones identificadas no puedan o no debe descomponerse más.
- Dibuje un diagrama de la jerarquía de funciones que ha creado. Viendo el funciones y sus relaciones es algo muy útil ya que permite desarrolladores para visualizar el sistema funcionalmente. Hay muchos CASO (Ingeniería de software asistida por computadora) herramientas que ayudan con esto, pero cualquier Se puede utilizar una herramienta de dibujo (como Visio).
- Examine el diagrama en busca de funciones repetitivas. Es decir, funciones que hacen el misma cosa pero aparecen en diferentes lugares en el diseño. Estos son probablemente más funciones genéricas que se pueden reutilizar. Examine también el diagrama para ver si puede identificar las funciones que faltan.
- Refinar/Diseñar las interfaces entre una función y otra. Eso es lo que los datos/información se pasan hacia y desde una función a una subfunción, así como entre funciones. 10.3.3 Ejemplo de descomposición funcional de calculadora Como ejemplo de descomposición funcional, consideremos una calculadora simple programa. Queremos que este programa sea capaz de realizar un conjunto de operaciones matemáticas en dos números, como sumar, restar, multiplicar y dividir. Por lo tanto, podríamos dibujar un Diagrama de descomposición funcional (o FDD) como: 110 10 Introducción al análisis estructurado
Esto ilustra que la función Calculadora se puede descomponer en la suma, Funciones de resta, multiplicación y división. Podríamos entonces identificar la necesidad de introducir los dos números a operar. Este resultaría en la adición de una o más funciones nuevas: También podríamos identificar la necesidad de determinar qué operación numérica debe realizarse en función de la entrada del usuario. Esta función podría estar por encima de la función numérica. funciones o junto a ellas. De hecho, este es un ejemplo de una decisión de diseño que el diseñador/desarrollador debe hacer en función de su comprensión del problema y cómo se desarrollará/probará/utilizará el software, etc. En la siguiente versión del diagrama de descomposición funcional, la operación La función de selección se coloca al mismo nivel que las operaciones numéricas, ya que proporciona información a la función de nivel superior. 10.3 Descomposición funcional 111
10.4 Flujo Funcional Aunque la jerarquía de descomposición presentada en la Descomposición funcional El diagrama ilustra las funciones y sus relaciones jerárquicas; no es asi capturar cómo fluyen los datos entre las funciones o el orden en que la función ciones son invocadas. Hay varios enfoques para describir las interacciones entre las funciones identificados por descomposición funcional, incluido el uso de pseudocódigo, datos Diagramas de flujo y diagramas de secuencia: • Pseudo Código. Esta es una forma de inglés estructurado que no está ligado a ningún lenguaje de programación particular, pero que se puede utilizar para expresar ideas simples incluyendo opciones condicionales (similares a las declaraciones if) e iteración (como se tipifica mediante construcciones en bucle). Sin embargo, como es un pseudolenguaje, los desarrolladores no están ligado a una sintaxis específica y puede incluir funciones sin la necesidad de definir esas funciones en detalle. • Diagramas de flujo de datos. Estos diagramas se utilizan para trazar las entradas, procesos, y salidas de las funciones en forma gráfica estructurada. Un diagrama de flujo de datos típicamente no tiene flujo de control, no hay reglas de decisión ni bucles. Para cada flujo de datos, debe haber al menos una entrada y un punto final. cada proceso (función) se puede refinar mediante otro diagrama de flujo de datos de nivel inferior, que subdivide este proceso en subprocesos. • Diagramas de Secuencia. Se utilizan para representar interacciones entre diferentes entidades (u objetos) en secuencia. Las funciones invocadas se representan como siendo llamado de una entidad a otra. Los diagramas de secuencia se utilizan más típicamente con sistemas Orientados a Objetos. 10.5 Diagramas de flujo de datos Un diagrama de flujo de datos consta de un conjunto de entradas y salidas, procesos (funciones), flujos, almacenes de datos (también conocidos como almacenes) y terminadores. • Proceso. Este es el proceso (o función o transformación) que convierte entradas en salidas. El nombre del proceso debe ser descriptivo indicando lo que hace. • Flujo de datos. El flujo indica la transferencia de datos/información de un elemento a otro (es decir, un flujo tiene una dirección). El flujo debe tener un nombre que sugiere qué información/datos se están intercambiando. Los flujos vinculan procesos, datos tiendas y terminadores. • Almacén/almacén de datos. Un almacén de datos (que puede ser algo como un archivo, carpeta, base de datos u otro depósito de datos) se utiliza para almacenar datos para su uso posterior. El nombre del almacén de datos es un sustantivo plural (por ejemplo, empleados). El flujo de la almacén de datos por lo general representa la lectura de los datos almacenados en el almacén de datos, y 112 10 Introducción al análisis estructurado
el flujo al almacén de datos de almacenamiento generalmente expresa la entrada o actualización de datos (a veces también borrando datos). • Terminador. El Terminator representa una entidad externa (al sistema) que se comunica con el sistema. Ejemplos de entidades pueden ser usuarios humanos o otros sistemas etc A continuación se proporciona un ejemplo de un diagrama de flujo de datos usando las funciones iden- tificado para la calculadora: En este diagrama, la jerarquía de los DFD está indicada por los niveles que se expanden en cómo la función en el nivel anterior es implementada por las funciones en el siguiente nivel. Este DFD también solo presenta el flujo de datos para la situación en la que el usuario selecciona la función de suma como la operación numérica a aplicar. 10.6 diagramas de flujo Un diagrama de flujo es una representación gráfica de un algoritmo, flujo de trabajo o proceso para un problema dado. Los diagramas de flujo se utilizan en el análisis, diseño y documentación de software. sistemas Al igual que con otras formas de notación (como los DFD), los diagramas de flujo ayudan a los diseñadores y desarrolladores para visualizar los pasos involucrados en una solución y así ayudar en comprensión de los procesos y algoritmos involucrados. Los pasos en el algoritmo se representan como varios tipos de cajas. El el orden de los pasos se indica mediante flechas entre los recuadros. El flujo de control está representado por cuadros de decisión. 10.5 Diagramas de flujo de datos 113
Hay una serie de notaciones comunes que se utilizan con diagramas de flujo y la mayoría de esas notaciones usan los siguientes símbolos: El significado de estos símbolos se da a continuación: • Terminal. Este símbolo se utiliza para indicar el inicio o el final de un programa o subproceso. Suelen contener las palabras ‘Inicio’, ‘Fin’ o ‘Parada’ o una frase indicando el inicio o el final de algún proceso, como ‘Iniciando tirada de impresión’. • Proceso. Este símbolo representa una o más operaciones (o programación afirmaciones/expresiones) que de alguna manera aplican el comportamiento o cambian el estado de el sistema. Por ejemplo, pueden sumar dos números, ejecutar algún tipo de cálculo o cambio de una bandera booleana, etc. • Decisión. Esto representa un punto de decisión en el algoritmo; es decir representa un punto de decisión que alterará el flujo del programa (típicamente entre dos diferentes caminos). El punto de decisión a menudo se representa como una pregunta con un respuesta ‘sí’/’no’ y esto se indica en el diagrama de flujo mediante el uso de ‘sí’ (o etiquetas ‘y’) y ‘no’ (o ‘n’) en el diagrama de flujo. En Python, este punto de decisión puede implementarse mediante una sentencia if. • De entrada y salida. Este cuadro indica la entrada o salida de datos del algoritmo. Esto podría representar la obtención de datos de entrada del usuario o la impresión de resultados para el usuario. • Fluir. Estas flechas se utilizan para representar el orden de ejecución de los algoritmos de las cajas. • Proceso Predefinido. Esto representa un proceso que ha sido definido en otra parte. • Datos almacenados. Indica que los datos se almacenan en alguna forma de almacenamiento persistente sistema. • Conector fuera de página. Un conector etiquetado para usar cuando el objetivo está en otro página (otro diagrama de flujo). 114 10 Introducción al análisis estructurado
Usando los símbolos anteriores, podemos crear un diagrama de flujo para nuestro entero simple programa calculadora: El diagrama de flujo anterior muestra el funcionamiento básico de la calculadora; el usuario selecciona qué operación realizar, ingresa los dos números y luego, dependiendo del operación se realiza la operación seleccionada. Luego se imprime el resultado. 10.7 Diccionario de datos Otro elemento comúnmente asociado con el Análisis/Diseño Estructurado es el Data Diccionario. El Diccionario de datos es un depósito estructurado de elementos de datos en el sistema. Almacena las descripciones de todos los elementos de datos del diagrama de flujo de datos. Eso es registra detalles y definiciones de flujos de datos, almacenes de datos, datos almacenados en almacenes de datos, y los procesos. El formato utilizado para un diccionario de datos varía de un método a otro. método y proyecto a proyecto. Puede ser tan simple como una hoja de cálculo de Excel a un sistema de software empresarial como Semanta (https://www.semantacorp.com/ Diccionario de datos). 10.6 diagramas de flujo 115
10.8 Recursos en línea Hay muchos recursos en línea disponibles que discuten la descomposición funcional, tanto desde un punto de vista teórico como práctico orientado a Python, que incluye: • https://en.wikipedia.org/wiki/Análisis_estructurado Wikipedia Estructurado Página de análisis. • https://en.wikipedia.org/wiki/Top-down_and_bottom-up_design Wikipedia página sobre el diseño de arriba hacia abajo y de abajo hacia arriba. • https://en.wikipedia.org/wiki/Edward_Yourdon#Yourdon_Structured_Method Página de Wikipedia sobre el método Yourden. • https://en.wikipedia.org/wiki/Structured_systems_analysis_and_design_method Página de Wikipedia sobre SSADM. • https://en.wikipedia.org/wiki/Functional_decomposition La página de wikipedia en Descomposición funcional. • https://docs.python.org/3/howto/functional.html La documentación estándar de Python Mención sobre la descomposición funcional. • https://en.wikipedia.org/wiki/Data-flow_diagram Página de Wikipedia sobre flujo de datos Diagramas (DFD). • https://en.wikipedia.org/wiki/Sequence_diagram Página de Wikipedia sobre Secuencia Diagramas. • https://en.wikipedia.org/wiki/Data_dictionary Wikipedia página en Datos Diccionarios. • https://en.wikipedia.org/wiki/Flowchart Página de diagrama de flujo de Wikipedia. 116 10 Introducción al análisis estructurado
Capítulo 11 Funciones en Python 11.1 Introducción Como se discutió en el último capítulo; cuando crea una aplicación de cualquier tamaño, quiere dividirlo en unidades más manejables; estas unidades se pueden trabajar por separado, probado y mantenido por separado. Una forma en que estas unidades pueden definirse como funciones de Python. Este capítulo presentará las funciones de Python, cómo se definen, cómo puede ser referenciado y ejecutado. Considera cómo funcionan los parámetros en Python funciones y cómo se pueden devolver los valores de las funciones. También introduce lambda o funciones anónimas. 11.2 ¿Qué son las funciones? En Python, las funciones son grupos de declaraciones relacionadas que se pueden llamar juntas, que normalmente realizan una tarea especfica, y que puede o no tomar un conjunto de parmetros o devolver un valor. Las funciones se pueden definir en un lugar y llamar o invocar en otro. Este ayuda a hacer que el código sea más modular y más fácil de entender. También significa que la misma función se puede llamar varias veces o en múltiples ubicaciones. Esto ayuda a garantizar que, aunque se utilice una parte de la funcionalidad en múltiples lugares; solo se define una vez y solo necesita ser mantenido y probado en una ubicación. La versión original de este capítulo fue revisada: el texto ha sido reemplazado. La corrección de esto el capítulo está disponible en https://doi.org/10.1007/978-3-030-20290-3_38 © Springer Nature Suiza AG 2019, publicación corregida 2020 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_11 117
11.3 Cómo funcionan las funciones Hemos dicho cuáles son y un poco sobre por qué pueden ser buenos pero no realmente cómo funcionan. Cuando se llama (o se invoca) una función, el flujo de control de un programa salta desde donde se llamó a la función hasta el punto donde se definió la función. El El cuerpo de la función se ejecuta antes de que el control regrese a donde estaba. llamado desde. Como parte de este proceso, todos los valores que estaban en su lugar cuando la función fue llamadas, se almacenan (en algo llamado la pila) de modo que si la función define sus propias versiones, no se sobrescriben entre sí. La invocación de una función ilustrada a continuación: Cada vez que se realiza la llamada a function_name(), el flujo del programa salta a el cuerpo de la función y ejecuta las declaraciones allí. Una vez que la función termina, vuelve al punto en el que se llamó a la función. En lo anterior, esto sucede dos veces ya que la función se llama en dos puntos separados en el programa. 11.4 Tipos de funciones Técnicamente hablando, hay dos tipos de funciones en Python; funciones integradas y funciones definidas por el usuario. Las funciones integradas son las que proporciona el lenguaje y hemos visto varias de estos ya. Por ejemplo, tanto print() como input() son funciones integradas. No necesitamos definirlos nosotros mismos, ya que Python los proporciona. 118 11 Funciones en Python
En contraste, las funciones definidas por el usuario son aquellas escritas por desarrolladores. Estaremos definir funciones definidas por el usuario en el resto de este capítulo y es probable que en En muchos casos, la mayoría de los programas que escribirá incluirán funciones definidas por el usuario. funciones de uno u otro tipo. 11.5 Definición de funciones La sintaxis básica de una función se ilustra a continuación: Esto ilustra varias cosas:
- Todas las funciones (nombradas) se definen usando la palabra clave def; esto indica el comienzo de una definición de función. Una palabra clave es parte de la sintaxis de Python lenguaje y no puede ser redefinido y no es una función.
- Una función puede tener un nombre que la identifique de manera única; también puedes tener funciones anónimas, pero las dejaremos para más adelante en este capítulo.
- Las convenciones de nomenclatura que hemos estado adoptando para las variables también se aplican a funciones, todas son minúsculas con los diferentes elementos de la función nombre separado por un ‘_’.
- Una función puede (opcionalmente) tener una lista de parámetros que permiten que los datos sean pasado a la función. Estos son opcionales ya que no todas las funciones necesitan ser suministrado con parámetros.
- Se utilizan dos puntos para marcar el final del encabezado de la función y el inicio de la cuerpo funcional. El encabezado de la función define la firma de la función (cuál es su llama y los parámetros que toma). El cuerpo de la función define lo que la función hace.
- Se puede proporcionar una cadena de documentación opcional (la docstring) que describe lo que hace la función. Por lo general, usamos la cadena de comillas triples dobles ya que esto permite que la cadena de documentación abarque varias líneas si requerido.
- Una o más sentencias de Python conforman el cuerpo de la función. Estos están sangrados en relación con la definición de la función. Todas las líneas que están sangradas son parte del función hasta una línea que está destinada al mismo nivel que la línea def.
- Es común usar 4 espacios (no una tabulación) para determinar cuánto sangrar el cuerpo de una función por. def nombre_función(lista de parámetros): “““cadena de documentación””” declaración declaraciones) 11.4 Tipos de funciones 119
11.5.1 Una función de ejemplo La siguiente es una de las funciones más simples que puede escribir; no toma parametros y tiene solo una declaración que imprime el mensaje ‘Hello World’: Esta función se llama print_msg y cuando se la llama (también conocida como invocada) ejecutará el cuerpo de la función que imprimirá la cadena, por ejemplo Generará la salida Tenga cuidado de incluir los corchetes () cuando llame a la función. Esto es porque si solo usa el nombre de la función, simplemente se está refiriendo a la ubicación en la memoria donde se almacena la función, y no la está invocando. Podríamos modificar la función para hacerla un poco más general y reutilizable por proporcionando un parámetro. Este parámetro podría usarse para proporcionar el mensaje que se va a impreso, por ejemplo: Ahora la función print_my_msg toma un solo parámetro y este parámetro se convierte en una variable que está disponible dentro del cuerpo de la función. Sin embargo, este parámetro solo existe dentro del cuerpo de la función; no está disponible fuera de la función Esto ahora significa que podemos llamar a la función print_my_msg con una variedad de diferentes mensajes: def imprimir_mensaje(): imprimir(’¡Hola mundo!’) imprimir_mensaje() ¡Hola Mundo! def imprimir_mi_mensaje(mensaje): imprimir (mensaje) print_my_msg(‘Hola mundo’) print_my_msg(‘Buen día’) print_my_msg(‘Bienvenido’) print_my_msg(‘Ola’) 120 11 Funciones en Python
El resultado de llamar a esta función con cada una de estas cadenas proporcionadas como el parámetro es: 11.6 Devolver valores de funciones Es muy común querer devolver un valor de una función. En Python esto puede ser hecho usando la declaración de retorno. Cada vez que se encuentra una declaración de devolución dentro de una función, esa función terminará y devolverá los valores siguientes la palabra clave de retorno. Esto significa que si se proporciona un valor, se pondrá a disposición de cualquier codigo de llamada Por ejemplo, lo siguiente define una función simple que eleva al cuadrado cualquier valor se le ha pasado: Cuando llamamos a esta función, multiplicará lo que sea dado por sí mismo y luego devolver ese valor. El valor devuelto se puede usar en el punto en que la función fue invocado, por ejemplo: Cuando se ejecuta este código, obtenemos: Hola Mundo Buen día Bienvenido Ola def cuadrado(n): volver n * n
Almacenar el resultado del cuadrado en una variable
resultado = cuadrado( 4) imprimir (resultado)
Enviar el resultado del cuadrado inmediatamente a otra función
imprimir (cuadrado ( 5))
Usar el resultado devuelto por el cuadrado en un condicional
expresión si cuadrado(3) < 15: print(’ Todavía menos de 15 ‘) 11.5 Definición de funciones 121
También es posible devolver múltiples valores de una función, por ejemplo en este función de intercambio el orden en que se proporcionan los parámetros se intercambia cuando se devuelven: Entonces podemos asignar los valores devueltos a las variables en el punto en que el función se llama: que produce De hecho, el resultado devuelto por la función de intercambio es lo que se llama una tupla que es una forma sencilla de agrupar datos. Esto significa que también podríamos haber escritó: Lo que habría impreso la tupla: Veremos más las tuplas cuando consideremos las colecciones de datos. dieciséis 25 Todavía menos de 15 intercambio de definición (a, b): volver b, un un = 2 segundo = 3 x, y = intercambiar (a, b) imprimir (x, ‘,’, y) 3 , 2 z = intercambiar (a, b) imprimir (z) (3, 2) 122 11 Funciones en Python
11.7 cadena de documentación Hasta ahora, nuestras funciones de ejemplo no han incluido ninguna cadena de documentación (la cadena de documentación). Esto se debe a que la cadena de documentación es opcional y las funciones que tenemos escrito han sido muy simples. Sin embargo, a medida que las funciones se vuelven más complejas y pueden tener múltiples parámetros la documentación proporcionada puede volverse más importante. La cadena de documentación permite que la función brinde orientación sobre lo que se espera en términos de los datos pasados a los parámetros, ¿qué sucederá potencialmente si el los datos son incorrectos, así como cuál es el propósito de la función en primer lugar. En el siguiente ejemplo, la cadena de documentación se utiliza para explicar el comportamiento de la función y cómo se utiliza el parámetro. Cuando se usa, este método garantizará que se devolverá un entero válido al código de llamada: A continuación se muestra un ejemplo de lo que sucede cuando se ejecuta: def get_integer_input(mensaje): "”” Esta función mostrará el mensaje al usuario. y solicite que ingresen un número entero. Si el usuario ingresa algo que no es un número entonces la entrada será rechazada y se mostrará un mensaje de error. Se le pedirá al usuario que vuelva a intentarlo.””" value_as_string = entrada (mensaje) mientras que no value_as_string.isnumeric(): print(‘La entrada debe ser un entero’) value_as_string = entrada (mensaje) devuelve int(valor_como_cadena) edad = get_integer_input(‘Ingrese su edad:’) print(’edad es’, edad) Por favor ingrese su edad: Juan La entrada debe ser un número entero. Por favor ingrese su edad: 21 la edad es 21 11.7 cadena de documentación 123
La cadena de documentación se puede leer directamente desde el código, pero también está disponible para los IDE. como PyCharm para que puedan proporcionar información sobre la función. es incluso disponible para el programador a través de una propiedad muy especial de la función llamada doc al que se puede acceder a través del nombre de la función utilizando la notación de puntos: que genera 11.8 Parámetros de función Antes de continuar, vale la pena aclarar alguna terminología asociada con pasar datos a funciones. Esta terminología se relaciona con los parámetros definidos como parte del encabezado de la función y los datos pasados a la función a través de estos parámetros: • Un parámetro es una variable definida como parte del encabezado de la función y se utiliza para hacer que los datos estén disponibles dentro de la propia función. • Un argumento es el valor real o los datos pasados a la función cuando se llamado. Los datos se mantendrán dentro de los parámetros. Desafortunadamente, muchos desarrolladores usan estos términos indistintamente, pero vale la pena siendo claro en la distinción. 11.8.1 Funciones de parámetros múltiples Hasta ahora las funciones que hemos definido solo han tenido cero o un parámetro; sin embargo, eso fue solo una elección. Podríamos haber definido fácilmente una función que definido dos o más parámetros. En estas situaciones, la lista de parámetros contiene una lista de nombres de parámetros separados por una coma. imprimir (obtener_entrada_entero.doc) Esta función mostrará el mensaje al usuario. y solicite que ingresen un número entero. Si el usuario ingresa algo que no es un número entonces la entrada será rechazada y se mostrará un mensaje de error. A continuación, se le pedirá al usuario que vuelva a intentarlo. 124 11 Funciones en Python
Por ejemplo Aquí la función de saludo toma define dos parámetros; nombre y mensaje. Estos parámetros (que son locales a la función y no se pueden ver fuera de la función) se utilizan luego dentro del cuerpo de la función. la salida es Puede tener cualquier número de parámetros definidos en una función (antes de Python 3.7 había un límite de 256 parámetros, aunque si tiene tantos, entonces probablemente tenga un problema importante con el diseño de su función; sin embargo, esto el límite ahora se ha ido). 11.8.2 Valores de parámetros predeterminados Una vez que tenga uno o más parámetros, es posible que desee proporcionar valores predeterminados para algunos o todos esos parámetros; particular para los que no se pueden utilizar en la mayoría casos. Esto se puede hacer muy fácilmente en Python; todo lo que se requiere es que el valor predeterminado El valor debe declararse en el encabezado de la función junto con el nombre del parámetro. Si se proporciona un valor para el parámetro, se anulará el valor predeterminado. Si no El valor se proporciona cuando se llama a la función, luego se utilizará el valor predeterminado. Por ejemplo, podemos modificar la función de saludo() de la sección anterior. para proporcionar un mensaje predeterminado como ‘Live Long and Prosper’. Ahora podemos llamar a la función de saludo() con uno o dos argumentos. Cuando ejecutemos este ejemplo, obtendremos: def saludador(nombre, mensaje): print(‘Bienvenido’, nombre, ‘-’, mensaje) saludador(‘Eloise’, ‘Espero que te guste el rugby’) Bienvenida Eloise - Espero que te guste el Rugby def saludador(nombre, mensaje = ‘Live Long and Prosper’): print(‘Bienvenido’, nombre, ‘-’, mensaje) saludador(‘Eloise’) saludador(‘Eloise’, ‘Espero que te guste Python’) 11.8 Parámetros de función 125
Como puede ver en esto en el primer ejemplo (donde solo un argumento fue proporcionado) se utilizó el mensaje predeterminado. Sin embargo, en el segundo ejemplo donde un se proporcionó el mensaje, junto con el nombre, luego se usó ese mensaje en lugar de el valor por defecto. Tenga en cuenta que podemos usar los términos obligatorio y opcional para los parámetros en Saludador(). En este caso • el nombre es un campo/parámetro obligatorio • mensaje es un campo/parámetro opcional (ya que tiene un valor predeterminado). Un punto sutil a tener en cuenta es que cualquier número de parámetros en una función la lista de parámetros puede tener un valor predeterminado; sin embargo, una vez que un parámetro tiene un valor predeterminado valor todos los parámetros restantes a la derecha de ese parámetro también deben tener valores predeterminados valores. Por ejemplo, no podríamos definir la función de saludo como Como esto generaría un error indicando que el nombre debe tener un valor predeterminado ya que viene después (a la derecha) de un parámetro con un valor predeterminado. 11.8.3 Argumentos con nombre Hasta ahora nos hemos basado en la posición de un valor que se usará para determinar qué parámetro al que se asigna el valor. En muchos casos esta es la forma más sencilla y limpia. opción. Sin embargo, si una función tiene varios parámetros, algunos de los cuales tienen valores, puede volverse imposible confiar en el uso de la posición de un valor para asegurar se le da al parámetro correcto (porque es posible que queramos usar algunos de los parámetros predeterminados). valores en su lugar). Por ejemplo, supongamos que tenemos una función con cuatro parámetros Bienvenida Eloise - Vive Larga y Prosperamente Bienvenida Eloise - Espero que te guste Python def saludador(mensaje = ‘Live Long and Prosper’, nombre): print(‘Bienvenido’, nombre, ‘-’, mensaje) def saludador(nombre, título = ‘Dra’, aviso = ‘Bienvenido’, mensaje = ‘Live Long and Prosper’): imprimir (mensaje, título, nombre, ‘-’, mensaje) 126 11 Funciones en Python
Esto ahora plantea la pregunta de cómo proporcionamos el nombre y el mensaje. argumentos cuando nos gustaría tener el título y el indicador predeterminados? La respuesta es usar argumentos con nombre (o argumentos de palabras clave). En este enfoque proporcionamos el nombre del parámetro al que queremos que se le asigne un argumento/valor; la posición ya no es relevante. Por ejemplo: En este ejemplo estamos usando los valores predeterminados para título y mensaje y han cambiado el orden del mensaje y el nombre. Esto es completamente legal y da como resultado la siguiente salida: De hecho, podemos mezclar argumentos posicionales y con nombre en Python, por ejemplo: Aquí ‘Lloyd’ está ligado al parámetro de nombre ya que es el primer parámetro, pero ‘Nosotros como Python’ está vinculado al parámetro del mensaje, ya que es un argumento con nombre. Sin embargo, no puede colocar argumentos posicionales después de un argumento con nombre, por lo que no puede escribir: Como esto dará como resultado que Python genere un error. 11.8.4 Argumentos arbitrarios En algunos casos, no sabe cuántos argumentos se proporcionarán cuando un se llama la función. Python le permite pasar un número arbitrario de argumentos a una función y luego procesar esos argumentos dentro de la función. Para definir una lista de parámetros como de longitud arbitraria, se marca un parámetro con un asterisco (*). Por ejemplo: saludador (mensaje = ‘Nos gusta Python’, nombre = ‘Lloyd’) Bienvenido Dr. Lloyd - Nos gusta Python saludador(‘Lloyd’, mensaje = ‘Nos gusta Python’) saludador(nombre=‘John’, ‘Nos gusta Python’) def saludador(*argumentos): para nombre en args: imprimir(‘Bienvenido’, nombre) saludador (‘John’, ‘Denise’, ‘Phoebe’, ‘Adam’, ‘Gryff’, ‘Jasmine’) 11.8 Parámetros de función 127
Esto genera Tenga en cuenta que este es otro uso del bucle for; pero esta vez es una secuencia de cadenas en lugar de una secuencia de enteros que se está utilizando. 11.8.5 Argumentos posicionales y de palabras clave Algunas funciones en Python están definidas de manera que los argumentos de los métodos pueden puede proporcionarse utilizando un número variable de argumentos posicionales o de palabras clave. Estas funciones tienen dos argumentos *args y **kwargs (para argumentos posicionales). mentos y argumentos de palabras clave). Son útiles si no sabe exactamente cuántos de cada posición o Se proporcionarán argumentos de palabras clave. Por ejemplo, la función my_function toma un número variable de Argumentos posicionales y de palabras clave: Esto se puede llamar con cualquier número de argumentos de cualquier tipo: bienvenido juan Bienvenida Denise Bienvenida Phoebe Bienvenido Adán Bienvenido Gryff Bienvenida jazmín def mi_función(*argumentos, **kwargs): para arg en args: imprimir (‘argumento:’, argumento) para clave en kwargs.keys(): print(‘clave:’, clave, ’tiene valor: ‘, kwargs[clave]) mi_funcion(‘Juan’, ‘Denise’, hija=‘Phoebe’, hijo=‘Adan’) imprimir(’-’ * 50) my_function(‘Paul’, ‘Fiona’, son_number_one=‘Andrew’, son_number_two=‘James’, hija=‘Joselyn’) 128 11 Funciones en Python
Lo que produce la salida: También tenga en cuenta que las palabras clave utilizadas para los argumentos no son fijas. También puede definir métodos que solo usen uno de los *args y **kwargs dependiendo de sus requisitos (como vimos con la función de saludo () arriba), Por ejemplo: En este caso, la función nombrada solo admite la provisión de palabras clave argumentos Su salida en el caso anterior es: En general, es más probable que estas instalaciones sean utilizadas por aquellos que crean bibliotecas como permiten una gran flexibilidad en el uso de la biblioteca. 11.9 Funciones anónimas Todas las funciones que hemos definido en este capítulo han tenido un nombre que pueden ser referenciado por, como saludador o get_integer_input, etc. Esto significa que podemos hacer referencia y reutilizar estas funciones tantas veces como queramos. argumento: Juan arg: denise clave: hijo tiene valor: Adán clave: hija tiene valor: Phoebe
argumento: Pablo arg: Fiona clave: son_number_one tiene valor: Andrew clave: son_number_two tiene valor: James clave: hija tiene valor: Joselyn def named(**kwargs): para clave en kwargs.keys(): print(‘argumento:’, clave, ’tiene valor:’, kwargs[clave]) nombrado(a=1, b=2, c=3) arg: a tiene valor: 1 arg: c tiene valor: 3 arg: b tiene valor: 2 11.8 Parámetros de función 129
Sin embargo, en algunos casos queremos crear una función y usarla solo una vez; donación un nombre para esta vez puede contaminar el espacio de nombres del programa (es decir, hay muchos nombres alrededor) y también significa que alguien podría llamarlo cuando no lo hacemos. espera que lo hagan. Python, por tanto, otra opción a la hora de definir una función; es posible definir una función anónima. En Python, una función anónima es aquella que no tiene un nombre y sólo se puede utilizar en el punto en que se define. Las funciones anónimas se definen utilizando la palabra clave lambda y para ello razón por la que también se conocen como funciones lambda. La sintaxis utilizada para definir una función anónima es: Las funciones anónimas pueden tener cualquier número de argumentos pero solo uno expresión (es decir, una declaración que devuelve un valor) como su cuerpo. la expresión es ejecutado, y el valor generado a partir de él se devuelve como resultado de la función. Como ejemplo, definamos una función anónima que elevará al cuadrado un número: En este ejemplo, la definición lambda indica que hay un parámetro para el función anónima (‘i’) y que el cuerpo de la función se define después de los dos puntos ‘:’ que múltiplos i * i; cuyo valor se devuelve como resultado de la función. Luego, toda la función anónima se almacena en una variable llamada doble. Podemos almacenar la función anónima en la variable ya que todas las funciones son instancias de la función de clase y se puede hacer referencia de esta manera (simplemente no hemos hecho esto hasta ahora). Para invocar la función, podemos acceder a la referencia a la función contenida en el variable doble y luego use los corchetes para hacer que la función sea ejecutado, pasando los valores que se utilizarán para los parámetros: Cuando se ejecuta esto, se imprime el valor 100. A continuación se dan otros ejemplos de funciones lambda/anónimas (que ilustran que una función anónima puede tomar cualquier número de argumentos): argumentos lambda: expresión doble = lambda yo : yo * yo imprimir (doble (10)) func0 = lambda: print(‘sin argumentos’) func1 = lambda x: x * x func2 = lambda x, y: x * y func3 = lambda x, y, z: x + y + z 130 11 Funciones en Python
Estos se pueden utilizar como se muestra a continuación: El resultado de este fragmento de código es: 11.10 Recursos en línea Consulte la documentación de la biblioteca estándar de Python para: • https://docs.python.org/3/library/functions.html para obtener una lista de funciones integradas en Pitón. • https://www.w3schools.com/python/python_functions.asp el resumen de W3 Schools Introducción a las funciones de Python. • https://www.w3schools.com/python/python_lambda.asp un breve resumen de funciones lambda. 11.11 Ejercicios Para este capítulo, los ejercicios involucran el number_guess_game que creó en el último capítulo: Tome el juego de adivinar números y divídalo en varias funciones. Hay no necesariamente una forma correcta o incorrecta de hacer esto; buscar funciones que sean significativas útil para usted dentro del código, por ejemplo:
- Podría crear una función para obtener información del usuario.
- Podrías crear otra función que implementará el juego principal. bucle.
- También puede proporcionar una función que imprima un mensaje que indique si el jugador ganó o no.
- Podrías crear una función para imprimir un mensaje de bienvenida cuando comience el juego arriba. función0() imprimir (func1 (4)) imprimir (func2 (3, 4)) imprimir (func3 (2, 3, 4)) sin argumentos dieciséis 12 9 11.9 Funciones anónimas 131
Capítulo 12 Alcance y vida útil de las variables 12.1 Introducción Ya hemos definido varias variables en los ejemplos que hemos estado trabajando con en este libro. En la práctica, la mayoría de estas variables han sido lo que se conoce como variables globales. Es decir, son (potencialmente) accesibles en cualquier lugar (o globalmente) en nuestros programas. En este capítulo veremos las variables locales como se definen dentro de una función, en variables globales y cómo pueden ser referenciadas dentro de una función y finalmente considerará variables no locales. 12.2 Variables locales En la práctica, los desarrolladores suelen tratar de limitar el número de variables globales en sus Se puede acceder a los programas como variables globales desde cualquier lugar y se pueden modificar en cualquier lugar y esto puede resultar en comportamientos inesperados (y ha sido la causa de muchos, muchos errores en todo tipo de programas a lo largo de los años). Sin embargo, no todas las variables son globales. Cuando definimos una función, podemos crear Variables que tienen un alcance solo para esa función y no son accesibles o visibles. fuera de la función. Estas variables se conocen como variables locales (ya que son local a la función). Esta es una gran ayuda en el desarrollo de código más modular que se ha demostrado que ser más fácil de mantener y, de hecho, desarrollar y probar. En la siguiente función se ha creado una variable local llamada a_variable e inicializado para mantener el valor 100. © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_12 133
Cuando se llama a esta función, a_variable se inicializará a 100 y luego se imprimirá en la consola: 100 Por lo tanto, cuando ejecutamos my_function(), imprimió con éxito el valor 100 que se mantuvo en la variable local (a la función) a_variable. Sin embargo, si intentamos acceder a una_variable fuera de la función, entonces no estar definido y generaremos un error, por ejemplo: Cuando ejecutamos este código, obtenemos el número 100 impreso de la llamada al mi_funcion(). Sin embargo, Python informa un error: Esto indica que a_variable no está definida en el nivel superior (que es el alcance global). Por tanto, podemos decir que una_variable no está definida globalmente. Esto se debe a que a_variable solo existe y solo tiene significado dentro mi_funcion; fuera de esa función no se puede ver. De hecho, cada vez que se llama a la función, una_variable vuelve a existir. tenencia como una nueva variable, por lo que el valor en a_variable ni siquiera se ve desde uno invocación de la función a otra. Esto plantea la pregunta de qué sucede si una variable global llamada a_variable esta definido? Por ejemplo, si tenemos lo siguiente: En realidad, esto está bien y es compatible con Python. Ahora hay dos versiones de a_variable en el programa; uno de los cuales se define globalmente y uno de los cuales es definida dentro del contexto de la función. Python no se confunde entre estos y los trata como completamente por separado. Esto es como tener dos personas llamadas John en la misma clase en escuela. Si solo se llamaran John, esto podría causar cierta confusión, pero si tienen apellidos diferentes, entonces es fácil distinguirlos a través de su nombre completo nombres como John Jones y John Smith. a_variable = 25 mi_funcion() imprimir (una_variable) 100 Rastreo (llamadas recientes más última): Archivo “localvars.py”, línea 7, en <módulo> imprimir (una_variable) NameError: el nombre ‘a_variable’ no está definido mi_funcion() imprimir (una_variable) def mi_funcion(): a_variable = 100 imprimir (una_variable) mi_funcion() 134 12 Alcance y vida útil de las variables
En este caso tenemos global a_variable y my_function a_variable. Por lo tanto, si ejecutamos el código anterior, obtenemos El valor 100 no sobrescribe el valor 25 ya que son completamente diferentes variables 12.3 La palabra clave global Pero que pasa si lo que quieres es referenciar la variable global dentro de un función. Siempre que Python no crea que ha definido una variable local, todo lo hará. estar bien Por ejemplo Esto imprime el valor 100. Sin embargo, las cosas se tuercen un poco si intentas modificar la variable global dentro del función. En este punto, Python cree que está creando una variable local. Si como parte de la asignación que intenta hacer referencia al valor actual de esa (ahora) variable local obtendrá un error que indica que actualmente no tiene un valor. Por ejemplo, si escribimos: Y luego ejecute este ejemplo, obtendremos Indicando que hemos hecho referencia a max antes de que se le asignara un valor, incluso ¡aunque se le asignó un valor globalmente antes de llamar a la función! 100 25 máx = 100 def imprimir_max(): imprimir (máximo) imprimir_max() def imprimir_max(): máx = máx + 1 imprimir (máximo) imprimir_max() Rastreo (llamadas recientes más última): Archivo “localvars.py”, línea 17, en <módulo> imprimir_max() Archivo “localvars.py”, línea 14, en print_max máx = máx + 1 UnboundLocalError: variable local ‘max’ referenciada antes asignación 12.2 Variables locales 135
¿Por qué hace esto? Para protegernos de nosotros mismos, Python en realidad está diciendo ‘¿Tienes ¿Realmente quieres modificar una variable global aquí?’. En cambio, está tratando a max como un variable local y, como tal, se hace referencia a ella antes de que se le asigne un valor. Para decirle a Python que sabemos lo que estamos haciendo y que queremos hacer referencia al variable global en este punto necesitamos usar la palabra clave global con el nombre de La variable. Por ejemplo: Ahora, cuando tratamos de actualizar la variable max dentro de la función print_max(), Python sabe que nos referimos a la versión global de la variable y usa esa. El resultado es que ahora imprimimos el valor 101 y max se actualiza a 101 para todos ¡en todos lados! 12.4 Variables no locales Es posible definir funciones dentro de otras funciones, y esto puede ser muy útil cuando estamos trabajando con colecciones de datos y operaciones como map() (que asigna una función a todos los elementos de una colección de datos). Sin embargo, las variables locales son locales para una función específica; incluso funciones definidas dentro de otra función no puede modificar las funciones externas variables locales (como el función interna es una función separada). Pueden hacer referencia a él, al igual que nosotros podríamos hacer referencia a la variable global anterior; el problema es nuevamente la modificación. La palabra clave global no es de ayuda aquí ya que las variables de la función externa no son globales, son locales para una función. Por ejemplo, si definimos una función anidada (interna) dentro de la función externa principal (exterior) y queremos que la función interna modifique el campo local, tenemos un problema: máx = 100 def imprimir_max(): máximo global máx = máx + 1 imprimir (máximo) imprimir_max() imprimir (máximo) def exterior(): título = ’título original’ def interior(): título = ‘otro título’ imprimir(‘interior:’, titulo) interno() imprimir(’exterior:’, titulo) exterior() 136 12 Alcance y vida útil de las variables
En este ejemplo, tanto la función externa() como la interna() modifican la variable del título. capaz. Sin embargo, no son la misma variable de título y mientras esto sea lo que necesito entonces que está bien; ambas funciones tienen su propia versión de una variable local de título. Esto se puede ver en la salida donde la función externa mantiene su propio valor por titulo: Sin embargo, si lo que queremos es que la función inner() modifique la función outside() variable de título de la función, entonces tenemos un problema. Este problema se puede resolver usando la palabra clave nonlocal. Esto indica que un La variable no es global pero tampoco es local para la función actual y Python debería mire dentro del ámbito en el que se define la función para financiar una variable local con el mismo nombre: Si ahora declaramos el título como no local en la función inner(), entonces será use la versión de título de funciones externas () (se compartirá entre ellos) y por lo tanto, cuando la función interior () cambia el título, lo cambiará para ambos funciones: El resultado de ejecutar esto es 12.5 Sugerencias Puntos a tener en cuenta sobre el alcance y la duración de las variables
- El alcance de una variable es la parte de un programa donde se conoce la variable. Los parámetros y variables definidos dentro de una función no son visibles desde el exterior. Por lo tanto, tienen un alcance local.
- La vida útil de una variable es el período durante el cual la variable sale en el memoria de su programa Python. El tiempo de vida de las variables dentro de una función es mientras la función se ejecute. Estas variables locales se destruyen tan pronto como la función regresa o termina. Esto significa que la función no almacena los valores en una variable de una invocación a otra. def exterior(): título = ’título original’ def interior(): título no local título = ‘otro título’ imprimir(‘interior:’, titulo) interno() imprimir(’exterior:’, titulo) exterior() interior: otro título exterior: otro título interior: otro título exterior: título original 12.4 Variables no locales 137
12.6 Recursos en línea Consulte la documentación de la biblioteca estándar de Python para: • https://docs.python.org/3/faq/programming.html#what-are-the-rules-for-local- and-global-variables-in-python que proporciona más información sobre el Reglas de Python para variables locales y globales. 12.7 Ejercicio Vuelva al juego de adivinar números: ¿tuvo que hacer algún compromiso con las variables para superar el problema de la variable global? Si es así, ¿puedes resolverlos ahora? con el uso de la global? 138 12 Alcance y vida útil de las variables
Capítulo 13 Implementando una calculadora usando Funciones 13.1 Introducción En este capítulo, veremos paso a paso el desarrollo de otro programa de Python; esta vez, el programa proporcionará una calculadora simple que se puede usar para sumar, restar, multiplicar y dividir números. La implementación de la calculadora es basarse en la descomposición de funciones realizada anteriormente en el libro en el Introducción al capítulo de Análisis Estructurado. La calculadora se implementará utilizando funciones de Python para ayudar a modularizar el código. 13.2 Qué hará la calculadora Esta será una aplicación puramente impulsada por comandos que permitirá al usuario especificar • la operación a realizar y • los dos números a utilizar con esa operación. Cuando el programa se inicia, puede usar un bucle para seguir procesando las operaciones hasta que el usuario indica que desea terminar la aplicación. También podemos usar una declaración if para seleccionar la operación a realizar, etc. Como tal, también se basará en varias otras funciones de Python que tenemos ya ha estado trabajando con. © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_13 139
13.3 Empezando El primer paso será crear un nuevo archivo de Python. Si está utilizando el IDE de PyCharm puede hacerlo usando la opción de menú New>PythonFile (vea el número adivina el capítulo del juego si no recuerdas cómo hacerlo). El archivo se puede llamar lo que quieras, pero calculadora parece un nombre razonable. En el archivo calculadora.py recién creado (y vacío), escriba una impresión de bienvenida mensaje como: print(‘Aplicación de calculadora simple’) Ahora ejecute el programa calculadora.py (nuevamente si no recuerda cómo hacer eso mira hacia atrás en el capítulo Juego de adivinanzas de números). Debería ver el mensaje impreso en la consola de Python. Esto verifica que el archivo se ha creado correctamente y que puede ejecutar el código de Python que defina en ella. 13.4 Las operaciones de la calculadora Vamos a empezar definiendo un conjunto de funciones que implementarán las funciones add, Operaciones de resta, multiplicación y división. Todas estas funciones toman dos números y devuelven otro número. Tenemos también le dio a cada función una cadena de documentación para ilustrar su uso, aunque en la práctica el Las funciones son tan simples y autodescriptivas que la cadena de documentación probablemente sea redundante. Las funciones se enumeran a continuación; ahora puede agregarlos al archivo calculadora.py: def suma(x, y): """" Suma dos números """ volver x + y def restar(x, y): """ Resta dos números """ volver x - y def multiplicar(x, y): """ Múltiplos de dos números """ volver x * y def divide(x, y): “““Dividir dos números””” volver x / y Ahora tenemos las funciones básicas que necesita la calculadora. 140 13 Implementar una calculadora usando funciones
13.5 Comportamiento de la calculadora No podemos explorar cuál debería ser el funcionamiento del programa calculadora. Esencialmente, queremos permitir que el usuario pueda seleccionar la operación que desea para realizar, proporcione los dos números a utilizar con la operación y luego para el programa para llamar a la función apropiada. El resultado de la operación debe ser entonces presentado al usuario. Luego queremos preguntarle al usuario si desea continuar usando la calculadora. o para salir del programa. Esto se ilustra a continuación en forma de diagrama de flujo: Con base en este diagrama de flujo, podemos establecer el esqueleto de la lógica para el ciclo de procesamiento de la calculadora. Necesitaremos un ciclo while para determinar si el usuario ha terminado o no. y una variable para contener el resultado e imprimirlo. El siguiente código proporciona este esqueleto. terminado = falso mientras no termine: resultado = 0 # Obtener la operación del usuario # Obtener los números del usuario # Seleccione la operación imprimir(‘Resultado:’, resultado) imprimir(’=================’) # Determinar si el usuario ha terminado imprimir(‘adiós’) 13.5 Comportamiento de la calculadora 141
Si intenta ejecutar esto ahora mismo, encontrará que este código se repetirá para siempre ya que aún no se le solicita al usuario que diga si desea continuar o no. De todos modos, eso proporciona el marco básico; tenemos • una variable, terminado, con una bandera booleana para indicar si el usuario ha terminado o no. Esto se conoce como una bandera porque es un valor booleano y porque está siendo se utiliza para determinar si terminar o no el bucle de procesamiento principal. • una variable para contener el resultado de la operación y los dos números. • el bucle while que representa el bucle de procesamiento principal de la calculadora. 13.6 Identificar si el usuario ha terminado Podríamos abordar varias de las áreas restantes a continuación; sin embargo, seleccionaremos el último paso—el de determinar si el usuario ha terminado o no. Esto nos permitirá comience a ejecutar la aplicación para que podamos probar el comportamiento. Para hacer esto, debemos pedirle al usuario que le pregunte si desea continuar usando la calculadora. En un nivel, esto es muy sencillo; podríamos pedirle al usuario que ingrese ‘y’ o ’n’ para indica sí he terminado o no quiero continuar. Por lo tanto, podríamos usar la función de entrada de la siguiente manera: user_input = input(’¿Quieres terminar (s/n): ‘) Entonces podríamos verificar si han ingresado un carácter ‘y’ y terminar el bucle. Sin embargo, cada vez que tomamos una entrada de algo fuera de nuestro programa (como el usuario) debemos verificar la entrada. Por ejemplo, ¿qué debe hacer el programa? hacer si el usuario ingresa ‘x’ o el número ‘1’? Una opción es tratar cualquier cosa que no sea un ‘y’ como ser: quiero seguir adelante. Sin embargo, esto está abriendo nuestro programa simple a malas prácticas y (en un sistema mucho más grande) a posibles problemas de seguridad y ciertamente a posibles ataques de piratas informáticos. Es una idea mucho mejor verificar que la entrada es lo que se espera y rechazar cualquier entrada hasta que sea una ‘y’ o una ’n’. Esto significa que el código es más complejo que una sola declaración de entrada; hay por ejemplo, un bucle implícito aquí y también alguna idea de validación de entrada. Esto significa que este es un candidato ideal para una función que encapsulará este comportamiento en una operación separada. Entonces podemos probar esta función que siempre es una buena idea. También significa que donde usamos la función, tenemos un nivel de abstracción. Es decir, podemos nombrar la función apropiadamente, lo que la hará más fácil ver lo que pretendíamos, en lugar de tener una gran cantidad de código en un solo lugar. Llamaremos a la función comprobar_si_el_usuario_ha_terminado; este nombre hace muy claro cuál es el propósito de la función. También significa que cuando lo usamos en nuestro bucle de procesamiento principal, su papel en ese bucle será obvio. 142 13 Implementar una calculadora usando funciones
La función se da a continuación: def comprobar_si_el_usuario_ha_terminado(): """ Comprueba que el usuario quiere terminar o no. Realiza alguna verificación de la entrada.""" ok_to_finish = Verdadero user_input_accepted = Falso mientras no se acepta la entrada del usuario: user_input = input(’¿Quieres terminar (s/n): ‘) si entrada_usuario == ‘y’: user_input_accepted = Verdadero elif entrada_usuario == ’n’: ok_to_finish = Falso user_input_accepted = Verdadero demás: print(‘La respuesta debe ser (s/n), inténtelo de nuevo’) volver ok_to_finish Observe el uso de dos variables que son locales a la función: • la primera variable (ok_to_nish) contiene el resultado de la función; si se trata de Está bien terminar o no. Se le da un valor predeterminado de True; esto sigue al fail enfoque cerrado, lo que sugiere que siempre es mejor fallar cerrando una aplicación o conexión. En este caso significa que si algo sale mal con el código (si contiene un error de software o un error de lógica) el usuario no mantendrá dando vueltas para siempre. • la segunda variable (user_input_accepted) se utiliza para indicar si el usuario ha proporcionado una entrada aceptable o no (es decir, ha ingresado ‘y’ o ’n’) hasta que hagan el bucle dentro, la función se repetirá. El bucle en sí es interesante ya que estamos haciendo un bucle mientras la entrada del usuario no ha sido aceptado; tenga en cuenta que podemos (casi) leer el bucle while como texto en inglés sin formato. Esto es tanto una característica de Python (está destinado a ser fácilmente legible) y también del uso de un nombre significativo para la propia variable. Dentro del ciclo obtenemos la entrada del usuario; verifique si es ‘y’ o ’n’. Si es cualquiera de estas opciones, establecemos el indicador user_input_accepted en True. De lo contrario, el código imprimirá un mensaje que indica que el único aceptable la entrada es una ‘y’ o ’n’. Tenga en cuenta que solo establecemos la variable ok_to_nish en False si el usuario ingresa un’; esto se debe a que la variable ok_to_finish por defecto tiene un valor de True y, por lo tanto, no hay necesidad de reasignar True si el usuario selecciona ’n’. Ahora podemos agregar esta función a nuestro bucle de procesamiento principal en lugar de la última comentario: 13.6 Identificar si el usuario ha terminado 143
terminado = falso mientras no termine: resultado = 0 # Obtener la operación del usuario # Obtener los números del usuario # Seleccione la operación imprimir(‘Resultado:’, resultado) imprimir(’=================’) terminado = verificar_si_el_usuario_ha_terminado(() imprimir(‘adiós’) Ya podemos ejecutar la aplicación. Tal vez se pregunte por qué haríamos esto en este momento, ya que todavía no hace nada. cálculos para nosotros; la respuesta es que podemos verificar que el comportamiento general del el bucle principal funciona y que la función check_if_user_has_finished() funciona correctamente. 13.7 Selección de la operación A continuación, implementemos la función utilizada para obtener la operación a realizar. Nuevamente, queremos nombrar esta función de tal manera que ayude con la com- prensibilidad de nuestro programa. En este caso, le estamos pidiendo al usuario que seleccione qué operación ellos desear a llevar a cabo, entonces vamos llamar el función get_operation_choice. Esta vez necesitamos presentar una lista de opciones al usuario y luego pedirles que Has una elección. Una vez más, queremos escribir nuestra función a la defensiva, de modo que haga asegúrese de que el usuario solo ingrese una opción válida; si no lo hacen, la función solicita ellos para otra entrada. Esto significa que nuestra función tendrá un bucle y algunas validaciones. código de dación. Hay cuatro opciones disponibles para el usuario: Sumar, Restar, Multiplicar y Dividir. Entonces numeraremos del 1 al 4 y le pediremos al usuario que seleccione una opción entre 1 y 4 Hay varias formas en las que podemos comprobar que han introducido un número en este rango, incluyendo • convertir la cadena ingresada en un número y usar la comparación numérica (pero luego debemos verificar que ingresaron un número entero), • tener múltiples sentencias if y elif (pero eso parece un poco largo) o • comprobando que el carácter introducido es uno de un conjunto de valores (que es el enfoque que usaremos). 144 13 Implementar una calculadora usando funciones
Para verificar que un valor está en un conjunto de otro valor (que es uno de los valores en el set) puede usar el operador ‘in’, por ejemplo: selección_usuario en (‘1’, ‘2’, ‘3’, ‘4’) Esto devolverá True si (y solo si) user_selection contiene uno de los cadenas ‘1’, ‘2’, ‘3’ o ‘4’. Por lo tanto, podemos usarlo en nuestra función para verificar que el usuario ingresó un aporte. La función get_operation_choice se muestra a continuación: def get_operation_choice(): input_ok = Falso mientras no input_ok: print(‘Las opciones de menú son:’) imprimir(’\t1. Añadir’) imprimir(’\t2. Restar’) imprimir(’\t3. Multiplicar’) imprimir(’\t4. Dividir’) imprimir(’—————–’) user_selection = input(‘Por favor haga una selección:’) si user_selection en (‘1’, ‘2’, ‘3’, ‘4’): input_ok = Verdadero demás: print(‘Entrada inválida (debe ser 1 - 4)’) imprimir(’—————–’) volver user_selection Trabaje con esta función y asegúrese de sentirse cómodo con todas sus funciones. elementos. El carácter ‘\t’ es un carácter especial que denota una pestaña. Ahora podemos actualizar nuestro ciclo de calculadora principal con esta función: terminado = falso mientras no termine: resultado = 0 opción_menú = obtener_opción_operación() # Obtener los números del usuario # Seleccione la operación imprimir(‘Resultado:’, resultado) imprimir(’=================’) terminado = verificar_si_el_usuario_ha_terminado(() imprimir(‘adiós’) 13.7 Selección de la operación 145
13.8 Obtención de los números de entrada A continuación, necesitamos obtener dos números del usuario para usar con el seleccionado operación. En nuestra introducción a Funciones en el capítulo de Python vimos una función (la función get_integer_input()) que podría usarse para recibir información del usuario y convertirlo (de forma segura) en un número entero; si el usuario ingresó un no-número entonces esto la función les pedirá que introduzcan un número real. Podemos reutilizar la función aquí. Sin embargo, necesitamos pedirle al usuario dos números; por lo tanto, crearemos un función que utiliza la función get_integer_input() para solicitar al usuario dos números y luego devolver ambos números. Ambas funciones se muestran aquí: def obtener_números_del_usuario(): num1 = get_integer_input(‘Ingrese el primer número:’) num2 = get_integer_input(‘Ingrese el segundo número:’) devuelve num1, num2 def get_integer_input(mensaje): value_as_string = entrada (mensaje) mientras que no value_as_string.isnumeric(): print(‘La entrada debe ser un entero’) value_as_string = entrada (mensaje) devuelve int(valor_como_cadena) Hacer que una función llame a otra función es muy común y, de hecho, tenemos ya he estado haciendo esto; la función input() se ha utilizado varias veces, el la única diferencia aquí es que hemos escrito get_integer_input() funcionar nosotros mismos. Cuando podamos la función get_numbers_from_user() podemos almacenar el resultados devueltos en dos variables; uno para cada resultado; Por ejemplo: n1, n2 = obtener_números_del_usuario() Ahora podemos agregar esta declaración al ciclo principal de la calculadora: terminado = falso mientras no termine: resultado = 0 opción_menú = obtener_opción_operación() n1, n2 = obtener_números_del_usuario() # Seleccione la operación imprimir(‘Resultado:’, resultado) imprimir(’=================’) terminado = verificar_si_el_usuario_ha_terminado(() imprimir(‘adiós’) 146 13 Implementar una calculadora usando funciones
13.9 Determinación de la operación a ejecutar Ya casi llegamos y podemos actualizar nuestro bucle de cálculo principal con algo de lógica para determinar la operación real a invocar. Para ello utilizaremos una sentencia if con las piezas elif opcionales. La sentencia if estará condicionada a la operación seleccionada y luego llamará a la función apropiada (como sumar, restar, etc.) como se muestra aquí: si opción_menú == ‘1’: resultado = suma(n1, n2) elif menu_choice == ‘2’: resultado = restar(n1, n2) elif menu_choice == ‘3’: resultado - multiplicar(n1, n2) elif menu_choice == ‘4’: resultado = dividir(n1, n2) Cada parte de la instrucción if llama a una función diferente; pero todos almacenan el valor devuelto en la variable de resultado. Ahora podemos agregar esto al ciclo de cálculo para crear nuestro completamente funcional bucle de calculadora: terminado = falso mientras no termine: resultado = 0 opción_menú = obtener_opción_operación() n1, n2 = obtener_números_del_usuario() si opción_menú == ‘1’: resultado = suma(n1, n2) elif menu_choice == ‘2’: resultado = restar(n1, n2) elif menu_choice == ‘3’: resultado - multiplicar(n1, n2) elif menu_choice == ‘4’: resultado = dividir(n1, n2) imprimir(‘Resultado:’, resultado) imprimir(’=================’) terminado = verificar_si_el_usuario_ha_terminado(() imprimir(‘adiós’) 13.10 Ejecutar la calculadora Si ahora ejecuta la calculadora, se le pedirá que ingrese lo que corresponda. Tú puede intentar romper la calculadora ingresando caracteres cuando se solicitan números, o valores fuera de rango para las operaciones, etc. y debe ser lo suficientemente resistente para manejar estas entradas erróneas, por ejemplo: 13.9 Determinación de la operación a ejecutar 147
Aplicación de calculadora simple Las opciones del menú son:
- Agregar
- Restar
- Multiplicar
- Dividir
Por favor haga una selección: 5 Entrada no válida (debe ser 1 - 4) Las opciones del menú son:
- Agregar
- Restar
- Multiplicar
- Dividir
Por favor haga una selección: 1
Ingrese el primer número: 5 Ingrese el segundo número: 4 resultado: 9
quieres terminar (t/n): y Adiós 13.11 Ejercicios Para este capítulo, los ejercicios se relacionan con extensiones de la calculadora:
- Agregue una opción para aplicar el operador de módulo (%) a los dos números ingresados por el usuario. Esto implicará definir una función apropiada y agregarla como opción al menú. También deberá extender el control principal de la calculadora loop para manejar esta opción.
- Agregue una opción de potencia de (**) a la calculadora.
- Modifique el programa para aceptar números de coma flotante en lugar de enteros simples.
- Permitir la elección del operador de división o del operador de división de enteros (esto tiene ambos ‘/’ y ‘//’ disponibles. 148 13 Implementar una calculadora usando funciones
capitulo 14 Introducción a Funcional Programación 14.1 Introducción Ha habido mucha expectación en torno a la Programación Funcional en los últimos años. Sin embargo, la programación funcional no es una idea nueva y, de hecho, se remonta a la década de 1950 y el lenguaje de programación LISP. Sin embargo, muchas personas no son claro en cuanto a lo que es la programación funcional y, en su lugar, pasar a ejemplos de código y nunca entiendo realmente algunas de las ideas clave asociadas con Functional Programación como Transparencia Referencial. Este capítulo presenta la programación funcional (también conocida como FP) y la clave concepto de Transparencia Referencial (o RT). Una idea a tener en cuenta es que la programación funcional es una codificación de software estilo o enfoque y está separado del concepto de una función en Python. Las funciones de Python se pueden usar para escribir programas funcionales, pero también se pueden usar escribir programas de estilo procedimental; así que no te obsesiones demasiado con la sintaxis que podría usarse o el hecho de que Python tiene funciones todavía. En su lugar, explore la idea de definir un enfoque funcional para su diseño de software. 14.2 ¿Qué es la programación funcional? Wikipedia describe la programación funcional como: … un paradigma de programación, un estilo de construir la estructura y los elementos de la computadora programas, eso trata el cálculo como el evaluación de matematicas funciones y evita el estado y los datos mutables. Hay una serie de puntos a tener en cuenta acerca de esta definición. La primera es que es se centró en el lado computacional de la programación de computadoras. Esto puede parecer obvio, pero la mayor parte de lo que hemos visto hasta ahora en Python se consideraría de naturaleza procesal. © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_14 149
Otra cosa a tener en cuenta es que la forma en que se representan los cálculos enfatiza funciones que generan resultados basados puramente en los datos proporcionados a a ellos. Es decir, estas funciones solo dependen de sus entradas para generar una nueva salida. No generan ningún efecto secundario y no dependen del estado actual de el programa. Como ejemplo de un efecto secundario, si una función almacena un total acumulado en un variable global y otra función utilizó ese total para realizar algún cálculo; entonces la primera función tiene el efecto secundario de modificar una variable global y la segunda depende de algún estado global para su resultado. Tomando cada uno de estos a su vez:
- La Programación Funcional tiene como objetivo evitar efectos secundarios. Una función debe ser reemplazable tomando los datos que recibe y alineando el resultado generado (esto se denomina transparencia referencial). Esto significa que no debe haber efectos secundarios ocultos de la función. Los efectos secundarios ocultos hacen que sea más difícil entender lo que está haciendo un programa y así hacer que la comprensión, el desarrollo ment y mantenimiento más difícil. Las funciones puras tienen los siguientes atributos: • la única salida observable es el valor devuelto. • la única dependencia de salida son los argumentos. • los argumentos se determinan por completo antes de que se genere cualquier salida.
- La Programación Funcional evita conceptos como el de estado. Si alguna operación es depende del estado (potencialmente oculto) del programa o de algún elemento de un programa, entonces su comportamiento puede diferir dependiendo de ese estado. Esto puede que sea más difícil de comprender, implementar, probar y depurar. Como todos estos impactos en la estabilidad y probablemente la confiabilidad de un sistema, las operaciones basadas en estado pueden resultar en el desarrollo de software menos confiable. Como funciones no (no deberían) confiar en cualquier estado dado (solo en los datos que se les dan) deberían como un el resultado sea más fácil de entender, implementar, probar y depurar.
- Funcional Programación promueve inmutable datos. Funcional La programación también tiende a evitar conceptos como datos mutables. Los datos mutables son datos que pueden cambiar su estado. Por el contrario, la inmutabilidad indica que una vez creado, los datos no se pueden cambiar. En Python, las cadenas son inmutables. Una vez tú crea una nueva cadena, no puedes modificarla. Cualquier función que se aplique a una cadena que podría alterar conceptualmente el contenido de la cadena, dar como resultado una nueva Cadena siendo generado. Muchos desarrolladores llevan esto más lejos al tener una presunción de inmutabilidad en su código; eso significa que, por defecto, todos los tipos de almacenamiento de datos son implementado como inmutable. Esto asegura que las funciones no pueden tener un lado oculto. efectos y por lo tanto simplifica la programación en general.
- La programación funcional promueve la programación declarativa, lo que significa que la programación se orienta en torno a expresiones que describen la solución en lugar de centrarse en el enfoque imperativo de la mayoría de la programación procedimental idiomas Los lenguajes imperativos enfatizan aspectos de cómo es la solución. derivado. Por ejemplo, un enfoque imperativo para recorrer algunos parámetros tainer e imprimir cada resultado a su vez se vería así: 150 14 Introducción a la Programación Funcional
Mientras que un enfoque de programación funcional se vería así: La Programación Funcional tiene sus raíces en el cálculo lambda, originalmente desarrollado abrió en la década de 1930 para explorar la computabilidad. Muchos lenguajes de programación funcional Por lo tanto, los indicadores pueden considerarse como elaboraciones de este cálculo lambda. Allí tienen Ha habido numerosos lenguajes de programación funcional puros, incluidos Common Lisp, Clojure y Haskell. Python proporciona algo de soporte para escribir en el funcional estilo; particularmente donde los beneficios de la misma son particularmente fuertes (como en procesar varios tipos diferentes de datos). De hecho, cuando se usa juiciosamente, la programación funcional puede ser un gran beneficio y una mejora del conjunto de herramientas disponible para los desarrolladores. Para resumir entonces: • La Programación Imperativa es lo que actualmente se percibe como pro- gramática Es decir, es el estilo de programación utilizado en lenguajes como C, C++, Java y C#, etc. En estos lenguajes, un programador le dice a la computadora qué hacer. Por lo tanto, está orientado en torno a declaraciones de control, construcciones de bucles y asignaciones • La Programación Funcional tiene como objetivo describir la solución, eso es lo que el pro- gram necesita hacer (en lugar de cómo debe hacerse). 14.3 Ventajas de la programación funcional Hay una serie de ventajas significativas en la programación funcional en comparación a la programación imperativa. Éstas incluyen:
- Menos código. Por lo general, una solución de programación funcional requerirá menos código escribir que una solución imperativa equivalente. Como hay menos código para escribir, también hay menos código para entender y mantener. Por lo tanto, es posible que Los programas funcionales no solo son más elegantes de leer, sino que también son más fáciles de actualizar y mantener. Esto también puede conducir a una mayor productividad de los programadores a medida que gastan menos tiempo escribiendo montones de código y menos tiempo leyendo esos montones de código.
- Ausencia de efectos secundarios (ocultos) (Transparencia Referencial). Programación sin efectos secundarios es bueno ya que hace más fácil razonar sobre las funciones (es decir int sizeOfContainer = contenedor.longitud para (int i = 1 a sizeOfContainer) hacer elemento = contenedor.get(i) imprimir (elemento) terminar container.foreach(imprimir) 14.2 ¿Qué es la programación funcional? 151
una función está completamente descrita por los datos que entran y los resultados que regresar). Esto también significa que es seguro reutilizar estas funciones en diferentes situaciones (ya que no tienen efectos secundarios inesperados). También debería ser más fácil para desarrollar, probar y mantener dichas funciones. 3. La recursividad es una estructura de control natural. Los lenguajes funcionales tienden a enfatizar la recursión como una forma de procesar estructuras que usarían alguna forma de construcciones en bucle en un lenguaje imperativo. Aunque normalmente puede implementar la recursión en lenguajes imperativos, a menudo es más fácil hacerlo en funciones lenguas nacionales. También vale la pena señalar que aunque la recursividad es muy expresivo y una excelente manera para que un programador escriba una solución a un problema, no es tan eficiente en tiempo de ejecución como la iteración. Sin embargo, cualquier expresión que pueda ser escrito como una rutina recursiva también se puede escribir usando construcciones de bucle. Los lenguajes de programación funcional a menudo incorporan optimización recursiva final de cola. ciones para convertir rutinas recursivas en iterativas en tiempo de ejecución. Un fin útil función recursiva es aquella en la que lo último que hace una función antes de regresar es llamarse a sí mismo. Esto significa que en lugar de invocar realmente la función y tener que configurar el contexto para esa función, debería ser posible reutilizar el contexto actual y tratarlo de manera iterativa como un bucle alrededor de ese rutina. Así, el programador se beneficia de la construcción recursiva expresiva y los beneficios de tiempo de ejecución de una solución iterativa utilizando el mismo código fuente. Este La opción normalmente no está disponible en los idiomas imperativos. 4. Bueno para crear prototipos de soluciones. Se pueden crear soluciones muy rápidamente para problemas algorítmicos o de comportamiento en un lenguaje funcional. Permitiendo así ideas y conceptos para ser explorados en un estilo de desarrollo rápido de aplicaciones. 5. Funcionalidad modular. La programación funcional es modular en términos de funcionalidad (donde los lenguajes orientados a objetos son modulares en la dimensión de componentes). Por lo tanto, se adaptan bien a situaciones en las que es natural querer para reutilizar o componer el comportamiento de un sistema. 6. La evitación del comportamiento basado en el estado. Como las funciones sólo se basan en su entradas y salidas (y evitar acceder a cualquier otro estado almacenado) exhiben un estilo de programación más limpio y simple. Esta evitación de las políticas basadas en el estado El comportamiento simplifica muchas áreas difíciles o desafiantes de la programación. (como los de las aplicaciones concurrentes). 7. Estructuras de control adicionales. Un fuerte énfasis en el control adicional estructuras como la coincidencia de patrones, la gestión del alcance de las variables, la recursividad de la cola optimizaciones etc 8. Concurrencia y datos inmutables. Como sistemas de programación funcional defienden las estructuras de datos inmutables, es más sencillo construir sistemas concurrentes. Esto se debe a que los datos que se intercambian y a los que se accede son inmutables. Por lo tanto, múltiples subprocesos o procesos en ejecución no pueden afectarse negativamente entre sí. El El modelo Akka Actor se basa en este enfoque para proporcionar un modelo muy limpio para múltiples sistemas concurrentes que interactúan. 9. Evaluación parcial. Dado que las funciones no tienen efectos secundarios, también se convierte en práctico para vincular uno o más parámetros a una función en tiempo de compilación y reutilizar estas funciones con valores enlazados como nuevas funciones que toman menos parámetros. 152 14 Introducción a la Programación Funcional
14.4 Desventajas de la programación funcional Si la programación funcional tiene todas las ventajas descritas anteriormente, ¿por qué no la fuerza dominante que son los lenguajes de programación imperativos? la realidad es que la programación funcional no está exenta de desventajas, que incluyen: • Input-Output es más difícil en un lenguaje puramente funcional. Flujos de entrada-salida alinearse naturalmente con el procesamiento de estilo de flujo, que no encaja perfectamente en el entrada de datos, salida de resultados, naturaleza de los sistemas funcionales. • Las aplicaciones interactivas son más difíciles de desarrollar. Las aplicaciones interactivas son construido a través de ciclos de respuesta de solicitud iniciados por una acción del usuario. De nuevo, estos no se sienta naturalmente dentro del paradigma puramente funcional. • Los programas en ejecución continua, como servicios o controladores, pueden ser más difíciles de desarrollar, ya que se basan naturalmente en la idea de un bucle continuo. • Los lenguajes de programación funcionales han tendido a ser menos eficientes en plataformas de hardware actuales. Esto se debe en parte a que la plataforma de hardware actual formularios no están diseñados con la programación funcional en mente y también porque muchos de los sistemas previamente disponibles estaban enfocados en el ámbito académico comunidad donde el rendimiento fuera y fuera no era el enfoque principal. Sin embargo, esto ha cambiado en gran medida con los lenguajes funcionales modernos. como Scala y Heskell. • No orientado a datos. Un lenguaje funcional puro no se alinea realmente con el necesidades de la naturaleza principalmente orientada a datos de muchos de los sistemas actuales. Muchos (la mayoría) de los sistemas comerciales están orientados en torno a la necesidad de recuperar datos de un base de datos, manipularlo de alguna manera y almacenar esos datos en una base de datos. Semejante los datos se pueden representar de forma natural a través de objetos en un lenguaje orientado a objetos. • Los programadores están menos familiarizados con los conceptos de programación funcional y por lo tanto, les resulta más difícil aprender lenguajes orientados a funciones. • Los lenguajes de programación funcional a menudo son menos intuitivos para el proceso (tradicional). programadores durales que modismos imperativos que pueden hacer que la depuración y el mantenimiento tenencia más difícil. Aunque con el uso de un enfoque funcional en muchos otros Los lenguajes ahora se están volviendo más populares (incluso en Python), esta tendencia está cambiando. • Muchos lenguajes de programación funcional han sido vistos como torre de marfil Idiomas que sólo son utilizados por académicos. Esto ha sido cierto para algunos mayores lenguajes funcionales pero está cambiando cada vez más con el advenimiento de los lenguajes como Scala y con las facilidades proporcionadas en la programación más convencional lenguajes como Python. 14.5 Transparencia Referencial Un concepto importante dentro del mundo de la programación funcional es el de Transparencia referencial. 14.4 Desventajas de la programación funcional 153
Se dice que una operación es referencialmente transparente si puede ser reemplazada por su valor correspondiente, sin cambiar el comportamiento del programa, para un conjunto dado de parámetros Por ejemplo, supongamos que hemos definido la función incremento como mostrado a continuación. Si usamos este ejemplo simple en una aplicación para incrementar el valor 5: Podemos decir que la función es referencialmente transparente (o RT) si siempre regresa el mismo resultado para el mismo valor (es decir, ese incremento (5) siempre devuelve 6): Cualquier función que haga referencia a un valor que ha sido capturado de su no se puede garantizar que el contexto de redondeo y que se puede modificar sea RT. Este puede tener consecuencias significativas para la mantenibilidad del código resultante. Este puede suceder si, por ejemplo, la función de incremento no agregó 1 al parámetro pero agregó un valor global. Si se cambia este valor global, la función sería de repente comienza a devolver diferentes valores para los parámetros ingresados previamente. Para ejemplo, el siguiente código ya no es referencialmente transparente: El resultado de este código es 6 y 7, ya que el valor de la cantidad ha cambiado entre llamadas a la función increment(). Una idea estrechamente relacionada es la de Sin Efectos Secundarios. Es decir, una función no debe tiene efectos secundarios, debe basar su funcionamiento únicamente en los valores que recibe, incremento def(num): devolver número + 1 imprimir (incremento (5)) imprimir (incremento (5)) cantidad = 1 incremento def(num): número de devolución + cantidad imprimir (incremento (5)) cantidad = 2 imprimir (incremento (5)) 154 14 Introducción a la Programación Funcional
y su único impacto debe ser el resultado devuelto. Cualquier efecto secundario oculto de nuevo hacer que el software sea más difícil de mantener. Por supuesto, dentro de la mayoría de las aplicaciones existe una necesidad significativa de efectos secundarios, por ejemplo cualquier registro de las acciones realizadas por un programa tiene un efecto secundario de actualizando alguna información registrada en algún lugar (típicamente en un archivo), cualquier base de datos las actualizaciones tendrán algún efecto secundario (es decir, el de actualizar la base de datos). Además algún comportamiento es inherentemente no RT, por ejemplo, una función que devuelve el la hora actual nunca puede ser referencialmente transparente. Sin embargo, para funciones puras es una consideración útil a seguir. 14.6 Otras lecturas Hay una gran cantidad de material en la web que puede ayudarlo a aprender más sobre Programación funcional que incluye: • https://codeburst.io/una-introducción-amigable-para-principiantes-a-la-programación-funcional- 4f69aa109569 pretende ser una introducción amigable a la programación funcional. • https://medium.freecodecamp.org/an-introduction-to-the-basic-principles-of- programación-funcional-a2c2a15c84 que proporciona una introducción a la principios básicos de la programación funcional. • https://www.tutorialspoint.com/function_programming que proporciona una buena fundamentación en los conceptos básicos de la Programación Funcional. • https://docs.python.org/3/howto/funcional.html que es el estándar de Python Tutorial de biblioteca sobre Programación Funcional. 14.5 Transparencia Referencial 155
Capítulo 15 Funciones de orden superior 15.1 Introducción En este capítulo exploraremos el concepto de funciones de alto orden. Estos son funciones que toman como parámetro, o devuelven (o ambos), una función. Para hacer esto haremos primero mire cómo Python representa las funciones en la memoria y explore qué en realidad sucede cuando ejecutamos una función de Python. 15.2 Resumen de funciones en Python Primero recapitulemos algunas cosas sobre las funciones en Python: Las funciones (en su mayoría) tienen un nombre y cuando se invocan (o ejecutan) el cuerpo de se ejecuta el código asociado con el nombre de la función. Hay algunas ideas importantes para recordar al considerar las funciones: • las funciones se pueden ver como bloques de código con nombre y son una de las formas principales en el que podemos organizar nuestros programas en Python, • las funciones se definen usando la palabra clave def y constituyen un encabezado de función (el nombre de la función y los parámetros, si los hay, definidos para esa función) y el cuerpo de la función (lo que se ejecuta cuando se ejecuta la función), • las funciones se invocan o ejecutan utilizando su nombre seguido de corchetes ‘()’ con o sin parámetros dependiendo de cómo se haya definido la función. Esto significa que podemos escribir una función como la siguiente función get_msg: def get_msg(): return ‘¡Hola, mundo Python!’ © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_15 157
Entonces podemos llamarlo especificando su nombre y los corchetes: Por supuesto, esto imprime la cadena ‘Hello Python World!’ Que es que usted debe esperar por ahora. 15.3 Funciones como objetos Unos capítulos atrás agregamos algo que decía que si se olvidaba de incluir el corchetes, entonces estabas haciendo referencia a la función en sí en lugar de intentar ¡ejecutalo! ¿Qué significa eso exactamente? Veamos qué pasa si nos olvidamos de incluir el entre paréntesis: La salida generada ahora es: que puede parecer muy confuso a primera vista. Lo que esto realmente te está diciendo es que has hecho referencia a una función llamada get_msg que se encuentra en una dirección (hexadecimal) en la memoria. Es interesante notar que así como los datos deben ubicarse en la memoria, también lo hace código del programa (para que pueda ser encontrado y ejecutado); aunque típicamente los datos y el código son ubicados en áreas separadas de la memoria (ya que los datos tienden a ser de corta duración). Otra cosa interesante que hacer es averiguar cuál es el tipo de get_msg, oye es una función, pero ¿qué significa eso? Si emitimos esta declaración y la ejecutamos en Python: Entonces obtendremos lo siguiente: Esto significa que es de la clase de cosas que son funciones tal como 1 es de la clase de cosas llamadas enteros, ‘John’ es de la clase de cosas llamadas cadenas y 42.6 pertenece a la clase de cosas llamadas números de coma flotante. mensaje = get_msg() imprimir (mensaje) mensaje = get_msg imprimir (mensaje) imprimir(escribir(obtener_mensaje)) <función get_msg en 0x10ad961e0> <clase ‘función’> 158 15 Funciones de orden superior
Llevando esto más lejos, en realidad significa que la cosa a la que hace referencia get_msg es un objeto de función (un ejemplo o instancia de la clase Función). Este get_msg es realmente un tipo de variable que hace referencia (o apunta) a la función objeto en memoria que podemos ejecutar usando los corchetes. Esto se ilustra mediante el siguiente diagrama: Esto significa que cuando ejecutamos get_msg() lo que realmente sucede es que vamos a la variable get_msg y siguiendo la referencia (o puntero) allí a la función y luego, debido a que tenemos los corchetes, ejecutamos esa función. Esto tiene dos implicaciones:
- podemos pasar la referencia a una función,
- podemos hacer referencia get_msg (punto) en una función diferente Veamos la primera de estas implicaciones. Si asignamos la representación de referencia enviado por get_msg a otra cosa, entonces en efecto tenemos un alias para este función. Esto se debe a que ahora otra variable también hace referencia a la misma función. Por ejemplo, si escribimos: Entonces el resultado es que la cadena ‘Hello Python World!’ se vuelve a imprimir. Lo que esto ha hecho es copiar la referencia contenida en get_msg en otra_referencia (pero es una copia de esa referencia y esa es la dirección de la función en la memoria). Así, ahora tenemos en memoria: Entonces, solo para enfatizar esto, no hicimos una copia de la función; solo su dirección en la memoria. Por lo tanto, el mismo valor se mantiene tanto en get_msg como en otra_referencia y ambos valores son referencias al mismo objeto de función en memoria. ¿Qué significa esto y por qué debería importarnos? Bueno, eso significa que podemos pasar. referencias a funciones dentro de nuestro programa que pueden ser muy útiles característica que veremos más adelante en este capítulo. Ahora volvamos a la segunda implicación mencionada anteriormente; podemos reasignar otra función para get_msg. otra_referencia = obtener_mensaje imprimir(otra_referencia()) 15.3 Funciones como objetos 159
Por ejemplo, digamos que escribimos esto a continuación: Ahora get_msg ya no hace referencia a las funciones originales; ahora hace referencia a la nueva función definida por get_some_other_msg. Significa que en la memoria ahora tienen Lo que significa que el resultado de llamar a print(get_msg()) será que la cadena ¡¡¡Algún otro mensaje!!! es devuelto e impreso (en lugar del ‘¡Hola mundo Python!’). Sin embargo, observe que no sobrescribimos la función original; todavía está siendo referenciado por la variable another_reference y, de hecho, todavía se puede llamar a través de esta variable. Por ejemplo, el código: ahora genera la salida: Esto ilustra parte del poder, pero también la posible confusión que surge de cómo se representan las funciones y se pueden manipular en Python. 15.4 Conceptos de funciones de orden superior Dado que podemos asignar una referencia a una función a una variable; entonces esto podría implica que también podemos usar el mismo enfoque para pasar una referencia a una función como un argumento de otra función. imprimir(obtener_mensaje()) imprimir(otra_referencia()) Algún otro mensaje!!! ¡Hola Mundo Pitón! def get_some_other_msg(): volver ‘¡¡¡Algún otro mensaje!!!’ get_msg = get_some_other_msg imprimir(obtener_mensaje()) 160 15 Funciones de orden superior
Esto significa que una función puede tomar otra función como parámetro. Semejante funciones se conocen como funciones de orden superior y son una de las construcciones clave en Programación Funcional. Es decir, una función que toma como parámetro a otra función se conoce como función de orden superior. De hecho, en Python, las funciones de orden superior son funciones que realizan al menos una de lo siguiente (y puede hacer ambas cosas): • Tomar una o más funciones como parámetro, • Devolver como resultado una función. Todas las demás funciones en Python son funciones de primer orden. Muchas de las funciones que se encuentran en las bibliotecas de Python son funciones de orden superior. Él es un patrón lo suficientemente común que una vez que eres consciente de él lo reconocerás en muchas bibliotecas diferentes. 15.4.1 Ejemplo de función de orden superior Como ejemplo abstracto, considere aplicar la siguiente función de orden superior. Este función (escrita en pseudocódigo, no un lenguaje de programación real) toma un entero y una función. Dentro del cuerpo de la función que se está definiendo, la función pasado como un parámetro se aplica al parámetro entero. el resultado de la A continuación, se devuelve la función que se está definiendo: La función apply es una función de orden superior porque su comportamiento (y su resultado) dependerá del comportamiento definido por otra función, la que pasó en ello. También podríamos definir una función que multiplique un número por 10,0, por ejemplo: Ahora podemos usar la función mul con la función apply, por ejemplo: Esto devolvería el valor 50.0 def aplicar(x, función): resultado = función(x) resultado devuelto def multi(y): devolver y * 10.0 aplicar (5, multi) 15.4 Conceptos de funciones de orden superior 161
15.5 Funciones de orden superior de Python Como ya hemos visto, cuando definimos una función, en realidad crea una función objeto al que hace referencia el nombre de la función. Por ejemplo, si creamos el función mul_por_dos: Entonces esto ha creado un objeto de función al que se hace referencia con el nombre multi_by_two que podemos invocar (ejecutar) usando los corchetes ‘()’. También es una función de un parámetro que toma un número y devuelve un valor que es el doble de ese numero Así, un parámetro que espera recibir una referencia a una función que toma un número y devuelve un número se le puede dar una referencia a cualquier función que cumpla este contrato (implícito). Esto incluye nuestra función mul_by_two pero también cualquiera de la siguiente: Todo lo anterior podría usarse con la siguiente función de orden superior: Por ejemplo: La salida de este código es: La siguiente lista proporciona un conjunto completo de las funciones de muestra anteriores y cómo se pueden usar con la función de aplicación: def mult_by_two(num): número de retorno * 2 def multi_por_cinco(num): número de retorno * 5 def cuadrado(num): devolver número * número def agregar_uno(num): devolver número + 1 def aplicar(num, func): función de retorno (num) resultado = aplicar (10, mul_por_dos) imprimir (resultado) 20 imprimir(aplicar(10, multiplicar_por_cinco)) imprimir (aplicar (10, cuadrado)) imprimir (aplicar (10, agregar_uno)) imprimir (aplicar (10, mul_by_two)) 162 15 Funciones de orden superior
La salida de esto es: 15.5.1 Uso de funciones de orden superior Mirando la sección anterior, puede que se pregunte por qué querría usar una función de orden superior o, de hecho, por qué definir una. Después de todo, ¿no podrías haber llamada una de las funciones (multi_por_cinco, cuadrado, sumar_uno o mul_by_two) directamente pasando el entero a used? Sí, podríamos tener, por ejemplo que podríamos haber hecho: Y esto tendría exactamente el mismo efecto que llamar: El primer enfoque parecería ser más simple y más eficiente. La clave de por qué las funciones de orden superior son tan poderosas es considerar qué sucede si sabemos que se debe aplicar alguna función al valor 10 pero lo hacemos aun no se que es. La función real se proporcionará en algún punto de la futuro. Ahora estamos creando un fragmento de código reutilizable que podrá aplicar un función apropiada a los datos que tenemos cuando se conoce esa función. Por ejemplo, supongamos que queremos calcular la cantidad de impuestos alguien debe pagar en base a su salario. Sin embargo, no sabemos cómo calcular el impuesto que esta persona debe pagar ya que depende de factores externos. La función de calcular_impuestos podría tomar una función apropiada que realiza ese cálculo y proporciona el valor fiscal adecuado. La siguiente lista implementa este enfoque. La función calcular_impuestos no sabe cómo calcular el impuesto real a pagar, en su lugar se debe realizar una función proporcionado como un parámetro para la función calcular_impuestos. La función pasada en toma un número y devuelve el resultado de realizar el cálculo. se usa con el parámetro de salario también pasó a la función de cálculo de impuestos. 50 100 11 20 cuadrado(10) aplicar (10, cuadrado) importar matematicas def simple_tax_calculator(cantidad): devuelve math.ceil (cantidad * 0.3) def calcular_impuesto(salario, función): función de retorno (salario) imprimir (calcular_impuestos (45000.0, simple_tax_calculator)) 15.5 Funciones de orden superior de Python 163
La función simple_tax_calculator define una función que toma un número y lo multiplica por 0.3 y luego usa la función math.ceil (importado de la biblioteca/módulo de matemáticas) para redondearlo a un número entero. Entonces se hace una llamada hecho a la función de calcular_impuestos pasando el float 45000.0 como el salario y una referencia a la función simple_tax_calculator. Finalmente, se imprime el impuesto calculado. El resultado de ejecutar este programa es: Por lo tanto, la función calcular_impuestos es una función reutilizable que puede tener diferentes estrategias de cálculo de impuestos definidas para ello. 15.5.2 Funciones que devuelven funciones En Python, además de pasar una función a otra función; las funciones pueden ser devuelto de una función. Esto se puede utilizar para seleccionar entre un número de diferentes opciones o para crear una nueva función basada en los parámetros. Por ejemplo, el siguiente código crea una función que se puede usar para verificar si un número es par, impar o negativo según la cadena que se le pasa: Tenga en cuenta el uso del error de aumento de valor; por el momento solo diremos que esta es una forma de mostrar que hay un problema en el código que puede ocurrir si esto se llama a la función con un valor de parámetro inadecuado para ’s’. Esta función es una fábrica de funciones que se pueden crear para realizar funciones específicas. operaciones. Se utiliza a continuación para crear tres funciones que se pueden utilizar para validar que tipo es un numero: def make_checker(s): si s == ‘par’: devolver lambda n: n%2 == 0 elif s == ‘positivo’: devuelve lambda n: n >= 0 elif s == ’negativo’: retorno lambda n: n < 0 demás: aumentar ValueError(‘Solicitud desconocida’) f1 = make_checker(‘par’) f2 = make_checker(‘positivo’) f3 = make_checker(’negativo’) imprimir (f1 (3)) imprimir (f2 (3)) imprimir (f3 (3)) 164 15 Funciones de orden superior
Por supuesto, no son sólo las funciones anónimas las que se pueden devolver desde un función; también es posible devolver una función con nombre. Esto se hace volviendo solo el nombre de la función (es decir, sin los corchetes). En el siguiente ejemplo, una función con nombre se define dentro de una función externa (aunque podría haberse definido en otra parte del código). Luego se devuelve de la función: Luego podemos usar esta función make_function para crear la función de sumador y almacenar en otra variable. Ahora podemos usar esta función en nuestro código, por ejemplo: Que produce la salida def hacer_función(): def sumador(x, y): volver x + y sumador de retorno f1 = hacer_función() imprimir (f1 (3, 2)) imprimir (f1 (3, 3)) imprimir (f1 (3, 1)) 5 6 4 15.6 Recursos en línea Se puede encontrar más información sobre funciones de orden superior en Python usando el siguientes recursos en línea: • https://en.wikipedia.org/wiki/Higher-order_function Página de Wikipedia en Higher Funciones de orden. • https://docs.python.org/3.1/library/functools.html un módulo para apoyar la cre- ación y uso de funciones de orden superior. • https://www.tutorialspoint.com/funcional_programación/funcional_ programación_funciones_de_orden_superior.htm Un tutorial sobre funciones de orden superior. 15.7 Ejercicios El objetivo de este ejercicio es explorar funciones de orden superior. Tú debería escribir a más alto orden función función llamado my_higher_order_function(i, func). Esta función toma un parámetro y una segunda función para aplicar al parámetro. 15.5 Funciones de orden superior de Python 165
Ahora debe escribir un programa de muestra que use la función de orden superior que acaba de crear para realizar. Un ejemplo del tipo de cosa que podría implementar es dada a continuación: Si está utilizando el código anterior como su aplicación de prueba, debe escribir cada una de las funciones de apoyo; cada uno debe tomar un solo parámetro. El resultado de muestra de este fragmento de código es: Tenga en cuenta que una forma sencilla de encontrar la raíz cuadrada de un número es usar el exponente (o potencia de) operador y multiplicar por 0,5. imprimir (my_higher_order_function (2, doble)) imprimir (my_higher_order_function (2, triple)) print(my_higher_order_function(16, square_root)) imprimir (my_higher_order_function (2, is_prime)) imprimir (my_higher_order_function (4, is_prime)) imprimir (my_higher_order_function (‘2’, is_integer)) print(my_higher_order_function(‘A’, is_integer)) print(my_higher_order_function(‘A’, is_letter)) print(my_higher_order_function(‘1’, is_letter)) 4 8 4.0 Verdadero FALSO Verdadero FALSO Verdadero FALSO 166 15 Funciones de orden superior
capitulo 16 Funciones al curry 16.1 Introducción Currying es una técnica que permite crear nuevas funciones a partir de funciones existentes. funciones vinculando uno o más parámetros a un valor específico. es una fuente importante de reutilización de funciones en Python, lo que significa que la funcionalidad se puede escribir una vez, en un lugar y luego reutilizado en muchas otras situaciones. El nombre Currying puede parecer oscuro, pero la técnica lleva el nombre de Haskell. Curry (por quien también se nombra el lenguaje de programación Haskell). Este capítulo presenta las ideas centrales detrás del curry y explora cómo Rying se puede implementar en Python. El capítulo también introduce el concepto de cierres y cómo afectan las funciones curry. 16.2 Conceptos de curry En un nivel abstracto, considere tener una función que tome dos parámetros. Estos dos parámetros, x e y se utilizan dentro del cuerpo de la función con la multiplicación operador en la forma x * y. Por ejemplo, podríamos tener: Esta función, operación (), podría usarse de la siguiente manera Lo que daría como resultado que 5 se multiplique por 2 para dar 10. O podría usarse: operación(x, y): devuelve x * y total = operación(2, 5) © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_16 167
Lo que daría como resultado que 5 se multiplique por 10 para dar 50. Si necesitáramos duplicar un número, podríamos reutilizar la operación() muchas veces, por ejemplo: Todo lo anterior duplicaría el segundo número. Sin embargo, hemos tenido que recuerda proporcionar el 2 para que el número se pueda duplicar. sin embargo, el el número 2 no ha cambiado entre ninguna de las invocaciones de la operación() función. ¿Qué pasa si fijamos el primer parámetro para que siempre sea 2, esto significaría que podríamos crear una nueva función que aparentemente solo toma un parámetro (el número a duplicar). Por ejemplo, digamos que podríamos escribir algo como: Tal que ahora podríamos escribir: En esencia, double() es un alias para operation(), pero un alias que proporciona el valor 2 para el primer parámetro y deja el segundo parámetro para ser llenado por la futura invocación de la doble función. 16.3 Funciones de Python y Curry Una función curry en Python es una función en la que uno o más de sus parámetros tienen aplicado o vinculado a un valor, lo que resulta en la creación de una nueva función con uno menos parámetros que el original. Por ejemplo, creemos una función que multiplica dos números juntos: total = operación(10, 5) operación(2, 5) operación(2, 10) operación(2, 6) operación(2, 151) doble = operación(2, *) doble(5) doble(151) def multiplicar(a, b): devolver a * b 168 dieciséis Funciones al curry
Esta es una función general que hace exactamente lo que dice; multiplica dos números juntos. Estos números pueden ser dos enteros o puntos flotantes. numeros etc Por lo tanto, podemos invocarlo de la manera normal: El resultado de ejecutar esta instrucción es: Ahora podríamos definir un nuevo método que toma una función y un número y devuelve una nueva función (anónima) que toma un nuevo parámetro y llama al función pasada con el número pasado y el nuevo parámetro: Mire cuidadosamente esta función; ha usado o vinculado el número pasado al función multby a la invocación de la función pasada, pero también ha definido una nueva variable ‘y’ que deberá proporcionarse cuando esta nueva función anónima es invocado. Luego devuelve una referencia a la función anónima como resultado de multby La función multby ahora se puede usar para enlazar el primer parámetro del multiplicar la función a cualquier cosa que queramos. Por ejemplo, podríamos unirlo a 2 para que siempre duplicará el segundo parámetro y almacenará la referencia de la función resultante en una propiedad doble: También podríamos vincular el valor 3 al primer parámetro de múltiple para hacer un función que triplicará cualquier valor: Lo que significa que ahora podemos escribir: def multby(func, num): devuelve lambda y: func(num, y) doble = multby(multiplicar, 2) imprimir (multiplicar (2, 5)) 10 triple = multby(multiplicar, 3) imprimir (doble (5)) imprimir(triple(5)) 16.3 Funciones de Python y Curry 169
que produce la salida No está limitado a vincular solo un parámetro; puede vincular cualquier número de parámetros de esta manera. Por lo tanto, las funciones curry son muy útiles para crear nuevas funciones a partir de funciones existentes. 16.4 Cierres Una pregunta que bien podría estar en su mente ahora es qué sucede cuando un La función hace referencia a algunos datos que están dentro del alcance donde están definidos pero ya no están disponible cuando se evalúa? Esta pregunta se responde con la implementación de un concepto conocido como cierre. Dentro de las Ciencias de la Computación (y los lenguajes de programación en particular) un cierre (o un cierre léxico o cierre de función) es una función (o más estrictamente una referencia a una función) junto con un entorno de referencia. Este entorno de referencia registra el contexto dentro del cual se definió originalmente la función y, si es necesario, ensayo, una referencia a cada una de las variables no locales utilizadas por esa función. Estos Las variables no locales o libres permiten que el cuerpo de la función haga referencia a variables que son externos a la función, pero que son utilizados por esa función. esta referencia entorno es una de las características distintivas entre un lenguaje funcional y un lenguaje que admita punteros de función (como C). El concepto general de una clausura léxica se desarrolló por primera vez durante la década de 1960, pero se implementó completamente por primera vez en el esquema lingüístico en la década de 1970. desde entonces ha sido Se utiliza en muchos lenguajes de programación funcionales, incluidos LISP y Scala. A nivel conceptual, un cierre permite que una función haga referencia a una variable disponible en el ámbito donde la función se definió originalmente, pero no disponible por defecto en el ámbito donde se ejecuta. Por ejemplo, en el siguiente programa simple, la variable more se define fuera del cuerpo de la función denominada aumento. Esto es permisible como el variable es una variable global. Por lo tanto, la variable más está dentro del alcance en el punto de definición 10 15 170 dieciséis Funciones al curry
Dentro de nuestro programa invocamos la función de aumento pasando el valor 10. Esto se hace dos veces y la variable más se restablece a 50 entre los dos. La salida de este programa se muestra a continuación: Tenga en cuenta que es el valor actual de more el que se usa cuando la función ejecuta y no el valor de más presente en el punto en que la función fue definido. Por lo tanto, la salida es 110 y 60, es decir, 100 + 10 y luego 50 + 10. Esto puede parecer obvio ya que la variable more todavía está dentro del alcance dentro del mismo funcionan como las invocaciones de la función a la que hace referencia el aumento. Sin embargo, considere el siguiente ejemplo: En la lista anterior, el incremento de la función inicialmente agrega 1 a cualquier valor se le ha pasado. Luego en el programa se llama a esta función con el valor 5 y se imprime el resultado devuelto por la función. Este será el valor 6. Sin embargo, después de esto, se invoca una segunda función, reset_function(). Este función tiene una variable que es local a la función. Es decir, normalmente solo sería estar disponible dentro de la función reset_function. Esta variable se llama suma y tiene el valor 50. más = 100 def aumentar(num): devolver num + más imprimir(aumentar(10)) más = 50 imprimir(aumentar(10)) 110 60 incremento def(num): devolver número + 1 def restablecer_función(): incremento global suma = 50 incremento = lambda num: num + suma imprimir (incremento (5)) restablecer_función() imprimir (incremento (5)) 16.4 Cierres 171
Sin embargo, la suma variable se usa dentro del cuerpo de la función de una nueva definición de función anónima. Esta función toma un número y le suma el valor de suma a ese número y lo devuelve como el resultado de la función. este nuevo Luego se asigna la función al incremento de nombre. Tenga en cuenta que para garantizar que hagamos referencia el incremento de nombre global debemos usar la palabra clave global (de lo contrario, crear una variable local que tenga el mismo nombre que la función). Ahora, cuando el segundo invocación de incremento ocurre, el restablecer_función() método tiene terminado y normalmente el variable la adición ya no existiría. Sin embargo, cuando este programa ejecuta el valor 55 se imprime desde la segunda invocación de incremento. Eso es la función a la que hace referencia el incremento de nombre, cuando se llama el segunda vez, es la definida dentro de reset_function() y que usa el adición de variables. La salida real se muestra a continuación: Entonces, ¿qué ha pasado aquí? Cabe señalar que el valor 50 no fue copiado en el segundo cuerpo de función. Más bien es un ejemplo concreto del uso de un entorno de referencia con el concepto de cierre. Python asegura que la variable adición está disponible para la función, incluso si la invocación de la función es en algún lugar diferente a donde se definió al vincular cualquier variable libre (aquellas definidos fuera del alcance de la función) y almacenándolos para que puedan ser accedido por el contexto de la función (en efecto, moviendo la variable de ser un local variable a una que está disponible para la función en cualquier lugar; pero solo a la función). 16.5 Recursos en línea Más información sobre curry ver: • https://en.wikipedia.org/wiki/Currying Página de Wikipedia sobre curry. • https://wiki.haskell.org/Currying Una página que presenta el curry (basado en el lenguaje Haskell, pero sigue siendo una referencia útil). • https://www.python-course.eu/currying_in_python.php Un tutorial sobre curry en Pitón. 172 dieciséis Funciones al curry
16.6 Ejercicios Este ejercicio trata sobre la creación de un conjunto de funciones para realizar conversiones de moneda. basado en tasas especificadas usando el curring para crear esas funciones. Escribe una función que cursará otra función y un parámetro de manera similar manera de multby en este capítulo—llame a esta función curry(). Ahora defina una función que pueda usarse para convertir una cantidad en otra monto basado en una tasa. La definición de esta función de conversión es muy directa. hacia adelante y solo implica multiplicar el número por la tasa. Ahora cree un conjunto de funciones que se pueden usar para convertir un valor en una moneda a otra moneda con base en una tasa específica. No queremos tener que recordar la tasa, sólo el nombre de la función. Por ejemplo: Si se ejecuta el código anterior, la salida sería: dolares_a_libras esterlinas = curry(convertir, 0.77) imprimir (dólares_a_libras esterlinas (5)) euro_to_sterling = curry(convertir, 0.88) imprimir(euro_a_libras esterlinas(15)) sterling_to_dollars = curry (convertir, 1.3) imprimir(esterlinas_a_dolares(7)) sterling_to_euro = curry (convertir, 1.14) imprimir(esterlina_a_euro(9)) 3.85 13.2 9.1 10.26 16.6 Ejercicios 173
capitulo 17 Introducción a la Orientación a Objetos 17.1 Introducción Este capítulo presenta los conceptos básicos de la orientación a objetos. Define la terminología utilizada e intentos de aclarar cuestiones asociadas con los objetos. También analiza algunas de las fortalezas y debilidades percibidas de la orientación a objetos acercarse. Luego ofrece alguna orientación sobre el enfoque a seguir para aprender sobre objetos. 17.2 Clases Una clase es uno de los componentes básicos de Python. También es un concepto central en un estilo de programación conocido como Programación Orientada a Objetos (o POO). Programación orientada a objetos proporciona un enfoque para estructurar programas/aplicaciones de modo que los datos se mantengan, y las operaciones realizadas en esos datos, se agrupan en clases y se accede a través de objetos. Como ejemplo, en un programa de estilo OOP, los empleados pueden estar representados por un clase Employee donde cada empleado tiene un id, un nombre, un departamento y un desk_number, etc. También pueden tener operaciones asociadas con ellos, como tomar_unas_vacaciones() o recibir_pago(). En muchos casos, las clases se utilizan para representar entidades del mundo real (como empleados) pero no es necesario, también pueden representar conceptos más abstractos como una transacción entre una persona y otra (por ejemplo, un acuerdo para comprar una comida). Las clases actúan como plantillas que se utilizan para construir instancias o ejemplos de un clase de cosas. Cada ejemplo de la clase Persona puede tener un nombre, una edad, una © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_17 175
dirección, etc., pero tienen sus propios valores para su nombre, edad y dirección. Para ejemplo, para representar a las personas de una familia podríamos crear un ejemplo de la clase Persona de nombre Paul, de 52 años de edad y con domicilio fijo en Londres. también podemos crear otro objeto Person (instancia) con el nombre Fiona, la edad 48 y el dirección también de Londres y así sucesivamente. Por lo tanto, una instancia u objeto es un ejemplo de una clase. Todas las instancias/objetos de una clase posee las mismas variables de datos pero contiene sus propios valores de datos. Cada instancia de una clase responde al mismo conjunto de solicitudes y tiene el mismo comportamiento. Las clases permiten a los programadores especificar la estructura de un objeto (es decir, sus atributos o campos, etc.) y su comportamiento por separado de los propios objetos. Esto es importante, ya que consumiría mucho tiempo (además de ser ineficaz). ciente) para que los programadores definan cada objeto individualmente. En cambio, definen clases y crear instancias u objetos de esas clases. Luego pueden almacenar datos relacionados juntos en un concepto con nombre que lo hace mucho más fácil de estructurar y mantener el código. 17.3 ¿Para qué son las clases? Ya hemos visto varios tipos de datos en Python, como enteros, cadenas, booleano, etc. Cada uno de estos nos permitió mantener un solo elemento de datos (como el entero 42 o la cadena ‘John’ y el valor True). Sin embargo, ¿cómo podríamos representar a una Persona, un Estudiante o un Empleado de una empresa? Una forma en que podemos hacer esto es utilizar una clase para representarlos. Como se indicó anteriormente, podríamos representar cualquier tipo de elemento de datos (más complejo) utilizando una combinación de atributos (o campos) y comportamientos. Estos atributos se use tipos de datos existentes, estos pueden ser enteros, cadenas, booleanos, punto flotante números u otras clases. Por ejemplo, al definir la clase Persona podríamos darle: • un campo o atributo para el nombre de la persona, • un campo o atributo para su edad, • un campo o atributo para su correo electrónico, • algún comportamiento para darles un cumpleaños (lo que incrementará su edad), • algún comportamiento que nos permita enviarles un mensaje a través de su correo electrónico, • etc. En Python se utilizan clases: • como plantilla para crear instancias (u objetos) de esa clase, • definir métodos de instancia o comportamiento común para una clase de objetos, • definir atributos o campos para contener datos dentro de los objetos, • ser enviado mensajes. 176 17 Introducción a la Orientación a Objetos
Los objetos (o instancias), por otro lado, pueden: • ser creado a partir de una clase, • mantener sus propios valores para las variables de instancia, • recibir mensajes, • ejecutar métodos de instancia, • Puede tener muchas copias en el sistema (todas con sus propios datos). 17.3.1 ¿Qué debe hacer una clase? Una clase debe lograr un propósito específico; debe capturar sólo una idea. Si más de una idea está encapsulada en una clase, puede reducir las posibilidades de reutilización, así como contravenir las leyes de encapsulamiento en sistemas orientados a objetos. Para ejemplo, es posible que haya fusionado dos conceptos para que uno pueda acceder a los datos de otro. Esto rara vez es deseable. Las siguientes pautas pueden ayudarlo a decidir si dividir la clase con que estás trabajando. Mire el comentario que describe la clase (si no hay comentario de clase, esto es una mala señal en sí mismo). Considere los siguientes puntos: • ¿La descripción de la clase es breve y clara? Si no, ¿es esto un reflejo de la ¿clase? Considere cómo se puede dividir el comentario en una serie de breves comentarios claros. Base las nuevas clases en torno a esos comentarios. • Si el comentario es breve y claro, ¿tienen sentido las variables de clase e instancia? dentro del contexto del comentario? Si no lo hacen, entonces la clase necesita ser reevaluado. Puede ser que el comentario sea inapropiado, o que la clase y variables de instancia inapropiadas. • Observa cómo y dónde se usan los atributos de la clase. ¿Está su uso en línea con el comentario de la clase? Si no es así, debe tomar las medidas adecuadas. 17.3.2 Terminología de la clase Los siguientes términos se usan en Python (y otros lenguajes que admiten objetos orientación): • Clase Una clase define una combinación de datos y comportamiento que opera en ese datos. Una clase actúa como una plantilla al crear nuevas instancias. • Instancia u objeto Una instancia también conocida como objeto es un ejemplo de una clase. Todas las instancias de una clase poseen los mismos campos de datos/atributos pero contienen sus propios valores de datos. Cada instancia de una clase responde al mismo conjunto de solicitudes. • Atributo/campo/variable de instancia Los datos contenidos en un objeto se representan por su atributos (a veces también conocido como un campo o una variable de instancia). El estado" de un objeto en cualquier momento particular se relaciona con los valores actuales mantenidos por su atributos 17.3 ¿Para qué son las clases? 177
• Método Un método es un procedimiento definido dentro de un objeto. • Mensaje Se envía un mensaje a un objeto solicitando que se realice alguna operación. formado o algún atributo para ser accedido. Es una petición al objeto de hacer. algo o devolver algo. Sin embargo, depende del objeto determinar cómo para ejecutar esa solicitud. Un mensaje puede considerarse similar a una llamada de procedimiento en otros idiomas. 17.4 ¿Cómo se construye un sistema OO? En este punto, quizás se pregunte cómo se puede construir un sistema a partir de clases y objetos instanciados de esas clases? ¿Cómo sería una aplicación de este tipo? Está claro que es diferente a escribir funciones y código de aplicación independiente que llama a esas funciones? Usemos un sistema (físico) del mundo real para explorar qué es una aplicación OOP. podría parecer. Este sistema tiene como objetivo proporcionar un tutor de diagnóstico para el equipo ilustrado. arriba. En lugar de usar el sistema de lavado y limpieza de un automóvil real, los estudiantes en un automóvil curso de diagnóstico de mecánica utilizar este software de simulación. el sistema de software imita el sistema real, por lo que el comportamiento de la bomba depende de la información proporcionada por el relé y la botella de agua. El funcionamiento del sistema de lavado y limpieza se controla mediante un interruptor que puede estar en una de cinco posiciones: apagado, intermitente, lento, rápido y lavado. Cada uno de estos ajustes coloca el sistema en un estado diferente: 178 17 Introducción a la Orientación a Objetos
Para que la bomba y el motor del limpiaparabrisas funcionen correctamente, el relé debe funcionar correctamente. A su vez, el relé debe estar alimentado con un circuito eléctrico. Esta electricidad El circuito eléctrico tiene un fusible negativo y, por lo tanto, el fusible debe estar intacto para que el circuito hecho. Los automóviles se conmutan negativamente, ya que esto reduce las posibilidades de cortocircuitos. que conduce a la conmutación involuntaria de los circuitos. 17.4.1 ¿Donde empezamos? Este es a menudo un punto muy difícil para aquellos nuevos en los sistemas orientados a objetos. Eso es, han leído los conceptos básicos y entienden diagramas simples, pero no saben dónde para comenzar. Es la vieja castaña, “Entiendo el ejemplo pero no sé cómo aplicar los conceptos yo mismo”. Esto no es inusual y, en el caso de la orientación a objetos. tación, es probablemente normal. La respuesta a la pregunta “¿por dónde empiezo?” puede parecer al principio algo oscuro; debe comenzar con los datos. Recuerda que los objetos son cosas que intercambiar mensajes entre ellos. Las cosas poseen los datos que están en poder del sistema y los mensajes solicitan acciones relacionadas con los datos. Así, un El sistema orientado a objetos se ocupa fundamentalmente de los elementos de datos. Antes de pasar a considerar la vista del sistema orientada a objetos, detengámonos y pensar un rato. Pregúntese por dónde podría empezar; puede ser que pienses sobre comenzar “con alguna forma de descomposición funcional” (rompiendo el problema hacia abajo en términos de las funciones que proporciona), ya que esta podría ser la vista que el usuario tiene del sistema. Como parte natural de este ejercicio, identificaría los datos necesarios para soportar la funcionalidad deseada. Note que el énfasis estaría en la funcionalidad del sistema. Llevemos esto más lejos y consideremos las funciones que podríamos identificar para el ejemplo presentado arriba: Configuración del interruptor Estado del sistema Apagado El sistema está inactivo Intermitente Las escobillas limpian el parabrisas cada pocos segundos. Lento Las escobillas del limpiaparabrisas limpian el parabrisas continuamente Rápido Las escobillas limpian el parabrisas de forma continua y rápida. Lavar La bomba extrae agua de la botella de agua y la rocía sobre el parabrisas Función Descripción Lavar Bombear agua de la botella de agua al parabrisas Limpiar Mueva los limpiaparabrisas por el parabrisas 17.4 ¿Cómo se construye un sistema OO? 179
Luego, identificaríamos las variables y subfunciones importantes del sistema para respaldar las funciones anteriores. Ahora volvamos a la visión del mundo orientada a objetos. En esta vista, nosotros poner mucho más énfasis en los elementos de datos involucrados y considerar la operaciones asociadas con ellos (efectivamente, lo contrario de la descomposición funcional). vista de posición). Esto significa que empezamos intentando identificar los datos primarios artículos en el sistema; a continuación, buscamos para ver qué operaciones se aplican a, o per- formado en, los elementos de datos; finalmente, agrupamos los elementos de datos y las operaciones para formar objetos. Al identificar las operaciones, es posible que tengamos que considerar elementos de datos adicionales, que pueden ser objetos o atributos separados del actual objeto. Identificarlos es principalmente una cuestión de habilidad y experiencia. El enfoque de diseño orientado a objetos considera que las operaciones son mucho menos importantes que los datos y sus relaciones. En la siguiente sección examinamos los objetos que podría existir en nuestro sistema de simulación. 17.4.2 Identificación de los objetos Miramos el sistema como un todo y preguntamos qué indica el estado del sistema. Nosotros podría decir que la posición del interruptor o el estado de la bomba es importante. Esto da como resultado los elementos de datos que se muestran a continuación. La identificación de los elementos de datos se considera con mayor detalle más adelante. En este punto, simplemente tenga en cuenta que aún no hemos mencionado la funcionalidad del sistema o cómo podría encajar, solo hemos mencionado los elementos significativos. Como esto es un sistema tan simple, podemos suponer que cada uno de estos elementos es un objeto y ilustrarlo en un diagrama de objeto simple: elemento de datos estados ajuste del interruptor ¿Está el interruptor en apagado, intermitente, limpieza, limpieza rápida o lavado? motor del limpiaparabrisas ¿El motor funciona o no? estado de la bomba ¿Funciona o no la bomba? estado del fusible ¿Se ha fundido o no el fusible? nivel de la botella de agua El nivel de agua actual estado del relé ¿Fluye corriente o no? 180 17 Introducción a la Orientación a Objetos
Tenga en cuenta que hemos nombrado cada objeto después del elemento asociado con los datos elemento (por ejemplo, el elemento asociado con la condición del fusible es el fusible mismo) y que los datos reales (p. ej., el estado del fusible) son una variable de instancia del objeto. Esta es una forma muy común de nombrar objetos y sus variables de instancia. Nosotros ahora tener los objetos básicos requeridos para nuestra aplicación. 17.4.3 Identificación de los servicios o métodos Por el momento, tenemos un conjunto de objetos, cada uno de los cuales puede contener algunos datos. Para Por ejemplo, la botella de agua puede contener un número entero que indica el nivel actual del agua. Aunque los sistemas orientados a objetos están estructurados en torno a los datos, aún necesitamos algunos contenido procedimental para cambiar el estado de un objeto o hacer que el sistema logre alguna meta. Por lo tanto, también debemos considerar las operaciones de un usuario de cada objeto. podría requerir. Note que el énfasis aquí está en el usuario del objeto y lo que requieren del objeto, en lugar de qué operaciones se realizan en los datos. Comencemos con el objeto interruptor. El estado del interruptor puede tomar varios valores. Como no queremos que otros objetos tengan acceso directo a esta variable, debemos identificar los servicios que debe ofrecer el conmutador. Como usuario de un conmutador queremos podrá moverlo entre sus diferentes configuraciones. Como estos ajustes son esencialmente una tipo enumerado, podemos tener el concepto de incrementar o decrementar el posición del interruptor. Por lo tanto, un interruptor debe proporcionar un movimiento hacia arriba y un movimiento hacia abajo. interfaz. Exactamente cómo se hace esto depende del lenguaje de programación; por ahora, nos concentramos en especificar las instalaciones requeridas. Si examinamos cada objeto en nuestro sistema e identificamos los servicios requeridos, podemos puede terminar con la siguiente tabla: 17.4 ¿Cómo se construye un sistema OO? 181
Generamos esta tabla examinando cada uno de los objetos de forma aislada para identificar los servicios que razonablemente se podrían requerir. Bien podemos identificar otros servicios vicios cuando intentamos ponerlo todo junto. Cada uno de estos servicios debe relacionarse con un método dentro del objeto. Por ejemplo, los servicios moveUp y moveDown deben relacionarse con métodos que cambian el estado variable de instancia dentro del objeto. Usando un pseudocódigo genérico, el move_up El método, dentro del objeto de cambio, podría contener el siguiente código: Este método cambia el valor de la variable de estado en switch. El nuevo valor de la variable de instancia depende de su valor anterior. Puede definir moveDown en un manera similar. Observe que la referencia a la variable de instancia ilustra que es global al objeto. El método moveUp no requiere parámetros. en orientado a objetos sistemas, es común que se pasen pocos parámetros entre métodos (particularmente normalmente del mismo objeto), ya que es el objeto el que contiene los datos. 17.4.4 Refinando los Objetos Si miramos hacia atrás en la tabla, podemos ver que el fusible, el motor del limpiaparabrisas, el relé y la bomba, todos Poseer un servicio llamado trabajo?. Esta es una pista de que estos objetos pueden tener algo en común. Cada uno de ellos presenta la misma interfaz al exterior. mundo. Si consideramos sus atributos, todos ellos poseen una instancia común Objeto Servicio Descripción cambiar ascender Incrementar el valor del interruptor mover hacia abajo Valor del interruptor de disminución ¿Estado? Devuelve un valor que indica el estado actual del interruptor fusible ¿laboral? Indicar si el fusible se ha quemado o no motor del limpiaparabrisas ¿laboral? Indicar si los limpiaparabrisas funcionan o no relé ¿laboral? Indicar si el relé está activo o no bomba ¿laboral? Indicar si la bomba está activa o no botella de agua llenar Llene la botella de agua con agua extracto Retire un poco de agua de la botella de agua. vacío Vaciar la botella de agua definitivamente mover_arriba(uno mismo): si self.state == “apagado” entonces self.tate = “lavar” else if self.state == “lavar” entonces self.estado = “borrar” 182 17 Introducción a la Orientación a Objetos
variable. En este punto, es demasiado pronto para decir si el fusible, el motor del limpiaparabrisas, el relé y bomba son todas las instancias de la misma clase de objeto (por ejemplo, una clase de componente) o si son todas instancias de clases que heredan de algún super- común clase (ver abajo). Sin embargo, esto es algo que debemos tener en cuenta más adelante. 17.4.5 Reuniéndolo todo Hasta ahora, hemos identificado los objetos primarios en nuestro sistema y el conjunto básico de servicios que deben presentar. Estos servicios se basaban únicamente en los datos que los objetos aguantan. Ahora debemos considerar cómo hacer que nuestro sistema funcione. Para hacer esto, tenemos que considerar cómo se podría utilizar. El sistema es parte de un muy simple tutor de diagnóstico; un estudiante usa el sistema para aprender sobre los efectos de varias fallas en el funcionamiento de un sistema de limpiaparabrisas real, sin necesidad de componentes electrónicos costosos. Por lo tanto, deseamos permitir que un usuario del sistema lleve a cabo lo siguiente operaciones: • cambiar el estado de un dispositivo componente • preguntar al motor cuál es su nuevo estado 17.4 ¿Cómo se construye un sistema OO? 183
Las operaciones move_up y move_down en el interruptor cambian el estado. Se pueden proporcionar operaciones similares para el fusible, la botella de agua y el relé. Para el fusible y el relé, podríamos proporcionar una interfaz change_state usando el siguiente algoritmo: Descubrir el estado del motor es más complicado. Nos hemos encontrado con un situación en la que el estado de un objeto (el valor de su variable de instancia) depende de información proporcionada por otros objetos. Si escribimos procedimentalmente cómo el valor de otros objetos afectan el estado de la bomba, podríamos obtener lo siguiente pseudocódigo: Este algoritmo dice que el estado de la bomba depende del estado del relé, el interruptor ajuste y el estado del fusible. Este es el tipo de algoritmo que podría esperar encontrar en su aplicación. Vincula las subfunciones y procesa los datos. En un sistema orientado a objetos, los objetos educados pasan mensajes a uno otro. Entonces, ¿cómo logramos el mismo efecto que el algoritmo anterior? El La respuesta es que debemos hacer que los objetos pasen mensajes solicitando el información. Una forma de hacerlo es definir un método en el objeto bomba que obtiene la información requerida de los otros objetos y determina el estado del motor. Sin embargo, esto requiere que la bomba tenga enlaces con todos los demás objetos para que pueda enviarles mensajes. Esto es un poco artificial y pierde la estructura de la sistema subyacente. También pierde cualquier modularidad en el sistema. Es decir, si queremos agregar nuevos componentes, entonces tenemos que cambiar el objeto de la bomba, incluso si el nuevo Los componentes solo afectan al interruptor. Este enfoque también indica que el desarrollador es pensar demasiado procedimentalmente y no realmente en términos de objetos. En una vista del sistema orientada a objetos, el objeto bomba solo necesita saber el estado del relé. Por lo tanto, debe solicitar esta información al relé. En a su vez, el relé debe solicitar información a los interruptores y al fusible. si el fusible funciona entonces si el interruptor no está apagado entonces si el relé está funcionando entonces estado de la bomba = “funcionando” definir change_state(self) if self.state == “trabajando” entonces self.tate = “no funciona” demás self.estado = “trabajando” 184 17 Introducción a la Orientación a Objetos
Lo anterior ilustra la cadena de mensajes iniciados por el objeto bomba:
- ¿La bomba envía un mensaje de trabajo? mensaje al relevo,
- relé envía un estado? mensaje al interruptor, el interruptor responde al relé,
- ¿El relé envía un segundo trabajo? mensaje al fusible: • El fusible responde al relé • el relé responde al motor • Si la bomba está funcionando, el objeto bomba envía el mensaje final al botella de agua
- la bomba envía un extracto de mensaje a la botella de agua En el paso cuatro, se pasa un parámetro con el mensaje porque, a diferencia del anterior mensajes que simplemente solicitaban información de estado, este mensaje solicita un cambio en estado. El parámetro indica la velocidad a la que la bomba extrae agua del botella de agua. La botella de agua no debe registrar el valor del estado de la bomba ya que no poseer este valor. Si necesita el estado del motor en el futuro, debe solicitarlo a la bomba en lugar de utilizar el valor (potencialmente obsoleto) que se le pasó anteriormente. En la figura anterior asumimos que la bomba brindaba el servicio funcionando? que permite iniciar el proceso. Para completar, el pseudo-código del ¿laboral? método para el objeto bomba es: definitivamente trabajando? (uno mismo) self.status = relé.trabajando(). if self.status == “trabajando” entonces botella_de_agua.extract(self.status) 17.4 ¿Cómo se construye un sistema OO? 185
Este método es mucho más simple que el programa procedimental presentado anteriormente. en ningún punto cambiamos el valor de cualquier variable que no sea parte de la bomba, aunque pueden haber sido modificados como consecuencia de los mensajes enviados. También, solo nos muestra la parte de la historia que es directamente relevante para la bomba. Este significa que puede ser mucho más difícil deducir el funcionamiento de un sistema orientado a objetos simplemente leyendo el código fuente. Algo de entorno de Python (como PyCharm IDE) alivian este problema, hasta cierto punto, a través de el uso de navegadores sofisticados. 17.5 ¿Dónde está la estructura en un programa OO? Las personas nuevas en la orientación a objetos pueden confundirse porque han perdido uno de los Elementos clave que utilizan para comprender y estructurar un sistema de software: el cuerpo principal del programa. Esto se debe a que los objetos y las interacciones entre ellos son la piedra angular del sistema. En muchos sentidos, la siguiente figura muestra el equivalente orientado a objetos de un programa principal. Esto también destaca una importante característica de la mayoría de los enfoques orientados a objetos: ilustraciones gráficas. Muchos aspectos de tecnología de objetos, por ejemplo, estructura de objetos, herencia de clases y mensajes cadenas, se explican más fácilmente gráficamente. Consideremos ahora la estructura de nuestro sistema orientado a objetos. es dictado por los mensajes que se envían entre objetos. Es decir, un objeto debe poseer un referencia a otro objeto para enviarle un mensaje. El sistema resultante estructura se ilustra a continuación. En Python, esta estructura se logra haciendo que las variables de instancia hagan referencia a la objetos apropiados. Esta es la estructura que existe entre las instancias en el sistema y no se relaciona con las clases, que actúan como plantillas para las instancias. 186 17 Introducción a la Orientación a Objetos
Ahora consideramos las clases que crean las instancias. Podríamos suponer que cada objeto es una instancia de una clase equivalente (ver arriba (a)). Sin embargo, como ha Como ya se ha señalado, algunas de las clases tienen un parecido muy fuerte. en par- En particular, el fusible, el relé, el motor y la bomba comparten una serie de características comunes. características. La siguiente tabla compara las características (variables de instancia y vicios) de estos objetos. De esta tabla, los objetos difieren solo en el nombre. Esto sugiere que todos son instancias de una clase común como Component. Esta clase poseería un variable de instancia adicional, para simplificar la identificación de objetos. Si todos son instancias de una clase común, todos deben comportarse exactamente de la misma manera. mismo camino. Sin embargo, queremos que la bomba inicie el proceso de análisis cuando recibe el mensaje trabajando?, por lo que debe poseer una definición diferente de trabajar? de fusible y relé. En otros aspectos, es muy similar al fusible y al relé, por lo que pueden ser instancias de una clase (digamos Componente) y bomba y motor pueden ser instancias de clases que heredan de Component (¿pero redefinen el funcionamiento?). Esto se ilustra en la figura anterior (c). El diagrama de clase completo se presenta en la figura siguiente. fusible relé motor bomba Instancia variable estado estado estado estado servicios ¿laboral? ¿laboral? ¿laboral? ¿laboral? 17.5 ¿Dónde está la estructura en un programa OO? 187
17.6 Otras lecturas Si desea explorar algunas de las ideas presentadas en este capítulo con más detalle Aquí hay algunas referencias en línea: • https://en.wikipedia.org/wiki/Object-oriented_programming Esta es la wikipe- entrada dia para la Programación Orientada a Objetos y, por lo tanto, proporciona una referencia rápida gran parte de la terminología y la historia del tema y actúa como un salto Punto para otras referencias. • https://dev.to/charanrajgolla/beginners-guide—programación orientada a objetos que proporciona una mirada alegre a los cuatro conceptos dentro de la orientación a objetos. ciones a saber, abstracción, herencia, polimorfismo y encapsulación. • https://www.tutorialspoint.com/python/python_classes_objects.htm A Tutoriales Curso puntual sobre Programación Orientada a Objetos y Python. 188 17 Introducción a la Orientación a Objetos
capitulo 18 Clases de Python 18.1 Introducción En Python todo es un objeto y como tal es un ejemplo de un tipo o clase de cosas. Por ejemplo, los números enteros son un ejemplo de la clase int, los números reales son ejemplos de la clase flotante, etc. Esto se ilustra a continuación para una serie de diferentes tipos dentro de Python: Esto imprime una lista de clases que definen qué es un int, o un float o un bool, etc. en Python: Sin embargo, no solo está restringido a los tipos integrados (también conocidos como clases); tambien es es posible definir tipos definidos por el usuario (clases). Estos se pueden utilizar para crear su propia estructuras de datos, sus propios tipos de datos, sus propias aplicaciones, etc. Este capítulo considera las construcciones en Python utilizadas para crear definidos por el usuario clases imprimir (tipo (4)) imprimir (tipo (5.6)) imprimir (tipo (Verdadero)) imprimir (tipo (‘Ewan’)) imprimir (tipo ([1, 2, 3, 4])) <clase ‘int’> <clase ‘flotador’> <clase ‘bool’> <clase ‘str’> <clase ’lista’> © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_18 189
18.2 Definiciones de clase En Python, una definición de clase tiene el siguiente formato Aunque debe tener en cuenta que puede mezclar el orden de la definición de atributos butes y métodos según sea necesario dentro de una sola clase. El siguiente código es un ejemplo de una definición de clase: Aunque esta no es una regla estricta y rápida, es común definir una clase en un archivo nombrado después de esa clase. Por ejemplo, el código anterior se almacenaría en un archivo llamado Persona.py; esto hace que sea más fácil encontrar el código asociado con una clase. Esto es se muestra a continuación usando PyCharm IDE: clase nombreDeClase(SuperClase): en eso atributos métodos Persona de clase: def init(yo, nombre, edad): self.nombre = nombre self.edad = edad 190 18 Clases de Python
La clase Persona posee dos atributos (o variables de instancia) llamados nombre y edad. También hay un método especial definido llamado init. Este es un inicializador (también conocido como constructor) para la clase. Indica qué datos hay que suministrar cuándo se crea una instancia de la clase Person y cómo se almacenan esos datos internamente. En este caso, se debe proporcionar un nombre y una edad cuando una instancia del Se crea la clase de persona. Los valores proporcionados se almacenarán dentro de una instancia de la clase (representada por la variable especial self) en variables de instancia/atributos self.name y auto.edad. Tenga en cuenta que los parámetros del método init son variables locales y desaparecerá cuando finalice el método, pero self.name y self.age son variables de instancia y existirán mientras el objeto esté disponible. Veamos por un momento la variable especial self. Este es el primer parámetro pasado a cualquier método. Sin embargo, cuando se llama a un método, no pasamos un valor para este parámetro nosotros mismos; Python lo hace. Se utiliza para representar el objeto dentro que el método está ejecutando. Esto proporciona el contexto dentro del cual el método se ejecuta y permite que el método acceda a los datos que contiene el objeto. Así, el yo es el objeto mismo. Quizás también se esté preguntando acerca de ese término método. Un método es el nombre dado al comportamiento que está vinculado directamente a la clase Persona; no es un función independiente más bien es parte de la definición de la clase Persona. Históricamente, proviene del lenguaje Smalltalk; este lenguaje fue usado por primera vez para simular una planta de producción y un método representaba algún comportamiento que podría utilizarse para simular un cambio en la línea de producción; por lo tanto representó un método para hacer un cambio. 18.3 Creación de ejemplos de la persona de la clase Se pueden crear nuevas instancias/objetos (ejemplos) de la clase Persona usando el nombre de la clase y pasando los valores que se utilizarán para los parámetros de la mtodo de inicializacin (con la excepcin del primer parmetro self que es pro- vided automáticamente por Python). Por ejemplo, lo siguiente crea dos instancias de la clase Persona: La variable p1 contiene una referencia a la instancia u objeto de la clase Persona cuyos atributos contienen los valores ‘John’ (para el atributo de nombre) y 36 (para la edad atributo). A su vez, la variable p2 hace referencia a una instancia de la clase Persona cuyo Los atributos de nombre y edad contienen los valores ‘Phoebe’ y 21. Por lo tanto, en la memoria tenemos: p1 = Persona(‘Juan’, 36) p2 = Persona(‘Phoebe’, 21) 18.2 Definiciones de clase 191
Las dos variables hacen referencia a instancias separadas o ejemplos de la clase. Persona. Por lo tanto, responden al mismo conjunto de métodos/operaciones y tienen el mismo conjunto de atributos (como el nombre y la edad); Sin embargo, tienen su propia valores para esos atributos (como ‘John’ y ‘Phoebe’). Cada instancia también tiene su propio identificador único, que muestra que incluso si el los valores de los atributos resultan ser los mismos entre dos objetos (por ejemplo, hay resultan ser dos personas llamadas John que tienen 36 años); todavía están separados instancias de la clase dada. Se puede acceder a este identificador usando el id() función, por ejemplo: Cuando se ejecuta este código, p1 y p2 generarán diferentes identificadores, por ejemplo: Tenga en cuenta que el número real generado puede variar del anterior, pero aún debe ser único (dentro de su programa). 18.4 Tenga cuidado con la asignación Dado que en el ejemplo anterior, p1 y p2 hacen referencia a diferentes instancias del Persona de clase; ¿Qué sucede cuando p1 o p2 se asignan a otra variable? Eso es, lo que sucede en este caso: imprimir(‘id(p1):’, id(p1)) imprimir(‘id(p2):’, id(p2)) identificación (p1): 4547191808 identificación (p2): 4547191864 p1 = Persona(‘Juan’, 36) px = p1 192 18 Clases de Python
¿A qué hace referencia px? En realidad, hace una copia completa del valor retenido por p1; sin embargo, p1 no contiene la instancia de la clase Persona; tiene el dirección del objeto. Por lo tanto, copia la dirección contenida en p1 en la variable px. Este significa que tanto p1 como px ahora hacen referencia (apuntan a) la misma instancia en la memoria; ahí tenemos esto: Esto puede no ser obvio cuando imprime p1 y px: Como esto podría implicar que el objeto ha sido copiado: Sin embargo, si imprimimos el identificador único para lo que p1 y px hacen referencia entonces queda claro que es la misma instancia de la clase Persona: que imprime Como puede verse, el identificador único es el mismo. Por supuesto, si a p1 se le asigna posteriormente un objeto diferente (por ejemplo, si corrió p1 = p2), entonces esto no tendría efecto en el valor contenido en px; de hecho, nosotros ahora tendría: imprimir (p1) imprimir (px) juan tiene 36 juan tiene 36 imprimir(‘id(p1):’, id(p1)) imprimir(‘id(px):’, id(px)) identificación (p1): 4326491864 identificación (px): 4326491864 18.4 Tenga cuidado con la asignación 193
18.5 Impresión de objetos Si ahora usamos la función print() para imprimir los objetos en poder de p1 y p2, obtendrá lo que a primera vista podría parecer un resultado ligeramente extraño: La salida generada es Lo que esto muestra es el nombre de la clase (en este caso Persona) y un El número hexadecimal indica dónde se encuentra en la memoria. ninguno de los cuales es particularmente útil y ciertamente no nos ayuda a saber qué información p1 y p2 están aguantando. 18.5.1 Acceso a atributos de objetos Podemos acceder a los atributos que tienen p1 y p2 usando lo que se conoce como el punto notación. Esta notación nos permite seguir la variable que contiene el objeto con un punto (’.’) y el atributo al que nos interesa acceder. Por ejemplo, para acceder al nombre de un objeto de persona podemos usar p1.name o para su edad podemos usar p1.age: imprimir (p1) imprimir (p2) <main.Objeto de persona en 0x10f08a400> <main.Objeto de persona en 0x10f08a438> print(p1.nombre, ’es’, p1.edad) print(p2.nombre, ’es’, p2.edad) 194 18 Clases de Python
El resultado de esto es que sacamos Lo cual es bastante más significativo. De hecho, también podemos actualizar los atributos de un objeto directamente, por ejemplo, puede escribir: Si ahora corremos entonces obtendremos Veremos en un capítulo posterior (Propiedades de Python) que podemos restringir el acceso a estos atributos convirtiéndolos en propiedades. 18.5.2 Definición de una representación de cadena predeterminada En la sección anterior imprimimos información de las instancias de la clase Persona accediendo a los atributos nombre y edad. Sin embargo, ahora necesitábamos conocer la estructura interna de la clase Persona para imprimir sus detalles. Es decir, necesitamos saber que hay atributos llamados nombre y edad disponibles en esta clase. Sería mucho más conveniente si el propio objeto supiera cómo convertir su self en una cadena para imprimir! De hecho, podemos hacer que la clase Persona haga esto definiendo un método que puede ser se utiliza para convertir un objeto en una cadena con fines de impresión. Este método es el método str. Se espera que el método devuelva una cadena. que se puede usar para representar información apropiada sobre una clase. La firma del método es Los métodos que comienzan con un guión bajo doble (’__’) se consideran por convención special en Python y veremos varios de estos métodos más adelante en el libro. Para de momento nos centraremos únicamente en el método str(). juan tiene 36 Phoebe tiene 21 años p1.nombre = ‘Bob’ p1.edad = 54 print(p1.nombre, ’es’, p1.edad) bob tiene 54 def str(uno mismo) 18.5 Impresión de objetos 195
Podemos agregar este método a nuestra clase Persona y ver cómo eso afecta la salida generado al usar la función print(). Devolveremos una cadena del método str que proporciona y el nombre y edad de la persona: Tenga en cuenta que en el método str accedemos a los atributos de nombre y edad usando el parámetro self pasado al método por Python. También tenga en cuenta que es necesario ensayo para convertir el atributo de número de edad en una cadena. Esto se debe a que el ‘+’ operador hará la concatenación de cadenas a menos que uno de los operandos (uno de los lados de el ‘+’) es un número; en cuyo caso intentará hacer sumas aritméticas cuál de Por supuesto, no funcionará si el otro operando es una cadena. Si ahora tratamos de imprimir p1 y p2: La salida generada es: Que es mucho más útil. 18.6 Proporcionar un comentario de clase Es común proporcionar un comentario para una clase que defina lo que hace esa clase, su propósito y cualquier punto importante a tener en cuenta sobre la clase. Esto se puede hacer proporcionando una cadena de documentación para la clase justo después de la clase encabezado de declaración; puede usar la cadena de comillas triples (’’ ’’ ‘’…’’ ’’ ‘’) para crear cadenas de documentos de varias líneas, por ejemplo: Persona de clase: def init(yo, nombre, edad): self.nombre = nombre self.edad = edad def str(uno mismo): return self.nombre + ’ es ’ + str(self.edad) imprimir (p1) imprimir (p2) juan tiene 36 Phoebe tiene 21 años 196 18 Clases de Python
Se puede acceder a la cadena de documentos a través del atributo doc de la clase. El La intención es hacer que la información esté disponible para los usuarios de la clase, incluso en tiempo de ejecución. Él Los IDE también pueden utilizarlo para proporcionar información sobre una clase. 18.7 Agregar un método de cumpleaños Agreguemos ahora algo de comportamiento a la clase Persona. En el siguiente ejemplo, nos defina un método llamado birthday() que no tome parámetros e incremente el atributo edad por 1: Tenga en cuenta que, de nuevo, el primer parámetro que se pasa al método cumpleaños es self. Esto representa la instancia (el ejemplo de la clase Persona) que este método se usará con. Si ahora creamos una instancia de la clase Person y llamamos a birthday() en ella, la edad se incrementará en 1, por ejemplo: Persona de clase: """ Una clase de ejemplo para contener un nombre y edad de la persona""" def init(yo, nombre, edad): self.nombre = nombre self.edad = edad def str(uno mismo): return self.nombre + ’ es ’ + str(self.edad) Persona de clase: """ Una clase de ejemplo para contener el nombre y la edad de una persona""" def init(yo, nombre, edad): self.nombre = nombre self.edad = edad def str(uno mismo): return self.nombre + ’ es ’ + str(self.edad) def cumpleaños (uno mismo): Feliz cumpleaños fuiste’, self.age) ' += 1 imprimir ( edad propia print(‘Eres ahora’, self.edad) p3 = Persona(‘Adán’, 19) imprimir (p3) p3.cumpleaños() imprimir (p3) 18.6 Proporcionar un comentario de clase 197
Cuando ejecutamos este código, obtenemos Como puede ver, Adam tiene inicialmente 19 años; pero después de su cumpleaños ahora tiene 20. 18.8 Definición de métodos de instancia El método birthday() presentado arriba es un ejemplo de lo que se conoce como método de instancia; es decir, está vinculado a una instancia de la clase. En ese caso el método no tomó ningún parámetro, ni devolvió ningún parámetro; sin embargo, instancia Los métodos pueden hacer ambas cosas. Por ejemplo, supongamos que la clase Persona también se usará para calcular cuánto se le debe pagar a alguien. Supongamos también que la tasa es de 7,50 libras esterlinas si eres menor de 21 años pero que hay un suplemento de 2,50 si tienes 21 años o más. Podríamos definir un método de instancia que tomará como entrada el número de horas trabajado y devolver la cantidad que alguien debe pagar: Podemos invocar este método nuevamente usando la notación de puntos, por ejemplo: Ejecutar esto muestra que Phoebe (que tiene 21 años) recibirá un pago de £400 mientras que Adam, que es solo 19 se pagará solo £ 300: Adán tiene 19 feliz cumpleaños cumpliste 19 ahora tienes 20 Adán tiene 20 años Persona de clase: """ Una clase de ejemplo para contener el nombre y la edad de una persona""" #… def calcular_pago(auto, horas_trabajadas): tarifa_de_pago = 7.50 si self.age >= 21: tarifa_de_pago += 2.50 volver hours_worked * rate_of_pay pago = p2.calculate_pay(40) print(‘Pagar’, p2.nombre, pagar) pago = p3.calculate_pay(40) print(‘Pagar’, p3.nombre, pagar) Pagar Phoebe 400.0 Paga Adán 300.0 198 18 Clases de Python
Otro ejemplo de un método de instancia definido en la clase Person es el método is_teenager(). Este método no toma un parámetro, pero sí devuelve un valor booleano según el atributo de edad: Tenga en cuenta que el parámetro ‘self’ proporcionado implícitamente todavía se proporciona incluso cuando un método no toma un parámetro. 18.9 Resumen de la clase de persona Reunamos los conceptos que hemos visto hasta ahora en la versión final. de la clase Persona. Esta clase exhibe varias características que ya hemos visto y amplía algunas otros: Persona de clase: """ Una clase de ejemplo para contener el nombre y la edad de una persona""" #… def es_adolescente(yo): volver auto.edad < 20 Persona de clase: """ Una clase de ejemplo para contener el nombre y la edad de una persona""" def init(yo, nombre, edad): self.nombre = nombre self.edad = edad def str(uno mismo): return self.nombre + ’ es ’ + str(self.edad) def cumpleaños (uno mismo): Feliz cumpleaños fuiste’, self.age) imprimir (’ auto.edad += 1 print(‘Eres ahora’, self.edad) def calcular_pago(auto, horas_trabajadas): tarifa_de_pago = 7.50 si self.age >= 21: tarifa_de_pago += 2.50 volver hours_worked * rate_of_pay def es_adolescente(yo): volver auto.edad < 20 18.8 Definición de métodos de instancia 199
• La clase tiene un inicializador de dos parámetros que toma una Cadena y un Entero. • Define dos atributos que posee cada una de las instancias de la clase; nombre y edad. • Define un método str para que los detalles del objeto Persona puedan ser fácilmente impreso. • Él define tres métodos cumpleaños(), calcular_pagar() y es_adolescente(). • El método birthday() no devuelve nada (es decir, no devuelve ningún valor) y se compone de tres sentencias, dos sentencias impresas y una tarea. • is_teenager() devuelve un valor booleano (es decir, uno que devuelve verdadero o falso). A continuación se muestra una aplicación de ejemplo que utiliza esta clase: Esta aplicación crea una instancia de la clase Persona usando los valores ‘John’ y 36. Luego imprime p1 usando print (que llamará automáticamente al str() en las instancias que se le pasan). Luego accede a los valores de propiedades de nombre y edad y las imprime. A continuación, llama al es_adolescente() método y huellas dactilares el resultado devuelto Él entonces llamadas el método cumpleaños(). Finalmente, asigna un nuevo valor al atributo edad. El La salida de esta aplicación se da a continuación: 18.10 La palabra clave del Habiendo creado en un punto un objeto de algún tipo (ya sea un bool, un int o un tipo definido por el usuario, como Persona), es posible que luego sea necesario eliminar ese objeto. Esto se puede hacer usando la palabra clave del. Esta palabra clave se utiliza para eliminar objetos que permite que la memoria que están usando sea reclamada y utilizada por otros partes de su programa. p1 = Persona(‘Juan’, 36) imprimir (p1) imprimir (p1.nombre, ’es’, p1.edad) imprimir(‘p1.es_adolescente’, p1.es_adolescente()) p1.cumpleaños() imprimir (p1) p1.edad = 18 imprimir (p1) juan tiene 36 juan tiene 36 p1.is_teenager Falso feliz cumpleaños cumpliste 36 ahora tienes 37 Juan tiene 37 juan tiene 18 200 18 Clases de Python
Por ejemplo, podemos escribir Después de la declaración del, el objeto en poder de p1 ya no estará disponible y cualquier intento de referenciarlo generará un error. No es necesario que use del para establecer p1 arriba en el valor Ninguno (que representa la nada) tendrá el mismo efecto. Además, si el código anterior se define dentro de una función o un método, entonces p1 dejará de existir una vez que la función o el método termina y esto nuevamente tendrá el mismo efecto que eliminar el objeto y liberando la memoria. 18.11 Gestión automática de memoria La creación y eliminación de objetos (y su memoria asociada) es gestionada por el administrador de memoria de Python. De hecho, la provisión de un administrador de memoria (también conocida como gestión automática de memoria) es una de las ventajas de Python cuando en comparación con lenguajes como C y C++. No es raro escuchar C++ programadores que se quejan de pasar muchas horas tratando de rastrear un error particularmente incómodo solo para descubrir que era un problema asociado con la memoria asignación o manipulación de punteros. Del mismo modo, un problema habitual para los desarrolladores de C++ es el de la fluencia de memoria, que ocurre cuando se asigna memoria pero no se libera arriba. La aplicación utiliza toda la memoria disponible o se queda sin espacio y produce un error de tiempo de ejecución. La mayoría de los problemas asociados con la asignación de memoria en lenguajes como C ++ ocurren porque los programadores no solo deben concentrarse en el (a menudo complejo) lógica de la aplicación, sino también en la gestión de la memoria. Deben asegurarse de que asignar solo la memoria que se requiere y desasignarla cuando ya no sea requerido. Esto puede sonar simple, pero no es poca cosa en un gran complejo solicitud. Una pregunta interesante que hacer es “¿por qué los programadores tienen que administrar la memoria ¿asignación?”. Hay pocos programadores hoy en día que esperarían tener que gestionar los registros que utilizan sus programas, aunque hace 30 o 40 años los la situación era muy diferente. Una respuesta a la pregunta de gestión de memoria, citada a menudo por aquellos a quienes les gusta manejar su propia memoria, es que “es más eficiente, tienes más control, es más rápido y conduce a un código más compacto”. De Por supuesto, si desea llevar estos comentarios al extremo, entonces todos deberíamos estar programación en ensamblador. Esto nos permitiría a todos producir más rápido, más eficientemente. código más eficiente y compacto que el producido por Python o lenguajes como Java. Sin embargo, el punto sobre los lenguajes de alto nivel es que son más productivos, introducen menos errores, son más expresivos y son lo suficientemente eficientes (dado computadoras y tecnología de compilación). El problema de la gestión de la memoria es algo p1 = Persona(‘Juan’, 36) imprimir (p1) del p1 18.10 La palabra clave del 201
similar. Si el sistema maneja automáticamente la asignación y desasignación de memoria, entonces el programador puede concentrarse en la lógica de la aplicación. Esto hace el programador más productivo, elimina los problemas debido a la mala gestión de la memoria gestion y, cuando implementado eficientemente, poder aún proporcionar aceptable actuación. Por lo tanto, Python proporciona una gestión de memoria automática. Esencialmente, asigna proporciona una parte de la memoria cuando sea necesario. Cuando la memoria es corta, parece para áreas que ya no están referenciadas. Estas áreas de la memoria luego se liberan (desasignados) para que puedan ser reasignados. Este proceso a menudo se denomina Recolección de basura. 18.12 Atributos intrínsecos Cada clase (y cada objeto) en Python tiene un conjunto de atributos intrínsecos establecidos por el sistema de tiempo de ejecución de Python. Algunos de estos atributos intrínsecos se dan a continuación para clases y objetos. Las clases tienen los siguientes atributos intrínsecos: • name el nombre de la clase • module el módulo (o biblioteca) desde el que se cargó • bases una colección de sus clases base (ver herencia más adelante en este libro) • dict un diccionario (un conjunto de pares clave-valor) que contiene todos los atributos (incluidos los métodos) • doc la cadena de documentación. Para objetos: • class el nombre de la clase del objeto • dict un diccionario que contiene todos los atributos del objeto. Tenga en cuenta que todos estos atributos intrínsecos comienzan y terminan con un subrayado doble: esto indica su estado especial dentro de Python. Un ejemplo de cómo imprimir estos atributos para la clase Persona y una instancia de la clase se muestran a continuación: print(‘Atributos de clase’) imprimir(Persona.nombre) imprimir (Persona.módulo) imprimir(Persona.doc) imprimir(Persona.dict) print(‘Atributos del objeto’) imprimir(p1.clase) imprimir (p1.dict) 202 18 Clases de Python
La salida de esto es: 18.13 Recursos en línea Consulte lo siguiente para obtener más información sobre las clases de Python: • https://docs.python.org/3/tutorial/classes.html El Pitón Estándar biblioteca Tutoría de clase. • https://www.tutorialspoint.com/python3/python_classes_objects.htm El tutorial- Tutorial de rials point sobre las clases de Python 3. 18.14 Ejercicios El objetivo de este ejercicio es crear una nueva clase llamada Cuenta.
Defina una nueva clase para representar un tipo de cuenta bancaria.
Cuando se instancia la clase, debe proporcionar el número de cuenta, el nombre del titular de la cuenta, un saldo de apertura y el tipo de cuenta (que puede ser una cadena que representa ‘actual’, ‘depósito’ o ‘inversión’, etc.). Esto significa que debe haber un método init y deberá almacenar los datos dentro el objeto. Atributos de clase Persona principal Una clase de ejemplo para contener el nombre y la edad de una persona. {’module’: ‘main’, ‘doc’: ’ Una clase de ejemplo para tener el nombre y la edad de una persona’, ‘instance_count’: 4, ‘increment_instance_count’: <objeto de método de clase en 0x105955588>, ‘static_function’: <objeto de método estático en 0x1059555c0>, ‘init’: <función Persona.init en 0x10595d268>, ‘str’: <función Persona.str en 0x10595d2f0>, ‘cumpleaños’: <función Persona.cumpleaños a las 0x10595d378>, ‘calcular_pago’: <función Persona.calcular_pago en 0x10595d400>, ’es_adolescente’: <función Persona.es_adolescente en 0x10595d488>, ‘dict’: <atributo ‘dict’ de ‘Persona’ objetos>, ‘weakref’: <atributo ‘weakref’ de ‘Persona’ objetos>} Atributos de objeto <clase ‘principal.Persona’> {’nombre’: ‘Juan’, ’edad’: 36} 18.12 Atributos intrínsecos 203
Proporcione tres métodos de instancia para la Cuenta; cantidad del depósito), retirar (cantidad) y obtener_saldo (). El comportamiento de estos los métodos deben ser los esperados, el depósito aumentará el saldo, el retiro disminuye el saldo y get_balance() devuelve el saldo actual.
Defina una aplicación de prueba simple para verificar el comportamiento de su clase Cuenta. Puede ser útil ver cómo se espera que se utilice la cuenta de su clase. Para esto motivo, a continuación se proporciona una aplicación de prueba simple para la cuenta: El siguiente resultado ilustra cuál es el resultado de ejecutar esta aplicación de prueba podría verse como: acc1 = Cuenta(‘123’, ‘Juan’, 10.05, ‘actual’) acc2 = Cuenta(‘345’, ‘Juan’, 23.55, ‘ahorros’) acc3 = Cuenta(‘567’, ‘Phoebe’, 12.45, ‘inversion’) imprimir (acc1) imprimir (acc2) imprimir (acc3) acc1.deposito(23.45) acc1.retirar (12.33) imprimir(‘saldo:’, acc1.get_balance()) Cuenta[123] - Juan, cuenta corriente = 10,05 Cuenta[345] - John, cuenta de ahorros = 23,55 Cuenta[567] - Phoebe, cuenta de inversión = 12,45 saldo: 21.17 204 18 Clases de Python
capitulo 19 Lado de clase y comportamiento estático 19.1 Introducción Las clases de Python pueden contener datos y comportamientos que no forman parte de una instancia u objeto; en cambio, son parte de la clase. Este capítulo presenta los datos del lado de la clase, el comportamiento y el comportamiento estático. 19.2 Datos del lado de la clase En Python las clases también pueden tener atributos; estos se conocen como variables de clase o atributos (a diferencia de las variables de instancia o los atributos). En Python, las variables se definen dentro del alcance de la clase, pero fuera de cualquier Los métodos están vinculados a la clase en lugar de a cualquier instancia y, por lo tanto, son variables de clase. Por ejemplo, podemos actualizar la clase Persona para llevar la cuenta de cuántos Se crean instancias de la clase: Persona de clase: """ Una clase de ejemplo para contener el nombre y la edad de una persona""" recuento_de_instancias = 0 def init(yo, nombre, edad): Person.instance_count += 1 self.nombre = nombre self.edad = edad La variable instance_count no es parte de un objeto individual, sino que es parte de la clase y todas las instancias de la clase pueden acceder a esa variable compartida por prefijándolo con el nombre de la clase. © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_19 205
Ahora, cada vez que se crea una nueva instancia de la clase, el recuento de instancias es incrementado, por lo que si escribimos: p1 = Persona(‘Jason’, 36) p2 = Persona(‘Carola’, 21) p3 = Persona(‘James’, 19) p4 = Persona(‘Tom’, 31) imprimir (Persona.instancia_recuento) La salida será: 4 Esto se debe a que se han creado 4 instancias y, por lo tanto, init() se ha se ejecuta 4 veces y el recuento de instancias se ha incrementado cuatro veces. 19.3 Métodos del lado de la clase También es posible definir un comportamiento que está vinculado a la clase en lugar de un objeto individual; este comportamiento se define en un método de clase. Los métodos de clase se escriben de manera similar a cualquier otro método, pero son decorado con @classmethod y toma un primer parámetro que representa el clase en lugar de una instancia individual. Esta decoración está escrita antes de la declaración de método. A continuación se muestra un ejemplo de un método de clase: Persona de clase: """ Una clase de ejemplo para contener el nombre y la edad de una persona""" recuento_de_instancias = 0 @métodoclase def increment_instance_count(cls): cls.instance_count += 1 def init(yo, nombre, edad): Persona.increment_instance_count() self.nombre = nombre self.edad = edad En este caso, el método de clase incrementa la variable instance_count; nota que se accede a la variable instance_count a través del parámetro cls pasado a el método increment_instance_count de Python. Como esta es una clase método no necesita prefijar el atributo de clase con el nombre de la clase; en cambio el primer parámetro del método de clase, cls, representa la clase misma. 206 19 Lado de clase y comportamiento estático
Se puede acceder al método de clase prefijándolo con el nombre de la clase y utilizando la notación de puntos para indicar a qué método llamar. Esto se ilustra en el cuerpo del método init(). 19.3.1 ¿Por qué métodos del lado de la clase? Al principio, puede parecer poco claro qué debería ir normalmente en un método de instancia como opuesto a lo que debería ir en un método de clase. Después de todo, ambos están definidos en el clase. Sin embargo, es importante recordar que • Los métodos de instancia definen el comportamiento de la instancia o del objeto. • Los métodos de clase definen el comportamiento de la clase. Los métodos del lado de la clase solo deben realizar uno de los siguientes roles: • Creación de instancias Esta función es muy importante ya que es cómo puede usar una clase como fábrica de objetos y puede ayudar a ocultar una gran cantidad de configuración e instanciación trabajar. • Responder consultas sobre la clase. Este rol puede proporcionar información útil en general. objetos, frecuentemente derivados de variables de clase. Por ejemplo, pueden volver el número de instancias de esta clase que se han creado. • Gestión de instancias En esta función, los métodos del lado de la clase controlan el número de instancias creadas. Por ejemplo, una clase solo puede permitir una sola instancia del clase a crear; esto se denomina una clase singleton. Gestión de instancias También se pueden usar métodos para acceder a una instancia (por ejemplo, aleatoriamente o en un estado). • Ejemplos En ocasiones, los métodos de clase se utilizan para proporcionar ejemplos útiles. que explican el funcionamiento de una clase. Esto puede ser una muy buena práctica. • Los métodos de prueba del lado de la clase se pueden usar para respaldar la prueba de una instancia de un clase. Puede usarlos para crear una instancia, realizar una operación y comparar el resultado con un valor conocido. Si los valores son diferentes, el método puede informar un error. Esta es una forma muy útil de proporcionar pruebas de regresión. • Soporte para uno de los roles anteriores. Cualquier otra tarea debe ser realizada por un método de instancia. 19.4 Métodos estáticos Hay un tipo más de método que se puede definir en una clase; estos son estáticos métodos. 19.3 Métodos del lado de la clase 207
Los métodos estáticos se definen dentro de una clase, pero no están vinculados ni a la clase ni a la clase.
cualquier instancia de la clase; no reciben el primer parámetro especial que representa
ya sea la clase (cls para métodos de clase) o las instancias (self para métodos de instancia).
Son, en efecto, lo mismo que las funciones independientes, pero se definen dentro de un
class a menudo por conveniencia o para proporcionar una forma de agrupar dichas funciones.
Un método estático es un método que está decorado con el decorador @staticmethod.
A continuación se muestra un ejemplo de un método estático:
Persona de clase:
@métodoestático
def función_estática():
imprimir (‘método estático’)
Los métodos estáticos se invocan a través del nombre de la clase en la que están definidos, por
ejemplo:
Persona.static_function()
Una nota para los programadores de Java y C#; tanto en Java como en C# el término lado de la clase
y static se usan indistintamente (no ayuda el uso de la palabra clave static para
estos métodos). Sin embargo, en ambos casos esos métodos son el equivalente de la clase
métodos secundarios en Python. En Python, los métodos de clase y los métodos estáticos son dos muy,
cosas muy diferentes—no use estos términos indistintamente.
19.5
Sugerencias
Hay una variedad de métodos especiales disponibles en Python en una clase.
Todos estos métodos especiales comienzan y terminan con un guión bajo doble (’__’).
En general, en Python todo lo que comienza y termina con estas barras dobles es
considerados especiales, por lo que se debe tener cuidado al usarlos.
Nunca debe nombrar uno de sus propios métodos o funciones
• https://python-reference.readthedocs.io/en/latest/docs/functions/staticmethod. documentación html sobre métodos estáticos. • https://python-reference.readthedocs.io/en/latest/docs/functions/classmethod. html?highlight=classmethod documentación sobre métodos de clase. • https://www.tutorialspoint.com/class-method-vs-static-method-in-python tuto- rial sobre métodos de clase versus métodos estáticos. 19.7 Ejercicios El objetivo de este ejercicio es agregar métodos de estilo de limpieza a la clase Cuenta. Debes seguir estos pasos:
- Queremos permitir que la clase Cuenta del último capítulo realice un seguimiento de la número de instancias de la clase que se han creado.
- Imprima un mensaje cada vez que se cree una nueva instancia de la clase Cuenta.
- Imprima el número de cuentas creadas al final del programa de prueba anterior. Por ejemplo, agregue las siguientes dos declaraciones al final del programa: print(‘Número de instancias de cuenta creadas:’, Cuenta.instancia_recuento) 19.6 Recursos en línea 209
capitulo 20 Herencia de clase 20.1 Introducción La herencia es una característica fundamental de la programación orientada a objetos. Permite que una clase heredar datos o comportamiento de otra clase y es una de las formas clave en las que la reutilización está habilitada dentro de las clases. Este capítulo presenta la herencia entre clases en Python. 20.2 ¿Qué es la herencia? La herencia permite que las características definidas en una clase sean heredadas y reutilizadas en la definición de otra clase. Por ejemplo, una clase de Persona podría tener los atributos nombre y edad. También podría tener un comportamiento asociado con una Persona como cumpleaños(). Entonces podríamos decidir que queremos tener otra clase Empleado y que los empleados también tienen un nombre y una edad y tendrán cumpleaños. Sin embargo, en suma un Empleado puede tener un empleado Identificación atributo y a comportamiento de calcular_pagar(). En este punto podríamos duplicar la definición de los atributos de nombre y edad y el comportamiento de cumpleaños () en la clase Empleado (por ejemplo, cortando y pegando el código entre las dos clases). Sin embargo, esto no sólo es ineficiente; también puede causar problemas en el futuro. Por ejemplo, podemos darnos cuenta de que hay un problema o error en la implementación de cumpleaños() y puede corregirlo en la clase Persona; sin embargo, podemos olvidarnos de aplique el mismo arreglo a la clase Empleado. En general, en el diseño y desarrollo de software se considera una buena práctica definir algo una vez y reutilizar ese algo cuando sea necesario. © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_20 211
En un sistema orientado a objetos podemos lograr la reutilización de datos o comportamiento a través de herencia. Esa es una clase (en este caso, la clase Empleado) puede heredar características de otra clase (en este caso Persona). Esto se muestra pictóricamente a continuación: En este diagrama, la clase Empleado se muestra heredando de la clase Persona. Esto significa que la clase Empleado obtiene todos los datos y el comportamiento de la Persona clase. Por lo tanto, es como si la clase Empleado hubiera definido tres atributos: nombre, edad e id y dos métodos cumpleaños() y calcular_pago(). Una clase que se define como una extensión de una clase principal tiene la siguiente sintaxis: Tenga en cuenta que la clase principal se especifica proporcionando el nombre de esa clase en corchetes después del nombre de la nueva clase (secundaria). Podemos definir la clase Persona en Python como antes: Ahora podríamos definir la clase Empleado como una clase cuya definición se basa en (o hereda de) la clase Persona: clase NombreSubclase(NombreClaseBase): cuerpo de clase Persona de clase: def init(yo, nombre, edad): self.nombre = nombre self.edad = edad def cumpleaños (uno mismo): print(‘Feliz cumpleaños fuiste’, self.edad) auto.edad += 1 print(‘Eres ahora’, self.edad) 212 20 Herencia de clase
Aquí hacemos varias cosas:
- La clase se llama Empleado pero se extiende a Persona. Esto está indicado por incluyendo el nombre de la clase que se hereda entre paréntesis después del nombre de la clase que se está definiendo (por ejemplo, Empleado(Persona)) en la declaración de clase.
- Dentro del método init hacemos referencia al método init() definido en la clase Persona y utilizado para inicializar instancias de esa clase (a través de la super().init() referencia. Esto permite que cualquier inicialización sea requerido para que Persona suceda. Esto se llama desde dentro del Empleado init() de la clase que luego permite cualquier inicialización requerida por el Empleado a ocurrir. Tenga en cuenta que la llamada a super().init() ini- tialiser puede venir en cualquier lugar dentro del método Employee.init(); pero por convención, lo primero es asegurarse de que cualquier cosa que haga la clase Person durante la inicialización no sobrescribe lo que sucede en la clase Empleado.
- Todas las instancias de la clase Persona tienen un nombre, una edad y la comportamiento cumpleaños().
- Todas las instancias de la clase Empleado tienen un nombre, una edad y una identificación y tienen los comportamientos cumpleaños() y calcular_pago(casa_trabajada).
- El método calcular_pagar() definido en la clase Empleado puede acceder el nombre y la edad de los atributos tal como puede acceder a la identificación del atributo. De hecho, utiliza la edad del empleado para determinar la tasa de pago a aplicar. Podemos ir más allá, y podemos crear una subclase de Empleado, por ejemplo con la clase Vendedor: clase Empleado(Persona): def init(yo, nombre, edad, id): super().init(nombre, edad) self.id = id def calcular_pago(auto, horas_trabajadas): tarifa_de_pago = 7.50 si self.age >= 21: tarifa_de_pago += 2.50 volver hours_worked * rate_of_pay clase Vendedor(Empleado): def init(self, nombre, edad, id, región, ventas): super().init(nombre, edad, id) self.region = región self.sales = ventas bono def (uno mismo): devolver auto.ventas * 0.5 20.2 ¿Qué es la herencia? 213
Ahora podemos decir que la clase Vendedor tiene un nombre, una edad y un id así como una región y un total de ventas. También tiene los métodos cumpleaños(), calcular_pago(horas_trabajadas) y bono(). En este caso, el método SalesPerson.init() llama al empleado. método init() ya que esa es la siguiente clase en la jerarquía y, por lo tanto, queremos ejecute ese comportamiento de inicialización de clases antes de configurar la clase SalesPerson (que, por supuesto, a su vez ejecuta el comportamiento de inicialización de clases de persona). Ahora podemos escribir código como: Siendo la salida: Es importante notar que no le hemos hecho nada a la clase Persona por definiendo Empleado y Vendedor; es decir, no se ve afectado por esas clases definiciones. Por lo tanto, una Persona no tiene una identificación de empleado. Del mismo modo, ni un Ni el Empleado ni una Persona tienen una región o un total de ventas. imprimir(‘Persona’) p = Persona(‘Juan’, 54) imprimir (pag) imprimir(’-’ * 25) imprimir(‘Empleado’) e = Empleado(‘Denise’, 51, 7468) e.cumpleaños() print(’e.calculate_pay(40):’, e.calculate_pay(40)) imprimir(’-’ * 25) print(‘Vendedor’) s = Vendedor(‘Phoebe’, 21, 4712, ‘Reino Unido’, 30000.0) el cumpleaños de() print(’s.calculate_pay(40):’, s.calculate_pay(40)) print(’s.bonificación():’, s.bonificación()) Persona juan tiene 54
Empleado feliz cumpleaños cumpliste 51 ahora tienes 52 e.calculate_pay(40): 400.0
Vendedor feliz cumpleaños cumpliste 21 ahora tienes 22 s.calculate_pay(40): 400.0 s.bonus(): 15000.0 214 20 Herencia de clase
En términos de comportamiento, las instancias de las tres clases pueden ejecutar el método cumpleaños(), pero • solo los objetos Empleado y Vendedor pueden ejecutar el cálculo del método. cate_pay() y • sólo los objetos Vendedor pueden ejecutar el método bonus(). 20.3 Terminología sobre la herencia La siguiente terminología se usa comúnmente con la herencia en la mayoría de los objetos. lenguajes orientados incluyendo Python: Clase Una clase define una combinación de datos y procedimientos que operan sobre esos datos. Subclase Una subclase es una clase que hereda de otra clase. por ejemplo, un El empleado puede heredar de una clase Persona. Las subclases son, por supuesto, clases. en su propio derecho. Cualquier clase puede tener cualquier número de subclases. Superclase Una superclase es el padre de una clase. Es la clase de la que procede el la clase actual hereda. Por ejemplo, Person podría ser la superclase de Empleado. En Python, una clase puede tener cualquier número de superclases. Herencia única o múltiple La herencia única y múltiple se refiere a la número de superclases de las que una clase puede heredar. Por ejemplo, Java es un sistema de herencia única, en el que una clase solo puede heredar de una clase. Pitón por el contrario, es un sistema de herencia múltiple en el que una clase puede heredar de uno o más clases Tenga en cuenta que un conjunto de clases, involucradas en una jerarquía de herencia, como las que se muestra arriba, a menudo reciben el nombre de la clase en la raíz (parte superior) de la jerarquía; en este caso haría que estas clases fueran parte de la jerarquía de clases de Persona. 20.2 ¿Qué es la herencia? 215
Tipos de jerarquía En la mayoría de los sistemas orientados a objetos hay dos tipos de jerarquía; uno se refiere herencia (ya sea única o múltiple) y la otra se refiere a la instanciación. El La jerarquía de herencia ya se ha descrito. Es la forma en que una clase hereda características de una superclase. La jerarquía de creación de instancias se relaciona con instancias u objetos en lugar de clases y es importante durante la ejecución del objeto. Hay dos tipos de relaciones de instancia: una indica una relación de parte de barco, mientras que el otro se relaciona con una relación de uso (se lo conoce como un is- una relación). Esto se ilustra a continuación: La diferencia entre una relación es-un y una relación parte-de es a menudo confuso para los nuevos programadores (y a veces para aquellos que tienen experiencia en lenguajes no orientados a objetos). La figura anterior ilustra que un estudiante es un tipo de Persona mientras que un Motor es parte de un Coche. No tiene sentido decir que un estudiante es parte-de una persona o que un motor es-un tipo de coche! En Python, las relaciones de herencia se implementan mediante la subclasificación mecanismo. Por el contrario, las relaciones parte de se implementan utilizando instancia atributos en Python. El problema con las clases, la herencia y las relaciones es que en la superficie parecen capturar un concepto similar. En la siguiente figura las jerarquías todas capturar algún aspecto del uso de la frase es-a. Sin embargo, todos están destinados a capturar una relación diferente. La confusión se debe al hecho de que en inglés moderno tendemos a abusar del término es-a. Por ejemplo, en inglés podemos decir que un Empleado es un tipo de Persona o que Andrés es una Persona; ambos son semánticamente correctos. Sin embargo, en Python clases como Empleado y Persona y un objeto como Andrew son diferentes cosas. Podemos distinguir entre los diferentes tipos de relación siendo más precisos acerca de nuestras definiciones en términos de un lenguaje de programación, como Pitón. 216 20 Herencia de clase
20.4 El objeto de clase y la herencia Cada clase en Python extiende una o más superclases. Esto es cierto incluso de los clase Persona que se muestra a continuación: Esto se debe a que si no especifica una superclase explícitamente, Python automáticamente agrega el objeto de clase como una clase principal. Por lo tanto, lo anterior es exactamente lo mismo que el siguiente listado que enumera explícitamente el objeto de clase como la superclase de Person: Ambos listados anteriores definen una clase llamada Persona que extiende el objeto de la clase. De hecho, entre Python 2.2 y Python 3 se requería usar la mano larga formulario para asegurarse de que se estaban utilizando las nuevas clases de estilo (a diferencia de una antigua Persona de clase: def init(yo, nombre, edad): self.nombre = nombre self.edad = edad clase Persona(objeto): def init(yo, nombre, edad): self.nombre = nombre self.edad = edad 20.4 El objeto de clase y la herencia 217
forma en que las clases se definían antes de Python 2.2). Como tal, es común encontrar que los desarrolladores de Python todavía usan la forma manual larga (explícita) al definir clases que extienden directamente el objeto. El hecho de que todas las clases eventualmente hereden del objeto de clase significa que el comportamiento definido en objeto está disponible para todas las clases en todas partes. 20.5 La clase de objeto integrada El objeto de clase es la clase base (raíz) para todas las clases en Python. Tiene métodos que son por lo tanto disponible en todos los objetos de Python. Define un conjunto común de métodos especiales y atributos intrínsecos. Los métodos incluyen los métodos especiales str(), init(), eq() (igual) y hash() (método hash). También define atributos como class, dict, doc y module. 20.6 Propósito de las subclases Las subclases se utilizan para refinar el comportamiento y las estructuras de datos de una superclase. Una clase padre puede definir algunos métodos y atributos genéricos/compartidos; estos pueden luego ser heredado y reutilizado por varias otras (sub) clases que agregan subclase atributos y comportamientos específicos. De hecho, solo hay un pequeño número de cosas que una subclase debería hacer en relación a su padre o superclase. Si una subclase propuesta no hace nada de esto, entonces su la clase principal seleccionada no es la superclase más apropiada para usar. Una subclase debe modificar el comportamiento de su clase principal o ampliar los datos sostenido por su clase padre. Esta modificación debe refinar la clase en uno o más de estas formas: • Cambios al protocolo externo o interfaz de la clase, es decir debe extenderse el conjunto de métodos o atributos proporcionados por la clase. • Cambios en la implementación de los métodos; es decir, la forma en que el se implementa el comportamiento proporcionado por la clase. • Comportamiento adicional que hace referencia al comportamiento heredado. Si una subclase no proporciona uno o más de los anteriores, entonces es incorrectamente metido. Por ejemplo, si una subclase implementa un conjunto de nuevos métodos, pero no se refieren a los atributos o métodos de la clase padre, entonces la clase no es realmente una subclase del padre (no lo extiende). 218 20 Herencia de clase
Como ejemplo, considere la jerarquía de clases ilustrada arriba. Una clase raíz genérica ha sido definido. Esta clase define un Medio de Transporte que tiene puertas, combustible (ambos con valores predeterminados) y un método, start_up(), que inicia el motor del transporte. También se han definido tres subclases de Medios de Transporte: Bote, Coche y Tanque. Dos de estas subclases son apropiadas, pero una probablemente no debería heredar de Transporte. Consideraremos cada uno a su vez para determinar su idoneidad. • La clase Tank anula el número de puertas heredadas, utiliza el start_up method dentro del movimiento de método y proporciona un nuevo atributo. por lo tanto coincide con nuestros tres criterios. • De manera similar, la clase Car anula el número de puertas y usa el método puesta en marcha(). También utiliza la variable de instancia combustible dentro de un nuevo método. acelerar(). También, por lo tanto, coincide con nuestros criterios. • La clase Dinghy define un nuevo atributo velas y un nuevo método zarpar_vela(). Como tal, no utiliza ninguna de las características heredadas de Transporte. Sin embargo, podríamos decir que ha extendido el Transporte por proporcionando este atributo y método. Entonces debemos considerar las características pro- proporcionado por Conveyance. Podemos preguntarnos si tienen sentido dentro el contexto de Dinghy. Si asumimos que un bote es un pequeño barco a vela, sin cabina y sin motor, entonces nada heredado de Conveyance es útil. En este caso, es probable que Transporte esté mal llamado, ya que define una especie de vehículo de motor, y la clase Dinghy no debería haberlo ampliado. 20.7 Métodos de anulación La anulación ocurre cuando se define un método en una clase (por ejemplo, Persona) y también en una de sus subclases (por ejemplo, Empleado). Significa que los casos de Tanto la persona como el empleado responden a las solicitudes de que se ejecute este método, pero cada uno tiene su propia implementación del método. 20.6 Propósito de las subclases 219
Por ejemplo, supongamos que definimos el método str() en estos clases (para que tengamos una representación de cadena de estos objetos para usar con el función de impresión). La definición de pseudocódigo de esto en Persona podría ser: En Employee, podría definirse como: El método en Empleado reemplaza la versión en Persona para todas las instancias de Empleado. Si le preguntamos a una instancia de Employee por el resultado de str(), obtenga la cadena ‘Empleado (<some_id>)’. Si estás confundido, piénsalo de esta manera: Si le pide a un objeto que realice alguna operación, entonces, para determinar qué versión del se ejecuta el método, busque en la clase utilizada para crear la instancia. Si el método no está definido allí, busque en el padre de la clase. Siga haciendo esto hasta que encuentre un método que implemente la operación solicitada. Esta es la versión que se utiliza. Como ejemplo concreto, vea las clases Persona y Empleado a continuación; en que el método str() en Person se anula en Employee. Las instancias de estas clases se convertirán en una cadena usando str() pero la versión utilizada por las instancias de Employee diferirá de la utilizada con instancias de Persona, por ejemplo: def str(uno mismo): return ‘Persona’ + self.name + ‘is’ + str(self.age) Persona de clase: def init(yo, nombre, edad): self.nombre = nombre self.edad = edad def str(uno mismo): return self.nombre + ’ es ’ + str(self.edad) clase Empleado(Persona): def init(yo, nombre, edad, id): super().init(nombre, edad) self.id = id def str(uno mismo): return self.nombre + ’ es ’ + str(self.edad) + ’ - i str(self.id) + ‘)’ def str(uno mismo): devuelve ‘Empleado(’ + str(self.id) + ‘)’ 220 20 Herencia de clase
Genera como salida: Como se puede ver en esto, la clase Empleado imprime el nombre, la edad y la identificación de el Empleado mientras que la clase Persona solo imprime el nombre y la edad. 20.8 Ampliación de métodos de superclase Sin embargo, en la sección anterior tuvimos que duplicar el código en Person down en Employee para que pudiéramos convertir los atributos de nombre y edad en cadenas. Sin embargo, podemos evitar esta duplicación invocando el método de la clase principal desde dentro de la versión de la clase secundaria (como de hecho hicimos para el inicializador init()). Por ejemplo: En esta versión del código, la versión de clases de Empleado de str() primero llama a la versión de clases padre de este método y luego agrega el información de ubicación a la cadena devuelta de eso. Esto significa que solo tenemos una ubicación que convierte el nombre y la edad en una cadena. p = Persona(‘Juan’, 54) imprimir (pag) e = Empleado(‘Denise’, 51, 1234) imprimir (e) juan tiene 54 Denise tiene 51 años - id (1234) clase Persona: definitivamente init(yo, nombre, edad): self.nombre = nombre self.edad = edad def str(uno mismo): return self.nombre + ’ es ’ + str(self.edad) clase Empleado(Persona): definitivamente init(yo, nombre, edad, id): super().init(nombre, edad) self.id = id def str(uno mismo): devuelve super().str() + ‘-id(’ + str(self.id) + ‘)’ 20.7 Métodos de anulación 221
La salida del código queda exactamente igual: 20,9 Convenciones de nomenclatura orientadas a la herencia Hay dos convenciones de nomenclatura a tener en cuenta con respecto a las clases de Python y herencia estos son eso • Convención debajo de la barra simple. Métodos o variables/atributos de instancia (aquellos accedido a través de uno mismo) cuyos nombres comienzan con una sola barra bajo se considera que estar protegidos, es decir, son privados para la clase, pero se puede acceder a ellos desde cualquier subclase. Su alcance es, por lo tanto, la clase y cualquier subclase (ya sea subclase directa clases o cualquier nivel de sub subclase). • Doble convención debajo de la barra. Variables/atributos de método o instancia (aquellos accedido a través de uno mismo) cuyos nombres comienzan con una barra doble debajo debe ser considerado privado a esa clase y no debe ser llamado desde fuera de la clase. Esto incluye cualquier subclase; privado significa privado para la clase y solo a esa clase. Cualquier identificador de la forma __somename (al menos dos guiones bajos iniciales y al menos la mayoría de un guión bajo al final) se reemplaza textualmente con _classname__somename, donde classname es el nombre de la clase actual sin los guiones bajos iniciales. Python hace lo que se llama manipulación de nombres para brindar cierto soporte a los métodos. que comienzan con una barra doble debajo. Esta manipulación se hace sin tener en cuenta la posición sintáctica del identificador, por lo que puede usarse para definir una instancia privada de clase y variables de clase, métodos, variables almacenadas en globales e incluso variables almacenadas en instancias. p = Persona(‘Juan’, 54) imprimir (pag) e = Empleado(‘Denise’, 51, 1234) imprimir (e) juan tiene 54 Denise tiene 51 años - id (1234) 222 20 Herencia de clase
20.10 Python y herencia múltiple Python apoya la idea de la herencia múltiple; esa es una clase que puede heredar de una o más clases (muchos lenguajes orientados a objetos limitan la herencia a una sola clase como Java y C#). Esta idea se ilustra con el siguiente diagrama: En este caso la clase ToyCar hereda de la clase Car y de la clase Toy. En convertir las clases Car y Toy heredan del objeto de clase base (predeterminado). La sintaxis para definir la herencia múltiple en Python permite múltiples super- clases que se incluirán en la lista de clases principal (definidas por los corchetes que siguen al nombre de la clase). Cada clase principal está separada por una coma. La sintaxis es así: Por ejemplo: clase NombreSubclase(NombreClaseBase1, NombreClaseBase2, … NombreClaseBaseN): cuerpo de clase Coche de clase: """ Auto """ Juguete de clase: """ Juguete """ clase ToyCar(Coche, Juguete): """ Un coche de juguete """ 20.10 Python y herencia múltiple 223
Podemos decir que la clase ToyCar hereda todos los atributos (datos) y métodos (comportamiento) definido en las clases Car, Toy y object. Una de las cuestiones fundamentales que esto plantea es cómo se hereda comportamiento gestionado dentro de una jerarquía de herencia múltiple. el reto que La herencia múltiple se ilustra agregando un par de métodos al jerarquía de clases que estamos viendo. En este ejemplo hemos añadido el método move() tanto a la clase Coche como a la clase Juguete: La pregunta aquí es qué versión del método move() se ejecutará cuando un instancia de la clase ToyCar se crea una instancia y llamamos toy_car.move()? Esto ilustra (una versión simple de) la llamada “herencia de diamantes” problema. El problema es que con múltiples clases base desde las cuales los atributos o métodos puede ser heredada, a menudo hay ambigüedad que debe ser resuelta. Aquí, cuando nosotros crear una instancia de la clase ToyCar y llamar al método move(), hace esto invocar el heredado de la clase base Car o de la clase base Toy? La respuesta es que en Python 3, se usa una búsqueda en amplitud para encontrar métodos definido en clases de padres; esto significa que cuando se llama al método move() ToyCar, primero buscaría en Car; entonces solo buscaría en Toy si no pudiera encuentra un método move() en Car. Si no puede encontrar el método en Car o Toy, entonces buscaría en el objeto de clase. Como resultado, primero encontrará la versión en Car y usará esa versión. 224 20 Herencia de clase
Esto se muestra a continuación: La salida de esto es Sin embargo, si alteramos el orden en que el ToyCar hereda del padre clases tales que intercambiamos Toy and Car alrededor: Luego se busca primero en la clase Toy y la salida se cambia a Toy – move(). Esto muestra que el orden en que una clase hereda de varias clases es significativo en Python. 20.11 Herencia múltiple considerada dañina A primera vista, la herencia múltiple en Python puede parecer particularmente útil; después de todo, le permite mezclar varios conceptos en una sola clase muy fácil y rápidamente. Esto es ciertamente cierto y puede ser una característica muy flexible si se usa con cuidado. Sin embargo, la palabra cuidado se usa aquí y debe tenerse en cuenta. La herencia múltiple también puede ser muy peligrosa y es un tema bastante polémico. para programadores y para aquellos que diseñan lenguajes de programación. Pocas cosas en la programación son intrínsecamente malas, pero la herencia múltiple puede resultar en un nivel de complejidad (y comportamiento inesperado) que puede atar a los desarrolladores en nudos. Parte del problema destacado por quienes protestan contra la herencia múltiple se debe a la mayor complejidad y ambigüedad que puede ocurrir con múltiples árboles de herencia que pueden interconectarse entre las diferentes clases. Una manera de Coche de clase: def move(self): print(‘Coche - mover()’) Juguete de clase: def move(self): print(‘Juguete - mover()’) clase ToyCar(Coche, Juguete): """ Un coche de juguete """ tc = Coche de juguete() tc.mover() Coche - mover() clase ToyCar(Juguete, Coche): """ Un coche de juguete """ 20.10 Python y herencia múltiple 225
pensar en esto es que si una clase hereda de varias clases, entonces esa clase puede tener las mismas clases en la jerarquía de clases varias veces, esto puede dificultar determinar qué versión de un método se puede ejecutar y esto puede permitir que se produzcan errores intactos o, de hecho, introducen problemas esperados debido a diferentes interacciones entre métodos. Esto se agrava cuando los métodos heredados llaman a super() usando el mismo nombre de método como: El siguiente diagrama presenta una herencia múltiple un tanto enrevesada ejemplo en el que se han utilizado los nombres de clase A-X para que no haya semántica significado atribuible a las clases heredadas. Diferentes clases definen varias métodos comunes (print_info() y get_data()). Todas las clases en la jerarquía definen un método str() que devuelve el nombre de la clase; si la clase extiende una clase que no sea objeto, entonces la super versión de str() también se invoca: El código para esta jerarquía de clases se proporciona al final de la sección para evitar rompiendo el flujo. Ahora podemos usar la clase X en un programa simple de Python: def get_data(auto): devolver super().get_data() + ‘FData’ 226 20 Herencia de clase
La pregunta ahora es ¿cuál es el resultado de este programa? ¿Cuál es la cadena impresa para representar X? Lo que se imprime como resultado de llamando al método print_info()? La salida de este código simple es: Sin embargo, si cambiamos el orden de herencia de la clase ‘H’ de (F, G) a (G, F) entonces la salida cambia: Por supuesto, esto se debe a que el orden de búsqueda, retrocediendo a través de la jerarquía de clases, es ahora diferente Tenga en cuenta que este cambio se produjo no debido a una modificación que hicimos en el clase que instanciamos (esa es la clase X), pero por el orden de las clases que uno heredado de sus padres. Esta puede ser una de las consecuencias no deseadas de herencia múltiple; cambiar algo en la jerarquía de clases múltiples a la vez nivel puede romper algún comportamiento más abajo en la jerarquía en una clase que es desconocido para el desarrollador. También tenga en cuenta que el diagrama de herencia de clases que presentamos anteriormente no indicaba en qué orden se enumeraron las clases principales para cualquier clase específica (esto se dejó al discreción del programador). Por supuesto, Python no es ambiguo ni se confunde; es el humano desarrollador que puede confundirse y sorprenderse con el comportamiento que luego se presentado. De hecho, si intenta definir una jerarquía de clases que Python no puede resuelve en una estructura consistente, te lo dirá, por ejemplo: x = x() imprimir (‘imprimir (x):’, x) imprimir(’-’ * 25) x.imprimir_info() imprimir(x): CGFHJX
HCDataGDataFData imprimir(x): CFGHJX
HCDataFDataGData Rastreo (llamadas recientes más última): Archivo “herencia_múltiple_ejemplo.py”, línea 65, en <mo clase Z(H, J): TypeError: no se puede crear una resolución de método consistente orden (MRO) para bases F, G 20.11 Herencia múltiple considerada dañina 227
Lo que puede ser confuso es que la capacidad de Python para producir una estructura consistente también puede depender del orden de herencia. Por ejemplo, si modificamos el clases de las que ‘X’ hereda tal que el orden es I y J: Entonces esto se compila y se puede usar con el código anterior (aunque con diferentes producción): Sin embargo, si cambiamos el orden de las clases principales de modo que intercambiemos I y J: Ahora obtenemos una excepción TypeError planteada: Por lo tanto, en general se debe tener cuidado al utilizar la herencia múltiple; pero eso no quiere decir que tales situaciones no sean útiles. En algunos casos, desea un clase para heredar de los padres que tienen jerarquías completamente diferentes y son completamente separados unos de otros; en tales situaciones la herencia múltiple puede ser muy útil: estos llamados comportamientos ortogonales son uno de los mejores usos de herencia múltiple y no debe ignorarse simplemente debido a preocupaciones de aumento complejidad. Las definiciones de clase utilizadas para la jerarquía de clases de herencia múltiple se dan abajo: clase X (I, J): def str(uno mismo): devuelve super().str() + ‘X’ imprimir(x): AIX
A clase X(J, I): def str(uno mismo): devuelve super().str() + ‘X’ Rastreo (llamadas recientes más última): Archivo “herencia_múltiple_ejemplo.py”, línea 73, en <módulo> clase X(J, I): TypeError: no se puede crear una resolución de método consistente orden (MRO) para bases J, I 228 20 Herencia de clase
clase A: def str(uno mismo): devuelve ‘A’ def imprimir_info(auto): imprimir(‘A’) clase B: def str(uno mismo): devuelve ‘B’ clase C: def str(uno mismo): devuelve ‘C’ def get_data(auto): devolver ‘CData’ clase D: def str(uno mismo): devuelve ‘D’ def imprimir_info(auto): imprimir(‘D’) clase E: def str(uno mismo): devuelve ‘E’ def imprimir_info(auto): imprimir(‘E’) clase F (C, D, E): def str(uno mismo): devuelve super().str() + ‘F’ def get_data(auto): devolver super().get_data() + ‘FData’ def imprimir_info(auto): imprimir(‘F’ + self.get_data()) clase G (C, D, E): def str(uno mismo): devuelve super().str() + ‘G’ def get_data(auto): devolver super().get_data() + ‘GData’ clase H(F, G): def str(uno mismo): devuelve super().str() + ‘H’ def imprimir_info(auto): imprimir(‘H’ + self.get_data()) 20.11 Herencia múltiple considerada dañina 229
20.12 Resumen Para recapitular sobre el concepto de herencia. Se admite la herencia entre clases en Pitón. Por ejemplo, una clase puede extender (subclase) otra clase o un conjunto de clases. Una subclase hereda todos los métodos y atributos definidos para la(s) clase(s) principal(es) pero puede anularlos en la subclase. En cuanto a la herencia decimos: • Una subclase hereda de una superclase. • Una subclase obtiene todo el código y los atributos de la superclase. • Una subclase puede agregar nuevos códigos y atributos. • Una subclase puede anular el código y los atributos heredados. • Una subclase puede invocar un comportamiento heredado o acceder a atributos heredados. 20.13 Recursos en línea Hay muchos recursos disponibles en línea relacionados con la herencia de clases, que incluyen: • https://docs.python.org/3/tutorial/classes.html#inheritance El software Python Tutorial básico sobre la herencia de clases. • https://en.wikipedia.org/wiki/Multiple_inheritance que proporciona una discusión sobre la herencia múltiple y los posibles desafíos que puede presentar. clase J(H): def str(uno mismo): devuelve super().str() + ‘J’ clase I(A, J): def str(uno mismo): devuelve super().str() + ‘I’ clase X(J, H, B): def str(uno mismo): devuelve super().str() + ‘X’ 230 20 Herencia de clase
20.14 Ejercicios El objetivo de estos ejercicios es ampliar la clase Cuenta que ha desarrollado. abierto de el último dos capítulos por Proporcionar Cuenta de depósito, Subclases CurrentAccount y InvestmentAccount. Cada una de las clases debe extender la clase Cuenta por: • CuentaCorriente agregando un límite de sobregiro y redefiniendo el método de dibujo. • DepositAccount agregando una tasa de interés. • InvestmentAccount agregando un atributo de tipo de inversión. Estas características se analizan a continuación: La clase CuentaActual puede tener un atributo overdraft_limit. Este se puede establecer cuando una instancia de una clase se crea y modifica durante la vida de el objeto. El límite de sobregiro debe incluirse en el método str() utilizado para convertir la cuenta en una cadena. El método de retiro de cuenta actual () debe verificar que el saldo nunca va por debajo del límite de sobregiro. Si lo hace, entonces el método de retiro () no debería reducir el saldo sino que debería imprimir un mensaje de advertencia. La Cuenta de Depósito debe tener una tasa de interés asociada que sea se incluye cuando la cuenta se convierte en una cadena. La cuenta de inversión tendrá un atributo tipo_inversión que puede contener una cadena como “seguro” o “alto riesgo”. Esto también significa que ya no es necesario pasar el tipo de cuenta como parámetro: está implícito en el tipo de clase que se crea. Por ejemplo, dado este fragmento de código:
CuentaActual(número_cuenta, titular_cuenta,
saldo_apertura, límite_sobregiro)
acc1 = CuentaActual(‘123’, ‘Juan’, 10.05, 100.0)
DepositAccount(número_cuenta, titular_cuenta,
saldo de apertura,
tasa de interés)
acc2 = DepositAccount(‘345’, ‘John’, 23.55, 0.5)
CuentaInversión(número_cuenta, titular_cuenta,
saldo de apertura,
tipo_de_inversión)
acc3 = CuentaInversión(‘567’, ‘Phoebe’, 12.45, ‘alto riesgo’) acc1.deposito(23.45) acc1.retirar (12.33) imprimir(‘saldo:’, acc1.get_balance()) acc1.retiro(300.00) imprimir(‘saldo:’, acc1.get_balance()) 20.14 Ejercicios 231
Entonces la salida podría ser: saldo: 21.17 El retiro excedería su límite de sobregiro saldo: 21.17 232 20 Herencia de clase
capitulo 21 ¿Por qué molestarse con la orientación a objetos? 21.1 Introducción Los cuatro capítulos anteriores han introducido los conceptos básicos detrás del origen del objeto. entación, la terminología y exploró algunas de las motivaciones. Este capítulo parece cómo la orientación a objetos aborda algunos de los problemas que se han planteado con lenguajes procesales. Para hacer esto, analiza cómo un pequeño extracto de un programa podría estar escrito en un lenguaje como C, considera los problemas que enfrenta el desarrollador de C y luego analiza cómo se podría lograr la misma funcionalidad en un lenguaje orientado a objetos como Python. No te preocupes demasiado por la sintaxis. se te presentará; es principalmente una forma de pseudocódigo y no debería en detrimento de la legibilidad de los ejemplos. 21.2 El enfoque procesal Considere el siguiente ejemplo: Esto define una estructura de datos para registrar fechas. Hay estructuras similares en muchos lenguajes procedimentales como C, Ada y Pascal. Entonces, ¿qué tiene de malo una estructura como esta? Nada, aparte de la cuestión de ¿visibilidad? Es decir, qué puede ver esta estructura y qué puede actualizar los contenidos de grabar fecha { día internacional mes internacional año int } © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_21 233
¿la estructura? Cualquier código puede acceder directamente y modificar su contenido. ¿Es este problema? lem? Podría ser, por ejemplo, que algún código pudiera establecer el día en −1, el mes en 13 y el año a 9999. En lo que respecta a la estructura, la información que contiene ahora está bien (es decir, día = 01, mes = 13, año = 9999). Esto se debe a que la estructura solo sabe que es se supone que contiene números enteros; no sabe nada acerca de las fechas per se. Esto no es sobre- Sorprendente, son solo datos. 21.2.1 Procedimientos para la Estructura de Datos Estos datos están asociados a procedimientos que realizan operaciones sobre ellos. Estos las operaciones pueden ser para • probar si la fecha representa una fecha en un fin de semana o parte de la semana laboral. • cambiar la fecha (en cuyo caso el procedimiento también puede verificar que la fecha es una válida). Por ejemplo: ¿Cómo sabemos que estos procedimientos están relacionados con la estructura de fechas que tenemos? acaba de mirar? Por las convenciones de nomenclatura de los procedimientos y por el hecho de que uno de los parámetros es un dato (registro). El problema es que estos procedimientos no están limitados en lo que pueden hacer a la datos (por ejemplo, el procedimiento setDay podría haber sido implementado por un británico quien asume que el orden de los datos es día, mes y año. Sin embargo, se puede utilizar por un estadounidense que asume que el orden de las fechas es mes, día, año. Así la media de set_day(fecha, 9, 3, 1946) se interpretará de manera muy diferente. El estadounidense ve esto como el 3 de septiembre de 1946, mientras que el británico ve esto como el 9 de marzo de 1946. En cualquier caso, nada impide que se registre la fecha. actualizado con ambas versiones. Obviamente, el procedimiento set_day() podría verificar el nueva fecha para ver que era legal, pero de nuevo podría no serlo. El problema es que los datos está desnudo y no tiene defensa contra lo que estos procedimientos le hacen. De hecho, no tiene defensa frente a lo que puedan hacer sobre él los procedimientos que puedan acceder a él. es_día_de_la_semana(fecha) en_mes(fecha, 2) next_day (fecha) set_day(fecha, 9, 3, 1946) 234 21 ¿Por qué molestarse con la orientación a objetos?
21.2.2 Paquetes Por supuesto, una posibilidad es utilizar una construcción de paquete. En idiomas como Ada, Los paquetes son un lugar común y se utilizan como una forma de organizar el código y restringiendo la visibilidad. Por ejemplo, La construcción del paquete proporciona cierta delimitación de la estructura de datos y una agrupación de la estructura de datos con los procedimientos asociados. Para usar este paquete un desarrollador debe importar el paquete. A continuación, pueden acceder a los procedimientos. y trabajar con datos del tipo especificado (en este caso Fecha). Incluso puede haber datos que estén ocultos para el usuario dentro de una parte privada. Este por lo tanto, aumenta la capacidad de encapsular los datos (ocultar los datos) de ven atencion 21.3 ¿La orientación a objetos funciona mejor? Esta es una pregunta importante “¿La orientación a objetos funciona mejor?” que la enfoque procedimental descrito anteriormente? Primero consideraremos las clases y luego herencia. 21.3.1 Paquetes Versus Clases Se ha argumentado (al menos para mí) que un paquete de Ada es como una clase. Él proporciona una plantilla a partir de la cual puede crear código ejecutable, proporciona un muro alrededor de sus datos con puertas de enlace bien definidas, etc. Sin embargo, hay una serie de diferencias muy significativas entre paquetes y clases. En primer lugar, los paquetes tienden a ser unidades más grandes (al menos conceptualmente) que las clases. Para ejemplo, el paquete TextIO en Ada es esencialmente una biblioteca de recursos IO textuales, en lugar de un solo concepto como la cadena de clase en Python. Así los paquetes son no se usa para encapsular un solo concepto pequeño como cadena o fecha, sino más bien un conjunto completo de conceptos relacionados (como de hecho se usan en Python). Así, una clase es un nivel más fino de granularidad que un paquete. las fechas del paquete son tipo Fecha es …. función es_día_de_la_semana(d: Fecha) return Boolean; función en_mes(d: Fecha, m: Entero) return booleano; … 21.2 El enfoque procesal 235
En segundo lugar, los paquetes aún proporcionan una asociación relativamente flexible entre los datos y los procedimientos. Un paquete de Ada en realidad puede tratar con muchos datos estructuras con una amplia gama de métodos. Los datos y los métodos están relacionados. principalmente a través del conjunto relacionado de conceptos representados por el paquete. En contraste un La clase tiende a relacionar estrechamente datos y métodos en un solo concepto. De hecho, uno de los Las pautas relacionadas con un buen diseño de clase es que si una clase representa más de una concepto, entonces debe dividirlo en dos clases. Por lo tanto, esta estrecha asociación entre datos y código significa que el resultado concepto es más que una simple estructura de datos (está más cerca de una realización concreta de concepto). Por ejemplo: Cualquiera que use una instancia de Fecha ahora obtiene un objeto que puede decirle si es un día de la semana o no y puede contener los datos apropiados. Tenga en cuenta que el método is_day_of_week() no toma otros parámetros además de sí mismo, no necesita como él y la información de la fecha son parte de la misma cosa. Esto significa que un el usuario de un objeto de fecha nunca necesitará tener en sus manos la retención de datos real la fecha (es decir, los números enteros día, mes y año). En su lugar, deben ir a través de la métodos. Esto puede parecer solo un pequeño paso, pero es significativo, nada fuera del objeto debería necesitar acceder a los datos dentro del objeto. En contraste el estructura de datos en la versión de procedimiento, no sólo se lleva a cabo por separado para el procedimiento duraciones, los valores para el día, el mes o el año también se deben modificar directamente. Por ejemplo, compare las diferencias entre un extracto de un programa y manipular fechas (usando un lenguaje de programación procedimental): fecha de la clase: def init(auto, día, mes, año): self.dia = dia self.mes = mes self.año = año def es_día_de_la_semana(self): “““Verificar si la fecha es un día de la semana””” # … Por definir def en_mes(self, index_mes): “““Comprobar si el mes está en month_index””” return self.mes == índice_mes 236 21 ¿Por qué molestarse con la orientación a objetos?
Tenga en cuenta que primero fue necesario crear los datos y luego establecer los campos en el estructura de datos. Aquí hemos sido buenos y hemos utilizado los procedimientos de interfaz para hacer esto. Una vez que tuvimos los datos configurados, pudimos llamar a métodos como IsDayOfWeek e InMonth en esos datos. En contraste, el código de Python usa un constructor para pasar la inicialización apropiada. información de cialización. La forma en que esto se inicializa internamente está oculta para el usuario de la fecha de la clase. Luego llamamos a un método como is_day_of_week() y is_month(12) directamente en la fecha del objeto. Lo que hay que pensar aquí es dónde se definiría el código. 21.3.2 Herencia La herencia es un elemento clave en un lenguaje orientado a objetos que permite que una clase heredar datos y métodos de otro. Una de las características más importantes de la herencia (irónicamente) es que permite que el desarrollador para entrar en la burbuja de encapsulación de forma limitada y controlada. Esto permite que la subclase aproveche las estructuras de datos internas y métodos, sin comprometer la encapsulación proporcionada a los objetos. Por ejemplo, definamos una subclase de la clase Fecha: fecha = fecha (12, 2, 1998) fecha.es_día_de_la_semana() fecha.en_mes(12) establecerDia(d, 28); establecerMes(d, 2); establecerAño(d, 1998); esDíaDeLaSemana(d); enMes(d, 2); d: Fecha; cumpleaños de la clase (fecha): nombre = ’’ edad = 0 def es_cumpleaños(): # … Comprobar para ver si es su cumpleaños 21.3 ¿La orientación a objetos funciona mejor? 237
El método is_birthday() podría verificar si la fecha actual coincide el cumpleaños representado por una instancia de Cumpleaños y devolver verdadero si lo hace y falso si no es así. Tenga en cuenta, sin embargo, que lo interesante aquí es que no solo no hemos tenido que definir enteros para representar la fecha, ni hemos tenido que definir métodos para acceder tales fechas. Ambos han sido heredados de la clase padre Fecha. Además, ahora podemos tratar una instancia de Cumpleaños como una Fecha o como un Cumpleaños dependiendo de lo que queramos hacer! ¿Qué harías en lenguajes como C, Pascal o Ada? Una posibilidad es que podría definir un nuevo paquete Cumpleaños, pero ese paquete no se extendería Fechas, ¿tendría que importar Fechas y agregarle interfaces, etc.? Sin embargo, usted ciertamente no podría tratar un paquete de Cumpleaños como un paquete de Fechas. En lenguajes como Python, debido al polimorfismo, puedes hacer exactamente eso. Puede reutilizar el código existente que solo conocía la Fecha, por ejemplo: Esto se debe a que el cumpleaños es de hecho un tipo de Fecha además de ser un tipo de Cumpleaños. También puede usar todas las funciones definidas para Fecha en cumpleaños: De hecho, en realidad no sabe dónde está definido el método. Este método podría definirse en la clase Cumpleaños (donde anularía lo definido en la fecha de la clase). Sin embargo, podría definirse en la clase Fecha (si no existe tal método). se define en Cumpleaños); sin mirar el código fuente no hay forma de ¡conocimiento! Por supuesto, también puede usar los nuevos métodos definidos en la clase Cumpleaños el instancia (objetos) de esta clase. Por ejemplo: cumpleaños = cumpleaños (12, 3, 1974) prueba de definición (fecha):
Haz algo que funcione con una cita
t.test(cumpleaños) cumpleaños.es_día_de_la_semana() cumpleaños.es_cumpleaños() 238 21 ¿Por qué molestarse con la orientación a objetos?
21.4 Resumen Las clases en un lenguaje orientado a objetos proporcionan una serie de características que no son presente en los lenguajes procedimentales. Para resumir, los puntos principales a destacar de este capítulo sobre la orientación a objetos son: • Las clases prevén la herencia. • La herencia prevé la reutilización. • La herencia prevé la extensión de un tipo de datos. • La herencia permite el polimorfismo. • La herencia es una característica única de la orientación a objetos. 21.4 Resumen 239
capitulo 22 Sobrecarga del operador 22.1 Introducción Exploraremos la sobrecarga de operadores en este capítulo; qué es, cómo funciona y por qué lo queremos. 22.2 Sobrecarga del operador 22.2.1 ¿Por qué hay sobrecarga de operadores? La sobrecarga de operadores permite que las clases definidas por el usuario parezcan tener una forma natural de usando operadores como +, −, <, > o == así como operadores lógicos como & (y) y | (o). Esto conduce a un código más sucinto y legible, ya que es posible escribir código como: Se siente más natural tanto para los desarrolladores como para quienes leen el código. El alternativa sería crear métodos como agregar y escribir código como Lo que semánticamente puede significar lo mismo pero se siente menos natural para la mayoría gente. q1 = Cantidad(5) q2 = Cantidad(10) q3 = q1 + q2 q1 = Cantidad(5) q2 = Cantidad(10) q3 = q1.sumar(q2) © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_22 241
22.2.2 ¿Por qué no tener sobrecarga de operadores? Si la sobrecarga de operadores es una buena idea, ¿por qué no todos los lenguajes de programación apoyarlo? Curiosamente, Java, un lenguaje de programación muy utilizado, no soporte de sobrecarga del operador! ¡Una respuesta es porque se puede abusar de ella! Por ejemplo, ¿cuál es el significado de la siguiente código: No está claro qué significa ‘+’ en este contexto; de qué manera se suma Denise a Juan; ¿Significa que se van a casar? Si es así, ¿cuál es el resultado que se tiene en p3? El problema aquí es que desde una perspectiva de diseño (que en este caso puede ser puramente intuitivo, pero en otros casos puede estar relacionado con la intención de una aplicación) el El operador más no tiene sentido para el tipo Persona. Sin embargo, no hay nada en el lenguaje Python para indicar esto y, por lo tanto, cualquiera puede codificar cualquier operador en ¡cualquier clase! Como principio general de diseño; los desarrolladores deben seguir la semántica de tipos y, por lo tanto, solo debe implementar aquellos operadores que sean apropiados para el tipo que se está desarrollando. Por ejemplo, para tipos de valores aritméticos como Cantidad tiene mucho sentido proporcionar un operador más, pero para dominios específicos tipos orientados a datos como Person no lo hace. 22.2.3 Implementación de sobrecarga de operadores Para implementar operadores como ‘+’ en una clase definida por el usuario es necesario implementar métodos específicos que luego se asignan a la aritmética o lógica operadores utilizados por los usuarios de la clase. Estos métodos se consideran especiales porque comienzan y terminan con un guion bajo doble (’__’). Estos métodos se consideran privados y, por lo general, restringido para implementaciones orientadas a Python (ya las hemos visto con métodos como init() y str()). Como ejemplo, supongamos que queremos implementar los operadores ‘+’ y ‘−’ para nuestro tipo Cantidad. También queremos que nuestro tipo de cantidad tenga un valor real y ser capaz de convertirse en una cadena con fines de impresión. Para implementar los operadores ‘+’ y ‘−’ necesitamos proporcionar dos métodos especiales uno proporcionará la implementación del operador ‘+’ y otro proporcionará el implementación del operador ‘−’: p1 = Persona(‘Juan’) p2 = Persona(‘Denise’) p3 = p1 + p2 242 22 Sobrecarga del operador
• El operador ‘+’ se implementa mediante un método con la firma def add (yo, otro): • El operador ‘−’ se implementa mediante un método con la firma def sub (yo, otro): Donde otro representa otra Cantidad u otro tipo adecuado que agregarse o restarse del objeto Cantidad actual. Python asignará los métodos a los operadores ‘+’ y ‘−’; tal que si alguien intenta sumar cantidades juntas, entonces el método add() lo hará ser llamado etc La definición de la clase Cantidad se da a continuación; tenga en cuenta que la clase en realidad solo envuelve un número contenido en el valor del atributo. Usando esta definición de clase, podemos crear dos instancias del tipo Cantidad y sumarlos: Si ejecutamos este fragmento de código obtenemos: cantidad de clase: def init(auto, valor=0): self.value = valor def add(uno mismo, otro): nuevo_valor = propio.valor + otro.valor cantidad devuelta (nuevo_valor) def sub(uno mismo, otro): nuevo_valor = propio.valor - otro.valor cantidad devuelta (nuevo_valor) def str(uno mismo): devuelve ‘Cantidad[’ + str(self.value) + ‘]’ q1 = Cantidad(5) q2 = Cantidad(10) imprimir(‘q1 =’, q1, ‘, q2 =’, q2) q3 = q1 + q2 imprimir(‘q3 =’, q3) q1 = Cantidad[5] , q2 = Cantidad[10] q3 = Cantidad[15] 22.2 Sobrecarga del operador 243
Tenga en cuenta que hemos hecho que la clase Cantidad sea inmutable; eso es una vez Se ha creado una instancia de cantidad, su valor no se puede cambiar (es fijo). Esto significa que cuando se suman dos cantidades más dura una nueva instancia de la Se crea la clase Cantidad. Esto es análogo a cómo funcionan los números enteros, si agrega juntos 2 + 3 entonces obtienes 5; sin embargo, ni 2 ni 3 se modifican; en cambio un nuevo se genera el entero 5: este es un ejemplo del principio de diseño general; desarrollar los operadores deben seguir la semántica de los tipos integrados; Los objetos de cantidad actúan como numerar objetos. 22.3 Operadores numéricos Hay nueve operadores numéricos diferentes que pueden implementarse mediante operaciones especiales. métodos; estos operadores se enumeran en la siguiente tabla: Ya hemos visto ejemplos de sumar y restar; esta tabla indica cómo también puede proporcionar operadores para multiplicación y división, etc. La tabla anterior también presenta operadores de desplazamiento bit a bit (tanto a la izquierda como a la derecha). Estos operar en el nivel de bit utilizado para representar números bajo el capó y puede ser muy manera eficiente de manipular valores numéricos; sin embargo, no queremos apoyar estos operadores para nuestra clase de cantidad, por lo tanto, solo implementaremos el núcleo operadores numéricos de multiplicación, división y potencia. También tenga en cuenta que los nombres de los métodos de división no son div sino truediv() y floordiv() indicando la diferencia de comportamiento entre ‘/’ y ‘//’. La clase de cantidad actualizada se proporciona a continuación: Operador Expresión Método Suma q1 + q2 añadir(uno mismo, q2) Sustracción q1 – q2 sub(uno mismo, q2) Multiplicación q1 * q2 mul(uno mismo, q2) Fuerza q1 ** q2 pow(uno mismo, q2) División q1 / q2 truediv(uno mismo, q2) División de piso q1 // q2 floordiv(uno mismo, q2) Módulo (resto) q1 % q2 mod(uno mismo, q2) Desplazamiento a la izquierda bit a bit q1 � q2 lshift(uno mismo, q2) Desplazamiento a la derecha bit a bit q1 � q2 rshift(uno mismo, q2) 244 22 Sobrecarga del operador
Esto significa que ahora podemos ampliar nuestra sencilla aplicación que utiliza el Clase de cantidad para incluir algunos de estos operadores numéricos adicionales: cantidad de clase: def init(auto, valor=0): self.value = valor def add(uno mismo, otro): nuevo_valor = propio.valor + otro.valor cantidad devuelta (nuevo_valor) def sub(uno mismo, otro): nuevo_valor = propio.valor - otro.valor cantidad devuelta (nuevo_valor) def mul(uno mismo, otro): nuevo_valor = self.value * otro.valor cantidad devuelta (nuevo_valor) def pow(uno mismo, otro): nuevo_valor = self.value ** otro.valor cantidad devuelta (nuevo_valor) def truediv(uno mismo, otro): new_value = self.value / other.value cantidad devuelta (nuevo_valor) def floordiv(uno mismo, otro): nuevo_valor = self.value // otro.valor cantidad devuelta (nuevo_valor) def mod(uno mismo, otro): new_value = self.value % other.value cantidad devuelta (nuevo_valor) def str(uno mismo): devuelve ‘Cantidad[’ + str(self.value) + ‘]’ 22.3 Operadores numéricos 245
La salida de esto es ahora: Un punto interesante a tener en cuenta es que los métodos de estilo múltiple y dividido, podría desear multiplicar una Cantidad por un número entero o dividir una Cantidad por un entero. No hay nada que nos impida hacer esto y, de hecho, esto podría ser muy útil. comportamiento. Esto permitiría multiplicar una Cantidad por 2 o dividirla por 2, Por ejemplo: De momento si intentáramos ejecutar el código anterior generaríamos un error diciéndonos que un int no tiene un atributo de valor. Sin embargo, podemos probar para ver si el argumento pasado a los métodos mult() y truediv() es un int o no usar la función isinstance. Esta función toma una variable y la nombre de una clase y devuelve True si el contenido de la variable es una instancia de la clase nombrada, por ejemplo: q1 = Cantidad(5) q2 = Cantidad(10) imprimir(‘q1 =’, q1, ‘, q2 =’, q2) q3 = q1 + q2 imprimir(‘q3 =’, q3) imprimir(‘q2 - q1 =’, q2 - q1) imprimir(‘q1 * q2 =’, q1 * q2) imprimir(‘q1 / q2 =’, q1 / q2) q1 = Cantidad[5] ,q2 = Cantidad[10] q3 = Cantidad[15] q2 - q1 = Cantidad[5] q1 * q2 = Cantidad[50] q1 / q2 = Cantidad[0.5] imprimir(‘q1 * 2’, q1 * 2) imprimir(‘q2 / 2’, q2 / 2) 246 22 Sobrecarga del operador
Ahora, cuando ejecutamos las declaraciones de impresión anteriores, generamos la salida: 22.4 Operadores de comparación Los tipos numéricos (como números enteros y números reales) también admiten la comparación operadores como igual, no igual, mayor que, menor que así como mayor que o igual a y menor que o igual a. Python permite que estos operadores de comparación se definan para tipos definidos por el usuario/ clases también. Así como los operadores numéricos como ‘+’ y ‘−’ son implementados por especiales los métodos también lo son los operadores de comparación. Por ejemplo, se implementa el operador ‘<’ por un método llamado lt(self, other). La lista completa de operadores de comparación y los métodos especiales asociados es dado en la siguiente tabla: q1 * 2 Cantidad[10] q2 / 2 Cantidad[5.0] cantidad de clase:
Código omitido por brevedad
def mul(uno mismo, otro): si es instancia (otro, int): nuevo_valor = self.value * otro demás: nuevo_valor = self.value * otro.valor cantidad devuelta (nuevo_valor) def truediv(uno mismo, otro): si es instancia (otro, int): nuevo_valor = self.value / otro demás: new_value = self.value / other.value cantidad devuelta (nuevo_valor) Operador Expresión Método Menos que q1 < q2 lt(q1, q2) Menos que o igual a q1 <= q2 le(q1, q2) Igual a q1 == q2 eq(q1, q2) No igual a q1 != q2 ne(q1, q2) Mas grande que q1 > q2 gt(q1, q2) Mayor qué o igual a q1 >= q2 ge(q1, q2) 22.3 Operadores numéricos 247
Podemos agregar estas definiciones a nuestra clase Cantidad para proporcionar una tipo completo que se puede usar en pruebas de estilo de comparación (como declaraciones if). La clase de cantidad actualizada se da a continuación (con algunos de los valores numéricos operadores omitidos por brevedad): Esto ahora significa que podemos actualizar nuestra aplicación de muestra para aprovechar estos operadores de comparación: cantidad de clase: def init(auto, valor=0): self.value = valor def add(uno mismo, otro): nuevo_valor = propio.valor + otro.valor cantidad devuelta (nuevo_valor)
operadores numéricos restantes omitidos por brevedad…
def eq(uno mismo, otro): return self.value == otro.valor def ne(uno mismo, otro): return self.value != other.value def ge(uno mismo, otro): return self.value >= other.value def gt(uno mismo, otro): return auto.valor > otro.valor def lt(uno mismo, otro): return auto.valor < otro.valor def le(uno mismo, otro): return self.value <= otro.valor def str(uno mismo): devuelve ‘Cantidad[’ + str(self.value) + ‘]’ q1 = Cantidad(5) q2 = Cantidad(10) imprimir(‘q1 =’, q1, ‘,q2 =’, q2) q3 = q1 + q2 imprimir(‘q3 =’, q3) imprimir(‘q1 <q2: ‘, q1 <q2) imprimir(‘q3 > q2: ‘, q3 > q2) imprimir(‘q3 == q1: ‘, q3 == q1) 248 22 Sobrecarga del operador
La salida de esto es ahora: 22.5 Operadores logicos La última categoría de operadores que se pueden definir en una clase son los operadores lógicos. Estos son operadores que se pueden usar con y/o pruebas de tipo; normalmente devuelven el valores Verdadero o Falso. Al igual que con los operadores numéricos y los operadores de comparación; el lógico Los operadores se implementan mediante un conjunto de métodos especiales. La siguiente tabla resume los operadores lógicos y los métodos utilizados para implementarlos: Como estos operadores realmente no tienen sentido para el tipo Cantidad, no definirlos. Por ejemplo, ¿qué significaría decir: ¿De qué manera q1 es una alternativa a q2? 22.6 Resumen Solo use operadores cuando tengan sentido y solo implemente aquellos operadores que trabajar con el tipo que está definiendo. En general, esto significa • Los operadores aritméticos solo deben usarse para tipos de valores con un valor numérico. propiedad. q1 = Cantidad[5] ,q2 = Cantidad[10] q3 = Cantidad[15] q1 < q2: Verdadero q3 > q2: Verdadero q3 == q1: Falso Operador Expresión Método Y q1 y q2 y(q1, q2) O q1 | q2 o(q1, q2) XOR q1 ^ q2 xor(q1, q2) NO *q1 invertir() q1 | q2 22.4 Operadores de comparación 249
• Los operadores de comparación generalmente solo tienen sentido para las clases que se pueden ordenar. • Los operadores lógicos normalmente funcionan para tipos que son de naturaleza similar a los booleanos. 22.7 Recursos en línea Algunos recursos en línea sobre la sobrecarga de operadores incluyen: • https://docs.python.org/3/reference/datamodel.html para obtener información sobre el operador sobrecarga en Python. • https://pythonprogramming.net/operator-overloading-intermediate-python- tutorial/Tutorial sobre sobrecarga de operadores. • http://cafe.elharo.com/programming/operator-overloading-considered-dañino/ Un artículo sobre por qué la sobrecarga de asistentes a la ópera puede ser perjudicial para una buena programación estilo. 22.8 Ejercicios El objetivo de este ejercicio es crear una nueva clase de estilo numérico. Debe crear una nueva clase definida por el usuario llamada Distancia. Será muy similar a la cantidad. Debería poder sumar dos distancias, restar una distancia de otro, dividir una distancia por un número entero, multiplicar una distancia por un número entero, etc. Por lo tanto, debería poder admitir el siguiente programa: d1 = Distancia(6) d2 = Distancia(3) imprimir (d1 + d2) imprimir (d1 - d2) imprimir (d1 / 2) imprimir(d2 // 2) imprimir (d2 * 2) 250 22 Sobrecarga del operador
Tenga en cuenta que los operadores de división y multiplicación trabajan con una distancia y una entero; por lo tanto, tendrá que pensar en cómo implementar el especial métodos para estos operadores. La salida de esto podría ser: Distancia[9] Distancia[3] Distancia[3.0] Distancia[1] Distancia[6] 22.8 Ejercicios 251
capitulo 23 Propiedades de Python 23.1 Introducción Muchos lenguajes orientados a objetos tienen el concepto explícito de encapsulación; eso es la capacidad de ocultar datos dentro de un objeto y solo para proporcionar puertas de enlace específicas en esos datos Estas puertas de enlace son métodos definidos para obtener o establecer el valor de un atributo (a menudo denominados getters y setters). Esto permite un mayor control sobre el acceso a la datos; por ejemplo, es posible comprobar que sólo un número entero positivo por encima de cero, pero por debajo de 120, se utiliza para la edad de una persona, etc. En muchos lenguajes, como Java y C#, los atributos se pueden ocultar del exterior. acceder usando palabras clave específicas (como privado) que indican que los datos deben ser privado del objeto. Python no tiene explícitamente el concepto de encapsulación; en cambio, se basa en dos cosas; una convención estándar utilizada para indicar que un atributo debe ser considerado privado y un concepto llamado propiedad que permite a setters y getters a definir para un atributo. 23.2 Atributos de Python Todos los atributos de los objetos están disponibles públicamente en Python; es decir, todos son visibles para cualquier código usando el objeto. Por ejemplo, dada la siguiente definición de la clase Persona tanto nombre como edad son parte de la interfaz pública de la clase Persona; © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_23 253
Debido a que el nombre y la edad son parte de la interfaz pública de la clase, significa que puede escribir: Lo cual, por supuesto, es un poco extraño ya que la persona ahora tiene el nombre ‘42’ y una edad de −1, por lo que la salida de esto es: Podemos indicar que queremos tratar la edad y el nombre como privados del objeto. prefijando los nombres de los atributos con un guión bajo (’_’) como se muestra a continuación: Esto les dice a los programadores de Python que queremos considerar _name y _age como siendo privado. Sin embargo, debe tenerse en cuenta que esto es solo una convención; aunque sea un fuertemente adherido a la convención. Todavía no hay nada aquí que detenga alguien escribiendo: Persona de clase: def init(yo, nombre, edad): self.nombre = nombre self.edad = edad def str(uno mismo): return ‘Persona[’ + str(self.name) + ‘] is ’ + str(auto.edad) persona = Persona(‘Juan’, 54) persona.nombre = 42 persona.edad = -1 imprimir (persona) Persona[42] es -1 Persona de clase: def init(yo, nombre, edad): self._name = nombre self._edad = edad def str(uno mismo): return ‘Persona[’ + str(self._name) +’] is ’ + s str(self._edad) persona = Persona(‘Juan’, 54) persona._edad = -1 imprimir (persona) 254 23 Propiedades de Python
Sin embargo, el desarrollador de la clase Person tiene la libertad de cambiar las partes internas de la clase (como edad) sin previo aviso y la mayoría consideraría que cualquiera que haya Ignoraron la convención y ahora tenían un problema que solo tenían ellos mismos para culpar. 23.3 Métodos de estilo Setter y Getter Esto, por supuesto, plantea la pregunta; ¿Cómo debemos obtener ahora el nombre de una persona? y la edad de una manera aceptable? La respuesta es que un desarrollador debe proporcionar métodos getter y métodos setter que se puede utilizar para acceder a los valores. Podemos actualizar la clase Person con algunos métodos getter y un único setter método: Los dos métodos getter tienen el formato get seguido del nombre del atributo que están recibiendo. Por lo tanto, tenemos get_age y get_name. Por lo general, todos lo que hacen los captadores es devolver el atributo que se está utilizando (como es el caso aquí). El método de colocador único es un poco diferente; valida los datos que han sido proporcionado para comprobar que es apropiado (es decir, que es un número entero que utiliza isinstance (new_age,int) y que es un valor superior a cero pero inferior a 120). Solo si los datos pasa estas comprobaciones se utiliza como el nuevo valor de la edad de la persona, por ejemplo si tratamos de establecer la edad de una persona en −1: Persona de clase: def init(yo, nombre, edad): self._name = nombre self._edad = edad def get_age(self): volver self._age def set_age(self, new_age): si es instancia(nueva_edad,int) & nueva_edad > 0 & nueva_era < 120: self._edad = nueva_edad def get_name(self): volver self._name def str(uno mismo): return ‘Persona[’ + str(self._name) +’] is ’ + str(self._edad) 23.2 Atributos de Python 255
Luego esto se ignora y la edad de la persona permanece como estaba, por lo tanto, la salida de esto es: Cabe señalar que esto podría considerarse una falla silenciosa; eso es lo que tratamos de establecer la edad y falló, pero nadie lo sabe. En muchos casos, en lugar de fallar en silencio, preferiría notificar a alguien sobre el error lanzando algún tipo de objeto Error; esto se discutirá en el próximo capítulo sobre manejo de errores y excepciones. Bien podría preguntarse en este punto, ¿dónde está el setter para el atributo _name? El La respuesta es que queremos hacer que el atributo _name sea un atributo de solo lectura y por lo tanto, no hemos proporcionado un método de estilo de establecimiento. Este es un modismo común seguido en Python: puede tener atributos de lectura y escritura y atributos de solo lectura dependiendo de si tienen métodos getter y setter o no. También es posible a un atributo de solo escritura, pero esto es muy raro y solo tiene algunos casos de uso. 23.4 Interfaz pública a las propiedades Aunque ahora tenemos una interfaz más formal para los atributos que tiene una instancia de la clase Persona; es bastante desgarbado: Terminamos teniendo que escribir más código y aunque hay un argumento de que hace que el código sea más obvio (es decir, person.get_age() se puede leer como get the edad del objeto persona); es algo detallado y hay que recordar incluir los paréntesis (()’). Para evitar esto, se introdujo un concepto conocido como Propiedades en Python 2.2. En la sintaxis original para esto, era posible agregar una línea adicional de código al clase que le dijo a Python que quería proporcionar una nueva propiedad y que se utilizarían métodos para establecer y obtener los valores de esta propiedad. persona = Persona(‘Juan’, 54) persona.set_age(-1) imprimir (persona) La persona [Juan] tiene 54 años. persona = Persona(‘Juan’, 54) imprimir (persona) imprimir(persona.obtener_edad()) imprimir(persona.obtener_nombre()) 256 23 Propiedades de Python
La sintaxis para definir una propiedad de esta manera es: Donde fget indica la función getter, fset la función setter fdel the función que se utilizará para eliminar un valor y doc proporciona documentación sobre la propiedad (Todos ellos son opcionales). Podemos modificar nuestra clase Persona para que la edad sea ahora una propiedad (observe una la convención común es que si el atributo se llama _edad, los métodos se nombran get_age y set_age y la propiedad se llamará age): Ahora podemos escribir: <nombre_propiedad> = propiedad(fget=Ninguno, fset=Ninguno, fdel=Ninguno, doc=Ninguno) Persona de clase: def init(yo, nombre, edad): self._name = nombre self._edad = edad def get_age(self): volver self._age def set_age(self, new_age): si es instancia(nueva_edad,int) & nueva_edad > 0 & nueva_era < 120: self._edad = nueva_edad edad = propiedad(get_age, set_age, doc=“Una propiedad de edad”) def get_name(self): volver self._name nombre = propiedad(obtener_nombre, doc=“Una propiedad de nombre”) def str(uno mismo): return ‘Persona[’ + str(self._name) +’] is ’ + str(self._edad) persona = Persona(‘Juan’, 54) imprimir (persona) imprimir(persona.edad) imprimir(persona.nombre) persona.edad = 21 imprimir (persona) 23.4 Interfaz pública a las propiedades 257
Observe cómo ahora podemos escribir persona.edad y persona.edad = 21; en ambos
en estos casos estamos accediendo a la propiedad edad que da como resultado el método get_age()
y set_age() siendo ejecutados respectivamente. Por lo tanto, el colocador sigue protegiendo al
actualice el atributo _age subyacente que realmente se usa para contener el valor real.
También tenga en cuenta que si no se proporciona un método para uno de los métodos fget, fset, fdel
métodos entonces esto no es un error; simplemente indica que la propiedad no
admite ese tipo de accesorio. Por lo tanto, la propiedad de nombre es una propiedad de solo lectura, ya que
no define un método setter.
Se puede usar un método de eliminación para liberar la memoria asociada con un atributo;
en el caso de un int no es necesario pero puede ser necesario para un más complejo,
tipo definido por el usuario.
Por lo tanto, podríamos escribir:
Tenga en cuenta que estamos usando una referencia de palabra clave para el método de eliminación, ya que tenemos
omitió el colocador y, por lo tanto, no puede confiar en argumentos posicionales.
23.5
Definiciones de propiedades más concisas
El ejemplo que se muestra en la sección anterior funciona, pero sigue siendo bastante detallado;
Si bien esto está del lado de los escritores de clase, todavía parece algo pesado.
Para superar esto, una opción más concisa está disponible desde Python 2.4.
Este enfoque utiliza lo que se conoce como decoradores. Los decoradores representan metadatos
(esa es información sobre su código que el intérprete de Python puede usar para resolver
lo que quieres que haga con ciertas cosas).
Python 2.4 introdujo tres nuevos decoradores @property, @
Note tres cosas importantes acerca de este ejemplo: • El nombre de los métodos ya no es set_age y get_age; en cambio ambos Los métodos ahora son solo edad y el decorador distingue su papel. También tenga en cuenta que ya no tenemos una declaración separada que declara la propiedad, ahora es implícito en el uso del decorador @property y el nombre del asociado método. • El decorador @property se usa para definir el nombre de la propiedad (en este edad del caso) y definir otros decoradores que llevarán el nombre de la propiedad con un elemento .setter o .deleter, p. @age.setter. • La cadena de documentación ahora está definida en el método asociado con el decorador @property (proporcionar esta cadena de documentación generalmente se buenas prácticas consideradas). Sin embargo, no necesitamos cambiar el programa que usó la clase Persona, ya que la interfaz de la clase siguió siendo la misma. Persona de clase: def init(yo, nombre, edad): self._name = nombre self._edad = edad @propiedad def edad(auto): """ La cadena de documentación para la propiedad de edad """ print(‘En método de edad’) volver self._age @age.setter def edad(auto, valor): print(‘En el método set_age’) si es instancia (valor, int) & valor > 0 & valor < 120: self._edad = valor @propiedad def nombre(auto): imprimir(‘En nombre’) volver self._name @nombre.eliminar def nombre(auto): del self._nombre def str(uno mismo): return ‘Persona[’ + str(self._name) +’] is ’ + str(self._edad) 23.5 Definiciones de propiedades más concisas 259
23.6 Recursos en línea Algunos recursos en línea sobre propiedades son: • https://www.python-course.eu/python3_properties.php una discusión sobre Python propiedades versus getters y setters. • https://www.journaldev.com/14893/python-property-decorador un corto intro- ducción al decorador de @property. 23.7 Ejercicios En este ejercicio agregará propiedades a una clase existente. Regrese a la clase Cuenta que creó hace varios capítulos; convertir el balance en una propiedad de solo lectura usando decoradores, luego verifique que lo siguiente el programa funciona correctamente: acc2 = DepositAccount(‘345’, ‘John’, 23.55, 0.5) acc3 = acc3 = CuentaInversión(‘567’, ‘Phoebe’, 12.45, ‘alto riesgo’) imprimir (acc1) imprimir (acc2) imprimir (acc3) acc1.deposito(23.45) acc1.retirar (12.33) print(‘saldo:’, cuenta1.saldo) print(‘Número de instancias de cuenta creadas:’, Cuenta.instancia_recuento) print(‘saldo:’, cuenta1.saldo) acc1.retiro(300.00) print(‘saldo:’, cuenta1.saldo) acc1 = CuentaActual(‘123’, ‘Juan’, 10.05, 100.0) 260 23 Propiedades de Python
La salida de esto podría ser: Creando nueva cuenta Creando nueva cuenta Creando nueva cuenta Cuenta[123] - Juan, cuenta corriente = 10,05 sobregiro límite: -100.0 Cuenta[345] - Juan, cuenta de ahorros = 23,55 de interés tasa: 0.5 Cuenta[567] - Phoebe, cuenta de inversión = 12,45 saldo: 21.17 Número de instancias de cuenta creadas: 3 saldo: 21.17 El retiro excedería su límite de sobregiro saldo: 21.17 23.7 Ejercicios 261
capitulo 24 Manejo de errores y excepciones 24.1 Introducción Este capítulo considera el manejo de excepciones y errores y cómo se implementa en Pitón. Se le presenta el modelo de objetos del manejo de excepciones, el lanzamiento y captura de excepciones, y cómo definir nuevas excepciones y construcciones específicas de excepción. 24.2 Errores y excepciones Cuando algo sale mal en un programa de computadora, alguien debe saberlo. él. Una forma de informar a otras partes de un programa (y potencialmente a aquellos que ejecutan un program) es generando un objeto de error y propagándolo a través del código hasta que algo maneje el error y lo solucione o el punto en el que el se introduce el programa se encuentra. Si el error se propaga fuera del programa, el usuario que ejecutó el programa necesita saber que algo ha ido mal. Se les notifica de un problema a través de un breve informe sobre el error que ocurrió y un seguimiento de la pila de dónde se puede encontrar ese error encontró. Es posible que ya los haya visto usted mismo al escribir sus propios programas. Para ejemplo, el siguiente volcado de pantalla ilustra un error de programación donde alguien ha intentado concatenar una cadena y un número usando el ‘+’ © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_24 263
operador. Este error se ha propagado fuera del programa y un seguimiento de pila del código que fue llamado se presenta (en este caso, la función de impresión utilizó el str() método de la clase Persona). Tenga en cuenta que los números de línea están incluidos, lo que ayuda con depurando el problema. En Python, los términos Error y Excepción se usan indistintamente; a pesar de desde un punto de vista de estilo Las excepciones pueden usarse para representar problemas con operaciones como excepciones aritméticas y errores pueden estar asociadas con problemas funcionales, como que no se encuentre un archivo. 24.3 ¿Qué es una excepción? En Python, todo es un tipo de objeto, incluidos números enteros, cadenas, valores booleanos y de hecho Excepciones y Errores. En Python, los tipos de Excepción/Error se definen en un jerarquía de clases con la raíz de esta jerarquía siendo el tipo BaseException. Todos los errores y excepciones incorporados eventualmente se extienden desde BaseException tipo. Tiene una excepción de subclase que es la raíz de todas las excepciones definidas por el usuario. (así como muchas excepciones integradas). A su vez, ArithmeticException es la clase base para todas las excepciones integradas asociadas con errores aritméticos. 264 24 Manejo de errores y excepciones
El diagrama anterior ilustra la jerarquía de clases para algunos de los tipos comunes de errores y excepciones. Cuando ocurre una excepción, esto se conoce como generar una excepción y cuando es pasado al código para manejar esto se conoce como lanzar una excepción. Estos son términos eso se hará más obvio a medida que avance este capítulo. 24.4 ¿Qué es el manejo de excepciones? Una excepción mueve el flujo de control de un lugar a otro. En la mayoría de las situaciones ciones, esto se debe a que ocurre un problema que no se puede manejar localmente pero que puede ser manejado en otra parte del sistema. El problema suele ser algún tipo de error (como dividir por cero), aunque puede ser cualquier problema (por ejemplo, identificar que el código postal especificado con un dirección no coincide). El propósito de una excepción, por lo tanto, es manejar un condición de error cuando ocurre en tiempo de ejecución. Vale la pena considerar por qué debería desear manejar una excepción; después de todo el El sistema no permite que un error pase desapercibido. Por ejemplo, si tratamos de dividir por cero, entonces el sistema genera un error para usted. Esto puede significar que el usuario tiene 24.3 ¿Qué es una excepción? 265
ingresó un valor incorrecto y no queremos que los usuarios vean un cuadro de diálogo sugiriendo que ingresen al depurador del sistema. Por lo tanto, podemos usar excepciones para Obligar al usuario a corregir el error y volver a ejecutar el cálculo. La siguiente tabla ilustra la terminología que normalmente se usa con excepciones/errores manejo en Python. Diferentes tipos de error producen diferentes tipos de excepción. Por ejemplo, si el el error se produce al dividir un número entero por cero, entonces la excepción es una aritmética excepción. El tipo de excepción es identificado por objetos y puede ser capturado y procesados por manejadores de excepciones. Cada controlador puede tratar las excepciones asociadas con su clase de error o excepción (y sus subclases). Se crea una instancia de excepción cuando se genera. El sistema busca una copia de seguridad de la pila de ejecución (el conjunto de funciones o métodos que se han invocado a la inversa) order) hasta que encuentre un manejador que pueda manejar la excepción. El asociado Luego, el controlador procesa la excepción. Esto puede implicar la realización de algunos remedios acción o terminar la ejecución actual de manera controlada. En algunos casos, puede ser posible reiniciar la ejecución del código. Como un controlador solo puede tratar con una excepción de una clase (o subclase) específica, un excepción puede pasar a través de una serie de bloques de controlador antes de encontrar uno que pueda procesalo. Excepción Un error que se genera en tiempo de ejecución. Levantando una excepción Generando una nueva excepción lanzando un excepción Activación de una excepción generada Manejo de una excepción Código de procesamiento que se ocupa del error. Manipulador El código que se ocupa del error (denominado bloque catch) Señal Un tipo particular de excepción (como fuera de los límites o dividir por cero) 266 24 Manejo de errores y excepciones
La figura anterior ilustra una situación en la que una excepción de división por cero llamada Se genera ZeroDivisionError. Esta excepción se pasa a la pila de ejecución. donde encuentra un controlador de excepciones definido para una excepción de fin de archivo. Este el controlador no puede manejar el ZeroDivisionError y, por lo tanto, se pasa más arriba la pila de ejecución. Luego encuentra un controlador para una excepción de falta de memoria. Nuevamente, no puede lidiar con un ZeroDivisionError y se pasa la excepción más arriba en la pila de ejecución hasta que encuentre un controlador definido para el Error de división cero. Este controlador luego procesa la excepción. 24.5 Manejo de una excepción Puede detectar una excepción implementando la construcción try—except. Este La construcción se divide en tres partes: • bloque de prueba. El bloque try indica el código que será monitoreado para el excepciones enumeradas en las expresiones excepto. • excepto la cláusula. Puede usar una cláusula de excepción opcional para indicar qué hacer cuando ocurren ciertas clases de excepción/error (por ejemplo, resolver el problema o generar un mensaje de advertencia). Puede haber cualquier número de cláusulas excepto en Comprobación de secuencias para diferentes tipos de errores/excepciones. • cláusula else. Esta es una cláusula opcional que se ejecutará si y solo si no se lanzó una excepción en el bloque try. Es útil para el código que debe ser se ejecuta si la cláusula try no genera una excepción. • cláusula final. La cláusula finalmente opcional se ejecuta después de que el bloque try sale. (ya sea que esto se deba o no a que se haya generado una excepción). Puedes usarlo para limpiar cualquier recurso, cerrar archivos, etc. Esta construcción del lenguaje puede parecer confusa al principio, sin embargo, una vez que haya trabajado con él por un tiempo le resultará menos desalentador. Como ejemplo, considere la siguiente función que divide un número por cero; esto generará ZeroDivisionError cuando se ejecute para cualquier número: Si ahora llamamos a esta función, obtendremos el trackback del error en el estándar producción: def runcalc(x): X / 0 cálculo de ejecución(6) 24.4 ¿Qué es el manejo de excepciones? 267
Esto se muestra a continuación: Sin embargo, podemos manejar esto ajustando la llamada a runcalc dentro de un intento declaración y proporcionando una cláusula de excepción. La sintaxis para una declaración de prueba con una cláusula de excepción es: Un ejemplo concreto de esto se da a continuación para una declaración de prueba que se utilizará para monitorear una llamada a runcalc: lo que ahora da como resultado que se imprima la cadena ‘oops’. Esto se debe a que cuando runcalc se llama el operador ‘/’ lanza el ZeroDivisionError que es devuelto al código de llamada que tiene una cláusula de excepción que especifica este tipo de excepción. Esto detecta la excepción y ejecuta el bloque de código asociado que en este caso imprime la cadena ‘oops’. De hecho, no tenemos que ser tan precisos como esto; a la cláusula de excepción se le puede dar un clase de excepción a buscar y coincidirá con cualquier excepción que sea de ese tipo o es una instancia de una subclase de la excepción. Por lo tanto también podemos escribir: La clase Exception es un abuelo de ZeroDivisionError, por lo tanto cualquier objeto ZeroDivisionError también es un tipo de excepción y, por lo tanto, la excepción el bloque coincide con la excepción pasada. Esto significa que puede escribir uno excepto cláusula y esa cláusula puede manejar una amplia gama de excepciones. intentar: <código para monitorear> excepto <tipo de excepción para monitorear>: <código para llamar si se encuentra una excepción> intentar: cálculo de ejecución(6) excepto ZeroDivisionError: imprimir (‘ups’) intentar: cálculo de ejecución(6) excepto Excepción: imprimir (‘ups’) 268 24 Manejo de errores y excepciones
Sin embargo, si no desea tener un bloque de código común que maneje su excepciones, puede definir diferentes comportamientos para diferentes tipos de excepción. Esto se hace teniendo una serie de cláusulas de excepción; cada uno monitoreando un tipo diferente de excepción: En este caso, el primero excepto los monitores para un ZeroDivisionError pero el otras excepciones supervisan otros tipos de excepciones. Tenga en cuenta que la excepción La excepción es la última cláusula excepto en la lista como ZeroDivisionError, IndexError y FileNotFoundError son todas subclases eventuales de Excepción y, por lo tanto, esta cláusula captaría cualquiera de estos tipos de excepción. Como solo se permite ejecutar una cláusula excepto; si este excepto el controlador vino puño el otros, excepto los handers, nunca se ejecutarían. 24.5.1 Acceso al objeto de excepción Es posible obtener acceso al objeto de excepción capturado por el objeto de excepción. cláusula usando la palabra clave as. Esto sigue al tipo de excepción que se está monitoreando y se puede usar para vincular el objeto de excepción a una variable, por ejemplo: Que produce: Si hay varias cláusulas de excepción, cada cláusula de excepción puede decidir si para vincular el objeto de excepción a una variable o no (y cada variable puede tener un nombre diferente): intentar: cálculo de ejecución(6) excepto ZeroDivisionError como exp: imprimir (exp) imprimir (‘ups’) intentar: cálculo de ejecución(6) excepto ZeroDivisionError: imprimir (‘ups’) excepto IndexError: imprimir (‘arrgh’) excepto FileNotFoundError: imprimir (’¡eh!’) excepto Excepción: imprimir(’¡Eh!’) división por cero ups 24.5 Manejo de una excepción 269
En el ejemplo anterior, tres de las cuatro cláusulas excepto vinculan la excepción a un variable (cada una con un nombre diferente, aunque todas podrían tener el mismo nombre) pero uno, la cláusula FileNotFoundError excepto no vincula la excepción a un variable. 24.5.2 Saltar a los controladores de excepciones Una de las características interesantes del manejo de excepciones en Python es que cuando un Se genera un error o una excepción, se lanza inmediatamente a los controladores de excepciones (las cláusulas excepto). Cualquier declaración que sigue al punto en el que la excepción se plantea no se ejecutan. Esto significa que una función puede terminar antes y más Es posible que no se ejecuten las declaraciones en el código de llamada. Como ejemplo, considere el siguiente código. Este código define una función my_function() que imprime una cadena, realiza una operación de división que provocar que se genere un ZeroDivisionError si el valor de y es cero y luego tiene un declaración de impresión adicional. Esta función se llama desde dentro de una declaración de prueba. Observe que hay una instrucción de impresión a cada lado de la llamada a my_function(). También hay un controlador para ZeroDivisionError. intentar: cálculo de ejecución(6) excepto ZeroDivisionError como exp: imprimir (exp) imprimir (‘ups’) excepto IndexError como e: imprimir (e) imprimir (‘arrgh’) excepto FileNotFoundError: imprimir (’¡eh!’) excepto Excepción como excepción: imprimir (excepción) imprimir(’¡Eh!’) def mi_funcion(x, y): print(‘mi_funcion en’) resultado = x / y imprimir(‘mi_funcion fuera’) resultado devuelto imprimir(‘Iniciando’) intentar: print(‘Antes de mi_funcion’) mi_funcion(6, 2) print(‘Después de mi_función’) excepto ZeroDivisionError como exp: imprimir (‘ups’) imprimir(‘Terminado’) 270 24 Manejo de errores y excepciones
Cuando ejecutamos esto, la salida es Que es lo que probablemente se esperaría; hemos ejecutado cada declaración con el excepción de la cláusula de excepción ya que no se generó ZeroDivisionError. Si ahora cambiamos la llamada a my_function() para pasar 6 y 0, aumentaremos el error de división cero. Ahora la salida es La diferencia es que la segunda declaración de impresión en my_function() no tiene ha sido ejecutado; en cambio, después de imprimir ‘my_function in’ y luego generar el error que tenemos saltó directamente a la cláusula excepto y ejecutó la declaración de impresión en la asociación bloque de código asociado. Esta es en parte la razón por la cual el término arrojar se usa con respecto al error y la excepción. manejo; porque el error o la excepción se genera en un lugar y se arroja al punto donde se maneja, o se elimina de la aplicación si no hay una cláusula excepto encontrado para manejar el error/excepción. A partir de Antes de mi_función mi_funcion en mi_función fuera Después de mi_función Hecho imprimir(‘Iniciando’) intentar: print(‘Antes de mi_funcion’) mi_funcion(6, 0) print(‘Después de mi_función’) excepto ZeroDivisionError como exp: imprimir (‘ups’) imprimir(‘Terminado’ A partir de Antes de mi_función mi_funcion en ups Hecho 24.5 Manejo de una excepción 271
24.5.3 Captura cualquier excepción También es posible especificar una cláusula de excepción que se puede usar para capturar cualquier tipo de error o excepción, por ejemplo: Esta debe ser la última cláusula excepto, ya que omite el tipo de excepción y, por lo tanto, actúa como comodín. Se puede usar para asegurarse de que se le notifique que se produjo un error: aunque no sabes que tipo de error fue en realidad; por lo tanto, use este característica con precaución. 24.5.4 La cláusula Else La sentencia try también tiene una cláusula else opcional. Si esto está presente, entonces debe vienen después de todo excepto las cláusulas. La cláusula else se ejecuta si y solo si no se plantearon excepciones. Si se generó alguna excepción, no se ejecutará la cláusula else. A continuación se muestra un ejemplo de la cláusula else: En este caso la salida es: intentar: mi_funcion(6, 0) excepto IndexError como e: imprimir (e) excepto: imprimir(‘Algo salió mal’) intentar: mi_funcion(6, 2) excepto ZeroDivisionError como e: imprimir (e) demás: imprimir(‘Todo funciono bien’) mi_funcion en mi_función fuera Todo funcionó bien 272 24 Manejo de errores y excepciones
Como puede ver, la declaración de impresión en la cláusula else ha sido ejecutada, sin embargo si cambiamos la llamada my_function() para pasar un cero como segundo parámetro (lo que hará que la función genere un ZeroDivisionError), entonces el resultado es: Como puede ver, la cláusula else no se ejecutó, pero se ejecutó el controlador de excepción. 24.5.5 La cláusula final También se puede proporcionar una cláusula final opcional con la sentencia try. Este cláusula es la última cláusula en la declaración y debe ir después de cualquier clase excepto así como la cláusula else. Se utiliza para el código que desea ejecutar tanto si se produjo una excepción como si no. Para ejemplo, en el siguiente fragmento de código: El bloque de prueba se ejecutará, si no se genera ningún error, la cláusula else será ejecutado y por último se ejecutará finalmente el código, por lo tanto tendremos como salida: Sin embargo, si pasamos 6 y 0 a my_function(): intentar: mi_funcion(6, 2) excepto ZeroDivisionError como e: imprimir (e) demás: imprimir(‘Todo funciono bien’) finalmente: print(‘Siempre se ejecuta’) mi_funcion en división por cero mi_funcion en mi_función fuera Todo funcionó bien siempre corre intentar: mi_funcion(6, 0) excepto ZeroDivisionError como e: imprimir (e) demás: imprimir(‘Todo funciono bien’) finalmente: print(‘Siempre se ejecuta’) 24.5 Manejo de una excepción 273
Ahora generaremos una excepción en my_function(), lo que significa que el intento se ejecutará el bloque, luego se generará ZeroDivisionError, se manejará por la cláusula except y luego se ejecutará la cláusula finally. La salida es ahora: Como puede ver, en ambos casos se ejecuta la cláusula finally. La cláusula final puede ser muy útil para actividades generales de limpieza. como apagar o cerrar cualquier recurso que su código pueda estar usando, incluso si ha ocurrido un error. 24.6 Generación de una excepción Se genera un error o una excepción utilizando la palabra clave aumento. La sintaxis de esto es Por ejemplo: En la función anterior, la segunda declaración en el cuerpo de la función creará una nueva instancia de la clase ValueError y luego elevarla para que se lance permitiéndole ser capturado por cualquier controlador de excepciones que haya sido definido. Podemos manejar esta excepción escribiendo un bloque de prueba con una cláusula de excepción para la clase ValueError. Por ejemplo: Esto genera la salida mi_funcion en división por cero siempre corre aumentar <Tipo de excepción/error para aumentar>() def function_bang(): imprimir (‘función_explosión en’) subir ValueError(’¡Bang!’) imprimir(‘funcion_bang’) intentar: función_bang() excepto ValueError como ve: imprimir function_bang en ¡Estallido! 274 24 Manejo de errores y excepciones
Tenga en cuenta que si solo desea generar una excepción sin proporcionar ninguna argumentos del structor, entonces puede simplemente proporcionar el nombre de la clase de excepción al aumentar palabra clave: También puede volver a generar un error o una excepción; esto puede ser útil si simplemente quiere notar que ha ocurrido un error y luego volver a lanzarlo para que pueda ser manejado más arriba en su aplicación: Esto volverá a generar el ValueError capturado por la cláusula de excepción. Tenga en cuenta aquí que ni siquiera lo vinculó a una variable; sin embargo, podríamos haber hecho esto si fuera necesario. 24.7 Definición de una excepción personalizada Puede definir sus propios errores y excepciones, lo que puede darle más control sobre lo que sucede en circunstancias particulares. Para definir una excepción, se crea un subclase de la clase Exception o una de sus subclases. Por ejemplo, para definir una InvalidAgeException, podemos extender el clase de excepción y generar un mensaje apropiado: Esta clase se puede usar para representar explícitamente un problema cuando se establece una edad en un Persona que no está dentro del rango de edad aceptable. aumentar ValueError # abreviatura de aumentar ValueError() intentar: función_bang() excepto ValueError: imprimir (‘ups’) aumentar intentar: función_bang() excepto ValueError como ve: imprimir aumentar clase InvalidAgeException(Excepción): """ Las edades válidas deben estar entre 0 y 120 """ 24.6 Generación de una excepción 275
Podemos usar esto con la clase Persona que definimos anteriormente en el libro; este versión de la clase Person definió la edad como una propiedad e intentó validar que se estaba fijando una edad adecuada: Tenga en cuenta que el método de establecimiento de edad ahora arroja una InvalidAgeException, por lo que si nosotros escribimos: Podemos capturar el hecho de que se ha especificado una edad inválida. Sin embargo, en el controlador de excepciones no sabemos cuál era la edad no válida. Nosotros por supuesto, puede proporcionar esta información incluyéndola en los datos en poder del Excepción de edad no válida. Persona de clase: def init(yo, nombre, edad): self._name = nombre self._edad = edad @propiedad def edad(auto): """ La cadena de documentación para la propiedad de edad """ print(‘En método de edad’) volver self._age @age.setter def edad(auto, valor): print(‘En el método set_age(’, valor, ‘)’) si es instancia (valor, int) & (valor > 0 & valor < 120): self._edad = valor demás: aumentar InvalidAgeException (valor) @propiedad def nombre(auto): imprimir(‘En nombre’) volver self._name @nombre.eliminar def nombre(auto): del self._nombre def str(uno mismo): return ‘Persona[’ + str(self._name) + ‘] es ’ + self._edad intentar: p = Persona(‘Adán’, 21) página edad = -1 excepto InvalidAgeException: imprimir(‘Aqui dentro’) 276 24 Manejo de errores y excepciones
Si ahora modificamos la definición de clase de modo que proporcionemos un inicializador para permitir parámetros que se pasarán a la nueva instancia de InvalidAgeException: También hemos definido un método str() adecuado para convertir la excepción en una cadena con fines de impresión. Por supuesto, necesitamos actualizar el setter para proporcionar el valor que ha causado la problema: Ahora podemos escribir: Ahora, si se genera la excepción, se imprimirá un mensaje con el valor real valor que causó el problema: 24,8 Excepciones de encadenamiento Una característica final que puede ser útil al crear sus propias excepciones es encadenar a una excepción subyacente genérica. Esto puede ser útil cuando un genérico se genera una excepción, por ejemplo, por alguna biblioteca o por el propio sistema Python, y desea convertirlo en una excepción de aplicación más significativa. clase InvalidAgeException(Excepción): """ Las edades válidas deben estar entre 0 y 120 """ def init(uno mismo, valor): self.value = valor def str(uno mismo): devuelve ‘InvalidAgeException(’ + str(self.value) + ‘)’ @age.setter def edad(auto, valor): print(‘En el método set_age(’, valor, ‘)’) si es instancia (valor, int) & (valor > 0 & valor < 120): self._edad = valor demás: aumentar InvalidAgeException (valor) intentar: p = Persona(‘Adán’, 21) página edad = -1 excepto InvalidAgeException como e: imprimir (e) En el método set_age (-1) Excepción de edad no válida (-1) 24.7 Definición de una excepción personalizada 277
Por ejemplo, digamos que queremos crear una excepción para representar un evento específico. problema con los parámetros pasados a una función divide, pero no queremos usar el genérico Excepción de división cero, en cambio nosotros desear a usar nuestro propio DivideByYWhenZeroException. Esta nueva excepción podría definirse como Y podemos usarlo en una función dividir: Hemos utilizado las palabras clave raise y from cuando estamos instanciando el DivideByYWhenZeroException. Esto encadena nuestra excepción al original. excepción que indica el problema subyacente. Ahora podemos llamar al método de división de la siguiente manera: Esto produce un Traceback como se indica a continuación: clase DivideByYWhenZeroException(Excepción): """ Clase de excepción de ejemplo""" def divide(x, y): intentar: resultado = x /y excepto Excepción como e: aumentar DivideByYWhenZeroException de e def principal(): dividir (6, 0) Rastreo (llamadas recientes más última): Archivo “/Usuarios/Compartido/espacios de trabajo/pycharm/pythonintro/excepciones/exce ptions.py”, línea 43, en división resultado = x /y ZeroDivisionError: división por cero La excepción anterior fue la causa directa de la siguiente excepción: Rastreo (llamadas recientes más última): Archivo “/Usuarios/Compartido/espacios de trabajo/pycharm/pythonintro/excepciones/exce ptions.py”, línea 136, en <módulo> principal() Archivo “/Usuarios/Compartido/espacios de trabajo/pycharm/pythonintro/excepciones/exce ptions.py”, línea 79, en principal dividir (6, 0) Archivo “/Usuarios/Compartido/espacios de trabajo/pycharm/pythonintro/excepciones/exce ptions.py”, línea 45, en división aumentar DivideByYWhenZeroException de e main.DivideByYWhenZeroException 278 24 Manejo de errores y excepciones
Como se puede ver, obtiene información tanto sobre (aplicación específica) DivideByYWhenZeroException y el ZeroDivisionError original: el dos están unidos entre sí. Esto puede ser muy útil al definir dicha aplicación específica excepciones (pero donde aún debe entenderse la excepción subyacente real). 24,9 Recursos en línea Para obtener más información sobre errores de Python y conjuntos de excepciones: • https://docs.python.org/3/library/exceptions.html El documento de la biblioteca estándar Mención para excepciones incorporadas. • https://docs.python.org/3/tutorial/errors.html La documentación estándar de Python. ción tutorial sobre errores y excepciones. • https://www.tutorialspoint.com/python/python_exceptions.htm Una alternativa tutorial sobre el manejo de excepciones de Python. 24.10 Ejercicios Este ejercicio implica agregar compatibilidad con el manejo de errores a la clase CurrentAccount. En la clase Cuenta Corriente no debería ser posible retirar o depositar una cantidad negativa. Defina una clase de excepción/error llamada AmountError. El error de la cantidad debe tomar la cuenta involucrada y un mensaje de error como parámetros. A continuación, actualice los métodos de depósito () y retiro () en la cuenta y Clase de cuenta actual para generar un error de monto si el monto proporcionado es negativo. Debería poder probar esto usando: Esto debería resultar en la impresión de la excepción ’e’, por ejemplo: A continuación, modifique la clase de modo que si se intenta retirar dinero que tomará el saldo por debajo del umbral de límite de sobregiro y se generará un error. AmountError (no se pueden depositar cantidades negativas) en la cuenta[123] - Juan, cuenta corriente = 21,17 límite de sobregiro: -100,0 intentar: acc1.deposito(-1) excepto AmountError como e: imprimir (e) 24,8 Excepciones de encadenamiento 279
El error debe ser un BalanceError que defina usted mismo. El La excepción BalanceError debe contener información sobre la cuenta que generó el error. Pruebe su código creando instancias de CurrentAccount y tomando el saldo por debajo del límite de sobregiro. Escriba el código que usará los bloques de prueba y excepción para detectar la excepción que han definido. Debería poder agregar lo siguiente a su aplicación de prueba: intentar: print(‘saldo:’, cuenta1.saldo) acc1.retiro(300.00) print(‘saldo:’, cuenta1.saldo) excepto BalanceError como e: print(‘Excepción de manejo’) imprimir (e) 280 24 Manejo de errores y excepciones
capitulo 25 Módulos y paquetes de Python 25.1 Introducción Los módulos y paquetes son dos construcciones utilizadas en Python para organizar programas más grandes. gramos Este capítulo presenta módulos en Python, cómo se accede a ellos, cómo están definidos y cómo Python encuentra módulos, etc. También explora paquetes de Python y subpaquetes. 25.2 Módulos Un módulo le permite agrupar funciones, clases y código relacionados en general. Puede pensar en un módulo como si fuera una biblioteca de código (aunque de hecho muchas bibliotecas están compuestas por varios módulos como, por ejemplo, un biblioteca puede tener extensiones opcionales a la funcionalidad principal). Es útil organizar su código en módulos cuando el código se vuelve grande o cuando desea reutilizar algunos elementos de la base de código en múltiples proyectos Dividir una gran cantidad de código de un solo archivo ayuda a simplificar el código mantenimiento y comprensibilidad del código, pruebas, reutilización y alcance del código. Estos se exploran a continuación: • Simplicidad—Enfocarnos en un subconjunto de un problema general nos ayuda a desarrollar soluciones que funcionan para el subconjunto y se pueden combinar para resolver el problema general. Esto significa que los módulos individuales pueden ser más simples que los solución general. • Mantenimiento: los módulos suelen facilitar la definición de límites lógicos entre un cuerpo de código y otro. Esto significa que es más fácil ver lo que comprende un módulo y verificar que el módulo funciona correctamente incluso © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_25 281
cuando se modifique. También ayuda a distinguir un cuerpo de código de otro para que hace que sea más fácil determinar dónde deben ir los cambios. • Pruebas: dado que un módulo se puede independizar de otro módulo, hay menos dependencias y cruces. Esto significa que un módulo se puede probar en aislamiento e incluso antes de que otros módulos, y la aplicación general, hayan sido escrito. • Reutilización: definir una función o clase en un módulo significa que es más fácil para reutilizar esa función o clase en otro módulo, ya que los límites entre el un modulo y otro son claros. • Ámbito: los módulos normalmente también definen un espacio de nombres, es decir, un ámbito dentro que cada función o clase es única. Piense en un espacio de nombres un poco como un apellido; dentro de un salón de clases puede haber varias personas con el primer nombre ‘Juan’, pero podemos distinguir a cada persona usando su nombre completo, por ejemplo ‘John Hunt’, ‘John Jones’, ‘John Smith’ y ‘John Brown’; cada apellido en este El ejemplo proporciona un espacio de nombres que asegura que cada John sea único (y puede ser referenciado de forma única). 25.3 Módulos de Python En Python, un módulo equivale a un archivo que contiene código de Python. Un módulo puede contener • Funciones • Clases • Variables • Código ejecutable • Atributos asociados al módulo como su nombre. El nombre de un módulo es el nombre del archivo en el que está definido (menos el sufijo ‘.py’). Por ejemplo, el siguiente diagrama ilustra una función y una clase definidas dentro de un archivo llamado utils.py: Por lo tanto, la función de impresora () y la clase Forma se definen en las utilidades módulo. Se puede hacer referencia a ellos a través del nombre del módulo utils. 282 25 Módulos y paquetes de Python
Como ejemplo, veamos una definición para nuestro módulo utils definido en el archivo util.py: El módulo tiene un comentario que está al comienzo del archivo; esto es útil documentación para cualquier persona que trabaje con el módulo. En algunos casos, el comentario en el inicio del módulo puede proporcionar una documentación extensa sobre el módulo, como qué proporciona, cómo usar las características del módulo y ejemplos que se pueden utilizado como referencia. El módulo también tiene algún código ejecutable (la declaración de impresión justo después de la comentario) que se ejecutará cuando Python cargue/inicie el módulo. Este sucederá cuando se haga referencia al módulo por primera vez en una aplicación. “““Este es un módulo de prueba””” print(‘Hola, soy el módulo de utilidades’) def impresora(algun_objeto): imprimir(‘impresora’) imprimir (algún_objeto) imprimir (‘hecho’) forma de clase: def init(auto, id): self._id = id def str(uno mismo): devuelve ‘Forma - ’ + self._id @propiedad def id(auto): """ La cadena de documentación para la propiedad id """ imprimir (‘En el método de identificación’) devolver self._id @id.setter def id(auto, valor): print(‘En el método set_age’) self._id = id forma_predeterminada = Forma(‘cuadrado’) 25.3 Módulos de Python 283
Una variable default_shape también se inicializa cuando se carga el módulo y también se puede hacer referencia fuera del módulo de manera similar a la del módulo función y clase. Tales variables pueden ser útiles para establecer valores predeterminados o predefinidos. datos que pueden ser utilizados por los desarrolladores que trabajan con el módulo. 25.4 Importación de módulos de Python 25.4.1 Importación de un módulo Un módulo definido por el usuario no es automáticamente accesible para otro archivo o script; es necesario para importar el módulo. Importar un módulo hace que las funciones, clases y variables definidas en el módulo visible para el archivo al que se importan. Por ejemplo, para importar todo el contenido del módulo utils a un archivo llamado my_app.py podemos usar: Tenga en cuenta que no le damos el nombre del archivo (es decir, utils.py) sino que le damos el nombre del módulo a importar (que no incluye el .py). El resultado es el que se muestra a continuación: Una vez que las definiciones dentro del módulo utils estén visibles dentro de my_app.py, puede usarlos como si hubieran sido definidos en el archivo actual. Tenga en cuenta que debido al alcance de la función, clase y variable definidas dentro del módulo utils serán precedidas por el nombre del módulo; es decir, utils.printer y utils.Shape, etc.: Cuando ejecutemos el archivo my_app.py, produciremos el siguiente resultado: Importar utilidades Importar utilidades utils.printer(utils.forma_predeterminada) forma = utils.Shape(‘círculo’) utils.printer(forma) 284 25 Módulos y paquetes de Python
Observe que la primera línea de la salida es la salida de la declaración de impresión en el módulo utils (es decir, la línea print(‘Hola, soy el módulo utils’)) esto ilustra la capacidad de Python para definir el comportamiento que se ejecutará cuando se cargue un módulo. cargado; por lo general, esto podría ejecutar algún código de limpieza o configurar el comportamiento requerido por el módulo. Cabe señalar que el módulo sólo se inicializa la primera tiempo que se carga y, por lo tanto, las declaraciones ejecutables solo se ejecutan una vez. Si olvidamos incluir la declaración de importación al comienzo del archivo, entonces Python No sé usar el módulo utils. Generaríamos así un error indicando que el El elemento utils no se conoce: NameError: el nombre ‘utils’ no está definido. Puede haber cualquier número de módulos importados en un archivo; estos pueden ser ambos usuarios módulos definidos y módulos proporcionados por el sistema. Los módulos se pueden importar a través de declaraciones de importación separadas o proporcionando una lista de módulos separados por comas: Una convención común es colocar todas las declaraciones de importación al principio de un archivo; sin embargo, esto es solo una convención y las declaraciones de importación se pueden colocar en cualquier lugar en un archivo anterior a donde se requieren las características del módulo importado. es un comun sin embargo, la convención suficiente es que herramientas como PyCharm indicarán un problema de estilo si no coloca las importaciones al principio del archivo. 25.4.2 Importación desde un módulo Un problema con el ejemplo anterior es que hemos tenido que seguir refiriéndonos a la instalaciones proporcionadas por el módulo utils como utils.<cosa de interés>; que si bien deja muy claro que estas características provienen del módulo utils, es un poco tedioso. Es el equivalente a referirse a una persona por su nombre completo cada vez que hablamos con ellos. Sin embargo, si es la única otra persona en la habitación entonces normalmente no necesitamos ser tan formales; podríamos simplemente usar su primer nombre. Importar utilidades soporte de importación importar módulo1, módulo2, módulo3 hola soy el modulo de utils impresora Forma - cuadrado hecho impresora Forma - círculo 25.4 Importación de módulos de Python 285
Una variante de la declaración de importación nos permite importar todo desde un módulo en particular y eliminar la necesidad de prefijar las funciones o clases de los módulos con el nombre del módulo, por ejemplo: Que se puede leer a partir de <nombre del módulo> importar todo en ese módulo y hacerlo directamente disponible. Como muestra, podemos hacer referencia a default_shape, la función de impresora () y la clase Shape directamente. Tenga en cuenta que el ‘*’ aquí a menudo se denomina comodín; lo que significa que representa todo en el módulo. El problema con esta forma de importación es que puede dar lugar a conflictos de nombres, ya que trae al alcance todos los elementos definidos en el módulo utils. Sin embargo, podemos solo estar realmente interesado en la clase Shape; en cuyo caso podemos elegir sólo trae esa característica, por ejemplo: Ahora solo la clase Shape se ha importado al archivo y se ha creado directamente disponible. Incluso puede dar un alias para un elemento que se importa desde un módulo usando la declaración de importación. Por ejemplo, puede crear un alias para todo el paquete: Por ejemplo: En este ejemplo, en lugar de hacer referencia al módulo que estamos importando como utils, le hemos dado un alias y lo llamamos utilidades. También podemos alias elementos individuales de un módulo, por ejemplo, una función puede ser dado un alias, al igual que una variable o una clase. La sintaxis para esto es de utils importar Forma s = Forma(‘rectángulo’) huellas dactilares) desde <nombre del módulo> importar * de la importación de utilidades * impresora (forma_predeterminada) forma = Forma(‘círculo’) impresora (forma) importar <nombre_módulo> como <nombre_módulo_alternativo> importar utilidades como utilidades utilidades.impresora(utilidades.forma_predeterminada) 286 25 Módulos y paquetes de Python
Por ejemplo:
En este caso, la función de impresora en el módulo utils se está creando un alias para
myfunc y, por lo tanto, se conocerá como myfunc en el archivo actual.
Incluso podemos combinar varios de las importaciones junto con algunos de los elementos.
mentos que se importan con alias:
25.4.3
Ocultar algunos elementos de un módulo
Por defecto, cualquier elemento en un módulo cuyo nombre comience con un guión bajo (’_’) es
se oculta cuando se realiza una importación con comodines del contenido de un módulo.
De esta manera, ciertos elementos con nombre pueden ocultarse a menos que estén explícitamente
importado. Por lo tanto, si nuestro módulo de utilidades ahora incluyera una función:
Y luego intentamos importar todo el módulo usando la importación comodín y
acceder a _función_especial:
desde <nombre_módulo> importar
como antes
def _función_especial(): imprimir(‘Función especial’) de la importación de utilidades * _funcion especial() 25.4 Importación de módulos de Python 287
Obtendremos un error: Sin embargo, si importamos explícitamente la función, aún podemos hacer referencia a ella: Ahora el código funciona: Esto se puede usar para ocultar características que no están destinadas a ser utilizadas externamente. nalmente desde un módulo (los desarrolladores luego los usan bajo su propio riesgo) o para hacer características avanzadas solo disponibles para aquellos que realmente las quieren. 25.4.4 Importar dentro de una función En algunos casos, puede ser útil limitar el alcance de una importación a una función; de este modo, evitando cualquier uso innecesario o conflictos de nombres con las características locales. Para hacer esto, simplemente agregue una importación en el cuerpo de una función, por ejemplo: En este caso, solo se puede acceder a la clase Shape dentro del cuerpo de my_func(). 25,5 Propiedades del módulo Cada módulo tiene un conjunto de propiedades que se pueden usar para encontrar qué características tiene. proporciona, cuál es su nombre, cuál (si corresponde) es su cadena de documentación, etc. Estas propiedades se consideran especiales ya que todas comienzan y terminan con un doble barra inferior (’__’). Estos son: • name el nombre del módulo • doc el doctorado para el módulo • fichero el fichero en el que se definió el módulo. def mi_func(): de la forma de importación útil s = Forma(’línea’) NameError: el nombre ‘_special_function’ no está definido de utils import _special_function _funcion especial() hola soy el modulo de utils Funcion especial 288 25 Módulos y paquetes de Python
También puede obtener una lista de los contenidos de un módulo una vez que ha sido importado usando la función dir(<nombre-módulo>). Por ejemplo: Que produce: Tenga en cuenta que la declaración de impresión ejecutable todavía se ejecuta como se ejecuta cuando el módulo se carga en el tiempo de ejecución actual de Python; aunque todos lo hacemos entonces acceder a algunas de las propiedades del módulo. La mayoría de las propiedades de los módulos son utilizadas por herramientas para ayudar a los desarrolladores; pero pueden ser un referencia útil cuando se encuentra por primera vez con un módulo nuevo. 25.6 Módulos estándar Python viene con muchos módulos integrados y muchos más disponibles en terceros. De particular utilidad es el módulo sys, que contiene una serie de elementos de datos y funciones que se relacionan con la plataforma de ejecución en la que se ejecuta un programa. Algunas de las características del módulo sys se muestran a continuación, incluyendo sys.path(), que enumera los directorios que se buscan para resolver un módulo cuando se realiza una importación se utiliza la declaración. Este es un valor escribible, lo que permite que un programa agregue directorios (y por lo tanto módulos) antes de intentar una importación. hola soy el modulo de utils útiles Este es un módulo de prueba. utils.py [‘Forma’, ‘integrados’, ‘en caché’, ‘doc’, ‘archivo’, ‘cargador’, ‘nombre’, ‘paquete’, ‘espec’, ‘_función_especial’, ‘forma_predeterminada’, ‘impresora’] Importar utilidades imprimir (utils.name) imprimir (utils.doc) imprimir (utils.archivo) imprimir (dir (utilidades)) imprimir(‘sys.maxsize: ‘, sys.maxsize) print(‘sys.ruta: ‘, sys.ruta) imprimir(‘sys.plataforma: ‘, sys.plataforma) sistema de importación print(‘sys.version: ‘, sys.version) 25,5 Propiedades del módulo 289
Lo que produce el siguiente resultado en una Mac: Una herramienta común de administración de módulos se conoce como Anaconda. Anaconda (que es ampliamente utilizado particularmente en el campo de Data Science y Data Analytics) es se envía con una gran cantidad de bibliotecas/módulos comunes de Python de terceros. El uso de Anaconda evita la necesidad de descargar módulos separados; en cambio anaconda actúa como un depósito de (la mayoría) de los módulos y significa que acceder a estos módulos es tan simple como importarlos en su código. Puede descargar Anaconda desde https://www.anaconda.com/download. Para ver las bibliotecas de Python disponibles en Anaconda, use Anaconda Navigator: [Clang 6.0 (clang-600.0.57)] sys.maxsize: 9223372036854775807 sys.ruta: [’/pythonintro/módulos’, ‘/espacios de trabajo/pycharm’, ‘/ Biblioteca/Frameworks/Python.framework/Versions/3.7/lib/ python37.zip’, ‘/Biblioteca/Frameworks/Python.framework/Versions/ 3.7/lib/python3.7’, ‘/Biblioteca/Frameworks/Python.framework/ Versiones/3.7/lib/python3.7/lib-dynload’, ‘/Library/Frameworks/ Python.framework/Versions/3.7/lib/python3.7/site-packages’] sys.plataforma: Darwin 290 25 Módulos y paquetes de Python
25.7 Ruta de búsqueda del módulo de Python Un punto que hemos pasado por alto hasta ahora es ¿cómo encuentra Python estos módulos? La respuesta es que utiliza una variable de entorno especial llamada PYTHONPATH. Esta es una variable que se puede configurar antes de ejecutar Python que le dice dónde busque para encontrar cualquier módulo con nombre. Se insinúa en la sección anterior donde la variable de ruta del módulo sys es impreso. En la máquina en la que se ejecutó este código en la salida de esta variable era: Esta es en realidad una lista de las ubicaciones que Python buscaría para encontrar un módulo; estos incluyen el proyecto PyCharm que contiene los módulos relacionados con los ejemplos utilizado en este libro (que es el directorio actual), luego una ubicación PyCharm de nivel superior y una serie de ubicaciones de Python (donde las ubicaciones equivalen a directorios en el host máquina). Python buscará a través de cada una de estas ubicaciones a su vez para encontrar el nombre módulo; utilizará el primer módulo que encuentre. El algoritmo de búsqueda real es: • El directorio actual. • Si no se encuentra el módulo, Python busca cada directorio en el shell variable PYTHONPATH. • Si todo lo demás falla, Python comprueba la ruta predeterminada. En Unix/Linux, esta ruta predeterminada normalmente es /usr/local/lib/python/. Un punto a tener en cuenta sobre este orden de búsqueda es que es posible ocultar un sistema módulo proporcionado creando uno definido por el usuario y dándole el mismo nombre que el módulo del sistema; esto se debe a que su propio módulo se encontrará primero y se ocultará el sistema proporcionó uno. En este caso, PYTHONPATH se usará para encontrar módulos integrados de Python en la instalación de Python, mientras que nuestros propios módulos definidos por el usuario se encontrarán dentro los directorios del espacio de trabajo del proyecto PyCharm. También es posible anular la variable PYTHONPATH predeterminada. es lo que es conocida como una variable de entorno y, por lo tanto, se puede configurar como Unix/Linux o Windows variable de entorno del sistema operativo que luego puede ser recogida por su Python ambiente. La sintaxis utilizada para configurar PYTHONPATH depende de si está utilizando Windows o Unix/Linux tal como está configurado a nivel del sistema operativo: Aquí hay un PYTHONPATH típico de un sistema Windows: sys.path: [’/pycharm/pythonintro/modules’, ‘/espacios de trabajo/ pycharm’, ‘/Biblioteca/Frameworks/Python.framework/Versions/3.7/ lib/python37.zip’, ‘/Biblioteca/Frameworks/Python.framework/ Versiones/3.7/lib/python3.7’, ‘/Biblioteca/Frameworks/ Python.framework/Versions/3.7/lib/python3.7/lib-dynload’, ‘/ Biblioteca/Frameworks/Python.framework/Versions/3.7/lib/python3.7/ paquetes del sitio’] 25.7 Ruta de búsqueda del módulo de Python 291
Y aquí hay un PYTHONPATH típico de un sistema Unix/Linux: 25,8 Módulos como scripts Cualquier archivo de Python no es solo un módulo, sino también un script o programa de Python. Este significa que se puede ejecutar directamente si es necesario. Por ejemplo, el siguiente es el contenido de un archivo llamado module1.py: Cuando este archivo se ejecuta directamente o se carga en Python REPL, entonces el se ejecutará el código independiente y la salida generada será: Esto se ve bien hasta que intenta usar module1 con su propio código; el el código independiente aún ejecutará esto si ahora escribimos: “““Este es un módulo de prueba””” print(‘Hola soy el modulo 1’) definición f1(): imprimir(‘f1[1]’) definición f2(): imprimir(‘f2[1]’) X = 1 + 2 imprimir(‘x es’, x) f1() f2() hola soy modulo 1 x es 3 f1[1] f2[1] establecer PYTHONPATH = c:\python30\lib; establecer PYTHONPATH = /usr/local/lib/python módulo de importación1 módulo1.f1() 292 25 Módulos y paquetes de Python
Donde podría esperar ver solo el resultado de ejecutar la función f1() del módulo 1 en realidad obtienes: Las primeras 4 líneas se ejecutan cuando se carga el módulo 1, ya que son independientes. sentencias ejecutables dentro del módulo. Por supuesto, podemos eliminar el código independiente; pero y si a veces queremos para ejecutar module1 como un script/programa y, a veces, usarlo como un módulo importado en otros módulos? En Python podemos distinguir entre cuando un archivo se carga como módulo y cuando se ejecuta como un script/programa independiente. Esto se debe a que Python establece el propiedad del módulo name al nombre del módulo cuando se está cargando como un módulo; pero si un archivo se está ejecutando como un script independiente (o el punto de entrada de un application), entonces el name se establece en la cadena main. esto es en parte porque históricamente main() ha sido el punto de entrada para aplicaciones en numerosos otros lenguajes como C, C++, Java y C#. Ahora podemos determinar si un módulo se está cargando o ejecutando como un script/principal aplicación comprobando la propiedad name del módulo (que está directamente accesible desde dentro de un módulo). Si el módulo es el punto de entrada principal entonces ejecutar algún código; si no es así, haz otra cosa. Por ejemplo, “““Este es un módulo de prueba””” print(‘Hola soy el modulo 1’) definición f1(): imprimir(‘f1[1]’) definición f2(): imprimir(‘f2[1]’) si nombre == ‘principal’: X = 1 + 2 imprimir(‘x es’, x) f1() f2() hola soy modulo 1 x es 3 f1[1] f2[1] f1[1] 25,8 Módulos como scripts 293
Ahora el código dentro de la declaración if solo se ejecutará cuando este módulo esté cargado como el punto de partida para una aplicación/secuencia de comandos. Tenga en cuenta que la declaración de impresión en la parte superior del módulo aún se ejecutará en ambos escenarios; esto puede ser útil como permite que el comportamiento de configuración o inicialización aún se ejecute cuando corresponda. Un patrón común, o expresión idiomática, es colocar el código que se ejecutará cuando se esté cargando un archivo. cargado directamente (en lugar de como un módulo) en una función llamada main() y para llamar esa función desde dentro de la instrucción if. Esto ayuda a aclarar qué comportamiento es destinado a ejecutarse cuando, por lo tanto, la versión final de nuestro módulo es: Esta versión es lo que ahora se llamaría Python idiomático o Pythonic en estilo. 25,9 Paquetes de Python 25.9.1 Organización del paquete Python permite a los desarrolladores organizar módulos en paquetes, en una jerarquía estructura árquica basada en directorios. Un paquete se define como • un directorio que contiene uno o más archivos fuente de Python y • un archivo fuente opcional llamado init.py. Este archivo también puede contener código que se ejecuta cuando se importa un módulo del paquete. “““Este es un módulo de prueba””” print(‘Hola soy el modulo 1’) definición f1(): imprimir(‘f1[1]’) definición f2(): imprimir(‘f2[1]’) def principal(): X = 1 + 2 imprimir(‘x es’, x) f1() f2() si nombre == ‘principal’: principal() 294 25 Módulos y paquetes de Python
Por ejemplo, la siguiente imagen ilustra un paquete utils que contiene dos Módulos de clases y funciones. En este caso, el archivo init.py contiene un código de inicialización a nivel de paquete: El contenido del archivo init.py se ejecutará una vez, la primera vez se hace referencia al módulo dentro del paquete. El módulo de funciones contiene varias definiciones de funciones; mientras que la El módulo de clases contiene varias definiciones de clase. Nos referimos a los elementos del paquete en relación con el nombre del paquete, como se muestra abajo: Aquí estamos importando tanto el módulo de funciones como el módulo de clases. del paquete utils. La función f1() se define en las funciones módulo mientras que la clase Procesador se define dentro del módulo de clases. Puedes usar todos los estilos from e import que ya hemos visto, por ejemplo puede importar una función de un módulo en un paquete y darle un alias: Es posible importar todos los módulos de un paquete simplemente importando el Nombre del paquete. Si desea proporcionar cierto control sobre lo que se importa de un paquete cuando esto sucede, puede definir una variable en el init.py archivo que indicará lo que se importará en esta situación. print(‘paquete de utilidades’) desde utils.functions importar * f1() desde la importación de utils.classes * p = Procesador() desde util.functions importar f1 como myfunc mifunc() 25,9 Paquetes de Python 295
25.9.2 Subpaquetes Los paquetes pueden contener subpaquetes a cualquier profundidad que necesite. por ejemplo, el El siguiente diagrama ilustra esta idea: En el diagrama anterior, utils es el paquete raíz, este contiene dos subpaquetes file_utils y network_utils. El paquete file_utils tiene una inicialización sation y un módulo file_support. El paquete network_utils también tiene un archivo de inicialización de paquetes y dos módulos; monitoreo_de_red y red_soporte. Por supuesto, el paquete raíz también tiene su propia inicialización. y sus propios módulos clases y funciones. Para importar el subpaquete, hacemos lo mismo que antes pero cada paquete en el la ruta al módulo está separada por un punto, por ejemplo: o 25.10 Recursos en línea Consulte la documentación de la biblioteca estándar de Python para: • https://docs.python.org/3/tutorial/modules.html#standard-modules el estándar módulos. • https://docs.python.org/3/tutorial/modules.html#packages paquetes. importar utils.file_utils.file_support desde utils.file_utils.file_support importar file_logger 296 25 Módulos y paquetes de Python
• https://docs.python.org/3/tutorial/stdlib.html breve recorrido por la biblioteca estándar parte 1. • https://docs.python.org/3/tutorial/stdlib2.html breve recorrido por la biblioteca estándar parte 2. • https://pymotw.com/3/ el sitio Módulo de la semana de Python que enumera muchos módulos y una referencia extremadamente útil. 25.11 Ejercicio El objetivo de este ejercicio es crear un módulo para las clases que has desarrollando. Debe mover su cuenta, cuenta actual, cuenta de depósito y las clases BalanceError en un módulo separado (archivo) llamado cuentas. Ahorrar este archivo en un nuevo paquete de Python llamado fintech. Separe la aplicación de prueba de este módulo para que pueda importar la clases del paquete. Su aplicación de prueba ahora se verá así: Por supuesto, también podría usar from accounts import * para evitar el prefijo las cuentas clases relacionadas con cuentas. importar fintech.accounts como cuentas acc1 = cuentas.CuentaActual(‘123’, ‘Juan’, 10.05, 100.0) acc2 = cuentas.DepositAccount(‘345’, ‘John’, 23.55, 0.5) acc3 = cuentas.InvestmentAccount(‘567’, ‘Phoebe’, 12.45, ‘high riesgo’) imprimir (acc1) imprimir (acc2) imprimir (acc3) acc1.deposito(23.45) acc1.retirar (12.33) print(‘saldo:’, cuenta1.saldo) print(‘Número de instancias de cuenta creadas:’, cuentas.Cuenta.cuenta_instancia) intentar: print(‘saldo:’, cuenta1.saldo) acc1.retiro(300.00) print(‘saldo:’, cuenta1.saldo) excepto cuentas.BalanceError como e: print(‘Excepción de manejo’) imprimir (e) 25.10 Recursos en línea 297
capitulo 26 Clases base abstractas 26.1 Introducción Este capítulo presenta Clases Base Abstractas (también conocidas como ABC) que fueron introducido originalmente en Python 2.6. Una clase base abstracta es una clase que no puede instanciar y que se espera ser ampliado por una o más subclases. Estas subclases luego completarán cualquiera de los las brechas dejaron la clase base. Las clases base abstractas son muy útiles para crear jerarquías de clases con un alto nivel de reutilización de la clase raíz en la jerarquía. 26.2 Clases abstractas como concepto Una clase abstracta es una clase a partir de la cual no puede crear un objeto. es típicamente falta uno o más elementos necesarios para crear un objeto completamente funcional. En contraste, una clase no abstracta (o concreta) no deja nada sin definir y puede ser utilizado para crear un objeto de trabajo. Por lo tanto, puede preguntarse para qué sirve una clase abstracta. La respuesta es que puedes agrupar elementos que se van a compartir entre varias clases, sin proporcionar una implementación completa. En Además, puede obligar a las subclases a proporcionar métodos específicos para garantizar que los implementadores de una subclase al menos proporcionan métodos con nombres apropiados. Tú por lo tanto, debe usar clases abstractas cuando: • desea especificar datos o comportamientos comunes a un conjunto de clases, pero insuficientes ciente para una sola instancia, • desea obligar a las subclases a proporcionar un comportamiento específico. © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_26 299
En muchos casos, las dos situaciones van juntas. Por lo general, los aspectos de la clase por definir como abstractos son específicos de cada clase, mientras que lo que se ha mented es común a todas las clases. 26.3 Clases base abstractas en Python Las clases base abstractas (o ABC, como a veces se las denomina) no se pueden se instancian a sí mismos, pero pueden ampliarse mediante subclases. Estas subclases pueden ser clases concretas o pueden ser ellos mismos Clases Base Abstractas (que amplían el concepto definido en la Clase Base Abstracta raíz). Las clases base abstractas se pueden usar para definir genéricos (potencialmente abstractos) comportamiento que se puede mezclar con otras clases de Python y actuar como una raíz abstracta de una jerarquía de clases. También se pueden utilizar para proporcionar una forma más formal de especificar comportamiento que debe ser proporcionado por una clase concreta. Las clases base abstractas pueden tener: • Cero o más métodos o propiedades abstractas (pero no están obligados a hacerlo). • Cero o más métodos y propiedades concretas (pero no están obligados a hacerlo). • Tanto los atributos privados como los protegidos (después del único guión bajo y convenciones de doble subrayado). Los ABC también se pueden usar para especificar una interfaz específica o un protocolo formal. Si una ABC define cualquier método abstracto o propiedad abstracta, entonces las subclases deben proporcionar implementaciones para todos estos elementos abstractos. Hay muchos ABC incorporados en Python que incluyen (pero no se limitan a): • estructuras de datos (módulo de recopilación), • módulo de números, • flujos (módulo IO). De hecho, los ABC se usan ampliamente internamente dentro de Python y muchos desarrolladores Los operadores usan ABC sin siquiera saber que existen o entender cómo definir a ellos. De hecho, los ABC no son muy utilizados por los desarrolladores que crean sistemas con Python. aunque esto se debe en parte a que son más apropiados para quienes construyen bibliotecas, particularmente aquellos que se espera que sean extendidos por los propios desarrolladores. 26.3.1 Subclasificación de un ABC Por lo general, será necesario importar una clase base abstracta del módulo en que se define; por supuesto, si el ABC está definido en el módulo actual, entonces esto no será necesario. 300 26 Clases base abstractas
Como ejemplo, la clase collections.MutableSequence es un ABC; este es un ABC para una secuencia de elementos que pueden ser modificados (mutables) y iterado sobre. Podemos usar esto como la clase base para nuestro propio tipo de colección que llamaremos a una Bolsa, por ejemplo: desde colecciones importar MutableSequence Bolsa de clase (MutableSequence): aprobar En este ejemplo estamos importando MutableSequence de la columna lecciones módulo. Nosotros entonces definir el clase Bolsa como extensión el Clase base abstracta MutableSequence. Por el momento estamos usando el La palabra clave especial de Python pasa como marcador de posición para el cuerpo de la clase. Sin embargo, esto significa que la clase Bag también es en realidad una clase abstracta, ya que no implementa ninguno de los métodos abstractos en MutableSequence ABC. Python, sin embargo, no valida esto en el momento de la importación; en su lugar, lo valida en tiempo de ejecución cuando se va a crear una instancia del tipo. En este caso, la clase Bag no implementa los métodos abstractos en MutableSequence y, por lo tanto, si un programa intenta crear una instancia de Bag, entonces se lanzaría el siguiente error: Rastreo (llamadas recientes más última): Archivo “/pythonintro/abstract/Bag.py”, línea 10, en <módulo> principal() Archivo “/pythonintro/abstract/Bag.py”, línea 7, en main bolsa = bolsa() TypeError: no se puede crear una instancia de clase abstracta Bolsa con resumen métodos delitem, getitem, len, setitem, insertar Como puede verse, este es un requisito bastante formal; si no implementas todos los métodos definidos como abstractos en la clase principal, entonces no puede crear una instancia de la clase que está definiendo (porque también es abstracta). Podemos definir un método para cada una de las clases abstractas en la clase Bag y luego podremos crear una instancia de la clase, por ejemplo: 26.3 Clases base abstractas en Python 301
Esta versión de Bag cumple con todos los requisitos que le impone el ABC MutableSequence; es decir, implementa cada uno de los métodos especiales enumerados y el método de inserción. La clase Bag ahora se puede considerar como una clase concreta. Sin embargo, en este caso los métodos en sí mismos no hacen nada (nuevamente usan el pase de palabra clave de Python que actúa como un marcador de posición para que el código lo implemente cada método). Sin embargo, ahora podemos escribir: bolsa = bolsa() Y la aplicación no generará un mensaje de error. En este punto, ahora podríamos avanzar a la implementación de cada método de tal manera que proporciona una implementación adecuada de la Bolsa. 26.3.2 Definición de una clase base abstracta Una clase base abstracta se puede definir especificando que la clase tiene una metaclase; típicamente, ABCMeta. La metaclase ABCMeta es proporcionada por el módulo abc. La metaclase se especifica mediante el atributo de metaclase de la lista de clases principal. Esto creará una clase que se puede usar como ABC. Alternativamente, puede extender la clase abc.ABC que especificó ABCMeta como su metaclase. Así es exactamente como son los ABC en el archivo _colecciones_abc.py implementado. El siguiente fragmento de código ilustra esta idea. Se importa la clase ABCMeta del módulo abc. Luego se usa con la clase Shape a través de la metaclase. atributo de la lista de herencia de clases: desde colecciones importar MutableSequence Bolsa de clase (MutableSequence): def getitem(uno mismo, índice): aprobar def delitem(uno mismo, índice): aprobar def len(uno mismo): aprobar def setitem(auto, índice, valor): aprobar def insert(auto, índice, valor): aprobar 302 26 Clases base abstractas
Tenga en cuenta que en este punto, aunque Shape es un ABC, no define ningún resumen elementos y, por lo tanto, en realidad se pueden instanciar como cualquier otra clase concreta. Sin embargo, a continuación definiremos algunos métodos abstractos para la Forma ABC. Para definir un método abstracto, también necesitamos importar el método abstracto decorador del módulo abc, (si queremos definir una propiedad abstracta entonces necesita agregar @property a un método abstracto apropiado). Importando el El decorador de método abstracto se ilustra a continuación: de abc importar ABCMeta, método abstracto Ahora podemos expandir la definición de nuestra clase Shape: de abc importar ABCMeta, método abstracto clase Forma(metaclase=ABCMeta): def init(auto, id): self._id = id @metodoabstracto def mostrar (auto): pasar @propiedad @metodoabstracto def id(auto): pasar La clase Shape ahora es una clase base abstracta y requiere que cualquier subclase debe proporcionar una implementación del método display() y la identificación de la propiedad (de lo contrario, la subclase se volverá automáticamente abstracta). La clase Circulo es una subclase concreta de la Forma ABC; proporciona así un método de inicialización init(), un método de visualización y una propiedad de id (la El método init() se usa para permitir que el atributo _id en la clase base sea inicializado). de abc importar ABCMeta clase Forma(metaclase=ABCMeta): def init(auto, id): self.id = id 26.3 Clases base abstractas en Python 303
Ahora podemos usar la clase Circle en una aplicación: c = Circulo(“circulo1”) imprimir (c.id) c.display() Podemos instanciar la clase Circle ya que es concreta y sabemos que podemos llamar el método display() y acceda al ID de propiedad en instancias de Circle. La salida del código anterior es así: circulo1 Círculo: círculo1 26.4 Definición de una interfaz Muchos lenguajes como Java y C# tienen el concepto de una definición de interfaz; Este es un contrato entre los implementadores de una interfaz y el usuario de la implementación garantizando que se proporcionarán ciertas facilidades. Python no tiene explícitamente el concepto de un contrato de interfaz (nota aquí interfaz se refiere a la interfaz entre una clase y el código que utiliza esa clase). Sin embargo, tiene clases base abstractas. Cualquier clase base abstracta que solo tenga métodos abstractos o propiedades puede ser tratado como un contrato que debe implementarse (por supuesto, también puede tener métodos, propiedades y atributos; depende del desarrollador). Sin embargo, como sabemos que Python garantizará que cualquier instancia solo se pueda crear a partir de concreto clases, podemos tratar un ABC que se comporta como un contrato entre una clase y esas utilizando esa clase. Este es un enfoque adoptado por numerosos marcos y bibliotecas dentro de Pitón. círculo de clase (forma): def init(auto, id): super().init(id) def mostrar (auto): imprimir(‘Círculo: ‘, self._id) @propiedad def id(auto): """ la propiedad id """ devolver self._id 304 26 Clases base abstractas
26.5 Subclases virtuales En la mayoría de los lenguajes de programación orientados a objetos, para que una clase sea tratada como subclase de otra clase, es necesario que la subclase amplíe la clase padre. Sin embargo, Python tiene un enfoque más relajado para escribir, como lo ilustra la idea de Duck Typing (discutido en el próximo capítulo). En algunas situaciones, sin embargo, es útil poder confirmar que un tipo es un subclase de otra o que una instancia es una instancia de un tipo específico (que puede provienen de la jerarquía de clases del objeto) en tiempo de ejecución. De hecho, en Python, no es necesario ser una subclase real de una clase principal para considerarse una subclase; en cambio, las subclases virtuales permiten que una clase sea tratada como una subclase de otra a pesar de que no existe una relación de herencia directa entre ellos. La clave aquí es que la subclase virtual debe coincidir con el requerido interfaz presentada por la clase principal virtual. Esto se hace registrando una clase como Subclase Virtual de una Base Abstracta Clase. Es decir, la clase principal virtual debe ser un ABC y luego la subclase puede ser registrado (en tiempo de ejecución) como una subclase virtual de ABC. Esto se hace usando un método llamado registro(). Una vez que una clase se registra como una subclase de un ABC, issubclass() y Los métodos isintance() devolverán True para esa clase con respecto a la virtual clase de padres. Por ejemplo, dadas las siguientes dos clases actualmente independientes: de abc importar ABCMeta clase Persona(metaclase=ABCMeta): def init(yo, nombre, edad): self.nombre = nombre self.edad = edad def cumpleaños (uno mismo): imprimir(‘Feliz cumpleaños’) clase Empleado(objeto): def init(yo, nombre, edad, id): self.nombre = nombre self.edad = edad self.id = id def cumpleaños (uno mismo): imprimir(‘Es tu cumpleaños’) Si ahora verificamos si Empleado es una subclase de Persona, obtendremos el valor Falso devuelto. Por supuesto, también obtendremos Falso si verificamos para ver un La instancia de Employee es en realidad una instancia de la clase Person: 26.5 Subclases virtuales 305
Esto generará la salida: FALSO FALSO Sin embargo, si ahora registramos la clase Empleado como una subclase virtual de la clase Persona, los dos métodos de prueba devolverán True: Persona.registrar(Empleado) imprimir(essubclase(Empleado, Persona)) e = Empleado(‘Megan’, 21, ‘MS123’) imprimir (es una instancia (e, Persona)) Que ahora genera la salida: Verdadero Verdadero Esto proporciona un nivel de flexibilidad muy útil que se puede aprovechar al utilizar bibliotecas y frameworks existentes. 26.6 mezclas Un mixin es una clase que representa alguna funcionalidad (típicamente concreta) que tiene la potencialmente para ser útil en múltiples situaciones, pero por sí solo no es algo que sería instanciado. Sin embargo, un mixin se puede mezclar con otras clases y puede ampliar los datos y comportamiento de ese tipo y puede acceder a los datos y métodos proporcionados por esas clases. Los mixins son una categoría común de clases base abstractas; aunque son implícito en su uso (y denominación) en lugar de ser una construcción concreta dentro de la El propio lenguaje Python. Por ejemplo, definamos una clase PrinterMixing que proporciona una utilidad método para ser utilizado con otras clases. No es algo que queramos que los desarrolladores instanciarse a sí mismo, por lo que lo convertiremos en un ABC, pero no define ningún resumen métodos o propiedades (por lo que no hay nada que una subclase tenga que implementar). imprimir(essubclase(Empleado, Persona)) e = Empleado(‘Megan’, 21, ‘MS123’) imprimir (es una instancia (e, Persona)) 306 26 Clases base abstractas
Ahora podemos usar esto con una clase Empleado que extiende la clase Persona y mezclas en la clase PrinterMixin: Esto ahora significa que cuando creamos una instancia de la clase Empleado, podemos llamar a la método print_me() en el objeto Empleado: e = Empleado(‘Megan’, 21, ‘MS123’) e.print_me() que imprimirá Empleado(MS123)Megan[21] Un punto a tener en cuenta sobre PrinterMixin es que es completamente independiente de la clase en la que se mezcla. Sin embargo, los mixins también pueden imponer algunas restricciones en el clases en las que se mezclarán. Por ejemplo, el IDPrinterMixin que se muestra a continuación asume que la clase en la que se mezclará tiene un atributo o propiedad llamado id. clase IDPrinterMixin(metaclase=ABCMeta): def print_id(auto): imprimir (self.id) Esto significa que no se puede mezclar con éxito en la clase Persona, si era entonces cuando se llamaba al método print_id() se generaba un error. Sin embargo, la clase Empleado tiene un atributo id y, por lo tanto, el IDPrinterMixin se puede mezclar con la clase Empleado: de abc importar ABCMeta clase PrinterMixin(metaclase=ABCMeta): def print_me(auto): imprimir (uno mismo) clase Persona(objeto): def init(uno mismo, nombre): self.nombre = nombre clase Empleado(Persona, PrinterMixin): def init(yo, nombre, edad, id): super().init(nombre) self.edad = edad self.id = id def str(uno mismo): return ‘Empleado(’ + self.id + ‘)’ + self.name + ‘[’
- str(auto.edad) + ‘]’ 26.6 mezclas 307
Lo que significa que ahora podemos llamar a escribir: e = Empleado(‘Megan’, 21, ‘MS123’) e.print_me() e.print_id() Lo cual generará: Empleado(MS123)Megan[21] MS123 26.7 Recursos en línea Algunas referencias en línea para las clases base abstractas incluyen: • https://docs.python.org/3/library/abc.html La documentación estándar de la biblioteca sobre Clases Base Abstractas. • https://pymotw.com/3/abc/index.html La página Módulo Python de la semana para Clases base abstractas. • https://www.python.org/dev/peps/pep-3119/ Python PEP 3119 que introdujo Clases base abstractas. 26.8 Ejercicios El objetivo de este ejercicio es utilizar una clase base abstracta. La clase Cuenta del proyecto en el que ha estado trabajando durante los últimos pocos capítulos es actualmente una clase concreta y, de hecho, se instancia en nuestra prueba solicitud. Modifique la clase Cuenta para que sea una Clase base abstracta que forzará todos los ejemplos concretos para ser una subclase de Cuenta. clase Empleado(Persona, PrinterMixin, IDPrinterMixin): def init(yo, nombre, edad, id): super().init(nombre) self.edad = edad self.id = id def str(uno mismo): return ‘Empleado(’ + self.id + ‘)’ + self.name + ‘[’
- str(auto.edad) + ‘]’ 308 26 Clases base abstractas
El elemento del código de creación de cuenta ahora podría verse así: acc1 = cuentas.CuentaActual(‘123’, ‘Juan’, 10.05, 100.0) acc2 = cuentas.DepositAccount(‘345’, ‘John’, 23.55, 0.5) acc3 = cuentas.CuentaInversión(‘567’, ‘Phoebe’, 12.45, ‘arriesgado’) 26.8 Ejercicios 309
capitulo 27 Protocolos, Polimorfismo y descriptores 27.1 Introducción En este capítulo exploraremos la idea de un contrato implícito entre un objeto y el código que usa ese objeto. Como parte de esta discusión exploraremos lo que es significa Duck Typing. A continuación, introduciremos el concepto de Python llamado un protocolo Exploraremos su papel dentro de la programación de Python y veremos dos protocolos comunes; el protocolo de administrador de contexto y el descriptor Protocolo. 27.2 Contratos implícitos Algunos lenguajes de programación (más notables Java y C#) tienen la idea de un contrato explícito entre una clase y el usuario de esa clase; este contrato proporciona una garantía de los métodos que se proporcionarán y los tipos que se utilizarán para parámetros y valores de retorno de estos métodos. En estos idiomas ayuda a garantizar que un método solo se llama con el tipo de valores apropiado y solo en situaciones apropiadas. De manera un poco confusa, estos contratos se denominan interfaces en Java y C#; pero están destinados a describir la programación de aplicaciones interfaz presentada por la clase. Python es un lenguaje mucho más flexible y fluido que Java o C#. y por lo tanto no tiene un concepto explícito de una interfaz. Sin embargo, esto puede hacer las cosas más complejo a veces; por ejemplo, considere la clase Calculadora muy simple dada abajo: Calculadora de clase: def suma(self, x, y): volver x + y © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_27 311
¿Cuáles son los valores válidos que se pueden pasar al método add y utilizar para los parámetros x e y? Inicialmente, podría parecer que valores numéricos como 1, 2 y 3,4, 5,77, etc. ser las únicas cosas que se pueden usar con el método add: Esto genera la siguiente salida: Sin embargo, esto en realidad representa un contrato en el que los valores pasan al El método Calculator.add() admitirá el operador más. En un capítulo anterior exploramos una clase Cantidad que implementó este operador (entre otros) y así también podemos usar objetos de Cantidad con el método add() de la Calculadora: Que imprime: Este contrato implícito dice que el método Calculator.add() funcionará con cualquier cosa que admita el operador de suma numérica (o para decirlo de otro modo); cualquier- cosa que es como numérico. Esto también se conoce como Duck Typing; Esto es descrito en la siguiente sección. calc = Calculadora() print(‘calc.add(3, 4):’, calc.add(3, 4)) imprimir(‘calc.add(3, 4.5):’, calc.add(3, 4.5)) imprimir(‘calc.add(4.5, 6.2):’, calc.add(4.5, 6.2)) imprimir(‘calc.add(2.3, 7):’, calc.add(2.3, 7)) print(‘calc.add(-1, 4):’, calc.add(-1, 4)) calc.add(3, 4): 7 calc.add(3, 4.5): 7.5 calc.add(4.5, 6.2): 10.7 calc.add(2.3, 7): 9.3 calc.add(-1, 4): 3 q1 = Cantidad(5) q2 = Cantidad(10) imprimir (calc.add (q1, q2)) Cantidad[15] 312 27 Protocolos, Polimorfismo y Descriptores
27.3 Mecanografía de pato Este término bastante extraño proviene de un viejo dicho: Si camina como un pato, nada como un pato y grazna como un pato, ¡entonces es un pato! En Python Duck Typing (también conocido como tipado de forma o tipado estructural) implica que si un objeto puede realizar el conjunto requerido de operaciones, entonces es algo adecuado para utiliza para lo que quieras. Por ejemplo, si su tipo se puede usar con la suma, multiplicación, operadores de división y resta de lo que puede tratarse como un tipo numérico (incluso si no lo es). Esta es una característica muy poderosa de Python y permite que el código originalmente escrito para trabajar con un conjunto específico de tipos, para ser utilizado también con un conjunto de tipos completamente nuevo; siempre y cuando cumplan con el contrato implícito definido en el código. También es interesante notar que un conjunto particular de métodos puede tener un superconjunto de requisitos en un tipo particular, pero solo necesita implementar tanto como sea necesarios para la funcionalidad que realmente usará. Por ejemplo, modifiquemos un poco la clase Calculadora y agreguemos algo más métodos para ello: A primera vista, esto puede indicar que cualquier cosa que se esté usando con la Calculadora debe implementar los cuatro operadores ‘+’, ‘−’, ‘/’ y ‘*’. Sin embargo, esto solo es cierto si Ud. necesita ejecutar los cuatro métodos definidos en la clase. Por ejemplo, considere el tipo Distancia: Calculadora de clase: """ Clase de calculadora simple""" def suma(self, x, y): volver x + y def restar(self, x, y): volver x - y def multiplicar(self, x, y): volver x * y def divide(self, x, y): volver x / y distancia de clase: def init(self, d): valor propio = d def add(uno mismo, otro): distancia de retorno (valor propio + otro valor) def sub(uno mismo, otro): distancia de retorno (valor propio - otro valor) def str(uno mismo): devuelve ‘Distancia[’ + str(self.value) + ‘]’ 27.3 Mecanografía de pato 313
Esto define una clase que implementa solo add() y sub() métodos y, por lo tanto, solo admitirá los operadores ‘+’ y ‘-’. ¿Se pueden usar instancias de Distancia con la clase Calculadora? La respuesta es que pueden pero solo con los métodos de suma y resta (ya que solo cumplen parte de el contrato implícito entre la clase Calculadora y cualquier tipo usado con esa clase). Así podemos escribir: Y obtener la salida: Sin embargo, si tratamos de usar los métodos multiplicar() o dividir(), obtener un error, por ejemplo: Básicamente, le dice que el operador ‘/’ no es compatible cuando se usa con el tipo Distancia. 27.4 Protocolos Como se mencionó anteriormente, Python no tiene ningún mecanismo formal para establecer qué se requiere entre el proveedor de alguna funcionalidad y el usuario o consumidor de esa funcionalidad. En cambio, el enfoque mucho menos formal denominado Duck Typing es adoptado en su lugar. d1 = Distancia(6) d2 = Distancia(3) imprimir (calc.add (d1, d2)) imprimir (calc. restar (d1, d2)) Distancia[9] Distancia[3] Rastreo (llamadas recientes más última): Archivo “Calculator.py”, línea 46, en <módulo> imprimir(calc.divide(d1, d2)) Archivo “Calculator.py”, línea 15, en división volver x / y TypeError: tipo(s) de operando no admitido(s) para /: ‘Distancia’ y ‘Distancia’ 314 27 Protocolos, Polimorfismo y Descriptores
Sin embargo, esto plantea la pregunta; ¿Cómo sabes lo que se requiere? Como hacer sabe que debe proporcionar los operadores numéricos para que un objeto se use con la clase Calculadora? La respuesta es que se utiliza un concepto conocido como Protocolo. Un protocolo es una descripción informal de la interfaz del programador proporcionada por algo en Python (por ejemplo, una clase, pero también podría ser un módulo o un conjunto de funciones independientes). Se define únicamente a través de la documentación (y, por lo tanto, la clase Calculadora debe tener una cadena de documentación de clase que defina su protocolo). Con base en la información proporcionada por el protocolo si una función o método requiere un objeto para proporcionar una operación específica (o método), entonces si todo funciona excelente; si no, se lanzará un error y el tipo no es compatible. Es uno de los elementos clave en Python que permite el concepto Orientado a Objetos de polimorfismo para operar. 27.5 Un ejemplo de protocolo Existen numerosos protocolos comunes que se pueden encontrar en Python. Por ejemplo, existe un protocolo para definir Secuencias, como un contenedor que puede se puede acceder a un elemento a la vez. Este protocolo requiere que cualquier tipo que vaya a ser retenido en el contenedor debe proporcione los métodos len() y getitem(). Por lo tanto, cualquier clase que implemente estos dos métodos cumple con los requisitos de la protocolo. Sin embargo, debido a que los protocolos son informales y no se aplican en Python, no es realmente necesario para implementar todos los métodos en un protocolo (como vimos en el sección previa). Por ejemplo, si se sabe que una clase solo se usará con iteración, entonces puede que solo sea necesario implementar el método getitem(). 27.6 El protocolo del administrador de contexto Otro ejemplo concreto es el del Context Manager Protocol. este protocolo se introdujo en Python 2.5, por lo que ahora está muy bien establecido. Está asociado con la declaración ‘con como’. Esta afirmación se usa típicamente con clases que necesitarán asignar y liberar los llamados recursos. Estos recursos pueden ser archivos, conexiones a bases de datos, etc. En cada uno de estos casos es necesario realizar una conexión (por ejemplo, a un archivo o una base de datos) antes de que se puede utilizar el objeto asociado. Sin embargo, la conexión debe cerrarse y liberarse antes de que terminemos. utilizando el objeto. Esto se debe a que las conexiones colgantes a cosas como archivos y las bases de datos pueden quedarse y causar problemas más adelante (por ejemplo, normalmente solo se permite un número limitado de conexiones simultáneas a un archivo o una base de datos a la vez 27.4 Protocolos 315
tiempo y si no se cierran correctamente un programa puede quedarse sin disponibilidad
conexiones).
La instrucción ‘with as’ garantiza que todos los pasos de configuración se realicen antes de que se
el objeto está disponible para su uso y que cualquier comportamiento de apagado se invoca cuando está
terminado con.
La sintaxis para el uso de la declaración ‘con como’ es
Por ejemplo:
Tenga en cuenta que, en este caso, el objeto al que hace referencia cmc solo está dentro del alcance dentro del
líneas sangradas después de la declaración ‘with as’; después de esto, la variable cmc es no
más accesible.
¿Cómo funciona esto? De hecho, lo que hace la declaración ‘con como’ es llamar a un
método especial cuando se ingresa la declaración ‘with as’ (justo después del ‘:’ arriba);
este método es el método enter(). Luego también llama a otro método especial
justo cuando se sale de la declaración ‘con como’ (justo después de la última declaración sangrada).
Este segundo método es el método exit().
• Se espera que el método enter() realice cualquier configuración/asignación de recursos/
hacer conexiones, etc. Se espera que devuelva un objeto que se utilizará
dentro del bloque de declaraciones que forman esa declaración ‘con como’. Es
común devolverse a sí mismo aunque no es un requisito para hacerlo (esta flexibilidad
permite que el objeto administrado actúe como una fábrica para otros objetos si es necesario).
• Se llama al método exit() en el objeto gestionado y se pasa
información sobre cualquier excepción que se haya generado durante el
cuerpo de la instrucción ‘with as’. Tenga en cuenta que el método exit() es
llamado ya sea que se haya lanzado una excepción o no. El método salir()
devuelve un bool, si devuelve True, cualquier excepción que se haya generado es
tragado (es decir, se suprime y no se pasa al código de llamada). Si se
devuelve Falso, entonces, si hay una excepción, también se devuelve a lo que sea
código llamado declaración ‘con como’.
Una clase de ejemplo que se puede usar con la instrucción ‘with as’ (que cumple
los requisitos del Protocolo de administrador de contexto) se proporciona a continuación:
con
La clase anterior implementa el Protocolo de administrador de contexto en el sentido de que define tanto el método enter() y el método exit(). Ahora podemos usar esta clase con la instrucción with as: clase ContextManagedClass (objeto): def init(uno mismo): imprimir(’init’) def enter(uno mismo): imprimir(’enter’) regresar a sí mismo # Tipo de excepción Args, valor de excepción y rastreo def exit(uno mismo, *argumentos): imprimir(’salir:’, argumentos) volver verdadero def str(uno mismo): devolver ‘Objeto de Clase Gestionada por el Contexto’ imprimir(‘Iniciando’) con ContextManagedClass() como cmc: print(‘Entrada con bloque’, cmc) imprimir(‘Saliendo’) imprimir(‘Terminado’) El resultado de esto es: A partir de en eso ingresar En bloque con el objeto ContextManagedClass saliendo salir: (Ninguno, Ninguno, Ninguno) Hecho A partir de esto, puede ver que el método enter() se llama antes que el código en el bloque y exit() se llama después del código en el bloque. 27.7 Polimorfismo El polimorfismo es la capacidad de enviar el mismo mensaje (solicitud para ejecutar un método) a objetos diferentes, cada uno de los cuales parece realizar la misma función. sin embargo, el La forma en que se maneja el mensaje depende de la clase del objeto. Polimorfismo es una palabra que suena extraña, derivada del griego, para un relativamente concepto sencillo. Es esencialmente la capacidad de solicitar que la misma operación sea 27.6 El protocolo del administrador de contexto 317
realizado por una amplia gama de diferentes tipos de cosas. Cómo se procesa la solicitud cessed depende de la cosa que recibe la solicitud. El programador no necesita preocuparse por cómo se maneja la solicitud, solo que así es. Esto se ilustra a continuación. En este ejemplo, el parámetro pasado a la función night_out() espera recibir algo que responda a los métodos comer(), beber() y dormir(). Cualquier objeto que cumpla con este requisito se puede utilizar con la función. Podemos definir múltiples clases que cumplan con este contrato informal, por ejemplo, puede definir una jerarquía de clases que proporcione estos métodos, o separar completamente clases que implementan los métodos. En el caso de la jerarquía de clases los métodos puede o no anular los de la clase principal. Efectivamente, esto significa que puede pedir muchas cosas diferentes para realizar el mismo acción. Por ejemplo, puede pedirle a un rango de objetos que proporcionen una cadena imprimible describiéndose a sí mismos. De hecho, en Python esto es exactamente lo que sucede. Por ejemplo, si le pide a una instancia de una clase Manager, un objeto compilador o un objeto de base de datos que devuelve una cadena de este tipo, utiliza el mismo método (str(), en Python). El nombre de polimorfismo es desafortunado y a menudo conduce a confusión. Hace todo el proceso suena bastante más grandioso de lo que realmente es. Tenga en cuenta que esta es una de las características más significativas y flexibles de Python; lo hace no atar una variable a un tipo específico; en su lugar a través de Duck Typing siempre que el objeto siempre que cumpla con el contrato implícito, entonces estamos bien. Las siguientes clases cumplen con el contrato implícito en la función night_out(): def night_out(pag): turba() p.beber() p.dormir() Persona de clase: def comer(auto): print(‘Persona - Comer’) def bebida(auto): print(‘Persona - Bebida’) def dormir(self): print(‘Persona - Dormir’) clase Empleado(Persona): def comer(auto): imprimir(‘Empleado - Comer’) def bebida(auto): print(‘Empleado - Bebida’) def dormir(auto): imprimir(‘Empleado - Dormir’) clase Vendedor(Empleado): def comer(self): print(‘Vendedor - Comer’) def bebida(auto): print(‘Vendedor - Bebida’) perro de clase: def comer(yo): print(‘Perro - Comer’) def beber(auto): print(‘Perro - Beber’) def dormir(auto): print(‘Perro - Dormir’) 318 27 Protocolos, Polimorfismo y Descriptores
Esto significa que las instancias de todas estas clases se pueden utilizar con el función night_out(). Tenga en cuenta que la clase SalesPerson cumple el contrato implícito en parte a través de herencia (el método sleep() se hereda de Employee). 27,8 El Protocolo Descriptor Otro protocolo es el protocolo descriptor que se introdujo en Python 2.2. Los descriptores se pueden utilizar para crear lo que se conoce como atributos administrados. Un atributo administrado es un atributo de objeto que se administra (o protege) de acceso directo por código externo a través del descriptor. El descriptor puede entonces tomar cualquier acción que sea apropiada, como validar los datos, verificar el formato, registrar la acción, actualizar un atributo relacionado, etc. El protocolo descriptor define cuatro métodos (como es habitual, se consideran métodos especiales y, por lo tanto, comience con un guión bajo doble ‘__’): • get(self, instancia, propietario) Este método se llama cuando el se accede al valor de un atributo. La instancia es la instancia que se está modificando. y el propietario es la clase que define el objeto. Este método debería devolver el valor de atributo (calculado) o genera una excepción AttributeError. • set(self, instancia, valor) Esto se llama cuando el valor de un se está configurando el atributo. El valor del parámetro es el nuevo valor que se establece. • delete(self, instancia) Llamado para eliminar el atributo. • set_name(self, propietario, nombre) Llamado en el momento del propietario se crea el propietario de la clase. El descriptor ha sido asignado a nombre. Este El método se agregó al protocolo en Python 3.6. La siguiente clase Logger implementa el protocolo Descriptor. Puede por lo tanto, ser utilizado con otras clases para registrar la creación, acceso y actualización de cualquier atributo sobre el que se aplique. 27.7 Polimorfismo 319
Cada uno de los métodos definidos para el protocolo imprime un mensaje para que el acceso puede ser monitoreado. La clase Logger se usa con la siguiente clase Cursor. Cursor de clase (objeto):
Configurar los descriptores a nivel de clase
x = Registrador(‘x’) y = Registrador(‘y’) def init(self, x0, y0):
Inicializar los atributos
# Tenga en cuenta el uso de __dict__ para evitar el uso de la notación self.x
# que invocaría el comportamiento del descriptor
self.dict[‘x’] = x0 self.dict[‘y’] = y0 def move_by(self, dx, dy): imprimir (‘mover_por’, dx, ‘,’, dy) self.x = self.x + dx self.y = self.y + dy def str(uno mismo): return ‘Punto[’ + str(self.dict[‘x’]) + ‘, ’ + str(self.dict[‘y’]) + ‘]’ registrador de clase (objeto): """ Clase de registrador que implementa el protocolo descriptor""" def init(uno mismo, nombre): self.nombre = nombre def get(self, inst, propietario): print(’get:’, inst, ‘propietario’, propietario, ‘, valor’, self.name, ‘=’, str(inst.dict[self.name])) return inst.dict[self.name] def set(self, inst, valor): print(’set:’, inst, ‘-’, self.name, ‘=’, valor) inst.dict[self.name] = valor def delete(self, instancia): imprimir(’borrar’, instancia) def set_name(yo, propietario, nombre): print(’set_name’, ‘propietario’, propietario, ‘configuración’, nombre) 320 27 Protocolos, Polimorfismo y Descriptores
Hay varios puntos a tener en cuenta sobre esta definición de clase, que incluyen: Los Descriptores deben definirse a nivel de clase, no a nivel de objeto/instancia. nivel. Estos atributos x e y del objeto Cursor están definidos como registradores descriptores dentro de la clase (no dentro del método init()). si intentas definalos usando self.x y self.y los descriptores no serán registrados. El método Cursor init() usa el diccionario dict para inicializar el atributos de instancia/objeto x e y. Este es un enfoque alternativo para acceder a un atributos de los objetos; es utilizado internamente por un objeto para contener los valores de atributos reales. Pasa por alto el mecanismo normal de búsqueda de atributos invocado cuando usa el notación de puntos (como cursor.x = 10). Esto significa que no será interceptado por el Descriptor. Esto se ha hecho porque el registrador usa el método str() para imprimir la instancia que contiene el atributo que utiliza los valores actuales de x e y. Cuando el valor de x se establece inicialmente, no habrá valor para y y, por lo tanto, se producirá un error. ser generado por str(). El método str() también usa el diccionario dict para acceder al atributos ya que no es necesario registrar este acceso. También se volvería recursivo si el registrador también usó el método para imprimir la instancia. Ahora podemos usar instancias del objeto Cursor sin saber que el el descriptor interceptará el acceso a los atributos x e y: El resultado de esto ilustra cómo los descriptores han interceptado el acceso a los atributos Tenga en cuenta que el método move_by() accede tanto al getter como al setter descriptor métodos como este método lee el valor actual de los atributos y luego los actualiza. imprimir(‘p1:’, cursor) cursor.x = 20 cursor.y = 35 imprimir (‘p1 actualizado:’, cursor) imprimir(‘p1.x:’, cursor.x) imprimir(’-’ * 25) cursor.move_by(1, 1) imprimir(’-’ * 25) del cursor.x cursor = Cursor(15, 25) imprimir(’-’ * 25) 27,8 El Protocolo Descriptor 321
27,9 Recursos en línea Están disponibles los siguientes recursos en línea centrados en los protocolos de Python: • https://ref.readthedocs.io/en/latest/understanding_python/interfaces/existing_ protocols.html Documentación sobre los protocolos (nativos) predeterminados de Python, incluidos los protocolos de comparación, hash, acceso a atributos y secuencia. • https://docs.python.org/3/library/stdtypes.html#context-manager-types para Contexto tipos de gerentes. • https://pymotw.com/3/contextlib/index.html El módulo Python de la semana para las utilidades del administrador de contexto. • https://en.wikipedia.org/wiki/Polymorphism_(informática_ciencia) Wikipedia página sobre polimorfismo. 27.10 Ejercicios Este ejercicio involucra la implementación del Protocolo de Administrador de Contexto. Regresa a tu Cuenta de clases relacionadas. Modifique la clase Cuenta para que implemente el Administrador de contexto Protocolo. Esto significa que necesitará implementar enter() y Métodos salir(). Coloque mensajes de impresión dentro de los métodos para que pueda ver cuándo se ejecutan. set_name propietario <clase ‘main.Cursor’> configuración x set_name propietario <clase ‘main.Cursor’> configuración y
p1: Punto[15, 25] conjunto: Punto[15, 25] - x = 20 conjunto: Punto[20, 25] - y = 35 p1 actualizado: Punto[20, 35] get: Point[20, 35] propietario <clase ‘main.Cursor’> , valor X = 20 p1.x: 20
move_by 1 , 1 get: Point[20, 35] propietario <clase ‘main.Cursor’> , valor X = 20 conjunto: Punto[20, 35] - x = 21 get: Point[21, 35] propietario <clase ‘main.Cursor’> , valor y = 35 conjunto: Punto[21, 35] - y = 36
eliminar Punto[21, 36] 322 27 Protocolos, Polimorfismo y Descriptores
Los nuevos métodos que ha definido serán heredados por cada una de las subclases tú tener creado; a saber Cuenta actual, Cuenta de depósito y Cuenta de inversión. Ahora pruebe su calculadora modificada usando: Que debería producir una salida similar a: Creando nueva cuenta ingresar 15.5 salir: (Ninguno, Ninguno, Ninguno) con cuentas.CurrentAccount (‘891’, ‘Adam’, 5.0, 50.0) como acc: depósito de cuenta (23.0) retiro de cuenta (12.33) imprimir(cuenta.saldo) 27.10 Ejercicios 323
capitulo 28 Monkey Patching y búsqueda de atributos 28.1 Introducción Monkey Patching es un término con el que es posible que te encuentres cuando busques en el Python más o al buscar en la web conceptos relacionados con Python. se relaciona con la capacidad en Python para extender la funcionalidad asociada con una clase/tipo en tiempo de ejecución Aunque no está directamente relacionado con Monkey Patching; cómo Python busca atributos peros y cómo se puede gestionar este proceso es un aspecto útil para entender. En particular, cómo manejar atributos desconocidos puede ser muy útil en la gestión de situaciones uaciones en las que Monkey Patching podría usarse para resolver un atributo inicial incompatibilidad. Este capítulo explora tanto la aplicación de parches de mono como la búsqueda de atributos de Python. 28.2 ¿Qué es el parche de mono? Monkey Patching es la idea de que es posible agregar comportamiento a un objeto, en tiempo de ejecución, para cumplir con algún requisito que originalmente el tipo no cumplió. Esto puede suceder, por ejemplo, ya que no hay un requisito fijo para que una clase implementar todo un protocolo; en muchos casos, una clase solo puede implementar la mayor parte de un protocolo que se requiere para satisfacer las necesidades actuales; si en una etapa posterior, otros elementos de se requiere un protocolo, luego se pueden agregar. Por supuesto, si es probable que esto suceda con frecuencia, entonces las características pueden ser agregado a la clase para uso de todos; pero si no, entonces esas características se pueden agregar dinámicamente en tiempo de ejecución a un objeto en sí. Esto evita la interfaz pública del El tipo se llena de características/funcionalidades que rara vez se utilizan. © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_28 325
28.2.1 ¿Cómo funciona el parche de mono? Python es un lenguaje dinámico que permite que la definición de un tipo cambie en tiempo de ejecución Como los métodos en los objetos son, en esencia, solo otro atributo de una clase, aunque uno que se puede ejecutar, es posible agregar nueva funcionalidad a una clase por definiendo nuevos atributos que contendrán referencias al nuevo comportamiento. 28.2.2 Ejemplo de parches de mono La siguiente clase, Bag, implementa un método de inicialización init(), str() y el método getitem() utilizados para admitir el acceso indexado a un tipo de contenedor (o colección). bolsa de clase(): def init(uno mismo): self.datos = [‘a’, ‘b’, ‘c’] def getitem(self, pos): devolver self.data[pos] def str(uno mismo): devuelve ‘Bolsa(’ + str(self.data) + ‘)’ b = Bolsa() imprimir (b) Esto crea un objeto Bolsa e imprime el contenido de la Bolsa: Bolsa([‘a’, ‘b’, ‘c’]) Sin embargo, si tratamos de ejecutar imprimir (len (b)) Obtendremos un error de tiempo de ejecución: Rastreo (llamadas recientes más última): Archivo “Bolsa.py”, línea 12, en <módulo> imprimir (len (b)) TypeError: el objeto de tipo ‘Bag’ no tiene len() Esto se debe a que la función len() espera que el objeto que se le pasa implemente Mencione el método len() que se utilizará para obtener su longitud. En este caso el class Bag no implementa este método. 326 28 Monkey Patching y búsqueda de atributos
Sin embargo, podemos definir una función independiente que se comporte de la forma en que necesitaría la Bolsa para calcular su longitud, por ejemplo: def get_length(self): volver len(self.data) Esta función toma un único parámetro al que hemos llamado self. luego usa este parámetro para hacer referencia a un atributo llamado datos que a su vez usa len() para devolver la longitud de los elementos de datos asociados. En la actualidad, esta función no tiene relación con la clase Bag aparte del hecho que asume que cualquier cosa que se le pase tendrá un atributo llamado datos— lo que hace la clase Bag. De hecho, la función get_length() es compatible con cualquier clase que tenga un datos de atributos que se pueden utilizar para determinar su longitud. Ahora podemos asociarlo con la clase Bag; esto se puede hacer asignando el referencia de función (en la práctica, el nombre de la función) a un atributo apropiado en la Bolsa clase. Dado que la función len() espera que una clase implemente el método len() podemos asignar la función get_length() al atributo len(). Este efectivamente agrega un nuevo método a la clase Bag con la firma len(self):
parche de mono
Bolsa.len = obtener_longitud Ahora cuando invocamos imprimir (len (b)) Obtenemos el valor 3 que se imprime. Ahora tenemos Monkey Patched the class Bag para que el método que falta se vuelve disponible. 28.2.3 El autoparámetro Una de las razones por las que Monkey Patching funciona es que todos los métodos reciben la primer parámetro especial (llamado self por convención) que representa el objeto mismo. Esto significa que cualquier función que trate el primer parámetro como si fuera una referencia a un El objeto puede usarse potencialmente para definir un método en una clase. Si una función no asume que el primer parámetro es una referencia a un objeto (el que tiene el método) entonces no se puede usar para agregar una nueva funcionalidad a un clase. 28.2 ¿Qué es el parche de mono? 327
28.2.4 Agregar nuevos datos a una clase La aplicación de parches Monkey no se limita solo a la funcionalidad; también es posible añadir nuevos atributos de datos a una clase. Por ejemplo, si quisiéramos que cada Bolsa tuviera un nombre, entonces podríamos agregar un nuevo atributo a la clase para mantener su nombre: Bag.name = ‘Mi bolsa’ imprimir(b.nombre) Que imprime la cadena ‘Mi bolsa’ que ahora actúa como un valor predeterminado de la nombre de cualquier bolsa. Una vez que se agrega el atributo, podemos cambiar el nombre de este instancia particular de una bolsa: Por ejemplo, si extendemos el ejemplo anterior: Bag.name = ‘Mi bolsa’ imprimir(b.nombre) b.name = ‘Bolsa de Juan’ imprimir(b.nombre) b2 = Bolsa() imprimir(b2.nombre) Ahora podemos generar: Mi bolsa Bolsa de calzoncillos Mi bolsa 28.3 Búsqueda de atributos Como se muestra arriba, Python es muy dinámico y es fácil agregar atributos y métodos a una clase, pero ¿cómo funciona esto? Vale la pena considerar cómo Python administra la búsqueda de atributos y métodos para un objeto. Las clases de Python pueden tener atributos orientados tanto a la clase como a la instancia, por ejemplo la siguiente clase Student tiene un conteo de atributos de clase (que está asociado con la clase en sí) y un nombre de atributo de objeto o instancia. Así cada instancia de la class Student tendrá su propio atributo de nombre. estudiante de clase: cuenta = 0 def init(uno mismo, nombre): self.nombre = nombre Student.count += 1 328 28 Monkey Patching y búsqueda de atributos
Cada vez que se crea una instancia de la clase Student, Student.count
el atributo se incrementará en 1.
Para administrar estos atributos, Python mantiene diccionarios internos; uno para la clase
atributos y uno para atributos de objeto. Estos diccionarios se llaman dict y pueden
ser accedido desde la clase
Diccionario de atributos de clase
print(‘Estudiante.dict:’, Estudiante.dict)
Diccionario de instancias/objetos
print(’estudiante.dict:’, estudiante.dict) Lo que produce el resultado que se muestra a continuación (tenga en cuenta que el diccionario de clase contiene más información que solo el recuento de atributos de clase): Estudiante.dict: {’módulo’: ‘principal’, ‘recuento’: 1, ‘init’: <función Estudiante.init en 0x10d515158>, ‘dict’: <atributo ‘dict’ de los objetos ‘Estudiante’>, ‘weakref’: <atributo ‘weakref’ de Objetos ‘Estudiante’>, ‘doc’: Ninguno} estudiante.dict: {’nombre’: ‘Juan’} Para buscar un atributo, Python hace lo siguiente para los atributos de clase:
- Busque en el Diccionario de la clase un atributo
- Si el atributo no se encuentra en el paso 1, busque en los diccionarios de la(s) clase(s) principal(es) Para los atributos de los objetos, Python primero busca en el diccionario de instancias y repite el pasos anteriores, por lo tanto realiza estos pasos:
- Busque en el diccionario de objetos/instancias
- Si no se encontró el atributo en el paso 1, busque en el diccionario de la clase un atributo
- Si el atributo no se encuentra en el paso 2, busque en los diccionarios de la(s) clase(s) principal(es) Por lo tanto, dadas las siguientes afirmaciones, se toman diferentes pasos cada vez: alumno = alumno(‘Juan’) print(‘Student.count:’, Student.count) # búsqueda de clase print(’estudiante.nombre:’, estudiante.nombre) # búsqueda de instancia print(‘student.count:’, student.count) # la búsqueda encuentra la clase atributo 28.3 Búsqueda de atributos 329
El resultado esperado es que intenta acceder al recuento de atributos de clase dará como resultado el valor 1, donde el nombre del atributo del objeto devuelve ‘John’. Cantidad de estudiantes: 1 estudiante.nombre: John número de estudiantes: 1 Como los diccionarios utilizados para contener los atributos de clase y objeto son solo eso diccionarios, proporcionan otra forma de acceder a los atributos de una clase como Alumno. Es decir, puede escribir código que accederá a un valor de atributo usando el dict apropiado en lugar de la notación de punto más habitual, por ejemplo, el siguientes son equivalentes:
búsqueda de clase
print(‘Estudiante.cuenta:’, Estudiante.cuenta) print(“Estudiante.dict[‘cuenta’]:”, Estudiante.dict[‘cuenta’])
Búsqueda de instancias/objetos
print(’estudiante.nombre:’, estudiante.nombre) print(“estudiante.dict[’nombre’]:”, estudiante.dict[’nombre’]) En ambos casos, el resultado final es el mismo, ya sea el recuento de atributos de clase es se accede o se accede al nombre del atributo del objeto/instancia: Cantidad de estudiantes: 1 Estudiante.dict[‘contar’]: 1 estudiante.nombre: John estudiante.dict[’nombre’]: John Sin embargo, acceder a los atributos a través de dict no desencadena una búsqueda. proceso; en cambio, es una búsqueda directa en el contenedor de diccionario asociado. Así si intenta acceder a una variable de clase a través de los objetos dict, obtendrá un error. Esto se ilustra a continuación, donde intentamos acceder a la variable de clase de conteo a través del objeto estudiante:
Intento de buscar la variable de clase a través del objeto
print(’estudiante.nombre:’, estudiante.nombre) print(“estudiante.dict[‘contar’]:”, estudiante.dict[‘contar’]) Esto generará un KeyError que indica que el objeto dict no mantenga presionada una tecla llamada ‘contar’: Rastreo (llamadas recientes más última): Archivo “Estudiante.py”, línea 60, en <módulo> print(“estudiante.dict[‘contar’]:”, estudiante.dict[‘contar’]) KeyError: ‘contar’ 330 28 Monkey Patching y búsqueda de atributos
28.4 Manejo del acceso a atributos desconocidos Monkey patching es, por supuesto, muy flexible y muy útil cuando sabes lo que necesita proporcionar; sin embargo, ¿qué sucede cuando una referencia de atributo (o método) invocación) ocurre cuando no se espera? Por defecto se genera un error como el AttributeError a continuación: alumno = alumno(‘Juan’) res1 = estudiante.dummy_attribute imprimir (‘p.dummy_attribute:’, res1) Esto genera un AttributeError para dummy_attribute Rastreo (llamadas recientes más última): Archivo “Estudiante.py”, línea 51, en <módulo> res1 = estudiante.dummy_attribute AttributeError: el objeto ‘Estudiante’ no tiene atributo ‘dummy_attribute’ Por supuesto, puede detectar el error de atributo si lo desea; pero el medio de envolver su código en un bloque de prueba. Un enfoque alternativo es definir un método llamado getattr(); este método será llamado cuando no se encuentre un atributo en el objetos (y clases) diccionario dict. Este método puede realizar cualquier la acción es apropiada, como registrar un mensaje o proporcionar un valor predeterminado, etc. Por ejemplo, si modificamos la definición de la clase Student para incluir un método getattr() tal que se devuelve un valor predeterminado: estudiante de clase: cuenta = 0 def init(uno mismo, nombre): self.nombre = nombre Student.count += 1
Método llamado si el atributo es desconocido
def getattr(uno mismo, atributo): imprimir (’getattr:’, atributo) devolver ‘predeterminado’ Ahora, cuando tratamos de acceder a dummy_attribute en un objeto de estudiante, obtendrá la cadena ‘predeterminada’ devuelta: alumno = alumno(‘Juan’) res1 = estudiante.dummy_attribute imprimir (‘p.dummy_attribute:’, res1) 28.4 Manejo del acceso a atributos desconocidos 331
Ahora genera: getattr: atributo_ficticio p.dummy_attribute: predeterminado Tenga en cuenta que el método getattr() solo se llama para atributos desconocidos como Python primero busca en dict y, por lo tanto, si se encuentra el atributo, no se realiza ninguna llamada a el método getattr(). También tenga en cuenta que si se accede a un atributo directamente desde el dict (por ejemplo estudiante.dict[’nombre’]) luego el método getattr() nunca se invoca. 28.5 Manejo de invocaciones de métodos desconocidos El método getattr() también se invoca si se llama a un método desconocido. Por ejemplo, si llamamos al método dummy_method() en un objeto Student, entonces se genera un error (de hecho, esto es nuevamente un AttributeError). Sin embargo, si nosotros definir un método getattr() podemos devolver una referencia a un método para usar como un valor predeterminado Por ejemplo, si modificamos el método getattr() para devolver un referencia del método (es decir, el nombre de un método en la clase Student): estudiante de clase: cuenta = 0 def init(uno mismo, nombre): self.nombre = nombre Student.count += 1
Método llamado si el atributo es desconocido
def getattr(uno mismo, atributo): imprimir (’getattr:’, atributo) devolver self.my_default def my_default(auto): devolver ‘predeterminado’ Ahora, cuando se invoca un método indefinido (como dummy-method()) Se llamará a getattr(). Este método devolverá una referencia a la método my_default(). Esto se ejecutará y el valor devuelto como un efecto secundario de la invocación del método como lo indican las canastas redondas (el ‘()’) después de la llamada a el método original: alumno = alumno(‘Juan’) res2 = estudiante.dummy_method() imprimir(’estudiante.dummy_method():’, res2) 332 28 Monkey Patching y búsqueda de atributos
Lo que produce el siguiente resultado: en lugar de un mensaje de error getattr: método_ficticio estudiante.dummy_method(): predeterminado 28.6 Búsqueda de atributos de intercepción También es posible interceptar siempre las búsquedas de atributos usando la nación de puntos (por ejemplo, estudiante.nombre) implementando el método getattribute(). Este siempre se llamará al método en lugar de buscar el atributo en los objetos diccionario. El getattr() método voluntad solo ser llamado si un Se genera AttributeError o el método getattribute() explícitamente llama al método getattr(). Por lo tanto, el método getattribute() debería devolver un valor de atributo (que puede ser un valor predeterminado) o generar un AttributeError si corresponde. Es importante evitar implementar código que recursivamente se llamará a sí mismo (por ejemplo, llamar a self.name dentro de getattribute() resultará en un llamada recursiva a getattribute()!). Para evitar esto, la implementación ción del método debe acceder al directorio dict o llamar a la base Clases método getattribute(). A continuación se proporciona un ejemplo de un método getattribute() simple que registra la llamada a un método y luego pasa la invocación a la clase base implementación: estudiante de clase: cuenta = 0 def init(uno mismo, nombre): self.nombre = nombre Student.count += 1
Método llamado si el atributo es desconocido
def getattr(uno mismo, atributo): imprimir (’getattr:’, atributo) devolver ‘predeterminado’
Siempre se llamará al método cuando un atributo
se accede, solo se llamará a getattr si
lo hace explícitamente o si se genera un AttributeError
def getattribute(yo, nombre): imprimir(’obteneratributo()’, nombre) devolver objeto.obteneratributo(yo, nombre) def my_default(auto): devolver ‘predeterminado’ 28.5 Manejo de invocaciones de métodos desconocidos 333
Podemos usar esta versión de la clase con el siguiente fragmento de código estudiante = Estudiante(‘Katie’) print(’estudiante.nombre:’, estudiante.nombre) # búsqueda de instancia res1 = estudiante.dummy_attribute # invocar atributo faltante imprimir (’estudiante.dummy_attribute:’, res1) La salida de esto es ahora: getattribute() nombre estudiante.nombre: Katie getattribute() atributo_ficticio getattr: atributo_ficticio estudiante.dummy_attribute: predeterminado Como puede ver en esto, se llama al método getattribute() para ambos nombre del estudiante y estudiante.dummy_attribute. Sin embargo el El método getattr() solo se llama cuando el dummy_attribute es accedido Tenga en cuenta que getattribute() solo se invoca para el acceso a atributos y no para la invocación de métodos (a diferencia de getattr()). 28.7 Interceptar Establecer un atributo También es posible interceptar la asignación de atributos de objeto/instancia cuando el punto se está utilizando la notación (por ejemplo, estudiante.nombre = ‘Bob’). Esto se puede hacer por implementando el método setattr(). Este método se invoca en lugar de la asignación. El método setattr() puede realizar cualquier acción requerida, incluido el almacenamiento el valor ofrecido. Sin embargo, para hacer esto debe insertar el valor directamente en el diccionario de objetos (por ejemplo, estudiante.dict[’nombre’] = ‘Bob’) o preferiblemente llama al método setattr() de la clase base, por ejemplo object. setattr__(self, name, value) como se muestra a continuación para la clase Student: estudiante de clase: cuenta = 0 def init(uno mismo, nombre): self.nombre = nombre Student.count += 1
Siempre se llamará al método cuando se establezca un atributo
def setattr(yo, nombre, valor): imprimir(’setattr:’, nombre, valor) objeto.setattr(yo, nombre, valor) 334 28 Monkey Patching y búsqueda de atributos
Si ahora definimos el siguiente programa que usa la clase Student: alumno = alumno(‘Juan’) estudiante.nombre = ‘Bob’ print(’estudiante.nombre:’, estudiante.nombre) # búsqueda de instancia Ejecutando esto podríamos generar el siguiente resultado: setattr: nombre Juan setattr: nombre Bob estudiante.nombre: Bob Hay algunas cosas a tener en cuenta sobre esta salida: • Primero la asignación del nombre del Estudiante dentro del método init() también invoca el método setattr(). • En segundo lugar, la asignación a la variable de clase cuenta no invoca la método setattr(). • En tercer lugar el nombre del estudiante asignación hace de curso invocar el método setattr(). 28.8 Recursos en línea Para obtener más información sobre Monkey Patching, los siguientes recursos pueden ser de utilidad interés: • https://en.wikipedia.org/wiki/Monkey_patch Wikipedia página en Mono Parcheo. • http://net-informations.com/python/iq/patching.htm Una discusión sobre si Monkey Patching debe considerarse una buena o mala práctica. 28,9 Ejercicios Este ejercicio se centra en la búsqueda de atributos. Debe agregar un método a la clase Cuenta que se pueda usar para manejar cómo las cuentas deben comportarse cuando se intenta acceder a un atributo no definido. En este caso, debe registrar el intento de acceder al atributo (lo que significa imprimir muestra un mensaje de advertencia) y luego devuelve un valor predeterminado de −1. Por ejemplo, si tuviera la siguiente línea en su aplicación: print(‘acc1.branch:’, acc1.branch) 28.7 Interceptar Establecer un atributo 335
Entonces esto debería invocar el método getattr() para el indefinido rama de atributo. Imprime un mensaje de advertencia y luego devuelve el valor −1 que ser impreso por la declaración anterior. La salida de esta declaración debería ser algo como: getattr: atributo desconocido accedido - rama acc1.rama: -1 336 28 Monkey Patching y búsqueda de atributos
capitulo 29 Decoradores 29.1 Introducción La idea detrás de Decorators proviene del libro Gang of Four Design Patterns. (llamado así porque hubo cuatro personas involucradas en la definición de estos patrones de diseño). En En este libro se presentan numerosos patrones de diseño orientado a objetos que ocurren comúnmente. enviado Uno de estos patrones de diseño es el patrón de diseño Decorador. El patrón Decorator aborda la situación en la que es necesario agregar comportamiento adicional a objetos específicos. Una forma de agregar dicho comportamiento adicional es decorar los objetos creados con tipografías que aportan el extra de funcionalidad. Estos decoradores envuelven el elemento original pero presentan exactamente la misma interfaz para el usuario de ese elemento. Por lo tanto, el patrón Decorator Design extiende el comportamiento de un objeto sin utilizar subclases. Esta decoración de un objeto es transparente. a los clientes de los decoradores. En Python, los decoradores son funciones que toman otra función (u otra función invocable). objeto como un método) y devolver una tercera función que representa el decorado comportamiento. Este capítulo presenta a los decoradores, cómo se definen, cómo se usan y presenta decoradores incorporados. 29.2 ¿Qué son los decoradores? Un decorador es un fragmento de código que se utiliza para marcar un objeto invocable (como un función, método, clase u objeto) normalmente para mejorar o modificar su comportamiento (posiblemente reemplazándolo). Decora así el comportamiento original. Los decoradores son, de hecho, objetos invocables en sí mismos y, como tales, se comportan más como macros en otros idiomas que se pueden aplicar a objetos invocables que luego devuelven un nuevo objeto invocable (típicamente una nueva función). © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_29 337
La idea básica se ilustra en el siguiente diagrama: Este diagrama ilustra un decorador que envuelve un objeto invocable (en este caso, un función). Tenga en cuenta que el decorador presenta exactamente la misma interfaz para el usuario de el decorador como lo presentaría la función original; es decir, se necesita lo mismo parámetros y no devuelve nada (Ninguno) o algo. También debe tenerse en cuenta que el decorador también tiene la libertad de reemplazar completamente un objeto invocable en lugar de envolverlo; es una decisión de diseño tomada por la implementación mentor del decorador. 29.3 Definición de un decorador Para definir un decorador, debe definir un objeto invocable, como una función que toma otra función como parámetro y devuelve una nueva función. Se da un ejemplo de la definición de una función de decorador de registrador muy simple abajo. En este caso, el decorador del registrador envuelve la función original dentro de una nueva función, aquí llamada interior. Cuando se ejecuta esta función, se registra una declaración antes y después de ejecutar la función original. Cada función tiene un atributo name que proporciona el nombre de la función y esto se usa en la función interior () anterior para imprimir la función real a punto de ser invocado. registrador def (función): def interior(): imprimir(’llamando’, func.nombre) función() imprimir(’llamado’, func.nombre) volver interior 338 29 Decoradores
Tenga en cuenta que la función inner() está definida dentro de la función logger() (esto es totalmente legales). Luego se devuelve como resultado una referencia a la función inner() de la función registrador(). ¡La función inner() no se ejecuta en este punto! 29.4 Uso de decoradores Para ver cuál es el efecto de aplicar un decorador; es útil explorar los conceptos básicos enfoque (explícito) de su uso. Esto se puede hacer definiendo una función (llamaremos target) que imprime un mensaje simple: Podemos aplicar explícitamente el decorador del registrador a esta función pasando el referencia a la función de destino (sin invocarla), por ejemplo: Cuando ejecutamos este código, en realidad ejecutamos la función internal() que fue devuelto por el decorador. Esta función, a su vez, imprime un mensaje y luego llama la función pasó al registrador. Una vez que esta función pasada se ha ejecutado, imprime otro mensaje. El efecto de ejecutar la función t1() es este para llamar al función inner() que llama a la función objetivo, imprimiendo así: Esto ilustra lo que sucede cuando se ejecuta una función de estilo decorador. Python proporciona algo de azúcar sintáctico que permite la definición de la función y la asociación con el decorador que se declararán juntos usando la sintaxis ‘@’, Por ejemplo: objetivo definido(): print(‘En la función de destino’) t1 = registrador (objetivo) t1() objetivo de llamada En la función de destino llamado objetivo @registrador objetivo definido(): print(‘En la función de destino’) objetivo() 29.3 Definición de un decorador 339
Esto tiene el mismo efecto que pasar el objetivo a la función de registro; pero ilustra el papel del registrador de una manera bastante más pitónica. Es por tanto cuanto más uso común de decoradores. La salida de esta función es la misma que la versión anterior. 29.5 Funciones con parámetros Los decoradores se pueden aplicar a funciones que toman parámetros; sin embargo el decorador La función también debe tomar estos parámetros. Por ejemplo, si tiene una función como Entonces la función devuelta por el decorador también debe tomar dos parámetros, por ejemplo: 29.6 Decoradores apilados Los decoradores se pueden apilar; que es más de un decorador se puede aplicar a la mismo objeto invocable. Cuando esto ocurre, cada función se envuelve dentro de otra función; esta idea se ilustra con el siguiente código: @registrador def mi_func(x, y): imprimir (x, y) mi_func(4, 5) registrador def (función): def interior(x, y): print(’llamando’, func.nombre, ‘con’, x, ‘y’, y) función(x, y) print(‘retornado de’, func.nombre) volver interior 340 29 Decoradores
En este ejemplo, la función hola() está marcada con dos decoradores, @make_bold y @make_italic. Esto significa que la función hello() se pasa primero a make_italic() y envuelto por la función makeitalic_wrapped. Esta función es luego regresó del decorador make_italic. El makeitalic_wrapped luego se pasa a la función make_bold() que luego lo envuelve dentro de la función makebold_wrapped; que se devuelve por el decorador make_bold. Esto significa que la función invocada cuando se llama hello() es la función makebold_wrapped que llama a dos funciones adicionales como se muestra abajo:
Definir las funciones del decorador
def make_bold(fn): def makebold_wrapped(): devuelve “” + fn() + “” volver makebold_wrapped def hacer_cursiva(fn): def makeitalic_wrapped(): devuelve “” + fn() + “” volver makeitalic_wrapped
Aplicar decoradores a la función hola
@make_bold @make_italic definitivamente hola(): volver ‘hola mundo’
Función de llamada hola
imprimir (hola ()) 29.6 Decoradores apilados 341
El resultado final es que la cadena devuelta por la función pasada es primero envuelto por y (indicando cursiva) y luego por y (en negrita) en HTML. Por lo tanto, la salida de print(hello()) es: 29.7 Decoradores parametrizados Los decoradores también pueden tomar parámetros; sin embargo, la sintaxis para tales decoradores es una un poco diferente; esencialmente hay una capa adicional de direccionamiento indirecto. el decorador función toma uno o más parámetros y devuelve una función que puede utilizar el parámetro y toma el objeto invocable que se está ajustando. Por ejemplo: En este ejemplo, la función envuelta solo se llamará si el parámetro activo es verdad. Este es el valor predeterminado y, por lo tanto, para func1() no es necesario especificar el (aunque tenga en cuenta que ahora es necesario proporcionar los corchetes). Para func2(), el decorador @register se define con el conjunto de parámetros activo a Falso. Esto significa que la función contenedora no llamará a la función proporcionada. hola mundo def registro(activo=Verdadero): ajuste de definición (función): envoltura de definición (): print(‘Llamando’, func.name, ‘parámetro decorador ‘, activo) si está activo: función() print(‘Llamado’, func.nombre) demás: print(‘Omitido’, func.nombre) envoltorio de devolución envoltura de retorno @registro() def func1(): imprimir (‘func1’) @registrar(activo=Falso) def func2(): imprimir (‘func2’) función1() imprimir(’-’ * 10) función2() 342 29 Decoradores
Tenga en cuenta que el uso del decorador solo difiere en la necesidad de incluir la ronda corchetes incluso si no se especifican parámetros; aunque ahora hay dos funciones internas definidas dentro del decorador de registros. 29.8 Decoradores de métodos 29.8.1 Métodos sin parámetros También es posible decorar métodos y funciones (ya que también son invocables objetos). Sin embargo, es importante recordar que los métodos toman la especial parámetro self como el primer parámetro que se utiliza para hacer referencia al objeto que el se aplica el método. Por lo tanto, es necesario que el decorador tome esta parámetro en cuenta; es decir, la función envuelta interna debe tomar al menos una parámetro que representa a sí mismo: El decorador pretty_print define una función interna que toma como su primera (y en este caso solamente) parámetro la referencia al objeto (que por convención usa el auto parámetro). Esto luego se pasa al método real cuando se llama. El decorador pretty_print ahora se puede usar con cualquier método que solo toma el parámetro self, por ejemplo: En la clase anterior, el método get_fullname() está decorado con bonita_impresión. Si ahora llamamos a get_fullname() en un objeto, el resultado la cadena se envolverá en
y
(que es marcado HTML para un párrafo): def pretty_print(método): def method_wrapper(auto): devuelve “{0}
".format(método(self)) devolver método_envoltura Persona de clase: def init(yo, nombre, apellido, edad): self.nombre = nombre self.apellido = apellido self.edad = edad def print_self(auto): print(‘Persona - ‘, propio.nombre, ‘, ‘, propio.edad) @pretty_print def get_fullname(self): return self.nombre + " " + self.apellido 29.7 Decoradores parametrizados 343Esto genera la salida: 29.8.2 Métodos con parámetros Al igual que con las funciones, los métodos que toman parámetros además de los propios también pueden ser decorado. En este caso, la función devuelta por el decorador debe tomar no solo el autoparámetro sino también cualquier parámetro pasado al método. Por ejemplo: Ahora este decorador de trazas define una función interna que toma el parámetro self y dos parámetros adicionales. Se puede usar con cualquier método que también tome dos parámetros como el método move_to() a continuación: imprimir(‘Iniciando’) p = Persona(‘Juan’, ‘Smith’, 21) p.print_self() imprimir (p.get_fullname()) imprimir(‘Terminado’) A partir de Persona - Juan , 21
Juan Smith
Hecho def rastrear (método): def method_wrapper(self, x, y): print('Llamando', método, 'con', x, y) método(yo, x, y) imprimir ('Llamado', método, 'con', x, y) devolver método_envoltura punto de clase: def __init__(self, x, y): self.x = x self.y = y @rastro def move_to(self, x, y): self.x = x self.y = y def __str__(uno mismo): return 'Punto - ' + str(self.x) + ',' + str(self.y) 344 29 DecoradoresCuando se crea un objeto Point a continuación, podemos llamar al método move_to() y ver el resultado: La salida de esto es: 29,9 Decoradores de clase Además de poder decorar funciones y métodos; es posible decorar clases Una clase se puede decorar para agregar la funcionalidad requerida que puede ser externa a esa clase. Como ejemplo, una operación común a nivel de clase es querer indicar que una clase debe implementar el patrón de diseño singleton. El patrón de diseño Singleton (de nuevo del libro Gang of Four Design Patterns) describe un tipo que solo puede tener un objeto construido para ello. Es decir, a diferencia de otros objetos, no debería ser posible para obtener más de una instancia dentro del mismo programa. Así, el Singleton El patrón de diseño garantiza que solo se cree una instancia de una clase. Todos los objetos que use una instancia de ese tipo use la misma instancia. Podemos definir un decorador que implemente el patrón de diseño singleton, por ejemplo: p = Punto(1, 1) imprimir (pag) p.mover_a(5, 5) imprimir (pag) Punto - 1,1 Llamar a <function Point.move_to at 0x110288b70> con 5 5 Llamado <function Point.move_to at 0x110288b70> con 5 5 Punto - 5,5 def singleton(cls): print(‘En singleton para: ‘, cls) instancia = Ninguno def obtener_instancia(): instancia no local si la instancia es Ninguna: instancia = cls() instancia de retorno devolver get_instance 29.8 Decoradores de métodos 345
Este decorador devuelve la función get_instance(). Esta función verifica ver si la instancia de la variable está establecida en Ninguno o no; si se establece en Ninguno, instancia la clase pasó al decorador y la almacena en la variable de instancia. entonces devuelve la instancia. Si la instancia ya está configurada, simplemente devuelve la instancia. Podemos aplicar este decorador a clases completas como Service y Foo a continuación: Ahora podemos usar las clases Service y Foo normalmente; sin embargo, solo una instancia of Service y una instancia de Foo se crearán alguna vez en el mismo programa: En el fragmento de código anterior, parece que hemos creado dos nuevos servicios objetos y dos objetos Foo; sin embargo, el decorador @singleton restringirá el número de instancias creadas a una y reutilizará esa instancia cada vez que una solicitud se hace para instanciar la clase dada. Por lo tanto, cuando ejecutamos este ejemplo, podemos ver que el número hexadecimal que representa la ubicación del objeto en la memoria es lo mismo para los dos objetos Servicio y lo mismo para los dos objetos Foo: @único servicio de clase (objeto): def print_it(self): imprimir (uno mismo) @único clase Foo(objeto): aprobar imprimir(‘Iniciando’) s1 = Servicio() imprimir(s1) s2 = Servicio() imprimir (s2) f1 = foo() imprimir (f1) f2 = foo() imprimir (f2) imprimir(‘Terminado’) En singleton para: <clase ‘main.Service’> En singleton para: <clase ‘main.Foo’> A partir de <principal.Objeto de servicio en 0x10ac3f780> <principal.Objeto de servicio en 0x10ac3f780> <main.Objeto Foo en 0x10ac3f7b8> <main.Objeto Foo en 0x10ac3f7b8> Hecho 346 29 Decoradores
29.10 ¿Cuándo se ejecuta un decorador? Una característica importante de los decoradores es que se ejecutan justo después del decorador. se define la función. Esto suele ser en el momento de la importación (es decir, cuando un módulo es cargado por Pitón). Por ejemplo, el registrador decorador que se muestra arriba, imprime ‘In Logger’ y ‘Registrador finalizado’ cuando se ejecuta. Si se examina la salida, se puede ver que esta salida se produce antes de que el programa imprima ‘Inicio’. Tenga en cuenta que la función decorada y la función envuelta solo se ejecutan cuando se invocan explícitamente. Esto destaca la diferencia entre lo que los Pythonistas llaman tiempo de importación y tiempo de ejecución registrador def (función): imprimir (‘En el registrador’) def interior(): print(‘En llamado interno’, func.name) función() print(‘En interior llamado’, func.name) print(‘Finishing Logger’) volver interior @registrador def imprime_es(): imprimir(‘Imprimirlo’) imprimir(‘Inicio’) Imprímelo() imprimir(‘Terminado’) en el registrador Registrador de acabado Comenzar En llamada interna print_it Imprímelo En interior llamado print_it Hecho 29.10 ¿Cuándo se ejecuta un decorador? 347
29.11 Decoradores incorporados Hay numerosos decoradores integrados en Python 3; algunos de los cuales ya tenemos visto como @classmethod, @staticmethod y @property. también vimos algunos decoradores cuando hablan de métodos y propiedades abstractas. también hay decoradores asociados con pruebas unitarias y operaciones asincrónicas. 29.12 Ajuste de herramientas de función Un problema con las funciones decoradas puede volverse evidente al depurar o tratando de rastrear lo que está sucediendo. El problema es que por defecto los atributos asociados con la función que se llama son en realidad los de la función interna devuelto por la función decoradora. Ese es el nombre, doc y módulo del función son los de la función devuelta por el decorador. El nombre y el documento umentación de la función original, decorada, se han perdido. Por ejemplo, volviendo al decorador registrador original tenemos: Cuando ejecutamos este código obtenemos: volver interior @registrador def obtener_texto(nombre): “““devuelve un texto””” volver “Hola “+nombre imprimir(’nombre:’, obtener_texto.nombre) imprimir(‘doc: ‘, obtener_texto.doc) print(‘módulo; ‘, obtener_texto.módulo) registrador def (función): def interior(): imprimir(’llamando’, func.nombre) función() imprimir(’llamado’, func.nombre) nombre: interior documento: Ninguno módulo; principal 348 29 Decoradores
Parece que la función get_text se llama interna y no tiene cadena de documentación asociado a ello. Sin embargo, si observamos la función, debería llamarse get_text() y tiene una cadena de documentación de ‘devuelve algo de texto’. Python (desde la versión 2.5) ha incluido el módulo functools que contiene el decorador functools.wraps que se puede utilizar para solucionar este problema. Wraps es un decorador para actualizar los atributos de la función de envoltura (interior) a los de la función original (en este caso get_text()). Esto es tan simple como decorando la función ‘interior’ con @wraps(func). El resultado final es que en el ejemplo anterior, el nombre y el documento ahora se actualizan al nombre de la función envuelta y la documentación asociada con eso función. Si ahora volvemos a ejecutar el ejemplo anterior, obtenemos: 29.13 Recursos en línea Para más información sobre decoradores ver: • https://www.python-course.eu/python3_decorators.php una breve introducción a Decoradores de pitón • https://www.python.org/dev/peps/pep-0318/ PEP 318 considerando decoradores para funciones y métodos. • https://www.python.org/dev/peps/pep-3129/ ENERGÍA 3129 introduciendo clase decoradores • https://docs.python.org/3.7/library/functools.html Pitón Estándar Biblioteca documentación para funciones. • https://pymotw.com/3/functools/index.html Módulo Python de la semana para funciones desde functools importar envolturas registrador def (función): @wraps(función) def interior(): imprimir(’llamando’, func.nombre) función() imprimir(’llamado’, func.nombre) volver interior nombre: obtener_texto doc: devuelve un texto módulo; principal 29.12 Ajuste de herramientas de función 349
• https://github.com/lord63/awesome-python-decorator Una página útil que enumera muchos decoradores de Python, así como contribuciones de terceros. • https://wiki.python.org/moin/PythonDecoratorLibrary que proporciona una repositorio de ejemplos de decoradores. 29.14 Referencia del libro Para obtener más información sobre los patrones de diseño Decorator y Singleton, consulte el Libro “Patterns” de Gang of Four (E. Gamma, R. Helm, R. Johnson y J. Vlissades, Patrones de diseño: Elementos de software orientado a objetos reutilizable, Addison-Wesley, 1995). 29.15 Ejercicios El objetivo de este ejercicio es desarrollar tu propio decorador. Escribirá un decorador de temporizador para usar con métodos en una clase que toman el primer parámetro self, seguido de otro parámetro. El decorador debe registrar cuánto tarda en ejecutarse un método. Para hacer esto, puede usar el módulo de tiempo e importar el archivo default_timer. A continuación, puede obtener un objeto default_timer para el inicio y el final de un llame a la función y utilice estos valores para generar el tiempo necesario, por ejemplo: Luego puede aplicar el decorador al depósito () y retirar () métodos definidos en la clase Cuenta. Por ejemplo, desde timeit import default_timer inicio = temporizador_predeterminado() func(auto, valor) fin = temporizador_predeterminado() print(‘regresó de’, func, ’tomó’, end - start, ‘segundos’) @Temporizador depósito def (auto, monto): self._saldo += cantidad @Temporizador def retirar(auto, cantidad): self._saldo -= cantidad 350 29 Decoradores
Estos métodos voluntad ser heredado por el Cuenta de depósito y Clases de cuenta de inversión. En la clase CurrentAccount el retiro el método está sobreescrito, por lo que también deberá decorar ese método con @timer también. Ahora, cuando ejecuta su aplicación de muestra, debe obtener información de tiempo impreso para los métodos de depósito y retiro: llamando al depósito en Cuenta[123] - John, cuenta corriente = 10.05 límite de sobregiro: -100.0 con 23.45 devuelto del depósito tardó 8.009999999999268e-07 segundos llamando a retirar en Cuenta[123] - John, cuenta corriente = 33.5límite de sobregiro: -100.0 con 12.33 regresó de retirar tomó 1.141999999999116e-06 segundos 29.15 Ejercicios 351
capitulo 30 Iterables, Iteradores, Generadores y corrutinas 30.1 Introducción Hay dos protocolos que es muy probable que utilice, o que posiblemente necesite implementar en algún momento u otro; estos son el protocolo iterable y el iterador protocolos Estos dos protocolos estrechamente relacionados son muy utilizados y respaldados por un gran cantidad de tipos. Una de las razones por las que se importan iteradores e iterables es que se pueden usar con para declaraciones en Python; esto hace que sea muy fácil integrar un iterable en el código que necesita procesar una secuencia de valores a su vez. Dos iteraciones más como Python Las características son generadores y corrutinas que se analizan al final de este capítulo. 30.2 Iteración 30.2.1 Iterables El protocolo Iterable es utilizado por tipos donde es posible procesar sus contenidos uno a la vez por turno. Un iterable es algo que proporcionará un iterador que puede ser utilizado para realizar este procesamiento. Como tal, no es el propio iterador; pero el proveedor del iterador. Hay muchos tipos iterables en Python, incluidas listas, conjuntos, diccionarios, tuplas, etc. Todos estos son contenedores iterables que proporcionarán un iterador. © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_30 353
Ser un tipo iterable; es necesario implementar el método iter() (que es el único método en el protocolo Iterable). Este método debe proporcionar una referencia al objeto iterador. Esta referencia podría ser al tipo de datos en sí mismo o podría ser a otro tipo que implemente el protocolo iterador. 30.2.2 iteradores Un iterador es un objeto que devolverá una secuencia de valores. Los iteradores pueden ser finitos de longitud o infinito (aunque muchos iteradores orientados a contenedores proporcionan un conjunto fijo de valores). El protocolo iterador especifica el método next(). Este método es se espera que devuelva el siguiente elemento en la secuencia para devolver o para elevar el Excepción StopIteration. Esto se usa para indicar que el iterador ha terminado suministrando valores. 30.2.3 Los métodos relacionados con la iteración Para resumir entonces tenemos • iter() del protocolo Iterable que se usa para devolver el iterador objeto, • next() del protocolo Iterator que se utiliza para obtener el siguiente valor en una secuencia de valores. Cualquier tipo de datos puede ser iterable e iterador; pero eso no es obligatorio. Un Iterable podría devolver un objeto diferente que se usará para implementar el iterador o puede regresar a sí mismo como el iterador, es la elección de los diseñadores. 30.2.4 La clase de pares iterables Para ilustrar las ideas detrás de iterables e iteradores implementaremos un simple clase; esta clase será una clase Evens que se utiliza para proporcionar un conjunto de valores pares de 0 a algún límite. Esto ilustra que no sólo los contenedores de datos pueden ser iterables/iteradores. También ilustra un tipo que es tanto iterable como iterador. 354 30 Iterables, Iteradores, Generadores y Corrutinas
Hay algunas cosas a tener en cuenta sobre esta clase. • El método iter() devuelve self; este es un patrón muy común y asume que la clase también implementa el protocolo iterador • El método next() devuelve el siguiente valor en la secuencia o genera la excepción StopIteration para indicar que no hay más valores disponible. 30.2.5 Uso de la clase Evens con un bucle for Ahora que hemos implementado los protocolos iterable e iterador para la clase Incluso podemos usarlo con una declaración for: Lo que genera la salida:
Hace que esta clase sea iterable
def iter(uno mismo): regresar a sí mismo
Hace que esta clase sea un iterador
def siguiente(uno mismo): si self.val > self.limit: subir StopIteration demás: return_val = self.val self.val += 2 volver return_val clase Evens(objeto): def init(auto, límite): self.limit = límite self.val = 0 imprimir(‘Inicio’) para i en pares (6): imprimir(i, fin=’, ‘) imprimir(‘Terminado’) Comenzar 0, 2, 4, 6, Listo 30.2 Iteración 355
Esto hace que parezca que el tipo Evens es un tipo incorporado, ya que se puede usar con una estructura Python existente; sin embargo, el bucle for simplemente espera recibir una iterable; como tal, Evens es compatible con el bucle for. 30.3 El Módulo Itertools El módulo itertools proporciona una serie de funciones útiles que devuelven adores construidos de varias maneras. Se puede utilizar para proporcionar un iterador sobre un selección de valores de un tipo de datos que es iterable; se puede usar para combinar iterables juntos, etc. 30.4 Generadores En muchos casos no es apropiado (o posible) obtener todos los datos a ser pro- cessed por adelantado (por razones de rendimiento, por razones de memoria, etc.). en cambio perezosamente crear los datos para ser iterados sobre la base de algún conjunto de datos subyacente, puede ser más adecuado. Los generadores son una función especial que se puede utilizar para generar una secuencia de valores que se repetirán a pedido (es decir, cuando se necesitan los valores) en lugar de que el producido por adelantado. Lo único que hace que un generador sea una función de generador es el uso de la palabra clave yield (que se introdujo en Python 2.3). La palabra clave yield solo se puede usar dentro de una función o un método. sobre su ejecución, la función se suspende y el valor de la declaración de rendimiento es devuelto como el valor del ciclo actual. Si esto se usa con un ciclo for, entonces el ciclo se ejecuta una vez para este valor. La ejecución de la función del generador se reanuda después de el bucle ha realizado un ciclo una vez y se obtiene el valor del siguiente ciclo. La función del generador seguirá proporcionando valores hasta que regrese (lo que significa que se puede generar una secuencia infinita de valores). 30.4.1 Definición de una función de generador A continuación se muestra un ejemplo muy simple de una función generadora. Esta función es llamó a la función gen_numbers(): def gen_numbers(): rendimiento 1 rendimiento 2 rendimiento 3 356 30 Iterables, Iteradores, Generadores y Corrutinas
Esta es una función generadora ya que tiene al menos una instrucción de rendimiento (de hecho, tiene tres). Cada vez que se llama a la función gen_numbers() dentro de una instrucción for devolverá uno de los valores asociados con una declaración de rendimiento; en este caso el valor 1, luego el valor 2 y finalmente el valor 3 antes de que regrese (termina). 30.4.2 Usar una función de generador en un bucle for Podemos usar la función gen_numbers() con una instrucción for como se muestra a continuación: Lo que produce 1, 2 y 3 como salida. Es común que el cuerpo de un generador tenga algún tipo de bucle. Este loop se usa típicamente para generar los valores que se generarán. Sin embargo, como es como se muestra arriba, eso no es necesario y aquí se repite una declaración de rendimiento tres veces. Tenga en cuenta que gen_numbers() es una función pero es una función especial ya que devuelve un objeto generador. Esta es una función generadora que devuelve un objeto generador que envuelve el generación de los valores requeridos pero esto está oculto para el desarrollador. 30.4.3 ¿Cuándo se ejecutan las declaraciones de rendimiento? Es interesante considerar lo que sucede dentro de la función generadora; en realidad es suspende cada vez que una declaración de rendimiento proporciona un valor y solo se reanuda cuando se recibe la siguiente solicitud de valor. Esto se puede ver agregando algunos imprimir sentencias a la función gen_numbers(): para i en gen_numbers(): imprimir (yo) def gen_numbers2(): imprimir(‘Inicio’) rendimiento 1 imprimir(‘Continuar’) rendimiento 2 imprimir(‘Final’) rendimiento 3 imprimir(‘Fin’) para i en gen_numbers(): imprimir (yo) 30.4 Generadores 357
Cuando ejecutamos este fragmento de código, obtenemos Por lo tanto, el generador ejecuta las declaraciones de rendimiento según sea necesario y no de repente. 30.4.4 Un generador de números pares Podríamos haber usado un generador para producir un conjunto de números pares hasta un límite específico, como hicimos antes con la clase Evens, pero sin la necesidad de crear una clase (y implementar los dos métodos especiales iter() y next()). Por ejemplo: esto produce Esto ilustra el beneficio potencial de un generador sobre un iterador; el La función evens_up_to() es mucho más simple y concisa que la iterable Evens clase. 30.4.5 Funciones del generador de anidamiento Incluso puede anidar funciones de generador ya que cada llamada a la función de generador es encapsulado en su propio objeto generador que captura toda la información de estado que necesita esa invocación del generador. Por ejemplo: Comenzar 1 Continuar 2 Final 3 Fin def evens_up_to(límite): valor = 0 while valor <= límite: valor de rendimiento valor += 2 para i en evens_up_to(6): imprimir(i, fin=’, ‘) 0, 2, 4, 6, 358 30 Iterables, Iteradores, Generadores y Corrutinas
Que genera: Como puede ver en esto, la variable de bucle i está vinculada a los valores producidos por la primera llamada a evens_up_to() (que produce una secuencia de hasta 4) mientras que la variable de bucle j está vinculada a los valores producidos por la segunda llamada a evens_up_to() (que produce una secuencia de valores hasta 6). 30.4.6 Uso de generadores fuera de un bucle for No necesita un bucle for para trabajar con una función de generador; el objeto generador realmente devuelto por la función generadora admite la función next(). Este función toma un objeto generador (devuelto de la función generador) y devuelve el siguiente valor en secuencia. esto produce Las llamadas posteriores a next(evens) no devuelven ningún valor; si es necesario el generador puede arrojar un error/excepción. para i en evens_up_to(4): imprimir(‘i:’, i) para j en evens_up_to(6): imprimir(‘j:’, j, fin=’, ‘) imprimir(’’) pares = pares_hasta_hasta(4) imprimir(siguiente(pares), fin=’, ‘) imprimir(siguiente(pares), fin=’, ‘) imprimir (siguiente (pares)) 0, 2, 4 yo: 0 j: 0, j: 2, j: 4, j: 6, yo: 2 j: 0, j: 2, j: 4, j: 6, yo: 4 j: 0, j: 2, j: 4, j: 6, 30.4 Generadores 359
30.5 corrutinas Las corrutinas se introdujeron en Python 2.5, pero todavía se malinterpretan en gran medida. Gran parte de la documentación introduce Coroutines diciendo que son similares a Generadores, sin embargo hay una diferencia fundamental entre Generadores y Corrutinas: • los generadores son productores de datos, • las corrutinas son consumidoras de datos. Es decir, las corrutinas consumen datos producidos por otra cosa; donde como un gene- erator produce una secuencia de valores que otra cosa puede procesar. La función send() se usa para enviar valores a una rutina. Estos elementos de datos son disponible dentro de la corrutina; que esperará a que se le suministren valores. Cuando se proporciona un valor, se puede desencadenar algún comportamiento. Así, cuando un coroutine consume un valor que activa algún comportamiento para ser procesado. Parte de la confusión entre generadores y corrutinas es que el rendimiento la palabra clave se reutiliza dentro de una rutina; se usa dentro de una corrutina para causar el coroutine para esperar hasta que se haya enviado un valor. A continuación, proporcionará este valor a la corrutina También es necesario preparar una Rutina usando next() o send(Ninguno) funciones Esto avanza el Coroutine a la llamada para ceder donde luego esperará hasta que se le envíe un valor. Una corrutina puede continuar para siempre a menos que se le envíe close(). Es posible recoger la corrutina que se está cerrando capturando la excepción GeneratorExit ción; luego puede desencadenar algún comportamiento de apagado si es necesario. La siguiente función grep() proporciona un ejemplo de rutina: Esta rutina esperará los datos de entrada; cuando los datos se envían a la rutina, entonces esos datos se asignarán a la variable de línea. A continuación, comprobará si el el patrón utilizado para inicializar la función coroutine está presente en la línea; si lo es imprimir la línea; luego hará un bucle y esperará a que se envíe el siguiente elemento de datos al corrutina Si mientras está esperando la corrutina está cerrada, entonces atrapará el Excepción GeneratorExit e imprime un mensaje adecuado. def grep(patrón): print(‘Buscando’, patrón) intentar: mientras que es cierto: línea = (rendimiento) si patrón en línea: imprimir (línea) excepto GeneratorExit: print(‘Saliendo de la Co-rutina’) 360 30 Iterables, Iteradores, Generadores y Corrutinas
La corrutina grep() se usa a continuación, observe que la función corrutina devuelve un objeto coroutine que se puede utilizar para enviar datos: La salida de esto es: 30.6 Recursos en línea Consulte lo siguiente para obtener más información. • https://docs.python.org/3/library/stdtypes.html#iterator-types para tipos de iteradores. 30.7 Ejercicios Estos ejercicios se centran en la creación de un generador. Escribe un generador de números primos; puedes usar el programa de números primos que escribió anteriormente en el libro pero convertirlo en un generador. El generador debe tomar una limit para dar el tamaño máximo del ciclo que usa para generar los números primos. Podrías llamar a este prime_number_generator(). Debería poder ejecutar el siguiente código: imprimir(‘Iniciando’)
Inicializar la corrutina
g = grep(‘Pitón’)
cebar la rutina
siguiente
Enviar datos a la corrutina
g.send(‘Java mola’) g.send(‘C++ es genial’) g.send(‘Python mola’)
ahora cierra la rutina
g.cerrar() imprimir(‘Terminado’) A partir de buscando pitón pitón es genial Salir de la Co-rutina Hecho 30.5 corrutinas 361
Ahora crea el infinite_prime_number_generator(); este generador no tiene límite y seguirá generando números primos hasta que ya no se use. Debería poder usar este generador de números primos de la siguiente manera: número = entrada(‘Por favor ingrese el número:’) si número.esnumérico(): numero = int(numero) si número <= 2: print(‘El número debe ser mayor que 2’) demás: para primo en prime_number_generator(num): imprimir (principal, final = ‘, ‘) demás: print(‘Debe ser un entero positivo’) Si el usuario ingresa 27, la salida sería: Por favor ingrese el número: 27 2, 3, 5, 7, 11, 13, 17, 19, 23, primo = infinite_prime_number_generator() imprimir (siguiente (principal)) imprimir (siguiente (principal)) imprimir (siguiente (principal)) imprimir (siguiente (principal)) imprimir (siguiente (principal)) 362 30 Iterables, Iteradores, Generadores y Corrutinas
capitulo 31 Colecciones, Tuplas y Listas 31.1 Introducción Anteriormente en este libro vimos algunos tipos integrados de Python como string, int y flotar así como bools. Estos no son los únicos tipos incorporados en Python; otro grupo de tipos incorporados se conocen colectivamente como tipos de colección. Esto se debe a que ellos representar una colección de otros tipos (como una colección de cadenas o enteros). Una colección es un único objeto que representa un grupo de objetos (como una lista o diccionario). Las colecciones también pueden denominarse contenedores (ya que contienen otros objetos). Estas clases de colección se utilizan a menudo como base para proyectos más complejos o estructuras de datos y tipos de datos específicos de la aplicación. Estos tipos de colecciones admiten varios tipos de estructuras de datos (como listas y mapas) y formas de procesar elementos dentro de esas estructuras. Este capítulo introduce los tipos de colección de Python. 31.2 Tipos de colección de Python Hay cuatro clases en Python que proporcionan un comportamiento similar al de un contenedor; eso es datos tipos para la celebración de colecciones de otros objetos, estos son • Tuplas Una Tupla representa una colección de objetos que están ordenados e inmu- tabla (no se puede modificar). Las tuplas permiten miembros duplicados y están indexadas. • Listas Las listas contienen una colección de objetos ordenados y mutables (cambio- able), están indexados y permiten miembros duplicados. • Conjuntos Los conjuntos son una colección desordenada y sin indexar. son mutables (modificable) pero no permite que se mantengan valores duplicados. • Diccionario Un diccionario es una colección desordenada que está indexada por una clave que hace referencia a un valor. El valor se devuelve cuando se proporciona la clave. No © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_31 363
Se permiten claves duplicadas. Se permiten valores duplicados. Los diccionarios son contenedores mutables. Recuerde que todo en Python es en realidad un tipo de objeto, los números enteros son instancias/objetos del tipo int, las cadenas son instancias del tipo cadena, etc. Por lo tanto, los tipos de contenedores como Set pueden contener colecciones de cualquier tipo de cosa en Python. 31.3 tuplas Las tuplas, junto con las listas, son probablemente uno de los tipos de contenedores más utilizados de Python. Estarán presentes en casi cualquier programa Python no trivial. Las tuplas son una colección ordenada e inmutable de objetos; que es cada elemento en un tuple tiene una posición específica (su índice) y esa posición no cambia con el tiempo. De hecho, no es posible agregar o eliminar elementos de la tupla una vez que se ha creado. 31.3.1 Crear tuplas Las tuplas se definen usando paréntesis (es decir, corchetes ‘()’) alrededor de los elementos que componen la tupla, por ejemplo: Esto define una nueva Tupla a la que hace referencia la variable tup1. El Tuple contiene exactamente 4 elementos (en este caso enteros) con el primer elemento en la tupla (el entero 1) que tiene el índice 0 y el último elemento en la Tupla (el entero 7) que tiene el índice 3. Esto se ilustra a continuación: 31.3.2 La función constructora tuple() La función tuple() también se puede usar para crear una nueva tupla a partir de un iterable. Un iterable es algo que implementa el protocolo iterable (ver el último capítulo). tu1 = (1, 3, 5, 7) 364 31 Colecciones, Tuplas y Listas
Esto significa que se puede crear una nueva tupla a partir de un Conjunto, una Lista, un Diccionario (ya que todos estos son tipos iterables) o cualquier tipo que implemente el protocolo iterable. La sintaxis de la función tuple() es: Por ejemplo: que genera la salida: Tenga en cuenta que en lo anterior, los corchetes se utilizan para representar una lista de cosas (el tipo de contenedor Lista se describe con más detalle más adelante en este capítulo). 31.3.3 Acceso a elementos de una tupla Se puede acceder a los elementos de una Tupla usando un índice entre corchetes. El index devuelve el objeto en esa posición, por ejemplo: que genera la salida 31.3.4 Creación de nuevas tuplas a partir de tuplas existentes También es posible devolver lo que se conoce como un segmento de una Tupla. esto es nuevo Tupla que se compone de un subconjunto de la Tupla original. Esto se hace por tupla (iterable) lista1 = [1, 2, 3] t1 = tupla(lista1) imprimir (t1) (1, 2, 3) imprimir(’tup1[0]:\t’, tup1[0]) imprimir(’tup1[1]:\t’, tup1[1]) imprimir(’tup1[2]:\t’, tup1[2]) imprimir(’tup1[3]:\t’, tup1[3]) tup1[0]: 1 tup1[1]: 3 tup1[2]: 5 tup1[3]: 7 31.3 tuplas 365
proporcionando los índices de inicio y fin para el segmento, separados por dos puntos, dentro del corchetes de índice. Por ejemplo: Que devuelve una nueva Tupla de dos elementos que contiene los elementos de índice 1 hasta (pero sin incluir) el elemento 3. Tenga en cuenta que la Tupla original no es afectado de alguna manera (recuerde que es inmutable, por lo que no se puede modificar). la salida de lo anterior es así: De hecho, existen numerosas variaciones en el uso de los índices de corte. Para ejemplo, si se omite el primer índice, indica que el segmento debe comenzar desde el principio de la tupla, mientras que omitir el último índice indica que debe ir al final de la Tupla. que genera: Puede invertir una Tupla usando la notación :: − 1 (de nuevo, esto devuelve una nueva Tupla y no tiene efecto sobre la Tupla original): Esto produce así: 31.3.5 Las tuplas pueden contener diferentes tipos Las tuplas también pueden contener una mezcla de diferentes tipos; es decir, no están restringidos a sosteniendo elementos todos del mismo tipo. Por lo tanto, puede escribir una Tupla como: imprimir(’tup1[1:3]:\t’, tup1[1:3]) tuup1[1:3]: (3, 5) imprimir(’tup1[:3]:\t’, tup1[:3]) imprimir(’tup1[1:]:\t’, tup1[1:]) tup1[:3]: (1, 3, 5) tup1[1:]: (3, 5, 7) imprimir(’tup1[::-1]:\t’, tup1[::-1]) tup1[::-1]: (7, 5, 3, 1) tup2 = (1, ‘Juan’, Persona(‘Phoebe’, 21), Verdadero, -23.45) imprimir (tup2) 366 31 Colecciones, Tuplas y Listas
Lo que produce la salida: 31.3.6 Iterando sobre tuplas Puede iterar sobre el contenido de una Tupla (es decir, procesar cada elemento en el Tupla a su vez). Esto se hace usando el bucle for que ya hemos visto; sin embargo, es la Tupla que se utiliza para el valor al que se aplicará la variable de bucle: Esto imprime cada uno de los elementos en la Tuple tup3 a su vez: Nótese que de nuevo se mantiene el orden de los elementos en la Tupla. 31.3.7 Funciones relacionadas con tuplas También puede averiguar la longitud de una Tupla Puede contar cuántas veces aparece un valor específico en una Tupla (recuerde Las tuplas permiten duplicados); También puede averiguar el (primer) índice de un valor en una Tupla: tup3 = (‘manzana’, ‘pera’, ’naranja’, ‘ciruela’, ‘manzana’) para x en tup3: imprimir (x) manzana pera naranja ciruela manzana (1, ‘Juan’, <principal. Objeto persona en 0x105785080>, Verdadero, -23.45) imprimir(’len(tup3):\t’, len(tup3)) print(tup3.count(‘manzana’)) # devuelve 2 print(tup3.index(‘pera’)) # devuelve 1 31.3 tuplas 367
Tenga en cuenta que tanto index() como count() son métodos definidos en la clase Tuple donde como len() es una función a la que se pasa la tupla. Esto se debe a que len() es un Función genérica y se puede usar con otros tipos, como cadenas. 31.3.8 Comprobar si existe un elemento Puede verificar si existe un elemento específico en una Tupla usando el operador in, Por ejemplo: 31.3.9 Tuplas anidadas Las tuplas se pueden anidar dentro de las tuplas; que es una Tupla puede contener, como una de sus elementos, otra Tupla. Por ejemplo, el siguiente diagrama ilustra el anidamiento de un árbol de Tuplas: En código podríamos definir esta estructura como: La salida de esto es: si ’naranja’ en tup3: print(’naranja está en la Tupla’) tupla1 = (1, 3, 5, 7) tupla2 = (‘Juan’, ‘Denise’, ‘Phoebe’, ‘Adán’) tupla3 = (42, tupla1, tupla2, 5.5) imprimir (tupla3) (42, (1, 3, 5, 7), (‘Juan’, ‘Denise’, ‘Phoebe’, ‘Adán’), 5.5) 368 31 Colecciones, Tuplas y Listas
Tenga en cuenta el anidamiento de corchetes redondos en la impresión que ilustra dónde está una Tupla contenida dentro de otra. Esta característica de Tuples (y otros contenedores) permite datos arbitrariamente complejos estructuras que se construirán según lo requiera una aplicación. De hecho, una Tupla puede haber anidado dentro de ella no solo otras Tuplas sino cualquier tipo de contenedor, y por lo tanto puede contener Listas, Conjuntos, Diccionarios, etc. Esto proporciona un gran nivel de flexibilidad al construir estructuras de datos para usar en Python programas 31.3.10 Cosas que no puedes hacer con las tuplas No es posible agregar o quitar elementos de una Tupla; son inmutables. Él Cabe señalar en particular que ninguna de las funciones o métodos presentados anteriormente cambio real de la tupla original a la que se aplican; incluso aquellos que devuelven un subconjunto de la Tupla original en realidad devuelve una nueva instancia de la clase Tupla y no tiene efecto sobre la Tupla original. 31.4 Liza Las listas son contenedores ordenados mutables de otros objetos. Son compatibles con todas las características. de la Tupla pero como son mutables también es posible agregar elementos a una Lista, eliminar elementos y modificar elementos. Los elementos de la lista mantienen su orden (hasta que se modifique). 31.4.1 Creación de listas Las listas se crean usando corchetes colocados alrededor de los elementos que componen la lista. Por ejemplo: En este caso hemos creado una lista de cuatro elementos con el primer elemento siendo indexado desde cero, tenemos así: lista1 = [‘Juan’, ‘Pablo’, ‘Jorge’, ‘Ringo’] 31.3 tuplas 369
Al igual que con las Tuplas, podemos tener listas anidadas y listas que contengan diferentes tipos de elementos. Así podemos crear la siguiente estructura de Listas anidadas: En código esto se puede definir como: Cuando se imprime root_list, obtenemos Tenga en cuenta los corchetes dentro de los corchetes exteriores que indican listas anidadas. Por supuesto, también podemos anidar tuplas en listas y listas en tuplas. por ejemplo, el La siguiente estructura muestra que las tuplas (los óvalos) contienen referencias a las listas (los rectángulos). gles) y viceversa: l1 = [1, 43.5, Persona(‘Phoebe’, 21), Verdadero] l2 = [‘manzana’, ’naranja’, 31] root_list = [‘Juan’, l1, l2, ‘Denise’] imprimir (lista_raíz) [‘Juan’, [1, 43.5, <tuplas. Objeto persona en 0x1042ba4a8>, Verdadero], [‘manzana’, ’naranja’, 31], ‘Denise’] 370 31 Colecciones, Tuplas y Listas
En código esto se vería así: que produce 31.4.2 Función de constructor de lista La función list() se puede usar para construir una lista a partir de un iterable; esto significa que puede construir una lista a partir de una Tupla, un Diccionario o un Conjunto. También puede construya una lista de cualquier cosa que implemente el protocolo iterable. La firma de la función list() es Por ejemplo: t1 = (1, ‘Juan’, 34,5) l1 = [‘Smith’, ‘Jones’] l2 = [t1, l1] t2 = (l2, ‘manzana’) imprimir (t2) ([(1, ‘Juan’, 34.5), [‘Smith’, ‘Jones’]], ‘manzana’) lista (iterable) VocalTupla = (‘a’, ’e’, ’i’, ‘o’, ‘u’) imprimir (lista (tupla de vocales)) 31.4 Liza 371
produce 31.4.3 Acceso a elementos de una lista Puede acceder a los elementos de una lista usando un índice (entre corchetes). El index devuelve el objeto en esa posición, por ejemplo: Esto imprimirá el elemento en el índice 1 que es Paul (las listas están indexadas desde Cero, por lo que el primer elemento es el elemento cero). Si usa un índice negativo como −1, entonces el índice se invierte, por lo que un índice de −1 comienza desde el final de la lista (−1 devuelve el último elemento, −2 el penúltimo etc.). También es posible extraer un segmento (o sublista) de una lista. Esto se hace por proporcionando un índice inicial y final dentro de los corchetes separados por un colon. Por ejemplo, [1:4] indica un segmento que comienza en el elemento uno y extendiéndose hasta (pero sin incluir) el cuarto elemento. Si cualquiera de los índices es perdido por un segmento entonces eso indica el inicio o el final de la lista respectiva. A continuación se ilustran algunas de estas ideas: Que produce: lista1 = [‘Juan’, ‘Pablo’, ‘Jorge’, ‘Ringo’] imprimir (lista1 [1]) [‘a’, ’e’, ’i’, ‘o’, ‘u’] lista1 = [‘Juan’, ‘Pablo’, ‘Jorge’, ‘Ringo’] imprimir(’lista1[1]:’, lista1[1]) imprimir(’lista1[-1]:’, lista1[-1]) imprimir(’lista1[1:3]:’, lista1[1:3]) imprimir(’lista[:3]:’, lista1[:3]) imprimir(’lista[1:]:’, lista1[1:]) lista1[1]: Pablo lista1[-1]: Ringo list1[1:3]: [‘Pablo’, ‘Jorge’] list[:3]: [‘Juan’, ‘Pablo’, ‘Jorge’] lista[1:]: [‘Pablo’, ‘Jorge’, ‘Ringo’] 372 31 Colecciones, Tuplas y Listas
31.4.4
Agregar a una lista
Puede agregar un elemento a una lista usando el método append() de la clase List (este
cambia la lista actual; no crea una copia de la lista). La sintaxis de este
método es:
Como ejemplo, considere la siguiente lista de cadenas, a la que agregamos una quinta
cadena:
esto generará la salida:
También puede agregar todos los elementos de una lista a otra lista. Hay varias opciones
aquí, podemos usar el método extend() que agregará los elementos que se le pasan al
final de la lista o podemos usar el operador += que hace lo mismo:
El resultado de este fragmento de código es:
El enfoque que prefiera utilizar depende de usted.
Tenga en cuenta que, estrictamente hablando, tanto extend() como += toman un iterable.
31.4.5
Insertar en una lista
También puede insertar elementos en una lista existente. Esto se hace usando el insert()
método de la clase List. La sintaxis de este método es:
El método insert() toma un índice que indica dónde insertar el elemento
y un objeto a insertar.
Por ejemplo, podemos insertar la cadena ‘Paloma’ entre el cero y el uno.
elemento en la siguiente lista de nombres:
El resultado es:
En otras palabras, hemos insertado la cadena ‘Paloma’ en la posición de índice 1
empujando ‘Madonna’ y ‘Cher’ hacia arriba en el índice dentro de la Lista.
31.4.6
Concatenación de listas
Es posible concatenar dos listas usando el operador de concatenación ‘+’:
genera
a_list = [‘Adele’, ‘Madonna’, ‘Cher’]
imprimir (una_lista)
a_list.insert(1, ‘Paloma’)
imprimir (una_lista)
31.4.7
Eliminar de una lista
Podemos eliminar un elemento de una Lista usando el método remove(). la sintaxis
para este método es:
Esto eliminará el objeto de la lista; si el objeto no está en la lista entonces un
Python generará un error.
La salida de esto es:
31.4.8
El método pop()
La sintaxis del método pop() es:
Elimina un elemento de la Lista; sin embargo, difiere del remove()
método de dos maneras:
• Se necesita un índice, que es el índice del elemento a eliminar de la lista, en lugar de
que el objeto mismo.
• El método devuelve el elemento que se eliminó como resultado.
A continuación se muestra un ejemplo del uso del método pop():
[‘Gary’, ‘Mark’, ‘Robbie’, ‘Jason’, ‘Howard’]
[‘Gary’, ‘Marca’, ‘Jason’, ‘Howard’]
a.pop(índice=-1)
Que genera:
Una sobrecarga de este método es simplemente
Lo que elimina el último elemento de la lista. Por ejemplo:
con la salida:
31.4.9
Eliminación de una lista
También es posible utilizar la palabra clave del para eliminar elementos de una lista.
La palabra clave del se puede usar para eliminar un solo elemento o una porción de una lista.
Para eliminar un elemento individual de una lista, use del y acceda al elemento a través de
su índice:
que salidas:
Para eliminar un segmento dentro de una lista, use la palabra clave del y el segmento devuelto
de la lista.
[‘Érase una vez’]
a
[‘Erase una vez’]
que elimina el segmento desde el índice 1 hasta (pero sin incluir) el índice 3: 31.4.10 Métodos de lista Python tiene un conjunto de métodos integrados que puede usar en las listas. 31.5 Recursos en línea Consulte la documentación de la biblioteca estándar de Python para: • https://docs.python.org/3/tutorial/datastructures.html Tutorial de Python sobre datos estructuras • https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range para listas y tuplas. • https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences el Tutorial de tuplas en línea. • https://docs.python.org/3/tutorial/datastructures.html#lists el en línea Lista tutorial. [‘A B C D E’] [‘A’, ‘D’, ‘E’] mi_lista = [‘A’, ‘B’, ‘C’, ‘D’, ‘E’] imprimir (mi_lista) del mi_lista[1:3] imprimir (mi_lista) Método Descripción adjuntar() Añade un elemento al final de la lista. claro() Elimina todos los elementos de la lista. Copiar() Devuelve una copia de la lista. contar() Devuelve el número de elementos con el valor especificado extender() Agregue los elementos de una lista (o cualquier iterable), al final de la lista actual índice() Devuelve el índice del primer elemento con el valor especificado insertar() Agrega un elemento en la posición especificada estallido() Elimina el elemento en la posición especificada eliminar() Elimina el elemento con el valor especificado contrarrestar() Invierte el orden de la lista clasificar() Ordena la lista 31.4 Liza 377
31.6 Ejercicios El objetivo de este ejercicio es trabajar con una colección/contenedor como una lista. Para ello volveremos a las clases relacionadas con su Cuenta. Debe modificar su clase de cuenta para que pueda mantener un historial de actas. Una Transacción es un registro de un depósito o retiro junto con una cantidad. Tenga en cuenta que la cantidad inicial en una cuenta puede tratarse como un depósito inicial. La historia podría implementarse como una lista que contiene una secuencia ordenada para actas. Una Transacción en sí podría ser definida por una clase con una acción (depósito o retiro) y una cantidad. Cada vez que se realiza un retiro o un depósito, se debe crear un nuevo registro de transacción. agregado a una lista de historial de transacciones. Ahora brinde soporte para iterar a través del historial de transacciones de la cuenta tal que cada depósito o retiro pueda ser revisado. Puedes hacer esto implementando mención del protocolo iterable: consulte el último capítulo si necesita comprobar cómo hacer esto. Tenga en cuenta que es el historial de transacciones lo que queremos poder iterar a través, para que pueda usar la lista de historial como base de su iterable. Debería poder ejecutar este siguiente código al final de sus Cuentas solicitud: Según el conjunto exacto de transacciones que haya realizado (depósitos y retiros) debe obtener una lista de esas transacciones que se están imprimiendo: para la transacción en acc1: imprimir (transacción) Transacción [depósito: 10.05] Transacción [depósito: 23,45] Transacción[retirar: 12.33] 378 31 Colecciones, Tuplas y Listas
capitulo 32 Conjuntos 32.1 Introducción En el último capítulo vimos Tuplas y Listas; En este capítulo veremos un otros tipos de contenedores (o colecciones); el tipo Conjunto. Un Conjunto es un desordenado (un indexado) colección de objetos inmutables que no permite duplicados. 32.2 Crear un conjunto Un Conjunto se define usando corchetes (por ejemplo, ‘{}’). Por ejemplo, Cuando se ejecute este código, se mostrará que Apple solo se agrega una vez al conjunto: Tenga en cuenta que debido a que un Conjunto no está ordenado, no es posible referirse a elementos del establecer mediante un índice. canasta = {‘manzana’, ’naranja’, ‘manzana’, ‘pera’, ’naranja’, ‘plátano’} imprimir (canasta) {‘plátano’, ’naranja’, ‘pera’, ‘manzana’} © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_32 379
32.3 La función constructora Set() Al igual que con las tuplas y las listas, Python proporciona una función predefinida que puede convertir cualquier tipo iterable en un conjunto. La firma de la función es: Dado un objeto iterable, esta función devuelve un nuevo Conjunto basado en los valores obtenido del iterable. Esto significa que se puede crear fácilmente un Conjunto a partir de una Lista, Tupla o Diccionario, así como cualquier otro tipo de datos que implemente el iterable protocolo. Por ejemplo, el siguiente fragmento de código convierte una tupla en un conjunto: que imprime 32.4 Acceso a elementos en un conjunto A diferencia de las Listas, no es posible acceder a los elementos de un Conjunto a través de un índice; esto es porque son contenedores desordenados y por lo tanto no hay índices disponibles. Sin embargo, son contenedores iterables. Los elementos de un conjunto se pueden iterar utilizando la instrucción for: Esto aplica la función de impresión a cada elemento de la lista por turno. 32.5 Trabajar con conjuntos 32.5.1 Comprobación de la presencia de un elemento Puede verificar la presencia de un elemento en un conjunto usando la palabra clave in, por ejemplo: conjunto (iterable) conjunto1 = conjunto((1, 2, 3) imprimir (conjunto1) {1, 2, 3} por artículo en la cesta: imprimir (elemento) 380 32 Conjuntos
Esto imprimirá True si ‘apple’ es un miembro de la cesta del conjunto. 32.5.2 Adición de elementos a un conjunto Es posible agregar elementos a un conjunto usando el método add(): Esto genera: Si desea agregar más de un elemento a un conjunto, puede usar el método de actualización (): generando El argumento a actualizar puede ser un conjunto, una lista, una tupla o un diccionario. El método convierte automáticamente el parámetro en un conjunto si aún no es un conjunto y luego agrega el valor al conjunto original. 32.5.3 Cambio de elementos en un conjunto No es posible cambiar los elementos que ya están en un conjunto. imprimir (‘manzana’ en la cesta) cesta = {‘manzana’, ’naranja’, ‘plátano’} cesta.add(‘albaricoque’) imprimir (canasta) {’naranja’, ‘manzana’, ‘plátano’, ‘albaricoque’, ‘pera’} cesta = {‘manzana’, ’naranja’, ‘plátano’} basket.update([‘albaricoque’, ‘mango’, ‘pomelo’]) imprimir (canasta) {’naranja’, ‘manzana’, ‘mango’, ‘plátano’, ‘albaricoque’, ‘pomelo’} 32.5 Trabajar con conjuntos 381
32.5.4 Obtención de la longitud de un conjunto Al igual que con otras clases de colección/contenedor; puedes averiguar la longitud de un conjunto utilizando la función len(). 32.5.5 Obtención de los valores máximo y mínimo en un conjunto También puede obtener los valores máximos o mínimos en un conjunto usando max() y min() funciones: 32.5.6 Eliminación de un elemento Para eliminar un elemento de un conjunto, utilice las funciones eliminar() o descartar(). El La función remove () elimina un solo elemento de un conjunto pero genera un error si ese elemento no estaba inicializando el conjunto. La función remove() también elimina un único elemento de un conjunto, pero no arroja un error si inicialmente no estaba presente en el conjunto. Esto genera: También hay un método pop() que se puede usar para eliminar un elemento (y devolverlo). ese elemento como resultado de ejecutar el método); sin embargo, elimina el último elemento de la Conjunto (aunque como un conjunto está desordenado no sabrás qué elemento será). El método clear() se utiliza para eliminar todos los elementos de un Conjunto: imprimir (máximo (a_set)) imprimir (min (a_set)) canasta = {‘manzana’, ’naranja’, ‘manzana’, ‘pera’, ’naranja’, ‘banana’} imprimir (canasta) cesta.remove(‘manzana’) cesta.discard(‘albaricoque’) imprimir (canasta) {‘pera’, ‘plátano’, ’naranja’, ‘manzana’} {‘pera’, ‘plátano’, ’naranja’} canasta = {‘manzana’, ’naranja’, ‘manzana’, ‘pera’, ’naranja’, ‘banana’} print(len(canasta)) # genera 4 382 32 Conjuntos
que imprime Que se utiliza para representar un conjunto vacío. 32.5.7 Conjuntos de anidamiento Es posible mantener cualquier objeto inmutable dentro de un conjunto. Esto significa que un conjunto puede contener una referencia a una Tupla (ya que es inmutable). Así podemos escribir: Esto imprime: Sin embargo, no podemos anidar Listas u otros Conjuntos dentro de un Conjunto ya que estos no son tipos inmutables. Lo siguiente generaría un error de tiempo de ejecución en Python: Sin embargo, podemos usar Frozensets y anidarlos dentro de conjuntos. Un conjunto congelado es exactamente como un Conjunto excepto que es inmutable (no puede ser modificado) y por lo tanto se puede anidar dentro de un conjunto. Por ejemplo: cesta = {‘manzana’, ’naranja’, ‘plátano’} cesta.borrar() imprimir (canasta) colocar() s1 = {(1, 2, 3)} imprimir(s1) {(1, 2, 3)}
No se puede tener lo siguiente
s2 = { {1, 2, 3} } imprimir (s2) s3 = { [1, 2, 3] } imprimir (s3) 32.5 Trabajar con conjuntos 383
Esto genera: 32.6 Establecer operaciones El contenedor de conjuntos también admite operaciones similares a conjuntos como (|), intersección (&), diferencia (-) y diferencia simétrica (^). Estos se basan en la teoría de conjuntos simple. Dados los dos conjuntos: Por ejemplo, la unión de dos conjuntos representa la combinación de todos los valores en los dos conjuntos: Esto imprimiría: La intersección de dos conjuntos representa los valores comunes entre dos conjuntos: {conjunto congelado ({1, 2, 3})} {conjunto congelado ({1, 2, 3})}
Necesidad de convertir conjuntos y listas en conjuntos congelados
s2 = { conjunto congelado ({1, 2, 3}) } imprimir (s2) s3 = { conjunto congelado ([1, 2, 3]) } imprimir (s3) imprimir(‘Unión:’, s1 | s2) s1 = {‘manzana’, ’naranja’, ‘plátano’} s2 = {‘pomelo’, ’lima’, ‘plátano’} Unión: {‘manzana’, ’lima’, ‘plátano’, ‘pomelo’, ’naranja’} 384 32 Conjuntos
Esto genera La diferencia entre dos conjuntos es el conjunto de valores en el primer conjunto que no están en el segundo conjunto: que produce la salida La diferencia simétrica representa todos los valores únicos en los dos conjuntos (es decir es el inverso de la intersección: La salida de esta última operación es imprimir (‘Intersección:’, s1 y s2) Intersección: {‘banana’} imprimir(‘Diferencia:’, s1 - s2) Diferencia: {‘manzana’, ’naranja’} print(‘Diferencia simétrica:’, s1 ^ s2) Diferencia simétrica: {’naranja’, ‘manzana’, ’lima’, ‘pomelo’} 32.6 Establecer operaciones 385
Además de los operadores, también hay versiones de métodos: • s1.union(s2) es el equivalente de s1 | s2 • s1.interaction(s2) es el equivalente de s1 y s2 • s1.diferencia(s2) es el equivalente de s1 − s2 • s1.diferencia_simétrica(s2) es el equivalente de s1 ^ s2 32.7 Establecer métodos Python tiene un conjunto de métodos integrados que puede usar en conjuntos. 32.8 Recursos en línea Los recursos en línea sobre conjuntos se enumeran a continuación: • https://docs.python.org/3/tutorial/datastructures.html Tutorial de Python sobre datos estructuras • https://docs.python.org/3/tutorial/datastructures.html#sets el tutorial de Set en línea. Método Descripción agregar() Añade un elemento al conjunto. claro() Elimina todos los elementos del conjunto. Copiar() Devuelve una copia del conjunto. diferencia() Devuelve un conjunto que contiene la diferencia entre dos o más conjuntos diferencia_actualizar() Elimina los elementos de este conjunto que también están incluidos en otro, conjunto especificado desechar() Eliminar el elemento especificado intersección() Devuelve un conjunto, que es la intersección de otros dos conjuntos. intersección_actualizar() Elimina los elementos de este conjunto que no están presentes en otros, conjunto(s) especificado(s) es disjunto() Devuelve si dos conjuntos tienen una intersección o no issubconjunto() Devuelve si otro conjunto contiene este conjunto o no essuperconjunto() Devuelve si este conjunto contiene otro conjunto o no estallido() Elimina un elemento del conjunto. eliminar() Elimina el elemento especificado diferencia_simétrica() Devuelve un conjunto con las diferencias simétricas de dos conjuntos. actualización_diferencia_simétrica () inserta las diferencias simétricas de este conjunto y otro Unión() Devuelve un conjunto que contiene la unión de conjuntos actualizar() Actualizar el conjunto con la unión de este conjunto y otros 386 32 Conjuntos
• https://www.python-course.eu/python3_sets_frozensets.php Un tutorial sobre conjuntos y conjuntos congelados. • https://docs.python.org/3/library/stdtypes.html#set-types-set-frozenset para conjuntos 32,9 Ejercicios El objetivo de este ejercicio es utilizar un Set. Cree dos grupos de estudiantes, uno para los que tomaron un examen y otro para los que presentó un proyecto. Puede usar cadenas simples para representar a los estudiantes, por ejemplo: Usando estos conjuntos responde las siguientes preguntas: • ¿Qué estudiantes tomaron el examen y enviaron un proyecto? • ¿Qué estudiantes solo tomaron el examen? • ¿Qué estudiantes solo presentaron el proyecto? • Haga una lista de todos los estudiantes que tomaron el examen y el proyecto (o ambos). • Haga una lista de todos los estudiantes que tomaron el examen y el proyecto (pero no ambos).
Configurar conjuntos
examen = {‘Andrew’, ‘Kirsty’, ‘Beth’, ‘Emily’, ‘Sue’} proyecto = {‘Kirsty’, ‘Emily’, ‘Ian’, ‘Stuart’}
Salida de los conjuntos básicos
print(’examen:’, examen) imprimir(‘proyecto:’, proyecto) 32.8 Recursos en línea 387
capitulo 33 Diccionarios 33.1 Introducción Un diccionario es un conjunto de asociaciones entre una clave y un valor que no es or- dered, cambiable (mutable) e indexado. Pictóricamente, podríamos ver un Diccionario como se muestra a continuación para un conjunto de países y sus ciudades capitales. Tenga en cuenta que en un diccionario las claves deben ser únicas pero los valores no necesitan ser únicos. 33.2 Creación de un diccionario Un diccionario se crea usando corchetes (’{}’) donde cada entrada en el diccionario es un par clave:valor: ciudades = {‘Gales’: ‘Cardiff’, ‘Londres, Inglaterra’, ‘Escocia’: ‘Edimburgo’, ‘Irlanda del Norte’: ‘Belfast’, ‘Irlanda’: ‘Dublín’} imprimir (ciudades) © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_33 389
Esto crea un diccionario al que hace referencia la variable cities que contiene un conjunto de pares clave:valor para las ciudades capitales del Reino Unido e Irlanda. Cuando se ejecuta este código vemos: 33.2.1 La función constructora dict() La función dict() se puede utilizar para crear un nuevo objeto de diccionario a partir de un iterable o una secuencia de pares clave:valor. La firma de esta función es: Esta es una función sobrecargada con tres versiones que pueden tomar diferentes tipos de argumentos: • La primera opción toma una secuencia de pares clave:valor. • El segundo toma un mapeo y (opcionalmente) una secuencia de pares clave:valor. • La tercera versión toma una iteración de pares clave:valor y una secuencia opcional de pares clave:valor. Algunos ejemplos se dan a continuación como referencia: La salida impresa por estos ejemplos es: {‘Gales’: ‘Cardiff’, ‘Inglaterra’: ‘Londres’, ‘Escocia’: ‘Edimburgo’, ‘Irlanda del Norte’: ‘Belfast’, ‘Irlanda’: ‘Dublín’} dictado (**kwarg) dict(asignación, **kwarg) dict(iterable, **kwarg)
las teclas de nota no son cadenas
dict1 = dict(Reino Unido=‘Londres’, Irlanda=‘Dublín’, Francia=‘París’) imprimir(‘dict1:’, dict1)
pares de valores clave son tuplas
dict2 = dict([(‘Reino Unido’, ‘Londres’), (‘Irlanda’, ‘Dublín’), (‘francia’, ‘París’)]) imprimir(‘dict2:’, dict2)
pares de valores clave son listas
dict3 = dict(([‘uk’, ‘Londres’], [‘irlanda’, ‘Dublin’], [‘francia’, ‘París’])) imprimir(‘dict3:’, dict3) dict1: {‘uk’: ‘Londres’, ‘irlanda’: ‘Dublin’, ‘france’: ‘Paris’} dict2: {‘uk’: ‘Londres’, ‘irlanda’: ‘Dublin’, ‘france’: ‘Paris’} dict3: {‘uk’: ‘Londres’, ‘irlanda’: ‘Dublin’, ‘france’: ‘Paris’} 390 33 Diccionarios
33.3 Trabajar con diccionarios 33.3.1 Acceso a elementos a través de teclas Puede acceder a los valores contenidos en un Diccionario utilizando su clave asociada. Este se especifica usando la notación de corchetes (’[]’) (donde la clave está dentro los corchetes) o el método get(): La salida de esto es: 33.3.2 Agregar una nueva entrada Se puede agregar una nueva entrada a un diccionario proporcionando la clave entre corchetes y el nuevo valor que se asignará a esa clave: 33.3.3 Cambiar el valor de una clave El valor asociado con una clave se puede cambiar reasignando un nuevo valor usando la notación de corchetes, por ejemplo: que ahora mostraría ‘Swansea’ como la capital de Gales: ciudades[Gales]: Cardiff cities.get (Irlanda): Dublín print(‘ciudades[Gales]:’, ciudades[‘Gales’]) print(‘ciudades.get(Irlanda):’, ciudades.get(‘Irlanda’)) ciudades[‘Francia’] = ‘París’ ciudades[‘Gales’] = ‘Swansea’ imprimir (ciudades) {‘Gales’: ‘Swansea’, ‘Inglaterra’: ‘Londres’, ‘Escocia’: ‘Edimburgo’, ‘Irlanda del Norte’: ‘Belfast’, ‘Irlanda’: ‘Dublín’} 33.3 Trabajar con diccionarios 391
33.3.4
Eliminar una entrada
Se puede eliminar una entrada en el diccionario usando uno de los métodos pop() o
método popitem() o la palabra clave del.
• El método pop(
Lo que genera la siguiente salida: Tenga en cuenta que el diccionario vacío está representado por el ‘{}’ encima del cual como el el conjunto vacío se representó como set(). 33.3.5 Iterando sobre claves Puede recorrer un diccionario utilizando la declaración de bucle for. El bucle for procesa cada una de las claves en el diccionario a su vez. Esto se puede utilizar para acceder a cada de los valores asociados a las claves, por ejemplo: Lo que genera la salida: Si desea iterar sobre todos los valores directamente, puede hacerlo usando el método de valores(). Esto devuelve una colección de todos los valores, que por supuesto usted luego puede iterar sobre: ciudades = {‘Gales’: ‘Cardiff’, ‘Londres, Inglaterra’, ‘Escocia’: ‘Edimburgo’, ‘Irlanda del Norte’: ‘Belfast’, ‘Irlanda’: ‘Dublín’} imprimir (ciudades) ciudades.clear() imprimir (ciudades) {‘Gales’: ‘Cardiff’, ‘Inglaterra’: ‘Londres’, ‘Escocia’: ‘Edimburgo’, ‘Irlanda del Norte’: ‘Belfast’, ‘Irlanda’: ‘Dublín’} {} para el campo en las ciudades: imprimir(país, fin=’, ‘) imprimir(ciudades[país]) Gales, Cardiff Londres, Inglaterra Escocia, Edimburgo Irlanda del Norte, Belfast Irlanda, Dublín 33.3 Trabajar con diccionarios 393
33.3.6 Valores, claves y elementos Existen tres métodos que le permiten obtener una vista del contenido de un diccionario, estos son valores(), claves() y elementos(). • El método de valores() devuelve una vista de los valores del diccionario. • El método keys() devuelve una vista de las claves de un diccionario. • El elementos() método devoluciones a vista sobre el del diccionario elementos ((clave, valor) pares). Una vista proporciona una ventana dinámica a las entradas del diccionario, lo que significa que cuando el diccionario cambia, la vista refleja estos cambios. El siguiente código usa los diccionarios de ciudades con estos tres métodos: El resultado deja en claro que todos estos están relacionados con un diccionario al indicar que el tipo es dict_values, dict_keys o dict_items, etc.: 33.3.7 Comprobación de membresía clave Puede verificar si una clave es miembro de un diccionario usando la sintaxis in (y que no está en un diccionario usando la sintaxis not in), por ejemplo: Que ambos imprimen True para el diccionario de ciudades. para e en d.values(): imprimir (e) imprimir(ciudades.valores()) imprimir(ciudades.claves()) imprimir(ciudades.elementos()) dict_values([‘Cardiff’, ‘Londres’, ‘Edimburgo’, ‘Belfast’, ‘Dublín’]) dict_keys([‘Gales’, ‘Inglaterra’, ‘Escocia’, ‘Irlanda del Norte’, ‘Irlanda’]) dict_items([(‘Gales’, ‘Cardiff’), (‘Inglaterra’, ‘Londres’), (‘Escocia’, ‘Edimburgo’), (‘Irlanda del Norte’, ‘Belfast’), (‘Irlanda’, ‘Dublín’)]) print(‘Gales’ en ciudades) print(‘Francia’ no en ciudades) 394 33 Diccionarios
33.3.8 Obtención de la longitud de un diccionario Una vez más, como con otras clases de colección; puedes averiguar la longitud de un Diccionario (en términos de sus pares clave:valor) usando la función len(). 33.3.9 Diccionarios anidados La clave y el valor en un diccionario deben ser un objeto; sin embargo, todo en Python es un objeto y, por lo tanto, cualquier cosa puede usarse como clave o valor. Un patrón común es donde el valor en un diccionario es en sí mismo un contenedor como como Lista, Tupla, Conjunto o incluso otro Diccionario. El siguiente ejemplo utiliza tuplas para representar los meses que componen el estaciones: La salida es: Cada temporada tiene una Tupla para el elemento de valor de la entrada. Cuando esta Tupla se devuelve usando la clave, se puede tratar como cualquier otra tupla. Tenga en cuenta que en este caso podríamos haber usado fácilmente una Lista o, de hecho, un Conjunto en lugar de un Tupla. ciudades = {‘Gales’: ‘Cardiff’, ‘Londres, Inglaterra’, ‘Escocia’: ‘Edimburgo’, ‘Irlanda del Norte’: ‘Belfast’, ‘Irlanda’: ‘Dublín’} print(len(ciudades)) # imprime 5 estaciones = {‘Primavera’: (‘Mar’, ‘Abr’, ‘Mayo’), ‘Verano’: (‘Junio’, ‘Julio’, ‘Agosto’), ‘Otoño’: (‘Septiembre’, ‘Octubre’, ‘Noviembre’), ‘Invierno’: (‘Diciembre’, ‘Enero’, ‘Febrero’)} imprimir(estaciones[‘Primavera’]) imprimir(estaciones[‘Primavera’][1]) (‘Mar’, ‘Abr’, ‘May’) Abr 33.3 Trabajar con diccionarios 395
33.4 Una nota sobre los objetos clave del diccionario Una clase cuyos objetos se van a utilizar como clave dentro de un diccionario debe considerar implementando dos métodos especiales, estos son hash() y eq(). El El método hash se usa para generar un número hash que puede ser usado por el diccionario. container y el método equals se usa para probar si dos objetos son iguales. Por ejemplo: El resultado de estas dos líneas para una ejecución de ejemplo es: Python tiene dos reglas asociadas con estos métodos: • Si dos objetos son iguales, entonces sus valores hash deben ser iguales. • Para que un objeto sea modificable, debe ser inmutable. También tiene dos propiedades asociadas con los códigos hash de un objeto que debería adherirse a: • Si dos objetos tienen el mismo hash, es probable que sean el mismo objeto. • El hash de un objeto debería ser barato de calcular. ¿Por qué necesita preocuparse por estos métodos? Para el tipo incorporado no necesita preocuparse; sin embargo, para clases definidas por el usuario/ Entonces, si estos tipos se van a usar como claves dentro de un diccionario, entonces debe considere implementar estos métodos. Esto se debe a que un Diccionario usa • el método hash para administrar cómo se organizan los valores y • el método equals para verificar si una clave ya está presente en el diccionario. Aparte, si desea hacer que una clase sea algo que no se puede usar como clave en un diccionario, es decir, no es hashable, entonces puede definir esto configurando el Método hash() a Ninguno. imprimir(‘clave.hash():’, clave.hash()) print(“clave.eq(‘Inglaterra’):”, clave.eq(‘Inglaterra’)) clave.hash(): 8507681174485233653 clave.eq(‘Inglaterra’): Verdadero clase NotHashableThing (objeto): hash = Ninguno 396 33 Diccionarios
33.5 Métodos de diccionario Python tiene un conjunto de métodos integrados que puede usar en los diccionarios. 33.6 Recursos en línea Los recursos en línea sobre diccionarios se enumeran a continuación: • https://docs.python.org/3/tutorial/datastructures.html Tutorial de Python sobre datos estructuras • https://www.python-course.eu/python3_dictionaries.php A tutorial en dic- cionarios en Python. • https://docs.python.org/3/library/stdtypes.html#mapping-types-dict para diccionarios. • https://docs.python.org/3/tutorial/datastructures.html#dictionaries el en línea tutorial de diccionario. • https://en.wikipedia.org/wiki/Hash_table Para obtener más información sobre la definición de hash funciones y cómo se utilizan en contenedores como Dictionary. Método Descripción claro() Elimina todos los elementos del diccionario. Copiar() Devuelve una copia del diccionario. de las teclas () Devuelve un diccionario con las claves y valores especificados. conseguir() Devuelve el valor de la clave especificada elementos() Devuelve una lista que contiene la tupla para cada par de valores clave llaves() Devuelve una lista que contiene las claves del diccionario. estallido() Elimina el elemento con la clave especificada elemento pop() Elimina el último par clave-valor insertado establecer predeterminado() Devuelve el valor de la clave especificada. Si la clave no existe: insertar la clave, con el valor especificado actualizar() Actualiza el diccionario con los pares clave-valor especificados valores() Devuelve una lista de todos los valores en el diccionario. 33.5 Métodos de diccionario 397
33.7 Ejercicios El objetivo de este ejercicio es usar un Diccionario como una forma simple de caché de datos. Calcular el factorial para un número muy grande puede llevar algún tiempo. Para ejemplo calcular el factorial de 150,000 puede tomar varios segundos. Podemos verifique esto usando un decorador de temporizador similar al que creamos en el capítulo sobre Decoradores. El siguiente programa ejecuta varios cálculos factoriales en números grandes y imprime el tiempo necesario para cada uno: desde timeit import default_timer def temporizador (función): def interior(valor): print(’llamando’, func.name, ‘con’, valor) inicio = temporizador_predeterminado() función(valor) fin = temporizador_predeterminado() print(‘regresó de’, func.name, ’tomó’, int(fin - inicio), ‘segundos’) volver interior @Temporizador def factorial(num): si numero == 0: volver 1 demás: valor_factorial = 1 para i en el rango (1, num + 1): valor_factorial = valor_factorial * i devolver valor_factorial imprimir(factorial(150000)) imprimir(factorial(80000)) imprimir(factoriales(120000)) imprimir(factorial(150000)) imprimir(factoriales(120000)) imprimir(factorial(80000)) 398 33 Diccionarios
A continuación se muestra un ejemplo de la salida generada por este programa: Como se puede ver a partir de esto, en esta corrida particular, calculando el factorial de 150 000 tomó 5 s, mientras que el factorial de 80 000 tomó poco más de 1 1/4 s, etc. En este caso particular, hemos decidido volver a ejecutar estos cálculos para que han calculado el factorial de 150.000, 80.000 y 120.000 al menos dos veces. La idea de un caché es que se puede usar para guardar cálculos anteriores y reutilizar aquellos, si corresponde, en lugar de tener que realizar el mismo cálculo varias veces. El uso de un caché puede mejorar en gran medida el rendimiento de los sistemas en los que estos se repiten los cálculos. Hay muchas bibliotecas comerciales de almacenamiento en caché disponibles para una amplia variedad de lenguajes incluyendo Python. Sin embargo, en su esencia todos son un tanto dic- cionario como; es decir, hay una tecla que suele ser una combinación de la operación ación invocada y los valores de los parámetros utilizados. A su vez, el elemento de valor es el resultado del calculo Estos cachés suelen tener también políticas de desalojo para que no se conviertan en demasiado grande; estas políticas de desalojo generalmente se pueden especificar para que coincidan con el forma en que se utiliza la memoria caché. Una política de desalojo común es la Menos recientemente Política usada (o LRU). Al usar esta política una vez que el tamaño de la memoria caché alcanza un límite predeterminado, el valor Usado menos recientemente se desaloja, etc. Para este ejercicio, debe implementar un mecanismo de almacenamiento en caché simple utilizando un diccionario (pero sin política de desalojo). El caché debe usar el parámetro pasado a la función factorial() como la clave y devolver el valor almacenado si hay uno presente. La lógica para esto suele ser: llamando factorial con 150000 regresado de factorial tardo 5 segundos Ninguno llamando factorial con 80000 devuelto de factorial tardo 1 segundo Ninguno llamando factorial con 120000 regresado de factorial tardo 3 segundos Ninguno llamando factorial con 150000 regresado de factorial tardo 5 segundos Ninguno llamando factorial con 120000 regresado de factorial tardo 3 segundos Ninguno llamando factorial con 80000 devuelto de factorial tardo 1 segundo Ninguno 33.7 Ejercicios 399
- Mire en el caché para ver si la clave está presente
- Si es devolver el valor
- Si no realiza el cálculo
- Almacene el resultado calculado para uso futuro
- Devolver el valor Tenga en cuenta que la función factorial() es exactamente eso una función; necesitaras piense en usar una variable global para mantener el caché. Una vez que se utiliza la memoria caché con la función factorial(), cada subsiguiente la invocación de la función utilizando un valor anterior debería volver casi inmediatamente. Esto se muestra en la salida de muestra anterior, donde las llamadas de método subsiguientes regresan en menos de un segundo. regresado de factorial tardo 5 segundos Ninguno llamando factorial con 80000 devuelto de factorial tardo 1 segundo Ninguno llamando factorial con 120000 regresado de factorial tardo 3 segundos Ninguno llamando factorial con 150000 devuelto de factorial tardó 0 segundos Ninguno llamando factorial con 120000 devuelto de factorial tardó 0 segundos Ninguno llamando factorial con 80000 devuelto de factorial tardó 0 segundos Ninguno llamando factorial con 150000 400 33 Diccionarios
capitulo 34 Módulos relacionados con la colección 34.1 Introducción El capítulo presenta una función conocida como comprensión de listas en Python. Luego presenta las colecciones y los módulos de itertools. 34.2 Lista de comprensión Este es un mecanismo muy poderoso que puede usarse para generar nuevas listas. La sintaxis de la comprensión de lista es: La nueva lista está formada por los resultados de la expresión. Tenga en cuenta que la totalidad para declaración y expresión está rodeada por los corchetes que normalmente se usan para crear una Lista. Cuando se ejecuta una Comprensión de lista, genera una nueva lista aplicando el expresión a los elementos de otra colección. Por ejemplo, dada una lista de enteros, se puede crear otra (nueva) lista usando el formato de comprensión de lista: [ <expresión> para elemento en iterable <si condición_opcional>] lista1 = [1, 2, 3, 4, 5,6] imprimir(’lista1:’, lista1) lista2 = [elemento + 1 para elemento en lista1] imprimir(’lista2:’, lista2) © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_34 401
que produce la salida: Aquí la nueva lista se genera agregando 1 a cada elemento en la lista inicial lista1. Esencialmente, iteramos (bucle) sobre todos los elementos en la lista inicial y enlazamos cada elemento de la lista a la variable del elemento a su vez. El resultado de la expresión el elemento + 1 se captura entonces, en orden, en la nueva lista. Esta función no se limita al procesamiento de valores en una lista; cualquier colección iterable se puede utilizar como tuplas o conjuntos como fuente de los valores a procesar. Otra característica de la Comprensión de listas es la capacidad de filtrar los valores pasado a la expresión usando la condición opcional if. Por ejemplo, si deseamos considerar solo números pares para la expresión, entonces podemos usar la instrucción if opcional para filtrar todos los números impares: El resultado de estas dos líneas de código es: Por lo tanto, solo los números pares se pasaron a la expresión elemento + 1 resultante en que solo se generen los valores 3, 5 y 7 para la nueva lista. 34.3 El módulo de colecciones El módulo de colecciones amplía las características básicas del módulo orientado a la colección. tipos de datos dentro de Python con tipos de datos de contenedor de alto rendimiento. Proporciona muchos contenedores útiles tales como: lista3 = [elemento + 1 para elemento en lista1 si elemento % 2 == 0] imprimir(’lista3:’, lista3) lista3: [3, 5, 7] lista1: [1, 2, 3, 4, 5, 6] lista2: [2, 3, 4, 5, 6, 7] Nombre Objetivo tupla nombrada() Función de fábrica para crear subclases de tupla con campos con nombre deque Contenedor tipo lista con agregados rápidos y elementos emergentes en cada extremo CadenaMapa Clase similar a Dict para crear una vista única de múltiples asignaciones Encimera Subclase de dictado para contar objetos hashable dictado ordenado Subclase de dictado que recuerda las entradas de pedido que se agregaron Predeterminadodict Subclase de dictado que llama a una función de fábrica para proporcionar valores faltantes UserDict Envoltura alrededor de objetos de diccionario para facilitar la subclasificación de dictados Lista de usuarios Envoltura alrededor de objetos de lista para una subclasificación de lista más fácil Cadena de usuario Envoltura alrededor de objetos de cadena para facilitar la subclasificación de cadenas 402 34 Módulos relacionados con la colección
Como este no es uno de los módulos predeterminados que se cargan automáticamente para usted por Pitón; tendrá que importar la colección. Como ejemplo, usaremos el tipo Contador para contener eficientemente múltiples copias del mismo elemento. Es eficiente porque solo contiene una copia de cada elemento, pero lleva la cuenta del número de veces que ese elemento se ha agregado al recopilación: La salida de esto es: Lo que hace que el comportamiento de conteo asociado con la clase Counter sea bastante claro. Hay muchos usos de una clase de este tipo, por ejemplo, se puede utilizar para averiguar el palabra más utilizada en un ensayo; todo lo que tienes que hacer es agregar cada palabra en un ensayo al Contador y luego recupere la palabra con el conteo más alto. Esto puede hacerse usando el método most_common() de la clase Counter. Este método toma un parámetro n que indica cuántos de los elementos más comunes deben ser devuelto Si se omite n (o Ninguno), el método devuelve una lista ordenada de elementos. Así, para obtener la fruta más común de la col- lección podemos usar: Que genera: También puede realizar algunas operaciones matemáticas con múltiples Counter objetos. Por ejemplo, puede sumar y restar objetos de contador. Tú también puedes obtener una combinación de Contadores que combine los valores máximos de dos Contador de objetos. También puede generar una intersección de dos Contadores. Estos se ilustran a continuación: importar colecciones fruta = colecciones.Contador([‘manzana’, ’naranja’, ‘pera’, ‘manzana’, ’naranja’, ‘manzana’]) imprimir (fruta) imprimir (fruta [’naranja’]) Contador ({‘manzana’: 3, ’naranja’: 2, ‘pera’: 1}) 2 print(‘fruta.más_común(1):’, fruta.más_común(1)) fruit.most_common(1): [(‘manzana’, 3)] 34.3 El módulo de colecciones 403
Que produce: Una vez que se ha creado un objeto Contador, puede probarlo para ver si un elemento es presente usando la palabra clave in, por ejemplo: También puede agregar elementos a un objeto Contador accediendo al valor usando el elemento como la clave, por ejemplo: fruta1 = colecciones.Contador([‘manzana’, ’naranja’, ‘pera’, ’naranja’]) fruta2 = colecciones.Contador([‘plátano’, ‘manzana’, ‘manzana’]) imprimir(‘fruta1:’, fruta1) imprimir(‘fruta2:’, fruta2) print(‘fruta1 + fruta2:’, fruta1 + fruta2) print(‘fruta1 - fruta2:’, fruta1 - fruta2)
Unión (max(fruta1[n], fruta2[n])
print(‘fruta1 | fruta2:’, fruta1 | fruta2)
Intersección (min(fruta1[n], fruta2[n])
print(‘fruta1 & fruta2:’, fruta1 & fruta2) fruit1: Contador ({’naranja’: 2, ‘manzana’: 1, ‘pera’: 1}) fruit2: Contador ({‘manzana’: 2, ‘plátano’: 1}) fruta1 + fruta2: Contador({‘manzana’: 3, ’naranja’: 2, ‘pera’: 1, ‘plátano’: 1}) fruta1 - fruta2: Contador({’naranja’: 2, ‘pera’: 1}) fruta1 | fruit2: Contador ({‘manzana’: 2, ’naranja’: 2, ‘pera’: 1, ‘plátano’: 1}) fruit1 & fruit2: Contador({‘manzana’: 1}) print(‘manzana’enfruta) fruit[‘apple’] = 1 # inicializa el número de manzanas fruit[‘apple’] =+ 1 # Suma uno al número de manzanas fruit[‘apple’] =- 1 # Resta 1 del número de manzanas 404 34 Módulos relacionados con la colección
34.4 El Módulo Itertools El módulo itertools es otro módulo con el que vale la pena familiarizarse. Este módulo proporciona una serie de funciones útiles que devuelven iteradores construidos de varias maneras. Como hay muchas opciones diferentes disponibles, vale la pena mirar la documentación para obtener una lista completa de las funciones disponibles. Para darle una idea de algunas de las instalaciones disponibles, consulte lo siguiente listado: La salida de este código es: importar itertools
Conectar dos iteradores juntos
r1 = lista(itertools.cadena([1, 2, 3], [2, 3, 4])) imprimir (r1)
Crear iterador con elemento repetido número especificado de
veces (posiblemente infinitas)
r2 = lista(itertools.repeat(‘hola’, 5)) imprimir (r2)
Crear iterador con elementos desde el inicio del primer iterador
donde falla la función de predicado
valores = [1, 3, 5, 7, 9, 3, 1] r3 = lista(itertools.dropwhile(lambda x: x < 5, valores)) imprimir (r3)
Crear iterador con elementos del iterador proporcionado entre
los dos índices (use ‘Ninguno’ para que el segundo índice vaya al final)
r4 = lista(itertools.islice(valores, 3, 6)) imprimir (r4) [1, 2, 3, 2, 3, 4] [‘Hola hola hola hola hola’] [5, 7, 9, 3, 1] [7, 9, 3] 34.4 El Módulo Itertools 405
34.5 Recursos en línea Los recursos en línea en la biblioteca de itertools se enumeran a continuación: • https://docs.python.org/3.7/library/itertools.html La biblioteca estándar docu- mención en el módulo itertools. • https://pymotw.com/3/itertools/index.html El módulo Python de la página de la semana para itertools. 34.6 Ejercicios El objetivo de este ejercicio es crear un programa de concordancia en Python usando el colecciones. Clase de contador. A los efectos de este ejercicio, una concordancia es una lista alfabética de las palabras presentes en un texto o textos con un conteo del número de veces que la palabra ocurre. Su programa de concordancia debe incluir los siguientes pasos: • Pídale al usuario que ingrese una oración. • Divida la oración en palabras individuales (puede usar el método split() de la clase de cadena para esto. • Use la clase Contador para generar una lista de todas las palabras en la oración y el número de veces que ocurren. • Producir una lista alfabética de las palabras con sus conteos. Puedes obtener un lista ordenada de las claves usando la función sorted(). A continuación, puede utilizar un recopilación. OrderedDict para generar un diccionario que mantenga el orden en qué claves se agregaron. • Imprima la lista ordenada alfabéticamente. A continuación se muestra un ejemplo de cómo podría funcionar el programa: Por favor ingrese el texto para ser analizado: gato sat estera sombrero gato sombrero gato Contador desordenado Contador ({‘gato’: 3, ‘sombrero’: 2, ‘sat’: 1, ’estera’: 1}) Recuento ordenado de palabras OrderedDict([(‘gato’, 3), (‘sombrero’, 2), (’estera’, 1), (‘sat’, 1)]) 406 34 Módulos relacionados con la colección
capitulo 35 ADT, Colas y Pilas 35.1 Introducción Hay una serie de estructuras de datos comunes que se utilizan dentro de la computadora programas que podría esperar ver dentro de la lista de colección o contenedor de Python clases; estos incluyen Colas y Pilas. Sin embargo, en las clases de colección básicas estos faltan Sin embargo, podemos crear nuestras propias implementaciones o usar una de las bibliotecas de extensión que proporcionan dichas colecciones. En este capítulo vamos a explorar la implementación de nuestras propias versiones. 35.2 Tipos de datos abstractos La Cola y la Pila son ejemplos concretos de lo que se conoce como Datos Abstractos Tipos (o ADT). Un tipo de datos abstractos (o ADT) es un modelo para un tipo particular de datos, donde un tipo de datos se define por su comportamiento (o semántica) desde el punto de vista de la usuario de ese tipo de datos. Este comportamiento se define típicamente en términos de posibles valores, posibles operaciones sobre los datos de este tipo y comportamiento de las operaciones proporcionada por el tipo de datos. Un ADT se usa para definir un concepto común que puede ser implementado por uno o estructuras de datos más concretas. Estas implementaciones pueden usar diferentes representaciones de los datos o diferentes algoritmos para proporcionar el comportamiento; por semánticamente cumplen con las descripciones proporcionadas por el ADT. Por ejemplo, se puede definir un ADT de lista que defina las operaciones y comportamiento que debe proporcionar una estructura de datos tipo Lista. Implementaciones concretas puede cumplir con la semántica de una lista utilizando una matriz subyacente de elementos, o por vincular elementos con punteros o usar algún tipo de tabla hash (todas que son diferentes representaciones internas que podrían usarse para implementar una lista). © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_35 407
35.3 Estructuras de datos Veremos cómo se pueden usar los tipos de colección de Python como una Cola y una Stack, pero primero necesitamos definir estos dos ADT: • Queue es un ADT que define cómo se administra y se administra una colección de entidades. mantenido Las colas tienen lo que se conoce como Primero en entrar, primero en salir (o FIFO) el comportamiento que es la primera entidad agregada a una cola es lo primero que se elimina de la cola. Dentro de la cola, el orden en que se agregaron las entidades es mantenido • Stack es otro ADT, pero esta vez tiene un comportamiento de último en entrar, primero en salir (o LIFO). viour Esa es la entidad agregada más recientemente a la pila será la próxima entidad ser eliminado. Dentro de la pila, el orden en que se agregaron las entidades es mantenido 35.4 Colas Las colas son muy utilizadas en Informática y Software. Ingeniería. Permiten conservar los datos con fines de tratamiento cuando la garantía antee es que los elementos anteriores agregados se procesarán antes que los posteriores. Existen numerosas variaciones en las operaciones básicas de la cola, pero en esencia todas Las colas proporcionan las siguientes características: • Creación de colas. • Agregar un elemento al final de la cola (conocido como puesta en cola). • Quitar un elemento del principio de la cola (lo que se conoce como eliminación de la cola). • Averigüe la longitud de la cola. • Compruebe si la cola está vacía. • Las colas pueden ser de tamaño fijo o variable (aumentable) en tamaño. El comportamiento básico de una cola se ilustra mediante: En el diagrama anterior hay cinco elementos en la cola, un elemento tiene ya se ha quitado del frente y se está agregando otro en la parte posterior. Nota que cuando un elemento se elimina del frente de la cola, todos los demás elementos avanzar una posición. Así, el elemento que era el segundo al frente de la queue se convierte en el frente de la cola cuando el primer elemento se elimina de la cola. 408 35 ADT, Colas y Pilas
Muchas colas también permiten funciones como: • Mire el elemento al principio de la cola (es decir, vea cuál es el elemento pero no lo quite de la cola). • Proporcionar prioridades para que los elementos con una prioridad más alta no se agreguen al al final de la cola, sino a un punto en el medio de la cola relacionado con su prioridad. 35.4.1 Lista de Python como una cola El contenedor de lista de Python se puede usar como una cola usando las operaciones existentes como append() y pop(), por ejemplo: queue = [] # Crear una cola vacía cola.append(’tarea1’) print(‘cola inicial:’, cola) cola.append(’tarea2’) cola.append(’tarea3’) print(‘cola después de las adiciones:’, cola) elemento1 = cola.pop(0) print(’elemento recuperado de la cola:’, elemento1) imprimir (‘cola después de la eliminación’, cola) cuya salida es cola inicial: [’tarea1’] cola después de las adiciones: [’tarea1’, ’tarea2’, ’tarea3’] elemento recuperado de la cola: tarea1 cola después de la eliminación [’tarea2’, ’tarea3’] Tenga en cuenta que cada tarea se agregó al final de la cola, pero la primera tarea obtuvo de la cola era la tarea 1. 35.4.2 Definición de una clase de cola En la última sección usamos la clase List como una forma de proporcionar una cola; este El enfoque funciona, pero no es obvio que estemos usando la lista como una cola (con el excepción del nombre de la variable en la que estamos guardando la Lista). Por ejemplo hemos usado pop(0) para sacar un elemento de la cola y hemos usado append() para poner en cola un elemento. Además no hay nada que detenga a un programador olvidarse de usar pop(0) y en su lugar usar pop(), que es un error fácil de cometer y que eliminará el elemento agregado más recientemente de la cola. Sería mejor crear un nuevo tipo de datos y asegurarse de que estos datos proporcionen el hacer cola como comportamiento y ocultar la lista dentro de este tipo de datos. 35.4 Colas 409
Podemos hacer esto definiendo nuestra propia clase Queue en Python. Cola de clase: def init(uno mismo): self._list = [] # datos internos iniciales def poner en cola(uno mismo, elemento): self._list.append(elemento) def dequeue(auto): volver self._list.pop(0) def len(uno mismo): "”” Admite el protocolo len "”" volver len(self._list) def está_vacío(uno mismo): volver self.len() == 0 def mirar (uno mismo): devolver self._list[0] def str(uno mismo): devuelve ‘Cola: ’ + str(self.list) Esta clase de cola contiene internamente una lista. Tenga en cuenta que estamos usando la convención de que el el nombre de la variable de instancia de la lista interna está precedido por un guión bajo (’’) por lo tanto indicando que nadie debe acceder a ella directamente. También hemos definido métodos para eliminar y poner en cola elementos al cola. Para completar la definición, también hemos definido métodos para verificar la longitud actual de la cola, ya sea que la cola esté vacía o no, lo que permite elemento al frente de la cola para ser visto y, por supuesto, probar una cadena versión de la cola para imprimir. Tenga en cuenta que el método is_empty() usa el método len() cuando determinar si la cola está vacía; este es un ejemplo de una idea importante; solo definir algo una vez. Como queremos usar la longitud de la cola para ayudar a determinar si la cola está vacía, reutilizamos el método len() en lugar del código implementar el método de longitud; así, si la representación interna cambia vamos a no afectará al método is_empty(). El siguiente programa corto ilustra cómo se puede usar la clase Queue: cola = cola() print(‘cola.está_vacía():’, cola.está_vacía()) queue.enqueue(’tarea1’) print(’largo(cola):’, largo(cola)) cola.poner en cola(’tarea2’) cola.poner en cola(’tarea3’) imprimir(‘cola:’, cola) 410 35 ADT, Colas y Pilas
La salida de esto es: cola inicial: [’tarea1’] cola después de las adiciones: [’tarea1’, ’tarea2’, ’tarea3’] elemento recuperado de la cola: tarea1 cola después de la eliminación [’tarea2’, ’tarea3’] cola.is_empty(): Verdadero len (cola): 1 cola: Cola: [’tarea1’, ’tarea2’, ’tarea3’] cola.mirar(): tarea1 cola.dequeue(): tarea1 cola: Cola: [’tarea2’, ’tarea3’] Esto proporciona una implementación mucho más explícita y semánticamente más significativa. Mentación de una cola que el uso de la estructura de datos de lista sin procesar. Por supuesto, Python entiende esto y proporciona una clase de contenedor de cola en el módulo de colecciones llamado deque. Esta implementación está optimizada para ser más eficiente que la Lista básica que no es muy eficiente cuando se trata de elementos emergentes del frente de la lista. 35.5 pilas Las pilas son otro ADT muy utilizado dentro de la informática y el software. aplicaciones A menudo se utilizan para evaluar expresiones matemáticas, analizar sintaxis, para la gestión de resultados intermedios, etc. Las instalaciones básicas proporcionadas por una pila incluyen: • Creación de pilas. • Agregar un elemento a la parte superior de la pila (lo que se conoce como empujar sobre la pila). • Eliminar un elemento de la parte superior de la pila (lo que se conoce como extracción de la pila). • Averigüe la longitud de la pila. • Compruebe si la pila está vacía. • Las pilas pueden ser de tamaño fijo o una pila variable (creciente). print(‘cola.mirar():’, cola.mirar()) print(‘cola.dequeue():’, cola.dequeue()) imprimir(‘cola:’, cola) 35.4 Colas 411
El comportamiento básico de una pila se ilustra mediante: Este diagrama ilustra el comportamiento de una pila. Cada vez que un nuevo elemento es empujado hacia la pila, fuerza cualquier elemento existente más abajo en la pila. De este modo el elemento más reciente está en la parte superior de la pila y el elemento más antiguo está en la parte inferior de la pila Para llegar a los elementos más antiguos, primero debe sacar los elementos más nuevos de la parte superior, etc. Muchas pilas también permiten funciones como • Arriba, que a menudo es una operación que le permite echar un vistazo al elemento en la parte superior de la pila (es decir, ver cuál es el elemento pero no eliminarlo de la cola). 35.5.1 Lista de Python como una pila Inicialmente, una Lista puede parecer particularmente adecuada para ser utilizada como una Pila como los métodos básicos append() y pop() se pueden usar para emular la pila comportamiento. Lo que se agregó más recientemente a la lista es el elemento que ser devuelto a continuación por el método pop(), por ejemplo: stack = [] # crea una pila vacía stack.append(’tarea1’) stack.append(’tarea2’) stack.append(’tarea3’) imprimir(‘pila:’, pila) elemento_superior = pila.pop() imprimir(’elemento_superior:’, elemento_superior) imprimir(‘pila:’, pila) 412 35 ADT, Colas y Pilas
Lo que produce la salida: pila: [’tarea1’, ’tarea2’, ’tarea3’] elemento_superior: tarea3 pila: [’tarea1’, ’tarea2’] Esto ciertamente funciona, aunque cuando imprimimos la pila no lo hace. claro que ’tarea3’ está al frente de la pila. Además, como cuando se usa la Lista como un ADT de cola, todavía es posible aplicar cualquiera de los otros métodos definidos en una lista a esta pila y, por lo tanto, aún podemos escribir stack.pop(0) que eliminaría el primer elemento agregado a la pila. Por lo tanto, podríamos implementar una clase Stack para envolver la lista y proporcionar comportamiento de pila adecuado como lo hicimos para la clase Queue. 35.6 Recursos en línea Para obtener más información sobre ADT, colas y pilas, consulte: • https://en.wikipedia.org/wiki/Abstract_data_type Página de Wikipedia sobre ADT (Tipos de datos abstractos). • https://en.wikipedia.org/wiki/Queue_(abstract_data_type) página de Wikipedia en la estructura de datos de la cola. • https://en.wikipedia.org/wiki/Stack_(abstract_data_type) Wikipedia página en pilas. • https://en.wikibooks.org/wiki/Data_Structures/Stacks_and_Queues Wikilibros tutorial sobre estructuras de datos Stack y Queue. 35.7 Ejercicios Implemente su propia clase Stack siguiendo el patrón utilizado para la clase Queue. La clase Stack debe proporcionar • Un método de inserción (elemento) utilizado para agregar un elemento a la pila. • Un método pop() para recuperar el elemento superior de la pila (este método elimina ese elemento de la pila). • Un método top() que le permite echar un vistazo al elemento superior de la pila (no no quitar el elemento de la pila). • Un método len() para devolver el tamaño de la pila. Este método también cumple los requisitos del protocolo len. • Un método is_empty() que verifica si la pila está vacía. • Un método str() utilizado para convertir la pila en una cadena. 35.5 pilas 413
Una vez completado, debería poder ejecutar la siguiente aplicación de prueba: pila = pila () pila.push(‘T1’) pila.push(‘T2’) pila.push(‘T3’) imprimir(‘pila:’, pila) imprimir (‘pila.está_vacío():’, pila.está_vacío()) print(‘pila.longitud():’, pila.longitud()) print(‘pila.superior():’, pila.superior()) print(‘pila.pop():’, pila.pop()) imprimir(‘pila:’, pila) Un ejemplo del tipo de salida que esto podría producir es pila: [’tarea1’, ’tarea2’, ’tarea3’] elemento_superior: tarea3 pila: [’tarea1’, ’tarea2’] pila: Pila: [‘T1’, ‘T2’, ‘T3’] stack.is_empty(): Falso pila.longitud(): 3 pila.superior(): T3 pila.pop(): T3 pila: Pila: [‘T1’, ‘T2’] 414 35 ADT, Colas y Pilas
capitulo 36 Mapear, Filtrar y Reducir 36.1 Introducción Python proporciona tres funciones que se utilizan ampliamente para implementar funcional soluciones de estilo de programación en combinación con tipos de contenedores de recolección. Estas funciones son lo que se conoce como funciones de orden superior que toman tanto un colección y una función que se aplicará de varias maneras a esa colección. Este capítulo presenta tres funciones filter(), map() y reduce(). 36.2 Filtrar La función filter() es una función de orden superior que utiliza una función para filtrar elementos de una colección. El resultado de la función filter() es una nueva iterable que contiene solo aquellos elementos seleccionados por la función de prueba. Es decir, la función pasada a filter() se usa para probar todos los elementos en el colección que también se pasa al filtro. Aquellos en los que el filtro de prueba devuelve True son incluidos en la lista de valores devueltos. El resultado devuelto es un nuevo con- iterable compuesto por todos los elementos de esta lista que satisfacen la función de prueba dada. Tenga en cuenta que el Se conserva el orden de los elementos. La sintaxis de la función filter() es filtro (función, iterable) Tenga en cuenta que el segundo argumento de la función de filtro es cualquier cosa que implemente mentos el protocolo iterable que incluye todas las listas, tuplas, conjuntos y diccionarios o y muchos otros tipos, etc. La función que se pasa como primer argumento es la función de prueba; puede ser un lambda (una función definida en línea) o el nombre de una función existente. El resultado devuelto será un iterable que se puede usar para crear una colección apropiada. © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_36 415
Estos son algunos ejemplos del uso de filtro con una lista simple de enteros: datos = [1, 3, 5, 2, 7, 4, 10] imprimir(‘datos:’, datos)
Filtrar números pares usando una función lambda
d1 = lista (filtro (lambda i: i % 2 == 0, datos)) imprimir(‘d1:’, d1) def es_par(i): devuelve i % 2 == 0
Filtrar números pares usando una función con nombre
d2 = lista (filtro (es_par, datos)) imprimir(‘d2:’, d2) La salida de esto es: Datos: [1, 3, 5, 2, 7, 4, 10] d1: [2, 4, 10] d2: [2, 4, 10] Una diferencia entre los dos ejemplos es que es más obvio cuál es el papel de la función de prueba en el segundo ejemplo como se nombra explícitamente (es decir, is_even()), esa es la función que está probando el número entero para ver si es par o no. el en línea la función lambda hace exactamente lo mismo, pero es necesario entender la prueba funciona por sí mismo para averiguar lo que está haciendo. También vale la pena señalar que definir una función con nombre como is_even() en realidad puede contaminar el espacio de nombres del módulo ya que ahora hay una función que otros podrían decidir usar aunque el diseñador original de este código nunca esperaba que alguien más usara la función is_even(). Por eso lambda Las funciones se usan a menudo con filter() (y, de hecho, map() y reduce()). Por supuesto, no solo está limitado a tipos integrados fundamentales como enteros, o números reales o incluso cadenas; se puede usar cualquier tipo. Por ejemplo, si tenemos un Persona de clase como: Persona de clase: def init(yo, nombre, edad): self.nombre = nombre self.edad = edad def str(uno mismo): return ‘Persona(’ + self.nombre + ‘, ’ + str(auto.edad) + ‘)’ 416 36 Mapear, Filtrar y Reducir
Entonces podemos crear una lista de instancias de la clase Persona y luego filtrar todas los mayores de 21: datos = [Persona(‘Alun’, 54), Persona(‘Niki’, 21), Persona(‘Megan’, 19)] para p en datos: imprimir(p, fin=’, ‘) imprimir(’\n—–’)
Use una lambda para filtrar personas mayores de 21 años
d3 = lista (filtro (lambda p: página edad <= 21, datos)) para p en d3: imprimir(p, fin=’, ‘) La salida de esto es: Persona(Alun, 54), Persona(Niki, 21), Persona(Megan, 19),
Persona(Niki, 21), Persona(Megan, 19), 36.3 Mapa El mapa es otra función de orden superior disponible en Python. Mapa aplica el suministrado a todos los elementos en los iterables que se le pasan. Devuelve un nuevo iterable del resultados generados por la función aplicada. Es el equivalente funcional de un bucle for aplicado a un iterable donde el se recopilan los resultados de cada iteración del ciclo for. La función map es muy utilizada dentro del mundo de la programación funcional y sin duda vale la pena familiarizarse con él. La firma de la función del mapa es mapa (función, iterable, …) Tenga en cuenta que el segundo argumento de la función de mapa es cualquier cosa que implemente el protocolo iterable. La función pasada a la función de mapa se aplica a cada elemento en el iterable pasado como el segundo argumento. El resultado devuelto por la función es entonces reunidos en el objeto iterable devuelto del mapa. El siguiente ejemplo aplica una función que suma uno a un número, a una lista de enteros: 36.2 Filtrar 417
datos = [1, 3, 5, 2, 7, 4, 10] imprimir(‘datos:’, datos)
Aplicar la función lambda a cada elemento de la lista
usando la función de mapa
d1 = lista (mapa (lambda i: i + 1, datos)) imprimir(‘d1’, d1) def agregar_uno(i): volver i + 1
Aplicar la función add_one a cada elemento en el
lista usando la función de mapa
d2 = lista(mapa(añadir_uno, datos)) imprimir(‘d2:’, d2) La salida del ejemplo anterior es: datos: [1, 3, 5, 2, 7, 4, 10] d1 [2, 4, 6, 3, 8, 5, 11] d2: [2, 4, 6, 3, 8, 5, 11] Al igual que con la función filter(), la función que se aplicará puede definirse en línea como una lambda o puede llamarse función como en add_one(). Se puede utilizar cualquiera de los dos, el La ventaja de la función con nombre add_one() es que hace que la intención del función explícita; sin embargo, contamina el espacio de nombres de las funciones definidas. Tenga en cuenta que se puede pasar más de un iterable a la función de mapa. si es multiple los iterables se pasan al mapa, entonces la función pasada debe tomar tantos parámetros ya que hay iterables. Esta función es útil si desea fusionar datos en dos o más colecciones en una sola colección. Por ejemplo, supongamos que queremos sumar los números de una lista a la números en otra lista, podemos escribir una función que tome dos parámetros y devuelve el resultado de sumar estos dos números: datos1 = [1, 3, 5, 7] datos2 = [2, 4, 6, 8] resultado = lista(mapa(lambda x, y: x + y, datos1, datos2)) imprimir (resultado) La salida impresa por esto es: [3, 7, 11, 15] Al igual que con la función de filtro, no son sólo los tipos integrados, como los números, los que puede ser procesado por la función suministrada al mapa; también podemos usar definido por el usuario tipos como la clase Persona. Por ejemplo, si quisiéramos recopilar todas las edades para una lista de Persona podríamos escribir: 418 36 Mapear, Filtrar y Reducir
datos = [Persona(‘Juan’, 54), Persona(‘Phoebe’, 21), Persona(‘Adán’, 19)] edades = lista(mapa(lambda p: p.edad, datos)) imprimir (edades) Lo que crea una lista de las edades de las tres personas: [54, 21, 19] 36.4 Reducir La función reduce() es la última función de orden superior que se puede usar con colecciones de datos que vamos a ver. La función reduce() aplica una función a un iterable y combina el resultado devuelto para cada elemento en un solo resultado. Esta función era parte del lenguaje central de Python 2, pero no se incluyó en el núcleo de Python 3. Esto se debe en parte a que Guido van Rossum creía (probablemente correctamente) que la aplicabilidad de reduce es bastante limitada, pero donde es útil es muy útil. Aunque hay que decir que algunos desarrolladores intentan calzarse reduce() en situaciones que simplemente hacen que la implementación sea muy difícil de entender. stand-recuerde siempre tratar de mantenerlo simple. Para usar reduce() en Python 3, debe importarlo desde el módulo functools. Un punto que a veces se malinterpreta con reduce() es que la función pasó en reducir toma dos parámetros, que son el resultado anterior y el siguiente valor en el secuencia; luego devuelve el resultado de aplicar alguna operación a estos parámetros. La firma de la función functools.reduce es: functools.reduce(función, iterable[, inicializador]) Tenga en cuenta que, opcionalmente, puede proporcionar un inicializador que se utiliza para proporcionar un valor inicial del resultado. Un uso obvio de reduce() es sumar todos los valores en una lista: de functools importar reducir datos = [1, 3, 5, 2, 7, 4, 10] resultado = reducir(total lambda, valor: total + valor, datos) imprimir (resultado) El resultado impreso para esto es 32. Aunque puede parecer que reduce() solo es útil para números como números enteros; se puede utilizar con otros tipos también. Por ejemplo, supongamos que nosotros queremos calcular la edad promedio de una lista de personas, podríamos usar reducir para agregar juntamos todas las edades y luego las dividimos por la longitud de la lista de datos que estamos procesando: 36.3 Mapa 419
datos = [Persona(‘Juan’, 54), Persona(‘Phoebe’, 21), Persona(‘Adán’, 19)] total_age = reduce(lambda running_total, persona: running_total
- persona.edad, datos, 0) edad_promedio = edad_total // len(datos) print(‘Edad promedio:’, promedio_edad) En este ejemplo de código, tenemos una lista de datos de tres personas. Entonces usamos la reducción función para aplicar una lambda a la lista de datos. La lambda toma un total_ejecutable y suma la edad de una persona a ese total. El valor cero se utiliza para inicializar esta ejecución. total. Cuando se aplique la lambda a la lista de datos, sumaremos 54, 21 y 19 juntos. Luego dividimos el resultado final devuelto por la longitud de la lista de datos (3) usando el // operador que utilizará la división floor() para devolver un número entero (en lugar de un número real como 3.11). Finalmente, imprimimos la edad promedio: Edad promedio: 31 36.5 Recursos en línea Se puede encontrar más información sobre mapear, filtrar y reducir utilizando el siguiente sitio en línea recursos: • http://book.pythontips.com/en/latest/map_filter.html Resumen de mapa, filtro y reducir. • https://www.w3schools.com/python/ref_func_map.asp El mapa de escuelas del W3C() tutorial de funciones. • https://www.w3schools.com/python/ref_func_filter.asp El filtro de escuelas W3() tutorial de funciones. • https://pymotw.com/3/functools/index.html El módulo Python de la semana página incluyendo reduce(). • https://docs.python.org/3.7/library/functools.html La biblioteca estándar de Python documentación para funtores incluyendo reduce(). 36.6 Ejercicios Este ejercicio tiene como objetivo permitirle usar mapas y filtros con su clase Stack. Tome la pila que desarrolló en el último capítulo y hágala iterable. Este se puede hacer implementando el método iter() del protocolo iterable. Como una lista se mantiene internamente en la pila, esto podría implementarse devolviendo un contenedor iterable alrededor de la lista, por ejemplo: 420 36 Mapear, Filtrar y Reducir
def iter(uno mismo): volver iter(self._list) Ahora defina una función que verificará si una cadena que se le pasó comienza con ‘Trabajo’; si devuelve True si no devuelve False. Llame a esta función is_job(). También defina una función que anteponga la cadena ’elemento’: a la cadena pasada y luego devolverá esto como el resultado de la función. Llame a esta función add_item(). Ahora debería poder usar las funciones filter() y map() con el Clase de pila como se muestra a continuación: pila = pila () stack.push(‘Tarea1’) stack.push(‘Tarea2’) stack.push(‘Trabajo1’) stack.push(‘Tarea3’) stack.push(‘Trabajo2’) print(‘contenido de la pila:’, pila)
Aplicar funciones para apilar contenidos usando mapa y filtro
lista_nueva = lista(mapa(agregar_elemento, pila)) imprimir(’nueva_lista:’, nueva_lista) lista_filtrada = lista(filtro(es_trabajo, pila)) print(’lista_filtrada: ‘, lista_filtrada) 36.6 Ejercicios 421
capitulo 37 Juego de tres en raya 37.1 Introducción En este capítulo exploraremos la creación de un TicTacToe simple (o ceros y Cruces) utilizando un enfoque Orientado a Objetos. Este ejemplo utiliza: • Clases, métodos y variables/atributos de instancia de Python. • Clases Base Abstractas y un método abstracto. • Propiedades de Python. • Listas de Python. • Una simple pieza de lógica de juego. • Bucles while, bucles for y sentencias if para comportamiento de flujo de control. El objetivo del juego es hacer una línea de 3 fichas (ya sea X u O) a través de un 3 por 3 cuadrícula. Cada jugador toma un turno para colocar una ficha. El primer jugador en lograr una línea. de tres (horizontal, vertical o diagonal) gana. 37.2 Clases en el juego Comenzaremos identificando las clases clave en el juego. Tenga en cuenta que no hay necesariamente una respuesta correcta o incorrecta aquí; aunque un conjunto de clases puede ser más obvio o más fácil de entender que otro. En nuestro caso, comenzaremos con qué datos necesitaremos representar para nuestro Juego TicTacToe como se recomienda en la ‘Introducción a la orientación a objetos’ capítulo. Nuestros elementos de datos clave incluyen: • el propio tablero de tres en raya, • los jugadores involucrados en el juego (tanto informáticos como humanos), • el estado del juego, es decir, de quién es la jugada y si alguien ha ganado, © Springer Nature Suiza AG 2019 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_37 423
• los movimientos realizados por los jugadores, etc. • los contadores utilizados que son tradicionalmente O y X (de ahí el nombre alternativo ‘Tres en raya’). Con base en un análisis de los datos, a continuación se muestra un posible conjunto de clases: En este diagrama tenemos • Juego la clase que sostendrá el tablero, los jugadores y la lógica de juego central, • Tablero esta es una clase que representa el estado actual del tablero de TicTacToe o cuadrícula dentro del juego, • Jugador humano esta clase representa al jugador humano involucrado en el juego, • Jugador de la computadora esta clase representa la computadora que juega el juego, • Mover esta clase representa un movimiento particular realizado por un jugador, • Ficha que se puede utilizar para representar las fichas con las que jugar; esto será ya sea X o Y. Podemos refinar esto un poco más. Por ejemplo, gran parte de los constituyentes de un jugador será común tanto para el humano como para el jugador de la computadora. Podemos por lo tanto, presente una nueva clase Player, con Computer Player y Jugador humano heredado de esta clase, por ejemplo: En términos de los datos que tienen las clases podemos decir: • El juego tiene un tablero, un jugador humano y una computadora. También tiene enlaces a la jugador actual y un atributo que indica si un jugador ha ganado. • El tablero contiene una cuadrícula de celdas de 3 por 3. Cada celda puede estar vacía o contener un encimera. • Jugador Cada jugador tiene un contador actual y puede ver el tablero. 424 37 Juego de tres en raya
• Mover representa el movimiento seleccionado por un jugador; por lo tanto, mantiene el contador siendo jugado y la ubicación para colocar el contador. • El mostrador tiene una etiqueta que indica X u O. Ahora podemos actualizar el diagrama de clases con datos y enlaces entre las clases: En este punto, parece que HumanPlayer y ComputerPlayer las clases son innecesarias ya que no contienen ningún dato propio. Sin embargo, en este caso de que el comportamiento de HumanPlayer y ComputerPlayer sea bastante diferente. La clase HumanPlayer le pedirá al usuario humano que seleccione el próximo movimiento. En contraste, la clase ComputerPlayer debe implementar un algoritmo que permitir que se genere el siguiente movimiento dentro del programa. Otros aspectos conductuales de las clases son: • Juego esto debe contener la lógica general del juego. También debe poder seleccionar qué jugador irá primero. También permitiremos que el jugador humano seleccione qué ficha con la que jugarán (X u O). Esta lógica también se ubicará dentro de la Clase de juego. • Tablero La clase Tablero debe permitir realizar un movimiento, pero también debe poder para verificar que un movimiento es legal (ya que la celda está vacía) y si un juego ha sido ganado o si hay un empate. Esta última lógica podría ubicarse dentro de la juego en su lugar; sin embargo, la Junta tiene los datos necesarios para determinar una victoria o un empate y así vamos ubicando la lógica con los datos. Ahora podemos agregar los aspectos de comportamiento de las clases al diagrama. Tenga en cuenta que han seguido la convención aquí para separar los datos y el comportamiento en diferentes diferentes áreas dentro de un cuadro de clase: 37.2 Clases en el juego 425
Ahora estamos listos para ver la implementación de Python de nuestro diseño de clase. 37.3 Clase de contador La clase Counter se da a continuación; es una clase orientada a datos, a veces denominada un tipo de valor. Esto se debe a que contiene valores pero no incluye ningún comportamiento. También hemos definido dos constantes X e Y para representar los contadores X y O utilizado en el juego. Contador de clase: """ Representa un contador utilizado en el tablero """ def init(uno mismo, cadena): self.etiqueta = cadena def str(uno mismo): volver self.label
Configurar contadores globales
X = Contador(‘X’) O = Contador(‘O’) 426 37 Juego de tres en raya
37.4 Mover clase La clase Move se proporciona a continuación; es otra clase orientada a datos o tipo de valor. 37.5 La clase de jugador La raíz de la jerarquía de la clase Player se presenta a continuación. Esta clase es un clase abstracta en la que el método get_move() está marcado como abstracto. El class mantiene una referencia al tablero ya un contador. Tenga en cuenta que el contador se define como una propiedad de Python. El clase Jugador es extendido por el clases jugador humano y ComputerPlayer. clase Jugador(metaclase=ABCMeta): """ Clase abstracta que representa a un jugador y su contador """ def init(uno mismo, tablero): self.tablero = tablero self._contador = Ninguno @propiedad contador def (uno mismo): """ Representa el contador de jugadores; puede ser X o Y""" volver self._counter @contra.setter contador def (uno mismo, valor): self._contador = valor @metodoabstracto def get_move(auto): pasar def str(uno mismo): return self.clase.nombre + ‘[’ + str(self.contador) + ‘]’ movimiento de clase: """ Representa un movimiento realizado por un jugador """ def init(auto, contador, x, y): self.x = x self.y = y self.contador = contador 37.4 Mover clase 427
37.6 La clase HumanPlayer Esta clase extiende la clase Player abstracta y define get_move() método. Este método devuelve un objeto Move que representa el contador que se colocará y la ubicación en la cuadrícula 3 × 3 en la que colocar el contador. Tenga en cuenta que el El método get_move() se basa en una referencia mantenida por el jugador al tablero para que pueda comprobar que la ubicación seleccionada está vacía. Para soportar el método get_move() tiene un método get_user_input() sido definido. Este método podría haberse definido como una función independiente, ya que es realmente independiente del HumanPlayer; sin embargo ha sido definido dentro de este clase para mantener el comportamiento relacionado juntos. También sigue la convención de Python. comenzando el nombre del método con un guión bajo () que indica que el método es privado y no se debe acceder desde fuera de la clase. clase HumanPlayer(Jugador): """ Representa un jugador humano y su comportamiento """ def init(uno mismo, tablero): super().init(tablero) def _get_user_input(self, prompt): invalid_input = Verdadero mientras entrada_no_válida: imprimir (prompt) entrada_usuario = entrada() si no es user_input.isdigit(): print(‘La entrada debe ser un número’) demás: usuario_entrada_int = int(usuario_entrada) si user_input_int < 1 o user_input_int > 3: print(‘La entrada debe ser un número en el rango 1 a 3’) demás: invalid_input = Falso devolver usuario_input_int - 1 def get_move(auto): """ Permitir que el jugador humano ingrese su movimiento """ mientras que es cierto: fila = self._get_user_input(‘Ingrese la fila: ‘) columna = self._get_user_input(‘Ingrese el columna: ‘) si self.board.is_empty_cell (fila, columna): return Move(self.contador, fila, columna) demás: print(‘Ese puesto no está libre’) imprimir(‘Por favor inténtalo de nuevo’) 428 37 Juego de tres en raya
37.7 La clase ComputerPlayer Esta clase proporciona una implementación algorítmica del método get_move(). Este algoritmo trata de encontrar la mejor ubicación de cuadrícula vacía en la que colocar el encimera. Si no puede encontrar una de estas ubicaciones libre, entonces encuentra aleatoriamente una vacía celda a llenar. El método get_move() podría reemplazarse con cualquier juego jugando a la lógica que quieras. clase ComputerPlayer(Jugador): """ Implementa algoritmos para jugar juegos """ def init(uno mismo, tablero): super().init(tablero) def randomly_select_cell(self): """ Usar un enfoque de selección aleatoria simplista para encontrar una celda para llenar. """ mientras que es cierto:
Seleccionar aleatoriamente la celda
fila = random.randint(0, 2) columna = random.randint(0, 2)
Comprobar para ver si la celda está vacía
si self.board.is_empty_cell (fila, columna): return Move(self.contador, fila, columna) def get_move(auto): """ Proporciona un algoritmo muy simple para seleccionar un mover""" si self.board.is_empty_cell(1, 1):
Elige el centro
return Move(self.counter, 1, 1) elif self.board.is_empty_cell(0, 0):
Elija la parte superior izquierda
return Move(self.counter, 0, 0) elif self.board.is_empty_cell(2, 2):
Elija la parte inferior derecha
volver Move(self.counter, 2, 2) elif self.board.is_empty_cell(0, 2):
Elija la parte superior derecha
return Move(self.counter, 0, 2) elif self.board.is_empty_cell(0, 2):
Elija la parte superior derecha
return Move(self.counter, 2, 0) demás: devolver self.randomly_select_cell() 37.7 La clase ComputerPlayer 429
37.8 La clase de tablero La clase Board contiene una cuadrícula de celdas de 3 por 3 en forma de lista de listas. También define la métodos usados para verificar o hacer un movimiento en el tablero. El check_for_winner() determina si hay un ganador dadas las posiciones actuales del tablero. Tablero de clase: """ El tablero de las tres en raya""" def init(uno mismo):
Configure la cuadrícula de celdas de 3 por 3
self.cells = [[’ ‘, ’ ‘, ’ ‘], [’ ‘, ’ ‘, ’ ‘], [’ ‘, ' ‘, ’ ‘]] self.separator = ‘\n’ + (’-’ * 11) + ‘\n’ def str(uno mismo): fila1 = ’ ’ + str(self.cells[0][0]) + ’ | + str(self.cells[0][1]) + ’ | ’ + str(self.cells[0][2]) fila2 = ’ ’ + str(self.cells[1][0]) + ’ | + str(self.cells[1][1]) + ’ | ’ + str(self.cells[1][2]) fila3 = ’ ’ + str(self.cells[2][0]) + ’ | + str(self.cells[2][1]) + ’ | ’ + str(self.cells[2][2]) return fila1 + auto.separador + fila2 + auto.separador + fila3 def add_move(mismo, mover): """ A un movimiento al tablero """ fila = self.celdas[mover.x] fila[movimiento.y] = movimiento.contador def is_empty_cell(self, fila, columna): """ Comprobar si una celda está vacía o no""" return self.celdas[fila][columna] == ’ ' def cell_contains(self, contador, fila, columna): """ Comprobar si una celda contiene lo proporcionado encimera """ return self.celdas[fila][columna] == contador def is_full(self): """ Comprobar si el tablero está lleno o no """ para fila en rango (0, 3): para la columna en el rango (0, 3): if self.is_empty_cell(fila, columna): falso retorno volver verdadero def check_for_winner(yo, jugador): """ Comprobar si un jugador ha ganado o no """ c = jugador.contador volver (# en la parte superior (self.cell_contains(c, 0, 0) y 430 37 Juego de tres en raya
37,9 la clase de juego La clase Game implementa el bucle de juego principal. El método play() bucle hasta que se encuentre un ganador. Cada vez que se da la vuelta al bucle, uno de los jugadores toma una gira y hace un movimiento. Luego se hace una verificación para ver si se ha ganado el juego. self.cell_contains(c, 0, 1) y self.cell_contains(c, 0, 2)) o
en el medio
(self.cell_contains(c, 1, 0) y self.cell_contains(c, 1, 1) y self.cell_contains(c, 1, 2)) o
en la parte inferior
(self.cell_contains(c, 2, 0) y self.cell_contains(c, 2, 1) y self.cell_contains(c, 2, 2)) o
por el lado izquierdo
(self.cell_contains(c, 0, 0) y self.cell_contains(c, 1, 0) y self.cell_contains(c, 2, 0)) o
por la mitad
(self.cell_contains(c, 0, 1) y self.cell_contains(c, 1, 1) y self.cell_contains(c, 2, 1)) o
por el lado derecho
(self.cell_contains(c, 0, 2) y self.cell_contains(c, 1, 2) y self.cell_contains(c, 2, 2)) o #diagonal (self.cell_contains(c, 0, 0) y self.cell_contains(c, 1, 1) y self.cell_contains(c, 2, 2)) o
otra diagonal
(self.cell_contains(c, 0, 2) y self.cell_contains(c, 1, 1) y self.cell_contains(c, 2, 0))) Juego de clase: """ Contiene la lógica de juego """ def init(uno mismo): self.tablero = Tablero() self.humano = HumanPlayer(self.tablero) self.computadora = ComputerPlayer(self.tablero) self.next_player = Ninguno self.winner = Ninguno def select_player_counter(self): """ Permitir que el jugador seleccione su contador """ contador = ’’ while not (contador == ‘X’ o contador == ‘O’): print(’¿Quieres ser X u O?’) contador = entrada (). superior () si contador != ‘X’ y contador != ‘O’: print(‘La entrada debe ser X o O’) si contador == ‘X’: self.humano.contador = X self.computadora.contador = O 37.8 La clase de tablero 431
def jugar (uno mismo): """ Bucle de reproducción del juego principal """ print(‘Bienvenido a TicTacToe’) self.select_jugador_contador() self.select_player_to_go_first() print(self.next_player, ‘jugará primero primero’) mientras que self.winner es Ninguno:
Los jugadores humanos se mueven
if self.siguiente_jugador == self.humano: imprimir(auto.tablero) print(‘Tu jugada’) mover = self.humano.get_move() self.board.add_move(mover) if self.board.check_for_winner(self.human): yo.ganador = yo.humano demás: self.next_player = self.computadora
Las computadoras se mueven
demás: print(‘Las computadoras se mueven’) mover = self.computadora.get_move() self.board.add_move(mover) if self.board.check_for_winner(self.computer): self.ganador = self.computadora demás: self.siguiente_jugador = self.humano
Comprobar si hay un ganador o un sorteo
si self.winner no es Ninguno: print(‘El ganador es el ’ + str(self.winner)) elif self.board.is_full(): print(‘El juego es un empate’) romper imprimir(auto.tablero) demás: self.humano.contador = O self.ordenador.contador = X def select_player_to_go_first(self): """ Selecciona aleatoriamente quién jugará primero - el humano o la computadora.""" si aleatorio.randint(0, 1) == 0: self.siguiente_jugador = self.humano demás: self.next_player = self.computadora 37.10 Ejecutar el juego Para ejecutar el juego, necesitamos crear una instancia de la clase Game y luego llamar a play() método sobre el objeto obtenido. Por ejemplo: 432 37 Juego de tres en raya
A continuación se muestra un resultado de muestra de la ejecución del juego en el que el ser humano los usuarios van primero. def principal(): juego = juego() Como se Juega() si nombre == ‘principal’: principal() Bienvenido a TicTacToe ¿Quieres ser X u O? X ComputerPlayer[Y] jugará primero primero Las computadoras se mueven | |
| Y |
| | Su movimiento Por favor ingrese la fila: 1 Por favor ingrese la columna: 1 Las computadoras se mueven X | |
| Y |
| | Y Su movimiento Por favor ingrese la fila: 2 Por favor ingrese la columna: 1 Las computadoras se mueven X | | Y
X | Y |
| | Y Su movimiento Por favor ingrese la fila: 3 Por favor ingrese la columna: 1 El ganador es el HumanPlayer[X] X | | Y
X | Y |
X | | Y 37.10 Ejecutar el juego 433
Corrección a: Funciones en Python Corrección a: Capítulo 11 en: J. Hunt, Una guía para principiantes a la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_11 En la versión original del libro, el texto “Rugby” ha sido reemplazado por “Python” en el Capítulo 11 página 126. Las correcciones se han realizado en el capítulo. El capítulo de la errata y el libro se han actualizado con el cambio. La versión actualizada de este capítulo se puede encontrar en https://doi.org/10.1007/978-3-030-20290-3_11 © Springer Nature Suiza AG 2020 J. Hunt, Una guía para principiantes de la programación de Python 3, Temas de Pregrado en Ciencias de la Computación, https://doi.org/10.1007/978-3-030-20290-3_38 C1
popular post
Temas de Pregrado en Ciencias de la Computación Guía avanzada a Python 3 Programación Juan caza
Read MoreTemas de Pregrado en Ciencias de la Computación Guía avanzada a Python 3 Programación Juan caza
Read More• https://docs.python.org/3/ El sitio principal de documentación de Python 3. Contiene tutoriales, referencias de bibliotecas, guías de configuración e instalación, así como Python cómo-tos • https://docs.
Read More