Created
March 28, 2015 07:51
-
-
Save mzipay/ee0fe0ccbaec56ad162f to your computer and use it in GitHub Desktop.
Fast, small-footprint Python data object template
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
# Copyright (c) 2010, 2015 Matthew Zipay <[email protected]>. | |
# 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