Skip to content

Instantly share code, notes, and snippets.

@devdave
Last active November 6, 2017 16: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 devdave/de18ce1b0d385d83712af8edf109fd5c to your computer and use it in GitHub Desktop.
Save devdave/de18ce1b0d385d83712af8edf109fd5c to your computer and use it in GitHub Desktop.
Messing around with annotations in python3
"""
Command line output
=================================
Test with both arguments supplied
Handling call
Parameter name: always_int
Parameter name: always_str
Original position arguments: ('123',)
Original keyword arguments: {'always_str': 345}
Transformed arguments: [123]
Transformed keyword arguments: {'always_str': '345'}
Completed
Test with only one positional argument supplied
Handling call
Parameter name: always_int
Parameter name: always_str
Original position arguments: ('123',)
Original keyword arguments: {}
Transformed arguments: [123]
Transformed keyword arguments: {'always_str': None}
Completed
Test with custom transformer
Handling call
Parameter name: bizarro
Original position arguments: ('Hello World',)
Original keyword arguments: {}
Transformed arguments: ['dlroW olleH']
Transformed keyword arguments: {}
Completed
"""
from inspect import signature
def magic_decorator(func):
"""
Anytime I put the word "magic" in a identifier, its my way of putting up
a red flag that whatever is attached is possible toxic, dangerous, or dangerously toxic.
In this case, what magic_decorate does is really nifty but I am confident there is a gotcha in here
like the threat of inspect.signature.parameter.annotation changing behavior in the future.
That said....
"""
#Grab the parameters of the function passed into the decorator
params = signature(func).parameters
#actual decorator logic
def decorator(*args, **kwargs):
#constants for noting where a function argument came from, either positional or keyword
POSITIONED = 1
KW = 2
new_args = []
new_kwargs = {}
#*args is a tuple which is inconveniant
arg_list = [x for x in args]
print("Handling call")
for parameter_name, parameter_signature in params.items():
print(f"\tParameter name: {parameter_name}")
#First step is to get the argument values passed in
source = None
if arg_list:
source = POSITIONED
original_value = arg_list.pop(0)
else:
source = KW
original_value = kwargs.get(parameter_signature.name, parameter_signature.empty)
#Handles cases where a value is not provided for a keyword argument
if original_value == parameter_signature.empty:
new_value = parameter_signature.default
elif parameter_signature.annotation != parameter_signature.empty:
#Here is the magic, in vanilla_func and goofy_func, the annotations are int/str/reversed_str
# and are provided as is via the inspect.Parameter.annotation property
new_value = parameter_signature.annotation(original_value)
else:
new_value = original_value
#Put the transformed (or untouched) arguments back to where they came from
if source == POSITIONED:
new_args.append(new_value)
else:
new_kwargs[parameter_signature.name] = new_value
print(f"\tOriginal position arguments: {args} \n\tOriginal keyword arguments: {kwargs}")
print(f"\tTransformed arguments: {new_args} \n\tTransformed keyword arguments: {new_kwargs}")
return_value = func(*new_args, **new_kwargs)
print("Completed")
return return_value
return decorator
@magic_decorator
def vanilla_func(always_int:int=None, always_str:str=None):
return always_int, always_str
def reversed_str(raw):
if not isinstance(raw, str):
raw = str(raw)
return raw[::-1]
@magic_decorator
def goofy_func(bizarro:reversed_str):
return bizarro
print("Test with both arguments supplied")
assert vanilla_func("123", always_str=345) == (123, "345",)
print("Test with only one positional argument supplied")
assert vanilla_func("123") == (123, None,)
print("Test with custom transformer")
assert goofy_func("Hello World") == "dlroW olleH"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment