web-dev-qa-db-fra.com

Manière Pythonic de convertir le dictionnaire en namedtuple, ou un autre dicton semblable à Hashable?

J'ai un dictionnaire comme:

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

que je voudrais convertir en un namedtuple . Mon approche actuelle est avec 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 me manque-t-il un élément intégré tel que ... 

nt = namedtuple.from_dict() ?

UPDATE: comme indiqué dans les commentaires, la raison pour laquelle je souhaite convertir mon dictionnaire en un nom nommé est pour qu'il devienne utilisable, mais reste généralement utilisable comme un dict.

25
Max Power

Pour créer la sous-classe, vous pouvez simplement passer les clés d'un dict directement:

MyTuple = namedtuple('MyTuple', sorted(d))

Maintenant, pour créer des instances à partir de ce dict, ou d’autres dict avec des clés correspondantes:

my_Tuple = MyTuple(**d)

Attention: nomméstuples comparent sur valeurs uniquement (ordonné). Ils sont conçus pour remplacer les nuplets ordinaires, avec un accès aux attributs nommés en tant que fonctionnalité supplémentaire. _ {Les noms de champs ne seront pas pris en compte lors des comparaisons d'égalité}. Cela diffère des comparaisons d'égalité dict, qui prennent en compte les clés, et il se peut que ce ne soit ni ce que vous vouliez ni ce que vous attendiez du type namedtuple!

Si vous avez un seul dict, plutôt qu'un groupe de dict partageant le même jeu de clés, il n'y a aucun intérêt à créer ce nom nommé en premier lieu. Vous devriez simplement utiliser un objet namespace à la place:

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

Pour une recette "attrayante" comme celle-ci, jetez un coup d’œil à une boîte :

>>> from box import Box
>>> b = Box(d, frozen_box=True)
>>> hash(b)
7686694140185755210
>>> b.a
1
>>> b['a']
1
32
wim

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
1
fuggy_yama

Regarde ça:

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 pour les noms incorrects ou le nombre d'arguments non valides sont gérées par __init__ de namedtuple.

Testez 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)."
0
Mikaelblomkvistsson

Bien que j'aime bien @fuggy_yama, avant de le lire, j'ai ma propre fonction, je le laisse donc ici pour montrer une approche différente. Il gère également les namedtuples imbriqués

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
0
Rodrigo E. Principe