Могу ли я использовать динамическое сопоставление для распаковки аргументов ключевых слов в Python? - PullRequest
6 голосов
/ 22 ноября 2011

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

'{Thing1} and {other_thing}'.format(**my_mapping)

Я пытался реализовать my_mapping так:

class Mapping(object):
  def __getitem__(self, key):
    return 'Proxied: %s' % key
my_mapping = Mapping()

Что работает, как и ожидалось, при вызове my_mapping['anything']. Но при передаче в format (), как показано выше, я получаю:

TypeError: format() argument after ** must be a mapping, not Mapping

Я попытался создать подкласс dict вместо object, но теперь вызов format(), как показано, поднимает KeyError. Я даже реализовал __contains__ как return True, но все же KeyError.

Так что, похоже, ** не просто вызывает __getitem__ для переданного объекта. Кто-нибудь знает, как обойти это?

Ответы [ 4 ]

6 голосов
/ 22 ноября 2011

В Python 2 вы можете сделать это, используя класс string.Formatter.

>>> class Mapping(object):
...     def __getitem__(self, key):
...         return 'Proxied: %s' % key
...
>>> my_mapping = Mapping()
>>> from string import Formatter
>>> Formatter().vformat('{Thing1} and {other_thing}', (), my_mapping)
'Proxied: Thing1 and Proxied: other_thing'
>>>

vformat принимает 3 аргумента: строку формата, последовательность позиционных полей и отображение полей ключевых слов.Поскольку позиционные поля не были нужны, я использовал пустой кортеж ().

5 голосов
/ 22 ноября 2011

Python 3.2 +:

'{Thing1} and {other_thing}'.format_map(my_mapping)
2 голосов
/ 27 октября 2014

Это может быть немного некромантия, но я недавно столкнулся с этой проблемой, и этот вопрос SO был первым результатом. Я не был доволен использованием string.Formatter, и хотел, чтобы он Just Work (TM) .

Если вы реализуете функцию keys() для вашего класса, а также __getitem__(), тогда **my_mapping будет работать.

т.е:

class Mapping(object):

  def __getitem__(self, key):
    return 'Proxied: %s' % key

  def keys(self):
    return proxy.keys()

, где

>>> my_mapping = Mapping()
>>> my_mapping.keys()
['Thing1','other_thing',...,'Thing2']

приведет к успешному отображению, которое будет работать с .format.

Очевидно (хотя я на самом деле не смотрел на источник для str.format), похоже, что он использует keys(), чтобы получить список ключей, затем сопоставить идентификаторы, приведенные в строке, с этими ключами, а затем использовать __getitem__() для извлечения указанных значений.

Надеюсь, это поможет.

EDIT :

Если вы находитесь в позиции @ aaron-mcmillin, и набор ключей большой, то возможный подход состоит в том, чтобы не генерировать полный набор ключей, а генерировать меньшее подмножество. Конечно, это работает, только если вы знаете, что вам нужно будет только отформатировать небольшое подмножество.

т.е:

class Mapping(object):
  ...
  def keys(self):
    return ['Thing1','other_thing', 'Thing2']
1 голос
/ 22 ноября 2011

Это лучшее, что я мог придумать:

Если у вас есть пользовательский объект сопоставления, который вы хотите передать в функцию, принимающую аргументы ключевых слов, то он должен иметь набор ключей (которые могут генерироваться динамически, но это должен быть конечный набор) должен быть в состоянии сопоставить эти ключи как-то. Итак, если вы можете предположить, что у него будет __iter__ для получения ключей, и __getitem__, который будет успешным для каждого из этих ключей, например ::

class Me(object):
    def __init__(self):
        pass

    def __iter__(self):
        return iter(['a', 'b', 'c'])

    def __getitem__(self, key):
        return 12

Скажите, что функция:

def myfunc(**kwargs):
    print kwargs, type(kwargs)

Тогда мы можем передать это, сделав диктовку:

m = Me()
myfunc(**dict((k, m[k]) for k in m))

В результате:

{'a': 12, 'c': 12, 'b': 12} <type 'dict'>

Очевидно, это должно быть так, как это делается ... даже если вы передадите объект, полученный из dict, функция все равно будет иметь dict для kwargs:

class Me(dict): pass

m = Me()
print type(m) #prints <class '__Main__.Me'>

def myfunc(**kwargs):
    print type(kwargs)
myfunc(**m) #prints <type 'dict'>

Поскольку звучит так, как будто вы хотели сделать что-то вроде возврата значения, основанного на том, какой была клавиша, без учета определенного набора клавиш, кажется, что вы не можете использовать функцию format.

...