I personally don't like the boilerplate of most __init__
methods:
self.a = a
self.b = b
...
So I thought this would be a nice opportunity to learn a bit more about decorators. As this is my first attempt on a class decorator I'm sure there is a lot to improve so fire away:
Implementation
from collections import namedtuple
def autofill(*args, **kwargs):
""" This class decorator declares all attributes given into the constructor
followed by a call of __init__ without arguments (beside reference to self).
Order is the same than namedtuple with the possibility of default elements.
Note that in this decorator alters the existing class instance instead of
returning a wrapper object. """
def filler(cls, *args, **kwargs):
""" This is our custom initialization method. Input sanitation and
ordering is outsourced to namedtuple. """
for key, val in InputSanitizer(*args, **kwargs)._asdict().items():
setattr(cls, key, val)
filler.super_init(cls)
def init_switcher(cls):
filler.super_init = cls.__init__
cls.__init__ = filler
return cls
# Taken from http://stackoverflow.com/questions/11351032/named-tuple-and-
# optional-keyword-arguments
InputSanitizer = namedtuple('InputSanitizer', args + tuple(kwargs.keys()))
InputSanitizer.__new__.__defaults__ = tuple(kwargs.values())
return init_switcher
Some test cases
import unittest
class TestAutoFill(unittest.TestCase):
@autofill('a', b=12)
class Foo(dict):
pass
def test_zero_input(self):
with self.assertRaises(TypeError):
self.Foo()
def test_one_input(self):
bar = self.Foo(1)
self.assertEqual(bar.a, 1)
self.assertEqual(bar.b, 12)
bar = self.Foo(a=1)
self.assertEqual(bar.a, 1)
self.assertEqual(bar.b, 12)
with self.assertRaises(TypeError):
self.Foo(b=1)
with self.assertRaises(TypeError):
self.Foo(c=12)
def test_two_input(self):
bar = self.Foo(1, 2)
self.assertEqual(bar.a, 1)
self.assertEqual(bar.b, 2)
bar = self.Foo(b=2, a=1)
self.assertEqual(bar.b, 2)
self.assertEqual(bar.a, 1)
def test_other_object_functions(self):
bar = self.Foo(1)
bar.c = 3
bar['key'] = 4
self.assertEqual(bar.a, 1)
self.assertEqual(bar.b, 12)
self.assertEqual(bar.c, 3)
self.assertEqual(bar['key'], 4)
if __name__ == '__main__':
unittest.main()