【问题标题】:perl style Function Templates in pythonpython中的perl样式函数模板
【发布时间】:2022-10-08 11:30:48
【问题描述】:

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

# 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,因为python 抱怨这是一个语法错误,并添加了print 语句)。添加的print 语句写出&lt;FONT COLOR='black'&gt;careful&lt;/FONT&gt; 而不是正确的&lt;FONT COLOR='red'&gt;careful&lt;/FONT&gt;

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

【问题讨论】:

  • CSS 现在过时了吗?
  • @北极熊:这只是 perl 文档中的示例代码

标签: python perl


【解决方案1】:

函数_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() 以某种方式绑定所有局部变量,但这有点棘手。

【讨论】:

  • 通过在 _f10 中分配 globals()[name],每次调用该函数时,它都会重新定义自己
  • 抱歉缩进不好
  • 好的 - 我需要解决一般情况 - 用户可以将他们想要的任何内容放入匿名子中,我将其转换为函数。如果他们在潜艇中更改“名称”怎么办?如果他们不更改它,我应该只添加“名称”作为参数吗?
  • 我不知道 perl,所以我不能完全回答你。如果 perl 只使用全局变量,那么在将 perl 代码转换为 python 时,您也应该只使用全局 python 变量。 Python代码会非常非常难看……
  • 是的,它将是一个全局变量,我的翻译器应该在 _f10 而不是非本地发出一个“全局”语句,但如果变量没有改变,我需要实现你提议的改变。
【解决方案2】:

正如其他答案中提到的,问题是 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
$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 中的猴子补丁一样工作。您可能需要考虑为以这种方式定义的所有全局变量添加前缀,或者决定是否允许这种重新定义关键字/内置/现有名称的行为。

【讨论】:

【解决方案3】:

好的,这需要做一些工作,但我最终发现模板函数需要使用嵌套的 def 才能在模板化时捕获模板值。这是解决此问题的最新 pythonizer 生成的代码:

#!/usr/bin/env python3
# Generated by "pythonizer stack_72307337.pl" v0.995 run by JO2742 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!")

【讨论】:

    猜你喜欢
    • 2016-12-15
    • 2014-07-23
    • 1970-01-01
    • 2012-08-08
    • 1970-01-01
    • 2011-10-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多