The last time Hackerfall tried to access this page, it returned a not found error. A cached version of the page is below, or click here to continue anyway

Lambdas and functions in Python ~ The Python Corner

The first problem here is the code duplication. There’s a principle of software engeneering that is called “DRY”. It stands for “Don’t Repeat Yourself”.

Peter has duplicated a lot of code because for every single function he has to get the operands, compute the operation and put the result back to the stack. Wouldn’t it be great if we could have a function that does exactly this job, computing the operation we request? How can we achieve this?

Well, it’s really simple actually, because as we said earlier… functions are first class objects in Python! So, Peter’s code can be simplifed a lot.

Let’s have a look to the functions we have to provide.

All the standard operations (divide, sum, add and multiply) needs two operand to be computed. The “sqrt” and the “pow2” functions need just one operand to be computed. The “C” (to drop last item in the stack) and “AC” (to clear the stack) functions, don’t need any operand to be computed.

So, let’s rewrite Peter’s code this way:

Isn’t it better? The duplicated code is far less than before and look at the functions, all they do is to compute the operation and return the results. They are no longer in charge of getting operands and pushing the result to the stack, the readability of the code is definately improved!

Now, looking at the code, the first thing I really hate are all the “ifs” in the compute function. Perhaps replacing them with a “switch” function… if only the switch function would exists in Python! :)

But we can do something better. Why don’t we create a catalog of the available functions and then we just use this catalog to decide which function to use?

Why don’t we use a dictionary for that?

Let’s try to modify our code again:

Wow, almost all our “if(s)” are gone! And now we have a catalog of functions that we can expand as we want. So for example, if we need to implement a factorial function, we will just add the function to the catalog and implement a custom method in the code. That’s really good!

Even if …

It would be great to act only on the catalog, wouldn’t it? But wait… shouldn’t we talk about lambdas in this article?

Here’s where lambdas can be useful! We don’t need a standard defined function for a simple calc, we need just an inline lambda for that!

So, our code could be:

Wow, this code rocks now! :)

Even if…

Let’s pretend that we have to add the factorial function, could we just modify the catalog?

Unfortunately no.

There’s another place we have to modify… we have to modify also the compute function because we need to specify that the factorial function is a “one operand function”.

That’s bad, we do know that it is a one operand function, it’s obvious since we need to call the math.factorial(x) function passing just the x argument. If only there were a way to determine how many arguments a function needs at runtime…

There is actually. In the “inspect” module, there’s a “signature” function that can help us inspecting the signature of our method at runtime. So, let’s start the REPL and do a quick test:

>>> a = lambda x, y: x + y
>>> from inspect import signature
>>> my_signature = signature(a)
>>> print(my_signature)
(x, y)
>>> print (my_signature.parameters)
OrderedDict([(‘x’, <Parameter “x”>), (‘y’, <Parameter “y”>)])
>>> print (len(my_signature.parameters))
2

Yes, amazing. We could determine at runtime how many operands our function needs!

Let’s try it now:

Note that in this last code we have modified the zero operands functions in the catalog from

“C”: self.stack.pop,
“AC”: self.stack.clear

to

“C”: lambda: self.stack.pop(),
“AC”: lambda: self.stack.clear()}

Why that?

Well, the problem is that in the compute function we are trying to determine the number of parameters from the signature of the method. The problem is that for built-in methods written in C, we can’t do that.

Let’s try it by yourself, start a REPL :

>>> from inpect import signature
>>> a = []
>>> my_sig = signature(a.clear)
Traceback (most recent call last):
 File “<stdin>”, line 1, in <module>
 File “C:\Users\MASTROMATTEO\AppData\Local\Continuum\anaconda3\lib\inspect.py”, line 3033, in signature
 return Signature.from_callable(obj, follow_wrapped=follow_wrapped)
 File “C:\Users\MASTROMATTEO\AppData\Local\Continuum\anaconda3\lib\inspect.py”, line 2783, in from_callable
 follow_wrapper_chains=follow_wrapped)
 File “C:\Users\MASTROMATTEO\AppData\Local\Continuum\anaconda3\lib\inspect.py”, line 2262, in _signature_from_callable
 skip_bound_arg=skip_bound_arg)
 File “C:\Users\MASTROMATTEO\AppData\Local\Continuum\anaconda3\lib\inspect.py”, line 2087, in _signature_from_builtin
 raise ValueError(“no signature found for builtin {!r}”.format(func))
ValueError: no signature found for builtin <built-in method clear of list object at 0x000001ED6EB18F88>

as you can see we can’t get the signature of a built-in method.

Our possibilities to solve this problem were:

1. Handle this special case in our code, trapping the exception raised when we tried to get the signature for the self.stack.pop() function and the self.stack.clear() function

2. Encapsulate the built-in functions in void lambdas, so as to have the signature functions extract the signature from our void lambda function and not from the built-in function contained.

And we have obviously choosen the second possibility, since it is the most “Pythonic” we had. :)

That’s all folks. Today’s article has explored some aspect of functions and lambdas in Python and I hope you got the message I wanted to send.

think twice, code once.

Sometimes developers are lazy and don’t think too much at what can mean mantain bad code.

Let’s have a look at the first Peter’s code of the article and try to figure out what could have meant to add the factorial function then. We should have created another function, duplicated more code, and modified the compute function, right? With our last code we just need to add a single line to our catalog:

“!”: lambda x: math.factorial(x),

Try to think at what could have meant to add another feature to the program for logging all the calculations requested and the results given. We had been supposed to modify a dozen functions of our code to add the feature right? And we would have had to modify as well all the new functions that we will have inserted from now on. Now we can add the feature just in the three methods that really compute the requested calculation depending on the number of the operands requested.

Wait, three methods? Wouldn’t it be possible to have just a method that works regardless the number of operands that are requested by the function? :)

Happy coding!

Continue reading on www.thepythoncorner.com