Duck typing with python 3.5 style type-annotations

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

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

Instead we are defining a custom abstract base class that expects readline abstract method to be defined by its child classes. Hence instead of any random object it would only accept instances of this new abstract base class, making it more explicit.

from abc import ABC, abstractmethod

class FileObject(ABC):
    def readline(self):
        raise NotImplementedError()

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

from typing import IO, TypeVar

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

Now let's test it using mypy:

from io import StringIO, BytesIO

class X(FileObject):
    def readline(self):

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


$ mypy error: Type argument 1 of "func" has incompatible value "object" error: Type argument 1 of "func" has incompatible value List[None] error: Type argument 1 of "func" has incompatible value "int"

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

In your case, you would define a very simple Protocol SupportsReadline with a single method and use this 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:

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

class LineRepeater:
    def readline(self) -> str:
        return "Hello again!"

func("a", LineRepeater())  # OK

The same holds for 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

Structural subtyping (static duck typing) is proposed by PEP 544 If/when it is accepted you will not need an explicit subclassing, you will be able to simply define your own protocols that will be understood by static type checkers.