Python实现Perl风格函数模板的闭包变量取值判定
问题背景
这是Perl风格函数模板Python实现相关问题的后续跟进。我是自动化Perl转Python转换工具pythonizer的作者,目前在编写通用转换逻辑、实现Perl特殊语法结构到Python的映射时遇到了难点:
- 循环内定义函数模板时,当前生成的等价Python代码存在闭包取值问题:循环变量会取循环结束后的最终值,而非函数模板创建时刻的瞬时值
- 但部分同类业务场景中,逻辑本身确实需要获取变量的最终值,而非定义时刻的原始值,不能直接全量做值绑定修复
我习惯用TDD模式开发,在落地之前提到的解决方案前,先编写了如下Perl测试用例:
# test function templates per the perlref documentation use Carp::Assert; sub _colors { return qw(red blue green yellow orange purple white black); } my $namemod = 'blue'; for my $name (_colors()) { $name2 = $name; my $name1 = $name; no strict 'refs'; *$name = sub { $name3 = $name; $name4 = $name2; $name5 = $name1; $name6 = $namemod; $namemod .= 'x'; return "<FONT COLOR='$name'>@_</FONT>" }; $name1 = "$name$name"; } $namemod = 'yellow'; assert(red("careful") eq "<FONT COLOR='red'>careful</FONT>"); assert($name3 eq 'red'); assert($name4 eq 'black'); assert($name5 eq 'redred'); assert($name6 eq 'yellow'); assert($namemod eq 'yellowx'); assert(green("light") eq "<FONT COLOR='green'>light</FONT>"); assert($name3 eq 'green'); assert($name4 eq 'black'); assert($name5 eq 'greengreen'); assert($name6 eq 'yellowx'); assert($namemod eq 'yellowxx'); print "$0 - test passed!\n";
当前生成代码的运行异常
以下是当前版本pythonizer生成的对应Python代码:
#!/usr/bin/env python3 # Generated by "pythonizer -v0 -m test_function_templates.pl" v0.978 run by JO2742 on Fri Jun 17 17:48:09 2022 # test function templates per the perlref documentation import builtins, sys, perllib _str = lambda s: "" if s is None else str(s) perllib.init_package("main") # SKIPPED: use Carp::Assert; def _colors(*_args): return "red blue green yellow orange purple white black".split() _args = perllib.Array() builtins.__PACKAGE__ = "main" namemod = "blue" for name in _colors(): name2 = name name1 = name pass # SKIPPED: no strict 'refs'; def _f14(*_args): global name2, name3, namemod, name4, name, name1, name6, name5 name3 = name name4 = name2 name5 = name1 name6 = namemod namemod += "x" return f"<FONT COLOR='{name}'>{perllib.LIST_SEPARATOR.join(map(_str,_args))}</FONT>" globals()[name] = _f14 name1 = f"{name}{name}" namemod = "yellow" assert _str(red("careful")) == "<FONT COLOR='red'>careful</FONT>" assert name3 == "red" assert name4 == "black" assert name5 == "redred" assert name6 == "yellow" assert namemod == "yellowx" assert _str(green("light")) == "<FONT COLOR='green'>light</FONT>" assert name3 == "green" assert name4 == "black" assert name5 == "greengreen" assert name6 == "yellowx" assert namemod == "yellowxx" perllib.perl_print(f"{sys.argv[0]} - test passed!")
运行上述生成代码会触发断言错误,出错的断言语句为:assert _str(main.red("careful")) == "<FONT COLOR='red'>careful</FONT>"
报错根因是函数内引用的循环变量name取到了循环结束后的最终值black,而非循环内函数定义时刻name对应的瞬时值。
待解决的核心问题
之前给出的两类修复方案——将变量值作为默认参数传入、使用functools.partial绑定函数引用——虽然能解决循环变量瞬时值取值错误的问题,但会导致name3、name4、name5、namemod等其他需要动态取值的变量返回结果不符合测试预期。
现在需要明确可落地的技术判定规则:
- 哪些场景下需要应用固定变量瞬时值的修复方案
- 哪些场景下保留现有全局变量引用的生成逻辑即可
内容的提问来源于stack exchange,提问作者snoopyjc
相关产品推荐
相关产品推荐

