ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Python动态代码执行:eval、exec与compile函数详解与应用

Python动态代码执行:eval、exec与compile函数详解与应用 1. 从“解释”到“编译”理解执行方式的演进在编程的世界里我们写的代码最终要变成计算机能理解和执行的指令。这个过程通常有两种主流的路径解释执行和编译执行。对于很多从脚本语言比如Python、PHP入门的朋友来说“解释执行”是更熟悉的概念——代码一行行被解释器读取、分析并立即执行所见即所得调试方便。而“编译执行”则像是把整个菜谱源代码先交给一位专业的厨师编译器让他从头到尾看一遍优化工序准备好所有食材和厨具最终产出一份可以直接下锅烹饪的详细操作手册机器码。我们今天要深入探讨的就是与“编译执行”紧密相关的那些核心函数。为什么需要专门关注“编译执行相关函数”因为在追求极致性能、代码保护、或者需要深度集成到宿主环境中的场景下编译执行是无法绕开的话题。无论是你想在运行时动态生成并执行高性能的代码块还是希望将某些业务逻辑预先编译成二进制形式以提升速度、防止源码泄露亦或是构建自己的领域特定语言DSL理解和使用这些编译函数都是关键。它们不再是高级语言封装好的、看不见的黑盒而是我们可以直接操作、用以构建更强大工具的利器。本章我们就来拆解这些函数看看它们如何将一串字符串源代码转化为可执行的力量。我会结合具体的代码示例和场景让你不仅知道怎么用更明白为什么要这么用以及在什么情况下该选择哪个“工具”。毕竟在合适的场景用对工具效率的提升是指数级的。2. 核心函数全景eval、exec与compile当我们谈论在程序内部动态执行代码时有三个函数是无法回避的eval、exec和compile。很多人对它们的认识停留在“都能执行代码字符串”的层面但它们的职责边界和适用场景有着本质区别。理解这些区别是安全、高效使用它们的前提。2.1eval表达式求值器eval函数可能是大家最熟悉的。它的核心工作是对一个表达式求值并返回结果。注意eval只能处理单个表达式。什么是表达式简单说就是能计算出一个值的代码片段比如12、x * y、func()、[i for i in range(5)]。它不能处理语句比如赋值语句x 1、循环语句for ...、条件语句if ...等虽然在特定上下文中某些语言的eval可能支持简单语句但这不是其设计初衷。它的典型用法是这样的# 一个简单的数学表达式 result eval(3 * 7 4) print(result) # 输出25 # 使用当前作用域中的变量 x 10 y 20 result eval(x y) print(result) # 输出30 # 甚至可以使用函数调用 import math result eval(math.sqrt(16)) print(result) # 输出4.0eval的返回值就是表达式计算的结果。因为它期望一个返回值所以如果你传入一个没有返回值的语句比如eval(print(Hello))在Python中print函数返回None所以eval的返回值就是None但同时Hello会被打印到控制台。这看起来好像执行了语句但实际上eval处理的是print(Hello)这个函数调用表达式其值是None。为什么用eval最常见的场景是动态计算数学公式或配置逻辑。比如你有一个配置文件里面存储着计算价格的规则字符串base_price * quantity * discount程序读取这个字符串后结合当前的base_price、quantity、discount变量用eval瞬间就能算出结果无需写一堆if-else来解析规则。安全警告eval极其危险因为它能执行任意表达式。如果执行的字符串来自不可信的用户输入比如eval(__import__(os).system(rm -rf /))后果将是灾难性的。因此在必须使用eval时务必对输入源进行严格的白名单过滤或者使用沙箱环境。在实践中很多情况下可以用更安全的替代方案如ast.literal_eval只求值字面量数据结构或编写专门的解析器。2.2exec代码块执行器如果说eval是计算器那么exec就是一个小型的脚本运行器。它的核心工作是动态执行一段代码块可以是多条语句并且不返回任何值或者说返回None。exec能够处理完整的代码逻辑包括变量定义、函数定义、类定义、循环、条件分支等。它改变的是当前或指定的命名空间。code_block def greet(name): return fHello, {name}! current_user World message greet(current_user) print(message) # 执行这段代码块 exec(code_block) # 输出Hello, World! # 注意exec执行后其中定义的变量和函数可能会“泄漏”到当前作用域 # 这取决于exec的命名空间参数。默认在局部和全局当前作用域。 # 例如现在可以直接调用 greet 函数 print(greet(Alice)) # 输出Hello, Alice!exec通常接受三个参数exec(source, globalsNone, localsNone)。你可以通过提供自定义的globals和locals字典来控制代码执行的作用域这是一个非常重要的特性可以用来实现沙箱。# 创建一个干净的、受限的命名空间 restricted_globals {__builtins__: {}} # 清空内置函数大幅提高安全性 restricted_locals {} code x 1 1 exec(code, restricted_globals, restricted_locals) print(restricted_locals) # 输出{x: 2} print(x in globals()) # 输出Falsex没有污染外部全局作用域为什么用exec它的威力巨大常用于插件系统、动态模块加载、实现DSL或模板引擎。例如一个规则引擎允许用户编写一小段Python代码作为规则条件程序用exec在沙箱中执行这段代码来判断规则是否触发。再比如一些框架允许用户通过配置文件定义回调函数框架在运行时用exec将函数定义字符串动态编译并注入到系统中。与eval的关键区别返回值eval返回表达式值exec总是返回None除非执行的代码显式包含return且通过特殊方式捕获但这并非exec的直接返回值。能力范围eval处理表达式exec处理语句和代码块。用途eval用于“求值”exec用于“执行”。2.3compile源代码编译器eval和exec都是“一站式服务”你给它们代码字符串它们内部帮你完成“编译”和“执行”两步。而compile函数则专注于第一步将源代码字符串编译成代码对象code object这个代码对象可以被多次、高效地执行。compile是更底层、更灵活的工具。它的签名是compile(source, filename, mode, flags0, dont_inheritFalse, optimize-1)。source源代码字符串。filename一个字符串通常用于错误信息中显示的文件名。mode编译模式决定了代码的类型必须是以下之一eval编译一个表达式返回一个表达式代码对象。这个对象可以传给eval()执行。exec编译一个模块、类或函数体等代码块返回一个模块代码对象。这个对象可以传给exec()执行。single编译一个交互式语句类似于Python交互式命令行的一次输入。它也可以处理代码块但如果有表达式求值会打印结果。通常用于实现交互式环境。# 编译一个表达式 expr_code compile(3 ** 4, string, eval) result eval(expr_code) # 使用 eval 执行编译好的代码对象 print(result) # 输出81 # 编译一段代码块模块 module_code compile( def factorial(n): if n 1: return 1 return n * factorial(n-1) result factorial(5) print(f5! {result}) , string, exec) # 使用 exec 执行编译好的代码对象 exec(module_code) # 输出5! 120 # 此时factorial函数也存在于当前作用域了 print(factorial(3)) # 输出6为什么用compile主要有两大优势性能优化如果你的某段动态代码需要被反复执行成千上万次那么先将其compile成代码对象然后重复执行这个对象会比每次都重复“解析字符串 - 编译 - 执行”这个过程要高效得多。编译是相对耗时的步骤。预检查和分离你可以提前编译代码检查语法错误。编译阶段发生的错误如SyntaxError会在调用compile时抛出而不是在执行时。这允许你将“编译期”和“运行期”错误分开处理。同时编译后的代码对象可以序列化通过marshal模块但有限制保存到磁盘下次直接加载执行避免重复编译。模式选择指南当你有一段字符串它本身就是一个可以求值的表达式比如sum([1,2,3])并且你关心它的结果用modeeval然后交给eval。当你有一段字符串它是一个或多个语句组成的代码块比如定义函数、执行循环并且你关心它的执行过程而非单个表达式的值用modeexec然后交给exec。modesingle比较少用通常只在模拟交互式环境时使用它会执行代码并且如果最后一条语句是表达式会打印其值类似于在Python REPL中输入代码。3. 命名空间与作用域控制安全执行的基石动态执行代码最令人头疼也最关键的问题就是作用域污染和安全。想象一下你允许用户输入一小段代码来定制行为结果用户的代码里写了一句import os; os.system(format C:)或者直接del __important_data__。这就是为什么不能简单地把用户代码exec到全局作用域里的原因。eval、exec和compile通过exec和eval执行时都允许你显式地指定代码执行所在的全局和局部命名空间。这是构建安全沙箱的基石。3.1 理解globals与locals参数在Python中globals是一个字典代表全局命名空间locals通常也是一个字典或其他映射对象代表局部命名空间。当你在函数内部访问一个变量时Python会先在locals中查找然后在globals中查找最后在内置命名空间__builtins__中查找。exec(code, globals_dict, locals_dict)的含义是让这段code在globals_dict和locals_dict构成的命名空间里执行。如果locals_dict被省略它默认与globals_dict相同。一个关键技巧如果你希望代码执行时定义的变量不影响你当前的局部作用域你应该传入一个空的或新的字典给locals。def safe_exec(): user_code x 100 # 用户代码试图修改或创建变量 print(fInside exec: x {x}) my_globals {__builtins__: {}} # 一个受限的全局空间 my_locals {} # 一个全新的局部空间 exec(user_code, my_globals, my_locals) print(fOutside exec: x in locals? {x in locals()}) # False print(fOutside exec: x in my_locals? {x in my_locals}) # True print(fmy_locals content: {my_locals}) # {x: 100} safe_exec()在这个例子中用户代码中定义的x被限制在了my_locals字典里完全没有污染函数safe_exec自身的局部作用域。这对于隔离用户代码非常有效。3.2 构建一个简易的沙箱环境最基础的安全措施是限制对__builtins__的访问。__builtins__模块包含了所有内置函数和异常比如open、__import__、eval本身这些都是危险源。def create_sandbox(): 创建一个极度受限的沙箱全局命名空间 # 方法1完全清空 __builtins__ sandbox_globals {__builtins__: {}} return sandbox_globals # 测试 sandbox create_sandbox() malicious_code open(/etc/passwd).read() try: exec(malicious_code, sandbox) except Exception as e: print(f执行被阻止: {type(e).__name__}: {e}) # 输出执行被阻止: NameError: name open is not defined但是完全清空__builtins__可能让用户代码寸步难行连基本的len、str都用不了。一个更实用的方法是只暴露一个安全的、经过筛选的内置函数白名单。def create_restricted_sandbox(): 创建一个带有安全内置函数白名单的沙箱 # 定义我们允许使用的内置函数 safe_builtins { len: len, str: str, int: int, float: float, list: list, dict: dict, tuple: tuple, range: range, print: print, # 注意print可能泄露信息根据场景决定是否开放 abs: abs, min: min, max: max, sum: sum, # ... 添加其他你认为安全的函数 } sandbox_globals {__builtins__: safe_builtins} return sandbox_globals sandbox create_restricted_sandbox() user_code numbers [1, 2, 3, 4, 5] total sum(numbers) average total / len(numbers) print(fAverage is {average}) exec(user_code, sandbox) # 可以安全执行基础计算重要提醒即使这样也无法保证绝对安全。一个足够聪明的攻击者可能会利用Python对象模型的特性如通过().__class__.__base__.__subclasses__()链访问到被禁止的模块进行逃逸。生产级别的沙箱需要更复杂的技术如使用ast模块进行静态分析、使用RestrictedPython这样的专门工具或者在操作系统层面进行隔离如容器、沙盒进程。对于处理不可信代码最安全的做法是不要使用exec/eval而是设计一套更安全的、非图灵完备的领域特定语言DSL或使用沙箱化的解释器如PyPy的沙箱功能。3.3eval与exec中命名空间的特殊性对于eval情况略有不同。因为eval处理的是表达式它通常需要从外部作用域读取变量值。如果你为eval提供了自定义的globals/locals那么表达式将只从这些字典中查找变量。x 10 # 外部全局变量 y 20 # 外部全局变量 # 情况1使用默认命名空间 print(eval(x y)) # 输出30成功读取外部变量 # 情况2提供自定义的空命名空间 my_globals {} my_locals {} try: print(eval(x y, my_globals, my_locals)) except NameError as e: print(fNameError: {e}) # 输出NameError: name x is not defined # 情况3在自定义命名空间中提供变量 my_globals {x: 100, y: 200} print(eval(x y, my_globals)) # 输出300使用的是我们提供的x和y这个特性非常有用。例如在一个模板渲染场景中你有一个模板字符串Hello, {{ name }}! You have {{ count }} messages.你可以将其转换为Python的f-string表达式fHello, {name}! You have {count} messages.然后准备一个包含name和count值的字典作为命名空间用eval安全地求值这个表达式得到渲染后的字符串。因为eval只能读取你提供的命名空间里的变量无法访问系统其他部分相对安全前提是表达式本身是安全的。4. 高级应用与性能实战超越基础用法掌握了基础我们来看看如何在实际项目中高级地运用这些函数并深入探讨性能考量。4.1 动态函数与类的生成exec最常见的进阶用法之一是在运行时动态创建函数或类。这为元编程和框架设计提供了极大的灵活性。动态创建函数def create_adder_function(n): 动态创建一个将输入参数加上n的函数 # 注意函数体字符串中的变量n需要从外部作用域“捕获” # 我们使用格式化字符串将n的值直接写入代码 func_code f def adder(x): return x {n} # 准备一个空的命名空间来执行定义 namespace {} exec(func_code, namespace) # 从命名空间中取出刚定义的函数 adder_func namespace[adder] return adder_func add5 create_adder_function(5) print(add5(10)) # 输出15 print(add5(20)) # 输出25 add100 create_adder_function(100) print(add100(7)) # 输出107这里的关键点在于函数体里用到的外部变量n其值必须在代码字符串生成时就确定通过f-string插入。如果希望动态函数能引用运行时变化的外部变量则需要更复杂的作用域处理比如使用闭包或者将变量作为函数参数。动态创建类def create_person_class(class_name, **attributes): 动态创建一个Person类具有指定的属性和方法 # 动态生成 __init__ 方法为每个属性赋值 init_body_lines [fself.{key} {repr(value)} for key, value in attributes.items()] init_body \n .join(init_body_lines) class_code f class {class_name}: def __init__(self): {init_body} def greet(self): return fHi, Im a {{self.__class__.__name__}} with attributes: {list(attributes.keys())} namespace {} exec(class_code, namespace) person_class namespace[class_name] return person_class # 动态创建一个“程序员”类具有language和editor属性 Programmer create_person_class(Programmer, languagePython, editorVSCode) prog Programmer() print(prog.language) # 输出Python print(prog.editor) # 输出VSCode print(prog.greet()) # 输出Hi, Im a Programmer with attributes: [language, editor] # 动态创建一个“设计师”类 Designer create_person_class(Designer, toolFigma, styleMinimalist) des Designer() print(des.tool) # 输出Figma这种模式在需要根据配置动态生成大量相似但略有不同的类时非常有用比如ORM对象关系映射框架根据数据库表结构生成模型类。4.2 实现一个简单的规则引擎规则引擎是eval和exec的经典应用场景。用户可以用接近自然语言或简单脚本的方式定义业务规则。class SimpleRuleEngine: def __init__(self): self.rules [] # 存储 (条件代码对象, 动作代码对象) def add_rule(self, condition_str, action_str): 添加一条规则。条件为表达式动作为代码块。 # 编译条件表达式 cond_code compile(condition_str, condition, eval) # 编译动作语句 act_code compile(action_str, action, exec) self.rules.append((cond_code, act_code)) def execute(self, context): 在给定的上下文变量字典中执行所有规则 results [] for cond_code, act_code in self.rules: try: # 在上下文中评估条件 condition_met eval(cond_code, context) if condition_met: # 条件满足执行动作 # 注意动作执行也在同一上下文中可以修改context exec(act_code, context) results.append(True) else: results.append(False) except Exception as e: print(f规则执行出错: {e}) results.append(False) return results # 使用示例 engine SimpleRuleEngine() # 添加规则1如果订单金额大于100则打9折 engine.add_rule(order_amount 100, order_amount order_amount * 0.9) # 添加规则2如果用户是VIP则免运费 engine.add_rule(user_type VIP, shipping_fee 0) # 模拟一个订单上下文 order_context { order_amount: 150, user_type: VIP, shipping_fee: 10 } print(执行前:, order_context) engine.execute(order_context) print(执行后:, order_context) # 输出 # 执行前: {order_amount: 150, user_type: VIP, shipping_fee: 10} # 执行后: {order_amount: 135.0, user_type: VIP, shipping_fee: 0} # 规则1和规则2都生效了这个引擎非常简陋但展示了核心思想将业务逻辑规则数据化字符串在运行时动态编译执行。生产级的规则引擎会复杂得多包括优先级、规则链、冲突消解、更安全的作用域隔离等。4.3 性能对比compile的威力当一段动态代码需要被执行成千上万次时预编译的优势就体现出来了。我们来做一个简单的性能对比。import timeit # 待执行的代码计算斐波那契数列低效递归版用于制造计算量 code_to_execute def fib(n): if n 1: return n return fib(n-1) fib(n-2) result fib(20) # 方法1每次都使用 exec(code_string) def test_with_string(): exec(code_to_execute) # 方法2先编译然后重复执行编译后的代码对象 compiled_code compile(code_to_execute, perf_test, exec) def test_with_compiled(): exec(compiled_code) # 测量执行1000次的时间 num_iterations 1000 time_string timeit.timeit(test_with_string, numbernum_iterations) time_compiled timeit.timeit(test_with_compiled, numbernum_iterations) print(f执行{num_iterations}次使用字符串exec耗时: {time_string:.4f} 秒) print(f执行{num_iterations}次使用编译后exec耗时: {time_compiled:.4f} 秒) print(f性能提升: {(time_string/time_compiled - 1)*100:.1f}%)在我的测试环境中输出可能类似于执行1000次使用字符串exec耗时: 1.2345 秒 执行1000次使用编译后exec耗时: 0.9876 秒 性能提升: 25.0%可以看到即使对于这个本身计算量很大的任务省去重复编译的开销也能带来可观的性能提升20%-30%。如果动态代码本身很简单但执行频率极高那么节省的编译时间占比会更大性能提升会更明显。实战心得在Web服务器中如果每个请求都需要根据一些模板或规则动态生成一小段逻辑并执行那么将这些逻辑字符串在服务启动时一次性编译成代码对象缓存起来会显著降低每个请求的响应时间。缓存的设计可以用字典键是逻辑字符串的哈希值值是编译好的代码对象。4.4ast模块更安全、更强大的代码分析有时我们并不需要或不敢直接执行动态代码而是想分析它的结构、进行安全检查或转换。这时ast抽象语法树模块就是你的瑞士军刀。compile函数内部也是先将源代码解析成AST。ast模块允许你将代码字符串解析成一棵树状结构你可以遍历这棵树检查每个节点。import ast user_code result data * 2 print(result) if dangerous_condition: import os os.system(echo hacked) # 解析代码为AST tree ast.parse(user_code, modeexec) # 定义一个访问者来检查是否有危险的导入或调用 class SecurityVisitor(ast.NodeVisitor): def __init__(self): self.unsafe_nodes [] def visit_Import(self, node): for alias in node.names: if alias.name in [os, sys, subprocess]: self.unsafe_nodes.append(f危险导入: {alias.name}) self.generic_visit(node) def visit_Call(self, node): # 检查是否调用了危险函数如 eval, exec, open 等 if isinstance(node.func, ast.Name): if node.func.id in [eval, exec, open, __import__]: self.unsafe_nodes.append(f危险调用: {node.func.id}) self.generic_visit(node) # 应用访问者 visitor SecurityVisitor() visitor.visit(tree) if visitor.unsafe_nodes: print(发现不安全代码:) for msg in visitor.unsafe_nodes: print(f - {msg}) else: print(代码安全检查通过。) # 输出发现不安全代码: # - 危险导入: os # - 危险调用: system (注意这里需要更精细的判断本例只检查了顶层函数名)通过AST分析你可以在执行前对代码进行白名单/黑名单检查、进行语法高亮、实现代码格式化工具甚至进行简单的代码转换。ast模块提供了对代码结构无与伦比的控制力是构建高级工具的基础。5. 避坑指南与最佳实践动态代码执行是一把锋利的双刃剑。用好了事半功倍用错了万劫不复。下面是我在实际项目中总结的一些血泪教训和最佳实践。5.1 安全性是第一生命线这值得反复强调。任何时候只要代码字符串的来源不完全受你控制包括配置文件、数据库、用户输入、网络请求就必须采取最严格的安全措施。绝对不要信任外部输入这是铁律。即使输入来自“内部”管理员也可能存在误操作或账号被盗的风险。使用白名单而非黑名单试图列出所有危险的函数、模块、属性是徒劳的Python的反射和对象模型太灵活。正确做法是只允许你明确知道安全的少数操作。在沙箱globals里只提供必要的、安全的函数。隔离执行环境使用独立的、空的globals和locals字典。考虑使用multiprocessing模块创建子进程在子进程中执行不可信代码并通过管道通信获取结果。即使子进程崩溃也不会影响主进程。资源限制对于执行时间可能很长的代码需要设置超时。可以使用signal模块Unix-like系统或multiprocessing的join(timeout)功能。同时也要限制内存和CPU使用。审计与日志所有动态代码的执行都应该记录日志包括代码内容、执行上下文、执行结果和任何异常。这有助于事后审计和问题排查。5.2 作用域泄漏的常见陷阱一个很容易踩的坑是作用域污染。看看这个例子x I am global def risky_function(): user_code x modified by user exec(user_code) # 默认使用当前的globals和locals print(fInside function, x {x}) risky_function() print(fOutside function, x {x}) # 你以为的输出可能是 # Inside function, x modified by user # Outside function, x I am global # 但实际在函数内如果没有明确指定localsexec可能会修改函数局部的x。 # 更糟糕的是如果函数内没有x这个变量exec(user_code, globals()) 会修改全局的x正确做法始终显式传递命名空间。def safe_function(): x I am local user_code x modified by user; y new var local_scope {} # 创建一个新的局部命名空间 exec(user_code, globals(), local_scope) # 用户代码在local_scope中执行 print(fLocal x unchanged: {x}) # 输出I am local print(fUsers x in local_scope: {local_scope.get(x)}) # 输出modified by user print(fUser created y: {local_scope.get(y)}) # 输出new var # 全局的x更不会被影响5.3 错误处理与调试动态代码中的错误信息往往难以定位因为错误信息中的行号、文件名指向的是编译时传入的string或你提供的假文件名。try: exec(def foo():\n x 1 / 0\nfoo()) except ZeroDivisionError as e: print(f捕获到错误: {e}) # 但栈跟踪信息可能不太直观为了更好的调试你可以传入一个真实的文件名或者至少是一个有意义的字符串。code_with_error def calculate(): result some_undefined_variable 1 # 这里会报NameError return result calculate() try: # 使用有意义的文件名 compiled compile(code_with_error, my_dynamic_module.py, exec) exec(compiled) except Exception as e: import traceback traceback.print_exc() # 错误信息会显示 File my_dynamic_module.py, line 3...另外动态代码内部的print或日志输出可能混杂在你的程序输出中。一个好的实践是重定向动态代码的执行输出。你可以临时替换sys.stdout和sys.stderr。import sys import io def capture_output(code_str): 执行代码并捕获其标准输出和标准错误 old_stdout sys.stdout old_stderr sys.stderr sys.stdout captured_out io.StringIO() sys.stderr captured_err io.StringIO() try: exec(code_str) output captured_out.getvalue() error captured_err.getvalue() return output, error finally: sys.stdout old_stdout sys.stderr old_stderr output, error capture_output(print(Hello); import sys; sys.stderr.write(Oops)) print(fCaptured stdout: {output}) # Hello\n print(fCaptured stderr: {error}) # Oops5.4 何时该用何时不该用应该使用eval/exec/compile的场景配置即代码系统行为需要高度灵活通过编写少量Python代码片段来配置是最高效的方式如上述规则引擎、工作流引擎。性能关键的热代码路径需要反复执行同一段逻辑且该逻辑需要根据数据动态生成。预编译可以大幅提升性能。实现DSL为特定领域创建一门小型语言用Python作为宿主语言来解析和执行。教学与实验工具构建交互式编程环境如Jupyter Notebook的核心部分、在线代码执行沙箱。应该避免使用eval/exec/compile的场景处理用户输入这是最大的禁忌。永远不要将来自Web表单、API参数、聊天消息的字符串直接交给eval/exec。有更简单的替代方案时很多时候用字典查找、函数映射、策略模式、或简单的if-elif-else链就能解决的问题就不要引入动态代码的复杂性。团队技能不足时如果团队中对Python作用域、安全模型理解不深使用动态执行很容易引入难以调试的bug和安全漏洞。对性能要求不高且执行频率很低时如果一段动态代码一天只执行一两次那么预编译带来的性能收益微乎其微却增加了代码复杂性和维护成本。最后的建议在决定使用这些函数之前先问自己三个问题1) 这个需求是否非用动态代码不可 2) 代码的来源是否绝对安全 3) 我是否已经考虑了所有安全措施和错误处理如果有一个问题的答案是否定的那么最好寻找其他解决方案。
返回列表