Dictionnaire Python à partir des champs d'un objet


344

Savez-vous s'il existe une fonction intégrée pour créer un dictionnaire à partir d'un objet arbitraire? Je voudrais faire quelque chose comme ça:

>>> class Foo:
...     bar = 'hello'
...     baz = 'world'
...
>>> f = Foo()
>>> props(f)
{ 'bar' : 'hello', 'baz' : 'world' }

REMARQUE: il ne doit pas inclure de méthodes. Seuls les champs.

Réponses:


423

Notez que la meilleure pratique dans Python 2.7 est d'utiliser des classes de nouveau style (non nécessaires avec Python 3), c'est-à-dire

class Foo(object):
   ...

En outre, il existe une différence entre un «objet» et une «classe». Pour construire un dictionnaire à partir d'un objet arbitraire , il suffit d'utiliser __dict__. Habituellement, vous déclarerez vos méthodes au niveau de la classe et vos attributs au niveau de l'instance, donc ça __dict__devrait aller. Par exemple:

>>> class A(object):
...   def __init__(self):
...     self.b = 1
...     self.c = 2
...   def do_nothing(self):
...     pass
...
>>> a = A()
>>> a.__dict__
{'c': 2, 'b': 1}

Une meilleure approche (suggérée par robert dans les commentaires) est la varsfonction intégrée :

>>> vars(a)
{'c': 2, 'b': 1}

Alternativement, selon ce que vous voulez faire, il peut être intéressant d'hériter dict. Ensuite, votre classe est déjà un dictionnaire, et si vous le souhaitez, vous pouvez remplacer getattret / ou setattrappeler et définir le dict. Par exemple:

class Foo(dict):
    def __init__(self):
        pass
    def __getattr__(self, attr):
        return self[attr]

    # etc...

3
Que se passe-t-il si l'un des attributs de A possède un getter personnalisé? (une fonction avec un décorateur @property)? Est-ce qu'il apparaît toujours dans ____dict____? Quelle sera sa valeur?
zakdances

11
__dict__ne fonctionnera pas si l'objet utilise des slots (ou défini dans un module C).
Antimony

1
Existe-t-il un équivalent de cette méthode pour les objets de classe? IE Au lieu d'utiliser f = Foo () puis de faire f .__ dict__, faites directement Foo .__ dict__?
chiffa

50
Désolé, j'arrive tard, mais ne devrait pas vars(a)faire ça? Pour moi, il est préférable d'invoquer __dict__directement le.
robert

2
pour le deuxième exemple, il serait préférable de __getattr__ = dict.__getitem__reproduire exactement le comportement, alors vous voudriez aussi __setattr__ = dict.__setitem__et __delattr__ = dict.__delitem__pour la complétude.
Tadhg McDonald-Jensen,

142

Au lieu de cela x.__dict__, c'est en fait plus pythonique à utiliser vars(x).


3
D'accord. Notez que vous pouvez également convertir dans l'autre sens (dict-> class) en tapant MyClass(**my_dict), en supposant que vous avez défini un constructeur avec des paramètres qui reflètent les attributs de classe. Pas besoin d'accéder à des attributs privés ou de remplacer le dict.
tvt173

2
Pouvez-vous expliquer pourquoi c'est plus Pythonic?
Hugh W

1
Tout d'abord, Python évite généralement directement les appels qui suppriment les éléments, et il existe presque toujours une méthode ou une fonction (ou un opérateur) pour y accéder indirectement. En général, les attributs et méthodes dunder sont un détail d'implémentation, et l'utilisation de la fonction "wrapper" vous permet de séparer les deux. Deuxièmement, de cette façon, vous pouvez remplacer la varsfonction et introduire des fonctionnalités supplémentaires sans modifier l'objet lui-même.
Berislav Lopac

1
Il échoue quand même si votre classe l'utilise __slots__.
cz

C'est exact, et j'ai toujours pensé que ce serait une bonne direction de s'étendre vars, c'est-à-dire de renvoyer un équivalent de __dict__pour les classes "slotted". Pour l'instant, il peut être émulé en ajoutant une __dict__propriété qui retourne {x: getattr(self, x) for x in self.__slots__}( je ne sais pas si cela affecte les performances ou le comportement de quelque manière que ce soit).
Berislav Lopac

59

La fonction dirintégrée vous donnera tous les attributs de l'objet, y compris des méthodes spéciales comme __str__, __dict__et tout un tas d'autres que vous ne voulez probablement pas. Mais vous pouvez faire quelque chose comme:

>>> class Foo(object):
...     bar = 'hello'
...     baz = 'world'
...
>>> f = Foo()
>>> [name for name in dir(f) if not name.startswith('__')]
[ 'bar', 'baz' ]
>>> dict((name, getattr(f, name)) for name in dir(f) if not name.startswith('__')) 
{ 'bar': 'hello', 'baz': 'world' }

Vous pouvez donc étendre cela pour ne renvoyer que des attributs de données et non des méthodes, en définissant votre propsfonction comme ceci:

import inspect

def props(obj):
    pr = {}
    for name in dir(obj):
        value = getattr(obj, name)
        if not name.startswith('__') and not inspect.ismethod(value):
            pr[name] = value
    return pr

1
Ce code comprend des méthodes. Existe-t-il un moyen d'exclure des méthodes? J'ai seulement besoin des champs de l'objet. Merci
Julio César

ismethodn'attrape pas les fonctions. Exemple: inspect.ismethod(str.upper). inspect.isfunctionn'est pas beaucoup plus utile, cependant. Je ne sais pas comment aborder cela tout de suite.
Ehtesh Choudhury

J'ai fait quelques ajustements pour se reproduire grossièrement et ignorer toutes les erreurs à fond, merci! gist.github.com/thorsummoner/bf0142fd24974a0ced778768a33a3069
ThorSummoner

26

Je me suis installé avec une combinaison des deux réponses:

dict((key, value) for key, value in f.__dict__.iteritems() 
    if not callable(value) and not key.startswith('__'))

Cela fonctionne aussi, mais sachez que cela ne vous donnera que les attributs définis sur l'instance, pas sur la classe (comme la classe Foo dans votre exemple) ...
dF.

Donc, jcarrascal, il vaut mieux encapsuler le code ci-dessus dans une fonction comme props (), alors vous pouvez appeler props (f) ou props (Foo). Notez que vous êtes presque toujours mieux d'écrire une fonction, plutôt que d'écrire du code «en ligne».
quamrana

Bien, notez que c'est pour python2.7, pour python3 relpace iteritems () avec simplement items ().
Morten

Et qu'en est-il staticmethod? Ça ne l'est pas callable.
Alex

17

J'ai pensé prendre un peu de temps pour vous montrer comment traduire un objet à dicter via dict(obj).

class A(object):
    d = '4'
    e = '5'
    f = '6'

    def __init__(self):
        self.a = '1'
        self.b = '2'
        self.c = '3'

    def __iter__(self):
        # first start by grabbing the Class items
        iters = dict((x,y) for x,y in A.__dict__.items() if x[:2] != '__')

        # then update the class items with the instance items
        iters.update(self.__dict__)

        # now 'yield' through the items
        for x,y in iters.items():
            yield x,y

a = A()
print(dict(a)) 
# prints "{'a': '1', 'c': '3', 'b': '2', 'e': '5', 'd': '4', 'f': '6'}"

La section clé de ce code est la __iter__fonction.

Comme les commentaires l'expliquent, la première chose que nous faisons est de récupérer les éléments de classe et d'empêcher tout ce qui commence par «__».

Une fois que vous avez créé cela dict, vous pouvez utiliser la updatefonction dict et passer l'instance __dict__.

Ceux-ci vous donneront un dictionnaire complet de classe + instance de membres. Maintenant, il ne reste plus qu'à les parcourir et à générer des rendements.

De plus, si vous prévoyez de l'utiliser beaucoup, vous pouvez créer un @iterabledécorateur de classe.

def iterable(cls):
    def iterfn(self):
        iters = dict((x,y) for x,y in cls.__dict__.items() if x[:2] != '__')
        iters.update(self.__dict__)

        for x,y in iters.items():
            yield x,y

    cls.__iter__ = iterfn
    return cls

@iterable
class B(object):
    d = 'd'
    e = 'e'
    f = 'f'

    def __init__(self):
        self.a = 'a'
        self.b = 'b'
        self.c = 'c'

b = B()
print(dict(b))

Cela saisira également toutes les méthodes, mais nous n'avons besoin que des champs classe + instance. Peut dict((x, y) for x, y in KpiRow.__dict__.items() if x[:2] != '__' and not callable(y))- être le résoudra-t-il? Mais il pourrait toujours y avoir des staticméthodes :(
Alex

15

Pour construire un dictionnaire à partir d'un objet arbitraire , il suffit d'utiliser __dict__.

Cela manque les attributs que l'objet hérite de sa classe. Par exemple,

class c(object):
    x = 3
a = c()

hasattr (a, 'x') est vrai, mais 'x' n'apparaît pas dans un .__ dict__


Dans ce cas, quelle est la solution? Puisque vars()ne fonctionne pas
should_be_working

@should_be_working direst la solution dans ce cas. Voir l'autre réponse à ce sujet.
Albert

8

Réponse tardive mais prévue pour l'exhaustivité et l'avantage des googleurs:

def props(x):
    return dict((key, getattr(x, key)) for key in dir(x) if key not in dir(x.__class__))

Cela n'affichera pas les méthodes définies dans la classe, mais il affichera toujours les champs incluant ceux assignés aux lambdas ou ceux qui commencent par un double soulignement.


6

Je pense que la façon la plus simple est de créer un attribut getitem pour la classe. Si vous devez écrire sur l'objet, vous pouvez créer un setattr personnalisé . Voici un exemple pour getitem :

class A(object):
    def __init__(self):
        self.b = 1
        self.c = 2
    def __getitem__(self, item):
        return self.__dict__[item]

# Usage: 
a = A()
a.__getitem__('b')  # Outputs 1
a.__dict__  # Outputs {'c': 2, 'b': 1}
vars(a)  # Outputs {'c': 2, 'b': 1}

dict génère les attributs des objets dans un dictionnaire et l'objet dictionnaire peut être utilisé pour obtenir l'élément dont vous avez besoin.


Après cette réponse, on ne sait toujours pas comment obtenir un dictionnaire à partir d'un objet. Pas des propriétés, mais tout le dictionnaire;)
maxkoryukov

6

Un inconvénient de l'utilisation __dict__est qu'elle est peu profonde; il ne convertira aucune sous-classe en dictionnaires.

Si vous utilisez Python3.5 ou supérieur, vous pouvez utiliser jsons:

>>> import jsons
>>> jsons.dump(f)
{'bar': 'hello', 'baz': 'world'}

3

Si vous souhaitez répertorier une partie de vos attributs, remplacez __dict__:

def __dict__(self):
    d = {
    'attr_1' : self.attr_1,
    ...
    }
    return d

# Call __dict__
d = instance.__dict__()

Cela aide beaucoup si vous instanceobtenez des données de bloc volumineuses et que vous souhaitez pousser dvers Redis comme la file d'attente de messages.


__dict__est un attribut, pas une méthode, donc cet exemple change l'interface (c'est-à-dire que vous devez l'appeler comme appelable), donc il ne la remplace pas.
Berislav Lopac

0

PYTHON 3:

class DateTimeDecoder(json.JSONDecoder):

   def __init__(self, *args, **kargs):
        JSONDecoder.__init__(self, object_hook=self.dict_to_object,
                         *args, **kargs)

   def dict_to_object(self, d):
       if '__type__' not in d:
          return d

       type = d.pop('__type__')
       try:
          dateobj = datetime(**d)
          return dateobj
       except:
          d['__type__'] = type
          return d

def json_default_format(value):
    try:
        if isinstance(value, datetime):
            return {
                '__type__': 'datetime',
                'year': value.year,
                'month': value.month,
                'day': value.day,
                'hour': value.hour,
                'minute': value.minute,
                'second': value.second,
                'microsecond': value.microsecond,
            }
        if isinstance(value, decimal.Decimal):
            return float(value)
        if isinstance(value, Enum):
            return value.name
        else:
            return vars(value)
    except Exception as e:
        raise ValueError

Vous pouvez maintenant utiliser le code ci-dessus dans votre propre classe:

class Foo():
  def toJSON(self):
        return json.loads(
            json.dumps(self, sort_keys=True, indent=4, separators=(',', ': '), default=json_default_format), cls=DateTimeDecoder)


Foo().toJSON() 

0

vars() est génial, mais ne fonctionne pas pour les objets imbriqués d'objets

Convertir un objet imbriqué d'objets en dict:

def to_dict(self):
    return json.loads(json.dumps(self, default=lambda o: o.__dict__))
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.