背景 {#背景}
我们公司平台的开发环境中使用的是 Python 2.7 版本,最近工作中,我遇到这样一个需求:我需要在一个方法中访问另一个函数内部定义的变量。这些变量包括字符串和数字类型,来源于一个预定义的固定函数。
这个固定函数会在执行文件前由平台自动注入,且不能被修改,并且函数中包含的一些变量是动态的,只能注入之后才能知道具体是什么。本质就是这个函数本身的逻辑无法满足我的需求,我需要重写该函数,并且需要保留函数中的一些变量的值。我的目标是,在不执行这个固定函数的前提下,获取到其中局部定义的字符串和数字变量。
例如,固定函数如下:
def private_func(*args, **kwargs):
client_cer = "xxxx cer xxxx"
client_key = 'xxxx key xxxx'
<span class="n">other_arg</span> <span class="o">=</span> <span class="n">client_key</span> <span class="o">+</span> <span class="n">client_cer</span>
在这个函数中,client_cer
和 client_key
是我要提取的变量,而像 other_arg
这种复杂计算结果的变量则可以忽略。
通过调研和实践,我找到了一种基于 静态分析 的优雅方法,借助 Python 的 inspect
和 ast
模块完成这一任务。
解决方案 {#解决方案}
思路 {#思路}
- 获取函数源码 :使用
inspect
模块提取函数源码。 - 解析源码为 AST(抽象语法树) :通过
ast
模块读取函数的语法结构。 - 遍历 AST,提取赋值变量:定位赋值语句,提取变量名和值,只保留字符串和数字类型的变量。
代码实现 {#代码实现}
以下是完整的代码实现,支持 Python 2.7 环境(Python 3 当然也是兼容的):
import inspect
import ast
def analyze_variables_from_source(func):
# 获取函数源码
source = inspect.getsource(func)
tree = ast.parse(source) # 解析为 AST(抽象语法树)
<span class="c1"># 提取赋值语句中的变量和值</span>
<span class="n">variables</span> <span class="o">=</span> <span class="p">{}</span>
<span class="k">for</span> <span class="n">node</span> <span class="ow">in</span> <span class="n">ast</span><span class="o">.</span><span class="n">walk</span><span class="p">(</span><span class="n">tree</span><span class="p">):</span>
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">node</span><span class="p">,</span> <span class="n">ast</span><span class="o">.</span><span class="n">Assign</span><span class="p">):</span> <span class="c1"># 如果是赋值语句</span>
<span class="k">for</span> <span class="n">target</span> <span class="ow">in</span> <span class="n">node</span><span class="o">.</span><span class="n">targets</span><span class="p">:</span>
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">target</span><span class="p">,</span> <span class="n">ast</span><span class="o">.</span><span class="n">Name</span><span class="p">):</span> <span class="c1"># 确保是变量名</span>
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">node</span><span class="o">.</span><span class="n">value</span><span class="p">,</span> <span class="n">ast</span><span class="o">.</span><span class="n">Str</span><span class="p">):</span> <span class="c1"># 字符串值</span>
<span class="n">variables</span><span class="p">[</span><span class="n">target</span><span class="o">.</span><span class="n">id</span><span class="p">]</span> <span class="o">=</span> <span class="n">node</span><span class="o">.</span><span class="n">value</span><span class="o">.</span><span class="n">s</span>
<span class="k">elif</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">node</span><span class="o">.</span><span class="n">value</span><span class="p">,</span> <span class="n">ast</span><span class="o">.</span><span class="n">Num</span><span class="p">):</span> <span class="c1"># 数字值</span>
<span class="n">variables</span><span class="p">[</span><span class="n">target</span><span class="o">.</span><span class="n">id</span><span class="p">]</span> <span class="o">=</span> <span class="n">node</span><span class="o">.</span><span class="n">value</span><span class="o">.</span><span class="n">n</span>
<span class="k">return</span> <span class="n">variables</span>
使用示例 {#使用示例}
假设我们要提取 private_func
中的字符串和数字变量:
def private_func(*args, **kwargs):
client_cer = "xxxx cer xxxx"
client_key = 'xxxx key xxxx'
<span class="n">other_arg</span> <span class="o">=</span> <span class="n">client_key</span> <span class="o">+</span> <span class="n">client_cer</span> <span class="c1"># 忽略这类逻辑</span>
调用代码:
result = analyze_variables_from_source(private_func)
print(result)
# 输出: {'client_cer': 'xxxx cer xxxx', 'client_key': 'xxxx key xxxx'}
支持更多数据类型的扩展 {#支持更多数据类型的扩展}
除了字符串和数字,我们还可以扩展代码以支持 列表、字典等数据类型 的提取。以下是改进版代码:
import inspect
import ast
def analyze_variables_from_source_extended(func):
source = inspect.getsource(func)
tree = ast.parse(source)
<span class="n">variables</span> <span class="o">=</span> <span class="p">{}</span>
<span class="k">for</span> <span class="n">node</span> <span class="ow">in</span> <span class="n">ast</span><span class="o">.</span><span class="n">walk</span><span class="p">(</span><span class="n">tree</span><span class="p">):</span>
<span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">node</span><span class="p">,</span> <span class="n">ast</span><span class="o">.</span><span class="n">Assign</span><span class="p">):</span> <span class="c1"># 跳过非赋值语句</span>
<span class="k">continue</span>
<span class="k">for</span> <span class="n">target</span> <span class="ow">in</span> <span class="n">node</span><span class="o">.</span><span class="n">targets</span><span class="p">:</span>
<span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">target</span><span class="p">,</span> <span class="n">ast</span><span class="o">.</span><span class="n">Name</span><span class="p">):</span> <span class="c1"># 跳过非变量名的目标</span>
<span class="k">continue</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">node</span><span class="o">.</span><span class="n">value</span>
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="n">ast</span><span class="o">.</span><span class="n">Str</span><span class="p">):</span> <span class="c1"># 字符串</span>
<span class="n">variables</span><span class="p">[</span><span class="n">target</span><span class="o">.</span><span class="n">id</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span><span class="o">.</span><span class="n">s</span>
<span class="k">elif</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="n">ast</span><span class="o">.</span><span class="n">Num</span><span class="p">):</span> <span class="c1"># 数字</span>
<span class="n">variables</span><span class="p">[</span><span class="n">target</span><span class="o">.</span><span class="n">id</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span><span class="o">.</span><span class="n">n</span>
<span class="k">elif</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="n">ast</span><span class="o">.</span><span class="n">List</span><span class="p">):</span> <span class="c1"># 列表</span>
<span class="n">variables</span><span class="p">[</span><span class="n">target</span><span class="o">.</span><span class="n">id</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
<span class="n">elt</span><span class="o">.</span><span class="n">s</span> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">elt</span><span class="p">,</span> <span class="n">ast</span><span class="o">.</span><span class="n">Str</span><span class="p">)</span> <span class="k">else</span>
<span class="n">elt</span><span class="o">.</span><span class="n">n</span> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">elt</span><span class="p">,</span> <span class="n">ast</span><span class="o">.</span><span class="n">Num</span><span class="p">)</span> <span class="k">else</span> <span class="kc">None</span>
<span class="k">for</span> <span class="n">elt</span> <span class="ow">in</span> <span class="n">value</span><span class="o">.</span><span class="n">elts</span>
<span class="p">]</span>
<span class="k">elif</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="n">ast</span><span class="o">.</span><span class="n">Dict</span><span class="p">):</span> <span class="c1"># 字典</span>
<span class="n">keys</span> <span class="o">=</span> <span class="p">[</span>
<span class="n">key</span><span class="o">.</span><span class="n">s</span> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">ast</span><span class="o">.</span><span class="n">Str</span><span class="p">)</span> <span class="k">else</span>
<span class="n">key</span><span class="o">.</span><span class="n">n</span> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">ast</span><span class="o">.</span><span class="n">Num</span><span class="p">)</span> <span class="k">else</span> <span class="kc">None</span>
<span class="k">for</span> <span class="n">key</span> <span class="ow">in</span> <span class="n">value</span><span class="o">.</span><span class="n">keys</span>
<span class="p">]</span>
<span class="n">values</span> <span class="o">=</span> <span class="p">[</span>
<span class="n">val</span><span class="o">.</span><span class="n">s</span> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">val</span><span class="p">,</span> <span class="n">ast</span><span class="o">.</span><span class="n">Str</span><span class="p">)</span> <span class="k">else</span>
<span class="n">val</span><span class="o">.</span><span class="n">n</span> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">val</span><span class="p">,</span> <span class="n">ast</span><span class="o">.</span><span class="n">Num</span><span class="p">)</span> <span class="k">else</span> <span class="kc">None</span>
<span class="k">for</span> <span class="n">val</span> <span class="ow">in</span> <span class="n">value</span><span class="o">.</span><span class="n">values</span>
<span class="p">]</span>
<span class="n">variables</span><span class="p">[</span><span class="n">target</span><span class="o">.</span><span class="n">id</span><span class="p">]</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">(</span><span class="nb">zip</span><span class="p">(</span><span class="n">keys</span><span class="p">,</span> <span class="n">values</span><span class="p">))</span>
<span class="k">return</span> <span class="n">variables</span>
假设 private_func
的变量变得更加多样化:
def private_func(*args, **kwargs):
client_cer = "xxxx cer xxxx"
client_key = 'xxxx key xxxx'
numbers = [1, 2, 3, 4]
config = {"host": "localhost", "port": 8080}
调用扩展版代码:
result = analyze_variables_from_source_extended(private_func)
print(result)
# 输出:
# {
# 'client_cer': 'xxxx cer xxxx',
# 'client_key': 'xxxx key xxxx',
# 'numbers': [1, 2, 3, 4],
# 'config': {'host': 'localhost', 'port': 8080}
# }
优缺点分析 {#优缺点分析}
优点:
- 安全性:不会执行目标函数代码,完全基于静态分析,避免副作用。
- 灵活性:支持多种数据类型的提取,包括字符串、数字、列表和字典。
- 适用性广:适合在调试、代码检查器、静态分析器等场景中使用。
局限性:
- 无法处理动态计算 :例如通过外部调用、运行时计算得到的变量值(如
other_arg
)无法直接解析。 - 复杂函数支持有限:如果函数包含嵌套定义或条件语句,需额外逻辑支持。
总结 {#总结}
在 Python 2.7 环境中,通过 inspect
和 ast
模块,我们能够优雅地提取未调用函数的局部变量,且完全避免执行代码的风险。此方法对简单的固定函数非常高效,适用于遗留系统和静态分析工具。