Skip to content

Instantly share code, notes, and snippets.

@dplepage
Last active December 21, 2015 09:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dplepage/6287322 to your computer and use it in GitHub Desktop.
Save dplepage/6287322 to your computer and use it in GitHub Desktop.
Helper for creating more robust immutable objects.

The goal is to make a namedtuple subclass with helper functions (which obviously are read-only, because it's a namedtuple). Simply creating a namedtuple and subclassing it doesn't work, as the subclass gets __dict__ and other mutable things that ruin the __slots__ awesomeness of namedtuple. So instead, this uses ActuallyCalls (from actually.py, https://gist.github.com/dplepage/5095902) to create a new namedtuple class and add your functions to it.

Obvious caveats:
  • If you subclass one of these classes, you'll have the same problem as with subclassing namedtuple directly
  • Don't specify methods like __new__ that will conflict with namedtuple's members
class Point3(NamedTuple('x', 'y', 'z')):
def radius(self):
return (self.x**2 + self.y**2 + self.z**2)**.5
def manhattan_radius(self):
return self.x+self.y+self.z
p = Point3(1, 2, 3)
assert str(p) == 'Point3(x=1, y=2, z=3)'
assert p.manhattan_radius() == 6
# Point3's attributes can't be set:
try:
p.x = 12
except AttributeError:
pass
else:
raise Exception("That shouldn't have worked.")
# And you can't add new ones:
try:
p.w = 12
except AttributeError:
pass
else:
raise Exception("That shouldn't have worked.")
from collections import namedtuple
from actually import ActuallyCalls
def make_named_tuple(field_names, name='Struct', **kwargs):
t = namedtuple(name, field_names)
for k, val in kwargs.items():
setattr(t, k, val)
return t
def NamedTuple(*field_names):
return ActuallyCalls(
lambda __clsname, **kwargs:
make_named_tuple(field_names, __clsname, **kwargs),
namearg='__clsname')
# In case you're curious, here's what's wrong with subclassing namedtuple directly:
from collections import namedtuple
class Point3(namedtuple('Point3', 'x y z')):
def manhattan(self): return self.x+self.y+self.z
p = Point3(1,2,3)
# This works because subclassing gave you a __dict__, instead of keeping __slots__ around.
p.w = 12
assert p.w == 12
# To be fair, you can get the same behavior by passing in empty slots, without the magic of the above:
class Point3(namedtuple('Point3', 'x y z')):
__slots__ = ()
def manhattan(self): return self.x+self.y+self.z
p = Point3(1,2,3)
try:
p.w = 12
except AttributeError:
pass
else:
raise Exception("That shouldn't have worked.")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment