10

I find myself writing this class often in my python code when I need a quick single use class.

class Struct(object):
   def __init__( self, **kwargs ):
      for k in kwargs:
         setattr(self,k,kwargs[k])

The basic idea is so I can do quick things like this:

foo = Struct( bar='one', baz=1 )
print foo.bar
foo.baz += 1
foo.novo = 42 # I don't do this as often.

Of course this doesn't scale well and adding methods is just insane, but even so I have enough data-only throw-away classes that I keep using it.

This is what I thought namedtuple was going to be. But the namedtuple's syntax is large and unwieldy.

Is there something in the standard library I haven't found yet that does this as well or better?

Is this bad bad style? or does it have some hidden flaw?

update

Two concrete example to show why I don't just use a dict. Both of these examples could be done with a dict but it obviously non-idiomatic.

#I know an order preserving dict would be better but they don't exist in 2.6.
closure = Struct(count=0)
def mk_Foo( name, path ):
   closure.count += 1
   return (name, Foo( name, path, closure.count ))

d = dict([
   mk_Foo( 'a', 'abc' ),
   mk_Foo( 'b', 'def' ),
   # 20 or so more
   ] )


@contextmanager
def deleter( path ):
   control = Struct(delete=True,path=path)
   try:      
      yield control
   finally:
      if control.delete:
         shutil.rmtree(path)

with deleter( tempfile.mkdtemp() ) as tmp:
   # do stuff with tmp.path
  
   # most contexts don't modify the delete member
   # but occasionally it's needed
   if keep_tmp_dir:
      tmp.delete = False
  
Community
  • 1
  • 1
deft_code
  • 57,255
  • 29
  • 141
  • 224

5 Answers5

9

From Python 3.3 and afterwards, you can use types.SimpleNamespace:

>>> import types
>>> foo = types.SimpleNamespace(bar='one', baz=1)
>>> print(foo.bar)
one
>>> foo.baz += 1
>>> foo.novo = 42

The builtin type is roughly equivalent to the following code:

class SimpleNamespace:

    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

    def __repr__(self):
        keys = sorted(self.__dict__)
        items = ("{}={!r}".format(k, self.__dict__[k]) for k in keys)
        return "{}({})".format(type(self).__name__, ", ".join(items))

    def __eq__(self, other):
        return self.__dict__ == other.__dict__

update

Starting with Python 3.7, you can use the dataclass module:

from dataclasses import dataclass, field

@dataclass
class Struct:
    bar: str = field(default='one')
    baz: int = field(default=1)

You can use this as follows:

foo = Struct( bar='one', baz=1 )
print(foo.bar)
foo.baz += 1
foo.novo = 42

By default, it incorporates equality testing and a nice looking repr:

>>> foo == Struct(bar='one', baz=2)
True
>>> foo
Struct(bar='one', baz=2)
Raymond Hettinger
  • 216,523
  • 63
  • 388
  • 485
9

There is a python recipe for this (It just updates the instance's dict instead of calling setattr) Recipe 52308

class Bunch(object):
    def __init__(self, **kwds):
        self.__dict__.update(kwds)
TigrisC
  • 1,320
  • 9
  • 11
3
class t(dict):

   def __init__(self, **kwargs):
      for key, value in kwargs.items():
         dict.__setitem__(self, key, value)
   def __getattr__(self, key):
      return dict.__getitem__(self, key)
   def __setattr__(self, key, value):
      raise StandardError("Cannot set attributes of tuple")      
   def __setitem__(self, key, value):
      raise StandardError("Cannot set attributes of tuple")      
   def __delitem__(self, key):
      raise StandardError("Cannot delete attributes of tuple")

point = t(x=10, y=500, z=-50)
print point.x        # 10
print point.y        # 500
print point['z']     # -50
print point          # {'z': -50, 'y': 500, 'x': 10}
point.x = 100        # StandardError: cannot set attributes of tuple
point.y += 5         # StandardError: cannot set attributes of tuple
point.z = -1         # StandardError: cannot set attributes of tuple

def hypo(x, y, z):
   return (x**2 + y**2 + z**2)**0.5

print hypo(point)    # TypeError: unsupported operand type(s)
print hypo(**point)  # 502.593274925   

for k in point.items():
   print k           # ('y', 500)
                     # ('x', 10)
                     # ('z', -50)

for k in point.keys():
   print k           # x
                     # y
                     # z

for k in point.values():
   print k           # 500
                     # 10
                     # -50

print len(point)     # 3

print dict(point)    # {'y': 500, 'x': 10, 'z': -50}

This is my solution to this problem. Beautiful syntax, immutable (at least without resorting to some nasty object.setattr() gymnastics), lightweight and pretty-printable. Although there is nothing you can do with this that you cannot do with a dict,

point = t(x=10, y=20, z=30)
d = point.x ** 2 + point.y ** 2 + point.z ** 2

has a really nice symmetry with

point = (10, 20, 30)
d = point[0] ** 2 + point[1] ** 2 + point[2] ** 2

and overall is just so much cleaner than

point = {'x': 10, 'y': 20, 'z': 30}
d = point['x'] ** 2 + point['y'] ** 2 + point['z'] ** 2
Li Haoyi
  • 49
  • 3
1

What you have is a perfectly reasonable prototype, but you're right that it doesn't scale.

If you like using them, but want to have a path to better code later, here's what I'd suggest:

  • every time you do that, subclass Structure:

    class Control(Structure): pass

  • later, when you want a "real" class, replace the superclass with something like strongbox.Strongbox (example usage) that provides that same constructor and attribute interface, but constrains which slots you can fill in.

A discipline like this only costs you one extra line up front, and won't break your code if you want more power later.

tangentstorm
  • 7,183
  • 2
  • 29
  • 38
1

You may want to look at Records by George Sakkis. It has worked well for me as a "mutable named tuple."

TimothyAWiseman
  • 14,385
  • 12
  • 40
  • 47