web-dev-qa-db-fra.com

Comment créer un nouvel objet inconnu ou dynamique / expando dans Python

Dans python comment pouvons-nous créer un nouvel objet sans avoir une classe prédéfinie et ensuite lui ajouter dynamiquement des propriétés?

exemple:

dynamic_object = Dynamic()
dynamic_object.dynamic_property_a = "abc"
dynamic_object.dynamic_property_b = "abcdefg"

Quelle est la meilleure façon de procéder?

MODIFIER Parce que de nombreuses personnes ont indiqué dans les commentaires que je n'en aurais peut-être pas besoin.

Le fait est que j'ai une fonction qui sérialise les propriétés d'un objet. Pour cette raison, je ne veux pas créer un objet de la classe attendue en raison de certaines restrictions de constructeur, mais en créer un semblable, disons comme un mock, ajouter des propriétés "personnalisées" I besoin, puis réinjectez-le dans la fonction.

30
Jimmy Kane

Définissez simplement votre propre classe pour le faire:

class Expando(object):
    pass

ex = Expando()
ex.foo = 17
ex.bar = "Hello"
40
Ned Batchelder

Utiliser un objet juste pour contenir des valeurs n'est pas le style de programmation le plus Pythonic. C'est courant dans les langages de programmation qui n'ont pas de bons conteneurs associatifs, mais en Python, vous pouvez utiliser un dictionnaire:

my_dict = {} # empty dict instance

my_dict["foo"] = "bar"
my_dict["num"] = 42

Vous pouvez également utiliser un "dictionnaire littéral" pour définir le contenu du dictionnaire à la fois:

my_dict = {"foo":"bar", "num":42}

Ou, si vos clés sont toutes des identifiants légaux (et elles le seront, si vous envisagiez qu'elles soient des noms d'attribut), vous pouvez utiliser le constructeur dict avec des arguments de mot clé comme paires clé-valeur:

my_dict = dict(foo="bar", num=42) # note, no quotation marks needed around keys

Remplir un dictionnaire est en fait ce que Python fait dans les coulisses lorsque vous utilisez un objet, comme dans la réponse de Ned Batchelder. Les attributs de son objet ex sont stockés dans un dictionnaire, ex.__dict__, qui devrait finir par être égal à un dict équivalent créé directement.

Sauf syntaxe d'attribut (par exemple ex.foo) est absolument nécessaire, vous pouvez aussi bien ignorer complètement l'objet et utiliser directement un dictionnaire.

19
Blckknght

Si vous adoptez l'approche de métaclasse de la réponse de @ Martijn, la réponse de @ Ned peut être réécrite plus court (bien qu'elle soit évidemment moins lisible, mais fait la même chose).

obj = type('Expando', (object,), {})()
obj.foo = 71
obj.bar = 'World'

Ou tout simplement, qui fait la même chose que ci-dessus en utilisant l'argument dict:

obj = type('Expando', (object,), {'foo': 71, 'bar': 'World'})()

Pour Python 3, passer l'objet à l'argument bases n'est pas nécessaire (voir la documentation type ).

Mais pour les cas simples, l'instanciation n'a aucun avantage, il est donc normal de le faire:

ns = type('Expando', (object,), {'foo': 71, 'bar': 'World'})

Dans le même temps, personnellement, je préfère une classe simple (c'est-à-dire sans instanciation) pour les cas de configuration de test ad-hoc aussi simple et lisible:

class ns:
    foo = 71
    bar = 'World'

Mettre à jour

Dans Python 3.3+, il y a exactement ce que OP demande, types.SimpleNamespace . C'est juste:

Une simple sous-classe object qui fournit un accès d'attribut à son espace de noms, ainsi qu'une reprsentation significative.

Contrairement à object, avec SimpleNamespace vous pouvez ajouter et supprimer des attributs. Si un objet SimpleNamespace est initialisé avec des arguments de mots clés, ceux-ci sont directement ajoutés à l'espace de noms sous-jacent.

import types
obj = types.SimpleNamespace()
obj.a = 123
print(obj.a) # 123
print(repr(obj)) # namespace(a=123)

Cependant, dans stdlib des deux Python 2 et Python 3 il y a argparse.Namespace , qui a le même objectif:

Objet simple pour stocker des attributs.

Implémente l'égalité par les noms et valeurs d'attribut et fournit une représentation de chaîne simple.

import argparse
obj = argparse.Namespace()
obj.a = 123
print(obj.a) # 123 
print(repr(obj)) # Namespace(a=123)

Notez que les deux peuvent être initialisés avec des arguments de mots clés:

types.SimpleNamespace(a = 'foo',b = 123)
argparse.Namespace(a = 'foo',b = 123)
8
saaj

Utilisez la fabrique de classes collections.namedtuple() pour créer une classe personnalisée pour votre valeur de retour:

from collections import namedtuple
return namedtuple('Expando', ('dynamic_property_a', 'dynamic_property_b'))('abc', 'abcdefg')

La valeur renvoyée peut être utilisée à la fois en tant que Tuple et par accès d'attribut:

print retval[0]                  # prints 'abc'
print retval.dynamic_property_b  # prints 'abcdefg'  
4
Martijn Pieters

Une façon que j'ai trouvée est aussi de créer un lambda. Il peut avoir des effets secondaires et possède certaines propriétés qui ne sont pas souhaitées. Je poste juste pour l'intérêt.

dynamic_object = lambda:expando
dynamic_object.dynamic_property_a = "abc"
dynamic_object.dynamic_property_b = "abcdefg"
3
Jimmy Kane