Reputation: 844
I have a complex library, which users can add functions to. This library is then used in a program which then accepts input. Problem is, the functions aren't being processed the way I want them to. Let me illustrate (I've simplified the code to illustrate the salient points):
import functools
def register_command(func):
def wrapper(*args, **kwargs):
func(*args, **kwargs)
return wrapper
def execute_command(func, *args, **kwargs)):
return functools.partial(func, *args, **kwargs))
'''DECORATORS: EASY WAY FOR THE USER TO ADD NEW FUNCTIONS TO THE LIBRARY'''
@register_command
def some_math_function(variable):
return variable + 1
'''THIS IS THE PROGRAM WHICH USES THE ABOVE LIBRARY & USER-CREATED FUNCTIONS'''
input_var = input("input your variable: ")
response = execute_command(register_command, input_var)
print(response())
So, if the user were to input '10', input_var = 10
, I expect the algorithm to return 11. Instead I get this:
<function register_command.<locals>.wrapper at 0x110368ea0>
Where am I going wrong?
Upvotes: 1
Views: 2496
Reputation: 114300
Let's look at what decorators do first. A decorator accepts an object (could be a function, class, or possibly something else), does something, and returns a replacement object that gets reassigned to the name of the original.
In that context, register_function
does nothing useful. It returns a wrapper that calls the unmodified original. Based on the name, you'd probably want to record the function in some registry, like a dictionary:
function_registry = {}
def register_command(func):
function_registry[func.__name__] = func
return func
Remember, the things that a decorator does don't have to affect the function directly (or at all). And there's nothing wrong with simply returning the input function.
Now let's take a look at using the registry we just made. Your current execute_command
does not execute anything. It creates and returns a callable object which would call the decorated function if you were to invoke it with no arguments. execute_command
does not actually call the result.
You would probably want a function with a name like execute_command
to look up a command by name, and run it:
def execute_command(name, *args, **kwargs)):
return function_registry[name](*args, **kwargs)
So now you can do something like
@register_command
def some_math_function(variable):
return variable + 1
That will leave the function unmodified, but add an entry to the registry that maps the name 'some_math_function'
to the function object some_math_function
.
Your program becomes
func_name = input('what function do you want to run? ') # enter some_math_func
input_var = int(input("input your variable: "))
response = execute_command(func_name, input_var)
print(response())
This example does not perform any type checking, keep track of the number of arguments, or otherwise allow you to get creative with calling the functions, but it will get you started with decorators.
So here's a rudimentary system for converting a fixed number of inputs to the required types. Arguments are converted in the order provided. The function is responsible for raising an error if you didn't supply enough inputs. Keywords aren't supported:
function_registry = {}
def register_function(*converters):
def decorator(func):
def wrapper(*args):
real_args = (c(arg) for c, arg in zip(converters, args))
return func(*real_args)
function_registry[func.__name__] = wrapper
return wrapper
return decorator
@registrer_function(int)
def some_math_func(variable):
return variable + 1
def execute_command(name, *args):
return function_registry[name](*args)
command = input("command me: ") # some_math_func 1
name, *args = command.split()
result = execute_command(name, *args)
print(result)
Upvotes: 2