Hacker News new | ask | show | jobs
by NewMountain 1534 days ago
I'm catching up on this thread now, sorry for the delay. You're absolutely correct. My function needs A, B and C to work is valid. That's perfectly fine.

    if __name__ == "__main__":
        a = os.getenv("foo")
        b = get_from_somewhere()
        c = "some_hardcoded_variable"
        my_app(a, b, c)
Is what I would expect in any reasonable codebase. The indirection and cognitive overhead was taken from Java where seeing @Autowired or @Inject, hitting reverse search and not being able to keyword search for the thing being injected is nightmare fuel. That is not something I would wish on an enemy, let alone on any user of a programming language I care for.

My original reply was in response to "please don't make Python Java", where that flavor of spooky action DI seems to be common.

To your original question: `what do I suggest`. I point to the authors example:

    di = Di()

    @di.register('A')
    class A:

        def __init__(self, di: Di):
            pass

        def action(self):
            print("Hi from A")

    @di.register('B')
    class B:

        def __init__(self, di: Di):
            self._di = di
            self._a = None

        def run(self):
            self.get_a().action()

        def get_a(self):
            if self._a is None:
            self._a = self._di.get('A')
            return self._a

    @di.override('A') # replace class A definition with a new one
    class C:

        def __init__(self, di: Di):
            pass

        def action(self):
            print("Hi from C")

    di.get('B').run() # prints "Hi from C"
    di.remove('B') # removes the B dependency from the registry
My suggestion: please not that.

How about:

    class A:

        def __init__(self):
            pass

        def action(self):
            print("Hi from A")

    class B:

        # A is default for some reason
        def __init__(self, a=A()):
            self._a = a

        def run(self):
            self._a.action()

    class C:

        def __init__(self):
            pass

        def action(self):
            print("Hi from C")


    if __name__ == "__main__":
        if os.getenv('ENV') == "PROD":
            a = A()
            b = B()
        # test/non-prod/whatever version
        else:
            a = A()
            b = B(C())

As someone who's been writing python for a while, I could walk into this having never read it before and say...ok sure, when prod, B takes A behavior, but when not prod, B takes C behavior.

In the proposed example, I would go in, read it (assuming I know to even look for this non-idiomatic use of the language) and then read through this code to figure out what should have just been an argument passed to B.

Which leads back to my original question: what is this buying me over dunder main or handing some arguments? In terms of cost, I have to understand something and walk through the code to understand something that could have been done more simply and more explicitly without this DI pattern.

Edit: formatting

1 comments

Thank you for very detailed reply! As the matter of fact, I mostly agree with you and prefer such a direct (or manually wired) approach to DI, over Spring's declarative one. Somehow I was under impression that you are against DI as a concept. I am sorry for that.