Skip to content

Instantly share code, notes, and snippets.

@thom-nic
Created January 3, 2014 15:55
Show Gist options
  • Save thom-nic/8240282 to your computer and use it in GitHub Desktop.
Save thom-nic/8240282 to your computer and use it in GitHub Desktop.
inspired by collections.namedtuple, I wanted an easy struct-type for data objects without the boilerplate __init__() method for setting all instance attributes from the argument list. I also added some features to namedtuple, most notably mutable properties & default values. It can be extended to act as a base for any class definition.
import sys
def Record(name, required, **defaults):
required = tuple(required.split())
class RecordBase(object):
__slots__ = required + tuple(defaults.keys())
def __init__(self, *args, **kwargs):
all_args = set()
if defaults:
for k,v in defaults.iteritems():
setattr(self, k, v)
if args:
for i in xrange(len(args)):
attr = self.__slots__[i]
all_args.add(attr)
setattr(self, attr, args[i])
if kwargs:
for k,v in kwargs.iteritems():
all_args.add(k)
setattr(self, k, v)
for attr in required:
if attr not in all_args:
raise AttributeError("Missing required argument: '%s'" % attr)
def items(self):
"dict style items"
return [ (attr, val) for attr, val in self.iteritems() ]
def iteritems(self):
"dict style items"
return ( (attr, getattr(self, attr)) for attr in self.__slots__ )
def _asdict(self):
'For namedtuple compatibility'
return {k:v for k,v in self.iteritems()}
def _replace(self,**kwargs):
params = self._asdict()
for k,v in kwargs.iteritems():
params[k] = v
return RecordBase(**params)
def __len__(self): return len(self.__slots__)
def __iter__(self): return iter(self.__slots__)
def __str__(self): return repr(self)
def __repr__(self):
return "%s.%s(%s)" % (self.__class__.__module__, self.__class__.__name__,
', '.join( ('%s=%r' % (k,getattr(self,k)) for k in self.__slots__) ) )
def __getitem__(self, index):
"tuple/list style getitem"
return getattr(self, self.__slots__[index])
def __getstate__(self):
'For pickling'
return tuple(getattr(self,v) for v in self.__slots__)
def __setstate__(self,state):
'for unpickling'
for i,v in enumerate(state): setattr(self,self.__slots__[i],v)
def __eq__(self,other):
'''Equality is based on type and property values.'''
if other is None or self.__class__ != other.__class__: return False
for k in self.__slots__:
if getattr(self,k) != getattr(other,k): return False
return True
def __ne__(self, other): return not self.__eq__(other)
def __hash__(self):
'''
Hash is based on type and values
'''
_hash = hash(self.__class__)
for key in sorted(self.__slots__):
_hash ^= hash(getattr(self,key))
return _hash
cls = RecordBase
# set module and class names for pickling:
cls.__name__ = name
if hasattr(sys, '_getframe'):
cls.__module__ = sys._getframe(1).f_globals.get('__name__', '__main__')
return cls
if __name__ == '__main__':
Person = Record('Person', 'first last',age=10)
p = Person(first='Bob',last='fish')
print p
p2 = Person(last='fish',first='hi')
print p2
p2.last = "tiger"
print "people are mutable: %s" % p2
p2 = Person(last='fish',first='Bob')
print "Instances are equal of the same type and values: %s" % (p2 == p)
p3 = Person('Bob', 'fish')
print "you can use positional or kwargs: %s" % (p3 == p2)
print "Hash values are equal too: %s" % (hash(p2) == hash(p))
Alien = Record('Alien', 'first last',age=1200)
impostor = Alien(first='Bob',last='fish',age=10)
print "Alien impostor is not equal even with the same values: %s" % (impostor != p)
from cPickle import dumps, loads
print "unpickled person has the same equality: %s" % (p == loads(dumps(p)))
try:
badPerson = Person(first='bill',warts=10)
raise Exception("badPerson should not be created! %s" % badPerson)
except AttributeError as e: print "Got expected exception: %s" % e
try:
missingPerson = Person(first='bill')
raise Exception("missingPerson should not be created! %s" % missingPerson)
except AttributeError as e: print "Got expected exception: %s" % e
Mutant = Record('Mutant','name',arms=2,legs=2)
bob = Mutant(name='bob')
bob.arms = 3
bob.legs = 8
print "Bob the mutant now has 3 arms! %s" % (bob.arms == 3)
print "Transporter works: %s" % (bob == loads(dumps(bob)))
try:
bad_mutant = Mutant(name='bob', tentacles=10)
raise Exception("Mutants can't have tentacles!")
except AttributeError as e: print "Got expected exception: %s" % e
try:
bob.tentacles = 10
raise Exception("Mutants can't grow tentacles either!")
except AttributeError as e: print "Got expected exception: %s" % e
try:
bad_bob = Mutant(arms=5)
raise Exception("Mutants should still have a name!")
except AttributeError as e: print "Got expected exception: %s" % e
Alien = Record("Alien", 'eyes teeth', tentacles=10)
zombot = Alien(3,teeth=15)
print "alien has 3 attributes: %s" % (len(zombot) == 3)
print "alien has eyes: %s" % (zombot.eyes==3)
print "alien has teeth: %s" % (zombot.teeth==15)
print "alien has tentacles: %s" % (zombot.tentacles==10)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment