Protokol Python: bebek mengetik dengan cara baru

Dalam versi baru Python, anotasi tipe mendapatkan lebih banyak dukungan, dan semakin banyak digunakan di perpustakaan, kerangka kerja, dan proyek Python. Selain dokumentasi tambahan dari kode tersebut, anotasi tipe memungkinkan alat seperti mypy untuk secara statis melakukan pemeriksaan validasi tambahan dan mengidentifikasi kemungkinan kesalahan dalam kode. Artikel ini akan berbicara tentang satu, saya pikir, topik yang menarik tentang pemeriksaan tipe statis dengan Python - protokol, atau seperti yang dinyatakan dalam PEP-544 , pengetikan bebek statis .





Kandungan

  • Mengetik bebek





  • Pengetikan 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. , , , , .





Jika Anda memiliki sesuatu untuk ditambahkan tentang pro dan kontra dari pengetikan terstruktur, silakan bagikan pemikiran Anda di komentar.





Catatan (sunting)

  • Semua contoh yang dibahas dalam artikel ini diuji dengan Python 3.9 / mypy 0.812.





  • File pengaturan mypy





link yang berguna

  • mypy





  • PEP-544 - Protokol: Subtipe struktural





  • Mengetik Bebek (Istilah Python)





  • Sistem tipe nominal (Wikipedia)





  • Sistem tipe struktural (Wikipedia)





  • Protokol Iterator: Bagaimana "For Loops" Bekerja dengan Python





  • Protokol dan subtipe struktural (Dokumentasi mypy)





  • Pergi antarmuka








All Articles