Skip to content

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
You can’t perform that action at this time.