Как заставить Python изящно форматировать None и несуществующие поля [дубликаты]

Если я пишу на Python:

data = {'n': 3, 'k': 3.141594, 'p': {'a': 7, 'b': 8}}
print('{n}, {k:.2f}, {p[a]}, {p[b]}'.format(**data))
del data['k']
data['p']['b'] = None
print('{n}, {k:.2f}, {p[a]}, {p[b]}'.format(**data))

Я получил:

3, 3.14, 7, 8
Traceback (most recent call last):
  File "./funky.py", line 186, in <module>
    print('{n}, {k:.2f}, {p[a]}, {p[b]}'.format(**data))
KeyError: 'k'

Вместо сообщения об ошибке, как мне заставить Python более изящно форматировать поля None и несуществующие?

Чтобы привести пример, я хотел бы видеть на выходе что-то более похожее на:

3, 3.14, 7, 8
3, ~, 7, ~

В идеале, конечно, я хотел бы указать строку, используемую вместо этих отсутствующих значений.


person Juan A. Navarro    schedule 27.11.2013    source источник
comment
См. также: пустые значения python str.format   -  person dreftymac    schedule 29.10.2017


Ответы (3)


В PEP 3101 рекомендуется создать подкласс Форматтер:

import string
class PartialFormatter(string.Formatter):
    def __init__(self, missing='~~', bad_fmt='!!'):
        self.missing, self.bad_fmt=missing, bad_fmt

    def get_field(self, field_name, args, kwargs):
        # Handle a key not found
        try:
            val=super(PartialFormatter, self).get_field(field_name, args, kwargs)
            # Python 3, 'super().get_field(field_name, args, kwargs)' works
        except (KeyError, AttributeError):
            val=None,field_name 
        return val 

    def format_field(self, value, spec):
        # handle an invalid format
        if value==None: return self.missing
        try:
            return super(PartialFormatter, self).format_field(value, spec)
        except ValueError:
            if self.bad_fmt is not None: return self.bad_fmt   
            else: raise

fmt=PartialFormatter()
data = {'n': 3, 'k': 3.141594, 'p': {'a': '7', 'b': 8}}
print(fmt.format('{n}, {k:.2f}, {p[a]}, {p[b]}', **data))
# 3, 3.14, 7, 8
del data['k']
data['p']['b'] = None
print(fmt.format('{n}, {k:.2f}, {p[a]:.2f}, {p[b]}', **data))
# 3, ~~, !!, ~~

В соответствии с настройками он будет печатать ~~, если поле или атрибут не найдены, и !!, если для значения поля используется недопустимый формат. (Просто используйте None для аргумента ключевого слова bad_fmt, если вы хотите, чтобы по умолчанию поднималась ошибка значения.)

Чтобы обработать отсутствующие ключи, вам нужно создать подкласс как get_field, чтобы поймать KeyError, так и AttributeError и format_field, чтобы вернуть значение по умолчанию для отсутствующего ключа.

Поскольку вы отлавливаете ошибки format_field, вы также можете отловить поле неправильного формата, перехватив ValueError из суперкласса.

person dawg    schedule 27.11.2013
comment
Спасибо, я думаю, что эта версия более надежна. Я немного подправил его, так что ошибки формата по-прежнему вызывают исключения, моя версия здесь: gist. github.com/navarroj/7689682 - person Juan A. Navarro; 28.11.2013
comment
Ошибка здесь, я думаю, в том, что форматировщики не используют свои собственные self.missing: val=None,field_name vs val=self.missing,field_name - person royal; 12.12.2018
comment
Если вы посмотрите ниже под format_field, вы увидите, где None обрабатывается с if value==None: return self.missing - person dawg; 12.12.2018

Если вы можете выполнять форматирование отдельно, вы можете использовать Template.safe_substitute, который изящно обрабатывает отсутствующие значения:

>>> from string import Template
>>> t = Template("$a $b $c")
>>> t.safe_substitute(a=3)
'3 $b $c'
person Simeon Visser    schedule 27.11.2013
comment
Я тоже думал об этом. Но как тогда заменить незаменяемые поля на ~ или другую строку? Также, конечно, я хочу форматировать поплавки и т.д. - person Juan A. Navarro; 27.11.2013

Метод str.format() не дает вам прямого метода для обработки отсутствующих ключей или замены значений.

Вы можете добавить уровень косвенности; передайте сопоставление, которое обрабатывает отсутствующие и None значения, и измените формат, чтобы использовать только этот аргумент:

class PlaceholderFormatValue():
    def __format__(self, spec):
        return '~'
    def __getitem__(self, name):
        # handle further nested item access
        return self

class formatting_dict(dict):
    def __getitem__(self, name):
        value = self.get(name)
        if isinstance(value, dict):
            # rewrap nested dictionaries to handle missing nested keys
            value = type(self)(value)
        return value if value is not None else PlaceholderFormatValue()

print('{0[n]}, {0[k]:.2f}, {0[p][a]}, {0[p][b]}'.format(formatting_dict(data)))

Теперь все слоты ссылаются на позиционный аргумент 0, который обрабатывается как словарь, но поиск по ключу всегда завершается успешно, а отсутствующие значения и None заменяются значением-заполнителем.

Здесь PlaceholderFormatValue() гарантирует, что независимо от того, что дает спецификация формата, значение может быть интерполировано в формат. Например, это заставляет работать {0[k]:.2f}.

Обертывая любые значения dict и имея доступ к элементу дескриптора PlaceholderFormatValue, вышеизложенное также может обрабатывать отказ от предоставления вложенных ключей или целых словарей:

>>> data = {'n': 3, 'k': 3.141594, 'p': {'a': 7, 'b': 8}}
>>> del data['k']
>>> data['p']['b'] = None
>>> print('{0[n]}, {0[k]:.2f}, {0[p][a]}, {0[p][b]}'.format(formatting_dict(data)))
3, ~, 7, ~
>>> del data['p']['a']
>>> print('{0[n]}, {0[k]:.2f}, {0[p][a]}, {0[p][b]}'.format(formatting_dict(data)))
3, ~, ~, ~
>>> del data['p']
>>> print('{0[n]}, {0[k]:.2f}, {0[p][a]}, {0[p][b]}'.format(formatting_dict(data)))
3, ~, ~, ~
person Martijn Pieters    schedule 27.11.2013
comment
Это хорошо, но все еще не совсем работает. Если я удаляю ключ b внутри data['p'], я все равно получаю сообщение об ошибке (я думаю, потому что форматирование 0[p] вернуло обычный dict, а не formatting_dict), также, если я удаляю весь p в data, тогда он ломается, поскольку он пытается написать не- скриптовый PlaceholderFormatValue. - person Juan A. Navarro; 27.11.2013
comment
@JuanA.Navarro: Верно, с поправкой на эти два случая. - person Martijn Pieters; 27.11.2013
comment
The str.format() method doesn't give you a direct method to handle missing keys or replace values. -- Да, конечно. Просто подкласс Formatter. - person dawg; 27.11.2013
comment
@dawg: Однако это не метод str.format(). Это класс Formatter. - person Martijn Pieters; 27.11.2013
comment
Цитата из документации: «Класс Formatter в модуле string позволяет создавать и настраивать собственные способы форматирования строк, используя ту же реализацию, что и встроенный метод format()».» Почему вы так говорите? не такой же? - person dawg; 27.11.2013
comment
@dawg: Потому что вы не звоните str.format(); вы используете отдельный класс. :-) На что, я признаю, мне следовало обратить внимание, но это не одно и то же. :-) - person Martijn Pieters; 27.11.2013
comment
@dawg Formatter устарел, начиная с версии 3.4: из-за недостаточного использования модуль форматирования устарел. Цитируется из официальной документации Python - person loko; 16.06.2021