Skip to main content
gamedev.net
🔒 Locked

[Python] Module Organization

Started by doctorsixstring 10 replies 1.8k views
Original Post
doctorsixstring
doctorsixstring
I've recently started playing around with Python. I'm very attracted to its syntax, the availability of libraries like PyGame and PyUI, and its platform independence. My problem is that I am unable to figure out how to organize my .py files. My past experience is C++ and C#, so I need some pointers on how to get into the Python mindset. Here's what I have so far... main.py

import pygame, sys, state
from pygame import *

pygame.init()
displaySurface = pygame.display.set_mode((800, 600))

states = {
	"MainMenu" : state.MenuState(),
	"Action" : state.ActionState() }

curState = state.MenuState()

while True:
	for e in pygame.event.get():
		if e.type == QUIT:
			sys.exit()
		else:
			curState.event(e)

	displaySurface.fill((0,0,0))
	curState.paint()
	pygame.display.flip()


state.py

from pygame import *
	
class State:
	def event(self, event):
		pass
	def paint(self):
		pass

class MenuState(State):
	def event(self, event):
		if event.type == KEYDOWN and event.key == K_RETURN:
			main.curState = main.states["Action"]
			
	def paint(self):
		# draw stuff onto the screen (GUI, demo background, etc.)
		main.displaySurface.blit(...)

class ActionState(State):
	def event(self, event):
		if event.type == KEYDOWN and event.key == K_RETURN:
			main.curState = main.states["MainMenu"]

	def paint(self):
		main.displaySurface.blit(...)


The important bits are in the state.py file, where the main module is referenced. Of course, this doesn't work. Running the code as is, I get a "global name 'main' is not defined" error when the RETURN key is pressed. I can see a few ways to fix this: 1) Add "import main" immediately before "main.curState = ...". This solution works great, but would require me to sprinkle this statement in every method that accesses the "global" objects. 2) Remove all "global" objects from the design. If an object/method needs a reference to an object, the reference will be passed to it. In my case, the State.event() methods could take a reference to a SetState() method in main.py, which could be used to set the current state based on a given state name. The State.paint() methods could require a pygame.Surface object reference for rendering. 3) Is it possible to import an object reference from a module without actually executing the entire module? All forms of the "import" keyword seem to execute the entire module, even if it has already been executed elsewhere in the application. Which solution would be the cleanest and/or most "pythonic"? Is there a fourth solution that would work any better? Thanks in advance, - Mike
Fruny
Fruny
2 is a good choice, and as for 3, not quite, but:

def main():    displaySurface = pygame.display.set_mode((800, 600))    states = {        "MainMenu" : state.MenuState(),        "Action" : state.ActionState() }    curState = state.MenuState()    while True:        for e in pygame.event.get():            if e.type == QUIT:                sys.exit()            else:                curState.event(e)        displaySurface.fill((0,0,0))        curState.paint()        pygame.display.flip()if __name__ == "__main__":    pygame.init()    try:        main()    except:        pygame.quit()        raise


__name__ receives the name of the module, or "__main__" if it is the "main" module that was used to start the application. You can thus fence off bootstrap code (or module test code!). Beware circular imports, though.
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
doctorsixstring
doctorsixstring
Importing Fruny's main.py would not provide the subsystem references to the importing module, would it? Since displaySurface, for example, is only declared if the main() method is executed, the state.py module would not be able to access it.

Going with option #2 would increase the complexity, but reduce the coupling between my modules, right? I don't know if I will like passing subsystem object references in and out of a lot of methods, but I suppose that it will encourage me to think more about the design.

So is option #2 essentially the most recommended choice?
Fruny
Fruny
Quote:
Original post by Anonymous Poster
Rather
*** Source Snippet Removed ***


Duh, that's what I meant to do. Brain fart.


doctorsixstring - Alternatively, think about writing an Application class rather than just a main() function.
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
doctorsixstring
doctorsixstring
I would still need a seperate statement to create an instance of the App class, right? If so, wouldn't that statement get executed everytime I "import main" from another module? Adding "if __name__ == '__main__':" would simply prevent the modules from accessing the application object, as far as I can tell.

I'll probably lean towards using option #2, but it would be nice to understand how the "global" references could work.
Lucidquiet
Lucidquiet
Quote:
Original post by doctorsixstring
3) Is it possible to import an object reference from a module without actually executing the entire module? All forms of the "import" keyword seem to execute the entire module, even if it has already been executed elsewhere in the application.


I just finished Dive Into Python, and Practical Python, and IIRC the call to the second import shouldn't actually execute the entire module... but if you tested this, then its likely to be true... however the text said that Python just performs a lookup to see if it is loaded already.

If I'm wrong, I would be interested in findout, thanks,

L-
"Education is when you read the fine print; experience is what you get when you don't." -Pete Seegerwww.lucid-edge.net
doctorsixstring
doctorsixstring
Quote:
Original post by Lucidquiet
...IIRC the call to the second import shouldn't actually execute the entire module...


As far as I can tell, this only applies to multiple imports in the same module. If I import module A from B and C, the code in module A will get executed twice (once when B is executed, and once when C is executed).

When I get home from work, I'll post a little example code. In the meantime, if anyone else has a counter-example, please post it.
Fruny
Fruny
foo.py
print "foo"

bar.py
import fooprint "bar"

quux.py
import fooimport barprint "quux"



prints out "foo" "bar" "quux", and not "foo" "foo" "bar" "quux"

"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
doctorsixstring
doctorsixstring
Ah, that makes sense. My example falls under the "circular dependency" problem:

foo.py
import bar
print "foo"

bar.py
import foo
print "bar"

The output here is: "foo" "bar" "foo" (when running foo.py).
doctorsixstring
doctorsixstring
Quote:
Original post by doctorsixstring
I would still need a seperate statement to create an instance of the App class, right? If so, wouldn't that statement get executed everytime I "import main" from another module? Adding "if __name__ == '__main__':" would simply prevent the modules from accessing the application object, as far as I can tell.


Any thoughts? Is it impossible to import the reference without re-executing main() every time?
doctorsixstring
doctorsixstring
It has been a few weeks since this thread has been updated, but I thought I'd post my thoughts from my recent development. My "engine" is used in a way that is similar to PyGame (i.e. "pygame.graphics.set_mode(...)"). Rather than typing out a long-winded explanation, I'll post some sample/pseudo-code:

engine.py
from graphics import Graphicsgraphics = Noneinput = Nonesound = Nonetimer = NonecurrentState = Nonedef Init(...):    global graphics, input, sound, timer, currentState    graphics = Graphics(...)    # etc...def Run():    global graphics, input, sound, timer, currentState    while True:        engine.timer.Update()        engine.input.Update()        engine.sound.Update()        engine.graphics.Render()        currentState.Update()


graphics.py
class Graphics:    def __init__(self, width, height, bpp, ...)        # initialize PyGame and PyOpenGL    def Render():        # draw stuff to the screen


main.py
import engine, actionStateengine.Init(...)engine.currentState = actionState.ActionState()engine.Run()


actionState.py
import engineclass ActionState:    def Update(self):        # here we access the "global" timer and get the current system time        time = engine.timer.GetSystemTime()        # update the game world or something


I like this solution better than passing the "engine" reference back and forth between every object in my game, since it is far simpler, which means less time spent on maintenance and initial development.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.