开发者

Namedtuple like class

开发者 https://www.devze.com 2023-02-13 12:13 出处:网络
I find myself writing this class often in my python code when I need a quick single use class. class Struct(object):

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
  


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)


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)


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


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.


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

0

精彩评论

暂无评论...
验证码 换一张
取 消