更多pythonic方法来替换字符串中的关键字?

时间:2016-03-12 19:43:54

标签: python

我正在尝试使用以下函数包装API。 API的端点与此类似:

/users/{ids}
/users/{ids}/permissions

我的想法是,我将能够将字典传递给包含ids列表的函数,并将其格式化为API期望:

users = {'ids': [1, 2, 3, 5]}
call_api('/users/{ids}/permissions', users)

然后在call_api,我目前做了类似的事情

def call_api(url, data):
    for k, value in data.items():
        if "{" + key + "}" in url:      
            url = url.replace("{"+k+"}", ';'.join(str(x) for x in value))
            data.pop(k, None)

这有效,但我无法想象if语句是有效的。

如何改进它并让它在Python 2.7和Python 3.5中都有效?

我也被告知在迭代时更改字典很糟糕,但在我的测试中我从来没有遇到过问题。我pop这个值,因为我后来检查是否有意外的参数(即data中剩下的任何内容)。我现在正在以正确的方式做什么?

4 个答案:

答案 0 :(得分:1)

这是做到这一点的方法。首先,解析字符串的键。然后它会记住网址中未使用的所有密钥并将其保存在侧面。最后,它使用dict的给定参数格式化url。该函数返回未使用的变量和格式化的URL。如果您希望通过迭代它们并从dict中删除,可以从dict中删除未使用的变量。 这里有一些文档,其中包含有关format syntax的示例。

import string

users = {'ids': [1, 2, 3, 5]}

def call_api(url, data):
    data_set = set(data)
    formatter = string.Formatter()
    used_set = {f[1] for f in formatter.parse(url) if f[1] is not None}
    unused_set = data_set - used_set
    formatted = url.format(**{k: ";".join(str(x) for x in v) 
                              for k, v in data.items()})
    return unused_set, formatted

print(call_api('/users/{ids}/permissions', users))

答案 1 :(得分:1)

您可以使用re.subn返回替换次数:

import re

def call_api(url, data):
    for k, value in list(data.items()):
        url, n = re.subn(r'\{%s\}' % k, ';'.join(str(x) for x in value), url)
        if n:
            del data[k]

请注意,为了兼容python2和python3,还需要在对dict进行破坏性迭代时创建项列表的副本。

修改

似乎主要的瓶颈是检查密钥是否在网址中。 in运算符是执行此操作的最有效方法,并且比正在使用的简单模式的正则表达式快得多。分别记录未使用的密钥也比破坏性迭代更有效,但它并没有产生那么大的差异(相对而言)。

所以:原始解决方案没有太大问题,但@wegry给出的解决方案效率最高。

答案 2 :(得分:1)

不是在迭代时修改字典,而是创建另一个对象以保存未使用的密钥,这可能是要走的路。至少在Python 3.4+中,在迭代期间删除键会引发一个 RuntimeError: dictionary changed size during iteration

def call_api(url, data):
    unused_keys = set()
    for k, value in data.items():
        key_pattern = "{" + k + "}"
        if key_pattern in url:
            formatted_value = ';'.join(map(str, value))     
            url = url.replace(key_pattern, formatted_value)
        else:
            unused_keys.add(k)

此外,如果您认为自己更有可能遇到未使用的密钥,则可能需要撤消条件。

答案 3 :(得分:1)

可以使用RegEx找到格式化键,然后将其与字典中的键进行比较。您的字符串已设置为使用str.format,因此您将转换应用于数据中的值,然后应用该转换。

import re
from toolz import valmap

def call_api(url, data):
    unused = set(data) - set(re.findall('\{(\w+)\}', url))
    url = url.format_map(valmap(lambda v: ';'.join(map(str, v)), data))
    return url, unused

用法如下:

users = {'ids': [1, 2, 3, 5], 'unused_key': 'value'}
print(call_api('/users/{ids}/permissions', users))
# ('/users/1;2;3;5/permissions', {'unused_key'})

这不是那么好,但它很简洁。正如其中一条评论所述,这种方法似乎不太可能成为瓶颈。