Capítulo 4. Funciones e interfaces
Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com
Este capítulo presenta un módulo llamado jupyturtle
, que te permite crear dibujos sencillos dando instrucciones a una tortuga imaginaria. Utilizaremos este módulo para escribir funciones que dibujen cuadrados, polígonos y círculos, y para demostrar el diseño de interfaces, que es una forma de diseñar funciones que trabajen juntas.
El módulo jupyturtle
Para utilizar el módulo jupyturtle
, podemos importarlo así:
import
jupyturtle
Ahora podemos utilizar las funciones definidas en el módulo, como make_turtle
y forward
:
jupyturtle
.
make_turtle
()
jupyturtle
.
forward
(
100
)
make_turtle
crea un lienzo, que es un espacio en la pantalla donde podemos dibujar, y una tortuga, que se representa con un caparazón circular y una cabeza triangular. El círculo muestra la ubicación de la tortuga y el triángulo indica la dirección hacia la que mira.
forward
mueve la tortuga una distancia determinada en la dirección hacia la que mira, dibujando un segmento de línea por el camino. La distancia está en unidades arbitrarias; el tamaño real depende de la pantalla de tu ordenador.
Utilizaremos muchas veces funciones definidas en el módulo jupyturtle
, por lo que estaría bien que no tuviéramos que escribir el nombre del módulo cada vez. Eso es posible si importamos el módulo así:
from
jupyturtle
import
make_turtle
,
forward
Esta versión de la declaración de importación importa make_turtle
y forward
del módulo jupyturtle
para que podamos llamarlos así:
make_turtle
()
forward
(
100
)
jupyturtle
proporciona otras dos funciones que utilizaremos, llamadas left
y right
. Las importaremos de la siguiente manera:
from
jupyturtle
import
left
,
right
left
hace que la tortuga gire a la izquierda. Toma un argumento, que es el ángulo del giro en grados. Por ejemplo, podemos hacer un giro a la izquierda de 90 grados así:
make_turtle
()
forward
(
50
)
left
(
90
)
forward
(
50
)
Este programa mueve la tortuga hacia el este y luego hacia el norte, dejando dos segmentos de línea. Antes de continuar, comprueba si puedes modificar el programa para hacer un cuadrado.
Hacer un cuadrado
Aquí tienes una forma de hacer un cuadrado:
make_turtle
()
forward
(
50
)
left
(
90
)
forward
(
50
)
left
(
90
)
forward
(
50
)
left
(
90
)
forward
(
50
)
left
(
90
)
Como este programa repite el mismo par de líneas cuatro veces, podemos hacer lo mismo de forma más concisa con un bucle for
:
make_turtle
()
for
i
in
range
(
4
):
forward
(
50
)
left
(
90
)
Encapsulación y generalización
Tomemos el código de trazado de cuadrados de la sección anterior y pongámoslo en una función llamada square
:
def
square
():
for
i
in
range
(
4
):
forward
(
50
)
left
(
90
)
Ahora podemos llamar a la función así
make_turtle
()
square
()
Envolver un trozo de código en una función se llama encapsulación. Una de las ventajas de la encapsulación es que asigna un nombre al código, que sirve como una especie de documentación. Otra ventaja es que, si reutilizas el código, ¡es más conciso llamar dos veces a una función que copiar y pegar el cuerpo!
En la versión actual, el tamaño del cuadrado es siempre 50
. Si queremos dibujar cuadrados con tamaños diferentes, podemos tomar la longitud de los lados como parámetro:
def
square
(
length
):
for
i
in
range
(
4
):
forward
(
length
)
left
(
90
)
Ahora podemos dibujar cuadrados de diferentes tamaños:
make_turtle
()
square
(
30
)
square
(
60
)
Añadir un parámetro a una función se llama generalización porque hace que la función sea más general: con la versión anterior, el cuadrado siempre tiene el mismo tamaño; con esta versión puede tener cualquier tamaño.
Si añadimos otro parámetro, podemos hacerla aún más general. La siguiente función dibuja polígonos regulares con un número determinado de lados:
def
polygon
(
n
,
length
):
angle
=
360
/
n
for
i
in
range
(
n
):
forward
(
length
)
left
(
angle
)
En un polígono regular con n
lados, el ángulo entre lados adyacentes es 360 / n
grados.
El siguiente ejemplo dibuja un polígono de 7
lados con una longitud lateral de 30
:
make_turtle
()
polygon
(
7
,
30
)
Cuando una función tiene más de unos pocos argumentos numéricos, es fácil olvidar cuáles son o en qué orden deben ir. Puede ser una buena idea incluir los nombres de los parámetros en la lista de argumentos:
make_turtle
()
polygon
(
n
=
7
,
length
=
30
)
A veces se denominan "argumentos con nombre" porque incluyen los nombres de los parámetros. Pero en Python se llaman más a menudo argumentos de palabra clave (no confundir con palabras clave de Python como for
y def
).
Este uso del operador de asignación, =
, es un recordatorio de cómo funcionan los argumentos y los parámetros: cuando llamas a una función, los argumentos se asignan a los parámetros.
Aproximación a un círculo
Supongamos ahora que queremos dibujar un círculo. Podemos hacerlo, aproximadamente, dibujando un polígono con un gran número de lados, de modo que cada lado sea lo suficientemente pequeño como para que sea difícil de ver. Aquí tienes una función que utiliza polygon
para dibujar un polígono de 30 lados que se aproxima a un círculo:
import
math
def
circle
(
radius
):
circumference
=
2
*
math
.
pi
*
radius
n
=
30
length
=
circumference
/
n
polygon
(
n
,
length
)
circle
toma el radio del círculo como parámetro. Calcula circumference
, que es la circunferencia de un círculo con el radio dado. n
es el número de lados, por lo que circumference / n
es la longitud de cada lado.
Esta función puede tardar mucho en ejecutarse. Podemos acelerarla llamando a make_turtle
con un argumento de palabra clave llamado delay
que establece el tiempo, en segundos, que la tortuga espera después de cada paso. El valor por defecto es 0.2
segundos; si lo fijamos en 0.02
, se ejecutará unas 10 veces más rápido.
make_turtle
(
delay
=
0.02
)
circle
(
30
)
Una limitación de esta solución es que n
es una constante, lo que significa que para círculos muy grandes, los lados son demasiado largos, y para círculos pequeños, perdemos tiempo dibujando lados muy cortos. Una opción es generalizar la función tomando n
como parámetro. Pero de momento vamos a simplificarlo.
Refactorización
Ahora escribamos en una versión más general de circle
, llamada arc
, que toma un segundo parámetro, angle
, y dibuja un arco de círculo que abarca el ángulo dado. Por ejemplo, si angle
es 360
grados, dibuja un círculo completo. Si angle
es 180
grados, dibuja medio círculo.
Para escribir circle
, pudimos reutilizar polygon
, porque un polígono de muchos lados es una buena aproximación a un círculo. Pero no podemos utilizar polygon
para escribir arc
.
En su lugar, crearemos la versión más general de polygon
, llamada polyline
:
def
polyline
(
n
,
length
,
angle
):
for
i
in
range
(
n
):
forward
(
length
)
left
(
angle
)
polyline
toma como parámetros el número de segmentos de línea a dibujar, n
; la longitud de los segmentos, length
; y el ángulo entre ellos, angle
.
Ahora podemos reescribir polygon
para utilizar polyline
:
def
polygon
(
n
,
length
):
angle
=
360.0
/
n
polyline
(
n
,
length
,
angle
)
Y podemos utilizar polyline
para escribir arc
:
def
arc
(
radius
,
angle
):
arc_length
=
2
*
math
.
pi
*
radius
*
angle
/
360
n
=
30
length
=
arc_length
/
n
step_angle
=
angle
/
n
polyline
(
n
,
length
,
step_angle
)
arc
es similar a circle
, salvo que calcula arc_length
, que es una fracción de la circunferencia de un círculo.
Por último, podemos reescribir circle
para utilizar arc
:
def
circle
(
radius
):
arc
(
radius
,
360
)
Para comprobar que estas funciones funcionan como es debido, las utilizaremos para dibujar algo parecido a un caracol. Con delay=0
, la tortuga corre lo más rápido posible.
make_turtle
(
delay
=
0
)
polygon
(
n
=
20
,
length
=
9
)
arc
(
radius
=
70
,
angle
=
70
)
circle
(
radius
=
10
)
En este ejemplo, partimos de un código que funcionaba y lo reorganizamos con diferentes funciones. Los cambios de este tipo, que mejoran el código sin cambiar su comportamiento, se denominan refactorización.
Si lo hubiéramos planificado con antelación, podríamos haber escrito primero polyline
y evitar la refactorización, pero a menudo no sabes lo suficiente al principio de un proyecto para diseñar todas las funciones. Una vez que empiezas a codificar, comprendes mejor el problema. A veces, refactorizar es señal de que has aprendido algo.
Diagrama de pila
Cuando llamamos a circle
, éste llama a arc
, que a su vez llama a polyline
. Podemos utilizar un diagrama de pila para mostrar esta secuencia de llamadas a funciones y los parámetros de cada una:
Observa que el valor de angle
en polyline
es diferente del valor de angle
en arc
. Los parámetros son locales, lo que significa que puedes utilizar el mismo nombre de parámetro en diferentes funciones; es una variable diferente en cada función, y puede referirse a un valor diferente.
Un plan de desarrollo
Un plan de desarrollo es un proceso para escribir programas. El proceso que utilizamos en este capítulo es "encapsulación y generalización". Los pasos de este proceso son
-
Empieza escribiendo un pequeño programa sin definiciones de funciones.
-
Una vez que consigas que el programa funcione, identifica un trozo coherente del mismo, encapsula el trozo en una función y dale un nombre. Copia y pega el código que funcione para evitar volver a escribirlo (y volver a depurarlo).
-
Repite los pasos del 1 al 3 hasta que tengas un conjunto de funciones que funcionen.
-
Busca oportunidades para mejorar el programa mediante la refactorización. Por ejemplo, si tienes código similar en varios lugares, considera la posibilidad de factorizarlo en una función adecuadamente general.
Este proceso tiene algunos inconvenientes -más adelante veremos alternativas-, pero puede ser útil si no sabes de antemano cómo dividir el programa en funciones. Este enfoque te permite diseñar sobre la marcha.
El diseño de una función tiene dos partes:
- interfaz
-
Cómo se utiliza la función, incluyendo su nombre, los parámetros que toma y lo que se supone que debe hacer la función
- aplicación
Por ejemplo, aquí tienes la primera versión de circle
que escribimos, que utiliza polygon
:
def
circle
(
radius
):
circumference
=
2
*
math
.
pi
*
radius
n
=
30
length
=
circumference
/
n
polygon
(
n
,
length
)
Y aquí está la versión refactorizada que utiliza arc
:
def
circle
(
radius
):
arc
(
radius
,
360
)
Estas dos funciones tienen la misma interfaz -toman los mismos parámetros y hacen lo mismo-, pero tienen implementaciones diferentes.
Docstrings
Una docstring es una cadena al principio de una función que explica la interfaz ("doc" es la abreviatura de "documentación"). He aquí un ejemplo:
def
polyline
(
n
,
length
,
angle
):
"""Draws line segments with the given length and angle between them.
n: integer number of line segments
length: length of the line segments
angle: angle between segments (in degrees)
"""
for
i
in
range
(
n
):
forward
(
length
)
left
(
angle
)
Por convención de , las docstrings son cadenas entre comillas triples, también conocidas como cadenas multilínea porque las comillas triples permiten que la cadena abarque más de una línea.
Un docstring debe:
-
Explica de forma concisa lo que hace la función, sin entrar en los detalles de cómo funciona,
-
Explica qué efecto tiene cada parámetro en el comportamiento de la función, y
-
Indica de qué tipo debe ser cada parámetro, si no es obvio.
Escribir este tipo de documentación es una parte importante del diseño de la interfaz. Una interfaz bien diseñada debe ser sencilla de explicar; si te cuesta explicar una de tus funciones, tal vez la interfaz podría mejorarse.
Depurando
Una interfaz es como un contrato entre una función y un llamante. La persona que llama se compromete a proporcionar ciertos argumentos y la función se compromete a realizar cierto trabajo.
Por ejemplo, polyline
requiere tres argumentos: n
tiene que ser un número entero, length
debe ser un número positivo y angle
tiene que ser un número, que se entiende en grados.
Estos requisitos se llaman precondiciones porque se supone que deben ser verdaderos antes de que la función empiece a ejecutarse. Por el contrario, las condiciones al final de la función son postcondiciones. Las postcondiciones incluyen el efecto previsto de la función (como dibujar segmentos de línea) y cualquier efecto secundario (como mover la tortuga o realizar otros cambios).
Las precondiciones son responsabilidad de quien las invoca. Si el autor de la llamada incumple una condición previa y la función no funciona correctamente, el fallo está en el autor de la llamada, no en la función.
Si las precondiciones se cumplen y las postcondiciones no, el fallo está en la función. Si tus precondiciones y postcondiciones son claras, pueden ayudar a depurar.
Glosario
diseño de la interfaz: Proceso de diseño de la interfaz de una función, que incluye los parámetros que debe tomar.
Lienzo: Ventana utilizada para mostrar elementos gráficos, como líneas, círculos, rectángulos y otras formas.
encapsulación: El proceso de transformar una secuencia de sentencias en una definición de función.
generalización: El proceso de sustituir algo innecesariamente específico (como un número) por algo adecuadamente general (como una variable o un parámetro).
argumento de palabra clave: Un argumento que incluye el nombre del parámetro.
refactorización: El proceso de modificar un programa en funcionamiento para mejorar las interfaces de las funciones y otras cualidades del código.
plan de desarrollo: Un proceso para redactar programas.
docstring: Cadena que aparece en la parte superior de la definición de una función para documentar su interfaz.
cadena multilínea: Cadena encerrada entre comillas triples que puede abarcar más de una línea de un programa.
precondición: Requisito que debe satisfacer el invocador antes de que se inicie una función.
postcondición: Requisito que debe satisfacer la función antes de que termine.
Ejercicios
Para estos ejercicios, hay algunas funciones más de tortuga que quizá quieras utilizar:
penup
-
Levanta el bolígrafo imaginario de la tortuga para que no deje rastro cuando se mueva.
pendown
La siguiente función utiliza penup
y pendown
para mover la tortuga sin dejar rastro:
from
jupyturtle
import
penup
,
pendown
def
jump
(
length
):
"""Move forward length units without leaving a trail.
Postcondition: Leaves the pen down.
"""
penup
()
forward
(
length
)
pendown
()
Ejercicio
Escribe una función llamada rhombus
que dibuje un rombo con una longitud lateral y un ángulo interior dados. Por ejemplo, aquí tienes un rombo con una longitud lateral de 50
y un ángulo interior de 60
grados:
Ejercicio
Escribe ahora una función más general llamada parallelogram
que dibuje un cuadrilátero con lados paralelos. Luego reescribe rectangle
y rhombus
para utilizar parallelogram
.
Ejercicio
Escribe un conjunto de funciones adecuadamente generales que puedan dibujar formas como ésta.
Pista: escribe una función llamada triangle
que dibuje un segmento triangular, y luego una función llamada draw_pie
que utilice triangle
.
Ejercicio
Escribe un conjunto de funciones adecuadamente generales que puedan dibujar flores como ésta.
Sugerencia: utiliza arc
para escribir una función llamada petal
que dibuje un pétalo de flor.
Pregunta a una Asistente Virtual
Varios módulos como jupyturtle
en Python, y el que utilizamos en este capítulo, han sido personalizados para este libro. Así que si pides ayuda a un asistente virtual, no sabrá qué módulo utilizar. Pero si le das algunos ejemplos con los que trabajar, probablemente pueda averiguarlo. Por ejemplo, prueba con esta instrucción y comprueba si puede escribir una función que dibuje una espiral:
The following program uses a turtle graphics module to draw a circle: from jupyturtle import make_turtle, forward, left import math def polygon(n, length): angle = 360 / n for i in range(n): forward(length) left(angle) def circle(radius): circumference = 2 * math.pi * radius n = 30 length = circumference / n polygon(n, length) make_turtle(delay=0) circle(30) Write a function that draws a spiral.
Ten en cuenta que el resultado podría utilizar funciones que aún no hemos visto, y podría tener errores. Copia el código del asistente virtual y comprueba si puedes hacerlo funcionar. Si no has conseguido lo que querías, prueba a modificar el aviso.
Get Piensa en Python, 3ª Edición now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.