# -*- coding:utf-8; tab-width:4; mode:python -*-
"""
.. module:: pattern
:synopsis: Common pythonic design pattern
.. moduleauthor:: David Villa Alises <David.Villa@gmail.com>
"""
import collections
import string
import inspect
from functools import partial
# Borg pattern
from .type_ import checked_type
try:
unicode
except NameError:
unicode = str
# Exceptions
# class ObserverException(Exception):
# def __str__(self):
# return "%s: %s" % (self.__class__.__name__, Exception.__str__(self))
[docs]def make_exception(name, message=''):
return type(name, (Exception,), {})
[docs]class DummyLogger(object):
def debug(self, *args):
pass
def warning(self, *args):
pass
# other: http://code.activestate.com/recipes/578231-probably-the-fastest-memoization-decorator-in-the-/
[docs]def memoized(method_or_arg_spec=None, ignore=None):
"""Memoized decorator for functions and methods, including classmethods, staticmethods and
properties
>>> import random
>>>
>>> @memoized
... def get_identifier(obj):
... return random.randrange(1000)
...
>>> n1 = get_identifier("hi")
>>> n2 = get_identifier("hi")
>>> assert n1 == n2
It allows to ignore some arguments in memoization, so different values for those
arguments DO NOT implies new true invocations:
>>> @memoized(ignore=['b'])
... def gen_number(a, b):
... return random.randrange(1000)
...
>>> n1 = gen_number(1, 2)
>>> n2 = gen_number(1, 3)
>>> print n1, n2
>>> assert n1 == n2
* http://pko.ch/2008/08/22/memoization-in-python-easier-than-what-it-should-be/
* http://micheles.googlecode.com/hg/decorator/documentation.html
"""
if method_or_arg_spec is not None:
return _memoized(func=method_or_arg_spec)
return partial(_memoized, ignore=ignore)
class _memoized(object):
def __init__(self, func, ignore=None):
self.instance = None
self.func = func
self.cache = {}
self.ignore = checked_type(list, ignore or [])
self.argspec = inspect.getargspec(func).args
def hash_args(self, *args, **kargs):
args = self.filterout_ignored(args)
if kargs:
return args, frozenset(kargs.iteritems())
return args
def pack_args(self, args):
if self.instance is None:
return args
return (self.instance,) + args
def __get__(self, instance, owner):
self.instance = instance
return self
def filterout_ignored(self, args):
args = list(args)
for n in self.ignore:
try:
pos = self.argspec.index(n)
del args[pos]
except ValueError:
raise ValueError("%s() has not argument '%s'" % (self.func.__name__, n))
return tuple(args)
def __call__(self, *args, **kargs):
cache_args = self.pack_args(args)
key = self.hash_args(*cache_args, **kargs)
if key not in self.cache:
self.cache[key] = self.func(*cache_args, **kargs)
return self.cache[key]
def repr(self):
return "memoized({0})".format(self.func)
# EXPERIMENTAL, DO NOT USE IN PRODUCTION CODE
# from cached_property in http://wiki.python.org/moin/PythonDecoratorLibrary
[docs]class memoizedproperty(object):
"""Memoized properties
>>> import random
>>> class A:
... @memoizedproperty
... def a_property(self):
... return random.randrange(1000)
...
>>> a1 = A()
>>> v1 = a1.a_property
>>> v2 = a1.a_property
>>> assert v1 == v2
"""
def __init__(self, method):
self.method = method
self.instance = None
self.cache = {}
def __get__(self, instance, owner):
self.instance = instance
if instance not in self.cache:
self.cache[instance] = self.method(instance)
return self.cache[instance]
def reset(self):
del self.cache[self.instance]
[docs]class Flyweight(type):
'''Flyweight dessign pattern (for identical objects) as metaclass
>>> class Sample(object):
... __metaclass__ = Flyweight
'''
def __init__(cls, name, bases, dct):
cls.__instances = {}
type.__init__(cls, name, bases, dct)
def __call__(cls, key, *args, **kw):
instance = cls.__instances.get(key)
if instance is None:
instance = type.__call__(cls, key, *args, **kw)
cls.__instances[key] = instance
return instance
[docs]class Observable(object):
InvalidObserver = make_exception('InvalidObserver', 'observer must be callable')
NotSuchObserver = make_exception('NotSuchObserver')
ObserverException = make_exception('ObserverExcepton')
def __init__(self):
self.observers = []
def attach(self, observer):
if not callable(observer):
raise self.InvalidObserver()
if observer in self.observers:
return
self.observers.append(observer)
def detach(self, observer):
try:
self.observers.remove(observer)
except ValueError:
raise self.NotSuchObserver(observer)
def notify(self, value):
for observer in self.observers:
try:
observer(value)
except Exception as ex:
raise self.ObserverException(ex)
# http://books.google.es/books?id=9_AXCmGDiz8C&lpg=PA9&pg=PA34&redir_esc=y#v=onepage&q&f=false
[docs]class Bunch(dict):
"""It provides dict keys as attributes and viceversa
>>> data = dict(ccc=2)
>>> bunch = Bunch(data)
>>> bunch.ccc
2
>>> bunch.ddd = 3
>>> bunch['ddd']
3
>>> bunch['eee'] = 4
>>> bunch.eee
4
"""
def __init__(self, *args, **kargs):
super(Bunch, self).__init__(*args, **kargs)
self.__dict__ = self
def copy(self):
return self.__class__(**self)
def __repr__(self):
items = []
for key in self.keys():
items.append("'%s': %s" % (key, repr(getattr(self, key))))
return "Bunch({0})".format(str.join(', ', items))
[docs]class NestedBunch(Bunch):
"""Bunch with recursive fallback in other bunch (its parent)
>>> a = NestedBunch()
>>> a.foo = 1
>>> a2 = a.new_layer()
>>> a2.bar = 2
>>> a2.foo
1
>>> a2.keys()
['foo', 'bar']
>>> b = NestedBunch()
>>> b.foo = 1
>>> b2 = b.new_layer()
>>> b2.foo
1
>>> b2['foo'] = 5000
>>> b2.foo
5000
>>> b['foo']
1
>>> c = NestedBunch()
>>> c.foo = 1
>>> c2 = c.new_layer().new_layer()
>>> c2.foo
1
"""
def __init__(self, *args, **kargs):
super(NestedBunch, self).__init__(*args, **kargs)
self.__parent = None
def new_layer(self):
retval = NestedBunch()
retval.__parent = self
return retval
def __getitem__(self, key):
try:
return getattr(self, key)
except AttributeError:
if self.__parent is None:
raise KeyError
return getattr(self.__parent, key)
def __getattr__(self, key):
if self.__parent is None:
raise AttributeError
return self.__parent[key]
def __dict_method(self, method):
retval = []
if self.__parent:
retval.extend(getattr(self.__parent, method)())
retval.extend(getattr(super(NestedBunch, self), method)())
return retval
def keys(self):
retval = self.__dict_method('keys')
retval.remove('_NestedBunch__parent')
return retval
def items(self):
retval = self.__dict_method('items')
retval.remove(('_NestedBunch__parent', self.__parent))
return retval
def values(self):
retval = self.__dict_method('values')
retval.remove(self.__parent)
return retval
[docs]class TemplateBunch(collections.MutableMapping):
"""A Bunch automatically templated with its own content
>>> t = TemplateBunch()
>>> t.name = "Bob"
>>> t.greeting = "Hi $name!"
>>> t.greeting
"Hi Bob!"
>>> t.person = "$name's sister"
>>> t.greeting = "Hi $person!"
>>> t.person
"Bob's sister"
>>> t.greeting
"Hi Bob's sister"
"""
def __init__(self):
self.__dict__['dct'] = Bunch()
def __iter__(self):
return iter(self.dct)
def __len__(self):
return len(self.dct)
def __repr__(self):
return repr(self.dct)
def __delitem__(self, key):
del self.dct[key]
def has_key(self, key):
return key in self.dct
def __setattr__(self, attr, value):
self.dct[attr] = value
def __getattr__(self, attr):
try:
value = self.dct[attr]
except KeyError:
raise AttributeError(attr)
if isinstance(value, (str, unicode)):
return string.Template(value).safe_substitute(self)
return value
def __setitem__(self, key, value):
self.__setattr__(key, value)
def __getitem__(self, key):
try:
return self.__getattr__(key)
except AttributeError:
raise KeyError(key)
def clear(self):
self.dct.clear()