Why does this python implementation for a “functional function” fail?

I wanted to demonstrate the usefulness of decorators in python for some people and could not run a simple example: consider two functions (for simplicity without arguments) f and g . Their sum f + g can be defined as a function that returns f () + g (). Of course, adding, subtracting, etc. Functions are not defined at all. But it’s easy to write a decorator that converts each function to an added function.

Now I would like to have a decorator that turns any function into an “operational” function, that is, a function that behaves in the described way for any operator in the standard operator module. My implementation is as follows:

 import operator class function(object): def __init__(self, f): self.f = f def __call__(self): return self.f() def op_to_function_op(op): def function_op(self, operand): def f(): return op(self(), operand()) return function(f) return function_op binary_op_names = ['__add__', '__and__', '__div__', '__eq__', '__floordiv__', '__ge__', '__gt__', '__le__', '__lt__', '__mod__', '__mul__', '__ne__', '__or__', '__pow__', '__sub__', '__truediv__', '__xor__'] for name in binary_op_names: type.__setattr__(function, name, op_to_function_op(getattr(operator, name))) 

Do a little test to see if it works:

 @function def a(): return 4 def b(): return 7 c = a + b print c() print c() == operator.__add__(4, 7) 

Output:

 11 True 

This is the final version that I received after some experiments. Now, let's make two small, irrelevant modifications to see what I tried before:

First: in the definition of binary_op_names change the square brackets to parentheses. Suddenly (for me) a completely unrelated error message appears:

 Traceback (most recent call last): File "example.py", line 30, in <module> c = a + b TypeError: unsupported operand type(s) for +: 'function' and 'function' 

Where does this come from?

Second: Write op_to_function_op as a lambda expression:

 op = getattr(operator, name) type.__setattr__(function, name, lambda self, other: function(lambda: op(self(), other()))) 

Run a slightly more complex test case:

 @function def a(): return 4 def b(): return 7 c = a + b print c() print c() == operator.__add__(4, 7) print c() == operator.__xor__(4, 7) 

Output:

 3 False True 

It looks like an area leak, but again I don't understand why this is happening.

+4
source share
1 answer

In the first release, I did not see any problems when changing binary_op_names from list to tuple , not sure why you saw it.

Regarding the second problem, all operations will be performed by XOR, because __xor__ was the last element for which op was set. Since op not passed to lambda when it is created, every time a lambda is called, it will look for op in the global scope and always see __xor__ .

You can prevent this by creating a lambda that acts like a closure, similar to your current op_tofunction_op , it might look something like this:

 op_to_function_op = lambda f: lambda self, other: function(lambda: f(self(), other())) for name in binary_op_names: op = getattr(operator, name) type.__setattr__(function, name, op_to_function_op(op)) >>> (function(lambda: 4) + (lambda: 7))() 11 >>> (function(lambda: 4) - (lambda: 7))() -3 >>> (function(lambda: 4) ^ (lambda: 7))() 3 
+2
source

Source: https://habr.com/ru/post/1383724/


All Articles