2016-03-12 5 views
2

Я пытаюсь обернуть API со следующей функцией. API имеет конечные точки, которые выглядят примерно так:Подробнее pythonic способ заменить ключевые слова в строке?

/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 s Эффективность.

Как я могу улучшить его и заставить его работать как в Python 2.7, так и в Python 3.5?

Мне также сказали, что изменение словаря при итерации плохо, но в моих тестах у меня никогда не было проблемы. Я принимаю значение, потому что позже проверяю, есть ли неожиданные параметры (т.е. что-то осталось в data). Что я сейчас делаю правильно?

+0

Операция if кажется прекрасной для того, что может быть относительно небольшим количеством ключей. Вы можете попытаться «определить» значение с помощью регулярного выражения, чтобы извлечь все, что находится между {и}, но если ваши «данные» имеют только несколько ключей, не беспокойтесь. И я не вижу причин для изменения значений «данных» вообще. Почему вы выскакиваете? –

+0

Я не показывал полную функцию @AustinHastings, но позже в функции мне нужно увидеть, являются ли эти «неизвестные» ключи - те, которые не были выскочены. У меня есть дополнительная логика для выполнения чего-либо с оставшимися ключами/значениями. – NewGuy

+0

Всего около 10 ключей – NewGuy

ответ

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) 

Кроме того, если вы думаете, что вы, скорее всего, работать в неиспользуемый ключ, реверсивный условия может быть путь.

1

Вот как это сделать. Сначала строка анализируется для ключей. Затем он запоминает все ключи, которые не используются в URL-адресе, и сохраняет их в стороне. Наконец, он форматирует URL с заданными параметрами dict. Функция возвращает неиспользуемые переменные и форматированный 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

Это решение разбивает код OPs, поскольку он удаляет элементы из данных, когда URL *** не содержит *** ключа. Проверка 'if' необходима, потому что OP хочет обнаружить неожиданные параметры. – ekhumoro

+0

Я исправил код. Теперь решение обнаружит неожиданные параметры и заменит все существующее. – Bharel

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.

EDIT:

Кажется, что основное препятствие является проверка, что ключ находится в ссылке. Оператор in - это самый эффективный способ сделать это и намного быстрее, чем регулярное выражение для простого шаблона, который используется здесь. Запись неиспользуемых ключей отдельно также более эффективна, чем деструктивная итерация, но она не делает столько разницы (относительно говоря).

Итак: в оригинальном решении нет ничего плохого, но тот, который дает @wegry, является наиболее эффективным.

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'}) 

Это не будет времени, что хорошо, но это кратким. Как отмечается в одном из комментариев, маловероятно, что этот метод является узким местом.

+0

Имейте в виду, что OP хочет отслеживать, какие слова не были преобразованы. – Bharel

+0

@Bharel Я обновил ответ соответствующим образом. –

Смежные вопросы