A valued lesson that I have learned the hard way is that mutable data can get nasty, and that it's really nice to have immutable data structures. I've been writing a lot of python recently, and after a while I realized that I was writing a lof of this:
class SomeDataStructure: def __init__(self, arg1, arg2): self.prop1 = arg1 self.prop2 = arg2
Not only was this repetitive, but I found that little mutability bugs were cropping up on me. It's just too easy to trip over them when the default is mutability, as it is in python. In fact, immutable data structures aren't even a built-in option in python.
Finally the pain of using mutable data structures grew too large. I looked for a way and couldn't find anything, so I created my own. It supports getters, setters, inheritance, default values, altering several values at once, and I think the syntax is nice. I've been using it for everything the last few months and it's been great. It's helped with code repetition, concurrency, and serialization.
I hope you can learn from my valued lesson and not repeat my mistakes. Here's how you use it.
class Person(Record("name", "age")): pass class OldPerson(Person): @classmethod def prepare(cls, name, age = None): return (name, age) peter = Person("Peter", 26) wes = Person("Wes", 28) grandpa = OldPerson("Bubba") wes2 = wes.setAge(29) wes3 = wes.alter(name = "Grandpa", age = 57) print peter, grandpa, wes.name, wes2.age
Here is the code. This is actually a simplified version of the original that I rewrote on my own time. I use a more comlete/complex version in the code I'm writing for my employer.
def Record(*props): class cls(RecordBase): pass cls.setProps(props) return cls class RecordBase(tuple): PROPS = () def __new__(cls, *values): if cls.prepare != RecordBase.prepare: values = cls.prepare(*values) return cls.fromValues(values) @classmethod def fromValues(cls, values): return tuple.__new__(cls, values) def __repr__(self): return self.__class__.__name__ + tuple.__repr__(self) ## overridable @classmethod def prepare(cls, *args): return args ## setting up getters and setters @classmethod def setProps(cls, props): for index, prop in enumerate(props): cls.setProp(index, prop) cls.PROPS = props @classmethod def setProp(cls, index, prop): getter_name = prop setter_name = "set" + prop.upper() + prop[1:] setattr(cls, getter_name, cls.makeGetter(index, prop)) setattr(cls, setter_name, cls.makeSetter(index, prop)) @classmethod def makeGetter(cls, index, prop): return property(fget = lambda self : self[index]) @classmethod def makeSetter(cls, index, prop): def setter(self, value): values = (value if current_index == index else current_value for current_index, current_value in enumerate(self)) return self.fromValues(values) return setter
For comparison, I've seen similar ideas at http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/500261 and http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/303439 but I don't like their implementation or design as much, especially since they lack proper setters.
I've also read that future versions of Python will have NamedTuples, which is something I wish it had already.