简体   繁体   中英

Web.py NameError When Importing Module in Module

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.

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