Introducción a la programación en Python
clase 2

Funciones (I)

Una función es un bloque de código con un nombre asociado, que recibe 0 o más argumentos como entrada, sigue una serie de instrucciones, y devuelve un valor y/o realiza una tarea.

Python dispone de una serie de funciones integradas al lenguaje (built-in), y también permite crear funciones definidas por el usuario.

El uso de funciones es un componente muy importante de la programación estructurada, y tiene varias ventajas:

  • modularización: permite segmentar un programa complejo en una serie de partes o módulos más simples, facilitando así la programación y el depurado
  • reutilización: permite reutilizar una misma función en distintos programas

Funciones integradas (built-in)

  • Repasemos primero las funciones integradas vistas en la clase 1.

int()

Devuelve un número entero, dependiendo del tipo de argumento o argumentos que reciba.

Si se ingresa un número de coma flotante (objeto tipo float), devuelve su parte entera.

In [1]:
int(2.5)
Out[1]:
2

También puede devolver el entero correspondiente a una cadena de caracteres (objeto tipo string).

Por defecto, usa la base 10, en cuyo caso la cadena debe estar conformada sólo por números decimales.

In [2]:
int("23")
Out[2]:
23

Pero también puede convertir cadenas en otras bases, si se explicita un segundo argumento. En ese caso, la cadena de caracteres debe ser coherente con una expresión numérica en la base.

In [3]:
int("10110101101", 2)
Out[3]:
1453
In [4]:
int("a0fc", 16)
Out[4]:
41212
In [5]:
int("3251", 8)
Out[5]:
1705
In [6]:
int("1012", 2)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-6-1e9b067ef99b> in <module>()
----> 1 int("1012", 2)

ValueError: invalid literal for int() with base 2: '1012'

float()

Devuelve un número de coma flotante, y accepta argumentos de tipo numérico (int o float), o de tipo string que sean coherentes con un número entero o de coma flotante (cadenas de caracteres formadas por números y hasta un punto).

In [7]:
float(2)
Out[7]:
2.0
In [8]:
float(2.5)
Out[8]:
2.5
In [9]:
float("2")
Out[9]:
2.0
In [10]:
float("2.5")
Out[10]:
2.5

print()

Uno de los cambios más notorios de Python2 a Python3 fue que la instrucción print fue convertida en una función.

  • En Python2:

print "Hola, Python"

  • En Python2:

print("Hola, Python")

Como función, print() recibe uno o más argumentos, que por defecto se imprimen en la salida estándar.
Los argumentos, si son más de uno, deben ir separados por comas.

In [11]:
print(1, 12, 123, "hola")
1 12 123 hola

La función print() puede recibir también algunos argumentos ocpionales, que deberán necesariamente pasarse con su nombre.

  • sep: cadena de separación entre los argumentos, si la función recibe más de uno. Por defecto es un espacio en blanco (sep=' '), pero puede ser cualquier otra cadena.
In [12]:
print(1, 12, 123, "hola", sep='__')
1__12__123__hola

Puede ser también un carácter extendido, como tabulador (\t) o salto de línea (\n).

In [13]:
print(1, 12, 123, "hola", sep='\t')
1	12	123	hola

In [14]:
print(1, 12, 123, "hola", sep='\n')
1
12
123
hola

  • end: cadena que se imprime al final de la línea. Por defecto es un salto de línea (end='\n'), pero puede ser otra cadena.
In [15]:
print(1, 12, 123, "hola", end=' - ')
print("otra llamada a la función, en la misma línea")
1 12 123 hola - otra llamada a la función, en la misma línea

Hay otros parámetros opcionales para la función print():

  • file: indica dónde se va a imprimir. Por defecto es la salida estándar (la consola en la pantalla), pero se puede indicar cualquier objeto en el que se pueda escribir, como por ejemplo un archivo.
  • flush: determina la existencia de un buffer en la salida a file. Por defecto es False, pero si se explicita como True, se fuerza el volcado inmediato (flush).

Formato de las cadenas de impresión

En los argumentos que se pasan a la función print() se pueden alternar cadenas de caracteres y variables:

In [16]:
operacion = "raíz cuadrada de dos"
valor = 2**0.5
print("el resultado de", operacion, "es:", valor)
el resultado de raíz cuadrada de dos es: 1.4142135623730951

Una forma más clara y elegante es referenciar objetos dentro de la misma cadena, y usar el método .format() para sustituirlos con los objetos que se le pasan como argumentos.

Los objetos se referencian con números entre llaves ({ }) dentro de la cadena (llamados campos de formato), y son sustituidos en el orden con que aparecen como argumentos de .format(), contando a partir de cero (argumentos posicionales).

In [17]:
operacion = "raíz cuadrada de dos"
valor = 2**0.5
print("el resultado de {0} es {1}".format(operacion, valor))
el resultado de raíz cuadrada de dos es 1.4142135623730951

Los objetos también pueden ser referenciados por nombre (argumentos por clave).

In [18]:
operacion = "raíz cuadrada de dos"
print("el resultado de {nombre} es {resultado}".format(nombre=operacion, resultado=2**0.5))
el resultado de raíz cuadrada de dos es 1.4142135623730951

En una misma llamada a la función print() pueden combinarse argumentos posicionales con argumentos por clave.

Opcionalmente se puede poner el signo de dos puntos después del número o nombre, y explicitar el tipo del objeto:

  • s para cadenas (tipo str)
  • d para números enteros (tipo int)
  • f para números de coma flotante (tipo float)

Esto permite controlar el formato de impresión del objeto. Por ejemplo, podemos utilizar la expresión .4f para determinar que un número de coma flotante (f) se imprima con cuatro dígitos después de la coma (.4).

In [19]:
operacion = "raíz cuadrada de dos"
valor = 2**0.5
print("el resultado de {0} es {resultado:.4f}".format(operacion, resultado=valor))
el resultado de raíz cuadrada de dos es 1.4142

También puede aplicarse el operador % para dar formato a las cadenas.
La sintaxis es la siguiente:

In [20]:
operacion = "raíz cuadrada de dos"
valor = 2**0.5
print("el resultado de %s es %f" % (operacion, valor))
el resultado de raíz cuadrada de dos es 1.414214

Con esta sintaxis hay que determinar el tipo del objeto:

  • %s = str
  • %d = int
  • %f = float

También aquí se puede controlar el formato de salida. Por ejemplo, para obtener el valor con 8 dígitos después de la coma:

In [21]:
operacion = "raíz cuadrada de dos"
valor = 2**0.5
print("el resultado de %s es %.8f" % (operacion, valor))
el resultado de raíz cuadrada de dos es 1.41421356

Una discusión más completa del control de la salida de Python se encuentra en https://docs.python.org/3/tutorial/inputoutput.html

input()

Como se vio en la clase 1, la función input() puede utilizarse para ingresar datos durante la ejecución del programa.
Opcionalmente, la función puede recibir un argumento, que de estar presente se imprime en la pantalla sin salto de línea final.
La función input() siempre devuelve un objeto de tipo str. Si el valor de entrada va a utilizarse como un número, deberá hacerse la conversión explícitamente.

In [22]:
entrada = int(input("ingrese un número entero: "))
print(entrada**2)
ingrese un número entero: 4
16

  • Veamos ahora algunas otras funciones integradas al lenguaje.

abs()

Devuelve el valor absoluto de un número (entero o de coma flotante).

In [23]:
abs(3)
Out[23]:
3
In [24]:
abs(-3)
Out[24]:
3
In [25]:
abs(-2.5)
Out[25]:
2.5

chr()

Recibe como argumento un entero, y devuelve una cadena con el carácter cuyo código Unicode corresponde a ese valor. El rango válido para el argumento es de 0 a 1114111.

In [26]:
chr(97)
Out[26]:
'a'
In [27]:
chr(934)
Out[27]:
'Φ'
In [28]:
chr(1044)
Out[28]:
'Д'
In [29]:
chr(19989)
Out[29]:
'丕'
In [30]:
chr(43457)
Out[30]:
'꧁'

divmod()

Debe recibir dos argumentos numéricos, y devuelve dos valores: resultado de la división entera, y el resto.

In [31]:
divmod(22, 4)
Out[31]:
(5, 2)

help()

Invoca el sistema integrado de ayuda, devolviendo información acerca del objeto que recibe como argumento.

In [32]:
help(divmod)
Help on built-in function divmod in module builtins:

divmod(...)
    divmod(x, y) -> (div, mod)
    
    Return the tuple ((x-x%y)/y, x%y).  Invariant: div*y + mod == x.


hex()

Convierte un número entero a una cadena en base hexadecimal, antecedida del prefijo '0x'.

In [33]:
hex(16)
Out[33]:
'0x10'
In [34]:
hex(127)
Out[34]:
'0x7f'

len()

Devuelve la longitud (cantidad de elementos) de un objeto. Puede ser una cadena de caracteres, o alguno de los otros tipos de secuencia o colección todavía no vistos.

In [35]:
len("Hola, Python")
Out[35]:
12
In [36]:
len("0123456789")
Out[36]:
10

max()

Si recibe más de un argumento, devuelve el mayor de ellos.

In [37]:
max(23, 12, 145, 88)
Out[37]:
145
In [38]:
max("a", "Z")
Out[38]:
'a'

Si recibe un solo argumento, devuelve el mayor de sus elementos. Debe ser un objeto iterable; puede ser una cadena de caracteres, o alguno de los otros tipos de secuencia o colección todavía no vistos.

In [39]:
max("Hola, Python")
Out[39]:
'y'

min()

Tiene un comportamiento similar a max(), pero devuelve el mínimo.

In [40]:
min(23, 12, 145, 88)
Out[40]:
12
In [41]:
min("Hola, Python")
Out[41]:
' '

oct()

Convierte un número entero en una cadena en base octal, antecedida del prefijo '0o'.

In [42]:
oct(8)
Out[42]:
'0o10'
In [43]:
oct(123)
Out[43]:
'0o173'

ord()

Es el inverso de chr(): dada una cadena representando un carácter Unicode, devuelve el entero del código correspondiente.

In [44]:
ord('w')
Out[44]:
119

Como argumento se puede ingresar el carácter literalmente, o su correspondiente código Unicode, antecedido del prefijo ''.

In [45]:
ord('丕')
Out[45]:
19989
In [46]:
ord("\u4e15")
Out[46]:
19989

pow()

Si recibe dos argumentos, eleva el primero a la potencia del segundo.

In [47]:
pow(2, 3)
Out[47]:
8
In [48]:
pow(10, 2)
Out[48]:
100
In [49]:
pow(10, -2)
Out[49]:
0.01

Si recibe un tercer argumento opcional, éste funciona como módulo.

In [50]:
pow(2, 3, 3)
Out[50]:
2

round()

Si recibe un argumento numérico, devuelve el entero más próximo (a diferencia de int(), que trunca a la parte entera.

In [51]:
round(2.3)
Out[51]:
2
In [52]:
round(2.8)
Out[52]:
3
In [53]:
round(-2.8)
Out[53]:
-3
In [54]:
round(2.5)
Out[54]:
2

Si se agrega un segundo parámetro opcional, se puede determinar la cantidad de cifras después de la coma (por defecto, cero).

In [55]:
a = 2**(0.5)
print(a)
print(round(a))
print(round(a, 2))
print(round(a, 4))
print(round(a, 8))
1.4142135623730951
1
1.41
1.4142
1.41421356

type()

Devuelve el tipo del objeto que recibe como argumento.

In [56]:
type(2)
Out[56]:
int
In [57]:
type(2.3)
Out[57]:
float
In [58]:
type("hola")
Out[58]:
str
In [59]:
type(int)
Out[59]:
type
  • Hay varias funciones integradas más en Python; la lista de todas las funciones disponibles en el lenguaje con la descripción correspondiente se puede encontrar en: https://docs.python.org/3/library/functions.html

Funciones definidas por el usuario

  • La instrucción para definir una función en Python comienza con la palabra clave def más el nombre de la función a definir seguido de paréntesis, dentro de los cuales va el o los parámetros que recibe la función (si son más de uno, separados por coma). La instrucción se termina con dos puntos (:)
  • Luego viene el bloque de código que conforma el cuerpo de la función; debe ir indentado (por convención: 4 espacios)
  • Si la función devuelve un valor, se hace con la instrucción return.
  • La definición de la función termina cuando termina el bloque de código indentado.
def nombre_de_funcion(par1, par2,... parN): [bloque de código (indentado)] [return objeto]

nombre de las funciones

  • para los nombres de las funciones rigen las mismas condiciones que para los nombres de las variables (ver clase 1).
  • es conveniente dar a la función un nombre descriptivo de su funcionalidad

creando funciones

Para crear una función es esencial definir claramente qué queremos que haga.
Es decir: - qué queremos ingresar a la función como argumento(s) - qué queremos que devuelva o haga la función

Ejemplo 2.1

Vamos a crear una función que convierta un valor de temperatura en grados Fahrenheit a grados Celsius (ver Ejemplo 1.1)
La función va a tener un único argumento, que es el valor de temperatura en Fahrenheit, y va a devolver el valor en grados Celsius.

In [60]:
def fahrenheit_to_celsius(grados_fahrenheit):
    grados_celsius = (grados_fahrenheit-32)*5/9
    return grados_celsius

temp1 = fahrenheit_to_celsius(80)
print(temp1)
print(fahrenheit_to_celsius(66))
temp2 = 75
print(fahrenheit_to_celsius(temp2))
26.666666666666668
18.88888888888889
23.88888888888889

Ejemplo 2.2

Vamos a crear una función que, partir de una frecuencia dada, calcule la frecuencia a un número determinado de cents de distancia (ver Ejemplo 1.2).

In [61]:
def cents_a_frecuencia(frec_inicial, cents):
    frec_final = frec_inicial*(2**(cents/1200))
    return frec_final

frec1 = 440
frec2 = cents_a_frecuencia(frec1, 50)
print(frec2)
frec3 = cents_a_frecuencia(frec1, -1200)
print(frec3)
452.8929841231365
220.0

Ejemplo 2.3

Vamos a definir una función que, dado un número de divisiones iguales de un intervalo "octavante" (no necesariamente 2), calcule el multiplicador de frecuencia entre grados sucesivos (por ejemplo, para la división en 12 partes iguales de la octava 2:1, el valor 1.0594630943592953). (ver Ejercicio 1.1)

Solución: Para una cantidad N de divisiones de un intervalo O, el valor del multiplicador de frecuencia entre grados conjuntos es raíz N-ésima de O.

In [62]:
def division(grados, octava):
    factor = octava**(1/grados)
    return factor

et12 = division(12, 2)
print(et12)
et19 = division(19, 2)
print(et19)
foo = division(23, 3)
print(foo)
1.0594630943592953
1.0371550444461919
1.0489249176452926

Orden de los parámetros

Por defecto, los argumentos que se pasan a la función son interpretados en el orden en que están los parámetros en la definición (argumentos posicionales). Pero se pueden pasar los argumentos en cualquier orden arbitrario, usando los nombres de los parámetros (argumentos por clave).

In [63]:
et12 = division(octava=2, grados=12)
print(et12)
1.0594630943592953

Parámetros por defecto

En una función podemos determinar que un parámetro sea opcional y adquiera un valor por defecto, asignándole un valor en la definición de la función.

Por ejemplo, en la función anterior podemos decidir que por defecto el valor de octava es 2, y sólo necesitamos explicitar ese parámetro cuando queremos un valor diferente.

In [64]:
def division(grados, octava=2):
    factor = octava**(1/grados)
    return factor

et12 = division(12)
print(et12)
et19 = division(19)
print(et19)
foo = division(23,3)
print(foo)
1.0594630943592953
1.0371550444461919
1.0489249176452926

Alcance de las variables

Por defecto, las variables en Python son locales. Esto quiere decir que las variables definidas y utilizadas en el bloque de código de una función, sólo tienen existencia dentro de la misma, y no interfieren con otras variables del resto del código.

A su vez, las variables existentes fuera de una función, no son visibles dentro de la misma.

En caso de que sea conveniente o necesario, una variable puede convertirse en global declarándola explícitamente como tal con la instrucción global.

In [65]:
var1 = "variable original"

def globvar():
    global var1
    var1 = "variable global modificada"

print(var1)
globvar()
print(var1)
variable original
variable global modificada

Como se puede ver, después de llamar a la función globvar(), la variable var1 queda modificada.
En general, este procedimiento debe utilizarse con precaución.

Docstrings

Las docstrings (o documentation strings) son, como lo dice su nombre, cadenas de texto que sirven para documentar diversos aspectos de una función: qué hace, el valor de sus parámetros, qué tipos de argumentos recibe, qué tipo de objeto devuelve, etc.

Aparecen como primera instrucción en el cuerpo de la función, a continuación de su declaración, y por convención van delimitadas por triple comillas.

In [66]:
def fahrenheit_to_celsius(grados_fahrenheit):
    """convierte una temperatura en grados Fahrenheit a grados Celsius"""
    grados_celsius = (grados_fahrenheit-32)*5/9
    return grados_celsius

En casos simples, la doctring consiste en una sola línea, con una descripción sumaria de la función.

Funciones más complejas pueden requerir una descripción más detallada. En ese caso, por convención se deja una línea en blanco después de la primera línea con la descripción sumaria, y luego se procede a expandir la descripción.

Una funcionalidad valiosa de las docstrings, es que Python genera a partir de ellas su propia documentación, que se puede invocar en línea mediante la función help() vista más arriba.

In [67]:
help(fahrenheit_to_celsius)
Help on function fahrenheit_to_celsius in module __main__:

fahrenheit_to_celsius(grados_fahrenheit)
    convierte una temperatura en grados Fahrenheit a grados Celsius


Al invocar la función help() pasándole como argumento el nombre de una función, nos devuelve la siguiente información:

  1. el módulo al cual pertenece la función, en este caso, el cuerpo del programa principal, indicado por el nombre de módulo __main__. Puede también ser una función integrada (módulo builtin, como en el ejemplo de help() expuesto arriba), o una función de un módulo importado, como se verá más adelante.
  2. la sintaxis básica de la función, tal cual fue declarada con la instrucción def
  3. toda la documentación que se haya incluido en la docstring.

Parámetros y salida

Se pueden definir funciones que no tengan parámetros de entrada. En ese caso, la función ejecuta el mismo código cada vez que es invocada.

In [68]:
def alarma():
    print("riiiiiiiiing")

alarma()
riiiiiiiiing

La función anterior tampoco utiliza la intrucción return para devolver un valor.
En realidad, toda función en Python devuelve un valor. Cuando no se utiliza la la instrucción return para devolver un valor explícito, la función devuelve el valor None.

In [69]:
valor = alarma()
print("El valor que devuelve la función alarma() es", valor)
riiiiiiiiing
El valor que devuelve la función alarma() es None

Comentarios

Los comentarios en el código tienen una vital importancia en el desarrollo de todo programa.

Son ignorados por el intérprete y no generan ningún tipo de código, pero constituyen una ayuda esencial tanto para quien está desarrollando el programa, como para otras personas que lean el código.

Entre otras, algunas de las funciones más importantes que pueden cumplir los comentarios en un programa, son:

  • brindar información general sobre el programa
  • explicar qué hace cada una de sus partes
  • aclarar y/o fundamentar el funcionamiento de un bloque específico de código, que no sea evidente de su propia lectura
  • indicar cosas pendientes para agregar o mejorar

El signo para indicar el comienzo de un comentario en Python es la almohadilla o numeral (#), a partir del cual y hasta el fin de la línea, todo se considera un comentario y es ignorado por el intérprete.

El carácter # puede estar al comienzo de línea (en cuyo caso toda la línea será ignorada), o después de finalizar una instrucción válida de código.

Como ejemplo, se muestra una forma posible de comentar el programa del Ejemplo 1.2 (clase 1).

In [70]:
# programa que calcula la frecuencia resultante al transportar
# una frecuencia dada una cierta cantidad de semitonos

# primero se pide al usuario que ingrese la frecuencia incial 
# y la cantidad de cents a ser transportada
frec_inicial = int(input("ingrese la frecuencia inicial: ")) # se convierte el string de entrada en un entero
cents = int(input("ingrese la cantidad de cents: "))
frec_final = frec_inicial*(2**(cents/1200)) # se calcula la frecuencia resultante
print("frecuencia final: ", frec_final)
ingrese la frecuencia inicial: 440
ingrese la cantidad de cents: 50
frecuencia final:  452.8929841231365

En un programa tan corto y sencillo quizás no sean necesarios tantos comentarios. Es importante escribir programas suficientemente comentados, pero no recargar el código con comentarios innecesarios de cosas obvias.

Por ejemplo, el comentario siguiente sería inadecuado, por resultar obvio de la propia instrucción:

In [71]:
print("frecuencia final: ", frec_final) # imprime la frecuencia resultante en la pantalla
frecuencia final:  452.8929841231365

comentarios multilínea

A diferencia de otros lenguajes de programación, Python no dispone de un método para delimitar bloques de comentarios de varias líneas.

Las alternativas para introducir comentarios multilíneas son:

  1. comentar cada una de las líneas con el carácter #: en general todos los editores de programación y entornos de desarrollo (IDEs) disponen de mecanismos que permiten comentar y descomentar fácilmente un conjunto de líneas.
  2. utilizar triple comillas para generar una cadena multilínea: si bien este método es aceptado, tiene el inconveniente que, aunque no genera código ejecutable, el bloque delimitado no es ignorado por el intérprete, que crea el correspondiente objeto de tipo string.

Ejercicio 2.1

  1. definir una función que reciba una altura expresada en número de nota MIDI (0 a 127), y devuelva la clase de altura correspondiente (0 a 11). Documentar la función con una docstring.
  2. escribir un programa en el que el usuario ingrese un número de nota MIDI, y se imprima en pantalla su clase de altura.
  3. documentar el programa con los comentarios que se considere pertinente.

Ejercicio 2.2

  1. definir una función que reciba un número de octava (4 = octava central) y una clase de altura, y devuelva la altura expresada en número de nota MIDI (0 a 127). Documentar la función con una docstring.
  2. escribir un programa en el que el usuario ingrese un número de octava y una clase de altura, y se imprima en pantalla el número de nota MIDI.
  3. documentar el programa con los comentarios que se considere pertinente.