Section 14.3. Executable Object Statements and Built-in Functions


14.3. Executable Object Statements and Built-in Functions

Python provides a number of BIFs supporting callables and executable objects, including the exec statement. These functions let the programmer execute code objects as well as generate them using the compile() BIF. They are listed in Table 14.5.

Table 14.5. Executable Object Statements and Built-in Functions

Built-in Function or Statement

Description

callable(obj)

Returns true if obj is callable and False otherwise

compile(string, file, type)

Creates a code object from string of type type; file is where the code originates from (usually set to "")

eval(obj, globals=globals(), locals=locals())

Evaluates obj, which is either an expression compiled into a code object or a string expression; global and/or local namespace may also be provided

exec obj

Executes obj,a single Python statement or set of statements, either in code object or string format; obj may also be a file object (opened to a valid Python script)

input(prompt='')

Equivalent to eval(raw_input(prompt=''))


14.3.1. callable()

callable() is a Boolean function that determines if an object type can be invoked via the function operator ( ( ) ). It returns true if the object is callable and False otherwise (1 and 0, respectively, for Python 2.2 and earlier). Here are some sample objects and what callable returns for each type:

    >>> callable(dir)              # built-in function     True     >>> callable(1)                # integer     False     >>> def foo(): pass     ...     >>> callable(foo)              # user-defined function     True     >>> callable('bar')            # string     False     >>> class C(object): pass     ...     >>> callable(C)                # class     True


14.3.2. compile()

compile() is a function that allows the programmer to generate a code object on the fly, that is, during runtime. These objects can then be executed or evaluated using the exec statement or eval() BIF. It is important to bring up the point that both exec and eval() can take string representations of Python code to execute. When executing code given as strings, the process of byte-compiling such code must occur every time. The compile() function provides a one-time byte-code compilation of code so that the precompile does not have to take place with each invocation. Naturally, this is an advantage only if the same pieces of code are executed more than once. In these cases, it is definitely better to precompile the code.

All three arguments to compile() are required, with the first being a string representing the Python code to compile. The second string, although required, is usually set to the empty string. This parameter represents the file name (as a string) where this code object is located or can be found. Normal usage is for compile() to generate a code object from a dynamically generated string of Python codecode that obviously does not originate from an existing file.

The last argument is a string indicating the code object type. There are three possible values:

'eval'

Evaluatable expression [to be used with eval()]

'single'

Single executable statement [to be used with exec]

'exec'

Group of executable statements [to be used with exec]


Evaluatable Expression

>>> eval_code = compile('100 + 200', '', 'eval') >>> eval(eval_code) 300


Single Executable Statement

>>> single_code = compile('print"Hello world!"', '', 'single') >>> single_code <code object ? at 120998, file "", line 0> >>> exec single_code Hello world!


Group of Executable Statements

>>> exec_code = compile(""" ... req = input('Count how many numbers? ') ... for eachNum in range(req): ...     print eachNum ... """, '', 'exec') >>> exec exec_code Count how many numbers? 6 0 1 2 3 4 5


In the final example, we see input() for the first time. Since the beginning, we have been reading input from the user using raw_input(). The input() BIF is a shortcut function that we will discuss later in this chapter. We just wanted to tease you with a sneak preview.

14.3.3. eval()

eval() evaluates an expression, either as a string representation or a pre-compiled code object created via the compile() built-in. This is the first and most important argument to eval()... it is what you want to execute.

The second and third parameters, both optional, represent the objects in the global and local namespaces, respectively. If provided, globals must be a dictionary. If provided, locals can be any mapping object, e.g., one that implements the __getitem__() special method. (Before 2.4, locals was required to be a dictionary.) If neither of these are given, they default to objects returned by globals() and locals(), respectively. If only a globals dictionary is passed in, then it is also passed in as locals.

Okay, now let us take a look at eval():

        >>> eval('932')         932         >>> int('932')         932


We see that in this case, both eval() and int() yield the same result: an integer with the value 932. The paths they take are somewhat different, however. The eval() BIF takes the string in quotes and evaluates it as a Python expression. The int() BIF takes a string representation of an integer and converts it to an integer. It just so happens that the string consists exactly of the string 932, which as an expression yields the value 932, and that 932 is also the integer represented by the string "932." Things are not the same, however, when we use a pure string expression:

        >>> eval('100 + 200')         300         >>> int('100 + 200')         Traceback (innermost last):           File "<stdin>", line 1, in ?         ValueError: invalid literal for int(): 100 + 200


In this case, eval() takes the string and evaluates "100 + 200" as an expression, which, after performing integer addition, yields the value 300. The call to int() fails because the string argument is not a string representation of an integerthere are invalid literals in the string, namely, the spaces and "+" character.

One simple way to envision how the eval() function works is to imagine that the quotation marks around the expression are invisible and think, "If I were the Python interpreter, how would I view this expression?" In other words, how would the interpreter react if the same expression were entered interactively? The output after pressing the RETURN or ENTER key should be the same as what eval() will yield.

14.3.4. exec

Like eval(), the exec statement also executes either a code object or a string representing Python code. Similarly, precompiling oft-repeated code with compile() helps improve performance by not having to go through the byte-code compilation process for each invocation. The exec statement takes exactly one argument, as indicated here with its general syntax:

execobj


The executed object (obj) can be either a single statement or a group of statements, and either may be compiled into a code object (with "single" or "exec," respectively) or it can be just the raw string. Below is an example of multiple statements being sent to exec as a single string:

        >>> exec """         ...x = 0         ...print  'x is currently:', x         ...while  x < 5:         ...   x += 1         ...   print 'incrementing x to:', x         ..."""         x is currently: 0         incrementing x to: 1         incrementing x to: 2         incrementing x to: 3         incrementing x to: 4         incrementing x to: 5


Finally, exec can also accept a valid file object to a (valid) Python file. If we take the code in the multi-line string above and create a file called xcount.py, then we could also execute the same code with the following:

        >>> f = open('xcount.py')      # open the file         >>> exec f                     # execute the file         x is currently: 0         incrementing x to: 1         incrementing x to: 2         incrementing x to: 3         incrementing x to: 4         incrementing x to: 5         >>> exec f                     # try execution again         >>>                            # oops, it failed... why?


Note that once execution has completed, a successive call to exec fails. Well, it doesn't really fail ... it just doesn't do anything, which may have caught you by surprise. In reality, exec has read all the data in the file and is sitting at the end-of-file (EOF). When exec is called again with the same file object, there is no more code to execute, so it does not do anything, hence the behavior seen above. How do we know that it is at EOF?

We use the file object's tell() method to tell us where we are in the file and then use os.path.getsize() to tell us how large our xcount.py script was. As you can see, there is an exact match:

        >>> f.tell()                         # where are we in the file?         116         >>> f.close()                       # close the file         >>> from os.path import getsize         >>> getsize('xcount.py')            # what is the file size?         116


If we really want to run it again without closing and reopening the file, you can just seek() to the beginning of the file and call exec again. For example, let us assume that we did not call f.close() yet. Then we can do the following:

        >>> f.seek(0)                  # rewind to beginning         >>> exec f         x is currently: 0         incrementing x to: 1         incrementing x to: 2         incrementing x to: 3         incrementing x to: 4         incrementing x to: 5         >>> f.close()


14.3.5. input()

The input() BIF is the same as the composite of eval() and raw_input(), equivalent to eval(raw_input()). Like raw_input(), input() has an optional parameter, which represents a string prompt to display to the user. If not provided, the string has a default value of the empty string.

Functionally, input() differs from raw_input() because raw_input() always returns a string containing the user's input, verbatim. input() performs the same task of obtaining user input; however, it takes things one step further by evaluating the input as a Python expression. This means that the data returned by input() are a Python object, the result of performing the evaluation of the input expression.

One clear example is when the user inputs a list. raw_input() returns the string representation of a list, while input() returns the actual list:

        >>> aString = raw_input('Enter a list: ')         Enter a list: [ 123, 'xyz', 45.67 ]         >>> aString         "[ 123, 'xyz', 45.67 ]"         >>> type(aString)         <type 'str'>


The above was performed with raw_input(). As you can see, everything is a string. Now let us see what happens when we use input() instead:

        >>> aList = input('Enter a list: ')         Enter a list: [ 123, 'xyz', 45.67 ]         >>> aList         [123, 'xyz', 45.67]         >>> type(aList)         <type 'list'>


Although the user input a string, input() evaluates that input as a Python object and returns the result of that expression.

14.3.6. Using Python to Generate and Execute Python Code at Runtime

In this section, we will look at two examples of Python scripts that take Python code as strings and execute them at runtime. The first example is more dynamic, but the second shows off function attributes at the same time.

Creating Code at Runtime and Executing It

The first example is loopmake.py script, which is a simple computer-aided software engineering (CASE) that generates and executes loops on-the-fly. It prompts the user for the various parameters (i.e., loop type (while or for), type of data to iterate over [numbers or sequences]), generates the code string, and executes it.

Example 14.1. Dynamically Generating and Executing Python Code (loopmake.py)

1   #!/usr/bin/env python 2 3   dashes = '\n' + '-' * 50 # dashed line 4   exec_dict = { 5 6   'f': """                    # for loop 7   for %s in %s: 8       print %s 9   """, 10 11  's': """                    # sequence while loop 12  %s  = 0 13  %s  = %s 14  while %s < len(%s): 15      print %s[%s] 16      %s = %s + 1 17  """, 18 19  'n': """                    # counting while loop 20  %s = %d 21  while %s < %d: 22      print %s 23      %s = %s + %d 24  """ 25  } 26 27  def main(): 28 29      ltype = raw_input('Loop type? (For/While) ') 30      dtype = raw_input('Data type? (Number/Seq) ') 31 32      if dtype == 'n': 33          start = input('Starting value? ') 34          stop = input('Ending value (non-inclusive)? ') 35          step = input('Stepping value? ') 36          seq = str(range(start, stop, step)) 37 38      else: 39          seq = raw_input('Enter sequence: ') 40 41      var = raw_input('Iterative variable name? ') 42 43      if ltype == 'f': 44          exec_str = exec_dict['f'] % (var, seq, var) 45 46      elif ltype == 'w': 47          if dtype == 's': 48              svar = raw_input('Enter sequence name? ') 49              exec_str = exec_dict['s'] % \ 50      (var, svar, seq, var, svar, svar, var, var, var) 51 52        elif dtype == 'n': 53            exec_str = exec_dict['n'] % \ 54     (var, start, var, stop, var, var, var, step) 55 56     print dashes 57     print 'Your custom-generated code:' + dashes 58     print exec_str + dashes 59     print 'Test execution of the code:' + dashes 60     exec exec_str 61     print dashes 62 63 if __name__ == '__main__': 64     main()

Here are a few example executions of this script:

      % loopmake.py  Loop type? (For/While) f  Data type? (Number/Sequence) n  Starting value? 0  Ending value (non-inclusive)? 4  Stepping value? 1  Iterative variable name? counter  --------------------------------------------------  The custom-generated code for you is:  --------------------------------------------------  for counter in [0, 1, 2, 3]:      print counter  --------------------------------------------------  Test execution of the code:  --------------------------------------------------  0  1  2  3  --------------------------------------------------  % loopmake.py  Loop type? (For/While) w  Data type? (Number/Sequence) n  Starting value? 0  Ending value (non-inclusive)? 4  Stepping value? 1  Iterative variable name? counter  --------------------------------------------------  Your custom-generated code:  --------------------------------------------------  counter = 0  while counter < 4:      print counter      counter = counter + 1  --------------------------------------------------  Test execution of the code:  --------------------------------------------------  0  1  2  3  --------------------------------------------------  % loopmake.py  Loop type? (For/While) f  Data type? (Number/Sequence) s  Enter sequence: [932, 'grail', 3.0, 'arrrghhh']  Iterative variable name? eachItem  --------------------------------------------------  Your custom-generated code:  --------------------------------------------------  for eachItem in [932, 'grail', 3.0, 'arrrghhh']:      print eachItem -------------------------------------------------- Test execution of the code: -------------------------------------------------- 932 grail 3.0 arrrghhh -------------------------------------------------- % loopmake.py Loop type? (For/While) w Data type? (Number/Sequence) s Enter sequence: [932, 'grail', 3.0, 'arrrghhh'] Iterative variable name? eachIndex Enter sequence name? myList ------------------------------------------------- Your custom-generated code: -------------------------------------------------- eachIndex = 0 myList = [932, 'grail', 3.0, 'arrrghhh'] while eachIndex < len(myList):     print myList[eachIndex]     eachIndex = eachIndex + 1 -------------------------------------------------- Test execution of the code: -------------------------------------------------- 932 grail 3.0 arrrghhh --------------------------------------------------


Line-by-Line Explanation
Lines 125

In this first part of the script, we are setting up two global variables. The first is a static string consisting of a line of dashes (hence the name) and the second is a dictionary of the skeleton code we will need to use for the loops we are going to generate. The keys are "f" for a for loop, "s" for a while loop iterating through a sequence, and "n" for a counting while loop.

Lines 2730

Here we prompt the user for the type of loop he or she wants and what data types to use.

Lines 3236

Numbers have been chosen; they provide the starting, stopping, and incremental values. In this section of code, we are introduced to the input() BIF for the first time. As we shall see in Section 14.3.5, input() is similar to raw_input() in that it prompts the user for string input, but unlike raw_input(), input() also evaluates the input as a Python expression, rendering a Python object even if the user typed it in as a string.

Lines 3839

A sequence was chosen; enter the sequence here as a string.

Line 41

Get the name of the iterative loop variable that the user wants to use.

Lines 4344

Generate the for loop, filling in all the customized details.

Lines 4650

Generate a while loop which iterates through a sequence.

Lines 5254

Generate a counting while loop.

Lines 5661

Output the generated source code as well as the resulting output from execution of the aforementioned generated code.

Lines 6364

Execute main() only if this module was invoked directly.

To keep the size of this script to a manageable size, we had to trim all the comments and error checking from the original script. You can find both the original as well as an alternate version of this script on the book's Web site.

The extended version includes extra features such as not requiring enclosing quotation marks for string input, default values for input data, and detection of invalid ranges and identifiers; it also does not permit built-in names or keywords as variable names.

Conditionally Executing Code

Our second example highlights the usefulness of function attributes introduced back in Chapter 11, "Functions", inspired by the example in PEP 232. Let us assume that you are a software QA developer encouraging your engineers to install either regression testers or regression instruction code into the main source but do not want the testing code mixed with the production code. You can tell your engineers to create a string representing the testing code. When your test framework executes, it checks to see if that function has defined a test body, and if so, (evaluates and) executes it. If not, it will skip and continue as normal.

Example 14.2. Function Attributes (funcAttrs.py)

Calling sys.exit() causes the Python interpreter to quit. Any integer argument to exit() will be returned to the caller as the exit status, which has a default value of 0.

1  #!/usr/bin/env python 2 3  def foo(): 4      return True 5 6  def bar(): 7      'bar() does not do much' 8      return True 9 10  foo.__doc__ = 'foo() does not do much' 11  foo.tester = ''' 12  if foo(): 13      print 'PASSED' 14  else: 15      print 'FAILED' 16  ''' 17 18  for eachAttr in dir(): 19      obj = eval(eachAttr) 20      if isinstance(obj, type(foo)): 21          if hasattr(obj, '__doc__'): 22              print '\nFunction "%s" has a doc                 string:\n\t%s' % (eachAttr, obj.__doc__) 23          if hasattr(obj, 'tester'): 24            print 'Function "%s" has a tester... execut                 ing' % eachAttr 25              exec obj.tester 26          else: 27             print 'Function "%s" has no tester... skip                 ping' % eachAttr 28      else: 29      print '"%s" is not a function' % eachAttr

Lines 18

We define foo() and bar() in the first part of this script. Neither function does anything other than return true. The one difference between the two is that foo() has no attributes while bar() gets a documentation string.

Lines 1016

Using function attributes, we add a doc string and a regression or unit tester string to foo(). Note that the tester string is actually comprised of real lines of Python code.

Lines 1829

Okay, the real work happens here. We start by iterating through the current (global) namespace using the dir() BIF. It returns a list of the object names. Since these are all strings, we need line 19 to turn them into real Python objects.

Other than the expected system variables, i.e., __builtins__, we expect our functions to show up. We are only interested in functions; the code in line 20 will let us skip any non-function objects encountered. Once we know we have a function, we check to see if it has a doc string, and if so, we display it.

Lines 2327 perform some magic. If the function has a tester attribute, then execute it, otherwise let the user know that no unit tester is available. The last few lines display the names of non-function objects encountered.

Upon executing the script, we get the following output:

  $ python funcAttr.py   "__builtins__" is not a function   "__doc__" is not a function   "__file__" is not a function   "__name__" is not a function   Function "bar" has a doc string:           bar() does not do much   Function "bar" has no tester... skipping   Function "foo" has a doc string:           foo() does not do much   Function "foo" has a tester... executing   PASSED




Core Python Programming
Core Python Programming (2nd Edition)
ISBN: 0132269937
EAN: 2147483647
Year: 2004
Pages: 334
Authors: Wesley J Chun

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net