La magie n'est qu'à un pas de la réalité: élaborer sur les appels circulaires, les références circulaires et les importations circulaires de Python

Quand j'étais jeune, j'étais souvent troublé par des questions ridicules - même si j'avais plus de doutes à l'âge adulte, il semblait que je m'étais adapté à la marche dans la confusion, mais que j'avais perdu l'envie de connaître les réponses à des problèmes non résolus. sens. Par exemple, debout au milieu de deux miroirs opposés, verrez-vous vous-même innombrables? Pour un jeune moi, c'est en effet une question très magique, ce n'est que lorsque j'ai compris l'atténuation de l'énergie quantique lumineuse que j'ai trouvé la réponse.
Insérez la description de l'image ici
Récemment, certains étudiants se sont consultés sur la référence circulaire et le ramasse-miettes d'objets Python, combinés aux problèmes d'appel circulaire et d'importation circulaire rencontrés il y a quelques jours, alors que je triais les réponses, j'ai soudainement réalisé que ces problèmes m'étonnaient depuis de nombreuses années. La question des «deux miroirs» a en fait quelque chose en commun: ils ont tous l'air un peu magique, se retourner c'est le monde réel!

1. L'appel de boucle de fonction ruineux

Si plusieurs fonctions s'appellent l'une l'autre pour former une boucle fermée, un appel cyclique de la fonction est formé. Dans l'exemple suivant, la fonction a appelle la fonction b dans son corps de fonction et la fonction b appelle la fonction a dans son corps de fonction. Il s'agit d'un appel de boucle de fonction typique.

>>> def a():
		print('我是a')
		b()
	
>>> def b():
		print('我是b')
		a()
	
>>> a()

Dans ce cas, que se passe-t-il lorsqu'une fonction est appelée (que ce soit une fonction ou une fonction b)?

>>> a()
我是a
我是b
我是a
我是b
...... # 此处省略了一千余行
Traceback (most recent call last):
  File "<pyshell#64>", line 1, in <module>
    a()
  File "<pyshell#59>", line 3, in a
    b()
...... # 此处省略了两千余行
RecursionError: maximum recursion depth exceeded while pickling an object

Bientôt, vous constaterez qu'il y a un problème avec l'opération, et le système lance continuellement des exceptions.Après quelques milliers de lignes de défilement, l'opération se termine enfin. Le dernier conseil est:

RecursionError: profondeur maximale de récursivité dépassée lors du décapage d'un objet

Cela signifie qu'une erreur de récursivité s'est produite et que la profondeur de récursivité maximale a été dépassée lors du décapage de l'objet.

Il s'avère que les appels de boucle sont similaires aux appels récursifs. Afin de protéger la pile contre le débordement, l'environnement Python définit généralement la protection de la profondeur de récursivité. Une fois la profondeur de récursivité vérifiée, une erreur récursive sera générée, puis la pile sera quittée couche par couche. C'est pourquoi l'écran défile avec des milliers de messages d'erreur.

En ce qui concerne la profondeur de récursivité de l'environnement Python, vous pouvez l'afficher et la définir via le module sys.

>>> import sys
>>> sys.getrecursionlimit()
1000
>>> sys.setrecursionlimit(500)
>>> sys.getrecursionlimit()
500

2. Références de cycle d'objets qui vivent et meurent ensemble

Il n'est pas difficile de comprendre l'appel circulaire d'une fonction, mais la référence circulaire d'un objet est un peu déroutante. Qu'est-ce qu'une référence circulaire d'un objet? Lorsqu'un objet est créé (comme l'instanciation d'une classe), Python définit un compteur de référence pour cet objet. Si cet objet est référencé, par exemple associé à un nom de variable, le compteur de référence de l'objet est augmenté de 1, et si la relation d'association est annulée, le compteur de référence de l'objet est diminué de 1. Lorsque le compteur de référence d'un objet est 1 (à propos de ce point, il est uniquement basé sur une observation personnelle, aucune déclaration faisant autorité n'est vue), le système récupérera automatiquement l'objet. C'est le mécanisme de garbage collection de Python. Le code suivant, à l'aide du module sys, permet de voir intuitivement le changement du compteur de référence d'un objet liste.

>>> import sys
>>> a = list('abc')
>>> sys.getrefcount(a)
2
>>> b = a
>>> sys.getrefcount(a)
3
>>> del b
>>> sys.getrefcount(a)
2

Lorsque plusieurs objets ont des références de membres mutuels, une fois qu'une boucle fermée est formée, ce que l'on appelle des références circulaires d'objet se produisent. Prenons un exemple: a et b sont deux objets d'instance de classe A. Lorsque ces deux objets sont del, la méthode __del__ de l'objet sera appelée, et enfin la "fin d'opération" est affichée.

class A:
    def __init__(self, name, somebody=None):
        self.name = name
        self.somebody = somebody
        print('%s: init'%self.name)
    def __del__(self):
        print('%s: del'%self.name)

a = A('a')
b = A('b')

del a
del b

print('运行结束')

Les résultats sont comme nous l'espérions.

a: init
b: init
a: del
b:
fin de l'opération del

Cependant, après avoir créé les instances a et b, si nous pointons a.quelqu'un vers b et b.quelqu'un vers a, alors une boucle fermée sera formée où les membres se réfèrent entre les instances.

class A:
    def __init__(self, name, somebody=None):
        self.name = name
        self.somebody = somebody
        print('%s: init'%self.name)
    def __del__(self):
        print('%s: del'%self.name)

a = A('a')
b = A('b')

a.somebody = b
b.sombody = a

del a
del b

print('运行结束')

Exécutez ce code, vous constaterez que lorsque les deux objets sont supprimés, la méthode __del__ de l'objet n'est pas exécutée immédiatement, mais seulement après la fin du programme.

a: init
b: l'
opération init se termine
a: del
b: del

Cela signifie que lors de l'exécution du programme, la mémoire qui doit être récupérée n'est pas récupérée correctement. Ce type de problème est une fuite de mémoire et doit être pris au sérieux. Habituellement, nous pouvons utiliser le module gc pour forcer la récupération de la mémoire.

import gc

class A:
    def __init__(self, name, somebody=None):
        self.name = name
        self.somebody = somebody
        print('%s: init'%self.name)
    def __del__(self):
        print('%s: del'%self.name)

a = A('a')
b = A('b')

a.somebody = b
b.sombody = a

del a
del b

gc.collect()

print('运行结束')

En regardant à nouveau les résultats, tout est normal.

a: init
b: init
a: del
b:
fin de l'opération del

3. Importation de circulation du module de tournage et de meulage

Relativement parlant, l'introduction cyclique de modules se produit généralement rarement. Si cela se produit, cela doit être causé par la division fonctionnelle déraisonnable du module.En ajustant la définition du module, le problème peut être facilement résolu. Prenons un exemple le plus simple pour montrer comment l'importation cyclique du module est générée.

Le contenu du fichier de script nommé a.py est le suivant:

import b

MODULE_NAME = 'a'
print(b.MODULE_NAME)

Le contenu du fichier de script nommé b.py est le suivant:

import a

MODULE_NAME = 'b'
print(a.MODULE_NAME)

Les deux scripts se référencent et chacun utilise la constante MODULE_NAME définie par l'autre. Quel que soit le script que nous exécutons, il ne s'exécutera pas correctement en raison de l'importation circulaire du module.

Traceback (dernier appel le plus récent):
Fichier «a.py», ligne 1, en
import b
Fichier «D: \ temp \ csdn \ b.py», ligne 1, en
import un
fichier «D: \ temp \ csdn \ a.py ", ligne 4, en
version imprimée (b.MODULE_NAME)
AttributeError: le module" b "n'a pas d'attribut" MODULE_NAME "

Je suppose que tu aimes

Origine blog.csdn.net/xufive/article/details/109552960
conseillé
Classement