Можно ли использовать динамическое сопоставление для распаковки аргументов ключевого слова в Python?

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

'{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']. Но когда он передается в формат(), как показано выше, я получаю:

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

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

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

Ответ 1

В 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 аргумента: строка формата, последовательность позиционных полей и отображение полей ключевых слов. Поскольку поля позиционирования не нужны, я использовал пустой набор ().

Ответ 2

Python 3.2 +:

'{Thing1} and {other_thing}'.format_map(my_mapping)

Ответ 3

Это может быть немного некромантии, но я недавно столкнулся с этой проблемой, и этот вопрос 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']

Ответ 4

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

Если у вас есть настраиваемый объект сопоставления, который вы хотите передать func, используя аргументы ключевого слова, тогда он должен иметь набор ключей (которые могут быть динамически сгенерированы, но это должен быть конечный набор), и это должны иметь возможность сопоставить эти ключи каким-то образом. Итак, если вы можете предположить, что у него будет __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)

Затем мы можем передать его, сделав dict:

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.