Skip to content

Instantly share code, notes, and snippets.

@neutrinoceros
Last active October 23, 2021 11:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save neutrinoceros/3f703d63761794b5e966330d51436b3c to your computer and use it in GitHub Desktop.
Save neutrinoceros/3f703d63761794b5e966330d51436b3c to your computer and use it in GitHub Desktop.
Python function decorators to progressively evolve function signatures over a deprecation cycle
import warnings
from functools import wraps
from types import FunctionType
from typing import Dict
def future_keyword_only(positions2names:Dict[int, str]):
"""Warn users when using a future keyword-only argument as positional.
Note that keyword-only arguments are available from Python 3.0
See https://www.python.org/dev/peps/pep-3102/
"""
def outer(func:FunctionType):
@wraps(func)
def inner(*args, **kwargs):
for no, name in sorted(positions2names.items()):
if len(args) <= no:
break
value = args[no]
msg = (
f"using the {name!r} argument positionally (on position {no}) "
"is deprecated and will stop working in a future release. "
"Pass the argument as keyword to supress this warning, "
f"i.e., use {func.__name__}(..., {name}={value!r}, ...)"
)
warnings.warn(msg, FutureWarning, stacklevel=2)
return func(*args, **kwargs)
return inner
return outer
def future_positional_only(positions2names:Dict[int, str]):
"""Warn users when using a future positional-only argument as keyword.
Note that positional-only arguments are available from Python 3.8
See https://www.python.org/dev/peps/pep-0570/
"""
def outer(func:FunctionType):
@wraps(func)
def inner(*args, **kwargs):
for no, name in sorted(positions2names.items()):
if name not in kwargs:
continue
value = kwargs[name]
msg = (
f"using the {name!r} argument as keyword (on position {no}) "
"is deprecated and will stop working in a future release. "
"Pass the argument as positional to supress this warning, "
f"i.e., use {func.__name__}({value!r}, ...)"
)
warnings.warn(msg, FutureWarning, stacklevel=2)
return func(*args, **kwargs)
return inner
return outer
if __name__ == "__main__":
# a minimal demo
@future_keyword_only({1: "counts"})
@future_positional_only({0: "name"})
def hello(name:str, counts:int = 1):
return " ".join([f"Hello {name} !"] * counts)
hello("Guido", 1) # warning: counts will become keyword-only
hello(name="Matt", counts=3) # warning: name will become positional-only
hello("darkness", counts=2) # all clear !
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment