Saltar al contenido

forma más rápida de crear JSON para reflejar una estructura de árbol en Python/Django usando mptt

Esta noticia ha sido analizado por especialistas así aseguramos la exactitud de nuestro contenido.

Solución:

Sospecho que, con mucho, la mayor desaceleración es que esto hará 1 consulta de base de datos por nodo. La representación json es trivial en comparación con los cientos de viajes de ida y vuelta a su base de datos.

Debe almacenar en caché los elementos secundarios en cada nodo para que esas consultas se puedan realizar todas a la vez. django-mptt tiene una función cache_tree_children() con la que puede hacer esto.

import json
from mptt.templatetags.mptt_tags import cache_tree_children

def recursive_node_to_dict(node):
    result = 
        'id': node.pk,
        'name': node.name,
    
    children = [recursive_node_to_dict(c) for c in node.get_children()]
    if children:
        result['children'] = children
    return result

root_nodes = cache_tree_children(Node.objects.all())
dicts = []
for n in root_nodes:
    dicts.append(recursive_node_to_dict(n))

print json.dumps(dicts, indent=4)

La codificación json personalizada, si bien puede proporcionar una ligera aceleración en algunos escenarios, es algo que desaconsejaría encarecidamente, ya que será una gran cantidad de código y es algo en lo que es fácil equivocarse.

Parece que su versión actualizada tendría muy pocos gastos generales. Creo que sería un poco más eficiente (¡y también más legible!) Usar una lista de comprensión:

def serializable_object(node):
    "Recurse into tree to build a serializable object"
    obj = 
        'name': node.name,
        'children': [serializable_object(ch) for ch in node.get_children()]
    
    return obj

Además de eso, todo lo que puedes hacer es perfilarlo para encontrar los cuellos de botella. Escriba un código independiente que cargue y serialice sus 300 nodos y luego ejecútelo con

python -m profile serialize_benchmark.py

(o -m cProfile si eso funciona mejor).

El puede ver 3 posibles cuellos de botella diferentes:

  • acceso a la base de datos (.get_children() y .name) — No estoy seguro exactamente de lo que sucede debajo del capó, pero he tenido un código como este que realiza una consulta de base de datos para cada nodo, lo que agrega una sobrecarga tremenda. Si ese es su problema, probablemente pueda configurar esto para hacer una “carga ansiosa” usando select_related o algo similar.
  • sobrecarga de la llamada de función (p. ej. serializable_object sí mismo) – Solo asegúrese de que ncalls para serializable_object parece un número razonable. Si entiendo su descripción, debería estar en el vecindario de 300.
  • serializando al final (json.dumps(nodeInstance)) — No es probable que sea el culpable ya que usted dijo que son solo 300 nodos, pero si ve que esto requiere mucho tiempo de ejecución, asegúrese de que las aceleraciones compiladas para JSON funcionen correctamente.

Si no puede decir mucho al perfilarlo, haga una versión simplificada que, digamos, llama recursivamente node.name y node.get_children() pero no almacena los resultados en una estructura de datos, y vea cómo se compara.


Actualización: Hay 2192 llamadas a execute_sql en la solución 3 y 2192 en la solución 5, por lo que creo que el exceso de consultas a la base de datos es un problema y eso select_related no hizo nada de la forma en que se usa arriba. Mirando el problema #88 de django-mptt: Permitir select_related en los métodos del modelo sugiere que lo estás usando más o menos bien, pero tengo mi duda, y get_children contra get_descendants podría hacer una gran diferencia.

También hay un montón de tiempo ocupado por copy.deepcopy, lo cual es desconcertante porque no lo está llamando directamente, y no veo que se llame desde el código MPTT. ¿Qué es tree.py?

Si está trabajando mucho con la creación de perfiles, le recomiendo la herramienta realmente ingeniosa RunSnakeRun, que le permite ver los datos de su perfil en una forma de cuadrícula realmente conveniente y dar sentido a los datos más rápidamente.

De todos modos, aquí hay un intento más de simplificar el lado DB de las cosas:

import weakref
obj_cache = weakref.WeakValueDictionary()

def serializable_object(node):
    root_obj = 'name': node.get_wbs_code(), 'wbsCode': node.get_wbs_code(),
            'id': node.pk, 'level': node.level, 'position': node.position,
            'children': []
    obj_cache[node.pk] = root_obj
    # don't know if the following .select_related() does anything...
    for descendant in node.get_descendants().select_related():
        # get_descendants supposedly traverses in "tree order", which I think
        # means the parent obj will always be created already
        parent_obj = obj_cache[descendant.parent.pk]    # hope parent is cached
        descendant_obj = 'name': descendant.get_wbs_code(),
            'wbsCode': descendant.get_wbs_code(), 'id': descendant.pk,
            'level': descendant.level, 'position': descendant.position,
            'children': []
        parent_obj['children'].append(descendant_obj)
        obj_cache[descendant.pk] = descendant_obj
    return root_obj

Tenga en cuenta que esto ya no es recursivo. Procede iterativamente a través de los nodos, teóricamente después de que sus padres hayan sido visitados, y todo usa una gran llamada para MPTTModel.get_descendants()así que espero que esté bien optimizado y los cachés .parentetc. (o tal vez hay una forma más directa de llegar al padre key?). Crea cada obj sin hijos inicialmente, luego “injerta” todos los valores a sus padres después.

Te mostramos reseñas y calificaciones

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *