En las nuevas versiones de Python, las anotaciones de tipo obtienen cada vez más soporte y se utilizan cada vez más en bibliotecas, marcos y proyectos de Python. Además de la documentación adicional del código, las anotaciones de tipo permiten que herramientas como mypy realicen verificaciones de validación adicionales de forma estática e identifiquen posibles errores en el código. Este artículo hablará sobre uno, creo, un tema interesante con respecto a la verificación de tipos estáticos en Python: los protocolos o, como se indica en PEP-544 , la tipificación estática de pato .
Contenido
, Python, , - :
, , , ,
>>> class Meter:
... def __len__(self):
... return 1_000
...
>>> len([1, 2, 3])
3
>>> len("Duck typing...")
14
>>> len(Meter())
1000
len
, , __len__()
.
. , , – . .
(nominal type system) , , , . Duck
Bird
, Duck
, Bird
. Python, mypy , , , .
:
class Bird:
def feed(self) -> None:
print("Feeding the bird...")
class Duck(Bird):
def feed(self) -> None:
print("Feeding the duck...")
class Goose:
"""
- Bird.
"""
def feed(self) -> None:
print("Feeding the goose...")
def feed(bird: Bird) -> None:
bird.feed()
# OK
feed(Bird())
# OK
feed(Duck())
# Mypy error: Argument 1 to "feed" has incompatible type "Goose";
# expected "Bird"
feed(Goose())
# Mypy error: Argument 1 to "feed" has incompatible type "None";
# expected "Bird"
feed(None)
Goose
feed
, Bird
, mypy.
. , Java, C#, C++ .
(structural type system) , . , , compile time duck typing.
. , Go – , . , Go - , , .
– TypeScript, :
// TypeScript
interface Person {
name: String
age: Number
}
function show(person: Person) {
console.log("Name: " + person.name)
console.log("Age: " + person.age)
}
class Employee {
name: String
age: Number
constructor(name: String, age: Number) {
this.name = name
this.age = age
}
}
class Figure {}
// OK
show(new Employee("John", 30))
// OK
show({name: "Peter", age: 25})
// Error:
// Argument of type 'Figure' is not assignable to parameter of type 'Person'.
// Type 'Figure' is missing the following properties
// from type 'Person': name, age
show(new Figure())
Employee
Person
, . , Employee
name
age
. Figure
, , , , , Person
.
Python
Python 3.8 (PEP-544), Python. Python , . , , , , .
"" , ( , , mypy). , - , (abc.ABC
), .
:
import typing as t
# t.Iterable[int] -
def iterate_by(numbers: t.Iterable[int]) -> None:
for number in numbers:
print(number)
# OK
iterate_by([1, 2, 3])
# OK
iterate_by(range(1_000_000))
# Mypy error: Argument 1 to "iterate_by" has incompatible type "str";
# expected "Iterable[int]"
# note: Following member(s) of "str" have conflicts:
# note: Expected:
# note: def __iter__(self) -> Iterator[int]
# note: Got:
# note: def __iter__(self) -> Iterator[str]
iterate_by("duck")
Mypy , iterate_by
(, __iter__
).
, , mypy , .
# ...
class Fibonacci:
def __iter__(self) -> t.Iterator[int]:
a, b = 0, 1
while True:
yield a
a, b = b, a + b
# OK
iterate_by(Fibonacci())
class Animals:
"""
, ,
,
.
"""
def __iter__(self) -> t.Iterator[str]:
yield from ["duck", "cat", "dog"]
# Mypy error: Argument 1 to "iterate_by" has incompatible type "Animals";
# expected "Iterable[int]"
iterate_by(Animals())
( typing
) . mypy.
, . mypy , .
:
import typing as t
class Figure(t.Protocol):
""" ."""
# ,
name: str
def calculate_area(self) -> float:
""" ."""
def calculate_perimeter(self) -> float:
""" ."""
def show(figure: Figure) -> None:
print(f"S ({figure.name}) = {figure.calculate_area()}")
print(f"P ({figure.name}) = {figure.calculate_perimeter()}")
Protocol
typing
. - , . - ( -).
, Figure
.
# ...
class Square:
name = ""
def __init__(self, size: float):
self.size = size
def calculate_area(self) -> float:
return self.size * self.size
def calculate_perimeter(self) -> float:
return 4 * self.size
def set_color(self, color: str) -> None:
"""
,
.
"""
self.color = color
# OK
show(Square(size=3.14))
, Square
Figure
. Mypy show
Figure
, Square
. , . , Figure
show
, Square
– ( ). , .
, mypy :
# ...
class Circle:
PI = 3.1415926
name = ""
def __init__(self, radius: float):
self.radius = radius
def calculate_perimeter(self) -> float:
return 2 * self.PI * self.radius
# Mypy error: Argument 1 to "show" has incompatible type "Circle";
# expected "Figure"
# note: 'Circle' is missing following 'Figure' protocol member:
# note: calculate_area
show(Circle(radius=1))
mypy , ( ).
, , . mypy , .
import typing as t
import abc
class Readable(t.Protocol):
@abc.abstractmethod
def read(self) -> str:
...
def get_size(self) -> int:
"""
-.
"""
return 1_000
# OK
class File(Readable):
def read(self) -> str:
return " "
# OK
print(File().get_size()) # 1000
# Mypy error: Return type "int" of "read" incompatible
# with return type "str" in supertype "Readable"
class WrongFile(Readable):
def read(self) -> int:
return 42
(abc.ABC
), , -. , , mypy .
runtime_checkable
, isinstance
issubclass
. , , mypy :
# ... Figure
s = Square(4)
# Mypy error: Only @runtime_checkable protocols can be used
# with instance and class checks
# isinstance(s, Figure)
isinstance(s, Figure)
, @runtime_checkable
, .
import typing as t
@t.runtime_checkable
class HasLength(t.Protocol):
def __len__(self) -> int:
...
# OK
print(isinstance("", HasLength)) # True
print(isinstance([1, 2, 3], HasLength)) # True
- , , PEP-544.
Python, . Mypy Python. , , , , .
Si tiene algo que agregar sobre los pros y los contras de la escritura estructurada, comparta sus pensamientos en los comentarios.
Notas (editar)
Todos los ejemplos discutidos en este artículo se probaron en Python 3.9 / mypy 0.812.
Archivo de configuración de Mypy
Enlaces útiles
-
PEP-544 - Protocolos: subtipificación estructural
Escritura de pato (glosario de Python)
Sistema de tipos nominales (Wikipedia)
Sistema de tipos estructurales (Wikipedia)
El protocolo de iterador: cómo funcionan los "bucles for" en Python
Protocolos y subtipificación estructural (documentación mypy)
-