Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>We could use some metaclass shenanegans to do this...</p> <p>In python 2, attributes are passed to the metaclass in a dict, without preserving order, we'll also want a base class to work with so we can distinguish class attributes that should be mapped into the row. In python3, we could dispense with just about all of this base descriptor class.</p> <pre><code>import itertools import functools @functools.total_ordering class DryDescriptor(object): _order_gen = itertools.count() def __init__(self, alias=None): self.alias = alias self.order = next(self._order_gen) def __lt__(self, other): return self.order &lt; other.order </code></pre> <p>We will want a python descriptor for every attribute we wish to map into the row. slots are a nice way to get data descriptors without much work. One caveat, though, we'll have to manually remove the helper instance to make the real slot descriptor visible.</p> <pre><code>class slot(DryDescriptor): def annotate(self, attr, attrs): del attrs[attr] self.attr = attr slots = attrs.setdefault('__slots__', []).append(attr) def annotate_class(self, cls): if self.alias is not None: setattr(cls, self.alias, getattr(self.attr)) </code></pre> <p>For computed fields, we can memoize results. Memoizing off of the annotated instance is tricky without a memory leak, we need weakref. alternatively, we could have arranged for another slot just to store the cached value. This also isn't quite thread safe, but pretty close.</p> <pre><code>import weakref class memo(DryDescriptor): _memo = None def __call__(self, method): self.getter = method return self def annotate(self, attr, attrs): if self.alias is not None: attrs[self.alias] = self def annotate_class(self, cls): pass def __get__(self, instance, owner): if instance is None: return self if self._memo is None: self._memo = weakref.WeakKeyDictionary() try: return self._memo[instance] except KeyError: return self._memo.setdefault(instance, self.getter(instance)) </code></pre> <p>On the metaclass, all of the descriptors we created above are found, sorted by creation order, and instructed to annotate the new, created class. This does not correctly treat derived classes and could use some other conveniences like an <code>__init__</code> for all the slots.</p> <pre><code>class DryMeta(type): def __new__(mcls, name, bases, attrs): descriptors = sorted((value, key) for key, value in attrs.iteritems() if isinstance(value, DryDescriptor)) for descriptor, attr in descriptors: descriptor.annotate(attr, attrs) cls = type.__new__(mcls, name, bases, attrs) for descriptor, attr in descriptors: descriptor.annotate_class(cls) cls._header_descriptors = [getattr(cls, attr) for descriptor, attr in descriptors] return cls </code></pre> <p>Finally, we want a base class to inherit from so that we can have a <code>to_row</code> method. this just invokes all of the <code>__get__</code>s for all of the respective descriptors, in order. </p> <pre><code>class DryBase(object): __metaclass__ = DryMeta def to_row(self): cls = type(self) return [desc.__get__(self, cls) for desc in cls._header_descriptors] </code></pre> <p>Assuming all of that is tucked away, out of sight, the definition of a class that uses this feature is mostly free of repitition. The only short coming is that to be practical, every field needs a python friendly name, thus we had the <code>alias</code> key to associate <code>'seeds?'</code> to <code>has_seeds</code></p> <pre><code>class ADryRow(DryBase): __slots__ = ['seeds'] ripeness = slot() colour = slot() juiciness = slot() @memo(alias='seeds?') def has_seeds(self): print "Expensive!!!" return self.seeds &gt; 0 </code></pre> <pre><code>&gt;&gt;&gt; my_row = ADryRow() &gt;&gt;&gt; my_row.ripeness = "tart" &gt;&gt;&gt; my_row.colour = "#8C2" &gt;&gt;&gt; my_row.juiciness = 0.3479 &gt;&gt;&gt; my_row.seeds = 19 &gt;&gt;&gt; &gt;&gt;&gt; print my_row.to_row() Expensive!!! ['tart', '#8C2', 0.3479, True] &gt;&gt;&gt; print my_row.to_row() ['tart', '#8C2', 0.3479, True] </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload