Proposal: Allow Modules to Accept Initialization Parameters Upon Import

Proposal

I propose introducing a mechanism in Python that allows modules to accept initialization parameters upon import. This would enable passing instances or configurations directly to a module at the time of import, facilitating better modularity and dependency management.


Example

# module_a.py

def __init__(a_instance):
    global _a_instance
    _a_instance = a_instance

def use():
    _a_instance.hello()

# main.py

class A:
    def hello(self):
        print("hello")

a = A()

import module_a(a)
module_a.use()

Motivation

  • Avoid circular imports by injecting dependencies at import time.
  • Enhance modularity by decoupling module definitions from their configurations.
  • Facilitate patterns similar to Dependency Injection, improving testability and maintainability.

Questions

  • Are there existing mechanisms in Python that achieve similar functionality?
  • Has this concept been proposed or discussed previously?

Please let me know your thoughts and any potential implications of this proposal.

1 Like

Can someone move this to Ideas please? Thanks.

I think both of these solutions are better than your proposal:

import module_a # look, I'm at the top
...
module_a.init(a) # less magic
module_a.use()
# or
class_a = module_a.ClassA(a) # no global configuration
class_a.use()
5 Likes

It requires a lot of work, and there’re really very few benefits, other than saving you from having to write a class, import it and initialise it (or use Nineteendo’s initialiser function).

1 Like

But with a constructor you could load something before everything else is loaded.

def __init__(instance_a):
    a = instance_a

class B:
    def test(x:a=a): # <--- for example Type Annotation or default values
        x.use()
1 Like

That requires dynamic runtime behaviour to be used in pre-runtime type checking analysis then. Use a Generic class, or Generic function instead, with a type variable.

A well designed module should come with its own sensible default values, so it works out of the box.

2 Likes

You might be interested in PEP 713.

You can still do that:

def make_a(default_text):
    class A:
        @staticmethod
        def print_(text=default_text):
            print(text)

    return A

a = make_a("foo")
a.print_() # prints foo

Modules that initialize on import has been a source of enormous pain for me over the years.

I absolutely hate how Jax does this, for example. It means that I have carefully ensure that I call the Ray library’s initialization function before I even import Jax, which isn’t easy in a large program.
Especially since I don’t always want to initialize Ray.

Similarly, matplotlib has initialization code so if you want to update settings, you to set the rcParams at a special time.

In general, I think libraries that initialize on import are a mistake. These libraries should have instead used a context manager that initializes the library on enter and un-initializes it on exit.

I don’t think we should encourage libraries to have initialization code like in this proposal.

Are there existing mechanisms in Python that achieve similar functionality?

Have you tried putting your initialization code into a context manager, and using the returned context if necessary?

9 Likes

I don’t like either the syntax or the idea.

In a statement like import mod, the code text ‘mod’ is first used to search sys.modules, and if not found, to create a file path used to initiate a new module. In either case, it is then used as the target for an assignment. In import mod(a), ‘mod(arg)’ looks like a function call evaluated. But it is not: ‘mod’ is not an identifier mapped to any object, let alone a callable. But ‘arg’ must be a valid python expression and it must be evaluated in the local namespace. Is such fake function call syntax used elsewhere?

The proposal pushed further the idea that modules are (like) classes. Both are namespaces, but the differences are important. In particular, classes are usually instanced, and mutating one instance normally does not affect other instances. But mutating (monkey-patching) modules affects all users. Mixing it in with module creation makes it less visible. (See Neil’s comment about resulting pain.)

If two modules have import mod(a) and import mod(b), is init called only for the first import, when mod is created, or for each import? Really better to be explicit, or create separate class instances as Nice Zombies suggested.

6 Likes

Based on your proposal, it seems the module depends on main.py to function properly, so it might not be considered a standalone module.

What specific problems is the proposal trying to address? Can you provide real-life examples where these issues still persist and are not yet fully resolved?

2 Likes

I like the ideas of having dependency injection for module or packages

But ghen the proposal should include concreting examples of doing those

Currently it seems very involved to get a module rights that has defaults but cam be imported differently formed tests or consumer’s

Dependency injection for python is currently very diverse so im terrified of the prospect of getting a naive one into module

I do like ocaml modules as example of working dependency injection for modules

And what would happen, in this example, when the module is imported in more than one place in the same program, with conflicting parameters?

2 Likes