Protocolos Python: tipificación de pato de una nueva manera

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

  • Tipeo de pato





  • Tipificación nominal









  • Python

















    • runtime_checkable





















, 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

  • mypy





  • 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)





  • Ir a las interfaces








All Articles