<jupyter_start><jupyter_text>FuncionesUna función se puede ver o entender como una unidad de encapsulación (agrupación) de funcionalidad de una tarea/proceso/procedimiento.Las funciones tienen un identificador para ser invocadas. Cada una, así mismo, tiene un conjunto de parámetros. Los parámetros son la información que entra (*input*) para ser procesada.Las funciones también pueden (eventualmente) generar un resultado.El lenguaje de programación Python cuenta con una **GRAN** variedad de funciones incoporadas. Ejemplos de esas funciones:- `max()`- `print()`- `range()`- `min()`- `len()`- `int()`- `str()`- `dict()`- `list()`- `tuple()`- `float()`- `input()`- ...Las funciones que crea el programador/analista se llaman funciones personalizadas (*custom funcitons*). Estas funciones se implementan para cumplir requerimientos específicos del problema de datos que estemos resolviendo.**Recomendación**: Consultar en la mayor medida posible la existencia de una función en alguna librería. Esto nos va a ahorar tiempo de desarrollo/anális. Esas funciones están optimizadas y altamente probadas (por la comunidad de programadores de la librería).**MUY IMPORTANTE**: Se puede concebir a una función como unidad de procesamiento que evita la duplicidad de código.  8.1 Funciones incorporadas  8.1.1 `print()`Imprime contenido en la salida estándar (la consola/la pantalla).<jupyter_code>help(print)<jupyter_output>Help on built-in function print in module builtins:

print(...)
    print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
    
    Prints the values to a stream, or to sys.stdout by default.
    Optional keyword arguments:
    file:  a file-like object (stream); defaults to the current sys.stdout.
    sep:   string inserted between values, default a space.
    end:   string appended after the last value, default a newline.
    flush: whether to forcibly flush the stream.<jupyter_text>**Argumento**: Es la información/datos que se pasan a la función para realizar el respectivo cálculo en su interior (implementación).<jupyter_code>print('Python')
print('Python', 'Java', 'C++', 'JavaScript')
print('Daniela', 'Edward', 'Germán', 'Juan', 'Oliva')
nombres = ['Daniela', 'Edward', 'Germán', 'Juan', 'Oliva']

print(nombres)
punto_3d = (2, 3, -1)

print(punto_3d)<jupyter_output>(2, 3, -1)<jupyter_text>Usar el argumento nombrado `sep` para indicar un separador de las cadenas que se van a imprimir:<jupyter_code>print('Daniela', 'Edward', 'Germán', 'Juan', 'Oliva', sep='-')
print('Daniela', 'Edward', 'Germán', 'Juan', 'Oliva', sep=';')
print('Daniela', 'Edward', 'Germán', 'Juan', 'Oliva', sep='\n')
print('Daniela', 'Edward', 'Germán', 'Juan', 'Oliva', sep='\n')
print('Más contenido...')
print('Daniela', 'Edward', 'Germán', 'Juan', 'Oliva', sep='\n', end='')
print('Más contenido...')
nombres
for n in nombres:
    print(n)
for n in nombres:
    print(n, end='')
for n in nombres:
    print(n, end='\t')
for n in nombres:
    print(n, end=';')<jupyter_output>Daniela;Edward;Germán;Juan;Oliva;<jupyter_text>¿Cuál es el tipo de dato del retorno de la función `print()`?<jupyter_code>print('¡Hola!')
type(print('¡Hola!'))<jupyter_output>¡Hola!<jupyter_text>**Nota importante**: Una función no necesariamente debe retornar un valor una vez finalice su ejecución.  8.1.2 Función `max()`Retorna el mayor de un conjunto de números.<jupyter_code>help(max)
max(11, 3, 19, 7, 5)
lista_numeros = [11, 3, 19, 7, 5]

max(lista_numeros)
tupla_numeros = (11, 3, 19, 7, 5)

max(tupla_numeros)
type(max(11, 3, 19, 7, 5))
type(max(11.17, 3.57, 19.58, 7.13, 5.11))<jupyter_output><empty_output><jupyter_text>8.2 Creación de una funciónLas funciones que crea el programador/analista/etc., se les conoce como funciones personalizadas (*custom functions*).  8.2.1 Sintaxis`def nombre_función(parámetros):`&emsp;` instrucciones`&emsp;` [return datos]`<jupyter_code>def saludar():
    print('¡Hola!')
saludar()
saludar()
saludar()<jupyter_output>¡Hola!<jupyter_text>¿Cuál es el tipo de dato de `saludar`?<jupyter_code>type(saludar)
help(list.index)
help(tuple.count)
help(str.lower)
help(saludar)
def saludar():
    """
    Muestra un mensaje de saludo.
    """
    print('¡Hola!')
help(saludar)
def saludarPersona(nombre):
    print('¡Hola,', nombre, '!')
help(saludarPersona)
saludarPersona('Daniela')
def saludarPersona(nombre):
    print(f'¡Hola, {nombre}!')
saludarPersona('Daniela')
def saludarPersona(nombre):
    """
    Saluda a una persona especificando su nombre.
    
    :param str nombre: El nombre de la persona.
    """
    print(f'¡Hola, {nombre}!')
help(saludarPersona)<jupyter_output>Help on function saludarPersona in module __main__:

saludarPersona(nombre)
    Saluda a una persona especificando su nombre.
    
    :param str nombre: El nombre de la persona<jupyter_text>Una función puede retornar un valor. Para ello debemos utilizar la sentencia `return`.<jupyter_code>type(saludarPersona('Daniela'))
resultado = saludarPersona('Daniela')

type(resultado)
def saludarPersona(nombre):
    """
    Saluda a una persona especificando su nombre.
    
    :param str nombre: El nombre de la persona.
    """
    return f'¡Hola, {nombre}!'
saludarPersona('Daniela')
resultado = saludarPersona('Daniela')

type(resultado)
resultado
help(saludarPersona)
def saludarPersona(nombre):
    """
    Saluda a una persona especificando su nombre.
    
    :param str nombre: El nombre de la persona.
    :return str: Saludo con el nombre de la persona.
    """
    return f'¡Hola, {nombre}!'
help(saludarPersona)<jupyter_output>Help on function saludarPersona in module __main__:

saludarPersona(nombre)
    Saluda a una persona especificando su nombre.
    
    :param str nombre: El nombre de la persona.
    :return str: Saludo con el nombre de la persona.<jupyter_text>**IMPORTANTE**: Escribir la documentación de una función ayuda a su legibilidad (ayuda a otros programadores a entender lo que hace la función sin necesidad de leer el código).Debe ser lo más concisa posible (lo más breve posible).La documentación debe tener buena ortografía. Ejemplo 8.2.1.2Crear una función que calcule la suma de dos números.<jupyter_code>def sumar(numero_1, numero_2):
    """
    Suma dos números (enteros o reales).
    
    :param float numero_1: primer número a sumar.
    :param float numero_2: segundo número a sumar.
    :return float: la suma de los dos números.
    """
    
    suma = numero_1 + numero_2
    
    return suma
type(sumar)
help(sumar)
sumar(2, 3) # 5
sumar(2.3, 5.2) # 7.5
sumar('2.0', '3.0')
# sumar('2.0', 3.0) # Genera error de tipo TypeError.
# sumar('2.0', 3) # Genera error de tipo TypeError
def sumar(numero_1, numero_2):
    """
    Suma dos números (enteros o reales).
    
    :param float numero_1: primer número a sumar.
    :param float numero_2: segundo número a sumar.
    :return float|None: La suma de los dos números. Retorna None cuando los parámetros no son del tipo especifcado.
    """
    
    if not isinstance(numero_1, (int, float)) or not isinstance(numero_2, (int, float)):
        return None
    
    suma = numero_1 + numero_2
    
    return suma
help(sumar)
sumar('2.0', '3.0')
type(sumar('2.0', '3.0'))
sumar('2.0', 3.0)
type(sumar('2.0', 3.0))
sumar(2.0, '3.0')
type(sumar(2.0, '3.0'))
sumar(2.0, 3.0)
sumar([1, 2, 3], (4, 5, 6))
type(sumar([1, 2, 3], (4, 5, 6)))
def sumar(numero_1, numero_2):
    """
    Suma dos números (enteros o reales).
    
    :param float numero_1: primer número a sumar.
    :param float numero_2: segundo número a sumar.
    :return float: La suma de los dos números. Retorna None cuando los parámetros no son del tipo especifcado.
    :raises TypeError: Al menos uno de los dos argumentos no es un número (entero o real).
    """
    
    if not type(numero_1) in [int, float]:
        raise TypeError('El primer argumento debe ser número (entero o real).')
        
    if not type(numero_2) in [int, float]:
        raise TypeError('El segundo argumento debe ser número (entero o real).')
    
    suma = numero_1 + numero_2
    
    return suma
help(sumar)
sumar(2, 3)
sumar('2', 3)
try:
    sumar('2', 3)
except TypeError as e:
    print(e)
try:
    sumar(2, '3')
except TypeError as e:
    print(e)<jupyter_output>El segundo argumento debe ser número (entero o real).<jupyter_text>Crear una firma de una función especificando el tipo de los parámetros y también el tipo de dato para el valor que retorna la función:<jupyter_code>def sumar(numero_1: float, numero_2: float) -> float:
    """
    Suma dos números (enteros o reales).
    
    :param float numero_1: primer número a sumar.
    :param float numero_2: segundo número a sumar.
    :return float: La suma de los dos números. Retorna None cuando los parámetros no son del tipo especifcado.
    :raises TypeError: Al menos uno de los dos argumentos no es un número (entero o real).
    """
    
    suma = numero_1 + numero_2
    
    return suma
help(sumar)
sumar(2, 3)
# sumar('2', 3) # Genera la excepción TypeError. No cumple con la especificación de la firma.
# sumar(2, '3') # Genera el error TypeError.<jupyter_output><empty_output><jupyter_text>8.3 Funciones con parámetros por defecto.Son funciones que en la especificación (encabezado o *signture*) se asigna valores por defecto para los parámetros.<jupyter_code>print('Python')
print()
print('v.3.9.1')
print('Python')
print(end='\n')
print('v.3.9.1')
print('Python')
print(end='-')
print('v.3.9.1')<jupyter_output>Python
-v.3.9.1<jupyter_text>Ejemplo 8.3.1: Crear una función para la operación de exponenciación.$x^{n}$<jupyter_code>def potencia(base, exponente=2):
    """
    Calcula la potencia de una base.
    
    :param float base: base de la exponenciación.
    :param exponente float exponente=2: exponente de la exponenciación.
    :return float: exponenciación de los dos números pasados como argumento.
    """
    
    resultado = base ** exponente
    
    return resultado
help(potencia)
potencia(2)
potencia(3)
potencia(2, 3)
potencia(base=2, exponente=3)
potencia(exponente=3, base=2)
# potencia(exponente=3, 2) # Genera el error SyntaxError.
potencia(2, exponente=3)<jupyter_output><empty_output><jupyter_text>8.4 Funciones con parámetros de longitud variable<jupyter_code>help(print)
print(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
print()
print(1)<jupyter_output>1<jupyter_text>Ejemplo 8.4.1: Crear una función que sume una cantidad variable de números.<jupyter_code>def sumar(*numeros):
    """
    Suma una cantidad variable de números.
    
    :param numeros: conjunto de números a sumar.
    :return float: suma de los números.
    """
    
    print(type(numeros))
sumar(1, 2, 3)
def sumar(*numeros):
    """
    Suma una cantidad variable de números.
    
    :param tuple numeros: conjunto de números a sumar.
    :return float: suma de los números.
    """
    
    suma = 0
    
    for n in numeros:
        suma += n
    
    return suma
help(sumar)
sumar()
sumar(1, 2, 3, 4, 5)
sumar(1, 2)
numeros = list(range(1, 11))

numeros
print(numeros)
print(*numeros)
print(numeros, sep=';')<jupyter_output>[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]<jupyter_text>Expande (de-construye) la lista `numeros` y cada elemento individual se convierte en argumento de la función `print()`.Además, cada elemento (número) cuando imprima va a quedar separado por un punto y coma (;).<jupyter_code>print(*numeros, sep=';')
sumar(*numeros)<jupyter_output><empty_output><jupyter_text>8.5 Parámetros llave/valor<jupyter_code>def funcion(**parametros):
    print(type(parametros))
    print(parametros)
type(funcion)
help(funcion)
funcion(llave1=100, llave2=200, llave3=300)
datos = {'llave1': 100, 'llave2': 200, 'llave3': 300}

datos
funcion(**datos)
def calcular_total_salarios(**datos):
    """
    Calcula el total de los n salarios que se pasan como argumentos.
    
    :param dict datos: diccionario con los datos de los salarios (nombre_trabajador: salario).
    :return float: salarios incrementados en 19%.
    """
    
    for k, v in datos.items():
        datos[k] = v * 1.19
    
    return datos
help(calcular_total_salarios)
trabajadores = {'Diana': 1000, 'Kevin': 1500, 'Alex': 1000, 'Angela': 2500}

trabajadores
resultado = calcular_total_salarios(**trabajadores)

resultado
trabajadores['Marcela'] = 2300

trabajadores
resultado = calcular_total_salarios(**trabajadores)
resultado<jupyter_output><empty_output><jupyter_text>8.6 Funciones generadoresEs un tipo de función que genera valores a partir de un cuerpo de implementación (conjunto de operaciones).Las funciones generadores producen valores **según se vayan necesitando**.Una de las diferencias esenciales respecto a una función común y corriente consiste en que las funciones generadores no terminan hasta que se cumpla una condición. Esa condición viene determinada por el número de apariciones de la sentencia `yield`.<jupyter_code>def generar_enteros():
    n = 0
    while True:
        yield n
        n += 1
generador = generar_enteros()
next(generador)
next(generador)
next(generador)
next(generador)
next(generador)<jupyter_output><empty_output><jupyter_text>Ejemplo 8.6.1:Crear una función generadora para generar números primos.Un número primo es aquel número natural que es divisible por sí mismo y la unidad.<jupyter_code>def generar_primos():
    """
    Genera un números primos de forma indefinida.
    
    :yield: es un número primo.
    """
    numero = 2
    yield numero
    
    while True:
        numero_temporal = numero + 1
        contador = 0
        
        for i in range(1, numero_temporal + 1):
            if numero_temporal % i == 0:
                contador += 1
        
        numero = numero_temporal
        if contador == 2:
            yield numero
help(generar_primos)
generador = generar_primos()
type(generador)
next(generador)
for i in range(10):
    print(next(generador))
for i in range(30):
    print(next(generador))
cantidad = 30
contador = 0
for primo in generador:
    print(primo)
    contador += 1
    
    if contador > cantidad:
        break
contador<jupyter_output><empty_output><jupyter_text>Ejemplo 8.6.2:Generar nombres de países de Sur América. Se debe crear una función generadora que entregue de forma aleatoria un nombre de país hasta que el listado completo se halla agotado.<jupyter_code>import random

def generar_pais():
    """
    Genera un país de forma aleatoria.
    
    :yield: entrega un país generado de forma aleatoria.
    :raises StopIteration: Indica cuando la función generadora ha terminado.
    """
    paises = ['Colombia', 'Perú', 'Ecuador', 'Bolivia', 'Venezuela', 'Chile', 'Argentina', 'Paraguay', 'Uruguay', 'Brasil']
    
    while len(paises) != 0:
        pais = random.choice(paises)
        yield pais
        paises.remove(pais)
    
    raise StopIteration('No hay más países que generar.')
help(generar_pais)
generador = generar_pais()

type(generador)
next(generador)
next(generador)
next(generador)
next(generador)
next(generador)
next(generador)
next(generador)
next(generador)
next(generador)
next(generador)
# next(generador) # StopIteration
generador = generar_pais()
try:
    while True:
        print(next(generador))
except (StopIteration, RuntimeError) as e:
    print('Se han agotado los elementos de la función generadora. No hay más países que generar.')<jupyter_output>Venezuela
Perú
Ecuador
Colombia
Paraguay
Bolivia
Brasil
Uruguay
Chile
Argentina
Se han agotado los elementos de la función generadora. No hay más países que generar.
{'fraction_non_alphanumeric': 0.11570087406927808, 'fraction_numerical': 0.020977662674004534, 'mean_word_length': 4.186702484889187, 'pattern_counts': {'":': 0, '<': 65, '<?xml version=': 0, '>': 67, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 1, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '19', 'dir': 'jupyter-structured-clean-dedup', 'id': '646131', 'license': 'MIT', 'n_tokens_mistral': 5786, 'n_tokens_neox': 5556, 'n_words': 1694, 'path': 'Taller001-Python-Intro/T001-08-Funciones.ipynb', 'repo_name': 'Fhernd/PandasTallerManipulacionDatos', 'size': '15445'}