0

VB (and C99 and C#, actually) have a way to set multiple attributes on one object with a contracted syntax where you don't have to repeat the object name before "." . Is there a way to do this in Python without having to loop over a dictionary, calling setattr on each item?

Something like

obj.update_attr(
   'attr1': 'foo',
   'attr2': 'bar'
)
8
  • 5
    obj.__dict__.update({'attr1': 'foo', 'attr2': 'bar'}) Commented Jul 23, 2014 at 20:22
  • Please add that as an answer - I think it's just what I need Commented Jul 23, 2014 at 20:23
  • 1
    Note that some objects won't have __dict__ though... Commented Jul 23, 2014 at 20:24
  • 1
    Keep in mind that the solution provided by vaultah will not always work. As Jon Clements mentioned, some objects don't have a obj.__dict__. Another issue is that the object you are working with might rely on __setattr__ for its functionality, and if you update obj.__dict__ directly then you are bypassing that functionality (libraries like SQLAlchemy come to mind). Commented Jul 23, 2014 at 20:26
  • 1
    Or, for those who don't like the dunder: vars(obj).update(dct), but for the reasons that others have already mentioned, I would avoid this idiom unless you actually know the object that you're working with pretty well (e.g. inside a class method where you're adding attributes to self) Commented Jul 23, 2014 at 20:26

4 Answers 4

5

First, keep in mind that you're really not saving much space—and you could save a lot more space, and make your style more Pythonic, by just temporarily binding the object to a shorter name:

_ = obj
_.attr1 = 'foo'
_.attr2 = 'bar'

Compare that to VB-ish syntax:

vbwith obj:
    .attr1 = 'foo'
    .attr2 = 'bar'

You're really only typing and reading one extra character per line. Is that too much of a burden?

This wouldn't work in languages with C-style variables with value-copying semantics, because you'd just be setting attributes on a copy of obj. But in Python, a = b makes a into another name for the value that b names, it doesn't copy anything. (If you're coming from a C background, it may help to think of this as _ = &obj followed by a bunch of lines starting _->.)


If you really want to do this, the safest way is to write update_attr as a loop around setattr:

def update_attrs(obj, **attrs):
    for attr, value in attrs.items():
        setattr(obj, attr, value)

update_attr(obj,
    attr1='foo',
    attr2='bar'
)

The reason you want to use setattr rather than modifying the object's __dict__ or vars() is that not all attributes are stored in the object's __dict__. They may be stored in __slots__, or @property descriptors on the class, or get-set methods on a C extension object, or some custom thing provided by the metaclass. So, modifying the __dict__ may fail in some cases, or may appear to work but do nothing, or, worst of all, do the wrong thing in a way that's very hard to tell (like hiding a property instead of setting it). setattr is guaranteed to work whenever direct access would work, and to fail whenever direct access would fail.


While much of the Python community thinks actual VB-style with syntax is a bad thing (although I can't find the link to Guido's blog post about it), your more explicit syntax seems readable and not at all misleading to me, so if you really want to use this function, go ahead. But again, I don't think there's much need.

Sign up to request clarification or add additional context in comments.

2 Comments

The only objection I see to using _ here is that code would behave differently when run at the console, where _ is assigned the result of the last command.
@CharlesDuffy: Well, I have a second objection: if you're using gettext for l10n and doing the common trick of assigning the gettext wrapper to _, this will break your code. :) Anyway, _ is the idiomatic way to say "I need a variable and I don't care what it's called" as long as neither of those issues is relevant, but you're right that it's worth pointing that "as long as" part, so +1.
2

Let's write a function!

def update_attr(obj, **kw):
    for key, value in kw.iteritems():
        setattr(obj, key, value)

Let's use the function!

class X:
    pass

x = X()

update_attr(x, blue=3, red=5)
update_attr(x, **{'yellow': 6})

print vars(x)

The output!

{'blue': 3, 'yellow': 6, 'red': 5}

2 Comments

The example is a little weird; creating class attributes and overriding them with instance attributes isn't exactly a common use case.
Yes, and I was going to suggest adding an __init__ method, but your resolution is obviously a lot shorter and simpler.
2

Perhaps this is unpythonic but VB-style With blocks can help make the program structure clearer. This is useful when setting up hierarchical structures, such as GUI layouts.

The "quick, dirty and perhaps unpythonic" way to do this is:

for x in [my_object_with_a_long_name]:
    x.my_method()
    x.another_method()
    ...

Comments

1

If your objective is just to avoid repetition and you are working with your own objects, you could use a fluent interface like this:

obj.set_attr1('foo')
   .set_attr2('bar')

3 Comments

This is very un-Pythonic, and almost certainly a bad idea. But probably less so than a VB-style with statement.
It's always hard to answer these "I want to do something un-Pythonic" questions. Is suggesting something much less Pythonic, but still not really common or idiomatic, a good idea? Hell if I know. That's why I usually end up writing 3 pages worth of alternatives and caveats for each one. :)
@user3557327 I got IndentationError: unexpected indent after the first method.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.