Python equivalent of Typescript interface
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.pyfrom typing import Optional, Sequenceclass 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_postfixclass Person: name: str adresses: Sequence[Address] def __init__(self, name: str, adresses: Sequence[str]) -> None: self.name = name self.adresses = adressesperson = 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:
@dataclassclass Address: street: str housenumber: int housenumber_postfix: Optional[str]@dataclassclass 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 packageCreated 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, Sequenceclass 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.
Python 3.6 added a new implementation of namedtuple that works with type hints, which removes some of the boilerplate required by the other answers.
from typing import NamedTuple, Optional, Listclass Address(NamedTuple): street: str housenumber: int housenumberPostfix: Optional[str] = Noneclass Person(NamedTuple): name: str adresses: List[Address]person = Person( name='Joe', adresses=[ Address(street='Sesame', housenumber=1), Address(street='Baker', housenumber=221, housenumberPostfix='b'), ],)
Edit: NamedTuple
s are immutable, so be aware that you can't use this solution if you want to modify the fields of your objects. Changing the contents of lists
and dicts
is still fine.
A simple solution I found (that doesn't require Python 3.7) is to use SimpleNamespace:
from types import SimpleNamespace as NSfrom typing import Optional, Listclass Address(NS): street: str housenumber: int housenumber_postfix: Optional[str]=Noneclass Person(NS): name: str addresses: List[Address]person = Person( name='Joe', addresses=[ Address(street='Sesame', housenumber=1), Address(street='Baker', housenumber=221, housenumber_postfix='b') ])
- This works in Python 3.3 and higher
- The fields are mutable (unlike NamedTuple solution)
- Code completion seems to work flawlessly in PyCharm but not 100% in VSCode (raised an issue for that)
- Type checking in mypy works, but PyCharm does not complain if I e.g do
person.name = 1
If anyone can point out why Python 3.7's dataclass
decorator would be better I would love to hear.