Basic closures

Asked

Viewed 93 times

0

Sirs,

I’ve been studying Python for about four months and now I’m entering the most complex concepts of language. I’m having a little trouble assimilating decoradores but I understood that first I must master the concept of closures.

Follow a script for analysis:

def incremento(n, x):
    return n + x

def incremento(n):
    def incrementado(x):
        return x + n
    return incrementado

z = incremento(8)
print(z) # não executa
print(z(18))
print(z(255))
print(z(1))
y = incremento(200)
print(y(1))
print(y(5))

Not if I am following the correct line of reasoning but in this example I could understand a little more about the concept. The first function does not have closure and I wondered why I would use it if I could build a normal function quickly. Later I realized, including understanding the terminology, that I could give a kind of lock in the value of the parameter of the upper function and then work on various values to be manipulated in the lower function. Notice how I’ve been 'playing with values, creating instances, etc. I think this might be really useful! Is this really the great balcony of closures? Can you give me a slightly more complex use examples? Or add observations to the example used?

  • 2

    Related: https://answall.com/q/2596/112052 | https://answall.com/q/31414/112052 | https://answall.com/q/34907/112052 | https://answall.com/q/1859/112052 (I do not know if it is duplicated, but these links already give a good idea of the concept of closures)

  • I think these links answer the question, not requiring an answer.

1 answer

3


You understood the usage correctly. But there are some traps that can still catch you - Pattern as you wrote in your example, does not bring great advantages over an object-oriented approach, or even a normal structured approach: simply pass all the parameters in a call.

There are some traps that can catch you - and these concepts sometimes appear more naturally when we are developing Patterns that actually make use of these closures.

Common trap: closure variables are 'live' and have the value of the moment they are read

For example: "non-local" variables that are "seen" by the functions in closures will always appear with the value of the moment they have at the time the internal function is called, not the value they had when it was created.

is a trap when you mount graphical interfaces with Tkinter or Qt, for example, where it is common to create small functions using the keyword lambda to include, for example, an identifier:

See this code:


import tkinter


def dizer_botao(mensagem, botao):
    mensagem["text"] = f"O botão {botao} foi pressionado"

def main():
    window = tkinter.Tk()

    mensagem = tkinter.Label(window, text="                           ")
    mensagem.pack()

    for i in range(5):
        b = tkinter.Button(
            window, 
            text=f"Botão {i}",
            command=lambda: dizer_botao(mensagem, i)
        )
        b.pack()

    tkinter.mainloop()

if __name__ == "__main__":
    main()

At first glance, when any button is pressed, it seems that it will call the "lambda" inside the for, that created the button, and call the function that displays the message with the button number, in the variable "i".

If you run the code, however, you will see that for any button is displayed the number '4' of the last button: why at the end of the for this is the value of the variable i.

The way to solve this is to "freeze" the value of "i" for each of the created Vans:

import tkinter


def dizer_botao(mensagem, botao):
    mensagem["text"] = f"O botão {botao} foi pressionado"

def main():
    window = tkinter.Tk()

    mensagem = tkinter.Label(window, text="                           ")
    mensagem.pack()

    for i in range(5):
        b = tkinter.Button(
            window, 
            text=f"Botão {i}",
            command= (lambda i:
                lambda: dizer_botao(mensagem, i)
                )(i)
            )
        b.pack()
    tkinter.mainloop()

if __name__ == "__main__":
    main()

Note that a new function has now been created around the lambda of the above listing: this function is calling for immediately, within the for for each value of i- and creates a new closure, for the internal lambda, that will "see" this value frozen.

(There are other ways to solve this, with default parameter values, for example, but it has nothing to do with the matter of closures).

consider if your problem really gets simpler with closures

Other than that, I think another important lesson is this: if you have to think too much, or take too many turns to solve your problem with this approach, it’s probably not the best.

Your example of "increment" for example, can be more readable, and about the same size, in an object-oriented approach:

class Incremento:

    def __init__(self, n):
        self.n = n

    def __call__(self, x):
        return x + self.n

(Here, the special method __call__ implies that the instances of this class can be called, as well as functions).

This class will work exactly like its "increment" function - the biggest difference is that (1) the attributes that will be used by __call__ are visible and not opaque, and can even be changed; (2) you can have several different methods to operate on the data given when creating the instance - (ok, a closure could create and return multiple functions); and (3) although it does not have as much importance in most applications, a class instance, will be faster to be created, and, with some care, much less in terms of memory usage than a copy of a function, returned by closure. In this case, we just need to actually save the "n" attribute along with the instance, for example.

And finally, there are cases where the closure Pattern will be much more readable - the example above with Tkinter, for example.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.