【问题标题】:Save lambda expressions to a file将 lambda 表达式保存到文件
【发布时间】:2019-03-23 21:40:45
【问题描述】:

我正在 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 中的 OO 感觉不太舒服,但我愿意接受建议。

【问题讨论】:

  • 这些函数必须是 lambda 表达式吗?我做了一个类似的技巧(在配置文件中定义函数),我做了一个 functions 包并在它下面定义每个函数,然后 obj_funcs: {obj_fun1: "functions.function_one", ...}
  • 在 YAML 文档中可以更改实际的 Lamba 表达式是否重要,还是更多/专门用于文档目的?
  • “我可以将我的 lambda 表达式保存为字符串”是什么意思?您将从该 YAML 文档中加载它们,从而生成一个 Python 字符串。在调用 eval 之前,你会将它们保存到哪里? (将数据结构保存到 YAML 文档的等价物称为 dumping)。
  • @AdamSmith:我喜欢你的建议。我相信只要人们有一些 Python 知识,它就会更好地工作。但是,就我而言,使用我的程序的人不知道如何用 Python 编程。不管怎样,谢谢你的建议!
  • @alexandredias3d 你的用户不会知道 Python,但会知道 lambda 演算?多么奇怪的特定用例!

标签: python python-3.x lambda


【解决方案1】:

我会特别遵守Zen of Python “显式优于隐式”和“可读性很重要”。

因此,将函数作为定义 lambda 的可读字符串是一种 好主意,但出于安全原因,在加载的字符串上调用 eval lambda 的表示可能不是。这 再次取决于谁拥有对文件的修改权限以及 他们运行的系统。

一般来说,如果有人可以,你不应该太在意 (非无意地)注入一些导致递归删除的东西 系统上的所有文件,如果它们具有登录访问权限 他们无论如何都可以这样做。但是,如果例如该软件在遥控器上运行 系统和这些文件可以通过一些网络界面进行编辑,或者如果 文件更改可以由其他人而不是使用 文件,这是你应该考虑的事情。

如果 lambdas 来自固定集合,您可以使用 它们作为查找的字符串表示:

lambdas = {}
for l in [
   'lambda x,y: x+y',
   'lambda x: (x**3)/2',
   # some more
]:
   lambdas[l] = eval(l)

然后您可以使用从配置 YAML 加载的字符串来获取 实际的 lambda 和该字符串不能被篡改,因为它有 以匹配您提供的可用 lambda 集。你当然可以 从只有您可以更改的文件中加载实际的 lambda 字符串, 而不是在源代码中硬编码它们。

这是 IMO 更多 显式而不是转储实际的 lambda,导致 YAML 看起来像:

!!python/name:__main__.%3Clambda%3E

,无论如何都需要不安全地加载 YAML 文档。

如果您需要比使用预定义的 lambda 更灵活,但是 不想使用eval的不安全感,那么另一种可能是 使用 Python 的AST module。那个模块 允许对一元和二元运算符进行安全评估,但可以 扩展为仅处理那些功能(例如一些数学 您希望在 lambda 中允许的函数)。我做过类似的 我的 Python Object Notation 模块中的扩展 (PON) 添加datetime and dedenting AST 评估输入的能力。


另外一点是您应该 IMO 改进您的 YAML。代替 使用gene1gene2 作为映射中的键,使用序列并标记项目:

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

或者,或者标记序列:

pre_init_gene: !Genes
    - 1.0
    - 1.0
    - 1.0

你的 lambdas 有同样的“问题”,我会做类似的事情:

obj_funcs:
   - !Lambda 'x, y: x+y'
   - !Lambda 'x: (x**3)/2'

其中实现from_yamlclassmethod 的对象为 标签!Lambda 透明地进行eval 或AST 评估。

【讨论】:

  • 我喜欢您使用 AST 同时提供可读性和安全性的想法。我一定会读到的。但是,我不确定我是否理解您改进 YAML 的建议:此标签要求我使用名为 from_yaml 的方法实现一个类,以告知 YAML 应如何读取该特定标签,对吗?如果这是真的,在您看来,即使我只对价值观感兴趣,您认为我应该这样做吗?就像metric 的情况一样,例如,它只是一个值列表,你会为它创建一个标签吗?感谢您的宝贵时间!
  • @alexandredias3d 我认为标签在许多加载 YAML 的应用程序中没有得到充分利用,并且经常滥用键来指示值应该是什么类型。带有一个浮点参数的!Gene 示例可能太多了,但是对于您要调用eval 或一些AST 处理的lambda 字符串,IMO 使用标签并获得智能类型更有意义(您总是可以延迟实际处理,直到您尝试访问计算值)。
【解决方案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。无论如何,感谢您的评论!每当我遇到不安全的环境时,我都会考虑您的解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-30
  • 2021-11-27
  • 1970-01-01
  • 1970-01-01
  • 2021-11-03
  • 2018-12-15
相关资源
最近更新 更多