La fermeture est comme JavaScript, mais j'ai eu l'occasion d'essayer d'utiliser la fermeture en Python, je vais donc la comparer dans plusieurs langages.
Si vous faites une erreur, faites-le nous savoir dans les commentaires! Bien sûr, je vais essayer de ne faire aucune erreur.
function outer(){
let a = 0;
let inner = () => {
console.log(a++);
};
return inner;
}
let fn = outer();
>>> fn();
0
>>> fn();
1
>>> fn();
2
La variable a reste non récupérée par le GC car la variable interne contient une référence à a. Simple et facile à comprendre. Cependant, notez que a est conservé même si a est passé comme argument de external comme indiqué ci-dessous.
function outer(a){
let inner = () => {
console.log(a++);
};
return inner;
}
let fn = outer(0);
>>> fn();
0
>>> fn();
1
>>> fn();
2
En conclusion, bien sûr, vous pouvez vous référer aux variables au moment de la définition même dans des endroits différents de ceux au moment de la définition.
Créez un fichier .mjs pour exécuter l'instruction d'importation sur le nœud et utilisez la commande node --experimental-modules.
module.mjs
// export let a = 1;
//ne pas exporter un
let a = 1;
export function outer(){
let b = 1000;
let inner1 = ()=>{
console.log(b++);
}
return inner1;
}
//Pas une fonction en fonction
export function inner2(){
console.log(a++)
}
closure.mjs
import * as m from "./module.mjs";
let fn = m.outer();
fn();
fn();
fn();
m.inner2();
m.inner2();
m.inner2();
console.log(a)
production:
$ node --experimental-modules closure.mjs
(node:12980) ExperimentalWarning: The ESM module loader is experimental.
1000
1001
1002
1
2
3
file:///***********/closure.mjs:11
console.log(a)
^
ReferenceError: a is not defined
De cette façon, a n'est pas défini, mais il peut être référencé dans inner2. Cela signifie qu'en JavaScript, une fonction devient une fermeture même si ce n'est pas une fonction en fonction.
En fait, lancez-le avec Firefox (66.0.3).
$ cp closure.mjs closure.js
$ cp module.mjs module.js
Réécrivez l'instruction d'importation de fermeture.js en tant que import * sous la forme m depuis "./module.js";.
closure.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script type="module" src="closure.js"></script>
<title>test</title>
</head>
<body>
</body>
</html>
Accédez à fermeture.html avec Firefox et vérifiez le journal.
1000
1001
1002
1
2
3
ReferenceError: a is not defined[Détails]
Le résultat était exactement le même. En Python, la portée des variables globales est limitée à ce fichier, mais si vous utilisez la fonction d'importation / exportation et n'exportez pas cette variable, est-ce la même chose en JavaScript? (C'est naturel car il n'exporte pas)
def outer():
a = 0
def inner():
nonlocal a
print(a)
a += 1
return inner
fn = outer()
>>> fn()
0
>>> fn()
1
>>> fn()
2
Python est presque le même et facile à comprendre, mais il y a un supplément. Son nom n'est pas local. Obligatoire lorsque vous essayez de modifier une variable dans la portée externe. À propos, si global a est utilisé au lieu de non local, alors a fera référence à la variable globale a.
L'instruction> nonlocal garantit que les identificateurs répertoriés font référence à la variable précédemment liée dans la portée externe, à l'exclusion de global.
L'instruction globale signifie spécifier que les identificateurs listés doivent être interprétés comme des variables globales.
(Source: Référence du langage Python)
a = 111
def outer():
a = 0
def inner1():
#Si vous voulez simplement vous y référer, vous n'avez pas besoin d'utiliser un non local
print(a)
#non local a ou a+=J'obtiens une erreur avec 1
# a += 1
def inner2():
#global a signifie un=Voir la définition de 111
global a
print(a)
return (inner1, inner2)
inner1, inner2 = outer()
>>> inner1()
0
>>> inner2()
111
#Evidemment, mais de la fermeture
#A à l'intérieur de la fonction externe référencée=0 est inaccessible de l'extérieur
#Dans ce cas a, qui est une variable globale=111 est imprimé
>>> print(a)
111
module.py
a = 1
def outer():
b = 1000
def inner1():
nonlocal b
print(b)
b += 1
return inner1
#inner2 n'est pas une fonction en fonction
def inner2():
#J'obtiens une erreur si je ne fais pas de global a
global a
print(a)
a += 1
closure.py
from module import *
inner1 = outer()
inner1()
inner1()
inner1()
inner2()
inner2()
inner2()
production:
$ python closure.py
1000
1001
1002
1
2
3
C'est comme JavaScript!
(J'ai écrit qu'il est différent de JavaScript car j'obtiens une erreur sans dire un global dans inner2, mais je l'ai corrigé en le faisant remarquer dans le commentaire.)
Il semble que Java (7 ou antérieur) n'ait pas de fermeture. Vous pouvez faire quelque chose de similaire (mais avec des restrictions) en utilisant une classe anonyme dans votre fonction. L'expression Lambda a été introduite à partir de Java8, mais il semble que ce ne soit pas non plus une fermeture. Les liens suivants, y compris leur historique, sont détaillés et très faciles à lire et recommandés.
Je n'ai pas encore lu la partie 2, mais je la posterai.
Ce qui suit est une explication approximative. Tout d'abord, essayons un exemple d'utilisation d'une classe anonyme. En Java, les fonctions ne sont pas des objets de première classe, alors que se passe-t-il si nous renvoyons un objet de classe anonyme qui n'a qu'une seule fonction en tant que membre de la fonction externe à la place?
interface Inner {
public void print();
}
public class ClosureTest {
public Inner outer() {
//Erreur si final n'est pas utilisé ici
final int a = 0;
return new Inner() {
public void print() {
System.out.println(a);
//Parce que c'est définitif, un++Ne peux pas
}
}
}
public static void main(String[] args) {
ClosureTest ct = new ClosureTest();
Inner inner = ct.outer();
inner.print();
}
}
Comme dans l'exemple ci-dessus, vous devez ajouter final, vous ne pouvez donc pas le faire comme JavaScript ou Python. Cependant, puisque final est juste final pour référence, il est possible de changer la valeur de l'élément pour chaque exécution en changeant la variable a en un tableau ou ArrayList. En d'autres termes, la même chose peut être réalisée.
Vient ensuite l'expression lambda. Dans le cas d'une expression lambda, lors du référencement d'une variable hors de portée, cette variable n'a pas besoin d'être finale. Cependant, j'obtiens une erreur lorsque je change la valeur.
public class Closure {
public static void main(String... args) {
//Il n'a pas besoin d'être définitif contrairement à la classe anonyme!
int a = 0;
//Mais un++J'ai une erreur dans la pièce
Runnable r = () -> System.out.println(a++);
r.run();
}
}
Les détails de l'erreur sont les suivants.
Exception in thread "main" java.lang.Error: Unresolved compilation problem: Local variable a defined in an enclosing scope must be final or effectively final
Cela signifie que la variable a doit être définitive ou pratiquement définitive. Substantiellement définitif signifie ne pas faire de changements comme l'exemple ci-dessus.
En d'autres termes, la gestion lors du référencement de variables hors de portée est (presque) la même pour les classes anonymes et les expressions lambda.
Avant, quand j'ai découvert les classes anonymes, les interfaces fonctionnelles, la notation lambda, etc., je me demandais ce que c'était, mais du point de vue de la fermeture, je sens que je peux le comprendre.
Il semble facile d'écrire à l'aide d'expressions lambda C ++ 11.
Tout d'abord, c'est l'expression lambda.
[](inta,intb) -> int { return a + b; }
Utilisez comme suit.
auto fn = [](inta,intb) -> int { return a + b; }
int c = fn();
La partie "-> int" indique le type de retour de cette fonction. Il peut être omis comme suit.
[](inta,intb) { return a + b; }
[] Sera décrit plus tard.
Et
Cette expression lambda définit un objet fonction à la volée:
struct F { auto operator()(inta,intb) const -> decltype(a + b) { return a + b; } };
(Source: cpprefjp - Référence japonaise C ++)
Il est intéressant de surcharger () pour réaliser un objet fonction. La raison pour laquelle il y a deux valeurs de retour, auto et decltype (a + b), est voir ici.
[] Signifie capture.
Les expressions Lambda ont une fonction appelée «capture» qui permet aux variables automatiques en dehors de l'expression lambda d'être référencées dans l'expression lambda. La capture est spécifiée dans le bloc [] au début de l'expression lambda, appelée le lambda-introducer.
La capture comprend la capture de copie et la capture de référence, et vous pouvez spécifier la méthode à capturer par défaut et la méthode à capturer les variables individuelles.
(Source: cpprefjp - Référence japonaise C ++)
Voici un exemple de capture
#include <iostream>
using namespace std;
int main(){
int a = 1;
int b = 2;
//Copier la capture a
auto fn1 = [a]() { cout << a << endl; };
//Capture de référence a
auto fn2 = [&a]() { cout << a << endl; };
//Copier la capture de a et b
auto fn3 = [=]() { cout << a + b << endl; };
//Capture de référence de a et b
auto fn4 = [&]() { cout << a + b << endl; };
a = 1000;
b = 2000;
fn1();
fn2();
fn3();
fn4();
}
production:
1
1000
3
3000
Au moment de la capture de copie, est-ce que ce sera comme suit? S'il vous plaît laissez-moi savoir si vous avez des détails.
C'est du code imaginaire
struct F {
//N'est-ce pas le nom de la variable a et b?
int a = 1;
int b = 2;
auto operator()() const -> decltype(a + b)
{
cout << a + b << endl;
}
};
Et la fermeture peut être écrite comme ça.
#include <iostream>
#include <functional>
std::function<int()> outer()
{
int a = 0;
//Copier la capture a
auto inner = [a]() mutable -> int {
return a++;
};
return inner;
}
int main()
{
auto inner = outer()
std::cout << inner() << std::endl;
std::cout << inner() << std::endl;
std::cout << inner() << std::endl;
return 0;
}
Concernant mutable,
La variable capturée est considérée comme une variable membre de l'objet de fermeture, et l'opérateur d'appel de fonction de l'objet de fermeture est qualifié par défaut. Par conséquent, la variable capturée par copie ne peut pas être réécrite dans l'expression lambda.
Si vous souhaitez réécrire la variable capturée par copie, écrivez mutable après la liste des paramètres d'expression lambda.
(Source: cpprefjp - Référence japonaise C ++) Et cela.
Dans cet exemple, copiez un dans la portée externe et enregistrez-le en tant que variable membre dans l'expression lambda (objet fonction). La variable a copiée comme Java est const (final), mais elle peut être modifiée par la phrase mutable.
Bien sûr, dans l'exemple ci-dessus
auto inner = [&a]() mutable -> int {}
Si vous effectuez une capture de référence comme celle-ci, la destination de référence sera libérée à la fin de l'exécution external (), il doit donc s'agir d'une capture de copie.
Vous pouvez utiliser des fermetures intéressantes en Python. Il existe une bibliothèque appelée http.server et vous pouvez configurer un serveur Web simple. Il est utilisé comme suit, mais le hd du deuxième argument de HTTPServer () doit être un objet de classe. Mais hd fonctionne bien avec les fermetures.
server = HTTPServer(('', int(port)), hd)
server.serve_forever()
Si hd est une fermeture:
def handler_wrapper():
counter = [0]
def handler(*args):
counter[0] += 1
return HandleServer(counter, *args)
return handler
hd = handler_wrapper()
Si j'ai le temps, y compris pourquoi je devrais faire cela, j'aimerais l'écrire dans un article séparé.
La fermeture est difficile.