[Tutor] functions first?

Steven D'Aprano steve at pearwood.info
Tue Jan 27 12:18:35 CET 2015


On Mon, Jan 26, 2015 at 06:04:10PM -0800, Alex Kleider wrote:
> Please correct me if I am wrong, but I've assumed that it is proper to 
> define all functions before embarking on the main body of a program.
> I find myself breaking this rule because I want to set the default 
> values of some named function parameters based on a configuration file 
> which I have to first read, hence the need to break the rule.
> ..so my question is "is this acceptable" or "is there a better way?"

I'm not really sure I understand the question correctly.

As far as *design* go, there are two competing paradigms, "top-down" 
versus "bottom-up". Perhaps you are thinking about that?

In top-down, you start with the part of the application closest to the 
user, say, a web browser:

    def run_browser():
        win = make_window()
        page = get_url(whatever)
        show_page(page, win)

Then you write the next level down:

   def make_window()
       ...

   def get_url(address):
       ...

   def show_page(page, win):
       ...

and so on, all the way down to the most primitive and simple parts of 
the application:

   def add_one(n):
       return n+1


In bottom-up programming, you do the same, only in reverse. You build 
the most primitive functions first, then add them together like Lego 
blocks, getting more and more complicated and powerful until you end up 
with a fully-functioning web browser.
    
Personally, I think that in any real application, you need a combination 
of both top-down and bottom-up, but mostly top-down. (How do you know 
what primitive operations you will need right at the start of the design 
process?) Also, many of the primitive "Lego blocks" already exist for 
you, in the Python standard library.
   
If you are talking about the order in which functions appear in the 
source file, I tend to write them like this:

=== start of file ===
hash-bang line
encoding cookie
licence
doc string explaining what the module does
from __future__ imports
import standard library modules
import custom modules
metadata such as version number
other constants
global variables
custom exception types
private functions and classes
public functions and classes
main function
if __name__ == '__main__' code block to run the application
=== end of file ===


All of those are optional (especially global variables, which I try hard 
to avoid). Sometimes I swap the order of private and public sections, 
but the main function (if any) is always at the end just before the "if 
__name__" section.


As far as the default values go, I *think* what you are doing is 
something like this:

f = open("config")
value = int(f.readline())
f.close()

def function(x, y=value):
    return x + y



Am I right?

If so, that's not too bad, especially for small scripts, but I think a 
better approach (especially for long-lasting applications like a server) 
might be something like this:

def read_config(fname, config):
    f = open(fname)
    config['value'] = int(f.readline())
    f.close()

PARAMS = {  
    # set some default-defaults that apply if the config file
    # isn't available
    value: 0,
    }

read_config('config', PARAMS)


def func(x, y=None):
    if y is None:
        y = PARAMS['value']
    return x + y



This gives lots of flexibility:

- you can easily save and restore the PARAMS global variable with 
  just a dict copy operation;

- you can apply multiple config files;

- you can keep multiple PARAMS dicts (although as written, func only 
  uses the one named specifically PARAMS);

- read_config can be isolated for testing;

- you can re-read the config file without exiting the application;

etc.


Does this help?


-- 
Steve


More information about the Tutor mailing list