Instantly share code, notes, and snippets.

Embed
What would you like to do?
Fast, small-footprint Python data object template
# -*- coding: utf-8 -*-
# Copyright (c) 2010, 2015 Matthew Zipay <mattz@ninthtest.net>.
# All rights reserved.
# Licensed under the MIT License <http://opensource.org/licenses/MIT>.
class PseudoStruct:
"""Base class for structure-like data objects.
Define the ``__slots__`` class member in a subclass of
``PseudoStruct`` to create a fast, small-footprint data object.
(Alternatively, use the :meth:`define` class method.)
PseudoStruct objects have get/set speed comparable to attribute
access on a simple class object, but with minimal memory footprint
(marginally larger than a list or tuple containing the field values,
but much smaller than a simple class object having the same fields
as attributes).
PseudoStruct was inspired by the concept of the same name described
in `The class File Format
<http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html>`_.
"""
# subclasses must define the fields they support
__slots__ = []
@classmethod
def define(cls, class_name, field_names):
r"""Dynamically create a PseudoStruct type.
:param str class_name: the class name for the new type
:param field_names: a sequence of names for the mutable
attributes of objects of the new type
.. warning::
Dynamically-created PseudoStruct types cannot be pickled!
>>> Part = PseudoStruct.define("Part", ["id", "name"])
>>> type(Part) is type
True
>>> issubclass(Part, PseudoStruct)
True
>>> Part.__name__
'Part'
>>> Part.__slots__
['id', 'name']
The ``define`` method can also create a subclass of a subclass:
>>> Part = PseudoStruct.define("Part", ["id", "name"])
>>> Item = Part.define("Item", ["description"])
>>> issubclass(Item, Part)
True
>>> Item.__name__
'Item'
>>> Item.__slots__
['description']
"""
return type(class_name, (cls,), {"__slots__": field_names})
def __init__(self, **fields):
r"""Initialize by passing the field name/value pairs as keyword
arguments.
:param dict fields: name/value pairs to initialize this object's
attributes
:raise AttributeError: if any name in *fields* is not defined as
a slot
>>> class Attribute(PseudoStruct):
... __slots__ = ["name", "value"]
...
>>> attr = Attribute(name="color", value="orange")
>>> attr.name
'color'
>>> attr.value
'orange'
"""
for (name, value) in fields.items():
setattr(self, name, value)
def __getattr__(self, name):
r"""Lazily initialize the value for the *name* slot.
:param str name: the name of an attribute of this object
:raise AttributeError: if *name* is not a valid attribute name
for this object
:return: ``None``
This method will be called **at most once**, and only if a value
has not yet been assigned to the *name* attribute. In this case,
``None`` is set and returned as the default value:
>>> class Attribute(PseudoStruct):
... __slots__ = ["name", "value"]
...
>>> attr = Attribute()
>>> attr.name is None
True
"""
setattr(self, name, None)
def __iter__(self):
r"""Return an iterator over all ``(name, value)`` pairs for this
object's slots, in the order in which the slots were defined.
>>> Part = PseudoStruct.define("Part", ["id", "name"])
>>> Item = Part.define("Item", ["desc"])
>>> item = Item(desc="3-sided polygon", id=1, name="triangle")
>>> list(iter(item))
[('id', 1), ('name', 'triangle'), ('desc', '3-sided polygon')]
"""
if (self.__class__ is PseudoStruct):
return
# be sure to include my own __slots__
classes = [self.__class__]
# classes that define __slots__ may only use single inheritance
parent = self.__class__.__bases__[0]
while (parent is not PseudoStruct):
classes.append(parent)
parent = parent.__bases__[0]
yielded_slots = set()
for class_ in reversed(classes):
for name in class_.__slots__:
# don't yield same (name, value) more than once - this can
# happen because it is legal for a subclass to repeat a
# slot name
if (name not in yielded_slots):
yielded_slots.add(name)
yield (name, getattr(self, name))
def __repr__(self):
r"""Return a string representation of this PseudoStruct.
>>> class Part(PseudoStruct):
... __slots__ = ["id", "name"]
...
>>> class Item(Part):
... __slots__ = ["desc"]
...
>>> item = Item(desc="3-sided polygon", id=1, name="triangle")
>>> repr(item)
"Item {id: 1, name: 'triangle', desc: '3-sided polygon'}"
"""
return "%s {%s}" % (self.__class__.__name__,
", ".join("%s: %r" % pair for pair in self))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment