Duck typing with type annotations type python 3.5

Suppose I have a function with a signature like:

def foo(self, name:str, stream): pass 

I want to add annotation to the argument to β€œstream”, which means β€œyou can have any x object if x.readline () β†’ str.

So this means that I can use any python file object as an argument here (since it has a readline method), but I could also provide an object that implements nothing but readline, and that would be perfectly acceptable.

How can I rewrite the definition of this function so that I can comment on the second argument?

+6
source share
3 answers

As Ivanle notes, PEP 544 adds protocols to support static duck. This PEP was recently adopted and was added in Python 3.8. You can also try the protocols in Python 3.6 and 3.7 with Mypy using the typing-extensions package.

In your case, you must define a very simple SupportsReadline protocol with a single method and use it in the annotation of your function arguments:

 # Python 3.8+, for 3.6 & 3.7 replace 'typing' with 'typing_extensions'. from typing import Protocol class SupportsReadline(Protocol): def readline(self) -> str: ... def func(name: str, stream: SupportsReadline) -> None: pass 

Now, any object with a readline method with a compatible signature is an implicit subtype of SupportsReadline and satisfies the annotations of your function argument. Note that LineRepeater does not explicitly inherit from SupportsReadline :

 class LineRepeater: def readline(self) -> str: return "Hello again!" func("a", LineRepeater()) # OK 

The same applies to other objects if the method signature matches exactly:

 from io import BytesIO, StringIO func("a", StringIO()) # OK func("a", open("foo.txt")) # OK func("a", BytesIO()) # ERROR (return type is bytes instead of str) func("a", []) # ERROR func("a", 1) # ERROR func("a", object()) # ERROR 
+3
source

A structural subtype (static typing of ducks) was proposed by PEP 544 https://www.python.org/dev/peps/pep-0544/ . If / when it is accepted, you do not need an explicit subclassing, you can simply define your own protocols that will be understood by static type controllers.

+7
source

This solution is not equivalent to what exactly you are looking for:

you can have any x object if x.readline() -> str

Instead, we define our own abstract base class, which expects the readline abstract method to be defined by its child classes. Therefore, instead of any random object, it will only accept instances of this new abstract base class, making it more explicit.

 from abc import ABC, abstractmethod class FileObject(ABC): @abstractmethod def readline(self): raise NotImplementedError() 

Now we are going to define a custom type that can work with Python file objects and FileObject instances:

 from typing import IO, TypeVar StreamType = TypeVar('StreamType', IO, FileObject) def func(name: str, stream: StreamType) -> None: pass 

Now test it using mypy :

 from io import StringIO, BytesIO class X(FileObject): def readline(self): pass func('a', StringIO()) # passed func('a', BytesIO()) # passed func('a', open('foo.txt')) # passed func('a', X()) # passed func('a', object()) # failed func('a', []) # failed func('a', 1) # failed 

Output:

 $ mypy so.py so.py:33: error: Type argument 1 of "func" has incompatible value "object" so.py:34: error: Type argument 1 of "func" has incompatible value List[None] so.py:35: error: Type argument 1 of "func" has incompatible value "int" 
+3
source

Source: https://habr.com/ru/post/1268169/


All Articles