I am creating a web app using web.py on python 2.7.3. I have the following folder structure:
start_app.py
/app
__init__.py
/models
__init__.py
ActionModel.py
AreaModel.py
/controllers
__init__.py
world.py
/views
Whenever I freshly start the app using python start_app.py
, and visit world/surrounding
I get the following error
<type 'exceptions.ImportError'> at /world/surrounding
cannot import name AreaModel
Python /home/dev/app/models/ActionModel.py in <module>, line 13
Web GET http://localhost:5000/world/surrounding
Line 13 is simply: from app.models import AreaModel
but I don't see why python is complaining here.
If I comment this importing line, it runs fine. However, if I call a different URL, eg world/view
, I get an error that AreaModel is not defined. Once I uncomment the line, it works fine again for all cases (ie /surrounding and /view).
I am suspecting that this has something to do with the fact that I am "importing in circles", ie world.py imports AreaModel, AreaModel imports ActionModel and ActionModel imports AreaModel. I doubt that this is 'the pythonic way' to do things or even the 'MVC way', so I would very much appreciate your enlightening me how to do this properly.
Note: app is not in my PYTHONPATH, but I don't think it is needed here, since start_app.py is in the top-level directory and according to this all modules should be available.
Basically, what it comes down to is: I need the models' functionalities in both the controllers and the models. Is it good practice to "import in circles"? Or is there a nicer approach to do this? Also, is this problem related to python in general or just web.py?
Update: Added init .py files, I had them, but did not include in original question. Sorry for that.
Update: ActionModel.py includes (among others) a class named BaseAction and a few functions, which return instances or subclasses of BaseAction depending on what type of Action we are dealing with. They are called using eg ActionModel.get_by_id()
@matthew-trevor : Are you suggesting in a) that I should move those functions get_by_id()
into a class ActionModel?
#actionmodel.py
class ActionModel(object):
def __init__(arg1, arg2, area_class):
self.area = area_class()
def get_by_id(self, id):
return BaseAction(id)
class BaseAction(object):
def __init__(id):
pass
I don't see how this should remedy my import problems though.
The Immediate Problem
You cannot import from folders, but you can import from packages. You can turn any folder into a package by adding an __init__.py
file to it:
start_app.py
/app
__init__.py
/models
__init__.py
ActionModel.py
AreaModel.py
/controllers
__init__.py
world.py
/views
__init__.py
I'm guessing that ActionModel.py
includes a class of the same name. If so, I recommend renaming the file to actionmodel.py
to distinguish it from the class.
Circular imports
Is it good practice to "import in circles"? Or is there a nicer approach to do this?
It's not only bad practice, it just won't work. There are a couple of ways to get around this, which will mostly depend on what you're trying to do:
a. In AreaModel
, import the ActionModel
module and then reference anything you want to use in it via attribute lookup and vice versa:
# areamodel.py
import actionmodel
def foo():
action = actionmodel.ActionModel(...)
As long as the references are inside class or function definitions, it will only occur at run time and not during importing, so the circular reference is avoided.
b. Turn models
into a module and put both ActionModel
and AreaModel
code inside it.
c. Move the shared code/functionality for ActionModel
and AreaModel
into a base module they both import from.
d. Make your ActionModel
class (or whatever) accept a class as an input, then pass AreaModel
into it in world.py
(ditto for AreaModel
). This way, ActionModel
doesn't need to contain a reference to AreaModel
, it just has to know what to do with it:
# actionmodel.py
class ActionModel(object):
def __init__(arg1, arg2, area_class):
self.area = area_class()
# areamodel.py
class AreaModel(object):
def __init__(action_class):
self.action = action_class()
# world.py
from actionmodel import ActionModel
from areamodel import AreaModel
action = ActionModel('foo', 'bar', AreaModel)
area = AreaModel(ActionModel)
This is known as object composition.
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.