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.
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)
– hkotsubo
I think these links answer the question, not requiring an answer.
– Augusto Vasques