Optional keys in string formats using '%' operator?
<p>Is is possible to have optional keys in <a href="https://docs.python.org/2/library/stdtypes.html#string-formatting-operations" rel="nofollow">string formats</a> using '%' operator?
Iâm using the <strong>logging</strong> API with PythonÂ 2.7, so I can't use <a href="https://www.python.org/dev/peps/pep-3101/" rel="nofollow">Advanced String Formatting</a>.</p>

<p>My problem is as follow:</p>

<pre><code>&gt;&gt;&gt; import logging

&gt;&gt;&gt; FORMAT = '%(asctime)-15s %(message)s %(user)s'
&gt;&gt;&gt; logging.basicConfig(format=FORMAT)

&gt;&gt;&gt; logging.warning("It works for:", extra={'user': 'me'})
2016-08-29 11:24:31,262 It works for: me

&gt;&gt;&gt; logging.warning("It does't work!")
Traceback (most recent call last):
  ...
KeyError: 'user'
Logged from file &lt;input&gt;, line 1
</code></pre>

<p>I want to have an empty string for <em>user</em> if missing. How can I do that?</p>

<p>I tried with a <a href="https://docs.python.org/2/library/collections.html#collections.defaultdict" rel="nofollow">defaultdict</a>, but it fails:</p>

<pre><code>&gt;&gt;&gt; import collections
&gt;&gt;&gt; extra = collections.defaultdict(unicode)
&gt;&gt;&gt; logging.warning("It does't work!", extra=extra)
Traceback (most recent call last):
  ...
KeyError: 'user'
Logged from file &lt;input&gt;, line 1
</code></pre>

<p>By contrast, with <a href="http://jinja.pocoo.org/docs/dev/" rel="nofollow">Jinja2</a>, we can do:</p>

<pre><code>&gt;&gt;&gt; import jinja2
&gt;&gt;&gt; jinja2.Template('name: {{ name }}, email: {{ email }}').render(name="me")
u'name: me, email: '
</code></pre>

<p>=> no exception here, just an empty string (for "email").</p>

<p>A) The <code>defaultdict</code> approach works fine, but only if used directly.</p>

<pre><code>&gt;&gt;&gt; import collections
&gt;&gt;&gt; dd=collections.defaultdict(str)
&gt;&gt;&gt; dd['k'] = 22
&gt;&gt;&gt; '%(k)s %(nn)s' % dd
'22 '
</code></pre>

<hr>

<p>B) The <code>extra</code> argument to a log function is used as described in the docs, i.e. not directly as shown above. That's why using a <code>defaultdict</code> instead of a regular <code>dict</code> does not make a difference.</p>

<blockquote>
  <p>The third keyword argument is extra which can be used to pass a
  dictionary which is used to populate the <strong>dict</strong> of the LogRecord
  created for the logging event with user-defined attributes.</p>
</blockquote>

<hr>

<p>C) You can use a logging filter to take care of the missing extra data:</p>

<pre><code>import logging

class UserFilter:
    def filter(self, record):
        try:
            record.user
        except AttributeError:
            record.user = '&lt;N/A&gt;'
        return True

FORMAT = '%(asctime)-15s %(message)s %(user)s'
logging.basicConfig(format=FORMAT)
logging.getLogger().addFilter(UserFilter())

logging.warning("It works for:", extra={'user': 'me'})

logging.warning("It doesn't work!")
# DATE TIME It doesn't work! &lt;N/A&gt;
</code></pre>

<p>Any class with a <code>filter</code> method is fine. It can modify the record in-place and it must return <code>True</code> for accepting the record or <code>False</code> for filtering it out.</p>

python python-2.7 string-formatting