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).
Unlike TypeScript, typing comes built into Python. The common types (str, float, int, bool, dict, list) are always available and the other types (e.g. Tuple, Union, Optional) are imported from the standard module typing.
Python doesn't have this issue because coercions between types are specified at the type level rather than the interpreter shrugging at some nonsense and moving on. Now, I will give you that typescript will catch your nonsense at compile time (well, sometimes) and Python will readily run until it blows up.
Interfaces are most recommended for defining new objects or methods or properties of an object where it will receive a specific component. Hence interface works better when using objects and method objects. Therefore it is our choice to choose between types or interface according to the program needs.
Interface is a structure that defines the contract in your application. It defines the syntax for classes to follow. Classes that are derived from an interface must follow the structure provided by their interface. The TypeScript compiler does not convert interface to JavaScript.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With