"""Mix-in classes for easy attribute setting and pretty representation
>>> class T(HasInitableAttributes, HasTypedAttributes, IsCallable):
... spam = str
... def __init__(self, eggs, ham = 'ham'): pass
...
>>> t = T('bacon'); t(ham = 'eggs'); t.spam += 'sausage'; t
__main__.T('bacon', ham = 'eggs', spam = 'sausage')
>>>
"""
from inspect import getargspec
from itertools import chain
class HasInitableAttributes(object):
"""Initializes attributes automatically
>>> class T(HasInitableAttributes):
... z = 0
... def __init__(self, x, y=0, **opts): pass
...
>>> t = T(0, a = 1); t
__main__.T(0, a = 1)
>>> t.x, t.y, t.z = 1, 2, 3; t
__main__.T(1, y = 2, a = 1, z = 3)
>>>
"""
def __new__(cls, *pars, **opts):
"Initialize all attributes in the signature and any other options supplied"
try:
self = super().__new__(cls, *pars, **opts)
except:
self = super().__new__(cls)
self._argspec = names, parsname, optsname, defaults = getargspec(self.__init__)
if not defaults: defaults = []
n = len(names) - len(defaults) - 1
if n - len(pars) > 0:
_s, _to = ('s', '-%d' % (n-1)) if n - len(pars) > 1 else ('', '')
missing = "%s %s (pos %d%s)" % (_s, ", ".join(names[1:n+1]), len(pars), _to)
raise TypeError("Required argument%s not found." % missing)
for n, v in chain(zip(names[-len(defaults):], defaults), zip(names[1:], pars), opts.items()):
setattr(self, n, v)
return self
def __repr__(self):
"Show all attributes in the signature and any other public attributes that are changed"
names, parsname, optsname, defaults = self._argspec
if not defaults: defaults = []
optnames = names[-len(defaults):] if defaults else []
optvalues = (getattr(self, name) for name in optnames)
othernames = sorted(set((n for n in self.__dict__ if n[0] != '_')) - set(names))
othervalues = list((getattr(self, name, None) for name in othernames))
otherdefaults = list((getattr(self.__class__, name, None) for name in othernames))
return "%s.%s(%s)" % (self.__module__, self.__class__.__name__, ", ".join(chain(
(repr(getattr(self, name)) for name in names[1:len(names)-len(defaults)]),
("%s = %r" % (name, value) for name, value, default in zip(optnames, optvalues, defaults) if value != default),
("%s = %r" % (name, value) for name, value, default in zip(othernames, othervalues, otherdefaults) if value != default))))
class HasTypedAttributes(object):
"""Objectifies class attributes automatically
>>> class T(HasTypedAttributes):
... spam = str
... class C(HasTypedAttributes):
... eggs = list
...
>>> a, b = T(), T(); a.spam += 'ham'; a.C.eggs.append('bacon'); a.spam, b.spam, a.C.eggs, b.C.eggs
('ham', '', ['bacon'], [])
>>>
"""
def __new__(cls, *pars, **opts):
try:
self = super().__new__(cls, *pars, **opts)
except:
self = super().__new__(cls)
for name in dir(self):
if name[0] != '_':
value = getattr(self, name)
if isinstance(value, type):
setattr(self, name, value(opts.pop(name)) if name in opts else value())
if opts:
raise TypeError("__init__() got%s unexpected keyword argument%s %r" %
(" an", "", opts.keys()[0]) if len(opts) == 1 else ("", "s", opts.keys()))
return self
class IsCallable(object):
"""Update attributes by calling
>>> class T(IsCallable):
... x = 0
...
>>> t = T(); t(x=1, y=2); t.x, t.y
(1, 2)
"""
def __call__(self, *pars, **opts):
self.__dict__.update(*pars, **opts)
if __name__ == '__main__':
from doctest import testmod
testmod()
class T(HasInitableAttributes, HasTypedAttributes, IsCallable):
spam = str
t = T()
assert t.spam != str
Diff to Previous Revision
--- revision 1 2014-12-02 14:29:18
+++ revision 2 2014-12-03 09:55:06
@@ -38,7 +38,7 @@
n = len(names) - len(defaults) - 1
if n - len(pars) > 0:
_s, _to = ('s', '-%d' % (n-1)) if n - len(pars) > 1 else ('', '')
- missing = "%s %s (pos %d%s)" % (_s, ", ".join(names[1:-len(defaults)]), len(pars), _to)
+ missing = "%s %s (pos %d%s)" % (_s, ", ".join(names[1:n+1]), len(pars), _to)
raise TypeError("Required argument%s not found." % missing)
for n, v in chain(zip(names[-len(defaults):], defaults), zip(names[1:], pars), opts.items()):
setattr(self, n, v)