繁体   English   中英

perl 样式 Function 模板在 python

[英]perl style Function Templates in python

我是pythonizer的作者,我正在尝试将 perl 样式的 function 模板转换为 python。当我生成我认为等效的代码时,循环变量的值是最后一个值而不是它原来的值当 function 模板出现时。 关于捕获正确循环变量值的代码的任何想法? 例如:

# test function templates per the perlref documentation
use Carp::Assert;

sub _colors {
    return qw(red blue green yellow orange purple white black);
}

for my $name (_colors()) {
    no strict 'refs';
    *$name = sub { "<FONT COLOR='$name'>@_</FONT>" };
}

assert(red("careful") eq "<FONT COLOR='red'>careful</FONT>");
assert(green("light") eq "<FONT COLOR='green'>light</FONT>");

print "$0 - test passed!\n";

被翻译成:

#!/usr/bin/env python3
# Generated by "pythonizer -v0 test_function_templates.pl" v0.978 run by snoopyjc on Thu May 19 10:49:12 2022
# Implied pythonizer options: -m
# test function templates per the perlref documentation
import builtins, perllib, sys

_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"
for name in _colors():
    pass  # SKIPPED:     no strict 'refs';

    def _f10(*_args):
        #nonlocal name
        return f"<FONT COLOR='{name}'>{perllib.LIST_SEPARATOR.join(map(_str,_args))}</FONT>"

    globals()[name] = _f10


print(red("careful"))
assert _str(red("careful")) == "<FONT COLOR='red'>careful</FONT>"
assert _str(green("light")) == "<FONT COLOR='green'>light</FONT>"

perllib.perl_print(f"{sys.argv[0]} - test passed!")

(我注释掉了 nonlocal 因为nonlocal抱怨这是一个语法错误,并添加了print语句)。 添加的print语句写出<FONT COLOR='black'>careful</FONT>而不是正确的<FONT COLOR='red'>careful</FONT>

当生成function red时,如何让它捕获循环计数器的red值?

函数_f10未正确绑定参数name

所以函数中使用的name取决于最后一个结果循环。 运行循环时, name在全局范围内,因此您仍然可以获得结果,只是不是预期的。

您应该将名称绑定到函数中,方法是向其添加name参数并部分解析函数,如下所示:

from functools import partial

def _f10(name, *_args):
    #nonlocal name
    return f"<FONT COLOR='{name}'>{perllib.LIST_SEPARATOR.join(map(_str,_args))}</FONT>"

globals()[name] = partial(_f10, name)

所以每个全局都绑定到一个稍微不同的函数(第一个参数是绑定的)。

⇒ 从 perl 代码中找到要绑定到函数的标识符可能很困难……您仍然可以尝试使用locals()以某种方式绑定所有局部变量,但这有点棘手。

正如其他答案中提到的,问题是name仍然是对变量的引用,而不是像预期的那样成为字符串文字。

实现此目的的另一种方法是将模板化字符串用作代码,然后执行生成的字符串。 这种方法的一个好处是未来的读者可以通过打印生成的模板化字符串来准确验证正在执行的内容。

退后一步并关注问题的领域,我创建了两个解决方案。 首先是我认为如果我要手动翻译代码会是什么样子,其次是采用您的文字示例并尝试使其工作。

手动转录

这里我尝试手动将 Perl 代码转录成 Python(对 Perl 知之甚少)。 我认为这说明了与原始行为最接近的 1:1 行为,同时试图保持在 Perl 中实现这一点的精神。

这是我推荐的解决方案,因为它可以实现非常优雅的 1:1 比例的代码行完成与每行原始 Perl 代码完全相同的工作(如果你能原谅在 Python 范例中通常被视为糟糕的风格)

import sys
from string import Template

def _colors(*_args):
    return "red blue green yellow orange purple white black".split()

for name in _colors():
    pass  # SKIPPED:     no strict 'refs';
    eval(compile(Template('''global $name\n$name = lambda x: f"<FONT COLOR='$name'>{x}</FONT>"''').substitute({'name':name}),'<string>', 'exec'))

assert red("careful") == "<FONT COLOR='red'>careful</FONT>"
assert green("light") == "<FONT COLOR='green'>light</FONT>"

print(f"{sys.argv[0]} - test passed!")

更新的操作码

在这里,我尝试复制 OP 提供的文字代码,以使该代码在尽可能少地修改的情况下工作。 (我更喜欢手动转录的版本)

请注意,由于我没有安装 perllib,因此无法对此进行测试。

#!/usr/bin/env python3
# Generated by "pythonizer -v0 test_function_templates.pl" v0.978 run by snoopyjc on Thu May 19 10:49:12 2022
# Implied pythonizer options: -m
# test function templates per the perlref documentation
import builtins, perllib, sys
from string import Template

_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"
for name in _colors():
    pass  # SKIPPED:     no strict 'refs';

    eval(compile(Template('''
def _f10(*_args):
    #nonlocal $name
    return f"<FONT COLOR='{$name}'>{perllib.LIST_SEPARATOR.join(map(_str,_args))}</FONT>"
globals()[$name] = _f10
''').substitute({'name':name}),'<string>', 'exec'))


print(red("careful"))
assert _str(red("careful")) == "<FONT COLOR='red'>careful</FONT>"
assert _str(green("light")) == "<FONT COLOR='green'>light</FONT>"

perllib.perl_print(f"{sys.argv[0]} - test passed!")

其他注意事项

安全 - 远程代码执行

通常,在使用 Eval/Exec/Compile 时应非常小心,因为任何输入值(在本例中为颜色)都可能是任意代码块。 如果最终用户可以以任何方式控制输入值,那就太糟糕了。 也就是说,这对于 Perl 来说大概也是如此,并且您选择的解决方案并不重要。

因此,如果出于任何原因输入数据不受信任,您将需要进行更多的源验证等。通常我会高度关注,但 IMO 我认为在将代码从一种语言翻译成另一种语言时,代码执行可能是可接受的风险。 您可能已经在执行原始代码以验证其功能,因此我假设源具有 100% 的信任。

重创

我相信你可能知道,但值得注意的是,像这样自动生成全局对象存在一些严重的问题。 您可能应该测试当您尝试使用现有关键字名称定义全局时会发生什么,从而导致命名空间冲突。 我的期望是在 Python 中它会产生一个错误,而在 Perl 中它会像 Python 中的猴子补丁一样工作。 您可能需要考虑为以这种方式定义的所有全局变量添加前缀,或者决定是否允许这种重新定义关键字/内置/现有名称的行为。

好的,这花了一些时间,但我终于弄清楚模板函数需要使用嵌套 def 才能在模板化时捕获模板值。 这是解决此问题的最新 pythonizer 生成的代码:

#!/usr/bin/env python3
# Generated by "pythonizer stack_72307337.pl" v0.995 run by snoopyjc on Fri Oct  7 23:28:01 2022
# Implied pythonizer options: -m
# test function templates per the perlref documentation
import perllib, sys, builtins

_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"
for name in _colors():
    pass  # SKIPPED:     no strict 'refs';

    def _f10(name):
        def _f10template(*_args):
            nonlocal name
            return f"<FONT COLOR='{name}'>{perllib.LIST_SEPARATOR.join(map(_str,_args))}</FONT>"

        return _f10template

    globals()[name] = _f10(name)


assert _str(red("careful")) == "<FONT COLOR='red'>careful</FONT>"
assert _str(green("light")) == "<FONT COLOR='green'>light</FONT>"

perllib.perl_print(f"{sys.argv[0]} - test passed!")

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM