简体   繁体   中英

Python subpackages and namespaces

I am struggling with what seems to me a very basic and common problem, and the fact that I could not find any answer after hours of Internet searching tells me that I must be doing something very wrong...

I am simply trying to find an elegent way to handle imports with my package.

The background:

My package is structured like this:

mypackage/
    __init__.py
    model/
        __init__.py
        A.py
        B.py
    controllers/
        __init__.py
        A.py
        B.py

# mypackage/model/A.py
class A:
    def __init__(self):
        print("This is A's model.")
# mypackage/model/B.py
from mypackage.model.A import A as AModel
class B:
    def __init__(self):
        self._a_model = AModel()
        print("This is B's model.")

# mypackage/controllers/A.py
class A:
    def __init__(self):
        print("This is A's controller.")
# mypackage/controllers/B.py
from mypackage.controllers.A import A as AController
class B:
    def __init__(self):
        self._a = AController()
        print("This is B's controller.")

The problem:

Two things are really bothering me with this design.

First: I want to use namespaces

I really don't like writing

from mypackage.controllers.A import A as AController
...
self._a = AController()

It feels cumbersome and not very pythonic... I would prefer using namespaces like in:

from mypackage import controllers
...
self._a = controllers.A.A()

But if I try, I get a AttributeError: module 'mypackage.controllers' has no attribute 'A'

Second: I really don't like typing the class's filename

I really don't like writing

from mypackage.controllers.A import A as AController

I would prefer:

from mypackage.controllers import A as AController

What did not work

Putting everything in one file

I understand that I could get what I want by puting all controller's class (A and B) defininitions in a single file (controllers.py) and do the same with the models...

I read several time that putting several class definitions in a single file is a quite common thing to do in python... But for big separate classes I just can't. If A and B are hundreds of lines and have nothing to do with each other (other than being controllers and models), having their definitions in a single file is unusable.

Using imports in the init .py files

Indeed it would solve all my problems...

Except that:

  1. It leads to circular imports. As you can see I need A's model in B's model... So if all models are imported when I need to access one of them, I'm stuck in vicious circle...
  2. It does not seems very pythonic. If only because it forces the user to load every modules.

Here I am...

What is wrong with my reasoning?

# mypackage/controllers/__init__.py
from A import A

Then you can make a new file outside of mypackage with.

# check.py
from mypackage.controllers import A as AController
from mypackage import controllers
a = controllers.A()
>>> This is A's controller.

let us know if it works for you.

[Off the top of my head, without testing]

I really don't like writing

from mypackage.controllers.A import A as AController #... self._a = AController()

It feels cumbersome and not very pythonic... I would prefer using namespaces like in:

 from mypackage import controllers #... self._a = controllers.AA()

In mypackage/controllers/__init__.py you would need: from. import A from. import A .

I really don't like writing

from mypackage.controllers.A import A as AController

I would prefer:

 from mypackage.controllers import A as AController

In mypackage/controllers/__init__.py you would need from A import A .

Using imports in the __init__.py file

That is the way to go. Sorry if it looks too much boiler plate for you, but if yru project is big, that is what is needed.

As for the circular import problem: it will kick in wether you write your imports in the __init__ files or not, and is just a matter of logistic. If in your __init__ file, you write the imports in the correct order, there will be no circularity problem.

Ie in your myproject/models/__init__.py you have:

from .A import A as AModel
from .B import B as BModel

Of course you naming the.py files the same names as the classes won't help you - if you will at least let go of the casing in the filename you can write:

from .a import A
from .b import B

Otherwise, you can do just:

import myproject.models.A
import myproject.models.B

A = myproject.models.A.A
B = myproject.models.B.B

To be able to use "myproject.models.A" as the class: The name A inside __init__ will override the module object with the same name.

One writting import myproject.models.A will get to the module, but by doing from myproject.models import A you get the module.

If that feels confusing... try not to use the same name for the module file than the classes. Even because in case-ignoring file systems, like Windows you would ambiguities anyway. Stick with the convention: module names in snake_case, class names in CamelCase

Back to the circular-imports matter: the point is that in this design, b.py is only read after a.py has been already imported, and no circular-import problem.

That is not always possible - sometimes cross-reference between submodules are needed. What is possible to do in these cases is to move the import lines into the functions or methods, instead of as a global statement. That way, when the import is executed, the referred module is already initialised.

In your example that would be:

mypackage.models.b.py

# mypackage/model/B.py

class B:
    def __init__(self):
        from mypackage.model.A import A as AModel
        self._a_model = AModel()
        print("This is B's model.")

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM