首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用同一字典中的key->value替换值中的键。

用同一字典中的key->value替换值中的键。
EN

Code Review用户
提问于 2015-02-11 14:59:12
回答 3查看 13.6K关注 0票数 8

我有一个字典,它的值可能包含字典中已经存在的键。我们实际上谈论的是参数。有时,一个参数由一个数字加上另一个参数定义,等等。

看起来是这样的:

代码语言:javascript
复制
paramDict = {'ad': '2*dmcg/factor*(xw/factor)',
'dmcg': 2.05e-07,
'dxwn_3vna': 10.0,
'factor': 1,
'xw': '0+dxwn_3vna'}

如果值中有另一个参数为string类型,则为intfloat。在替换之后,我希望值是一个浮点数。

我为解决这个问题编写的代码如下:

代码语言:javascript
复制
    for _ in range(10):
        for key, value in paramDict.items():
            if type(value) is str:
                matchList = re.findall('[a-z]\w*', value)
                matchList = [match for match in matchList if match != 'e']
                for match in matchList:
                    param = paramDict[match]
                    paramDict[key] = value.replace(match, str(param))
                try:
                    paramDict[key] = eval(paramDict[key])
                except:
                    pass

据我所知,这是可行的,但只是重复整个过程有限的次数,并希望所有的字符串都被替换是不对的。有没有更安全的方法?

EN

回答 3

Code Review用户

回答已采纳

发布于 2015-02-12 08:33:43

我花了一些时间(这个问题向我提出了挑战),但在尝试了一些(愚蠢的:)过于复杂的方法之后,我想出了一个非常简单的解决方案。它甚至不需要高端库。

我不认为它需要解释,但如果你有问题,我很乐意回答它们。

编辑:

我突然意识到,当一些表达式无法计算时,原始版本并没有处理这种情况。嗯,这可能很容易纠正。

代码语言:javascript
复制
def eval_dict(paramDict):
    evaluated_post_cnt = 0 # Total number of converted from previous loop
    result = {}
    while True:
        for var_name, value in paramDict.iteritems():
            if not var_name in result:
                try:
                    exec('{} = {}'.format(var_name, value))
                    result[var_name] = locals()[var_name]
                except NameError:
                    pass
        all_evaluated_num = len(result) 
        if all_evaluated_num == len(paramDict):
            # All values converted
            return result
        if evaluated_post_cnt == all_evaluated_num:
            # No new keys converted in a loop - dead end
            raise Exception('Cannot convert some keys')
        evaluated_post_cnt = all_evaluated_num

好吧,这是“魔法”:

exec -(不被纯粹主义者推荐)尝试以Python命令行的形式执行字符串,所以如果是,例如

代码语言:javascript
复制
'xw =0+dxwn_3vna'

如果还没有计算NameError异常,则执行将失败--或者将值10.0赋给名为xw的变量--作为常规赋值语句。赋值变量被添加到局部变量集(可以用函数本地人()访问)返回字典。因此,如果代码成功地计算值-它们将在您的字典中被替换。

这简化了代码,因为我不需要用值替换字符串中的变量名-如果这个字符串中的所有变量都是

代码语言:javascript
复制
2*dmcg/factor*(xw/factor)

,则可以执行此字符串。

代码语言:javascript
复制
'ad = 2*dmcg/factor*(xw/factor)'

现在清楚了吗?

票数 2
EN

Code Review用户

发布于 2015-02-11 21:51:26

您想要的是在跟踪依赖项的同时计算表达式,所以您绝对不应该通过正则表达式实现它,并且希望运行它十次就足够了。

Python中最好的方法可能是使用ast模块解析和计算引用的变量,然后通过compileeval计算依赖关系和计算解析表达式,同时在local参数中传递引用的变量。

这样,您就可以确保表达式实际上是有效的,您可以使用Python表达式,您也可以(而且应该)检测规范中的循环。

下面是一个草图如何做到这一点,而不是说那是最好的代码,请编辑或评论与改进,我找不到一个很好的例子搜索。

代码语言:javascript
复制
import ast, _ast

params = {
    'ad': '2*dmcg/factor*(xw/factor)',
    'dmcg': 2.05e-07,
    'dxwn_3vna': 10.0,
    'factor': 1,
    'xw': '0+dxwn_3vna',
    'foo': 'bar',
    'bar': 'foo'
}

parsed = {}

for key, value in params.iteritems():
    parsed[key] = value

    if type(value) in (str, unicode):
        parsed[key] = ast.parse(value, mode="eval")

evaluated = {key: value for key, value in parsed.iteritems()}

因此,此时,evaluated字典主要是params的副本,但表达式已被解析。

代码语言:javascript
复制
def referenced(code):
    return [node.id for node in ast.walk(code) if "id" in node._fields]

referenced返回一个列表,其中包含表达式中引用变量的名称。

代码语言:javascript
复制
evaluating = []

def resolve(key, value):
    if key in evaluating:
        raise Exception("Loop while evaluating {}".format(key))

    if type(value) is not _ast.Expression:
        evaluated[key] = value
        return value

    evaluating.append(key)

    locals = {name: resolve(name, evaluated[name]) for name in referenced(value)}

    result = eval(compile(value, "<unknown>", "eval"), globals(), locals)

    evaluated[key] = result

    evaluating.pop()

    return result

resolve递归地计算表达式,但也跟踪堆栈(在evaluating中),因此它可以通过查找当前变量来检测周期。它还会立即分配结果,因此在进一步的调用中,它将提前返回( not _ast.Expression情况)。

代码语言:javascript
复制
for key, value in parsed.iteritems():
    try:
        resolve(key, value)
    except Exception as exception:
        print ("Error while evaluating {}: {}".format(key, exception))

print(evaluated)

最后,迭代所有条目并尝试对它们进行评估。

票数 6
EN

Code Review用户

发布于 2015-02-13 06:12:11

一种比弗拉达更稳健的技术是在一个假的“范围”中捕捉评估结果,并避免弄乱字符串:

代码语言:javascript
复制
paramDict[var_name] = eval(value, {}, paramDict)

这使locals保持干净。

但是,您不希望从eval可用的所有东西开始,因为您永远不会以这种方式获得NameError。相反,在你去的时候建一本字典。

可能更好的做法是滥用eval的S locals论点,并采用懒散的缓存评估策略:

代码语言:javascript
复制
# from UserDict import UserDict on Python 2
from collections import UserDict

class StringScope(UserDict):
    def __getitem__(self, name):
        expr = self.data[name]

        if isinstance(expr, str):
            expr = self.data[name] = eval(expr, {}, self)

        return expr

scope = StringScope(paramDict)

仅此而已:scope现在将懒洋洋地评估它的值,因为它们是被请求的。如果你想要热切的评价,就做吧

代码语言:javascript
复制
scope = dict(StringScope(paramDict))

而不是。这也将自动完成依赖链的遍历,因此在技术上是更有效的算法。

票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/80273

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档