¿Cómo puedo crear un objeto y agregarle atributos?

311

Quiero crear un objeto dinámico (dentro de otro objeto) en Python y luego agregarle atributos.

Lo intenté:

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

Pero esto no funcionó.

¿Algunas ideas?

editar:

Estoy configurando los atributos de un forbucle que recorre una lista de valores, por ejemplo

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

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

En el ejemplo anterior me gustaría conseguir obj.a.attr1, obj.a.attr2, obj.a.attr3.

Usé la setattrfunción porque no sabía cómo hacerlo obj.a.NAMEdesde un forbucle.

¿Cómo establecería el atributo en función del valor de pen el ejemplo anterior?

Juan
fuente
44
¿Qué quieres decir con "no funcionó"? Supongo que generó una excepción AttributeError, ¿verdad?
Josh Wright
1
Si. 'objeto' objeto no tiene atributo 'somefield'
Juan
66
¿Por qué estás haciendo esto? Un "objeto" genérico no tiene un significado real . ¿Cuál es el significado de lo que estás creando? ¿Por qué no es una clase adecuada o una tupla nombrada?
S.Lott
1
El ejemplo no es mínimo y confuso para mí o simplemente no veo por qué no trabajas con algunos a = object()y lo necesitas obj.a = object(). Nuevamente, estoy hablando del ejemplo, en su código real, un objeto dentro de un objeto podría ser útil.
kon psych

Respuestas:

215

Podrías usar mi antigua receta Bunch , pero si no quieres hacer una "clase de grupo", ya existe una muy simple en Python: todas las funciones pueden tener atributos arbitrarios (incluidas las funciones lambda). Entonces, lo siguiente funciona:

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

Si la pérdida de claridad en comparación con la Bunchreceta venerable está bien, es una decisión de estilo que, por supuesto, dejaré en sus manos.

Alex Martelli
fuente
25
@FogleBird, una decisión de estilo, como mencioné. Algunos expertos en CS capacitados, por ejemplo, en el cálculo lambda de Church, están acostumbrados a pensar en las funciones (lambdas) como el tipo fundamental de todos los datos (el número entero 23, por ejemplo, puede verse como equivalente a lambda: 23), por lo que a dichos expertos usan lambdas para este propósito presumiblemente no sentiría nada como "un hack". Personalmente, no me gusta mucho lambda Python , pero eso es en gran medida un problema de gusto personal.
Alex Martelli
Y en algunos casos, considerar si el lambdapatrón se ajusta o no a su caso de uso puede llevarlo a darse cuenta de que algo que originalmente pensó como datos es en realidad más como una función, o, en cualquier caso, un functor.
Kyle Strand
55
@ naught101, una función es un objeto, en Python, por lo que su objeción es insondable.
Alex Martelli
66
@ naught101, evitar la creación de un nuevo tipo (reutilizar uno existente) no complica, simplifica. Hoy en día, podría preferirlo, from argparse import Namespaceaunque desearía que viviera en otro lugar *, por ejemplo, collection) - nuevamente reutilizando un tipo ahora existente, solo uno mejor, y aún evitando la creación de nuevos tipos. Pero, no estaba allí entonces :-).
Alex Martelli
1
Vea la respuesta a continuación de "JF Sebastian" con respecto a SimpleNamespace del módulo de tipos. Si su versión de Python lo admite, esta es la mejor solución (y exactamente para lo que está diseñado SimpleNamespace)
Tim Richardson
334

El incorporado objectpuede ser instanciado pero no puede tener ningún atributo establecido en él. (Ojalá pudiera, para este propósito exacto). No tiene __dict__que contener los atributos.

Generalmente solo hago esto:

class Object(object):
    pass

a = Object()
a.somefield = somevalue

Cuando puedo, le doy a la Objectclase un nombre más significativo, dependiendo de qué tipo de datos estoy poniendo.

Algunas personas hacen algo diferente, donde usan una subclase dictque permite el acceso a los atributos para obtener las teclas. (en d.keylugar de d['key'])

Editar : para agregar a su pregunta, usar setattrestá bien. Simplemente no se puede usar setattren object()instancias.

params = ['attr1', 'attr2', 'attr3']
for p in params:
    setattr(obj.a, p, value)
FogleBird
fuente
99
se puede instanciar, simplemente no se usa para nada útil una vez que se ha hecho. foo = object()funciona, pero simplemente no puedes hacer nada con él
Daniel DiPaolo
Hola. Gracias por la respuesta. He actualizado mi problema arriba. ver la edición ¿Sabes la respuesta a esto?
John
lo siento, todavía quiero establecerlo en el objeto. ver actualización arriba.
John
Realmente me gusta su respuesta y creo que me inclinaré por esta dirección en el futuro. He usado todo lo demás en esta publicación, excepto esta metodología muy simple, comprensible y legible. Usar type....o lambda nunca fue mi favorito, como el vómito de texto en mi código. Pero esta idea es excelente para usar objetos para mantener propiedades. Deja el código más legible b / c cuando veo lambda. ¡Disminuyo mi lectura al 25% mientras tu camino tiene sentido! Gracias.
Marc
gran respuesta, lo único que cambié fue usar Structcomo nombre de la clase para hacerlo más obvio. Me ahorró un montón de mecanografía ["y "], ¡salud!
pragman
137

Hay types.SimpleNamespaceclase en Python 3.3+ :

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

collections.namedtuple, typing.NamedTuplepodría usarse para objetos inmutables. PEP 557: las clases de datos sugieren una alternativa mutable.

Para una funcionalidad más rica, puedes probar el attrspaquete . Vea un ejemplo de uso .

jfs
fuente
3
Si necesita algo que funcione con Python 2.7, también puede probar la argparse.Namespaceclase
RolKau
De acuerdo: me gustaría saber si hay una desventaja aquí, pero esta es una increíblemente útil Python 3.3+.
ghukill
¡Maldición! esto no está disponible en 2.7?
Roel
El attrspaquete @Roel admite Python 2.7
jfs el
Esto me parece una mejor solución que unittest.mock; este último es demasiado pesado y un poco más maleable. Con un objeto simulado, simplemente asignarlo a un atributo hará que surja; SimpleNamespace resistirá eso.
jdzions
32

Hay algunas formas de alcanzar este objetivo. Básicamente necesitas un objeto que sea extensible.

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

obj.b = lambda:None

class Test:
  pass
obj.c = Test()
evilpie
fuente
14
obj.a = type('', (), {})
Iman
27

El mockmódulo está básicamente hecho para eso.

import mock
obj = mock.Mock()
obj.a = 5
Dunatotatos
fuente
3
La desventaja es que es una dependencia externa
Kangur
66
unittest.Mockes parte de la biblioteca estándar desde Python 3.3 ( docs.python.org/3/library/unittest.mock.html )
illagrenan
2
Depende del uso de su código, creo. Si se trata de un código de producción, no quisiera incluirlo mock. Solo se siente raro para mí.
Mike de Klerk
21

Ahora puedes hacerlo (no estoy seguro si es la misma respuesta que evilpie):

MyObject = type('MyObject', (object,), {})
obj = MyObject()
obj.value = 42
andreabedini
fuente
La respuesta de @ evilpie establece atributos directamente en MyObject (la clase), no en su instancia como la suya.
jfs
18

Prueba el siguiente código:

$ 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
fuente
1
¿Puedes explicar más de lo que esto hace? Si bien el código puede ser útil para resolver este problema, tenerlo explicado puede ir mucho más allá de un solo problema.
DeadChex
1
@DeadChex Claramente crea una nueva Clase (objeto) que es una clase vacía con propiedades de objeto, y almacena los atributos dentro de la clase. Esto es incluso mejor que instalar más módulos o confiar en lambdas.
m3nda
2
No estoy seguro de por qué esto no tiene más votos a favor. ¿Hay alguna razón para no usar esto para una clase de contenedor básico? Parece funcionar bien en Python 2.7, 2.6 y 3.4
user5359531
17

También puede usar un objeto de clase directamente; crea un espacio de nombres:

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

como dicen los documentos :

Nota : objectno , no tiene una __dict__, por lo que no se puede asignar atributos arbitrarios a una instancia de la objectclase.

Podrías usar una instancia de clase ficticia.

SilentGhost
fuente
2

Estas soluciones son muy útiles durante las pruebas. Basándome en las respuestas de todos los demás, hago esto en Python 2.7.9 (sin el método estático obtengo un TypeError (método independiente ...):

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

¿Qué objetos estás usando? Solo intenté eso con una clase de muestra y funcionó bien:

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

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

Y obtuve 123la respuesta.

La única situación en la que veo que esto falla es si estás probando un setattrobjeto incorporado.

Actualización: Del comentario, esta es una repetición de: ¿Por qué no puedes agregar atributos al objeto en Python?

jneves
fuente
bc se establece en object () no en una clase definida
John
0

Llegando a esto tarde en el día, pero aquí está mi pennyworth con un objeto que simplemente contiene algunas rutas útiles en una aplicación, pero puede adaptarlo para cualquier cosa donde desee un tipo de información a la que pueda acceder con getattr y notación de puntos (que es de lo que creo que se trata realmente esta pregunta):

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))

Esto es genial porque ahora:

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

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

Por lo tanto, esto usa el objeto de función como las respuestas anteriores, pero usa la función para obtener los valores (aún puede usar en (getattr, x_path, 'repository')lugar de x_path('repository')si lo prefiere).

Paul Whipp
fuente
0

Si podemos determinar y agregar todos los atributos y valores juntos antes de crear el objeto anidado, entonces podríamos crear una nueva clase que tome un argumento de diccionario en la creación.

# 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'

También podemos permitir argumentos de palabras clave. Ver este post .

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')
Harlem Ardilla
fuente
-2
di = {}
for x in range(20):
    name = '_id%s' % x
    di[name] = type(name, (object), {})
    setattr(di[name], "attr", "value")
lmokto
fuente
-2

De otra manera veo, de esta manera:

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
fuente
puede agregar anexar el nombre attr this di [name] .append ([at, cmds.getAttr (name + '.' + at) [0]])
Pablo Emmanuel De Leo
1
Esto agrega una dependencia no estándar muy grande, mientras que un simple class a: passbrinda toda la potencia requerida.
Alexis Paques