文章来自《Python cookbook》.

翻译仅仅是为了个人学习,其它商业版权纠纷与此无关!

-- 0.706 [2004-10-03 17:15:10]

Replacing Python Code with the Results of Executing That Code 把Python代码替换成其运行结果

Credit: Joel Gould

问题 Problem

You have a template string that may include embedded Python code, and you need a copy of the template in which any embedded Python code is replaced by the results of executing that code.

你有一个模板字符串,可能内嵌有Python代码,而且你需要生成模板的副本,并将其中的所有python代码用其运行结果代替。

解决 Solution

This recipe exploits the ability of the standard function re.sub to call a user-supplied replacement function for each match and to substitute the matched substring with the replacement function's result:

这一份食谱开发利用了标准的re.sub 函数的能力,re.sub可以为每个匹配子串调用一个由使用者提供的替换函数,并且用替换函数的返回结果替换被匹配的子串:

   1 import re
   2 import sys
   3 import string
   4 
   5 def runPythonCode(data, global_dict={}, local_dict=None, errorLogger=None):
   6     """ Main entry point to the replcode module """
   7 
   8     # Encapsulate evaluation state and error logging into an instance:
   9     eval_state = EvalState(global_dict, local_dict, errorLogger)
  10 
  11     # Execute statements enclosed in [!! .. !!]; statements may be nested by
  12     # enclosing them in [1!! .. !!1], [2!! .. !!2], and so on:
  13     data = re.sub(r'(?s)\[(?P<num>\d?)!!(?P<code>.+?)!!(?P=num)\]',
  14         eval_state.exec_python, data)
  15 
  16     # Evaluate expressions enclosed in [?? .. ??]:
  17     data = re.sub(r'(?s)\[\?\?(?P<code>.+?)\?\?\]',
  18         eval_state.eval_python, data)
  19 
  20     return data
  21 
  22 class EvalState:
  23     """ Encapsulate evaluation state, expose methods to execute/evaluate """
  24 
  25     def _ _init_ _(self, global_dict, local_dict, errorLogger):
  26         self.global_dict = global_dict
  27         self.local_dict = local_dict
  28         if errorLogger:
  29             self.errorLogger = errorLogger
  30         else:
  31             # Default error "logging" writes error messages to sys.stdout
  32             self.errorLogger = sys.stdout.write
  33 
  34         # Prime the global dictionary with a few needed entries:
  35         self.global_dict['OUTPUT'] = OUTPUT
  36         self.global_dict['sys'] = sys
  37         self.global_dict['string'] = string
  38         self.global_dict['_ _builtins_ _'] = _ _builtins_ _
  39 
  40     def exec_python(self, result):
  41         """ Called from the 1st re.sub in runPythonCode for each block of
  42         embedded statements. Method's result is OUTPUT_TEXT (see also the OUTPUT
  43         function later in the recipe). """
  44 
  45         # Replace tabs with four spaces; remove first line's indent from all lines
  46         code = result.group('code')
  47         code = string.replace(code, '\t', '    ')
  48         result2 = re.search(r'(?P<prefix>\n[ ]*)[#a-zA-Z0-9''"]', code)
  49         if not result2:
  50             raise ParsingError, 'Invalid template code expression: ' + code
  51         code = string.replace(code, result2.group('prefix'), '\n')
  52         code = code + '\n'
  53 
  54         try:
  55             self.global_dict['OUTPUT_TEXT'] = ''
  56             if self.local_dict:
  57                 exec code in self.global_dict, self.local_dict
  58             else:
  59                 exec code in self.global_dict
  60             return self.global_dict['OUTPUT_TEXT']
  61         except:
  62             self.errorLogger('\n---- Error parsing statements: ----\n')
  63             self.errorLogger(code)
  64             self.errorLogger('\n------------------------\n')
  65             raise
  66 
  67     def eval_python(self, result):
  68         """ Called from the 2nd re.sub in runPythonCode for each embedded
  69         expression. The method's result is the expr's value as a string. """
  70         code = result.group('code')
  71         code = string.replace(code, '\t', '    ')
  72 
  73         try:
  74             if self.local_dict:
  75                 result = eval(code, self.global_dict, self.local_dict)
  76             else:
  77                 result = eval(code, self.global_dict)
  78             return str(result)
  79         except:
  80             self.errorLogger('\n---- Error parsing expression: ----\n')
  81             self.errorLogger(code)
  82             self.errorLogger('\n------------------------\n')
  83             raise
  84 
  85 def OUTPUT(data):
  86     """ May be called from embedded statements: evaluates argument 'data' as
  87     a template string, appends the result to the global variable OUTPUT_TEXT """
  88 
  89     # a trick that's equivalent to sys._getframe in Python 2.0 and later but
  90     # also works on older versions of Python...:
  91     try: raise ZeroDivisionError
  92     except ZeroDivisionError:
  93         local_dict = sys.exc_info(  )[2].tb_frame.f_back.f_locals
  94         global_dict  = sys.exc_info(  )[2].tb_frame.f_back.f_globals
  95 
  96     global_dict['OUTPUT_TEXT'] = global_dict['OUTPUT_TEXT'] + runPythonCode(
  97         data, global_dict, local_dict)

讨论 Discussion

This recipe was originally designed for dynamically creating HTML.It takes a template, which is a string that may include embedded Python statements and expressions, and returns another string, in which any embedded Python is replaced with the results of executing that code.I originally designed this code to build my home page.Since then, I have used the same code for a CGI-based web site and for a documentation-generation program.

这一份食谱本来是为了动态地创建HTML而设计的。它读入一个模板,该模板是可能包括内嵌Python语句和表达式的字符串,并返回另一个字符串,在其中任何内嵌Python代码都被代码的运行结果所替换。我本来设计了这一段代码用来建立我的主页。在那以后,我已经用相同的代码实现了一个以CGI为基础的网站和一个文档生成程序。

Templating, which is what this recipe does, is a very popular task in Python, for which you can find any number of existing Pythonic solutions.Many templating approaches aim specifically at the task of generating HTML (or, occasionally, other forms of structured text).Others, such as this recipe, are less specialized, and thus can be simultaneously wider in applicability and simpler in structure.However, they do not offer HTML-specific conveniences.See Recipe 3.23 for another small-scale approach to templating with general goals that are close to this one's but are executed in a rather different style.

模板,如这一份食谱所做的,是Python的一件非常流行的工作,你能找到若干种已存的Pythonic解决方案。多数模板方法的目的特定于产生HTML(或,有时候,产生其他形式的结构化本文)。另外一些,像是这一份食谱,是较少特定化的,并且如此能适用于比较宽的应用,而且结构上是比较简单的。然而,它们没有为那些HTML特性提供便利。食谱3.23中可以看到另外一个小规模的通用模板方式,它的目标与这里相近,但是用一种相当不同的风格中运行。

Usually, the input template string is taken directly from a file, and the output expanded string is written to another file.When using CGI, the output string can be written directly to sys.stdout, which becomes the HTML displayed in the user's browser when it visits the script.

通常,输入的模板字符串直接取自一个文件,而且输出的扩展后的字符串被写到另外一个文件中。当使用CGI的时候,输出字符串可以被直接地写到sys.stdout,并在用户访问脚本的时候,变成在用户浏览器中显示的HTML。

By passing in a dictionary, you control the global namespace in which the embedded Python code is run.If you want to share variables with the embedded Python code, insert the names and values of those variables into the global dictionary before calling runPythonCode.When an uncaught exception is raised in the embedded code, a dump of the code being evaluated is first written to stdout (or through the errorLogger function argument, if specified) before the exception is propagated to the routine that called runPythonCode.

通过传入一个字典,你可以控制内嵌的python代码运行时的全局名字空间。如果你想要同内嵌的python代码共享变量,就要在调用runPythonCode之前,把那些变量的名字和值插入进global字典中。当内嵌的python代码抛出未被捕获的异常时,在异常被传播到调用runPythonCode的例程之前,被求值的代码首先被写到stdout( 或通过errorLogger函数参数, 如果指定的话) 。

This recipe handles two different types of embedded code blocks in template strings.Code inside [?? ??] is evaluated. Such code should be an expression and should return a string, which will be used to replace the embedded Python code. Code inside [!! !!] is executed. That code is a suite of statements, and it is not expected to return anything. However, you can call OUTPUT from inside embedded code, to specify text that should replace the executed Python code. This makes it possible, for example, to use loops to generate multiple blocks of output text.

这一份食谱处理模板字符串中两种不同类型的内嵌代码块。[ ?? ?? ]中的代码被求值。这样的密码应该是表达式并且应该返回用来替换内嵌代码的字符串。[ !! !! ]中的代码被运行,那些代码是一组语句,而且没期望它返回任何东西。然而,你能在内嵌代码调用OUTPUT, 指定应该替换被运行的python代码的文本。这使它可能, 举例来说, 使用循环产生多个文本块的输出。

Here is an interactive-interpreter example of using this replcode.py module:

这是一个使用replcode.py模块的交互解释例子:

>>> import replcode
>>> input_text = """
...     Normal line.
...     Expression [?? 1+2 ??].
...     Global variable [?? variable ??].
...     [!!
...         def foo(x):
...                 return x+x !!].
...     Function [?? foo('abc') ??].
...     [!!
...         OUTPUT('Nested call [?? variable ??]') !!].
...     [!!
...         OUTPUT('''Double nested [1!!
...                       myVariable = '456' !!1][?? myVariable ??]''') !!].
... """
>>> global_dict = { 'variable': '123' }
>>> output_text = replcode.runPythonCode(input_text, global_dict)
>>> print output_text

    Normal line.
    Expression 3.
    Global variable 123.
    .
    Function abcabc.
    Nested call 123.
    Double nested 456.

参考 See Also

Recipe 3.23.

PyCkBk-3-22 (last edited 2009-12-25 07:16:55 by localhost)