Python Parameters, Functions and Arguments

In this eighth part of a nine-part series on the Python programming language, we focus strongly on two aspects of functions: parameters and arguments. This article is excerpted from chapter four of the book Python in a Nutshell, Second Edition, written by Alex Martelli (O’Reilly; ISBN: 0596100469). Copyright © 2007 O’Reilly Media, Inc. All rights reserved. Used with permission from the publisher. Available from booksellers or direct from O’Reilly Media.

Parameters

Formal parameters that are just identifiers indicate mandatory parameters. Each call to the function must supply a corresponding value (argument) for each mandatory parameter.

In the comma-separated list of parameters, zero or more mandatory parameters may be followed by zero or more optional parameters, where each optional parameter has the syntax:

  identifier=expression

The def statement evaluates each such expression and saves a reference to the expression’s value, known as the default value for the parameter, among the attributes of the function object. When a function call does not supply an argument corresponding to an optional parameter, the call binds the parameter’s identifier to its default value for that execution of the function. Note that each default value gets computed when the def statement evaluates, not when the resulting function gets called. In particular, this means that the same object, the default value, gets bound to the optional parameter whenever the caller does not supply a corresponding argument. This can be tricky when the default value is a mutable object and the function body alters the parameter. For example:

  def f(x, y=[]):
     
y.append(x)
     
return y
 
print f(23)           # prints: [23]
  prinf f(42)           # prints: [23, 42]

The second print statement prints [23, 42] because the first call to f altered the default value of y, originally an empty list [], by appending 23 to it. If you want y to be bound to a new empty list object each time f is called with a single argument, use the following style instead:

  def f(x, y=None):
     
if y is None: y = []
     
y.append(x)
     
return y
 
print f(23)               # prints: [23]
  prinf f(42)               # prints: [42]

At the end of the parameters, you may optionally use either or both of the special forms *identifier1 and **identifier2 . If both forms are present, the form with two asterisks must be last. *identifier1 specifies that any call to the function may supply any number of extra positional arguments, while **identifier2  specifies that any call to the function may supply any number of extra named arguments (positional and named arguments are covered in "Calling Functions" on page 73). Every call to the function binds identifier1  to a tuple whose items are the extra positional arguments (or the empty tuple, if there are none). Similarly, identifier2  gets bound to a dictionary whose items are the names and values of the extra named arguments (or the empty dictionary, if there are none). Here’s a function that accepts any number of positional arguments and returns their sum:

  def sum_args(*numbers):
     
return sum(numbers)
  print sum_args(23, 42)        # prints: 65

The number of parameters of a function, together with the parameters’ names, the number of mandatory parameters, and the information on whether (at the end of the parameters) either or both of the single- and double-asterisk special forms are present, collectively form a specification known as the function’s signature. A function’s signature defines the ways in which you can call the function.

{mospagebreak title=Attributes of Function Objects}

The def statement sets some attributes of a function object. The attribute func_name, also accessible as __name__, refers to the identifier string given as the function name in the def statement. In Python 2.3, this is a read-only attribute (trying to rebind or unbind it raises a runtime exception); in Python 2.4, you may rebind the attribute to any string value, but trying to unbind it raises an exception. The attribute func_defaults, which you may freely rebind or unbind, refers to the tuple of default values for the optional parameters (or the empty tuple, if the function has no optional parameters).

Docstrings

Another function attribute is the documentation string, also known as the docstring. You may use or rebind a function’s docstring attribute as either func_doc or __doc__. If the first statement in the function body is a string literal, the compiler binds that string as the function’s docstring attribute. A similar rule applies to classes (see "Class documentation strings" on page 85) and modules (see "Module documentation strings" on page 142). Docstrings most often span multiple physical lines, so you normally specify them in triple-quoted string literal form. For example:

  def sum_args(*numbers):
     
”’Accept arbitrary numerical arguments and return their sum.
     
The arguments are zero or more numbers. The result is their sum.”’
     
return sum(numbers)

Documentation strings should be part of any Python code you write. They play a role similar to that of comments in any programming language, but their applicability is wider, since they remain available at runtime. Development environments and tools can use docstrings from function, class, and module objects to remind the programmer how to use those objects. The doctest module (covered in "The doctest Module" on page 454) makes it easy to check that sample code present in docstrings is accurate and correct.

To make your docstrings as useful as possible, you should respect a few simple conventions. The first line of a docstring should be a concise summary of the function’s purpose, starting with an uppercase letter and ending with a period. It should not mention the function’s name, unless the name happens to be a natural-language word that comes naturally as part of a good, concise summary of the function’s operation. If the docstring is multiline, the second line should be empty, and the following lines should form one or more paragraphs, separated by empty lines, describing the function’s parameters, preconditions, return value, and side effects (if any). Further explanations, bibliographical references, and usage examples (which you should check with doctest) can optionally follow toward the end of the docstring.

{mospagebreak title=Other attributes of function objects}

In addition to its predefined attributes, a function object may have other arbitrary attributes. To create an attribute of a function object, bind a value to the appropriate attribute reference in an assignment statement after the def statement executes. For example, a function could count how many times it gets called:

  def counter():
     
counter.count += 1
     
return counter.count
  counter.count = 0

Note that this is not common usage. More often, when you want to group together some state (data) and some behavior (code), you should use the object-oriented mechanisms covered in Chapter 5. However, the ability to associate arbitrary attributes with a function can sometimes come in handy.

The return Statement

The return statement in Python is allowed only inside a function body and can optionally be followed by an expression. When return executes, the function terminates, and the value of the expression is the function’s result. A function returns None if it terminates by reaching the end of its body or by executing a return statement that has no expression (or, of course, by executing return None).

As a matter of style, you should never write a return statement without an expression at the end of a function body. If some return statements in a function have an expression, all return statements should have an expression. return None should only be written explicitly to meet this style requirement. Python does not enforce these stylistic conventions, but your code will be clearer and more readable if you follow them.

Calling Functions

A function call is an expression with the following syntax:

  function-object(arguments)

function-object  may be any reference to a function (or other callable) object; most often, it’s the function’s name. The parentheses denote the function-call operation itself. arguments , in the simplest case, is a series of zero or more expressions separated by commas (,), giving values for the function’s corresponding parameters. When the function call executes, the parameters are bound to the argument values, the function body executes, and the value of the function-call expression is whatever the function returns.

Note that just mentioning a function (or other callable object) does not call it. To call a function (or other object) without arguments, you must use () after the function’s name.

The semantics of argument passing

In traditional terms, all argument passing in Python is by value. For example, if you pass a variable as an argument, Python passes to the function the object (value) to which the variable currently refers, not "the variable itself." Thus, a function cannot rebind the caller’s variables. However, if you pass a mutable object as an argument, the function may request changes to that object because Python passes the object itself, not a copy. Rebinding a variable and mutating an object are totally disjoint concepts. For example:

  def f(x, y):
     
x = 23
     
y.append(42)
 
a = 77
  b = [99]
  f(a, b)
  print a, b           # prints: 77 [99, 42]

The print statement shows that a is still bound to 77. Function f’s rebinding of its parameter x to 23 has no effect on f’s caller, nor, in particular, on the binding of the caller’s variable that happened to be used to pass 77 as the parameter’s value. However, the print statement also shows that b is now bound to [99, 42]. b is still bound to the same list object as before the call, but that object has mutated, as f has appended 42 to that list object. In either case, f has not altered the caller’s bindings, nor can f alter the number 77, since numbers are immutable. However, f can alter a list object, since list objects are mutable. In this example, f mutates the list object that the caller passes to f as the second argument by calling the object’s append method.

{mospagebreak title=Kinds of arguments}

Arguments that are just expressions are known as positional arguments. Each positional argument supplies the value for the parameter that corresponds to it by position (order) in the function definition.

In a function call, zero or more positional arguments may be followed by zero or more named arguments, each with the following syntax:

  identifier=expression

The identifier  must be one of the parameter names used in the def statement for the function. The expression  supplies the value for the parameter of that name. Most built-in functions do not accept named arguments, you must call such functions with positional arguments only. However, all normal functions coded in Python accept named as well as positional arguments, so you may call them in different ways.

A function call must supply, via a positional or a named argument, exactly one value for each mandatory parameter, and zero or one value for each optional parameter. For example:

  def divide(divisor, dividend):
     
return dividend // divisor
  print divide(12, 94)             # prints: 7
  print divide(dividend=94,
divisor=12)                        # prints: 7

As you can see, the two calls to divide are equivalent. You can pass named arguments for readability purposes whenever you think that identifying the role of each argument and controlling the order of arguments enhances your code’s clarity.

A common use of named arguments is to bind some optional parameters to specific values, while letting other optional parameters take default values:

  def f(middle, begin=’init’, end=’finis’):
     
return begin+middle+end
  print f(‘tini’, end=”)          # prints: inittini

Thanks to named argument end=”, the caller can specify a value, the empty string ,  for f’s third parameter, end, and still let f’s second parameter, begin, use its default value, the string ‘init’.

At the end of the arguments in a function call, you may optionally use either or both of the special forms *seq and **dct. If both forms are present, the form with two asterisks must be last. *seq  passes the items of seq  to the function as positional arguments (after the normal positional arguments, if any, that the call gives with the usual syntax). seq may be any iterable. **dct  passes the items of dct  to the function as named arguments, where dct must be a dictionary whose keys are all strings. Each item’s key is a parameter name, and the item’s value is the
argument’s value.

Sometimes you want to pass an argument of the form *seq  or **dct when the parameters use similar forms, as described earlier in "Parameters" on page 71. For example, using the function sum_args defined in that section (and shown again here), you may want to print the sum of all the values in dictionary d . This is easy with *seq :

  def sum_args(*numbers):
     
return sum(numbers)
  print sum_args(*d.values())

(Of course, in this case, print sum(d.values()) would be simpler and more direct!)

However, you may also pass arguments of the form *seq  or **dct  when calling a function that does not use the corresponding forms in its parameters. In that case, of course, you must ensure that iterable seq  has the right number of items, or, respectively, that dictionary dct  uses the right names as its keys; otherwise, the call operation raises an exception.

Please check back next week for the conclusion to this article.

Google+ Comments

Google+ Comments