108 votes

Comment convertir un dictionnaire en namedtuple ou en un autre dictionnaire hachable ?

J'ai un dictionnaire comme :

d = {'a': 1, 'b': 2, 'c': 3, 'd': 4}

que j'aimerais convertir en namedtuple. Mon approche actuelle consiste à utiliser le code suivant

namedTupleConstructor = namedtuple('myNamedTuple', ' '.join(sorted(d.keys())))
nt= namedTupleConstructor(**d)

qui produit

myNamedTuple(a=1, b=2, c=3, d=4)

Cela fonctionne bien pour moi (je pense), mais est-ce qu'il me manque un élément intégré tel que...

nt = namedtuple.from_dict() ?

MISE À JOUR : comme discuté dans les commentaires, la raison pour laquelle je veux convertir mon dictionnaire en namedtuple est qu'il devient hachable, mais reste généralement utilisable comme un dict.

UPDATE2 : 4 ans après que j'ai posé cette question, TLK publie une nouvelle réponse recommandant d'utiliser le décorateur de classe de données qui, à mon avis, est vraiment génial. Je pense que c'est ce que j'utiliserai à l'avenir.

0 votes

D'après la documentation, il n'y a pas de chemin plus court. Cependant, vous pourriez étendre la classe et implémenter la méthode.

0 votes

Avec les namedtuples, vous êtes censé créer le type namedtuple une fois et l'utiliser à plusieurs reprises, et non générer un nouveau type namedtuple à chaque fois. Générer un nouveau type de namedtuple à chaque fois est lent et va à l'encontre de tout avantage en termes d'espace.

1 votes

@user2357112 vraisemblablement, l'utilisateur a plusieurs dicts avec les mêmes clés.

172voto

wim Points 35274

Pour créer la sous-classe, il suffit de passer directement les clés d'un dict :

MyTuple = namedtuple('MyTuple', d)

Il s'agit maintenant de créer des instances de tuple à partir de ce dict, ou de tout autre dict dont les clés correspondent :

my_tuple = MyTuple(**d)

Attention : Comparaison de n-uplets nommés sur valeurs uniquement (commandés). Ils sont conçus pour remplacer directement les tuples ordinaires, avec l'accès aux attributs nommés comme caractéristique supplémentaire. Les noms des champs ne sont pas pris en compte lors des comparaisons d'égalité. . Il se peut que ce ne soit pas ce que vous vouliez ou attendiez de l namedtuple type ! Cela diffère de dict les comparaisons d'égalité, qui prennent en compte les clés et ne tiennent pas compte de l'ordre des comparaisons.

Pour les lecteurs qui n'ont pas vraiment besoin d'un type qui soit une sous-classe de tuple il n'y a probablement pas beaucoup d'intérêt à utiliser un namedtuple en premier lieu. Si vous souhaitez simplement utiliser la syntaxe d'accès aux attributs sur les champs, il serait plus simple et plus facile de créer espace de noms à la place :

>>> from types import SimpleNamespace
>>> SimpleNamespace(**d)
namespace(a=1, b=2, c=3, d=4)

La raison pour laquelle je veux convertir mon dictionnaire en namedtuple est qu'il devient hachable, mais reste généralement utilisable comme un dict.

Pour une recette de type "attrdict" pouvant être hachée, consultez un fichier congelé boîte :

>>> from box import Box
>>> b = Box(d, frozen_box=True)
>>> hash(b)
7686694140185755210
>>> b.a
1
>>> b["a"]
1
>>> b["a"] = 2
BoxError: Box is frozen

Il est également possible qu'un type de mappage gelé soit proposé dans une version ultérieure de Python ; surveillez ce projet de PEP pour savoir s'il est accepté ou rejeté :

PEP 603 -- Ajout d'un type frozenmap aux collections

8 votes

Pour le one-liner, vous avez besoin de : MonNamedTuple = namedtuple('MonNamedTuple', d.keys())(**d)

0 votes

Intéressant, un espace de noms est-il hachable ? C'était la raison pour laquelle je voulais convertir un dict en un namedtuple.

3 votes

@MaxPower : Savez-vous que les namedtuples que vous construisez à partir de {'a': 1} y {'b': 1} seront égaux et auront les mêmes codes de hachage ? Quelque chose comme tuple(sorted(d.items())) o frozenset(d.items()) peut être plus approprié. Ils gèrent également les clés qui ne sont pas des identifiants Python valides, comme 'for' o 3 .

18voto

mitendra Points 156
from collections import namedtuple
nt = namedtuple('x', d.keys())(*d.values())

4voto

fuggy_yama Points 336

Vous pouvez utiliser cette fonction pour gérer les dictionnaires imbriqués :

def create_namedtuple_from_dict(obj):
    if isinstance(obj, dict):
        fields = sorted(obj.keys())
        namedtuple_type = namedtuple(
            typename='GenericObject',
            field_names=fields,
            rename=True,
        )
        field_value_pairs = OrderedDict(
            (str(field), create_namedtuple_from_dict(obj[field]))
            for field in fields
        )
        try:
            return namedtuple_type(**field_value_pairs)
        except TypeError:
            # Cannot create namedtuple instance so fallback to dict (invalid attribute names)
            return dict(**field_value_pairs)
    elif isinstance(obj, (list, set, tuple, frozenset)):
        return [create_namedtuple_from_dict(item) for item in obj]
    else:
        return obj

-2voto

A vérifier :

def fill_tuple(NamedTupleType, container):
    if container is None:
        args = [None] * len(NamedTupleType._fields)
        return NamedTupleType(*args)
    if isinstance(container, (list, tuple)):
        return NamedTupleType(*container)
    elif isinstance(container, dict):
        return NamedTupleType(**container)
    else:
        raise TypeError("Cannot create '{}' tuple out of {} ({}).".format(NamedTupleType.__name__, type(container).__name__, container))

Les exceptions concernant les noms incorrects ou le nombre d'arguments non valides sont gérées par la fonction __init__ de namedtuple .

Test avec py.test :

def test_fill_tuple():
    A = namedtuple("A", "aa, bb, cc")

    assert fill_tuple(A, None) == A(aa=None, bb=None, cc=None)
    assert fill_tuple(A, [None, None, None]) == A(aa=None, bb=None, cc=None)
    assert fill_tuple(A, [1, 2, 3]) == A(aa=1, bb=2, cc=3)
    assert fill_tuple(A, dict(aa=1, bb=2, cc=3)) == A(aa=1, bb=2, cc=3)
    with pytest.raises(TypeError) as e:
        fill_tuple(A, 2)
    assert e.value.message == "Cannot create 'A' tuple out of int (2)."

-2voto

Bien que j'aime la réponse de @fuggy_yama, avant de la lire j'ai eu ma propre fonction, donc je la laisse ici juste pour montrer une approche différente. Elle gère également les namedtuples

def dict2namedtuple(thedict, name):

    thenametuple = namedtuple(name, [])

    for key, val in thedict.items():
        if not isinstance(key, str):
            msg = 'dict keys must be strings not {}'
            raise ValueError(msg.format(key.__class__))

        if not isinstance(val, dict):
            setattr(thenametuple, key, val)
        else:
            newname = dict2namedtuple(val, key)
            setattr(thenametuple, key, newname)

    return thenametuple

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X