organizing your scripts, with plenty of re-use

Steven D'Aprano steve at REMOVE-THIS-cybersource.com.au
Sat Oct 10 04:57:08 EDT 2009


On Fri, 09 Oct 2009 16:37:28 -0700, Buck wrote:

> Here's a scenario. A user does a cvs checkout into some arbitrary
> directory and sees this:
> 
> project/
> +-- python/
>     +-- animals.py
>     +-- mammals/
>         +-- horse.py
>         +-- otter.py
>     +-- reptiles/
>         +-- gator.py
>         +-- newt.py
>     +-- misc/
>         +-- lungs.py
>         +-- swimming.py
> 
> These are all runnable scripts that "just work" with no extra effort or
> knowlege, both in the testing scenario above, and for normal users that
> run it from some central location (maybe "/tools/mycompany/bin/
> mammals").
> 
> The frustrating thing, for me, is that all these requirements are met if
> you leave the scripts in jumbled into a flat directory. 


I bet that's not true. I bet that they Just Work only if the user cd's 
into the directory first. In other words, if you have all your scripts in 
the directory /tools/mycompany/bin/scripts, this will work:

$ cd /tools/mycompany/bin/scripts
$ animals.py

but this won't:

$ cd /home/username
$ /tools/mycompany/bin/scripts/animals.py


In the first case, it works because the current working directory is 
included in the PYTHONPATH, and all the modules you need are there. In 
the second, it doesn't because the modules aren't in either the current 
directory or any other directory in the PYTHONPATH.

That's my prediction.



> As soon as you
> start organizing things, you need a good amount of boilerplate in each
> script to make things work anything like they did with the flat
> directory.

You shouldn't need that much boilerplate. A little, perhaps, but not that 
much.

Although I have defended the practice of making modules executable, I do 
recognise that for complex packages this becomes difficult quickly. It 
sounds like you would benefit greatly from separating the interface from 
the backend. You should arrange matters so that the users see something 
like this:

project/
+-- animal
+-- mammal
+-- reptile
+-- backend/
    +-- __init__.py
    +-- animals.py
    +-- mammals/
        +-- __init__.py
        +-- horse.py
        +-- otter.py
    +-- reptiles/
        +-- __init__.py
        +-- gator.py
        +-- newt.py
    +-- misc/
        +-- __init__.py
        +-- lungs.py
        +-- swimming.py


where the front end is made up of three scripts "animal", "mammal" and 
"reptile", and the entire backend is in a package. Each front end script 
manages a small amount of boilerplate, something like this:

#!/usr/bin/python
import os, sys

if __name__ == '__main__':
    # find out where we are, and add it to the path
    location = __import__('__main__').__file__
    location = os.path.dirname(location)
    if location not in sys.path:
        sys.path.append(location)

    import animals
    animals.main()

That's not a lot of boilerplate for a script.

The backend modules rely on the path being setup correctly. For example, 
animals.py might do:

import mammals.horse
horse.ride('like the wind')


Calling the backend modules directly is not supported.



-- 
Steven



More information about the Python-list mailing list