Declaración de funciones en Python
Tipado y funciones: estático vs. dinámico
Un recordatorio rápido de cómo se diferencian Kotlin y Python en cuanto al sistema de tipos:
- Kotlin: tipado estático con inferencia y nulabilidad explícita. Los errores de tipo se detectan en compilación.
def function_name(param_1: Type1, param_2: Type2) -> ReturnType:
# cuerpo de la función
return result
-
def
def
-
function_name
function_name
-
(param: Type)
(param: Type)
-
-> ReturnType
-> ReturnType
- Cuerpo: bloque indentado; usa
return
return
En Python, las anotaciones de tipo son sugerencias que pueden validarse con herramientas externas, pero el intérprete las ignora en tiempo de ejecución.
Kotlin, en cambio, sí valida los tipos y la nulabilidad en compilación, ofreciendo seguridad antes de ejecutar el programa.
En Python, las funciones y variables deben nombrarse usando la convención snake_case, de acuerdo a las guías de estilo de PEP 8.
- El nombre comienza con una letra minúscula.
- Cada palabra siguiente se separa con un guion bajo.
Ejemplos correctos:
-
calculate_total
calculate_total
-
print_message
print_message
-
main
main
Usar un estilo de nombres consistente mejora la legibilidad y asegura que tu código esté alineado con las prácticas idiomáticas de Python.
Evita estilos heredados de otros lenguajes o que no son válidos en Python:
-
CalculateTotal
CalculateTotal
-
calculateTotal
calculateTotal
-
calculate-total
calculate-total
-
CALCULATE_TOTAL
CALCULATE_TOTAL
Ejemplo: Sumar dos números
def add(a: int, b: int) -> int:
return a + b
if __name__ == "__main__":
# Example usage of the add function
print("Adding 2 and 3 gives:", add(2, 3)) # ➜ 5
# At runtime, Python allows this and produces '12' (concatenation).
# However, ty/mypy/pyright will flag a *type error* (expected ints).
print("Adding '1' and '2' gives:", add("1", "2")) # ➜ 12
Argumentos por defecto y nombrados
Puedes definir valores por defecto y usar argumentos nombrados para mejorar la legibilidad:
def summon(character: str, location: str = "Rivendell") -> str:
return f"{character} has been summoned to {location}."
if __name__ == "__main__":
print(summon("Gandalf")) # ➜ Gandalf has been summoned to Rivendell.
print(
summon(character="Aragorn", location="Minas Tirith")
) # ➜ Aragorn has been summoned to Minas Tirith.
En Python, los valores por defecto de los parámetros se evalúan una sola vez al definir la función. Si usas una lista, dict o set como valor por defecto, se compartirá entre invocaciones y acumulará estado.
from typing import Any
def log_event(
event: dict[str, Any], buffer: list[dict[str, Any]] = []
) -> list[dict[str, Any]]:
buffer.append(event)
return buffer
if __name__ == "__main__":
event_a = {
"trainer": "Brendan",
"action": "catch",
"pokemon": "Ralts",
"location": "Route 102",
}
event_b = {
"trainer": "May",
"action": "badge",
"badge": "Stone Badge",
"gym": "Rustboro City",
}
a = log_event(event_a)
b = log_event(event_b)
print(a is b) # → True; same shared object
print(len(b)) # → 2; Brendan and May's events mixed
print([e["trainer"] for e in b]) # → ['Brendan', 'May']
Cómo solucionarlo
None
None
(referencia)
from typing import Any
def buffer_event_safe(
event: dict[str, Any],
buffer: list[dict[str, Any]] | None = None,
) -> list[dict[str, Any]]:
if buffer is None:
buffer = []
buffer.append(event)
return buffer
if __name__ == "__main__":
event_a = {
"trainer": "Brendan",
"action": "catch",
"pokemon": "Ralts",
"location": "Route 102",
}
event_b = {
"trainer": "May",
"action": "badge",
"badge": "Stone Badge",
"gym": "Rustboro City",
}
safe_a = buffer_event_safe(event_a)
safe_b = buffer_event_safe(event_b)
print(safe_a is safe_b) # → False; different objects
print(len(safe_b)) # → 1; only May's event
print([e["trainer"] for e in safe_b]) # → ['May']
- Los objetos mutables como valores por defecto se evalúan una sola vez y se comparten entre invocaciones.
- El uso de
None
None
- La anotación
list[dict[str, Any]] | None
list[dict[str, Any]] | None
- Si se pasa un buffer explícito, la mutación es intencional; si no, se crea una lista fresca de manera segura.
*args
*args
y **kwargs
**kwargs
)
Funciones variádicas (*args
*args
**kwargs
**kwargs
En Python, los argumentos posicionales capturados por
se agrupan en una tupla, y los argumentos con nombre
capturados por
*args
*args
se agrupan en un diccionario. Es decir, la llamada se transforma internamente en estos contenedores
estándar.
**kwargs
**kwargs
Veamos un ejemplo centrado en tipos: distingue el tipo del
contenedor (
/ tuple
tuple
)
y el tipo de cada elemento almacenado en ellos.
dict
dict
def variadic_types(*args: int, **kwargs: int | str) -> str:
return "\n".join(
(
f"args: {type(args)}", # type of "*args"
*map(lambda a: f"{a}: {type(a)}", args), # type of each element in "*args"
f"kwargs: {type(kwargs)}", # type of "**kwargs"
*map(
# type of each key-value pair in "**kwargs"
lambda kv: f"({kv[0]}: {type(kv[0])}) -> ({kv[1]}: {type(kv[1])})",
kwargs.items(),
),
)
)
if __name__ == "__main__":
print(
variadic_types(1, 2, k1="v1", k2=3)
)
concatena líneas en un único "\n".join(...)"
"\n".join(...)"
.
str
str
aplica f a cada elemento
para producir las líneas sin usar bucles. map(f, iterable)
map(f, iterable)
recorre las parejas clave–valor como tuplas kwargs.items()
kwargs.items()
.
(key, value)
(key, value)
El objetivo es evidenciar cómo *args y **kwargs empaquetan los argumentos (tupla/diccionario) y cómo inspeccionar el tipo de cada elemento.
args: <class 'tuple'>
1: <class 'int'>
2: <class 'int'>
Kwargs: <class 'dict'>
(k1: <class 'str'>) -> (v1: <class 'str'>)
(k2: <class 'str'>) -> (3: <class 'int'>)
TODO: completar esta sección
from functools import reduce
from operator import add
def sum_positives(*nums: int) -> int:
print(type(nums)) # Outputs: <class 'tuple'>
return reduce(add, filter(lambda n: n > 0, nums), 0)
if __name__ == "__main__":
print(sum_positives(1, -2, 3, 4, -5)) # Outputs: 8
print(sum_positives()) # Outputs: 0
print(sum_positives(-1, -2, -3)) # Outputs: 0