'''Bunch utility class.
Developed at ESSS (http://esss.com.br) and provided under the MIT license.
@author: Bruno Oliveira
@author: Fabio Zadrozny
Based on implementation by Alex Martelli (http://mail.python.org/pipermail
/python-list/2002-July/112007.html).
To use:
class Point(Bunch):
x = 0
y = 0
p0 = Point()
assert p0.x == 0
assert p0.y == 0
p1 = Point(x=10, y=20)
assert p1.x == 10
assert p1.y == 20
'''
import copy
from types import NoneType
#===============================================================================
# MetaBunch
#===============================================================================
class MetaBunch(type):
"""
metaclass for new and improved "Bunch": implicitly defines
__slots__, __init__ and __repr__ from variables bound in class scope.
An instance of metaMetaBunch (a class whose metaclass is metaMetaBunch)
defines only class-scope variables (and possibly special methods, but
NOT __init__ and __repr__!). metaMetaBunch removes those variables from
class scope, snuggles them instead as items in a class-scope dict named
__defaults__, and puts in the class a __slots__ listing those variables'
names, an __init__ that takes as optional keyword arguments each of
them (using the values in __defaults__ as defaults for missing ones), and
a __repr__ that shows the repr of each attribute that differs from its
default value (the output of __repr__ can be passed to __eval__ to make
an equal instance, as per the usual convention in the matter).
"""
def __new__(cls, classname, bases, classdict):
""" Everything needs to be done in __new__, since type.__new__ is
where __slots__ are taken into account.
"""
import inspect
# define as local functions the __init__ and __repr__ that we'll
# use in the new class
def __init__(self, **kw):
""" Simplistic __init__: first set all attributes to default
values, then override those explicitly passed in kw.
"""
for k, (value, copy_op) in self.__defaults__.iteritems():
if k not in kw: #No need to set value to be overridden later on.
if copy_op is None:
#No copy op (immutable value)
setattr(self, k, value)
else:
setattr(self, k, copy_op(value))
for k, value in kw.iteritems():
setattr(self, k, value)
def __repr__(self):
"""
repr operator.
"""
rep = ['%s=%r' % (k, getattr(self, k))
for k in sorted(self.__defaults__)]
return '%s(%s)' % (classname, ', '.join(rep))
def __eq__(self, other):
'''Basic __eq__.
'''
if not isinstance(other, type(self)):
return False
for attr in self.__defaults__:
if getattr(self, attr) != getattr(other, attr):
return False
return True
def __ne__(self, other):
return not self == other
# build the newdict that we'll use as class-dict for the new class
newdict = dict(
__slots__=classdict.pop('__slots__', []),
__defaults__={},
__init__=__init__,
__repr__=__repr__,
__eq__=__eq__,
__ne__=__ne__,
)
# update the dafaults dict with the contents of the bases's defaults, so
# they're properly initialized during __init__
for base in bases:
newdict['__defaults__'].update(getattr(base, '__defaults__', {}))
for k, value in classdict.iteritems():
if k.startswith('__') or inspect.isfunction(value) or \
type(value) is property:
# methods: copy to newdict
newdict[k] = value
else:
# class variables, store name in __slots__ and name and
# value as an item in __defaults__
#Default for each value is deepcopy, but we may optimize that
#(if the copy op is None, the value is considered immutable).
copy_op = copy.deepcopy
if value.__class__ in (
bool, int, float, long, str, NoneType
):
copy_op = None
else:
if value.__class__ in (tuple, frozenset):
if not value:
copy_op = None
if value.__class__ in (list, set, dict):
if not value:
copy_op = copy.copy
newdict['__slots__'].append(k)
newdict['__defaults__'][k] = (value, copy_op)
# finally delegate the rest of the work to type.__new__
return type.__new__(cls, classname, bases, newdict)
#===============================================================================
# Bunch
#===============================================================================
class Bunch(object):
""" For convenience: inheriting from Bunch can be used to get
the new metaclass (same as defining __metaclass__ yourself).
"""
__metaclass__ = MetaBunch
#===============================================================================
# main
#===============================================================================
if __name__ == '__main__':
class Point(Bunch):
x = 0
y = 0
p0 = Point()
assert p0.x == 0
assert p0.y == 0
p1 = Point(x=10, y=20)
assert p1.x == 10
assert p1.y == 20
Diff to Previous Revision
--- revision 2 2011-12-31 18:00:28
+++ revision 3 2011-12-31 18:03:02
@@ -5,15 +5,31 @@
@author: Bruno Oliveira
@author: Fabio Zadrozny
-Based on implementation by Alex Martelli (http://mail.python.org/pipermail/python-list/2002-July/112007.html).
+Based on implementation by Alex Martelli (http://mail.python.org/pipermail
+/python-list/2002-July/112007.html).
+
+To use:
+
+class Point(Bunch):
+ x = 0
+ y = 0
+
+p0 = Point()
+assert p0.x == 0
+assert p0.y == 0
+
+p1 = Point(x=10, y=20)
+assert p1.x == 10
+assert p1.y == 20
+
'''
import copy
from types import NoneType
-#===================================================================================================
+#===============================================================================
# MetaBunch
-#===================================================================================================
+#===============================================================================
class MetaBunch(type):
"""
metaclass for new and improved "Bunch": implicitly defines
@@ -58,7 +74,8 @@
"""
repr operator.
"""
- rep = ['%s=%r' % (k, getattr(self, k)) for k in sorted(self.__defaults__)]
+ rep = ['%s=%r' % (k, getattr(self, k))
+ for k in sorted(self.__defaults__)]
return '%s(%s)' % (classname, ', '.join(rep))
@@ -92,7 +109,8 @@
for k, value in classdict.iteritems():
- if k.startswith('__') or inspect.isfunction(value) or type(value) is property:
+ if k.startswith('__') or inspect.isfunction(value) or \
+ type(value) is property:
# methods: copy to newdict
newdict[k] = value
else:
@@ -125,9 +143,9 @@
-#===================================================================================================
+#===============================================================================
# Bunch
-#===================================================================================================
+#===============================================================================
class Bunch(object):
""" For convenience: inheriting from Bunch can be used to get
the new metaclass (same as defining __metaclass__ yourself).