Suppose I have a function which I have written as a binary-operator (binop), how do I extend it to a multi-operator (multiop) which takes an arbitrary number of arguments? Is there such a decorator in a library (e.g. in functools)?
For example (I want a decorator to give this behaviour):
#binop_to_multiop
def mult(a,b):
return a*b
mult(2,3,4) # 2*3*4 = 24
mult(7) # 7
mult(2,3) # 6
Obviously I can't ask a question about decorators without mentioning, this answer.
.
I've tried writing my own, but can't quite get it working, any explanation of where I'm going wrong would also be welcome:
def binop_to_multiop(f):
#functools.wraps(f)
def wrapper(*args, **kwds):
if len(args) == 1: return args[0] # fails
return f(args[0],(f(*args[1:], **kwds)), **kwds) #recursion attempt fails
return wrapper
Gives a TypeError: mult() takes exactly 2 arguments (N given) (for various N!=2).
reduce() comes to mind:
from functools import wraps
def binop_to_multiop(binop):
#wraps(binop)
def multiop(x, *xs):
return reduce(binop, xs, x)
return multiop
# ...
#binop_to_multiop
def mult(a, b):
return a * b
print mult(2, 3, 4)
print mult(7)
print mult(2, 3)
Result:
$ python multiop.py
24
7
6
Your attempt to code it yourself was very close to working. You just need to change the recursive step to recurse on wrapper rather passing all but one argument to f:
def binop_to_multiop(f):
#functools.wraps(f)
def wrapper(*args, **kwds):
if len(args) == 1: return args[0]
return f(args[0], wrapper(*args[1:], **kwds), **kwds)
return wrapper
I didn't have any problems with the base case, so I'm not sure what your comment #fails was about.
You may also need to think about which end of the list you start solving from (that is, does your operator have left or right associativity). For operators like multiplication and addition it won't matter since (a+b)+c = a+(b+c), but for other you may get strange results. For instance, subtraction may not work as you might expect:
#binop_to_multiop
def sub(a, b):
return a - b
With the decorator defined above, sub(a, b, c) will give a different result than a-b-c (it will do a-(b-c) instead of (a-b)-c). If you want them to behave the same way, you can redefine the decorator to be left associative (like most mathematical operators do in most computer languages) like this:
def left_associative_binop_to_multiop(f):
#functools.wraps(f)
def wrapper(*args, **kwds):
if len(args) == 1: return args[0]
return f(wrapper(*args[:-1], **kwds), args[-1], **kwds)
return wrapper
A more sophisticated approach would be to make the associativity be a parameter to the decorator, but that gets tricky if you don't want the parameter to be required.
def binop_to_multiop(f):
def wrapper(*args):
return reduce(f, args) if args else None
return wrapper
#binop_to_multiop
def mult(a, b):
return a*b
print mult(2,3,4)
print mult(7)
print mult(2,3)
print mult(4,5,6,7)
gives
24
7
6
840
Related
I'm learning python and I'm stuck at decorators
the way I understand it is that decorators add functionality to a function
I made a simple function that checks if a number is even
and then a decorator that adds taking the absolute value to it
def decorate(func):
def is_even_new(*args,**kwargs):
num = abs(*args,**kwargs)
func(num)
return is_even_new()
#decorate
def is_even(x):
if x%2 == 0:
return True
else:
return False
is_even(8)
but it I keep getting a TypeError: abs() takes exactly one argument (0 given)
is there is something wrong in the code or is my understanding of decorators is false?
When you decorate the function, you should return the decorated function itself:
def decorate(func):
def is_even_new(*args,**kwargs):
num = abs(*args,**kwargs)
func(num)
return is_even_new
rather than calling the decorated function and returning that:
def decorate(func):
def is_even_new(*args,**kwargs):
num = abs(*args,**kwargs)
func(num)
return is_even_new()
(notice the extra parentheses). In this last example, when you decorate a function with #decorate, the decorator defines this inner function is_even_new and then, instead of returning the latter, it tries to call it with no arguments: is_even_new(). This is also why you're getting the TypeError: abs expects (exactly) one argument, but you've given it none.
Remember that functions in Python are objects like anything else ("first-class citizens") and so you can reference them directly by their name.
Also, as a recommendation, if you know that the functions decorated with #decorate will only ever take exactly one argument (like is_even), don't use variable arguments and keyword arguments, just define the decorate function to take exactly one parameter as well:
import functools
def decorate(func):
#functools.wraps(func)
def decorated(x):
func(abs(x))
return decorated
This will make the error messages more helpful (instead of raising at the abs call,
Or, if you only want to apply abs to, say, the first argument:
def decorate(func):
#functools.wraps(func)
def decorated(x, *args, **kwargs):
func(abs(x), *args, **kwargs)
return decorated
You'll also have noticed the #functools.wraps. This is a very useful standard library utility for defining wrapper functions, such as those you return from a decorator. It sets special attributes like __name__, __module__, etc. on the wrapper function so that it essentially looks like the wrapped function.
in your code you need to return the value from the decorator in correct way and also change the input data to the decorator by keeping the limitation of other inside function in mind ie (abs take 1 positonal argument not many) and you cant directly call the return function from outside as full without provideing value as return is_new_even(), you need to pass the argument there also if want to do that way
def decorate(func):
def is_even_new(val):
num = abs(val)
return func(num)
return is_even_new
#decorate
def is_even(x):
if x%2 == 0:
return True
else:
return False
is_even(8)
I am not sure what your decorated function is trying to do as modulo is working on negative integers as well, but you basically have two problems:
like #Anakhand said you should return a function and not the function return value.
your is_even_new does not return nothing
so I guess this is the correct implemntation:
def decorate(func):
def is_even_new(*args,**kwargs):
num = abs(*args,**kwargs)
return func(num)
return is_even_new
#decorate
def is_even(x):
if x%2 == 0:
return True
else:
return False
print(is_even(-8)) # --> True
I tried to use function decorators, but in this example it dooesn't work for me, can you give me the solution ?
def multiply_by_three(f):
def decorator():
return f() * 3
return decorator
#multiply_by_three
def add(a, b):
return a + b
print(add(1,2)) # returns (1 + 2) * 3 = 9
Interpreter prints error: "TypeError: decorator() takes 0 positional arguments but 2 were given"
When you use a decorator, the function you return from the decorator replaces the old function. In other words, the decorator function in multiply_by_three replaces the add function.
This means that each functions signature's should match, including their arguments. However, in your code add takes two arguments while decorator takes none. You need to let decorator receive two arguments as well. You can do this easily by using *args and **kwargs:
def multiply_by_three(f):
def decorator(*args, **kwargs):
return f(*args, **kwargs) * 3
return decorator
If you now decorate your function and run it, you can see it works:
#multiply_by_three
def add(a, b):
return a + b
print(add(1,2)) # 9
I am trying to use currying to make a simple functional add in Python. I found this curry decorator here.
def curry(func):
def curried(*args, **kwargs):
if len(args) + len(kwargs) >= func.__code__.co_argcount:
return func(*args, **kwargs)
return (lambda *args2, **kwargs2:
curried(*(args + args2), **dict(kwargs, **kwargs2)))
return curried
#curry
def foo(a, b, c):
return a + b + c
Now this is great because I can do some simple currying:
>>> foo(1)(2, 3)
6
>>> foo(1)(2)(3)
6
But this only works for exactly three variables. How do I write the function foo so that it can accept any number of variables and still be able to curry the result? I've tried the simple solution of using *args but it didn't work.
Edit: I've looked at the answers but still can't figure out how to write a function that can perform as shown below:
>>> foo(1)(2, 3)
6
>>> foo(1)(2)(3)
6
>>> foo(1)(2)
3
>>> foo(1)(2)(3)(4)
10
Arguably, explicit is better than implicit:
from functools import partial
def example(*args):
print("This is an example function that was passed:", args)
one_bound = partial(example, 1)
two_bound = partial(one_bound, 2)
two_bound(3)
#JohnKugelman explained the design problem with what you're trying to do - a call to the curried function would be ambiguous between "add more curried arguments" and "invoke the logic". The reason this isn't a problem in Haskell (where the concept comes from) is that the language evaluates everything lazily, so there isn't a distinction you can meaningfully make between "a function named x that accepts no arguments and simply returns 3" and "a call to the aforementioned function", or even between those and "the integer 3". Python isn't like that. (You could, for example, use a zero-argument call to signify "invoke the logic now"; but that would break special cases aren't special enough, and require an extra pair of parentheses for simple cases where you don't actually want to do any currying.)
functools.partial is an out-of-box solution for partial application of functions in Python. Unfortunately, repeatedly calling partial to add more "curried" arguments isn't quite as efficient (there will be nested partial objects under the hood). However, it's much more flexible; in particular, you can use it with existing functions that don't have any special decoration.
You can implement the same thing as the functools.partial example for yourself like this:
def curry (prior, *additional):
def curried(*args):
return prior(*(args + additional))
return curried
def add(*args):
return sum(args)
x = curry(add, 3,4,5)
y = curry(b, 100)
print y(200)
# 312
It may be easier to think of curry as a function factory rather than a decorator; technically that's all a decorator does but the decorator usage pattern is static where a factory is something you expect to be invoking as part of a chain of operations.
You can see here that I'm starting with add as an argument to curry and not add(1) or something: the factory signature is <callable>, *<args> . That gets around the problem in the comments to the original post.
FACT 1: It is simply impossible to implement an auto currying function for a variadic function.
FACT 2: You might not be searching for curry, if you want the function that will be passed to it * to know* that its gonna be curried, so as to make it behave differently.
In case what you need is a way to curry a variadic function, you should go with something along these lines below (using your own snipped):
def curryN(arity, func):
"""curries a function with a pre-determined number of arguments"""
def curried(*args, **kwargs):
if len(args) + len(kwargs) >= arity:
return func(*args, **kwargs)
return (lambda *args2, **kwargs2:
curried(*(args + args2), **dict(kwargs, **kwargs2)))
return curried
def curry(func):
"""automatically curries a function"""
return curryN(func.__code__.co_argcount, func);
this way you can do:
def summation(*numbers):
return sum(numbers);
sum_two_numbers = curryN(2, summation)
sum_three_numbers = curryN(3, summation)
increment = curryN(2, summation)(1)
decrement = curryN(2, summation)(-1)
I think this is a decent solution:
from copy import copy
import functools
def curry(function):
def inner(*args, **kwargs):
partial = functools.partial(function, *args, **kwargs)
signature = inspect.signature(partial.func)
try:
signature.bind(*partial.args, **partial.keywords)
except TypeError as e:
return curry(copy(partial))
else:
return partial()
return inner
This just allow you to call functools.partial recursively in an automated way:
def f(x, y, z, info=None):
if info:
print(info, end=": ")
return x + y + z
g = curry_function(f)
print(g)
print(g())
print(g(2))
print(g(2,3))
print(g(2)(3))
print(g(2, 3)(4))
print(g(2)(3)(4))
print(g(2)(3, 4))
print(g(2, info="test A")(3, 4))
print(g(2, info="test A")(3, 4, info="test B"))
Outputs:
<function curry.<locals>.inner at 0x7f6019aa6f28>
<function curry.<locals>.inner at 0x7f6019a9a158>
<function curry.<locals>.inner at 0x7f6019a9a158>
<function curry.<locals>.inner at 0x7f6019a9a158>
<function curry.<locals>.inner at 0x7f6019a9a0d0>
9
9
9
test A: 9
test B: 9
I need to call a multi-parameter function many times while all but one parameter is fixed. I was thinking of using decorators:
# V1 - with #decorator
def dec_adder(num):
def wrap(fun):
def wrapped_fun(n1):
return fun(n1, second_num=num)
return wrapped_fun
return wrap
#dec_adder(2)
def adder(first_num, second_num):
return first_num + second_num
print adder(5)
>>> 7
But this seems confusing since it appears to be calling a 2-parameter function, adder with only one argument.
Another approach is to use a nested function definition that uses local variables from the parent function:
# V2 - without #decorator
def add_wrapper(num):
def wrapped_adder(num_2):
return num + num_2
return wrapped_adder
adder = add_wrapper(2)
print adder(5)
>>> 7
But I hesitate to use this approach since in my actual implementation the wrapped function is very complex. My instinct is that it should have a stand-alone definition.
Forgive me if this ventures into the realm of opinion, but is either approach considered better design and/or more Pythonic? Is there some other approach I should consider?
functools.partial should work nicely in this case:
from functools import partial
def adder(n1, n2):
return n1 + n2
adder_2 = partial(adder, 2)
adder_2(5)
Its' docstring:
partial(func, *args, **keywords) - new function with partial application
of the given arguments and keywords.
-- so, you can set keyword arguments as well.
PS
Sadly, the built-in sum does not suit this case: it sums over an iterable (in fact, sum(iterable[, start]) -> value), so partial(sum, 2) does not work.
Another possible solution - you can use functools and parametrized decorator:
from functools import wraps
def decorator(num):
def decor(f):
#wraps(f)
def wrapper(n,*args,**kwargs):
return f(n+num,*args,**kwargs)
return wrapper
return decor
#decorator(num=2) # number to add to the parameter
def test(n,*args,**kwargs):
print n
test(10) # base amount - prints 12
What is a most pythonic way to write a function to either pass in arguments or a tuple/list of arguments?
For example, a function add could either take in an argument of add(1, 2) or add((1, 2)) and both output 3.
What I have so far: (it works, but does not look nice)
def add(*args):
if len(args) == 1:
return (args[0][0] + args[0][1])
if len(args) == 2:
return args[0] + args[1]
else:
print "error: add takes in one or two arguments"
What I don't like about it is:
I have to print the error about passing in one or two arguments
The args[0][0] looks very unreadable
This way, it is hard to tell what the arguments passed in represent (they don't have names)
I dont know if this is the most "pythonic" way but it will do what you want:
def add(a, b=None):
return a+b if b is not None else sum(a)
If your function takes a specific number of arguments, then the most pythonic way to do this is to not do it. Rather if the user has a tuple with the arguments, you make them unpack them when they call the function. E.g.
def add(a, b):
return a + b
Then the caller can do
add(1,2)
or
t = (1,2)
add(*t)
The only time you want to accept either a sequence of params or individual params is when you can have any arbitrary (non-zero) number of arguments (e.g. the max and min builtin functions) in which case you'd just use *args
If you can only take a finite number of arguments, it makes more sense to ask for those specifically. If you can accept an arbitrary number of arguments, then the *args paradigm works well if you loop through it. Mixing and matching those two aren't very elegant.
def add(*args):
total = 0
for i in args:
total += i
return total
>>> add(1, 2, 3)
6
(I know we could just use sum() there, but I'm trying to make it look a bit more general)
In the spirit of python duck typing, if you see 1 argument, assume its something that expands to 2 arguments. If its then 2, assume its two things that add together. If it violates your rules, raise an exception like python would do on a function call.
def add(*args):
if len(args) == 1:
args = args[0]
if len(args) != 2:
raise TypeError("add takes 2 arguments or a tuple of 2 arguments")
return args[0] + args[1]
A decorator would be best suited for this job.
from functools import wraps
def tupled_arguments(f):
#wraps(f) # keeps name, docstring etc. of f
def accepts_tuple(tup, *args):
if not args: # only one argument given
return f(*tup)
return f(tup, *args)
return accepts_tuple
#tupled_arguments
def add(a, b):
return a + b