程式扎記: [Quick Python] 9. Functions

標籤

2012年2月8日 星期三

[Quick Python] 9. Functions



Preface : 
This chapter assumes you’re familiar with function definitions in at least one other computer language and with the concepts that correspond to function definitions, arguments, parameters, and so forth. This chapter covers : 
* Defining functions
* Using function parameters
* Passing mutable objects as parameters
* Understanding local and global variables
* Creating and using lambda expressions
* Using decorators

Basic function definitions : 
The basic syntax for a Python function definition is : 
  1. def name(parameter1, parameter2, . . .):  
  2.     body  
As it does with control structures, Python uses indentation to delimit the body of the function definition. The following simple example puts the factorial code from a previous section into a function body, so we can call a fact function to obtain the factorial of a number : 
 

The second line (1) is an optional documentation string or docstring. You can obtain its value by printing fact.__doc__. The intention of docstrings is to describe the external behavior of a function and the parameters it takes, whereas comments should document internal information about how the code works. Docstrings are strings that immediately follow the first line of a function definition and are usually triple quoted to allow for multiline descriptions. Browsing tools are available that extract the first line of document strings. It’s a standard practice for multiline documentation strings to give a synopsis of the function in the first line, follow this with a blank second line, and end with the rest of the information. This line shows the value after the return is sent back to the code calling the function (2). 
 

Function parameter options : 
Most functions need parameters, and each language has its own specifications for how function parameters are defined. Python is flexible and provides three options for defining function parameters. These are outlined in this section. 

- Positional parameters 
The simplest way to pass parameters to a function in Python is by position. In the first line of the function, you specify definition variable names for each parameter; when the function is called, the parameters used in the calling code are matched to the function’s parameter variables based on their order. The following function computes x to the power of y : 
 

This method requires that the number of parameters used by the calling code exactly match the number of parameters in the function definition, or a TypeError exception will be raised : 
>>> power(3)
Traceback (most recent call last):
File "", line 1, in
TypeError: power() takes exactly 2 arguments (1 given)

 

The following function also computes x to the power of y. But if y isn’t given in a call to the function, the default value of 2 is used : 
 

- Passing arguments by parameter name 
You can also pass arguments into a function using the name of the corresponding function parameter, rather than its position. Continuing with the previous interactive example, we can type : 
>>> power(2, 3) # 2^3
8
>>> power(3, 2) # 3^2
9
>>> power(y=2, x=3) # x^y -> 3^2
9

Because the arguments to power in the final invocation of it are named, their order is irrelevant; the arguments are associated with the parameters of the same name in the definition of power(), and we get back 3^2. This type of argument passing is called keyword passing

Keyword passing, in combination with the default argument capability of Python functions, can be highly useful when you’re defining functions with large numbers of possible arguments, most of which have common defaults. For example, consider a function that’s intended to produce a list with information about files in the current directory and that uses Boolean arguments to indicate whether that list should include information such as file size, last modified date, and so forth, for each file. We can define such a function along these lines : 
  1. def list_file_info(size=False, create_date=False, mod_date=False, ...):  
  2.     ...get file names...  
  3.     if size:  
  4.         # code to get file sizes goes here  
  5.     if create_date:  
  6.         # code to get create dates goes here  
  7.     .  
  8.     .  
  9.     .  
  10.     return fileinfostructure  
and then call it from other code using keyword argument passing to indicate that we want only certain information (in this example, the file size and modification date but not the creation date): 
fileinfo = list_file_info(size=True, mod_date=True)

This type of argument handling is particularly suited for functions with very complex behavior, and one place such functions occur is in graphical user interfaces. If you ever use the Tkinter package to build GUIs in Python, you’ll find that the use of optional, keyword-named arguments like this is invaluable. 

- Variable numbers of arguments 
Python functions can also be defined to handle variable numbers of arguments. You can do this two different ways. One way handles the relatively familiar case where you wish to collect an unknown number of arguments at the end of the argument list into a list. The other method can collect an arbitrary number of keyword-passed arguments, which have no correspondingly named parameter in the function parameter list, into a dictionary. These two mechanisms are discussed next. 

DEALING WITH AN INDEFINITE NUMBER OF POSITIONAL ARGUMENTS 
Prefixing the final parameter name of the function with a * causes all excess non-keyword arguments in a call of a function (that is, those positional arguments not assigned to another parameter) to be collected together and assigned as a tuple to the given parameter. Here’s a simple way to implement a function to find the maximum in a list of numbers. 

First, implement the function : 
 

Now, test out the behavior of the function : 
>>> maximum(3, 2, 8)
8
>>> maximum(1, 5, 9, -2, 2)
9

DEALING WITH AN INDEFINITE NUMBER OF ARGUMENTS PASSED BY KEYWORD 
An arbitrary number of keyword arguments can also be handled. If the final parameter in the parameter list is prefixed with **, it will collect all excess keyword-passed arguments into a dictionary. The index for each entry in the dictionary will be the keyword (parameter name) for the excess argument. The value of that entry is the argument itself. An argument passed by keyword is excess in this context if the keyword by which it was passed doesn’t match one of the parameter names of the function. For example : 
 

Trying out this function in an interactive session reveals that it can handle arguments passed in under the keywords foo and bar, even though these aren’t parameter names in the function definition : 
>>> example_fun(2, y="1", foo=3, bar=4) # variable foo, bar is not defined in function argument list
x: 2, y: 1, keys in 'other': ['foo', 'bar']
The total of values in 'other' is 7

- Mixing argument-passing techniques 
It’s possible to use all of the argument-passing features of Python functions at the same time, although it can be confusing if not done with care. Rules govern what you can do. See the documentation for the details. 

Mutable objects as arguments : 
Arguments are passed in by object reference. The parameter becomes a new reference to the object. For immutable objects (such as tuples, strings, and numbers), what is done with a parameter has no effect outside the function. But if you pass in a mutable object (for example, a list, dictionary, or class instance), any change made to the object will change what the argument is referencing outside the function. Reassigning the parameter doesn’t affect the argument, as shown in figures 9.1 and 9.2 : 
 
 
Figure 9.2 At the end of function f(), y (list1 inside the function) has been changed internally, whereas n and list2 refer to different objects. 

Figures 9.1 and 9.2 illustrate what happens when function f is called. The variable x isn’t changed because it’s immutable. Instead, the function parameter n is set to refer to the new value of 6. Likewise, variable z is unchanged because inside function f, its corresponding parameter list2 was set to refer to a new object, [4, 5, 6]. Only ysees a change because the actual list it points to was changed. 

Local, nonlocal, and global variables : 
Let’s return to our definition of fact() from the beginning of this chapter : 
 

Both the variables r and n are local to any particular call of the factorial function; changes to them made when the function is executing have no effect on any variables outside the function. Any variables in the parameter list of a function, and any variables created within a function by an assignment (like r = 1 in fact), are local to the function. 

You can explicitly make a variable global by declaring it so before the variable is used, using the global statement. Global variables can be accessed and changed by the function. They exist outside the function and can also be accessed and changed by other functions that declare them global or by code that’s not within a function. Let’s look at an example to see the difference between local and global variable : 
>>> def fun():
... global a
... a = 1
... b = 2
...

This defines a function that treats a as a global variable and b as a local variable and attempts to modify both a and b

Now, test this function : 
>>> a = "one"
>>> b = "two"
>>> fun()
>>> a a is defined global in fun(). So a is modified and variable a outside fun() will be changed too!
1
>>> b
'two'

The assignment to a within fun() is an assignment to the global variable a also existing outside of fun. Because a is designated global in fun(), the assignment modifies that global variable to hold the value 1 instead of the value "one". The same isn’t true for b—the local variable called b inside fun starts out referring to the same value as the variable b outside of fun, but the assignment causes b to point to a new value that’s local to the function fun. 

Similar to the global statement is the nonlocal statement, which causes an identifier to refer to a previously bound variable in the closest enclosing scope. We’ll discuss scopes and namespaces in more detail in the next chapter, but the point is that global is used for a top-level variable, whereas nonlocal can refer to any variable in an enclosing scope, as the example in listing 9.1 illustrates : 
- Listing 9.1 File nonlocal.py

When run, this code prints the following : 
>python nonlocal.py
top level-> g_var: 0 nl_var: 0
in test-> g_var: 0 nl_var: 2
in inner_test-> g_var: 1 nl_var: 4
in test-> g_var: 1 nl_var: 4
top level-> g_var: 1 nl_var: 0

Note that the value of the top-level nl_var hasn’t been affected, which would happen if inner_test() contained the line global nl_var

The bottom line is that if you want to assign to a variable existing outside a function, you must explicitly declare that variable to be nonlocal or global. But if you’re accessing a variable that exists outside the function, you don’t need to declare it nonlocal or globalIf Python can’t find a variable name in the local function scope, it will attempt to look up the name in the global scope. Hence, accesses to global variables will automatically be sent through to the correct global variable. Personally, I don’t recommend using this shortcut. It’s much clearer to a reader if all global variables are explicitly declared as global. Further, you probably want to limit the use of global variables within functions to only rare occasions. 

Assigning functions to variables : 
Functions can be assigned, like other Python objects, to variables, as shown in the following example : 
 

You can place them in lists, tuples, or dictionaries : 
>>> t = {'FtoK': f_to_kelvin, 'CtoK': c_to_kelvin} # (1)
>>> t['FtoK'](32)
273.15
>>> t['CtoK'](0)
273.15

A variable that refers to a function can be used in exactly the same way as the function (1). This last example shows how you can use a dictionary to call different functions by the value of the strings used as keys. This is a common pattern in situations where different functions need to be selected based on a string value, and in many cases it takes the place of the switch structure found in languages like C and Java. 

lambda expressions : 
Short functions like those you just saw can also be defined using lambda expressions of the form : 
lambda parameter1parameter2, . . .: expression

lambda expressions are anonymous little functions that you can quickly define inline. Often, a small function needs to be passed to another function, like the key function used by a list’s sort method. In such cases, a large function is usually unnecessary, and it would be awkward to have to define the function in a separate place from where it’s used. Our dictionary in the previous subsection can be defined all in one place with : 
 

This defines lambda expressions as values of the dictionary. Note that lambda expressions don’t have a return statement, because the value of the expression is automatically returned

Generator functions : 
generator function is a special kind of function that you can use to define your own iterators. When you define a generator function, you return each iteration’s value using the yield keyword. When there are no more iterations, an empty return statement or flowing off the end of the function ends the iterations. Local variables in a generator function are saved from one call to the next, unlike in normal functions : 
 

Note that this generator function has a while loop that limits the number of times the generator will execute. Depending on how it's used, a generator that doesn’t have some condition to halt it could cause an endless loop when called. 

You can also use generator functions with in to see if a value is in the series that the generator produces : 
 

Decorators : 
Because functions are first-class objects in Python, they can be assigned to variables, as you’ve seen. Functions can be passed as arguments to other functions and passed back as return values from other functions. 

For example, it’s possible to write a Python function that takes another function as its parameter, wrap it in another function that does something related, and then return the new function. This new combination can be used instead of the original function: 
 

A decorator is syntactic sugar for this process and lets you wrap one function inside another with a one-line addition. This still gives you exactly the same effect as the previous code, but the resulting code is much cleaner and easier to read. 

Very simply, using a decorator involves two parts: defining the function that will be wrapping or "decorating" other functions and then using an @ followed by the decorator immediately before the wrapped function is defined. The decorator function should take a function as a parameter and return a function, as follows : 
 

The decorate() function prints the name of the function it’s wrapping when the function is defined (1). When it’s finished, the decorator returns the wrapped function (2).myfunction is decorated using @decorate (3). The wrapped function is called after the decorator function has completed (4). 

Using a decorator to wrap one function in another can be handy for a number of purposes. In web frameworks such as Django, decorators are used to make sure a user is logged in before executing a function; and in graphics libraries, decorators can be used to register a function with the graphics framework. 

Supplement : 
[Python 學習筆記] 函式、類別與模組 : 函式 (def 陳述句) 
[Python 學習筆記] 函式、類別與模組 : 函式 (lambda 運算式) 
[Python 學習筆記] 函式、類別與模組 : 函式 (yield 產生器) 
[Python 學習筆記] 進階議題 : 修飾器 (函式修飾器)

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!