web-dev-qa-db-fra.com

comment taper un dictionnaire avec des valeurs de différents types en python

Lors de la déclaration d'un dictionnaire en tant que littéral, existe-t-il un moyen d'indiquer quelle valeur j'attends pour une clé spécifique?

Et ensuite, pour la discussion: existe-t-il des principes directeurs concernant la dactylographie en dictionnaire en python? Je me demande s'il est considéré comme une mauvaise pratique de mélanger des types dans des dictionnaires.

Voici un exemple:

Considérons la déclaration d'un dictionnaire dans le __init__ d'une classe:

(disclaimer: je me rends compte dans l'exemple, certaines des entrées .elements seraient probablement plus appropriées en tant qu'attributs de classe, mais c'est pour l'exemple).

class Rectangle:
    def __init__(self, corners: Tuple[Tuple[float, float]], **kwargs):
        self.x, self.z = corners[0][0], corners[0][1]
        self.elements = {
            'front': Line(corners[0], corners[1]),
            'left': Line(corners[0], corners[2]),
            'right': Line(corners[1], corners[3]),
            'rear': Line(corners[3], corners[2]),
            'cog': calc_cog(corners),
            'area': calc_area(corners),
            'pins': None
        }


class Line:
    def __init__(self, p1: Tuple[float, float], p2: Tuple[float, float]):
        self.p1, self.p2 = p1, p2
        self.vertical = p1[0] == p2[0]
        self.horizontal = p1[1] == p2[1]

quand je tape tapez ce qui suit

rec1 = Rectangle(rec1_corners, show=True, name='Nr1')
rec1.sides['f...

Pycharm suggérera 'front' pour moi. Mieux encore, quand je le fais

rec1.sides['front'].ver...

Pycharm suggérera .vertical

Donc, Pycharm mémorise les clés de la déclaration littérale du dictionnaire dans le __init__ de la classe, ainsi que les types attendus de leurs valeurs. Ou plutôt: il s'attend à ce que n'importe quelle valeur ait l'un des types figurant dans la déclaration littérale - probablement la même chose que si j'avais fait un self.elements = {} # type: Union[type1, type2]. De toute façon, je le trouve super utile. 

Si vos sorties ont des indications de type sur les sorties, Pycharm en tiendra également compte.

Donc, en supposant que dans l'exemple Rectangle ci-dessus, je voulais indiquer que pins est une liste d'objets Pin ... si pins était un attribut de classe normal, ce serait:

    self.pins = None  # type: List[Pin]

(à condition que les importations nécessaires aient été effectuées)

Existe-t-il un moyen de donner le même indice de type dans la déclaration littérale du dictionnaire?

Ce qui suit ne donne pas pas ce que je recherche:

Ajouter un indicateur de type Union[...] à la fin de la déclaration littérale?

            'area': calc_area(corners),
            'pins': None
        }  # type: Union[Line, Tuple[float, float], float, List[Pin]]

Ajouter un indice de type à chaque ligne:

            'area': calc_area(corners),  # type: float
            'pins': None  # type: List[Pin]
        }

Existe-t-il une meilleure pratique pour ce genre de chose?

Un peu plus de fond:

Je travaille avec python dans pycharm et j'utilise énormément la dactylographie car elle me permet de prédire et de valider mon travail au fur et à mesure. Lorsque je crée de nouvelles classes, je jette parfois aussi des propriétés moins fréquemment utilisées dans un dictionnaire pour éviter d'encombrer l'objet avec trop d'attributs (utile en mode débogage).

4
levraininjaneer

Vous recherchez TypedDict . Il ne s’agit pour le moment que d’une extension mypy, mais il existe des projets pour en faire un type officiellement approuvé dans un avenir proche. Cependant, je ne suis pas sûr si PyCharm prend en charge cette fonctionnalité.

Donc, dans votre cas, vous feriez:

from mypy_extensions import TypedDict

RectangleElements = TypedDict('RectangleElements', {
    'front': Line,
    'left': Line,
    'right': Line,
    'rear': Line,
    'cog': float,
    'area': float,
    'pins': Optional[List[Pin]]
})

class Rectangle:
    def __init__(self, corners: Tuple[Tuple[float, float]], **kwargs):
        self.x, self.z = corners[0][0], corners[0][1]
        self.elements = {
            'front': Line(corners[0], corners[1]),
            'left': Line(corners[0], corners[2]),
            'right': Line(corners[1], corners[3]),
            'rear': Line(corners[3], corners[2]),
            'cog': calc_cog(corners),
            'area': calc_area(corners),
            'pins': None
        }  # type: RectangleElements

Si vous utilisez Python 3.6+, vous pouvez taper cela de manière plus élégante en utilisant la syntaxe basée sur la classe .

Cependant, dans votre cas particulier, je pense que la plupart des gens voudraient simplement stocker ces données sous forme de champs standard au lieu d’un dict. Je suis sûr que vous avez déjà bien réfléchi aux avantages et aux inconvénients de cette approche, je vais donc vous éviter de vous en faire la leçon.

5
Michael0x2a

Après un examen plus approfondi, la meilleure solution que j’ai trouvée jusqu’à présent consistait à faire en sorte que la classe Pin puisse renvoyer un type d’espace réservé, puis l’utiliser comme valeur dans la déclaration littérale.

donc:

class Pin:
    def __init__(self, **kwargs):
        if len(kwargs) == 0:
            return

Maintenant, dans l'exemple du PO, je pourrais faire ce qui suit pour réaliser ce que je voulais:

        ...
        'area': calc_area(corners),
        'pins': List[Pin(),]
    } 

Cependant, si j'avais un type de base (ou des types) en tant qu'entrée, cela ne fonctionnerait pas.

        ...
        'area': calc_area(corners),
        'pins': List[Pin(),]
        'color': None
        'lap_times': None
    } 

color attend une chaîne et lap_times attend une liste avec des flottants ...

Dans un tel cas, la meilleure solution serait de 

        ...
        'area': calc_area(corners),
        'pins': List[Pin(),]
        'color': 'blue'
        'lap_times': [0.,]
    }

    self.elements['color'], self.elements['lap_times'] = None, None

Aucune de celles-ci ne paraissant très élégante, j'espère donc que quelqu'un pourra suggérer quelque chose de mieux.

0
levraininjaneer