Search code examples
pythonpython-typingmypy

Python equivalent of Typescript interface


Recently I have been working with Typescript a lot, it allows to express things like:

interface Address {
    street: string;
    housenumber: number;
    housenumberPostfix?: string;
}

interface Person {
    name: string;
    adresses: Address[]
}

const person: Person = {
    name: 'Joe',
    adresses: [
        { street: 'Sesame', housenumber: 1 },
        { street: 'Baker', housenumber: 221, housenumberPostfix: 'b' }
    ]
}

Pretty concise and giving all the luxuries as type checking and code completion while coding with Persons.

How is this done in Python?

I have been looking at Mypy and ABC but did not yet succeed in finding the pythonic way to do something similar as the above (my attempts resulted in way too much boilerplate to my taste).


Solution

  • For the code completion and type hinting in IDEs, just add static typing for the Person and Address classes and you are already good to go. Assuming you use the latest python3.6, here's a rough equivalent of the typescript classes from your example:

    # spam.py
    from typing import Optional, Sequence
    
    
    class Address:
        street: str
        housenumber: int
        housenumber_postfix: Optional[str]
    
        def __init__(self, street: str, housenumber: int, 
                     housenumber_postfix: Optional[str] = None) -> None:
            self.street = street
            self.housenumber = housenumber
            self.housenumber_postfix = housenumber_postfix
    
    
    class Person:
        name: str
        adresses: Sequence[Address]
    
        def __init__(self, name: str, adresses: Sequence[str]) -> None:
            self.name = name
            self.adresses = adresses
    
    
    person = Person('Joe', [
        Address('Sesame', 1), 
        Address('Baker', 221, housenumber_postfix='b')
    ])  # type: Person
    

    I suppose the boilerplate you mentioned emerges when adding the class constructors. This is indeed inavoidable. I would wish default constructors were generated at runtime when not declared explicitly, like this:

    class Address:
        street: str
        housenumber: int
        housenumber_postfix: Optional[str]
    
    
    class Person:
        name: str
        adresses: Sequence[Address]
    
    
    if __name__ == '__main__':
        alice = Person('Alice', [Address('spam', 1, housenumber_postfix='eggs')])
        bob = Person('Bob', ())  # a tuple is also a sequence
    

    but unfortunately you have to declare them manually.


    Edit

    As Michael0x2a pointed out in the comment, the need for default constructors is made avoidable in python3.7 which introduced a @dataclass decorator, so one can indeed declare:

    @dataclass
    class Address:
        street: str
        housenumber: int
        housenumber_postfix: Optional[str]
    
    
    @dataclass
    class Person:
        name: str
        adresses: Sequence[Address]
    

    and get the default impl of several methods, reducing the amount of boilerplate code. Check out PEP 557 for more details.


    I guess you could see stub files that can be generated from your code, as some kind of interface files:

    $ stubgen spam  # stubgen tool is part of mypy package
    Created out/spam.pyi
    

    The generated stub file contains the typed signatures of all non-private classes and functions of the module without implementation:

    # Stubs for spam (Python 3.6)
    #
    # NOTE: This dynamically typed stub was automatically generated by stubgen.
    
    from typing import Optional, Sequence
    
    class Address:
        street: str
        housenumber: int
        housenumber_postfix: Optional[str]
        def __init__(self, street: str, housenumber: int, housenumber_postfix: Optional[str]=...) -> None: ...
    
    class Person:
        name: str
        adresses: Sequence[Address]
        def __init__(self, name: str, adresses: Sequence[str]) -> None: ...
    
    person: Person
    

    These stub files are also recognized by IDEs and if your original module is not statically typed, they will use the stub file for type hints and code completion.