Récemment, j'ai beaucoup travaillé avec TypeScript, cela permet d'exprimer des choses comme:
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' }
]
}
Assez concis et donnant tout le luxe comme vérification de type et complétion de code lors du codage avec des personnes.
Comment cela se fait-il en Python?
J'ai regardé Mypy et ABC mais je n'ai pas encore réussi à trouver la façon Pythonique de faire quelque chose de similaire à ce qui précède (mes tentatives ont abouti à trop de passe-partout à mon goût).
Pour l'achèvement du code et l'indication de type dans les IDE, ajoutez simplement une saisie statique pour les classes Person
et Address
et vous êtes déjà prêt. En supposant que vous utilisez la dernière python3.6
, voici un équivalent approximatif des classes TypeScript de votre exemple:
# 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
Je suppose que le passe-partout que vous avez mentionné émerge lors de l'ajout des constructeurs de classe. Ceci est en effet inévitable. Je souhaiterais que les constructeurs par défaut soient générés lors de l'exécution lorsqu'ils ne sont pas déclarés explicitement, comme ceci:
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
mais malheureusement, vous devez les déclarer manuellement.
Comme Michael0x2a souligné dans le commentaire , le besoin de constructeurs par défaut est rendu évitable dans python3.7
qui a introduit un @dataclass
décorateur, donc on peut en effet déclarer:
@dataclass
class Address:
street: str
housenumber: int
housenumber_postfix: Optional[str]
@dataclass
class Person:
name: str
adresses: Sequence[Address]
et obtenir l'impl par défaut de plusieurs méthodes, réduisant la quantité de code passe-partout. Consultez PEP 557 pour plus de détails.
Je suppose que vous pouvez voir des fichiers stub qui peuvent être générés à partir de votre code, comme une sorte de fichiers d'interface:
$ stubgen spam # stubgen tool is part of mypy package
Created out/spam.pyi
Le fichier stub généré contient les signatures typées de toutes les classes et fonctions non privées du module sans implémentation:
# 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
Ces fichiers de raccord sont également reconnus par les IDE et si votre module d'origine n'est pas typé statiquement, ils utiliseront le fichier de raccord pour les conseils de type et la complétion de code.
Python 3.6 a ajouté une nouvelle implémentation de namedtuple qui fonctionne avec les indications de type, ce qui supprime une partie du passe-partout requis par les autres réponses.
from typing import NamedTuple, Optional, List
class Address(NamedTuple):
street: str
housenumber: int
housenumberPostfix: Optional[str] = None
class 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 sont immuables, alors sachez que vous ne pouvez pas utiliser cette solution si vous souhaitez modifier les champs de vos objets. Changer le contenu de lists
et dicts
est toujours correct.
Une solution simple que j'ai trouvée (qui ne nécessite pas Python 3.7) est d'utiliser SimpleNamespace :
from types import SimpleNamespace as NS
from typing import Optional, List
class Address(NS):
street: str
housenumber: int
housenumber_postfix: Optional[str]=None
class 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')
])
person.name = 1
Si quelqu'un peut expliquer pourquoi Python 3.7's dataclass
décorateur serait mieux que j'aimerais entendre.
Avec Python 3.5, vous pouvez utiliser des annotations pour spécifier le type de paramètres et les types de retour. La plupart des IDE récents, comme PyCharm, peuvent interpréter ces annotations et vous donner une bonne complétion de code. Vous pouvez également utiliser un commentaire pour spécifier la signature d'une fonction ou le type d'une variable.
Voici un exemple:
from typing import List, Optional
class Address(object):
def __init__(self, street: str, housenumber: int, housenumber_postfix: Optional[str]=None):
self.street = street
self.housenumber = housenumber
self.housenumber_postfix = housenumber_postfix
class Person(object):
def __init__(self, name: str, addresses: List[Address]):
self.name = name
self.addresses = addresses
person = Person(
name='Joe',
addresses=[
Address(street='Sesame', housenumber=1),
Address(street='Baker', housenumber=221, housenumber_postfix='b')
])
Notez que Python n'est pas un langage fortement typé. Les annotations ne sont donc qu'un guide pour les développeurs. Si vous voulez vraiment vérifier votre code, vous avez besoin d'un outil externe (actuellement, le meilleur est mypy ). Il peut être utilisé comme n'importe quel autre vérificateur de code pendant le contrôle de la qualité du code.
Peut-être que cela fonctionnera bien avec mypy
from typing import List
from mypy_extensions import TypedDict
EntityAndMeta = TypedDict("EntityAndMeta", {"name": str, "count": int})
my_list: List[EntityAndMeta] = [
{"name": "Amy", "count": 17},
{"name": "Bob", "count": 42},
]
En savoir plus sur TypedDict dans les mypy docs ou dans les code source
Je suis sûr que vous pouvez imbriquer ces choses , et en définir certaines sur Optional
si vous le souhaitez.
J'ai eu cette idée de https://stackoverflow.com/a/21014863/5017391
Une interface TypeScript décrit un objet JavaScript. Un tel objet est analogue à un Python avec des clés de chaîne bien connues, qui est décrit par un mypy TypedDict.
Par exemple, l'interface TypeScript:
interface Address {
street: string;
housenumber: number;
}
décrira des objets JavaScript comme:
var someAddress = {
street: 'SW Gemini Dr.',
housenumber: 9450,
};
Le mypy équivalent TypedDict
:
from typing_extensions import TypedDict
class Address(TypedDict):
street: str
housenumber: int
décrira Python dictionnaires comme:
some_address = {
'street': 'SW Gemini Dr.',
'housenumber': 9450,
}
# or equivalently:
some_address = dict(
street='SW Gemini Dr.',
housenumber=9450,
)
Ces dictionnaires peuvent être sérialisés vers/depuis JSON de manière triviale et seront conformes au type d'interface TypeScript analogue.
Remarque: Si vous utilisez Python 2 ou des versions antérieures de Python 3, vous devrez peut-être utiliser l'ancienne syntaxe basée sur les fonctions pour TypedDict:
from mypy_extensions import TypedDict
Address = TypedDict('Address', {
'street': str,
'housenumber': int,
})
Il existe d'autres façons dans Python pour représenter les structures avec des propriétés nommées.
Tuples nommés sont bon marché et ont des clés en lecture seule. Cependant, ils ne peuvent pas être sérialisés vers/depuis JSON automatiquement.
from typing import NamedTuple
class Address(NamedTuple):
street: str
housenumber: int
my_address = Address(
street='SW Gemini Dr.',
housenumber=9450,
)
Classes de données, disponibles dans Python 3.7, ont des clés de lecture-écriture. Ils ont également ne peut pas être sérialisé vers/depuis JSON automatiquement.
from dataclasses import dataclass
@dataclass
class Address:
street: str
housenumber: int
my_address = Address(
street='SW Gemini Dr.',
housenumber=9450,
)
Espaces de noms simples, disponible dans Python 3.3, sont similaires aux classes de données mais ne le sont pas très bien connu.
from types import SimpleNamespace
class Address(SimpleNamespace):
street: str
housenumber: int
my_address = Address(
street='SW Gemini Dr.',
housenumber=9450,
)
attrs est une bibliothèque tierce partie similaire aux classes de données mais avec beaucoup plus de fonctionnalités. attrs est reconnu par le vérificateur de caractères mypy .
import attrs
@attr.s(auto_attribs=True)
class Address:
street: str
housenumber: int
my_address = Address(
street='SW Gemini Dr.',
housenumber=9450,
)