How can I create an object and add attributes to it?
Asked Answered
B

18

439

I want to create a dynamic object in Python and then add attributes to it. This didn't work:

obj = object()
obj.somefield = "somevalue"

AttributeError: 'object' object has no attribute 'somefield'


For details on why it doesn't work, see Can't set attributes on instance of "object" class.

Beverie answered 13/5, 2010 at 14:34 Comment(2)
Why are you doing this? A generic "object" has no actual meaning. What is the meaning of the thing you are creating? Why is it not a proper class or namedtuple?Decontrol
The example is not minimal and confusing for me or I just don't see why you don't work with some a = object() and you need obj.a = object(). Again I am talking about the example, in your actual code an object inside an object might be useful.Mistakable
M
271

You could use my ancient Bunch recipe, but if you don't want to make a "bunch class", a very simple one already exists in Python -- all functions can have arbitrary attributes (including lambda functions). So, the following works:

obj = lambda: None
obj.somefield = 'somevalue'

Whether the loss of clarity compared to the venerable Bunch recipe is OK, is a style decision I will of course leave up to you.

Muggins answered 13/5, 2010 at 14:49 Comment(14)
And in some cases, considering whether or not the lambda pattern fits for your use case may lead you to realize that something you'd originally thought of as data is actually more like a function anyway--or, in any case, a functor.Borghese
but... then obj.a is a function, not an object. This makes no sense, compared to FogleBird's answer.Epner
@naught101, a function is an object, in Python, so your objection is unfathomable.Muggins
@AlexMartelli: yes, obviously just about everything in python is an object. So why complicate things? it just adds a whole lot of unnecessary attributes and methods, and makes it uselessly callable...Epner
@naught101, avoiding the creation of a new type (reusing an existing one) does not complicate, it simplifies. Nowadays on might actually prefer from argparse import Namespace though I wish it lived elsewhere *e.g, collection) -- again reusing a now-existing type, just a better one, and still avoiding new-type creation. But, it wasn't there then:-).Muggins
I was looking for exactly this functionality, but from argparse import Namespace is so counter intuitive. Has it ever been suggested to move it, indeed, to collections?Nguyen
@schatten, I don't know -- I haven't followed the python-ideas mailing list in a while -- I'm still mostly using Python 2.7 and my own ancient Bunch recipe anyway, and any changes in 3.5 or later would not benefit me for quite a while:-). BTW, I believe the Python standard library has other (essentially) implementations of Bunch, too -- argparse.Namespace is just one of them.Muggins
See answer below from "J F Sebastian" regarding SimpleNamespace from the types module. If your version of python supports it, this is the best solution (and exactly what SimpleNamespace is designed for)Trigonometry
@AlexMartelli could have even gone deeper there, 23 itself can be expressed as a bunch of functions. see "church encoding" :)Leahy
It became a pip package at some point :) pypi.python.org/pypi/bunchSalangia
@AlexMartelli Except that dynamically slapping more attributes onto some object that happens to be a callable has nothing to do with lambda calculus and is by all means "hacky" - abusing some random object that had no intention of being a dictionary for you, but just so happens to work. Not that I don't like lambda calculus. I really do.Skaggs
@AlexMartelli The way Python's lambda is being used here has nothing to do with the way functions work in the lambda calculus.Engrossment
This answrer from @AlexMartelli should not be marked as correct: NameError: name 'someobject' is not definedPagination
Dude, the OP's question clearly implies that someobject has been previously set -- indeed it STARTS by assigning it.Muggins
Z
470

The built-in object can be instantiated but can't have any attributes set on it. (I wish it could, for this exact purpose.) This is because it doesn't have a __dict__ to hold the attributes.


I generally just do this:

class Object(object):
    pass

obj = Object()
obj.somefield = "somevalue"

But consider giving the Object class a more meaningful name, depending on what data it holds.


Another possibility is to use a sub-class of dict that allows attribute access to get at the keys:

class AttrDict(dict):
    def __getattr__(self, key):
        return self[key]

    def __setattr__(self, key, value):
        self[key] = value

obj = AttrDict()
obj.somefield = "somevalue"

To instantiate the object attributes using a dictionary:

d = {"a": 1, "b": 2, "c": 3}

for k, v in d.items():
    setattr(obj, k, v)
Zante answered 13/5, 2010 at 14:41 Comment(2)
it can be instantiated, just not used for anything useful once it has been done. foo = object() works, but you just can't do much of anything with itLiturgy
great answer, the only thing I changed was to use Struct as the name of the class to make it more obvious. Saved me a ton of typing [" and "], Cheers!Acarid
M
271

You could use my ancient Bunch recipe, but if you don't want to make a "bunch class", a very simple one already exists in Python -- all functions can have arbitrary attributes (including lambda functions). So, the following works:

obj = lambda: None
obj.somefield = 'somevalue'

Whether the loss of clarity compared to the venerable Bunch recipe is OK, is a style decision I will of course leave up to you.

Muggins answered 13/5, 2010 at 14:49 Comment(14)
And in some cases, considering whether or not the lambda pattern fits for your use case may lead you to realize that something you'd originally thought of as data is actually more like a function anyway--or, in any case, a functor.Borghese
but... then obj.a is a function, not an object. This makes no sense, compared to FogleBird's answer.Epner
@naught101, a function is an object, in Python, so your objection is unfathomable.Muggins
@AlexMartelli: yes, obviously just about everything in python is an object. So why complicate things? it just adds a whole lot of unnecessary attributes and methods, and makes it uselessly callable...Epner
@naught101, avoiding the creation of a new type (reusing an existing one) does not complicate, it simplifies. Nowadays on might actually prefer from argparse import Namespace though I wish it lived elsewhere *e.g, collection) -- again reusing a now-existing type, just a better one, and still avoiding new-type creation. But, it wasn't there then:-).Muggins
I was looking for exactly this functionality, but from argparse import Namespace is so counter intuitive. Has it ever been suggested to move it, indeed, to collections?Nguyen
@schatten, I don't know -- I haven't followed the python-ideas mailing list in a while -- I'm still mostly using Python 2.7 and my own ancient Bunch recipe anyway, and any changes in 3.5 or later would not benefit me for quite a while:-). BTW, I believe the Python standard library has other (essentially) implementations of Bunch, too -- argparse.Namespace is just one of them.Muggins
See answer below from "J F Sebastian" regarding SimpleNamespace from the types module. If your version of python supports it, this is the best solution (and exactly what SimpleNamespace is designed for)Trigonometry
@AlexMartelli could have even gone deeper there, 23 itself can be expressed as a bunch of functions. see "church encoding" :)Leahy
It became a pip package at some point :) pypi.python.org/pypi/bunchSalangia
@AlexMartelli Except that dynamically slapping more attributes onto some object that happens to be a callable has nothing to do with lambda calculus and is by all means "hacky" - abusing some random object that had no intention of being a dictionary for you, but just so happens to work. Not that I don't like lambda calculus. I really do.Skaggs
@AlexMartelli The way Python's lambda is being used here has nothing to do with the way functions work in the lambda calculus.Engrossment
This answrer from @AlexMartelli should not be marked as correct: NameError: name 'someobject' is not definedPagination
Dude, the OP's question clearly implies that someobject has been previously set -- indeed it STARTS by assigning it.Muggins
M
190

There is types.SimpleNamespace class in Python 3.3+:

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

collections.namedtuple, typing.NamedTuple could be used for immutable objects. PEP 557 -- Data Classes suggests a mutable alternative.

For a richer functionality, you could try attrs package. See an example usage. pydantic may be worth a look too.

Mischiefmaker answered 16/8, 2015 at 15:13 Comment(3)
If you need something that works with Python 2.7, you can also try the argparse.Namespace classPuduns
@Roel attrs package supports Python 2.7Mischiefmaker
This seems a better solution to me than unittest.mock; the latter is a bit too heavy-weight and a bit more malleable. With a mock object, simply assigning to an attribute will cause it to spring into existence; SimpleNamespace will resist that.Hanes
M
64

You can also use a class object directly; it creates a namespace:

class a: pass
a.somefield1 = 'somevalue1'
setattr(a, 'somefield2', 'somevalue2')
Mckenzie answered 5/1, 2018 at 23:44 Comment(4)
I don't understand why this is not the top answer?Polychromatic
@Polychromatic it doesn't say anything that the top answer is missing.Moo
@KarlKnechtel This answer uses a custom class directly, while the top answer instantiates a custom class.Mckenzie
Easy to make mistakes with this (e.g. reusing the class). Instantiating an object is much more robust.Claudicant
K
58

The mock module is basically made for that.

import mock
obj = mock.Mock()
obj.a = 5
Kermanshah answered 9/2, 2017 at 15:47 Comment(3)
Disadvantge is that's an external dependencyCissy
unittest.Mock is a part of the standard library since Python 3.3 (docs.python.org/3/library/unittest.mock.html)Elwina
Depends on the usage of your code I think. If it is production code, I would not want some mock in it. Just feels weird to me.Portfire
B
36

There are a few ways to reach this goal. Basically you need an object which is extendable.

obj = type('Test', (object,), {})  
obj.b = 'fun'
obj = lambda:None
obj.b = 'fun'
class Test:
  pass

obj = Test()
obj.b = 'fun'
Babbitt answered 13/5, 2010 at 14:48 Comment(1)
obj.a = type('', (), {})Tedi
U
25

Now you can do (not sure if it's the same answer as evilpie):

MyObject = type('MyObject', (object,), {})
obj = MyObject()
obj.value = 42
Uptotheminute answered 13/9, 2013 at 0:32 Comment(1)
@evilpie's answer sets attributes directly on MyObject (the class), not its instance like yours.Mischiefmaker
S
23

Try the code below:

$ 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']
Shalom answered 23/7, 2015 at 20:49 Comment(4)
Can you explain more of what this does? while the code may be useful for solving this problem, having it explained can go a lot further than just one problem.Limousine
@Limousine Clearly it creates a new Class(object) which is a empty class with object properties, and stores the attributes inside the class. This is even better than install more modules, or rely in lambdas.Kammerer
Not sure why this does not have more upvotes. Is there a reason not to use this for a basic container class? Seems to work fine in Python 2.7, 2.6, and 3.4Trochlear
So how does this set the value of an attribute whose name is contained in a separate variable?Bulahbulawayo
B
10

as docs say:

Note: object does not have a __dict__, so you can’t assign arbitrary attributes to an instance of the object class.

You could just use dummy-class instance.

Barbarize answered 13/5, 2010 at 14:43 Comment(0)
R
4

These solutions are very helpful during testing. Building on everyone else's answers I do this in Python 2.7.9 (without staticmethod I get a TypeError (unbound method...):

In [11]: auth = type('', (), {})
In [12]: auth.func = staticmethod(lambda i: i * 2)
In [13]: auth.func(2)
Out[13]: 4
Religieux answered 27/8, 2015 at 20:34 Comment(0)
B
3

If we can determine and aggregate all the attributes and values together before creating the nested object, then we could create a new class that takes a dictionary argument on creation.

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

We can also allow keyword arguments. See this 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')
Bobodioulasso answered 19/11, 2018 at 21:59 Comment(0)
F
2

To create an object from a dictionary:

class Struct(object):
    def __init__(self, d):
        for key in d.keys():
            self.__setattr__(key, d[key])

Usage:

>>> obj = Struct({'a': 1, 'b': 2})
>>> obj.a
1
Fauve answered 11/5, 2023 at 22:35 Comment(2)
Another way is types.SimpleNamespace(**d).Claudicant
Thanks for sharing! This is why I always scroll down.Osteoid
S
1

Which objects are you using? Just tried that with a sample class and it worked fine:

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

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

And I got 123 as the answer.

The only situation where I see this failing is if you're trying a setattr on a builtin object.

Update: From the comment this is a repetition of: Why can't you add attributes to object in python?

Shoveler answered 13/5, 2010 at 14:52 Comment(1)
b.c is set to object() not a defined classBeverie
Q
1

I think the easiest way is through the collections module.

import collections
FinanceCtaCteM = collections.namedtuple('FinanceCtaCte', 'forma_pago doc_pago get_total')
def get_total(): return 98989898
financtacteobj = FinanceCtaCteM(forma_pago='CONTADO', doc_pago='EFECTIVO',
                                get_total=get_total)

print financtacteobj.get_total()
print financtacteobj.forma_pago
print financtacteobj.doc_pago
Quark answered 21/8, 2020 at 15:58 Comment(0)
M
1

if you are looking for chain assignment, to do things such as django model template abstract attribute assigning:

from types import SimpleNamespace


def assign(target, *args, suffix):
    ls = target
    for i in range(len(args) - 1):
        a = args[i]
        ns = SimpleNamespace()
        setattr(ls, a, ns)
        ls = ns
    setattr(ls, args[-1], suffix)
    return ls


a = SimpleNamespace()
assign(a, 'a', 'b', 'c', suffix={'name': 'james'})
print(a.a.b.c)
# {'name': 'james'}

which allows you to pass model as a target, and assign end attribute to it.

Mistreat answered 25/4, 2022 at 12:50 Comment(0)
H
0

Coming to this late in the day but here is my pennyworth with an object that just happens to hold some useful paths in an app but you can adapt it for anything where you want a sorta dict of information that you can access with getattr and dot notation (which is what I think this question is really about):

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

This is cool because now:

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

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

So this uses the function object like the above answers but uses the function to get the values (you can still use (getattr, x_path, 'repository') rather than x_path('repository') if you prefer).

Hey answered 2/3, 2016 at 2:17 Comment(0)
V
-2
di = {}
for x in range(20):
    name = '_id%s' % x
    di[name] = type(name, (object), {})
    setattr(di[name], "attr", "value")
Verde answered 27/12, 2013 at 19:41 Comment(0)
L
-3

Other way i see, this way:

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)
Langobardic answered 13/5, 2016 at 17:22 Comment(2)
you can add in append the name attr this di[name].append([at,cmds.getAttr(name+'.'+at)[0]])Langobardic
This is adding a very big non-standard dependency while a simple class a: pass gives all the power required.Paginal

© 2022 - 2024 — McMap. All rights reserved.