Cuando trabaja con funciones, es realmente importante conocer el pedido
en el que se ejecutan las declaraciones. Esto se llama el flujo de
ejecucióny ya hemos hablado de ello varias veces en este
capítulo.
La ejecución siempre comienza en la primera declaración del programa. Las declaraciones son
ejecutado uno a la vez, en orden, de arriba a abajo.
Las definiciones de funciones no alteran el flujo de ejecución del programa, sino
Recuerde que las declaraciones dentro de la función no se ejecutan hasta que el
se llama función.
Las llamadas de funciones son como un desvío en el flujo de ejecución. En lugar de ir a
La siguiente declaración, el flujo salta a la primera línea de la función llamada,
ejecuta todas las declaraciones allí, y luego regresa para recoger donde se fue
apagado.
Eso suena bastante simple, hasta que recuerdes que una función puede llamar
otro. Mientras que en el medio de una función, el programa podría tener que ejecutar
las declaraciones en otra función. Pero mientras ejecuta esa nueva función, el
¡El programa podría tener que ejecutar otra función!
Afortunadamente, Python es experto en realizar un seguimiento de dónde está, por lo que cada vez un
Completa la función, el programa aumenta donde dejó en la función que
lo llamó. Cuando llega al final del programa, termina.
¿Cuál es la moraleja de esta sórdida cuento? Cuando leas un programa, no leas desde
de arriba a abajo. En cambio, siga el flujo de ejecución. Esto significa que leerás el def declaraciones como tú
están escaneando de arriba a abajo, pero debe omitir el cuerpo de la función
Hasta que llegues a un punto donde se llama a esa función.
Comprueba tu comprensión
1. Considere el siguiente código Python. Tenga en cuenta que los números de línea se incluyen a la izquierda.
1)defpow(b,p):
2) y=b**p
3) returny
4)
5)defsquare(x):
6) a=pow(x,2)
7) returna
8)
9)n=5
10)result=square(n)
11)print(result)
¿Cuál de las siguientes siguientes refleja el orden en que se procesan estas líneas de código en Python?
Es importante comprender que cada una de las funciones que escribimos se puede usar
y llamado desde otras funciones que escribimos. Este es uno de los más importantes
formas en que los científicos informáticos toman un gran problema y lo dividen en un
grupo de problemas más pequeños. Este proceso de romper un problema en más pequeños
subproblemas se llama descomposición funcional.
Aquí hay un ejemplo simple de descomposición funcional utilizando dos funciones. El
Primera función llamadasquare Simplemente calcula el cuadrado de un número dado.
La segunda función llamó sum_of_squares utiliza el cuadrado para calcular
La suma de tres números que han sido cuadrados.
>>
def square(x): 2 y = x * x 3 return y 4 5 def sum_of_squares(x, y, z): 6 a = square(x) 7 b = square(y) 8 c = square(z) 9 10 return a + b + c 11 12 a = -5 13 b = 2 14 c = 10 15 result = sum_of_squares(a, b, c) 16 print(result)
>>
Aunque esta es una idea bastante simple, en la práctica este ejemplo
ilustra muchos conceptos de Python muy importantes, incluidos los locales y globales
Variables junto con el paso de parámetros. Tenga en cuenta que cuando atraviesas esto
Ejemplo, Codelens Bolds Line 1 y Line 5 a medida que se definen las funciones. El
cuerpo de cuadrado no se ejecuta hasta que se llama desde elsum_of_squaresfunción por primera vez en la línea 6. También tenga en cuenta que cuando square es
llamado hay dos grupos de variables locales, uno para squarey uno
para sum_of_squares. A medida que avance, notará que x, y y son variables locales en ambas funciones e incluso pueden tener
diferentes valores. Esto ilustra que a pesar de que se denominan iguales,
De hecho, son muy diferentes.
Ahora veremos otro ejemplo que usa dos funciones. Este ejemplo ilustra un
importante técnica de resolución de problemas de informática llamada
generalización. Suponga que queremos escribir una
función para dibujar un cuadrado. El paso de generalización es darse cuenta de que un
Square es solo un tipo especial de rectángulo.
Para dibujar un rectángulo necesitamos poder llamar a una función con diferente
Argumentos de ancho y altura. A diferencia del caso de la plaza,
No podemos repetir lo mismo 4 veces, porque los cuatro lados no son iguales.
Sin embargo, es el caso que dibujar los lados inferior y derecho son los
La misma secuencia que dibujar los lados superior e izquierdo. Entonces finalmente se nos ocurrió
Este código bastante bonito que puede dibujar un rectángulo.
>>def drawRectangle(t, w, h): >> """Get turtle t to draw a rectangle of width w and height h.""" >> for i in range(2): >> t.forward(w) >> t.left(90) >> t.forward(h) >> t.left(90)
Los nombres de los parámetros se eligen como letras individuales para la concisión.
En programas reales, insistiremos en mejores nombres de variables que este.
El punto es que el programa no "entiende" que estás dibujando un rectángulo o que el
Los parámetros representan el ancho y la altura. Conceptos como rectángulo, ancho y altura son significativos
para humanos. No son conceptos que el programa o la computadora entiendan.
Pensar como un científico informáticoimplica buscar patrones y
relaciones. En el código anterior, lo hemos hecho hasta cierto punto. Hicimos
No solo dibuja cuatro lados. En cambio, vimos que podríamos dibujar el
Rectángulo como dos mitades y usó un bucle para repetir ese patrón dos veces.
Pero ahora podríamos detectar que un cuadrado es un tipo especial de rectángulo. Un cuadrado
Simplemente usa el mismo valor tanto para la altura como para el ancho.
Ya tenemos una función que dibuja un rectángulo, por lo que podemos usarla para dibujar
nuestra plaza.
>>def drawSquare(tx, sz): # a new version of drawSquare >> drawRectangle(tx, sz, sz)
Aquí está el ejemplo completo con el código de configuración necesario.
Reescritura drawSquare como este captura la relación
que hemos visto.
Una persona que llama de esta función podría decir drawSquare(tess,50). Los parámetros
de esta función, txy sz, se les asigna los valores del objeto tess, y
El entero 50 respectivamente.
En el cuerpo de la función, tzy sz son como cualquier otra variable.
Cuando se hace la llamada a drawRectangle, los valores en las variables txy sz
se obtienen primero, luego la llamada ocurre. Entonces, cuando entramos en la parte superior de
función drawRectangle, su variable t se le asigna el objeto tess, y wy
h En esa función se les da el valor 50.
Hasta ahora, puede no estar claro por qué vale la pena crear todos estos
Nuevas funciones. En realidad, hay muchas razones, pero este ejemplo
Demuestra dos:
Crear una nueva función le brinda la oportunidad de nombrar a un grupo de
declaraciones. Las funciones pueden simplificar un programa ocultando un cálculo complejo
detrás de un solo comando. La función (incluido su nombre) puede capturar su
La fragmentación mental o la abstracción del problema.
Crear una nueva función puede hacer que un programa sea más pequeño al eliminar repetitivo
código.
A veces puedes escribir funciones que te permitan resolver un
Problema usando una solución más general.
Laboratorio
Dibujando un círculo en este ejercicio de laboratorio guiado, trabajaremos
A través de un simple ejercicio de resolución de problemas relacionados con el dibujo de un círculo con la tortuga.
En el ejemplo anterior, escribimos una función que calcula el cuadrado de un número. El algoritmo que usamos
En la función era simple: multiplique el número por sí mismo.
En esta sección volveremos a implementar la función cuadrada y usaremos un algoritmo diferente, uno que se basa en la adición.
de multiplicación.
Si desea multiplicar dos
números juntos, el enfoque más básico es pensar que se repite el proceso
de
agregando un número a sí mismo. El número de repeticiones es donde entra
en juego el segundo número. Por ejemplo, si
Quería multiplicar tres y cinco, podríamos pensar en ello como agregar
tres a sí mismo cinco veces. Tres más tres son seis, más tres son nueve,
más tres son 12, y finalmente más tres es 15. Generalizando esto, si
queremos implementar
La idea de cuadrar un número, llamarlo n , se agregaríamos a sí mismo n veces.
Haga esto a mano primero e intente aislar exactamente qué pasos tomas. Tu
Encuentra que necesitas mantener un "total en funcionamiento" de la suma hasta ahora, ya sea en una pieza
de papel, o en tu cabeza. Recordar cosas de un paso a otro es
Precisamente por qué tenemos variables en un programa. Esto significa que necesitaremos alguna variable
Para recordar el "total de ejecución". Debe inicializarse con un valor de cero. Luego, necesitamos actualizar el "total en ejecución" el número correcto de veces. Para cada repetición, queremos
Para actualizar el total de ejecución agregando el número.
En palabras podríamos decirlo de esta manera. Para cuadrar el valor de n , repetiremos el proceso de actualización de un total de ejecución n veces. Para actualizar el total de ejecución, tomamos el valor anterior del "total en ejecución" y agregamos n . Esa suma se convierte en la nueva
valor del "total de ejecución".
Aquí está el programa en
Activecode. Tenga en cuenta que el encabezado de la definición de
función es el mismo que antes. Todo lo que ha cambiado
son los detalles de cómo se realiza el cuadrado. Este es un gran ejemplo
de diseño de "caja negra". Podemos cambiar los detalles dentro de la
caja y aún usar la función exactamente como lo hicimos antes.
>>def square(x): >> runningtotal = 0 >> for counter in range(x): >> runningtotal = runningtotal + x
En el programa anterior, observe que la variable runningtotal comienza con un valor de 0. A continuación, la iteración se realiza x veces. Dentro del bucle for, se produce la actualización. runningtotal se reasigna un nuevo valor que es el valor anterior más el valor de x.
Este patrón de iterar la actualización de una variable es comúnmente
referido como el patrón del acumulador . Nos referimos a la variable como acumulador . Este patrón aparecerá una y otra vez. Recuerda que la clave
Hacer que funcione con éxito es asegurarse de inicializar la variable antes de comenzar la iteración.
Una vez dentro de la iteración, se requiere que actualice el acumulador.
Nota
¿Qué pasaría si pusiéramos la tarea? runningTotal=0 adentro
el para la declaración? ¿No está seguro? Pruébelo y averigüe.
6.5.1. El patrón de acumulador general
initializetheaccumulatorvariablerepeat:modifytheaccumulatorvariable# when the loop terminates the accumulator has the correct value
Nota
Este espacio de trabajo se
proporciona para su conveniencia. Puede usar esta ventana ActiveCode
para probar cualquier cosa que desee.
Un caso de prueba expresa los requisitos para un programa, de una manera que se puede verificar automáticamente. Específicamente, una prueba
afirma algo sobre el estado del programa en un punto particular en su ejecución. Una prueba unitaria es
una automática
Procedimiento utilizado para validar que las unidades individuales de
código funcionan correctamente. Una función es una forma de una unidad.
Una colección de estas pruebas unitarias se llama suite de prueba .
Anteriormente hemos sugerido que es una buena idea escribir primero comentarios sobre lo que se supone que debe hacer su código,
antes de escribir el código. Es una idea aún mejor escribir algunos casos de prueba antes de escribir un programa.
Hay varias razones por las que es un buen hábito escribir casos de prueba.
Antes de escribir código, tenemos en mente lo que debería hacer,
pero esos pensamientos pueden ser un poco vagos. Escribir casos de
prueba nos obliga a ser más concretos sobre lo que debería suceder.
A medida que escribimos el
código, los casos de prueba pueden proporcionar comentarios
automatizados. En realidad, ha sido el beneficiario de tales comentarios
automatizados a través de casos de prueba a lo largo de este libro en
algunas de las ventanas de Activecode y casi todos los ejercicios.
Escribimos el código para esos casos de prueba, pero lo mantuvimos
oculto, para no confundirlo y también evitar regalar las respuestas.
Puede obtener algunos de los mismos beneficios al escribir sus propios
casos de prueba.
En proyectos de software
más grandes, el conjunto de casos de prueba se puede ejecutar cada vez
que se realiza un cambio en la base del código. Pruebas unitarias verifique que los pequeños bits de código se implementen correctamente.
Una forma de implementar pruebas unitarias en Python es con assert.
Siguiendo la palabra afirmar que habrá una expresión de Python.
Si esa expresión se evalúa al booleano False, entonces el intérprete aumentará un error de tiempo de ejecución.
Si la expresión evalúa a True, entonces nada sucede y la ejecución pasa a la siguiente línea de código.
Eche un vistazo a la forma en que Afirmar se usa en el siguiente código.
>>assert type(9//5) == int >>assert type(9.0//5) == int
>>>
Error
AssertionError: en la línea 2
Descripción
Un
error de afirmación ocurre cuando Python encuentra una declaración de
afirmación. Python evalúa la expresión a la derecha de la palabra
afirmar; Si esa expresión es verdadera, todo está bien y el programa
continúa. Si la expresión es falsa, Python plantea un error y se
detiene.
Para arreglar
Verifique
la expresión a la derecha de afirmar. La expresión es falsa y deberá
determinar por qué es así. Es posible que simplemente desee imprimir las
partes individuales de la expresión para comprender por qué se está
evaluando a False.
Explicación:
En el código anterior,
establecemos explícitamente algunos supuestos naturales sobre cómo la
división truncada podría funcionar en Python.
Resulta que el segundo asumio está mal: 9.0//5 produce 2.0, un valor de punto flotante!
El intérprete de Python no
hace cumplir las restricciones sobre los tipos de datos de objetos que
pueden estar vinculados a particular
variables; Sin embargo, la comprobación de tipos podría alertarnos de
que algo ha salido mal en la ejecución de nuestro programa. Si somos
asumiendo eso x es una lista, pero en realidad es un entero, luego en algún momento más adelante en la ejecución del programa,
Probablemente habrá un error. Podemos agregar assert declaraciones que harán que un error se marque antes
que más tarde, lo que podría hacer que sea mucho más fácil depurar.
Comprueba tu comprensión
Cuando assertx==y se ejecuta e x e y tienen los mismos valores, ¿qué sucederá?
RESPUESTA: D
6.3.1. assert con for loops
¿Por qué alguna vez querrías
escribir una línea de código que nunca pueda calcular nada útil para ti,
pero a veces causa?
¿Un error de tiempo de ejecución? Por todas las razones que describimos
anteriormente sobre el valor de las pruebas automatizadas. Quieres una
prueba que
Alertará que usted de que alguna condición que asumió que era cierto no
es cierto. Es mucho mejor ser alertado para
ese hecho de inmediato que tener algún resultado inesperado mucho más
tarde en la ejecución de su programa, que tendrá
Problemas para trazar al lugar donde tuvo un error en su código.
¿Por qué no assert
imprime algo que dice que la prueba pasó? La razón es que no quieres
desordenar
Sube su ventana de salida con los resultados de las pruebas
automatizadas que pasan. Solo quieres saber cuándo una de tus pruebas
falla. En proyectos más grandes, se utilizan otros arneses de prueba en
lugar de assert, como el pitón unittest
módulo. Esos proporcionan algunas pruebas de resumen de salida que han
pasado, así como las que fallaron. En este libro de texto,
Simplemente usaremos simple assert declaraciones para pruebas automatizadas.
En el código a continuación, lst está vinculado a un objeto de lista. En pitón , no todos los elementos de una lista deben ser de la
mismo tipo. Podemos verificar que todos tengan el mismo tipo y obtener un error si no lo son. Observe que con lst2,
Una de las afirmaciones falla.
Un
error de afirmación ocurre cuando Python encuentra una declaración de
afirmación. Python evalúa la expresión a la derecha de la palabra
afirmar; Si esa expresión es verdadera, todo está bien y el programa
continúa. Si la expresión es falsa, Python plantea un error y se
detiene.
Para arreglar
Verifique
la expresión a la derecha de afirmar. La expresión es falsa y deberá
determinar por qué es así. Es posible que simplemente desee imprimir las
partes individuales de la expresión para comprender por qué se está
evaluando a False.
6.3.2. Pruebas de valor de retorno
Probar si una función devuelve el valor correcto es el caso de prueba más fácil de definir. Simplemente verifique si el
El resultado de invocar la función en una entrada particular produce la salida particular que espera. Echar un vistazo a
el siguiente código.
>>def square(x): >>#raise x to the second power >> return x*x >>print('testing square function') >>assert square(3) == 9
>>>
testing square function
Debido a que cada prueba verifica
si una función funciona correctamente en entradas específicas, los
casos de prueba nunca estarán completos: en
Principio, una función podría funcionar correctamente en todas las
entradas que se prueban en los casos de prueba, pero aún no funcionan
correctamente en otras entradas. Ahí es donde entra el arte de definir
los casos de prueba: intenta encontrar entradas específicas que
son representativos de todos los tipos importantes de entradas que
podrían pasar a la función.
Para el juego de Hangman, esta
función 'en blanco' toma una palabra y algunas letras que se han
adivinado, y devuelve una versión
de la palabra con _ para todas las letras que no se han adivinado. ¿Cuál
de las siguientes es la forma correcta de escribir?
una prueba para verificar que 'debajo' se mantendrá en blanco como 'u_d__' ¿Cuándo el usuario ha adivinado las letras D y usted hasta ahora?
La mayoría de las funciones requieren argumentos, valores que controlan cómo la función hace su
trabajo. Por ejemplo, si desea encontrar el valor absoluto de un número, tiene que indicar cuál es el número. Python tiene una función incorporada para la computación
el valor absoluto:
>>print(abs(5))
>>print(abs(-5))
Actividad: 6.2.1 Activecode (CH04_4)
En este ejemplo, los argumentos a la abs La función son 5 y -5.
Algunas funciones toman más de un argumento. Por ejemplo, el módulo matemático contiene una función
llamada
pow que toma dos argumentos, la base y el exponente.
>>importmath
>>print(math.pow(2, 3))
>>print(math.pow(7, 4))
Actividad: 6.2.2 Activecode (CH04_5)
Nota:
Por supuesto, ya hemos visto que elevar una base a un exponente se puede hacer con el operador **.
Otra función incorporada que toma más de un argumento es max.
>>print(max(7, 11))
>>print(max(4, 1, 17, 2, 12))
>>print(max(3*11, 5**3, 512-9, 1024**0))
Actividad: 6.2.3 Activecode (CH04_6)
maxse puede enviar cualquier número de argumentos, separados por comas, y
devolver el valor máximo enviado. Los argumentos pueden ser valores simples o
Expresiones. En el último ejemplo, se devuelve 503, ya que es mayor que 33,
125 y 1. Tenga en cuenta quemaxTambién funciona en listas de valores.
Además, funciones como range, int, abs Todos los valores de retorno que
se puede usar para construir expresiones más complejas.
Entonces, una diferencia importante entre estas funciones y una como drawSquare es que
drawSquare no fue ejecutado porque queríamos que calculara un valor, por el contrario,
escribimos drawSquare Porque queríamos que ejecutara una secuencia de pasos que causaron
la tortuga para dibujar una forma específica.
Las funciones que devuelven los valores a veces se llaman funciones fructíferas.
En muchos otros idiomas, una parte que no devuelve un valor se llama procedimiento ,
Pero nos quedaremos aquí con la forma de Python de llamarlo también una función, o si queremos
Para estresarlo, una no fruitante función
Las funciones fructíferas aún permiten al usuario proporcionar información (argumentos). Sin embargo, ahora hay un adicional de
Datos que se devuelven de la función.
¿Cómo escribimos nuestra propia función fructífera? Comencemos por crear un muy simple
función matemática que llamaremos square. La función cuadrada tomará un número
Como parámetro y devuelve el resultado de cuadrar ese número. Aquí está el
Diagrama de caja negra con el código Python siguiente.
>>defsquare(x):
>>y=x*x
>>returny
>>toSquare=10
>>result=square(toSquare)
>>print("The result of", toSquare, "squared is", result)
>>> el resultado de 10 square es 100
Actividad: 6.2.4 Activecode (CH04_Square)
La de retorno es seguida por una expresión que se evalúa. Es
El resultado se devuelve a la persona que llama como la "fruta" de llamar a esta función.
Porque la declaración de retorno puede contener cualquier expresión de pitón que podríamos tener
evitó crear la variable temporal y y simplemente usado
returnx*x.
Intente modificar la función cuadrada anterior para ver que esto funciona de la misma manera.
Por otro lado, usando variables temporales como y En el programa anterior hace
depuración
más fácil. Estas variables temporales son ejemplos de variables locales , perseguidas más
En la siguiente sección.
Observe algo importante aquí. El nombre de la variable que pasamos como un
argumento - toSquare - no tiene nada que ver con el nombre del parámetro formal
- x. Es como si x=toSquare se ejecuta cuando square se llama.
No importa cómo se llamara el valor en
la persona que llama. En square, su nombre es x. Puedes ver esto muy claramente en
Codelens, donde las variables globales (variables definidas fuera de cualquier función) y las variables locales para el cuadrado
La función está en cajas separadas.
A medida que avanza el ejemplo en Codelens, observe que la de devolución no solo causa el
función para devolver un valor, pero también devuelve el flujo de control al lugar del programa
donde se realizó la llamada de función. Esto es cierto en general:
Nota
La llamada a una función terminadespués de la ejecución de una declaración de devolución.
Esto es bastante obvio si la declaración de retorno es la última declaración en la función, pero
Veremos más tarde dónde tiene sentido tener una declaración de devolución incluso cuando otras declaraciones
Sigue y las declaraciones adicionales no ejecutan.
Salida de impresión (arrastre la esquina inferior derecha para cambiar el tamaño)
marcos
Objetos
Actividad: Codelens 6.2.5 (CH04_CLSQUARE)
Otra cosa importante a notar a
medida que atraviesa este codelens
La demostración es el movimiento de las flechas rojas y verdes. Codelens
usa estas flechas para mostrarle dónde se está ejecutando actualmente.
Recuerde que la flecha roja siempre apunta a la siguiente línea de
código que se ejecutará. La flecha verde claro apunta a la línea
Eso se ejecutó en el último paso.
Cuando comience a ejecutar
esta demostración de Codelens, notará que solo hay una flecha roja y
apunta a
Línea 1. Esto se debe a que la línea 1 es la siguiente línea que se
ejecutará y, dado que es la primera línea, no hay línea ejecutada
previamente
de código.
Cuando haga clic en el botón
Forward, observe que la flecha roja se mueve a la línea 5, omitiendo las
líneas 2 y 3 de la función (y
La flecha verde claro ha aparecido en la línea 1). ¿Por qué es esto?
La respuesta es que la definición de función no es la misma que la
ejecución de la función. Líneas 2
y 3 no se ejecutará hasta que la función se llame en la línea 6. La
línea 1 define la función y el nombre square se agrega al
variables globales, pero eso es todo el def
lo hace en ese punto. El cuerpo de la función se ejecutará más tarde.
Continuar haciendo clic
El botón de avance para ver cómo se mueve el flujo de control desde la
llamada, regresa al cuerpo de la función, y finalmente regresa a la
línea 7, después de que la función ha devuelto su valor y el valor se ha
asignado a squareResult.
Finalmente, hay un aspecto más
de los valores de retorno de la función que se debe tener en cuenta.
Todas las funciones de Python devuelven el valor None A menos que haya una declaración de devolución explícita con
un valor que no sea None.
Considere el siguiente error común cometido al comenzar Python
Programadores. A medida que atraviesa este ejemplo, preste mucha atención a la devolución
Valor en la lista de variables locales. Luego mira lo que se imprime cuando el
devoluciones de la función.
1
Def cuadrado (x):
2
y = x * x
3
Imprima (y) # mal! ¡Debería usar retorno en su lugar!
Salida de impresión (arrastre la esquina inferior derecha para cambiar el tamaño)
marcos
Objetos
Actividad: Codelens 6.2.6 (CH04_CLSQUARE_BAD)
El problema con esta función
es que a pesar de que imprime el valor del cuadrado, ese valor no se
devolverá al lugar
donde se hizo la llamada. Dado que la línea 6 usa el valor de retorno
como el lado derecho de una declaración de asignación, la evaluación del
la función será None. En este caso, squareResult
se referirá a ese valor después de la declaración de asignación y, por
lo tanto, el resultado impreso en la línea 7 es incorrecto. Por lo
general, las funciones devolverán valores que la persona que llama puede
imprimir o procesar de alguna otra manera.
Comprueba tu comprensión
¿Qué está mal con la siguiente definición de función: