Created
July 5, 2019 02:41
-
-
Save jlumbroso/57951c06a233c788e00d0fc309a93f91 to your computer and use it in GitHub Desktop.
Python method to format a string with missing keys.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
MissingFormatKey.py | |
Author: Jérémie Lumbroso <lumbroso@cs.princeton.edu> | |
Date: 2019-07-03 | |
Usage: | |
# Define replacement f"..." compatible with Python 2 and 3 | |
_f = _make_f(globals=lambda: globals(), locals=lambda: locals()) | |
# Use: | |
s = "Test" | |
var = 1 | |
assert _f("{s} {var}") == "Test 1" | |
# Inside a non-global scope, you may have to provide locals | |
def test(): | |
l_s = "Test" | |
l_var = 1 | |
assert _f("{l_s} {l_var} / {s} {var}") == "{l_s} {l_var} / Test 1" | |
assert _f("{l_s} {l_var} / {s} {var}", **locals()) == "Test 1 / Test 1" | |
Comments: | |
This example shows how to use the Pythonic paradigm of "asking for | |
forgiveness" to handle format strings that contain missing keys. | |
In this snippet, it is possible either to: | |
- throw an exception, the standardbehavior, with MissingFormatKey.ERROR; | |
- replace the missing key with a blank string, with MissingFormatKey.REMOVE; | |
- replace the key with "{keyname}" so that a further substitution will work | |
as expected, with MissingFormatKey.REMAIN. | |
Note that with MissingFormatKey.REMAIN does not support format strings with | |
additional qualifiers (such as "{key:.5}"). | |
""" | |
try: | |
# Python 3 | |
from enum import Enum as _Enum | |
except ImportError: # pragma: no cover | |
no_enum = True | |
# Python 2 fallbacks | |
try: | |
from aenum import Enum as _Enum | |
no_enum = False | |
except ImportError: | |
try: | |
from enum34 import Enum as _Enum | |
no_enum = False | |
except ImportError: | |
pass | |
if no_enum: | |
raise RuntimeError( | |
""" | |
This package requires an `Enum` object type. These are available | |
as part of the standard library in Python 3.4+, but otherwise | |
require a third-party library, either `enum34` or `aenum`. | |
=> You can install it with `pip` or `pipenv`: | |
pip install --user aenum | |
or | |
pipenv install aenum | |
""") | |
# ============================================================================= | |
class DocEnum(_Enum): | |
def __init__(self, value, doc): | |
# type: (str, str) -> None | |
try: | |
super().__init__() | |
except TypeError: # pragma: no cover | |
# Python 2: the super() syntax was only introduced in Python 3.x | |
super(DocEnum, self).__init__() | |
self._value_ = value | |
self.__doc__ = doc | |
def is_noarg_callable(obj): | |
# type: Any -> bool | |
try: | |
obj() | |
except: | |
return False | |
return True | |
# ============================================================================= | |
class MissingFormatKey(DocEnum): | |
""" | |
Describes all possible ways that the formatting helper method can address | |
the problem of missing format keys. | |
""" | |
ERROR = "missing-keys-error", "Missing format keys throw an error." | |
REMAIN = "missing-keys-remain", """ | |
Missing format keys are unaffected, and | |
can be filled by a later formatting call | |
""" | |
REMOVE = "missing-keys-remove", "Missing format keys are removed." | |
# ============================================================================= | |
def _make_f(globals, locals): | |
def _f(s, missing=MissingFormatKey.REMAIN, **kwargs): | |
""" | |
Formats a string using the local and global symbols available. | |
Suppresses any warning that is not related to string formatting. | |
""" | |
# Resolve the arguments (may be dictionaries or callables) | |
g = globals | |
l = locals | |
if is_noarg_callable(g): | |
g = g() | |
if is_noarg_callable(l): | |
l = l() | |
# Make the substitution if the string provided is not empty | |
if s: | |
try: | |
return s.format(**kwargs, **g, **l) | |
except KeyError as err: | |
missing_key = err.args[0] | |
missing_key_val = None | |
if missing == MissingFormatKey.ERROR: | |
# Re-raise the error | |
raise | |
elif missing == MissingFormatKey.REMAIN: | |
# Replace the missing key by {missing key} so it can | |
# still be substituted by a subsequent call. | |
missing_key_val = "{{{key}}}".format(key=missing_key) | |
elif missing == MissingFormatKey.REMOVE: | |
# Remove the pattern for the missing key | |
missing_key_val = "" | |
new_kw = { missing_key: missing_key_val } | |
return _f( | |
s=s, | |
missing=missing, | |
**new_kw, | |
**kwargs | |
) | |
except ValueError: | |
raise | |
except: | |
pass | |
return _f |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment