Formatter.check_unused_args
str.format silently ignores arguments the format string never mentions. Formatter calls check_unused_args(used_args, args, kwargs) after formatting — the default does nothing, so a subclass can turn "unused" into an error.
Common call
def check_unused_args(self, used_args, args, kwargs): ...
Returns
None — raise to fail
Replaces
manual comparisons of parse() output against the arguments
Watch out
used_args holds ints for positional and strs for named arguments
Formatter.check_unused_args(used_argsused_args — The key of every field that was formatted: ints for positional arguments (including automatic {}), strs for keywords. Only the first part of a compound field counts — {user.name} adds 'user'.type: set[int | str] · required, argsargs — The positional arguments given to vformat.type: sequence · required, kwargskwargs — The keyword arguments given to vformat.type: mapping · required)
→ None
Demo
Live evaluation
Reject keyword arguments the format string does not use.
Try:
Inputs
fmtstrformat string using {a} and/or {b}
astrvalue for a
bstrvalue for b
Code
import string class Strict(string.Formatter): def check_unused_args(self, used_args, args, kwargs): unused = set(kwargs) - used_args if unused: raise ValueError(f'unused: {sorted(unused)}') Strict().format('{a} and {b}', a='tea', b='cake')
Result
'tea and cake'
used_args is a set, so a field used twice appears once. Fields inside a format spec ({a:{b}}) count too. For {a[0]} only the key a is recorded — the [0] step does not matter. The check runs after all the formatting, so a strict formatter still does the whole job before it raises.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| used_args | set[int | str] | yes | The key of every field that was formatted: ints for positional arguments (including automatic {}), strs for keywords. Only the first part of a compound field counts — {user.name} adds 'user'. |
| args | sequence | yes | The positional arguments given to vformat. |
| kwargs | mapping | yes | The keyword arguments given to vformat. |
Return value
None — The return value is ignored; raise an exception to reject the call.
Common patterns
Strict positional and keyword arguments
Fail on any argument the string does not use.
import string class Strict(string.Formatter): def check_unused_args(self, used_args, args, kwargs): extra = [i for i in range(len(args)) if i not in used_args] extra += [k for k in kwargs if k not in used_args] if extra: raise ValueError(f'unused arguments: {extra}')
Warn instead of fail
Useful while migrating message catalogs.
import string, warnings class Warn(string.Formatter): def check_unused_args(self, used_args, args, kwargs): for k in set(kwargs) - used_args: warnings.warn(f'unused format argument {k!r}')
Examples
1. The default does nothing
import string
string.Formatter().format('{x}', x=1, extra=2)
Returns
'1'2. It returns None
import string
print(string.Formatter().check_unused_args({'x'}, (), {'x': 1, 'y': 2}))
Returns
None3. str.format ignores extras too
'{x}'.format(x=1, extra=2)
Returns
'1'4. A strict subclass
import string
class Strict(string.Formatter):
def check_unused_args(self, used_args, args, kwargs):
if set(kwargs) - used_args:
raise ValueError('unused keyword arguments')
Strict().format('{x}', x=1, extra=2)
Returns
ValueError: unused keyword arguments5. Positional keys are ints
import string
class Spy(string.Formatter):
def check_unused_args(self, used_args, args, kwargs):
self.used = used_args
s = Spy()
s.format('{}{}{x}', 1, 2, x=3)
sorted(s.used, key=str)
Returns
[0, 1, 'x']Pitfalls
1. Comparing used_args with field names
used_args contains keys, not field names: {user.name} records 'user'. Comparing against parse() names gives false alarms.
field names
import string fmt = '{user.name}' names = {n for _, n, _, _ in string.Formatter().parse(fmt) if n} names - {'user'}
{'user.name'}
keys from used_args
import string class Spy(string.Formatter): def check_unused_args(self, used_args, args, kwargs): self.used = used_args s = Spy() class U: name = 'Ada' s.format('{user.name}', user=U()) s.used
{'user'}
2. Forgetting positional arguments
set(kwargs) - used_args only checks keywords; unused positional values pass silently.
keywords only
import string class Strict(string.Formatter): def check_unused_args(self, used_args, args, kwargs): if set(kwargs) - used_args: raise ValueError('unused') Strict().format('{0}', 'a', 'b')
'a'
check indexes too
import string class Strict(string.Formatter): def check_unused_args(self, used_args, args, kwargs): if set(range(len(args))) - used_args or set(kwargs) - used_args: raise ValueError('unused') Strict().format('{0}', 'a', 'b')
ValueError: unused
When to use
Use it
- Catching typos in message catalogs: a value passed but never shown usually means a misspelt field
- APIs where every supplied argument must be used
Reach for something else
- Formatting with large shared context dicts — most keys are unused by design
Notes
CPython impl
vformat: used_args = set(); result = self._vformat(..., used_args, 2); self.check_unused_args(used_args, args, kwargs) — the default body is pass
Timing
Called after the whole string has been formatted, and only if formatting succeeded
Keys
get_field returns (obj, first); vformat adds first to used_args — an int index or a keyword name
FAQ
No. '{x}'.format(x=1, y=2) returns '1' and y is ignored. string.Formatter calls check_unused_args at the end of vformat so that a subclass can raise instead.