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.

Jules César
la source

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...
user6868
la source
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).

Berislav Lopac
la source
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
dF.
la source
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('__'))
Jules César
la source
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))
Seaux
la source
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__

échancrure
la source
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.

Score_Under
la source
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.

radtek
la source
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'}
RH
la source
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.

coanor
la source
__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() 
spattanaik75
la source
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__))
Ricky Levi
la source