【问题标题】:exec not working when creating functions from string从字符串创建函数时执行不工作
【发布时间】:2019-08-17 05:42:07
【问题描述】:

长话短说,我正在对 python 中的方法进行一些动态属性注入测试。

现在我遇到的问题是,当我在 getter 和 setter 字符串上调用 exec() 以将它们转换为动态创建的函数时,它们仍然是字符串。

def _injectProperties(self): 
        """docstring"""

        for p in self.InputParameters:
            index = p.Order
            name = p.Name.lstrip('@')
            pName = p.Name

            fnGet = (
                "def get(self): \n"
                "    rtnVal = [p.Value for p in self.InputParameters "
                "        if p.Name == '{0}'][0] \n"
                "    return rtnVal ").format(p.Name)

            fnSet = (
                "def set(self, value): \n"
                "    prop = [p for p in self.InputParameters "
                "        if p.Name == '{0}'][0] \n"
                "    prop.Value = value \n"
                "    return ").format(p.Name)

            exec(fnGet) in locals()
            exec(fnSet) in locals()

            self._addprop(name, fnGet, fnSet)

        return

所以基本上在上面的代码中_addprop 是一个简单地创建类的副本并为其设置属性的函数:

setattr(cls, name, property(fget=getter, fset=setter, fdel=destructor, doc=docstring))

为什么在这种情况下fnGetfnSet 变量在我调用exec(fnGet)exec(fnSet) 之后仍然引用get 和set 函数的字符串表示形式?

【问题讨论】:

  • 把 exec 改成 setattr(object, funname, anonymous_function)
  • 您为什么希望 exec 将您的 fnGetfnSet 变量转换为(我假设的)函数?
  • 顺便说一句,exec <code> in <scope> 语法已在 python 3 中删除。您应该使用exec(<code>, <scope>)
  • 只有当你的字符串变量与函数同名时才会发生这种情况。在这种情况下,函数定义会覆盖变量。但是你的函数被命名为get/set,而不是fnGet/fnSet
  • @JamieMarshall 反正没必要这么麻烦。您可以定义__getattr__来处理未定义的属性,而不是执行添加方法。

标签: python python-3.x properties setter getter


【解决方案1】:

您可以使用__getattr__,而不是使用 exec 来注入属性。它在缺少属性时调用。

我认为这可以满足您的需要:

def __getattr__(self, attr):
    for p in self.InputParameters:
        if p.Name == attr:
            return p.Value

def __setattr__(self, attr, value):
    for p in self.InputParameters:
        if p.Name == attr:
            p.Value = value
            break

【讨论】:

  • 我对这个解决方案的担忧是智能感知不会获取属性,因此我团队中的其他程序员必须知道 self.InputParameters 列表中对象的内容才能使用它.此外,它附加到对象上,因此如果我想跨多个类使用注入,它并不是特别模块化。这就是我将@martineau 的答案标记为“答案”的原因。我可以轻松地将该代码放入一个辅助类中,并在我不想注入属性的任何其他类上调用它。
【解决方案2】:

您没有在问题中提供MCVE。所以我做了一些可运行的东西来说明你如何做到这一点(尽管我认为@Ned Batchelder 可能有更好的建议)。

请注意,这也显示了我认为嵌入函数源代码的更好方法。

from textwrap import dedent

class InputParameter:  # Mock for testing
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)


class Class:
    def __init__(self):
        self.InputParameters = [  # For testing
            InputParameter(Order=42, Name='@foobar'),
        ]

    def _addprop(self, name, getter, setter):
        print('_addprop({!r}, {}, {})'.format(name, getter, setter))

    def _injectProperties(self):
            """docstring"""

            for p in self.InputParameters:
                index = p.Order
                name = p.Name.lstrip('@')
                pName = p.Name

                fnGet = dedent("""
                    def get(self):
                        rtnVal = [p.Value for p in self.InputParameters
                                    if p.Name == '{0}'][0]
                        return rtnVal
                """).format(p.Name)

                fnSet = dedent("""
                    def set(self, value):
                        prop = [p for p in self.InputParameters
                                    if p.Name == '{0}'][0]
                        prop.Value = value
                        return
                """).format(p.Name)

                locals_dict = {}
                exec(fnGet, globals(), locals_dict)
                exec(fnSet, globals(), locals_dict)

                self._addprop(name, locals_dict['get'], locals_dict['set'])

            return

cls = Class()
cls._injectProperties()

输出:

_addprop('foobar', <function get at 0x00270858>, <function set at 0x00572C00>)

【讨论】:

  • 是的,就是这样,这是我的愚蠢的语法错误。 locals_dict['get'] 是吸引我的部分。在我的交互式环境中进行测试时,我的 getter 的名称与保存字符串表示的变量相同。在我的测试/问题中并非如此。很好的收获。
  • @Jamie:很高兴听到它解决了您的问题。另请注意,当使用exec() 时,如果正在执行的代码创建了您想在之后检索的任何变量,您通常应该向其传递一个空的“locals”字典。这是因为它使用 locals() 字典的默认值,不应该(不能可靠地)修改它。
  • @JamieMarshall 甚至这个答案的作者也认为“@Ned Batchelder 可能有更好的建议”。 :) 这是很多你不需要的代码...
  • @NedBatchelder,您的解决方案非常相关,但我认为这对于我的构建来说是优越的,有几个原因。我已经评论了您的回答以进一步解释。
猜你喜欢
  • 2017-10-26
  • 1970-01-01
  • 1970-01-01
  • 2016-09-07
  • 2023-04-09
  • 2022-01-24
  • 2011-08-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多