将lambda表达式保存到文件中

5

我正在使用 Python 3 实现一种进化策略算法。我创建了一个名为“Individual”的类,它从一个文件(YAML 格式)中读取配置信息,该文件的格式如下:

num_of_genes: 3
pre_init_gene:
    gene1: 1.0
    gene2: 1.0
    gene3: 1.0
metrics:
    - metric1
    - metric2
obj_funcs:
    obj_fun1: 'lambda x,y: x+y'
    obj_fun2: 'lambda x: (x**3)/2'

这个想法是,个人可以阅读此文件以获取其配置信息。 我知道我可以将我的lambda表达式保存为字符串,然后调用eval。

但是,针对这个问题是否有更Pythonic的解决方案?虽然我不太熟悉Python中的面向对象编程,但我愿意听取建议。


这些函数必须是lambda表达式吗?我曾经用类似的技巧(在配置文件中定义函数),我是通过创建一个“functions”包并在其中定义每个函数,然后obj_funcs: {obj_fun1: "functions.function_one", ...}来实现的。 - Adam Smith
实际的lambda表达式在YAML文档中能被更改很重要吗?还是这只是用于文档目的而已? - Anthon
“我可以将我的lambda表达式保存为字符串”是什么意思?从这个YAML文档中,您将加载它们,并得到一个Python字符串。在调用eval之前,您将它们保存在哪里?(将数据结构保存到YAML文档的等效操作称为dumping)。 - Anthon
@AdamSmith:我喜欢你的建议。我相信只要人们有一些Python知识,它就能更好地发挥作用。然而,在我的情况下,将使用我的程序的人不知道如何使用Python编程。无论如何,感谢你的建议! - alexandredias3d
@alexandredias3d,你的用户可能不懂Python,但是会知道λ演算吗?这是一个奇怪而特定的用例! - Adam Smith
@AdamSmith:我刚刚简化了场景。用户只需编写“(metric1/metric2) + metric3”这样的表达式。在这种情况下,他不需要了解lambda演算,因为我已经有了度量标准来组成这个表达式作为一个lambda函数。我不知道我是否表述清楚。 - alexandredias3d
2个回答

4
我会遵循Python之禅中的特定原则,尤其是“显式优于隐式”和“可读性至上”。
因此,将函数作为可读字符串定义lambda表达式是一个好主意,但是出于安全原因,在加载的lambda字符串表示上调用eval可能不是一个好主意。这取决于谁有修改文件访问权限以及他们在哪个系统上运行。
通常情况下,如果某人具有登录访问权限,则不必过于关心是否可以(非故意地)注入某些内容导致递归删除系统上的所有文件。 但是,如果例如软件运行在远程系统上并且可以通过某些Web界面编辑这些文件,或者如果文件更改可以由其他人而不是使用文件的人进行,则应考虑这一点。
如果lambda来自固定集合,则可以将其字符串表示用作查找。
lambdas = {}
for l in [
   'lambda x,y: x+y',
   'lambda x: (x**3)/2',
   # some more
]:
   lambdas[l] = eval(l)

你可以使用从配置YAML加载的字符串来获取实际的lambda表达式,该字符串无法被篡改,因为它必须与您提供的可用lambda表达式集合匹配。当然,您可以从只有您可以更改的文件中加载实际的lambda字符串,而不是在源代码中硬编码它们。
这比转储实际的lambda表达式并生成类似YAML的输出更加明确。
!!python/name:__main__.%3Clambda%3E

如果你需要更灵活的方式,而不是使用预定义的lambda表达式,但又不想使用eval的不安全性,那么另一个可能性是使用Python的AST模块。该模块允许对一元和二元运算符进行安全评估,但可以扩展为仅处理您想要在lambda中允许的函数(例如某些数学函数)。我在我的Python对象表示法模块中做了类似的扩展(PON),添加了日期时间和去除缩进的功能到AST评估输入中。


我认为您应该改进一下您的YAML。不要使用gene1gene2作为映射中的键,而是使用序列并标记项目:

pre_init_gene:
    - !Gene 1.0
    - !Gene 1.0
    - !Gene 1.0

或者,另一种方法是标记这个序列:
pre_init_gene: !Genes
    - 1.0
    - 1.0
    - 1.0

你的lambda表达式存在相同的“问题”,我会这样做:
obj_funcs:
   - !Lambda 'x, y: x+y'
   - !Lambda 'x: (x**3)/2'

实现from_yamlclassmethod的对象会自动执行eval或AST评估,用于标签!Lambda


我喜欢你使用AST提供可读性和安全性的想法。我一定会去了解一下。然而,我不确定我是否理解了你改进YAML的建议:这个标签要求我实现一个名为from_yaml的方法的类,告诉YAML如何读取该特定标签,对吗?如果是这样,你认为我应该这样做,即使我只对值感兴趣吗?比如metric这种情况,它只是一个值列表,你会为它创建一个标签吗?谢谢你的时间! - alexandredias3d
@alexandredias3d 我认为在许多加载YAML的应用程序中标签被低估了,而且经常滥用键来指示值应该是什么类型。! Gene 的一个浮点参数的示例可能过于复杂,但对于您将要调用 eval 或某些AST处理的lambda字符串来说,使用标记并获得智能类型更有意义(您始终可以延迟实际处理,直到尝试访问计算出的值)。 - Anthon

2
使用cloudpickle,您可以将lambda函数转储为bytes。然后,您需要将bytes转换为str以便写入文件。
import cloudpickle
import base64


def lambda2str(expr):
    b = cloudpickle.dumps(expr)
    s = base64.b64encode(b).decode()
    return s


def str2lambda(s):
    b = base64.b64decode(s)
    expr = cloudpickle.loads(b)
    return expr


e = lambda x, y: x + y
s = lambda2str(e)      
print(s)           # => gASVNAEAAAAAAACMF2Nsb3VkcGlja2xlLmNsb3VkcGlja2xllIwOX2ZpbGxfZnVuY3Rpb26Uk5QoaACMD19tYWtlX3NrZWxfZnVuY5STlGgAjA1fYnVpbHRpbl90eXBllJOUjAhDb2RlVHlwZZSFlFKUKEsCSwBLAksCS0NDCHwAfAEXAFMAlE6FlCmMAXiUjAF5lIaUjCovVXNlcnMvYmxvd25oaXRoZXJtYS9wcm9qZWN0cy90ZXN0L3Rlc3QucHmUjAg8bGFtYmRhPpRLEUMAlCkpdJRSlEr/////fZSHlFKUf

# store s in file, read s from file

e2 = str2lambda(s)
print(e2(1, 1))    # => 2

请注意,base64编码的作用是避免编码后字符串中出现像\n这样可能破坏文件结构的字符。 decode() 只是将 bytes 转换为 str,以便将其写入文件。
这不是简洁的表示,而是安全的表示。如果您的工作环境是安全的,可以使用可读性更强的版本!

我喜欢你将编码的lambda函数转储到文件中的想法。在这种情况下,我需要表达式易于阅读和更改,因为我的程序用户可能不太了解Python。无论如何,感谢您的评论!每当我处于不安全的环境时,我都会考虑采用您的解决方案。 - alexandredias3d

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接