Почему 'var'% {1: 'variable'} печатает 'var' вместо того, чтобы вызывать исключение typeerror, как 'var'% (1,)?

Я отлаживал свой модуль отладки и полагаюсь на попытку ... catch для обнаружения TypeError и правильного форматирования моего сообщения, затем я заметил, что при передаче словарей Python не вызывает традиционное исключение.

>>> 'var' % {1: 'variable'}
'var'
>>> 'var' % (1,)
Traceback (most recent call last):
  File "<string>", line 1, in <module>

Это минимальный пример с модулем ведения журнала:

import logging

class SmartLogRecord(logging.LogRecord):

    def _getMessage(self, remaining_arguments):

        try:

            if self.args:
                remaining_arguments.append( self.msg % self.args )

            else:
                remaining_arguments.append( self.msg )

            return False

        except TypeError as error:
            last = self.args[-1]
            self.args = self.args[:-1]
            remaining_arguments.append( str( last ) )

            if len( self.args ):
                return True

            else:
                remaining_arguments.append( self.msg )
                return False

    def getMessage(self):
        """
        Return the message for this LogRecord.

        Return the message for this LogRecord after merging any user-supplied
        arguments with the message.
        """
        remaining_arguments = []
        self.msg = str( self.msg )

        while self._getMessage( remaining_arguments ): pass
        return " ".join( reversed( remaining_arguments ) )

logging.setLogRecordFactory(SmartLogRecord)

var = 'SmartLogRecord'
logging.warning('I am a', var)

dumb = {1: 'variable'}
logging.warning('I am a', dumb)

Запустив его, вы получите:

WARNING:root:I am a SmartLogRecord
WARNING:root:I am a

Как видите, последнее сообщение dumb было потеряно.

ОТ: Вы действительно хотите внести в свой модуль орфографическую ошибку, чтобы сохранить два символа для имени вашей переменной? remaing действительно режет мне глаза, и это тоже плохая практика.

Frieder 26.10.2018 08:38

Я просто неправильно написал имя и не заметил.

user 26.10.2018 08:48
0
2
33
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Я думаю, что наблюдаемое поведение соответствует документы.

If format requires a single argument, values may be a single non-tuple object. [5] Otherwise, values must be a tuple with exactly the number of items specified by the format string, or a single mapping object (for example, a dictionary).

Примечание [5]:

To format only a tuple you should therefore provide a singleton tuple whose only element is the tuple to be formatted.

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

Это также объясняет, что dict всегда принимается как тип, но может приводить к другим ошибкам.


И на всякий случай я ошибаюсь, есть всеохватывающая возможность, что вы только что обнаружили еще одну "причуду". Они прямо предупреждают:

The formatting operations described here exhibit a variety of quirks that lead to a number of common errors (such as failing to display tuples and dictionaries correctly). Using the newer formatted string literals or the str.format() interface helps avoid these errors.

В качестве обходного пути этой проблемы я предлагаю следующее решение:

import sys
import logging

if sys.version_info[0] < 3:
    is_python2 = True
    from collections import MutableMapping
else:
    from collections.abc import MutableMapping

class SmartLogRecord(logging.LogRecord):

    def _getMessage(self, remaining_arguments):

        try:
            args = self.args

            if args:

                # if isinstance( args, dict ):
                if isinstance( args, MutableMapping ):
                    new_msg = self.msg % args

                    if new_msg == self.msg:
                        remaining_arguments.append( str( args ) )
                        remaining_arguments.append( new_msg )

                    else:
                        remaining_arguments.append( new_msg )

                else:
                    remaining_arguments.append( self.msg % args )

            else:
                remaining_arguments.append( self.msg )

            return False

        except TypeError as error:
            self.args = args[:-1]
            remaining_arguments.append( str( args[-1] ) )

            if len( args ) - 1 > 0:
                return True

            else:
                remaining_arguments.append( self.msg )
                return False

    def getMessage(self):
        """
        Return the message for this LogRecord.

        Return the message for this LogRecord after merging any user-supplied
        arguments with the message.
        """
        remaining_arguments = []
        self.msg = str( self.msg )

        while self._getMessage( remaining_arguments ): pass
        return " ".join( reversed( remaining_arguments ) )

logging.setLogRecordFactory(SmartLogRecord)

var = 'SmartLogRecord'
logging.warning('I am a', var)

dumb = {1: 'variable'}
logging.warning('I am a', dumb)

Что работает правильно:

WARNING:root:I am a SmartLogRecord
WARNING:root:I am a {1: 'variable'}

Другие вопросы по теме