1

I often have to create a result object instance to return complex values from functions and wonder what's a nice Pythonic approach.

I am aware of the closely related attribute-assignment-to-built-in-object, but it is asking why he needs to hack a subclass workaround to use 'object'. I understand why already. I am asking instead if there is existing support in the standard library to avoid using 'object' and the subclass hack, either through an existing class or function.

What's the lightest, most Pythonic way to instantiate an instance that supports attribute assignment?

I am OK to be pointed right back to subclassing 'object' answer. It's really no big deal - just want to know if I had missed a cleaner approach supported by the standard library or builtins.

Sample of what I am trying to do:

try:
    returnval = object()
    returnval.foo = 1
    returnval.bar = 2

    print "\n\nSuccess with %s" % (returnval), vars(returnval), "has __slots__:", hasattr(returnval, "__slots__"), "has __dict__:", hasattr(returnval, "__dict__")

except Exception, e:
    print "\n\nFailure with %s:%s" % (returnval, e), "has __slots__:", hasattr(returnval, "__slots__"), "has __dict__:", hasattr(returnval, "__dict__")

This fails, as expected, with

Failure with <object object at 0x102c520a0>:'object' object has no attribute 'foo' has __slots__: False has __dict__: False

I am not surprised. 'object' is a bare stub, and does not allow attribute assignment because it has no __dict__.

Instead I have to declare a placeholder class. Is there a cleaner way?

try:
    class Dummy(object): pass

    returnval = Dummy()
    returnval.foo = 1
    returnval.bar = 2
    print "\n\nSuccess with %s" % (returnval), vars(returnval), "has __slots__:", hasattr(returnval, "__slots__"), "has __dict__:", hasattr(returnval, "__dict__")

except Exception, e:
    print "\n\nFailure with %s:%s" % (returnval, e), "has __slots__:", hasattr(returnval, "__slots__"), "has __dict__:", hasattr(returnval, "__dict__")

This gives:

Success with <__main__.Dummy object at 0x102d5f810> {'foo': 1, 'bar': 2} has __slots__: False has __dict__: True

Using the Dummy/MyClass approach avoids these problems, but it gives off a mild code smell to litter my modules with Dummy classes.

Things that would not work/are not satisfactory:

Dictionaries. I would avoid this if I used a dict instead, but I would lose the simple returnval.foo access.

AttrDict implementations perhaps? But those come in 3rd party packages, not in standard lib.

Mocks. Not what I want to be using here, because this is not testing code and I want an exception thrown if returnval.foo does not exist.

Module/class attribute assignment. Yes, I could assign attribute to an existing object in the namespace, like a class or a module declaration. But then that would essentially be assigning attributes to a singleton and successive function calls would clobber each other.

Community
  • 1
  • 1
JL Peyret
  • 10,917
  • 2
  • 54
  • 73
  • 1
    You don't need to litter your code with dummy classes. You can create *one* such class and use instances of it everywhere. – BrenBarn Mar 31 '15 at 20:08
  • 1
    How about `namedtuple`? – matino Mar 31 '15 at 20:12
  • Agree. But I need to import Dummy everywhere I use it. Should have added to my list of rejected alternatives, creating it "on location" is at least clearer about what the class is up to. – JL Peyret Mar 31 '15 at 20:13
  • namedtuple. Hmmm, I might want to look at that, but I think the problem is that you have to assign a unique typename when starting out, as well as freezing the attribute names. I was trying to use it to assign database cursor results. And, its use is a bit heavier-weight than a MyClass. anyone have any experience with using it in this context? – JL Peyret Mar 31 '15 at 20:17
  • From docs: "Named tuple instances do not have per-instance dictionaries, so they are lightweight and require no more memory than regular tuples." – matino Mar 31 '15 at 20:22

3 Answers3

4

Either use namedtuple or stick with a dictionary - if you really want an attribute access - create your own class for it:

>>> Dummy = namedtuple('Dummy', ['foo', 'bar'])
>>> d = Dummy(1, 2)
>>> d
Dummy(foo=1, bar=2)
>>> d.baz
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Dummy' object has no attribute 'baz'
Community
  • 1
  • 1
matino
  • 17,199
  • 8
  • 49
  • 58
  • Doesn't fully support my use cases. You have to decide from the start on all your attributes - MyClass/Dummy is more flexible there. And, even sticking to the declared d.bar, you can't change your mind and assign d.bar = 3 afterwards, it is a read-only - to be expected as tuples are immutable. Which is actually a very interesting aspect of your solution - write-once, read-only variables. – JL Peyret Apr 02 '15 at 00:40
1

A lightweight way is to use a function as the container

>>> foo = lambda: 0
>>> foo.bar = 'bar'
>>> foo.baz = 'baz'

If you are making a bunch of immutable objects with the same attributes collections.namedtuple is probably more appropriate

>>> foo = namedtuple("foo", "bar, baz")
>>> Foo = foo('bar', 'baz')
>>> foo.bar
'bar'
>>> foo.baz
'baz'
John La Rooy
  • 295,403
  • 53
  • 369
  • 502
  • 1
    Eesh. I could only imagine the look on someone's face while debugging a return value of `type(obj) == ` ;) – Demian Brecht Mar 31 '15 at 20:19
  • Am gonna go with this one. It is too exotic to really replace a MyClass/Dummy declaration, but it does do exactly what I think is important - lightweight to use, flexible - you can adjust the attributes as you go- and minimalist. The reason I'll stick to Dummy/MyClass is just that it seems to be more generally recognized hack, not that it's better. And, thanks for the lambda use case - I rarely deal with them. – JL Peyret Apr 02 '15 at 00:28
0

this is one of the uses of types.SimpleNamespace:

SimpleNamespace may be useful as a replacement for class NS: pass. However, for a structured record type use namedtuple() instead.

from types import SimpleNamespace

res = SimpleNamespace(foo=1,bar=2)
print(f"{res=} {res.foo=} {(res.foo == 1)=}")
res.zoom = 3
print(f"{res=}")

output:

res=namespace(foo=1, bar=2) res.foo=1 (res.foo == 1)=True
res=namespace(foo=1, bar=2, zoom=3)
JL Peyret
  • 10,917
  • 2
  • 54
  • 73