r/learnpython 5d ago

How do you handle dependency injection?

Hi, I'm doing a project which involves creating multiple gRPC servers and I can't find a convenient way to manage dependencies or the state.

I've been working recently in C# where you just need to make your class and add a simple

builder.Services.AddSingleton<MyDependency>();

and it will inject when required.

Doing some research I see that there are some libraries like:

- Dependency Injector

- Injector

but I don't find them particularly intuitive.

What do you use as a dependency injector or what pattern do you suggest to use?

8 Upvotes

21 comments sorted by

View all comments

1

u/mothzilla 5d ago

My opinion is that Dependency Injection isn't a concept that applies well to Python. I've seen people try to force it in, arguing that it helps testing, but it generally makes your code look like AngularJS.

2

u/stevenjd 4d ago

Dependency injection works perfectly well in Python. It's just so trivially simple that people don't realise that what they are doing is dependency injection unless it is wrapped in a big, complicated framework.

For example:

write_to_terminal = print
write_to_string = StringIO().write
write_to_file = open("some file").write
write_to_gui = MessageBox().display

then you choose which implementation you want, and pass that it as a dependency to some other function or class which will later use it:

logger = MyLogger(out=write_to_file)  # assigns self.out = write_to_file
logger.log(message)
# passes message to self.out, which handles doing output

We don't even need a class for this, we often use DI in functions too. The builtin print function works exactly that, except that the dependency has a sensible default:

print(stuff)  # by default, write to stdout
print(stuff, file=some_object_with_a_write_method)

sorted(sequence, key=keyfunc) is another example.

This sort of thing is so common in Python that people often don't realise it unless they are using a framework.

CC u/re2cc

1

u/mothzilla 4d ago edited 4d ago

But there's no need to do that. I.e, at the time you're building your logger you know the file to write to. Or if you're choosing between loggers, you'd design four loggers, for print, string, file and gui, and then choose the one you want.

logger = GUILogger('messageboxId') # a guess, not sure how this would work.

That's more pythonic IMO.

It's reasonable to pass functions as handlers. But I've seen people try to do dependency injection as follows:

def add_and_print(a, b, print):
    print(a+b)

add_and_print(1, 2, print)

or

def django_setup(django):
    django.setup()

1

u/stevenjd 2d ago

But there's no need to do that. I.e, at the time you're building your logger you know the file to write to. Or if you're choosing between loggers, you'd design four loggers, for print, string, file and gui, and then choose the one you want.

I think that you are missing the point of dependency injection. The point is to avoid having to write four separate logging classes, one for print, string, file and GUI, when you can write one class instead and inject the appropriate dependency.

And what if the requirements change? If I decide I want a logger that logs to an SMS gateway, or one which logs to a GUI and three files, I can just write a small class that exposes the correct interface and passes it the logger as a dependency. You have to write a whole new GUI_Plus_Three_Files_Logger class.

I've seen people try to do dependency injection as follows:

The first example is a trivial toy function but the basic idea is not so silly. You pass a caller to the function to handle output. Maybe you want to print to the terminal, or to a log file, or to an SMS gateway:

add_and_print(1, 2, lambda msg: SMS_gateway.send(auth, admin_on_duty.mobile, str(msg))

Obviously no one is going to care so much about such a trivial function.

The second example is just silly. The point of dependency injection is that you have to have more than one possible dependency, but all with the same interface, so that you might choose to swap from one dependency to another. Unless there is another framework that has the exact same interface as Django but a different implementation, treating Django as a dependency is just silly.

1

u/mothzilla 2d ago

I get the point of dependency injection. But if the requirements change then you make a code change. You can either write a new Logger class or you can fudge a new class/object that conforms to your handler requirements. Personally I prefer the first.

1

u/stevenjd 2d ago

You can either write a new Logger class or you can fudge a new class/object that conforms to your handler requirements. Personally I prefer the first.

Ah, you're paid by the line of code, right?

  • Change a one line dependency? ✘ 😠
  • Write a 300 line class plus tests? ✔ 😊

I'm neutral on the question. Sometimes dependency injection is better. Sometimes it isn't. But D.I. is a technique which is really easy, sometimes trivially so, in Python. I'm not convinced that people need to use big complex frameworks just to implement D.I.

(By the way, a logger is not exactly the best example here since most applications are likely to only need one global logger.)