Запис даних змінних у рядок нового формату


85

Я використовую систему реєстрації для python 2.7.3. У документації до цієї версії Python сказано :

пакет реєстрації попередньо датує нові варіанти форматування, такі як str.format () та string.Template. Підтримуються ці новіші варіанти форматування ...

Мені подобається "новий" формат з фігурними дужками. Тож я намагаюся зробити щось на зразок:

 log = logging.getLogger("some.logger")
 log.debug("format this message {0}", 1)

І отримати помилку:

TypeError: не всі аргументи перетворені під час форматування рядка

Чого я тут сумую?

PS Я не хочу використовувати

log.debug("format this message {0}".format(1))

оскільки в цьому випадку повідомлення завжди форматується незалежно від рівня реєстратора.


1
Ви можете зробити це: log.debug("format this message%d" % 1)
ronak

1
Вам потрібно налаштувати Formatterвикористання
символу

2
@ronak Дякую за пораду, але ні. Будь ласка, див. Розділ "ps", чому. До речі, log.debug ("форматувати це повідомлення% d", 1) - працює нормально.
MajesticRa

@mata Як його налаштувати? Чи існує пряме підтвердження цього?
MajesticRa

@mata Я знайшов. Будь ласка, дайте відповідь, щоб я міг встановити його як "правильну відповідь. Ще раз дякую.
MajesticRa

Відповіді:


38

EDIT: подивіться на StyleAdapterпідхід у відповіді @Dunes на відміну від цієї відповіді; це дозволяє використовувати альтернативні стилі форматування без шаблону під час виклику методів реєстратора (debug (), info (), error () тощо).


З документації - Використання альтернативних стилів форматування :

Журналізовані виклики (logger.debug (), logger.info () тощо) приймають позиційні параметри лише для власне повідомлення про реєстрацію, при цьому параметри ключових слів використовуються лише для визначення параметрів, як обробляти фактичний виклик реєстрації (наприклад, параметр ключового слова exc_info щоб вказати, що інформацію про зворотне відстеження слід реєструвати, або додатковий параметр ключового слова, щоб вказати додаткову контекстну інформацію, яку слід додати до журналу). Отже, ви не можете безпосередньо здійснювати дзвінки в журнал, використовуючи str.format () або string.Template синтаксис, оскільки внутрішньо пакет реєстрації використовує% -formatting для об'єднання рядка формату та аргументів змінної. Цього не можна було б змінити, зберігаючи зворотну сумісність, оскільки всі виклики журналу, які є в існуючому коді, будуть використовувати рядки% -format.

І:

Однак є спосіб, за допомогою якого ви можете використовувати {} - та $ - форматування для побудови ваших індивідуальних повідомлень журналу. Нагадаємо, що для повідомлення ви можете використовувати довільний об'єкт як рядок формату повідомлення, і що пакет реєстрації викличе str () для цього об'єкта, щоб отримати фактичний рядок формату.

Скопіюйте та вставте це в whereverмодуль:

class BraceMessage(object):
    def __init__(self, fmt, *args, **kwargs):
        self.fmt = fmt
        self.args = args
        self.kwargs = kwargs

    def __str__(self):
        return self.fmt.format(*self.args, **self.kwargs)

Тоді:

from wherever import BraceMessage as __

log.debug(__('Message with {0} {name}', 2, name='placeholders'))

Примітка: фактичне форматування затримується, поки це не буде необхідним, наприклад, якщо повідомлення DEBUG не реєструються, то форматування взагалі не виконується.


4
Станом на Python 3.6, ви можете використовувати f-рядки так:num = 2; name = 'placeholders'; log.debug(f'Message with {num} {name}')
Jacktose

11
@ P1h3r1e3d13 на відміну від коду реєстрації у відповіді, f '' - рядки виконують форматування негайно.
jfs

1
Правильно. Вони працюють тут, оскільки вони форматують і повертають звичайний рядок перед викликом методу log. Це може бути чи не стосуватися когось, тому я думаю, що це варто зазначити як варіант.
Jacktose 06.03.18

3
@Jacktose Я думаю, що користувачі не повинні реєструватися за допомогою f-рядків, це перемагає служби агрегації журналів (наприклад, часовий). Існує поважна причина, що реєстрація stdlib відкладає шаблонізацію рядків.
wim

31

Ось ще один варіант, який не має проблем із ключовими словами, згаданих у відповіді Dunes. Він може обробляти лише позиційні ( {0}) аргументи, а не аргументи ключових слів ( {foo}). Також не потрібно два виклики для форматування (з використанням символу підкреслення). Він має коефіцієнт ick для підкласифікації str:

class BraceString(str):
    def __mod__(self, other):
        return self.format(*other)
    def __str__(self):
        return self


class StyleAdapter(logging.LoggerAdapter):

    def __init__(self, logger, extra=None):
        super(StyleAdapter, self).__init__(logger, extra)

    def process(self, msg, kwargs):
        if kwargs.pop('style', "%") == "{":  # optional
            msg = BraceString(msg)
        return msg, kwargs

Ви використовуєте його так:

logger = StyleAdapter(logging.getLogger(__name__))
logger.info("knights:{0}", "ni", style="{")
logger.info("knights:{}", "shrubbery", style="{")

Звичайно, ви можете видалити позначку, позначену, # optionalщоб змусити всі повідомлення через адаптер використовувати форматування нового стилю.


Примітка для тих, хто читає цю відповідь роками пізніше : Починаючи з Python 3.2 , ви можете використовувати параметр style із Formatterоб’єктами:

Ведення журналу (станом на 3.2) забезпечує покращену підтримку цих двох додаткових стилів форматування. Клас Formatter був вдосконалений, щоб взяти додатковий, необов’язковий параметр ключового слова з іменем style. Це значення за замовчуванням '%', але інші можливі значення '{'і '$', які відповідають двом іншим стилям форматування. Зворотна сумісність підтримується за замовчуванням (як і слід було очікувати), але, явно вказавши параметр стилю, ви отримуєте можливість вказувати рядки форматування, які працюють з str.format()або string.Template.

Документи наводять приклад logging.Formatter('{asctime} {name} {levelname:8s} {message}', style='{')

Зверніть увагу, що в цьому випадку ви все одно не можете зателефонувати loggerза новим форматом. Тобто, наступне все одно не буде працювати:

logger.info("knights:{say}", say="ni")  # Doesn't work!
logger.info("knights:{0}", "ni")  # Doesn't work either

5
Ваше твердження про Python 3 є неправильним. Параметр стилю застосовується лише до рядка формату Formatter, а не до окремих повідомлень журналу. Сторінка, на яку ви посилаєтеся, прямо говорить: "Це не змінить, зберігаючи зворотну сумісність".
mhsmith

1
Дякую, що тримаєте мене чесно. Зараз перша частина менш корисна, але я переформулював її з точки зору Formatter, що зараз є правильним (я думаю). Все StyleAdapter ще працює,
Феліпе

@falstro - дякую, що вказали на це. Оновлена ​​версія тепер повинна працювати. Оскільки BraceStringце підклас рядків, з нього можна повернутися__str__
Феліпе,

1
єдина відповідь, в якій згадується style = "{", +1
Том С.

24

Це було моє рішення проблеми, коли я виявив, що журналювання використовує лише форматування в стилі printf. Це дозволяє зберігати журнали дзвінків незмінними - жодного спеціального синтаксису, такого як log.info(__("val is {}", "x")). Зміна, необхідна для кодування, полягає в тому, щоб обгортати реєстратор в StyleAdapter.

from inspect import getargspec

class BraceMessage(object):
    def __init__(self, fmt, args, kwargs):
        self.fmt = fmt
        self.args = args
        self.kwargs = kwargs

    def __str__(self):
        return str(self.fmt).format(*self.args, **self.kwargs)

class StyleAdapter(logging.LoggerAdapter):
    def __init__(self, logger):
        self.logger = logger

    def log(self, level, msg, *args, **kwargs):
        if self.isEnabledFor(level):
            msg, log_kwargs = self.process(msg, kwargs)
            self.logger._log(level, BraceMessage(msg, args, kwargs), (), 
                    **log_kwargs)

    def process(self, msg, kwargs):
        return msg, {key: kwargs[key] 
                for key in getargspec(self.logger._log).args[1:] if key in kwargs}

Використання:

log = StyleAdapter(logging.getLogger(__name__))
log.info("a log message using {type} substitution", type="brace")

Варто зазначити , що ця реалізація має проблеми , якщо ключові слова , використовувані для заміни распорной включають level, msg, args, exc_info, extraабо stack_info. Це імена аргументів, що використовуються logметодом Logger. Якщо вам потрібно одне з цих імен, змініть, processщоб виключити ці імена, або просто видаліть log_kwargsіз _logдзвінка. Далі, ця реалізація також мовчки ігнорує неправильно написані ключові слова, призначені для реєстратора (наприклад, ectra).


4
Цей спосіб рекомендує python doc, docs.python.org/3/howto/…
eshizhan

23

Більш простим рішенням було б використання чудового logbookмодуля

import logbook
import sys

logbook.StreamHandler(sys.stdout).push_application()
logbook.debug('Format this message {k}', k=1)

Або більш повне:

>>> import logbook
>>> import sys
>>> logbook.StreamHandler(sys.stdout).push_application()
>>> log = logbook.Logger('MyLog')
>>> log.debug('Format this message {k}', k=1)
[2017-05-06 21:46:52.578329] DEBUG: MyLog: Format this message 1

Це виглядає чудово, але чи є спосіб мати мілісекунди, а не просто секунди?
Джефф

@Jeff впевнений, що журнал дозволяє вам визначати власні обробники та використовувати власні формати рядків.
Thomas Orozco,

5
@Jeff Кілька років потому - точність часу за замовчуванням становить мілісекунди.
Ян Влчинський,

12

Як зазначаються в інших відповідях, форматування в дужковому стилі, введене в Python 3.2 , використовується лише в рядку форматування, а не в власне повідомленнях журналу.

Щоб увімкнути форматування в дужковому стилі для власне повідомлення журналу, ми можемо виправити трохи коду реєстратора.

Наступні виправлення loggingмодуля для створення get_loggerфункції, яка повертає реєстратор, який використовує форматування нового стилю для кожного запису журналу, який він обробляє.

import functools
import logging
import types

def _get_message(record):
    """Replacement for logging.LogRecord.getMessage
    that uses the new-style string formatting for
    its messages"""
    msg = str(record.msg)
    args = record.args
    if args:
        if not isinstance(args, tuple):
            args = (args,)
        msg = msg.format(*args)
    return msg

def _handle_wrap(fcn):
    """Wrap the handle function to replace the passed in
    record's getMessage function before calling handle"""
    @functools.wraps(fcn)
    def handle(record):
        record.getMessage = types.MethodType(_get_message, record)
        return fcn(record)
    return handle

def get_logger(name=None):
    """Get a logger instance that uses new-style string formatting"""
    log = logging.getLogger(name)
    if not hasattr(log, "_newstyle"):
        log.handle = _handle_wrap(log.handle)
    log._newstyle = True
    return log

Використання:

>>> log = get_logger()
>>> log.warning("{!r}", log)
<logging.RootLogger object at 0x4985a4d3987b>

Примітки:

  • Повністю сумісний із звичайними методами ведення журналу (просто замінити logging.getLoggerна get_logger)
  • Впливатиме лише на конкретні реєстратори, створені get_loggerфункцією (не порушує сторонні пакети).
  • Якщо до реєстратора знову отримати доступ із звичайного logging.getLogger()дзвінка, форматування в новому стилі все одно застосовуватиметься.
  • kwargs не підтримує ( НЕ дає можливість конфлікт з вбудованим exc_info, stack_info, stacklevelі extra).
  • Показ продуктивності повинен бути мінімальним (переписування єдиного покажчика функції для кожного повідомлення журналу).
  • Форматування повідомлення затримується, поки воно не виводиться (або взагалі не виконується, якщо повідомлення журналу відфільтровано).
  • Аргументи зберігаються на logging.LogRecordоб’єктах як зазвичай (корисно в деяких випадках із користувальницькими обробниками журналів).
  • З огляду на loggingвихідний код модуля здається, що він повинен працювати аж до Python 2.6, коли він str.formatбув представлений (але я протестував його лише в версії 3.5 і вище)

2
Єдина відповідь, яка вважає, що рядок налагодження слід обчислювати лише тоді, коли надруковується повідомлення налагоджувача. Дякую!
Фафаман

2

Спробуйте logging.setLogRecordFactoryв Python 3.2+:

import collections
import logging


class _LogRecord(logging.LogRecord):

    def getMessage(self):
        msg = str(self.msg)
        if self.args:
            if isinstance(self.args, collections.Mapping):
                msg = msg.format(**self.args)
            else:
                msg = msg.format(*self.args)
        return msg


logging.setLogRecordFactory(_LogRecord)

Це працює, але проблема полягає в тому, що ви зламаєте сторонні модулі, які використовують %форматування, оскільки фабрика записів є загальною для модуля реєстрації.
jtaylor

1

Я створив спеціальний Formatter під назвою ColorFormatter, який вирішує проблему так:

class ColorFormatter(logging.Formatter):

    def format(self, record):
        # previous stuff, copy from logging.py…

        try:  # Allow {} style
            message = record.getMessage()  # printf
        except TypeError:
            message = record.msg.format(*record.args)

        # later stuff…

Це підтримує його сумісність з різними бібліотеками. Недоліком є ​​те, що він, ймовірно, не працює, оскільки два рази спробував формат рядка.


0

Ось щось справді просте, що працює:

debug_logger: logging.Logger = logging.getLogger("app.debug")

def mydebuglog(msg: str, *args, **kwargs):
    if debug_logger.isEnabledFor(logging.DEBUG):
        debug_logger.debug(msg.format(*args, **kwargs))

Тоді:

mydebuglog("hello {} {val}", "Python", val="World")

0

Подібне рішення pR0Ps ', упаковка getMessageв LogRecordпо упаковці makeRecord(замість того , щоб handleв їх відповіді) у випадках , Loggerякі повинні бути новими форматування з підтримкою:

def getLogger(name):
    log = logging.getLogger(name)
    def Logger_makeRecordWrapper(name, level, fn, lno, msg, args, exc_info, func=None, extra=None, sinfo=None):
        self = log
        record = logging.Logger.makeRecord(self, name, level, fn, lno, msg, args, exc_info, func, sinfo)
        def LogRecord_getMessageNewStyleFormatting():
            self = record
            msg = str(self.msg)
            if self.args:
                msg = msg.format(*self.args)
            return msg
        record.getMessage = LogRecord_getMessageNewStyleFormatting
        return record
    log.makeRecord = Logger_makeRecordWrapper
    return log

Я перевірив це на Python 3.5.3.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.