Comment puis-je créer un objet et y ajouter des attributs?

311

Je veux créer un objet dynamique (à l'intérieur d'un autre objet) en Python, puis y ajouter des attributs.

J'ai essayé:

obj = someobject
obj.a = object()
setattr(obj.a, 'somefield', 'somevalue')

mais cela n'a pas fonctionné.

Des idées?

Éditer:

Je mets les attributs d'une forboucle qui boucle à travers une liste de valeurs, par exemple

params = ['attr1', 'attr2', 'attr3']
obj = someobject
obj.a = object()

for p in params:
   obj.a.p # where p comes from for loop variable

Dans l'exemple ci - dessus , je recevrais obj.a.attr1, obj.a.attr2, obj.a.attr3.

J'ai utilisé la setattrfonction parce que je ne savais pas comment faire à obj.a.NAMEpartir d'une forboucle.

Comment définirais-je l'attribut en fonction de la valeur de pdans l'exemple ci-dessus?

John
la source
4
Qu'entendez-vous par «n'a pas fonctionné»? Je suppose qu'il a déclenché une exception AttributeError, non?
Josh Wright
1
Ouais. L'objet 'object' n'a pas d'attribut 'somefield'
John
6
Pourquoi fais-tu ça? Un "objet" générique n'a aucune signification réelle . Quelle est la signification de la chose que vous créez? Pourquoi n'est-ce pas une classe appropriée ou un tuple nommé?
S.Lott
1
L'exemple n'est pas minimal et déroutant pour moi ou je ne vois tout simplement pas pourquoi vous ne travaillez pas avec certains a = object()et vous en avez besoin obj.a = object(). Encore une fois, je parle de l'exemple, dans votre code réel, un objet à l'intérieur d'un objet peut être utile.
kon psych

Réponses:

215

Vous pouvez utiliser mon ancienne recette de Bunch , mais si vous ne voulez pas créer une "classe de bunch", une très simple existe déjà en Python - toutes les fonctions peuvent avoir des attributs arbitraires (y compris les fonctions lambda). Ainsi, les travaux suivants:

obj = someobject
obj.a = lambda: None
setattr(obj.a, 'somefield', 'somevalue')

Que la perte de clarté par rapport à la vénérable Bunchrecette soit correcte, c'est une décision de style que je vous laisse bien sûr.

Alex Martelli
la source
25
@FogleBird, une décision de style, comme je l'ai mentionné. Certains experts CS formés par exemple au calcul lambda de Church sont habitués à considérer les fonctions (lambdas) comme le type fondamental de toutes les données (l'entier 23, par exemple, peut être considéré comme équivalent à lambda: 23), de sorte que ces experts utilisent lambdas à cette fin se sentirait probablement rien comme "un hack". Personnellement, je n'aime pas beaucoup lambda en Python - mais c'est vraiment une question de goût personnel.
Alex Martelli
Et dans certains cas, la question de savoir si le lambdamodèle convient à votre cas d'utilisation peut vous amener à réaliser que quelque chose que vous aviez initialement pensé comme des données est en fait plus une fonction - ou, en tout cas, un foncteur.
Kyle Strand
5
@ naught101, une fonction est un objet, en Python, donc votre objection est insondable.
Alex Martelli
6
@ naught101, éviter la création d'un nouveau type (réutiliser un type existant) ne complique pas, cela simplifie. De nos jours, from argparse import Namespaceje préférerais peut-être que je vive ailleurs *, par exemple collection) - en réutilisant à nouveau un type existant, juste meilleur, et en évitant toujours la création de nouveaux types. Mais ce n'était pas là alors :-).
Alex Martelli
1
Voir la réponse ci-dessous de "JF Sebastian" concernant SimpleNamespace du module types. Si votre version de python le prend en charge, c'est la meilleure solution (et exactement pour quoi SimpleNamespace est conçu)
Tim Richardson
334

L'intégré objectpeut être instancié mais aucun attribut ne peut y être défini. (Je souhaite que ce soit possible, dans ce but précis.) Il n'a pas __dict__de support pour les attributs.

Je fais généralement juste ceci:

class Object(object):
    pass

a = Object()
a.somefield = somevalue

Quand je le peux, je donne à la Objectclasse un nom plus significatif, selon le type de données que j'y mets.

Certaines personnes font une chose différente, où elles utilisent une sous-classe dictqui permet l'accès aux attributs pour obtenir les clés. ( d.keyau lieu de d['key'])

Edit : Pour l'ajout à votre question, l'utilisation setattrest très bien. Vous ne pouvez tout simplement pas utiliser setattrsur les object()instances.

params = ['attr1', 'attr2', 'attr3']
for p in params:
    setattr(obj.a, p, value)
FogleBird
la source
9
il peut être instancié, mais n'est pas utilisé pour quelque chose d'utile une fois qu'il a été fait. foo = object()fonctionne, mais vous ne pouvez pas faire grand-chose avec lui
Daniel DiPaolo
Salut. Merci d'avoir répondu. J'ai mis à jour mon problème ci-dessus. voir le montage. avez vous une réponse à ça?
John
désolé, je veux toujours le placer sur l'objet. voir la mise à jour ci-dessus.
John
J'aime vraiment votre réponse et je pense que je pencherai dans cette direction à l'avenir. J'ai utilisé à peu près tout le reste sur ce post, à l'exception de cette méthodologie très simple, compréhensible et lisible. Utiliser type....ou lambda n'a jamais été mon préféré, comme du vomi de texte dans mon code. Mais cette idée est idéale pour utiliser des objets pour conserver des propriétés. Laisse le code plus lisible b / c quand je vois lambda je ralentis ma lecture à 25% alors que votre chemin est totalement logique! Merci.
Marc
excellente réponse, la seule chose que j'ai changé a été d'utiliser Structle nom de la classe pour la rendre plus évidente. M'a sauvé une tonne de dactylographie ["et "], Cheers!
pragman
137

Il y a de la types.SimpleNamespaceclasse dans Python 3.3+ :

obj = someobject
obj.a = SimpleNamespace()
for p in params:
    setattr(obj.a, p, value)
# obj.a.attr1

collections.namedtuple, typing.NamedTuplepourrait être utilisé pour des objets immuables. PEP 557 - Les classes de données suggèrent une alternative mutable.

Pour une fonctionnalité plus riche, vous pouvez essayer le attrspackage . Voir un exemple d'utilisation .

jfs
la source
3
Si vous avez besoin de quelque chose qui fonctionne avec Python 2.7, vous pouvez également essayer la argparse.Namespaceclasse
RolKau
D'accord - je serais curieux de savoir s'il y a un inconvénient ici, mais c'est une offre incroyablement pratique en python 3.3+.
ghukill
Zut! ce n'est pas disponible sur 2.7?
Roel
Le attrspackage @Roel prend en charge Python 2.7
jfs
Cela me semble une meilleure solution que unittest.mock; ce dernier est un peu trop lourd et un peu plus malléable. Avec un objet factice, une simple attribution à un attribut le fera naître; SimpleNamespace résistera à cela.
jdzions
32

Il existe plusieurs façons d'atteindre cet objectif. Fondamentalement, vous avez besoin d'un objet extensible.

obj.a = type('Test', (object,), {})  
obj.a.b = 'fun'  

obj.b = lambda:None

class Test:
  pass
obj.c = Test()
evilpie
la source
14
obj.a = type('', (), {})
iman
27

Le mockmodule est essentiellement conçu pour cela.

import mock
obj = mock.Mock()
obj.a = 5
Dunatotatos
la source
3
Le désavantage est que c'est une dépendance externe
Kangur
6
unittest.Mockfait partie de la bibliothèque standard depuis Python 3.3 ( docs.python.org/3/library/unittest.mock.html )
illagrenan
2
Cela dépend de l'utilisation de votre code, je pense. S'il s'agit d'un code de production, je n'en voudrais pas mock. Je me sens juste bizarre.
Mike de Klerk
21

Maintenant, vous pouvez le faire (je ne sais pas si c'est la même réponse que evilpie):

MyObject = type('MyObject', (object,), {})
obj = MyObject()
obj.value = 42
andreabedini
la source
La réponse de @ evilpie définit les attributs directement sur MyObject (la classe), pas sur son instance comme la vôtre.
jfs
18

Essayez le code ci-dessous:

$ python
>>> class Container(object):
...     pass 
...
>>> x = Container()
>>> x.a = 10
>>> x.b = 20
>>> x.banana = 100
>>> x.a, x.b, x.banana
(10, 20, 100)
>>> dir(x)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', 
'__getattribute__', '__hash__', '__init__', '__module__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__',     '__sizeof__', 
'__str__', '__subclasshook__', '__weakref__', 'a', 'b', 'banana']
neldor
la source
1
Pouvez-vous expliquer davantage ce que cela fait? alors que le code peut être utile pour résoudre ce problème, le faire expliquer peut aller beaucoup plus loin qu'un seul problème.
DeadChex
1
@DeadChex Clairement, il crée une nouvelle classe (objet) qui est une classe vide avec des propriétés d'objet et stocke les attributs à l'intérieur de la classe. C'est encore mieux que d'installer plus de modules ou de compter sur lambdas.
m3nda
2
Je ne sais pas pourquoi cela n'a pas plus de votes positifs. Y a-t-il une raison de ne pas l'utiliser pour une classe de conteneur de base? Semble fonctionner
correctement
17

Vous pouvez également utiliser directement un objet de classe; il crée un espace de noms:

class a: pass
a.somefield1 = 'somevalue1'
setattr(a, 'somefield2', 'somevalue2')
Ernesto
la source
9

comme le disent les docs :

Note : objectne pas avoir un __dict__, donc vous ne pouvez pas affecter des attributs arbitraires à une instance de la objectclasse.

Vous pouvez simplement utiliser une instance de classe factice.

SilentGhost
la source
2

Ces solutions sont très utiles lors des tests. En s'appuyant sur les réponses de tous les autres, je le fais en Python 2.7.9 (sans méthode statique, j'obtiens une TypeError (méthode non liée ...):

In [11]: auth = type('', (), {})
In [12]: auth.func = staticmethod(lambda i: i * 2)
In [13]: auth.func(2)
Out[13]: 4
Robpol86
la source
1

Quels objets utilisez-vous? Je viens d'essayer cela avec un exemple de classe et cela a bien fonctionné:

class MyClass:
  i = 123456
  def f(self):
    return "hello world"

b = MyClass()
b.c = MyClass()
setattr(b.c, 'test', 123)
b.c.test

Et j'ai eu 123comme réponse.

La seule situation où je vois cet échec est si vous essayez un setattrsur un objet intégré.

Mise à jour: Du commentaire, il s'agit d'une répétition de: Pourquoi ne pouvez-vous pas ajouter d'attributs à un objet en python?

jneves
la source
bc est défini sur object () pas une classe définie
John
0

J'y arrive tard dans la journée, mais voici mon pennyworth avec un objet qui se trouve justement contenir des chemins utiles dans une application, mais vous pouvez l'adapter à tout ce que vous voulez un sorta dict d'informations que vous pouvez accéder avec getattr et notation par points (c'est à quoi je pense que cette question concerne vraiment):

import os

def x_path(path_name):
    return getattr(x_path, path_name)

x_path.root = '/home/x'
for name in ['repository', 'caches', 'projects']:
    setattr(x_path, name, os.path.join(x_path.root, name))

C'est cool parce que maintenant:

In [1]: x_path.projects
Out[1]: '/home/x/projects'

In [2]: x_path('caches')
Out[2]: '/home/x/caches'

Donc, cela utilise l'objet fonction comme les réponses ci-dessus mais utilise la fonction pour obtenir les valeurs (vous pouvez toujours utiliser (getattr, x_path, 'repository')plutôt que x_path('repository')si vous préférez).

Paul Whipp
la source
0

Si nous pouvons déterminer et agréger tous les attributs et valeurs ensemble avant de créer l'objet imbriqué, alors nous pourrions créer une nouvelle classe qui prend un argument de dictionnaire lors de la création.

# python 2.7

class NestedObject():
    def __init__(self, initial_attrs):
        for key in initial_attrs:
            setattr(self, key, initial_attrs[key])

obj = someobject
attributes = { 'attr1': 'val1', 'attr2': 'val2', 'attr3': 'val3' }
obj.a = NestedObject(attributes)
>>> obj.a.attr1
'val1'
>>> obj.a.attr2
'val2'
>>> obj.a.attr3
'val3'

Nous pouvons également autoriser les arguments de mots clés. Voir cet article .

class NestedObject(object):
    def __init__(self, *initial_attrs, **kwargs):
        for dictionary in initial_attrs:
            for key in dictionary:
                setattr(self, key, dictionary[key])
        for key in kwargs:
            setattr(self, key, kwargs[key])


obj.a = NestedObject(attr1='val1', attr2='val2', attr3= 'val3')
HarlemSquirrel
la source
-2
di = {}
for x in range(20):
    name = '_id%s' % x
    di[name] = type(name, (object), {})
    setattr(di[name], "attr", "value")
lmokto
la source
-2

Autre façon je vois, de cette façon:

import maya.cmds

def getData(objets=None, attrs=None):
    di = {}
    for obj in objets:
        name = str(obj)
        di[name]=[]
        for at in attrs:
            di[name].append(cmds.getAttr(name+'.'+at)[0])
    return di

acns=cmds.ls('L_vest_*_',type='aimConstraint')
attrs=['offset','aimVector','upVector','worldUpVector']

getData(acns,attrs)
Pablo Emmanuel De Leo
la source
vous pouvez ajouter en annexe le nom attr this di [name] .append ([at, cmds.getAttr (name + '.' + at) [0]])
Pablo Emmanuel De Leo
1
Cela ajoute une très grande dépendance non standard tandis qu'un simple class a: passdonne toute la puissance requise.
Alexis Paques