Additional Notes On Functions

The global Statement

In Functions and Namespaces we’ll describe some of the internal mechanisms Python uses for storing variables. We’ll introduce the global statement in The global Statement.

We’ll include a digression on the two common argument binding mechanisms: call by value and call by reference in Call By Value and Call By Reference. Note that this is a distinction that doesn’t apply to Python, but if you have experience in languages like C or C++, you may wander where and how this is implemented.

Finally, we’ll cover some aspects of functions as first-class objects in Function Objects.

Functions and Namespaces

This is an overview of how Python determines the meaning of a name. We’ll omit some details to hit the more important points. For more information, see section 4.1 of the Python Language Reference.

The important issue is that we want variables created in the body of a function to be private to that function. If all variables are global, then each function runs a risk of accidentally disturbing the value of a global variable. In the COBOL programming language (without using separate compilation or any of the modern extensions) all variables are globally declared in the data division, and great care is required to prevent accidental or unintended use of a variable.

To achieve privacy and separation, Python maintains several dictionaries of variables. These dictionaries define the context in which a variable name is understood. Because these dictionaries are used for resolution of variables, which name objects, they are called namespaces. A global namespace is available to all modules that are part of the currently executing Python script. Each module, class, function, lambda, or anonymous block of code given to the exec command has its own private namespace.

Names are resolved using the nested collection of namespaces that define an execution environment. Python always checks the most-local dictionary first, ending with the global dictionary.

Consider the following script.

def deep( a, b ):
     print "a=", a
     print "b=", b

def shallow( hows, things ):
    deep( hows, 1 )
    deep( things, coffee )

hows= 1
coffee= 2
shallow( "word", 3.1415926 )
shallow( hows, coffee )
  1. The deep() function has a local namespace, where two variables are defined: a and b. When deep() is called from shallow(), there are three nested scopes that define the environment: the local namespace for deep(): the local namespace for shallow(), and the global namespace for the main script.

  2. The shallow() function has a local namespace, where two variables are defined: hows and things.

    When shallow() is called from the main script, the local hows is resolved in the local namespace. It hides the global variable with the same name.

    The reference to coffee is not resolved in the local namespace, but is resolved in the global namespace. This is called a free variable, and is sometimes a symptom of poor software design.

  3. The main script – by definition – executes in the global namespace, where two variables (hows and coffee) are defined, along with two functions, deep() and shallow().

Built-in Functions. If you evaluate the function globals(), you’ll see the mapping that contains all of the global variables Python knows about. For these early programs, all of our variables are global.

If you simply evaluate locals(), you’ll see the same thing. However, if you call locals() from within the body of a function, you’ll be able to see the difference between local and global variables.

The following example shows the creation of a gobal variable a, and a global function, q. It shows the local namespace in effect while the function is executing. In this local namespace we also have a variable named a.

>>> a=22.0
>>> globals()
{'__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__',
'__doc__': None, 'a': 22.0}
>>> def q( x, y ):
...     a = x / y
...     print locals()
...
>>> locals()
{'__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__',
'q': <function q at 0x76830>, '__doc__': None, 'a': 22.0}
>>> globals()
{'__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__',
'q': <function q at 0x76830>, '__doc__': None, 'a': 22.0}
>>> q(22.0,7.0)
{'a': 3.1428571428571428, 'y': 7.0, 'x': 22.0}

The function vars() accepts a parameter which is the name of a specific local context: a module, class, or object. It returns the local variables for that specific context. The local variables are kept in a local variable named __dict__. The vars() function retrieves this.

The dir() function examines the __dict__ of a specific object to locate all local variables as well as other features of the object.

Assignment statements, as well as def and class statements, create names in the local dictionary. The del statement removes a name from the local dictionary.

Some Consequences. Since each imported module exists in it’s own namespace, all functions and classes within that module must have their names qualified by the module name. We saw this when we imported math and random. To use the sqrt() function, we must say math.sqrt, providing the module name that is used to resolve the name sqrt().

This module namespace assures that everything in a module is kept separate from other modules. It makes our programs clear by qualifying the name with the module that defined the name.

The module namespace also allow a module to have relatively global variables. A module, for example, can have variables that are created when the module is imported. In a sense these are global to all the functions and classes in the module. However, because they are only known within the module’s namespace, they won’t conflict with variables in our program or other modules.

Having to qualify names within a module can become annoying when we are making heavy use of a module. Python has ways to put elements of a module into the global namespace. We’ll look at these in Components, Modules and Packages.

The global Statement

The suite of statements in a function definition executes with a local namespace that is different from the global namespace. This means that all variables created within a function are local to that function. When the suite finishes, these working variables are discarded.

The overall Python session works in the global namespace. Every other context (e.g. within a function’s suite) is a distinct local namespace. Python offers us the global statement to change the namespace search rule.

global name

The global statement tells Python that the following names are part of the global namespace, not the local namespace.

The following example shows two functions that share a global variable.

ratePerHour= 45.50
def cost( hours ):
    global ratePerHour
    return hours * ratePerHour
def laborMaterials( hours, materials ):
    return cost(hours) + materials

Warning

Global Warning

The global statement has a consequence of tightly coupling pieces of software. This can lead to difficulty in maintenance and enhancement of the program. Classes and modules provide better ways to assemble complex programs.

As a general policy, we discourage use of the global statement.

Call By Value and Call By Reference

Beginning programmers can skip this section. This is a digression for experienced C and C++ programmers.

Most programming languages have a formal mechanism for determining if a parameter receives a copy of the argument (call by value) or a reference to the argument object (call by name or call by reference.)

The distinction is important in languages with “primitive” types: data which is not a formal object. These primitive types can be efficiently passed by value, where ordinary objects are more efficiently passed by reference.

Additionally, this allows a languge like C or C++ to use a reference to a variable as input to a function and have the function update the variable without an obvious assignment statement.

Bad News. The following scenario is entirely hypothetical for Python programmers, but a very real problem for C and C++ programmers. Imagine we have a function to2() , with this kind of definition in C.

int to2( int *a ) {
    /* set parameter a's value to 2 */
    *a= 2;
    return 0;
}

This function changes the value of the variable a to 2. This would be termed a side-effect because it is in addition to any value the function might return normally.

When we do the following in C

int x= 27;
int z= to2( &x );
printf( "x=%i, z=%i", x, z );

We get the unpleasant side-effect that our function to2() has changed the argument variable, x, and the variable wasn’t in an assignment statement! We merely called a function, using x as an argument.

In C, the & operator is a hint that a variable might be changed. Further, the function definition should contain the keyword const when the reference is properly read-only. However, these are burdens placed on the programmer to assure that the program compiles correctly.

Python Rules. In Python, the arguments to a function are always objects, never references to variables.

Consider this Python version of the to2() function:

def to2( a )
    a = 2
    return 0

x = 27
z = to2( x )
print "x=%d, z=%d" % ( x, z )

The variable x is a reference to an integer object with a value of 27. The parameter variable (a) in the to2() function is a reference to the same object, and a is local to the function’s scope. The original variable, x, cannot be changed by the function, and the original argument object, the integer 27, is immutable, and can’t be changed either.

If an argument value is a mutable object, the parameter is a reference to that object, and the function has access to methods of that object. The methods of the object can be called, but the original object cannot be replaced with a new object.

We’ll look at mutable objects in Data Structures. For now, all the objects we’ve used (strings and numbers) are immutable and cannot be changed.

The Python rules also mean that, in general, all variable updates must be done explicitly via an assignment statement. This makes variable changes perfectly clear.

Function Objects

One interesting consequence of the Python world-view is that a function is an object of the class function, a subclass of callable. The common feature that all callable objects share is that they have a very simple interface: they can be called. Other callable objects include the built-in functions, generator functions (which have the yield statement instead of the return statement) and things called lambdas.

Sometimes we don’t want to call and evaluate a function. Sometimes we want to do other things to or with a function. For example, the various factory functions (int(), long(), float(), complex()) can be used with the isinstance() function instead of being called to create a new object.

For example, isinstance(2,int) has a value of True. It uses the int() function, but doesn’t apply the int() function.

A function object is created with the def statement. Primarily, we want to evaluate the function objects we create. However, because a function is an object, it has attributes, and it can be manipulated to a limited extent.

From a syntax point of view, a name followed by () is a function call. You can think of the () as the “call” operator: they require evaluation of the arguments, then they apply the function.

name ( arguments )

There are a number of manipulations that you might want to do with a function object.

Call The Function. By far, the most common use for a function object is to call it. When we follow a function name with (), we are calling the function: evaluating the arguments, and applying the function. Calling the function is the most common manipulation.

Alias The Function. This is dangerous, because it can make a program obscure. However, it can also simplify the evoluation and enhancement of software. Here’s a scenario.

Imagine that the first version of our program had two functions named rollDie() and rollDice(). The definitions might look like the following.

def rollDie():
    return random.randrange(1,7)
def rollDice():
    return random.randrange(1,7) + random.randrange(1,7)

When we wanted to expand our program to handle five-dice games, we realized we could generalize the rollDice() function to cover both cases.

def rollNDice( n=2 ):
    t= 0
    for d in range(n):
        t += random.randrange( 1, 7 )
    return t

It is important to remove the duplicated algorithm in all three versions of our dice rolling function. Since rollDie() and rollDice() are just special cases of rollNDice().

We can replace our original two functions with something like the following.

def rollDie():
    return rollNDice( 1 )
def rollDice():
    return rollNDice()

However, we have an alternative.

This revised definition of rollDice() is really just an another name for the rollNDice(). Because a function is an object assigned to a variable, we can have multiple variables assigned to the function. Here’s how we create an alias to a function.

def rollDie():
    return rollNDice( 1 )

rollDice = rollNDice

Warning

Function Alias Confusion

Function alias definitions helps maintaining compatibility between old and new releases of software. It is not something that should be done as a general practice; we need to be careful providing multiple names for a given function. This can be a simplification. It can also be a big performance improvement for certain types of functions that are heavily used deep within nested loops.

Function Attributes. A function object has a number of attributes. We can interrogate those attributes, and to a limited extend, we can change some of these attributes. For more information, see section 3.2 of the Python Language Reference and section 2.3.9.3 of the Python Library Reference.

func_doc:
__doc__:Docstring from the first line of the function’s body.
func_name:
__name__:Function name from the def statement.
__module__:Name of the module in which the function name was defined.
func_defaults:Tuple with default values to be assigned to each argument that has a default value. This is a subset of the parameters, starting with the first parameter that has a default value.
func_code:The actual code object that is the suite of statements in the body of this function.
func_globals:The dictionary that defines the global namespace for the module that defines this function. This is m.__dict__ of the module which defined this function.
func_dict:
__dict__:The dictionary that defines the local namespace for the attributes of this function.

You can set and get your own function attributes, also. Here’s an example

def rollDie():
    return random.randrange(1,7)
rollDie.version= "1.0"
rollDie.authoor= "sfl"

Table Of Contents

Previous topic

Functions

Next topic

Sequences: Strings, Tuples and Lists

This Page