La magia está a solo un paso de la realidad: profundice en la llamada circular, la referencia circular y la importación circular de Python

Cuando era joven, a menudo me preocupaban algunas preguntas ridículas; aunque enfrenté más dudas en la edad adulta, parecía que me había adaptado a caminar en confusión, pero perdí el entusiasmo por conocer las respuestas a los problemas no resueltos. sentido. Por ejemplo, de pie en medio de dos espejos opuestos, ¿verá usted mismo innumerables? Para un yo joven, esta es de hecho una pregunta muy mágica. No es hasta que entiendo la atenuación de la energía cuántica de la luz que he encontrado la respuesta.
Inserte la descripción de la imagen aquí
Recientemente, algunos estudiantes consultaron sobre la referencia circular y la recolección de basura de los objetos de Python, combinados con la llamada circular y los problemas de importación circular encontrados hace unos días, cuando estaba clasificando las respuestas, de repente me di cuenta de que estos problemas me han desconcertado durante muchos años. La cuestión de los "dos espejos" en realidad tiene algo en común: todos se ven un poco mágicos, ¡dar la vuelta es el mundo real!

1. La llamada de bucle de función ruinosa

Si varias funciones se llaman entre sí para formar un bucle cerrado, se forma una llamada cíclica de la función. En el siguiente ejemplo, la función a llama a la función b en su cuerpo de función, y la función b llama a la función a en su cuerpo de función. Esta es una llamada de bucle de función típica.

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

En este caso, ¿qué sucede cuando se llama a una función (ya sea una función o una función 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

Pronto encontrará que hay un problema con la operación, y el sistema arroja continuamente excepciones Después de unos pocos miles de líneas de desplazamiento, la operación finalmente termina. El consejo final es:

RecursionError: se excedió la profundidad de recursividad máxima al decapar un objeto

Esto significa que se produjo un error de recursividad y se superó la profundidad máxima de recursividad al decapar el objeto.

Resulta que las llamadas de bucle son similares a las llamadas recursivas. Para proteger la pila del desbordamiento, el entorno de Python generalmente establece la protección de profundidad de recursión. Una vez que se comprueba la profundidad de recursividad, se lanzará un error recursivo y luego se saldrá de la pila capa por capa. Es por eso que la pantalla se desplaza con miles de mensajes de error.

Con respecto a la profundidad de recursividad del entorno de Python, puede verlo y configurarlo a través del módulo sys.

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

2. Referencias del ciclo de objetos que viven y mueren juntos

La llamada cíclica de una función no es difícil de entender, mientras que la referencia cíclica de un objeto es un poco confusa. ¿Qué es una referencia circular de un objeto? Cuando se crea un objeto (como instanciar una clase), Python establece un contador de referencia para este objeto. Si se hace referencia a este objeto, como cuando está asociado con un nombre de variable, el contador de referencia del objeto aumenta en 1, y si se cancela la relación de asociación, el contador de referencia del objeto se reduce en 1. Cuando el contador de referencia de un objeto es 1 (sobre este punto, solo se basa en la observación personal, no se ve ninguna declaración autorizada), el sistema automáticamente recuperará el objeto. Este es el mecanismo de recolección de basura de Python. El siguiente código, con la ayuda del módulo sys, puede ver intuitivamente el cambio del contador de referencia de un objeto de lista.

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

Cuando varios objetos tienen referencias mutuas de miembros, una vez que se forma un bucle cerrado, se producirán las denominadas referencias circulares de objetos. Veamos un ejemplo: ayb son dos objetos de instancia de la clase A. Cuando estos dos objetos son del, se llamará al método __del__ del objeto y finalmente se mostrará el "fin de la operación".

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('运行结束')

Los resultados son los que esperábamos.

a: init
b: init
a: del
b:
fin de la operación del

Sin embargo, después de crear las instancias ayb, si apuntamos a.alguien hacia byb.alguien hacia a, entonces se formará un ciclo cerrado donde los miembros se refieren entre sí entre instancias.

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('运行结束')

Ejecute este código, encontrará que cuando los dos objetos se eliminan, el método __del__ del objeto no se ejecuta inmediatamente, sino solo después de que finaliza el programa.

a: init
b: init
termina la operación
a: del
b: del

Esto significa que durante la ejecución del programa, la memoria que debe recuperarse no se recupera correctamente. Este tipo de problema es una pérdida de memoria y debe tomarse en serio. Por lo general, podemos usar el módulo gc para forzar la recuperación de la memoria.

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('运行结束')

Mirando los resultados nuevamente, todo es normal.

a: init
b: init
a: del
b:
fin de la operación del

3. Importación de circulación del módulo de torneado y rectificado

Hablando relativamente, la introducción cíclica de módulos generalmente ocurre raramente. Si sucede, debe ser causado por la división funcional irrazonable del módulo. Al ajustar la definición del módulo, el problema se puede resolver fácilmente. Usemos un ejemplo más simple para demostrar cómo se genera la importación cíclica del módulo.

El contenido del archivo de script llamado a.py es el siguiente:

import b

MODULE_NAME = 'a'
print(b.MODULE_NAME)

El contenido del archivo de script denominado b.py es el siguiente:

import a

MODULE_NAME = 'b'
print(a.MODULE_NAME)

Los dos scripts hacen referencia entre sí, y cada uno usa la constante MODULE_NAME definida por el otro. No importa qué script ejecutemos, no se ejecutará correctamente debido a la importación circular del módulo.

Traceback (última llamada más reciente):
Archivo "a.py", línea 1, en la
importación b
Archivo "D: \ temp \ csdn \ b.py", línea 1, en la
importación de un
Archivo "D: \ temp \ csdn \ a.py ”, línea 4,
impresa (b.MODULE_NAME)
AttributeError: el módulo 'b' no tiene el atributo 'MODULE_NAME'

Supongo que te gusta

Origin blog.csdn.net/xufive/article/details/109552960
Recomendado
Clasificación