I'm looking into having a simple dependency injection container in Python. I've Googled on it but so far the solutions that I've found are quite huge and use constructs such as decorators and so on which I'm not so fond of. So, inspired (or copying) the Twittee IoC written by Fabien Potencier in PHP I wrote this script. I'd like to get a review on itclass Pymple(object):    def __init__(self):        self._values = {}    def register(self, key, value, as_singleton=False):        self._values[key] = value        if as_singleton:            self._values[key] = self.resolve(key)    def resolve(self, key):        if not key in self._values:            raise ValueError('Value %s is not registered.' % key)        return self._values[key](self) if callable(self._values[key]) else self._values[key]This could latter be used as class Sum:    def __ini__():        pass    def sum(self, a, b):        return a + bclass Multiply:    def __init__(self, sum):        self.sum = sum    def multiply(self, a, b):        rv = 0        for k in range(0, a):            rv = self.sum.sum(rv, b)        return rvcontainer = Pymple()container.register('sum', lambda c: Sum(), True)container.register('multiply', lambda c: Multiply(c.resolve('sum')))
Simple Python IoC
python;dependency injection
I would expect your Pymple class to be a bit simpler to use. At least for the retrieval of values something like this:container = Pymple()container.register('sum', lambda c: Sum(), True)container.sum.sum(1, 2)  # returns 3or like this:container = Pymple()container.sum = lambda c: Sum()container.sum.sum(1, 2)  # returns 3feels more natural to me. You can even provide the two versions, register being able to take optional parameters that __setattr__ can't.So I would start with something like:class Pymple(object):    Some usefull docstring here    def __init__(self):        super().__setattr__('_values', {})    def register(self, key, value, as_singleton=False):        self._values[key] = value        if as_singleton:            self._values[key] = getattr(self, key)    def __setattr__(self, key, value):        self.register(key, value, True)    def __getattr__(self, key):        if key not in self._values:            raise AttributeError('{}' is not registered..format(key))        attribute = self._values[key]        return attribute(self) if callable(attribute) else attributeNote the change in the type of error raised since we are dealing with attributes now. You also need to deal with the modified version of __setattr__ so you can't directly assign {} to self._values.Now about the way you return your values Given the above interface, I am expecting that:def email(container):    mail = {c.name}.{c.lastname}@{c.domain}.format(c=container)    def functor():        return '[{}] {}'.format(time.time(), mail)    return functorcontainer = Pymple()container.name = lambda c: 'spam'container.lastname = lambda c: 'eggs'container.domain = lambda c: 'bacon'container.frozen_email = emailcontainer.name = lambda c: 'foo'container.register('email', email)print(container.frozen_email())print(container.email())container.domain = lambda c: 'example.org'print(container.frozen_email())print(container.email())would print:[1479207663.6114736] spam.eggs@bacon[1479207663.7469463] foo.eggs@bacon[1479207663.8756231] spam.eggs@bacon[1479207663.9963526] foo.eggs@example.orgHowever it will fail as, even though frozen_email is stored as a singleton, the callable returned by email will be called with the container as argument and it does not expect it.Two other things that can be observed from this example:as_singleton is more an as_constant than anything else;there is too much boilerplate required when storing a constant.I think that, instead of trying to know if you have stored a constant or a function in the getter, you should wrap your constants into functions taking the container as parameter into the setter. And the getter will blindly perform a function call:class Pymple(object):    Some usefull docstring here    def __init__(self):        super().__setattr__('_values', {})    def register(self, key, value, constant=False):        if constant:            self._values[key] = lambda c: value        else:            self._values[key] = value    def __setattr__(self, key, value):        self.register(key, value, True)    def __getattr__(self, key):        if key not in self._values:            raise AttributeError('{}' is not registered..format(key))        return self._values[key](self)And the above example is now:container = Pymple()container.name = 'spam'container.lastname = 'eggs'container.domain = 'bacon'container.frozen_email = email(container)container.name = 'foo'container.register('email', email)print(container.frozen_email())print(container.email())container.domain = 'example.org'print(container.frozen_email())print(container.email())Now that we simplified the registration of constant values (be they callable or not), let's see if we can't do the same with variable ones. For one, register offers a way to use your Pymple class with existing functions so let's keep it. For two, you can easily use __setattr__ to store variable attributes as well as constant ones if you provide a base class that your user can inherit from to mark a value as variable:class PympleVariable(object):    def __init__(self, container):        raise NotImplementedErrorclass Pymple(object):    def __init__(self):        super().__setattr__('_values', {})    def register(self, key, value, constant=False):        if constant:            self._value[key] = lambda c: value        else:            self._value[key] = value    def __setattr__(self, key, value):        try:            variable = issubclass(value, PympleVariable)        except TypeError:            variable = False        self.register(key, value, not variable)    def __getattr__(self, key):        if key not in self._values:            raise AttributeError('{}' is not registered..format(key))        return self._values[key](self)So you can write:class email(PympleVariable):    def __init__(self, container):        self.mail = {c.name}.{c.lastname}@{c.domain}.format(c=container)    def __call__(self):        return '[{}] {}'.format(time.time(), self.mail)container = Pymple()container.name = 'spam'container.lastname = 'eggs'container.domain = 'bacon'container.frozen_email = email(container)container.name = 'foo'container.email = emailprint(container.frozen_email())print(container.email())container.domain = 'example.org'print(container.frozen_email())print(container.email())and get a pretty nice interface.