Control de tipos en Python¶
Antes de empezar: ¿para qué nos sirve conocer el tipo de datos de una variable?¶
- Los tipos de datos nos permiten relacionar un conjunto de valores que son de ese tipo con las operaciones que se pueden aplicar sobre esos valores.
¿Qué es un sistema de tipos?¶
- El sistema de tipos es un conjunto de reglas que tiene un lenguaje que nos permite manipular los datos de nuestros programas.
- Incluyen las conversiones explícitas e implícitas que podemos realizar.
Lenguajes con tipado estático vs. dinámico¶
- Se refiere a si el tipo de una variable se conoce en tiempo de compilación o en ejecución.
In [15]:
Copied!
x = "casa"
type(x)
x = "casa"
type(x)
Out[15]:
str
Lenguajes fuertemente tipados vs. débilmente tipados¶
- Fuertemente tipados: no se puede usar aplicar operaciones de otro tipo a menos que se haga una conversión explícita. Por ejemplo: Java, Pascal y Python.
- Débilmente tipados: se pueden mezclar en una misma expresión valores de distinto tipo. Por ejemplo PHP y Javascript.
x = "a" + 5
In [2]:
Copied!
x = "a" + 5
x
x = "a" + 5
x
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-2-b25c2ff693f6> in <module> ----> 1 x = "a" + 5 2 x TypeError: can only concatenate str (not "int") to str
Python¶
- Es un lenguaje fuertemente tipado.
- Posee un tipado dinámico: el intérprete de Python realiza el chequeo de tipos durante la ejecución y el tipo de una variable puede cambiar durante su tiempo de vida.
La verificación de tipos¶
- Se refiere a chequeo de tipos.
- Es donde se aplican las reglas definidas en el sistema de tipos.
- La verificación de tipos puede ser:
- estática: ocurre en tiempo de compilación. Por ejemplo: Pascal y C
- dinámica: ocurre en tiempo de ejecución. Por ejemplo PHP, Ruby y Python.
In [17]:
Copied!
opcion = input("ingresa 1 para verificar y 2 para no")
if opcion == "1":
print("Estoy chequeando...")
print("e" + 4 )
else:
print("Ahora no estoy dando error")
opcion = input("ingresa 1 para verificar y 2 para no")
if opcion == "1":
print("Estoy chequeando...")
print("e" + 4 )
else:
print("Ahora no estoy dando error")
ingresa 1 para verificar y 2 para no1 Estoy chequeando...
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-17-953bb85b289f> in <module> 2 if opcion == "1": 3 print("Estoy chequeando...") ----> 4 print("e" + 4 ) 5 else: 6 print("Ahora no estoy dando error") TypeError: can only concatenate str (not "int") to str
Duck Typing¶
"Si parece un pato, nada como un pato y suena como un pato, entonces probablemente sea un patoӦ
Observemos el siguiente código¶
In [18]:
Copied!
def headline(text, align=True):
if align:
return f"{text.title()}\n{'-' * len(text)}"
else:
return f" {text.title()} ".center(50, "-")
def headline(text, align=True):
if align:
return f"{text.title()}\n{'-' * len(text)}"
else:
return f" {text.title()} ".center(50, "-")
In [19]:
Copied!
print(headline("python type checking"))
print(headline("python type checking"))
Python Type Checking --------------------
In [20]:
Copied!
print(headline("python type checking", align=False))
print(headline("python type checking", align=False))
-------------- Python Type Checking --------------
Probemos esto:¶
In [21]:
Copied!
print(headline("python type checking", align="left"))
print(headline("python type checking", align="left"))
Python Type Checking --------------------
Python permite agregar sugerencias de tipos: anotaciones¶
In [22]:
Copied!
def headline(text: str, align: bool = True) -> str:
if align:
return f"{text.title()}\n{'-' * len(text)}"
else:
return f" {text.title()} ".center(50, "-")
def headline(text: str, align: bool = True) -> str:
if align:
return f"{text.title()}\n{'-' * len(text)}"
else:
return f" {text.title()} ".center(50, "-")
In [23]:
Copied!
print(headline("python type checking", align="left"))
print(headline("python type checking", align="left"))
Python Type Checking --------------------
- ¿Cambió algo?
Si bien estas anotaciones están disponibles en tiempo de ejecución a través del atributo __annotations__, no se realiza ninguna verificación de tipo en tiempo de ejecución.
In [24]:
Copied!
headline.__annotations__
headline.__annotations__
Out[24]:
{'text': str, 'align': bool, 'return': str}
Pero si lo abrimos en un IDE (PyCharm en este caso)¶
- Usamos un verificador de tipos externo.
- La herramienta más común para realizar la verificación de tipos es Mypy
mypy¶
- Se instala con pip: pip install mypy
¿Cómo resolvemos este "error"?¶
In [25]:
Copied!
def headline(text: str, centered: bool = True) -> str:
if centered:
return f"{text.title()}\n{'-' * len(text)}"
else:
return f" {text.title()} ".center(50, "-")
def headline(text: str, centered: bool = True) -> str:
if centered:
return f"{text.title()}\n{'-' * len(text)}"
else:
return f" {text.title()} ".center(50, "-")
In [26]:
Copied!
print(headline("python type checking"))
print(headline("use mypy", centered=True))
print(headline("python type checking"))
print(headline("use mypy", centered=True))
Python Type Checking -------------------- Use Mypy --------
Anotaciones¶
- Como vimos, en las funciones se puede agregar anotaciones sobre los argumentos y el valor de retorno.
- En general:
def funcion(arg1: arg_type, arg2: arg_type = valor) -> return_type: ...
In [27]:
Copied!
import math
def area_circunferencia(radio: float) -> float:
return math.pi * radio ** 2
area = area_circunferencia(2)
print(area)
import math
def area_circunferencia(radio: float) -> float:
return math.pi * radio ** 2
area = area_circunferencia(2)
print(area)
12.566370614359172
También se pueden hacer anotaciones de variables¶
In [28]:
Copied!
pi: float = 3.1415
def area_circunferencia(radio: float) -> float:
return math.pi * radio ** 2
area = area_circunferencia(2)
print(area)
pi: float = 3.1415
def area_circunferencia(radio: float) -> float:
return math.pi * radio ** 2
area = area_circunferencia(2)
print(area)
12.566370614359172
In [29]:
Copied!
area_circunferencia.__annotations__
area_circunferencia.__annotations__
Out[29]:
{'radio': float, 'return': float}
In [30]:
Copied!
__annotations__
__annotations__
Out[30]:
{'pi': float, 'mensaje': str, 'nombre_bandas': list, 'notas': tuple, 'opciones': dict}
Un poco más sobre anotaciones¶
- Se puede realizar una anotación de una variable sin darle un valor.
In [31]:
Copied!
mensaje: str
__annotations__
mensaje: str
__annotations__
Out[31]:
{'pi': float, 'mensaje': str, 'nombre_bandas': list, 'notas': tuple, 'opciones': dict}
In [32]:
Copied!
mensaje = 10
mensaje
mensaje = 10
mensaje
Out[32]:
10
Otros ejemplos¶
In [33]:
Copied!
nombre_bandas: list = ["Led Zeppelin", "AC/DC", "Queen"]
notas: tuple = (7, 8, 9, 10)
opciones: dict = {"centered": False, "capitalize": True}
nombre_bandas: list = ["Led Zeppelin", "AC/DC", "Queen"]
notas: tuple = (7, 8, 9, 10)
opciones: dict = {"centered": False, "capitalize": True}
- ¿Cómo podemos indicar que se trata de una lista de elementos str? ¿O una tupla de enteros?
El modulo typing¶
- Permite escribir anotaciones un poco más complejas.
In [34]:
Copied!
from typing import Dict, List, Tuple
nombre_bandas: List[str] = ["Led Zeppelin", "AC/DC", "Queen"]
notas: Tuple[int, int, int, int] = (7, 8, 9, 10)
opciones: Dict[str, bool] = {"centered": False, "capitalize": True}
from typing import Dict, List, Tuple
nombre_bandas: List[str] = ["Led Zeppelin", "AC/DC", "Queen"]
notas: Tuple[int, int, int, int] = (7, 8, 9, 10)
opciones: Dict[str, bool] = {"centered": False, "capitalize": True}
Veamos este otro ejemplo¶
In [35]:
Copied!
from typing import List, Sequence
def cuadrados(elems: Sequence[float]) -> List[float]:
return [x**2 for x in elems]
from typing import List, Sequence
def cuadrados(elems: Sequence[float]) -> List[float]:
return [x**2 for x in elems]
In [36]:
Copied!
cuadrados([1, 2, 3])
cuadrados([1, 2, 3])
Out[36]:
[1, 4, 9]
- Una secuencia es cualquier objeto que admita len () y __ getitem __ (), independientemente de su tipo real.
¿Qué pasa con este código?¶
In [37]:
Copied!
import random
def elijo_al_azar(lista_de_elementos):
return random.choice(lista_de_elementos)
lista = [1, "dos", 3.1415]
elijo_al_azar(lista)
import random
def elijo_al_azar(lista_de_elementos):
return random.choice(lista_de_elementos)
lista = [1, "dos", 3.1415]
elijo_al_azar(lista)
Out[37]:
1
Para incorporar las anotaciones usamos el tipo: Any¶
In [38]:
Copied!
import random
from typing import Any, Sequence
def elijo_al_azar(lista_de_elementos: Sequence[Any]) -> Any:
return random.choice(lista_de_elementos)
lista = [1, "dos", 3.1415]
elijo_al_azar(lista)
import random
from typing import Any, Sequence
def elijo_al_azar(lista_de_elementos: Sequence[Any]) -> Any:
return random.choice(lista_de_elementos)
lista = [1, "dos", 3.1415]
elijo_al_azar(lista)
Out[38]:
1
Anotaciones y POO¶
¿Cómo agregamos anotaciones a los métodos?¶
In [39]:
Copied!
class Jugador:
def __init__(self,
nombre: str,
juego: str = "Tetris",
tiene_equipo: bool = False,
equipo: str = None) -> None:
self.nombre = nombre
self.juego = juego
self.tiene_equipo = tiene_equipo
self.equipo = equipo
def jugar(self) -> None:
if self.tiene_equipo:
print (f"{self.nombre} juega en el equipo {self.equipo} al {self.juego}")
else:
print(f"{self.nombre} juega solo al {self.juego}")
class Jugador:
def __init__(self,
nombre: str,
juego: str = "Tetris",
tiene_equipo: bool = False,
equipo: str = None) -> None:
self.nombre = nombre
self.juego = juego
self.tiene_equipo = tiene_equipo
self.equipo = equipo
def jugar(self) -> None:
if self.tiene_equipo:
print (f"{self.nombre} juega en el equipo {self.equipo} al {self.juego}")
else:
print(f"{self.nombre} juega solo al {self.juego}")
- Se usan las mismas reglas que para las funciones.
- self no necesita ser anotado. ¿De qué tipo es?
¿Cómo agregamos anotaciones a las variables de instancia y de clase?¶
- Se usan las mismas reglas que para las variables comunes.
In [40]:
Copied!
class SuperHeroe():
""" Esta clase define a un superheroe
villanos: representa a los enemigos de todos los superhéroes
"""
villanos: List[str] = []
def __init__(self, nombre: str, alias: str) -> None:
self._nombre = nombre
self._enemigos = []
class SuperHeroe():
""" Esta clase define a un superheroe
villanos: representa a los enemigos de todos los superhéroes
"""
villanos: List[str] = []
def __init__(self, nombre: str, alias: str) -> None:
self._nombre = nombre
self._enemigos = []
Hasta acá llegamos...¶
Más info¶
- La PEP 3107 introdujo la sintaxis para las anotaciones de funciones, pero la semántica se dejó deliberadamente sin definir.
- La PEP 484 introduce un módulo provisional para proporcionar definiciones y herramientas estándares, junto con algunas convenciones para situaciones en las que las anotaciones no están disponibles.
La PEP 526: tiene como objetivo mostrar de qué manera se pueden relaizar anotacion de varoables (incluidas las variables de clase y las variables de instancia),
Artículo de RealPython: https://realpython.com/python-type-checking/
- Artículo de the state of type hints in Python de Bernát Gábor.