Python 3 introduced a useful feature: keyword-only arguments. In order to get the same effect in Python 2, you must use **kwargs in your parameter list. Then, at the beginning of your function body you must manually extract what would be the keyword-only arguments once you upgrade to 3.
This recipe helps reduce the boilerplate to a single function call. You still don't get those parameters in your "def" clause (where they are more obvious), but at least it reduces the clutter.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | """kwonly module
"""
def emulate_kwonly(kws, required, withdefaults, leftovers=False):
"""Emulate Python 3's kwonly arguments.
Parameters:
kws - the kwargs from which to extract the kwonly args.
required - an iterable holding the required kwonly args.
withdefaults - an iterable of pairs mapping names to defaults.
leftovers - allow kws to be non-empty when all the kwonly args
have already been popped off.
Returns:
The remainder of kws, followed by the values for the kwonly args
in the same order as they stand in required and then in
withdefaults.
Examples:
Below each "def" clause you'll find the clause that would be
equivalent in Python 3 to the use of emulate_kwonly().
>>> def f(a, **kwargs):
... #def f(a, *, b, c=5):
... kwargs, b, c = emulate_kwonly(kwargs, ("b",), (("c", 5),))
... # continue as normal
...
>>> def g(a, *args, **kwargs):
... #def f(a, *args, b, **kwargs):
... kwargs, b = emulate_kwonly(kwargs, ("b",), (), True)
... # continue as normal
"""
if hasattr(withdefaults, "items"):
# allows for OrderedDict to be passed
withdefaults = withdefaults.items()
kwonly = []
# extract the required keyword-only arguments
missing = []
for name in required:
if name not in kws:
missing.append(name)
else:
kwonly.append(kws.pop(name))
# validate required keyword-only arguments
if len(missing) > 2:
end = "s: %s, and %s" % (", ".join(missing[:-1]), missing[-1])
elif len(missing) == 2:
end = "s: %s and %s" % tuple(missing)
elif len(missing) == 1:
end = ": %s" % tuple(missing)
if missing:
msg = "missing %s required keyword-only argument%s"
raise TypeError(msg % (len(missing), end))
# handle the withdefaults
for name, value in withdefaults:
if name not in kws:
kwonly.append(value)
else:
kwonly.append(kws.pop(name))
# handle any leftovers
if not leftovers and kws:
msg = "got an unexpected keyword argument '%s'"
raise TypeError(msg % (kws.keys()[0]))
return [kws] + kwonly
if __name__ == "__main__":
def f(a, **kwargs):
#def f(a, *, b, c=5):
kwargs, b, c = emulate_kwonly(kwargs, ("b",), (("c", 5),))
return a, b, c
assert f(1, b=2) == (1, 2, 5)
assert f(1, b=2, c=3) == (1, 2, 3)
try: f(b=2)
except TypeError: pass
else: raise AssertionError
try: f(1, 2)
except TypeError: pass
else: raise AssertionError
try: f(1, c=2)
except TypeError: pass
else: raise AssertionError
try: f(1, b=2, d=4)
except TypeError: pass
else: raise AssertionError
def g(a, *args, **kwargs):
#def f(a, *args, b, **kwargs):
kwargs, b = emulate_kwonly(kwargs, ("b",), (), True)
return a, b, kwargs
assert g(1, b=2) == (1, 2, {})
assert g(1, b=2, c=3) == (1, 2, dict(c=3))
|