ChatGPT解决这个技术问题 Extra ChatGPT

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

I want to create a dynamic object (inside another object) in Python and then add attributes to it.

I tried:

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

but this didn't work.

Any ideas?

edit:

I am setting the attributes from a for loop which loops through a list of values, e.g.

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

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

In the above example I would get obj.a.attr1, obj.a.attr2, obj.a.attr3.

I used the setattr function because I didn't know how to do obj.a.NAME from a for loop.

How would I set the attribute based on the value of p in the example above?

What do you mean by "didn't work"? I assume it raised an AttributeError exception, right?
yeah. 'object' object has no attribute 'somefield'
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?
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.

F
FogleBird

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

I generally just do this:

class Object(object):
    pass

a = Object()
a.somefield = somevalue

When I can, I give the Object class a more meaningful name, depending on what kind of data I'm putting in it.

Some people do a different thing, where they use a sub-class of dict that allows attribute access to get at the keys. (d.key instead of d['key'])

Edit: For the addition to your question, using setattr is fine. You just can't use setattr on object() instances.

params = ['attr1', 'attr2', 'attr3']
for p in params:
    setattr(obj.a, p, value)

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 it
Hi. Thanks for the answer. I've updated my problem above. see the edit. do you know the answer to this?
sorry I still want to set it on the object. see update above.
I really like your answer and I think I will lean this direction in the future. I have used about everything else on this post except for this very simple, understandable, and readable methodology. Using type.... or lambda was never my fav, like text vomit in my code. But this idea is great for using objects to hold properties. Leaves code more readable b/c when I see lambda I slow down my reading to 25% while your way makes total sense! Thanks.
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!
A
Alex Martelli

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 = someobject
obj.a = lambda: None
setattr(obj.a, '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.


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.
@naught101, a function is an object, in Python, so your objection is unfathomable.
@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:-).
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)
@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.
j
jfs

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.


If you need something that works with Python 2.7, you can also try the argparse.Namespace class
Agreed - I'd be curious if there is a downside here, but this is an incredibly handy python 3.3+ affordance.
damn! this is not available on 2.7?
@Roel attrs package supports Python 2.7
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.
D
Dunatotatos

The mock module is basically made for that.

import mock
obj = mock.Mock()
obj.a = 5

Disadvantge is that's an external dependency
unittest.Mock is a part of the standard library since Python 3.3 (docs.python.org/3/library/unittest.mock.html)
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.
M
MD. Khairul Basar

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

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

I don't understand why this is not the top answer?
Simple and effective 👍
m
mbarkhau

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

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

obj.b = lambda:None

class Test:
  pass
obj.c = Test()

obj.a = type('', (), {})
a
andreabedini

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

MyObject = type('MyObject', (object,), {})
obj = MyObject()
obj.value = 42

@evilpie's answer sets attributes directly on MyObject (the class), not its instance like yours.
E
Ellis Percival

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

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.
@DeadChex 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.
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.4
So how does this set the value of an attribute whose name is contained in a separate variable?
S
SilentGhost

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.


R
Robpol86

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

H
HarlemSquirrel

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

C
Community

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?


b.c is set to object() not a defined class
P
Pjl

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

P
Paul Whipp

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


W
Weilory

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.


V
Vinsplayer

This works just fine:

    exec("obj.a."+p)

If you want to set the attribute to some value, do this:

    exec("obj.a."+p+"=(the value here)")

For the value to be a string you will have to use these \" instead of quotation marks unless you have the value stored in a variable.


l
lmokto
di = {}
for x in range(20):
    name = '_id%s' % x
    di[name] = type(name, (object), {})
    setattr(di[name], "attr", "value")

l
leota

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)

you can add in append the name attr this di[name].append([at,cmds.getAttr(name+'.'+at)[0]])
This is adding a very big non-standard dependency while a simple class a: pass gives all the power required.