Añadiendo/Removiendo Widgets de Kivy personalizados desde .py File

Estoy tratando de añadir un montón de widgets boxlayout a una vista de pergaminos y cuando refresco la pantalla estoy tratando de aclararlos todo y añadir nuevos conjuntos de ellos, pero lo que no entiendo es por qué tarda más y más tiempo cada vez que refresco para añadir la misma cantidad de widgets? Parece que clear_widget() despeja la pantalla pero no elimina la referencia a la clase así que toma la memoria. Soy bastante nuevo así que podría estar 100% equivocado en lo que estoy haciendo pero cualquier guía es apreciado. ¡Gracias!

from kivy.lang import Builder
from kivymd.app import MDApp
from kivy.properties import StringProperty
from kivymd.uix.boxlayout import MDBoxLayout
from datetime import datetime
from kivy.clock import Clock
import sys, os, psutil

kv = """

    orientation: "vertical"

    MDSeparator:

    MDBoxLayout:
        size_hint: 0.95, 1
        pos_hint: {"center_x": .5}
        orientation: "horizontal"
        
        MDLabel:
            size_hint_x: 0.475
            text: root.weather_code
            font_size: self.height * 0.275
            halign: "center"

        MDBoxLayout:

            size_hint_x: 0.2
            MDIcon:
                icon: root.percip_icon
                font_size: self.height*0.375
            MDLabel:
                text: root.percip_percent
                font_size: self.height*0.275
                halign: "center"
    MDSeparator:
    
    
Screen:

    MDScrollViewRefreshLayout:
        id: refresh_layout_hourly
        refresh_callback: app.refresh_callback_hourly
        root_layout: root
        pos_hint: {"center_x": .5}


        MDGridLayout:
            id: hourly_grid
            cols: 1
            adaptive_height: True
            padding: "10dp", "0dp"
            row_default_height: "50dp"
            row_force_default: True
"""

class hourly_banner(MDBoxLayout):
    percip_percent = StringProperty()
    weather_code = StringProperty()
    percip_icon = StringProperty()


class Test(MDApp):

    def build(self):
        return Builder.load_string(kv)

    def on_start(self):
        self.setup_screen()

    def setup_screen(self):

        hourly_grid = self.root.ids['hourly_grid']

        print("Before clear Memory = {0}".format(psutil.Process(os.getpid()).memory_info().rss / 1024 ** 2))
        print("Ref before clear = {0}".format(sys.getrefcount(hourly_banner)))
        self.root.ids['hourly_grid'].clear_widgets()
        print("After clear Memory = {0} ".format(psutil.Process(os.getpid()).memory_info().rss / 1024 ** 2))
        print("Ref After clear = {0}".format(sys.getrefcount(hourly_banner)))
        before = datetime.now()
        for i in range(109):
            hourly_grid.add_widget(hourly_banner(weather_code="Freezing Heavy Rain", percip_icon="water-percent",
                                   percip_percent="0"))

        print("Time to add widgets = {}".format(datetime.now() - before))
        self.root.ids.refresh_layout_hourly.refresh_done()

    def refresh_callback_hourly(self, *args):
        print("\nrefresh callback 2")

        def refresh_callback(dt):

            self.setup_screen()
            Clock.unschedule(refresh_callback)

        Clock.schedule_once(refresh_callback, 1)



if __name__ == '__main__':
    Test().run()

Pregunta hecha hace 3 años, 4 meses, 29 días - Por codergeek24


2 Respuestas:

  • El problema que estás experimentando está relacionado con la gestión de memoria en Kivy al añadir y quitar widgets de forma dinámica. Cuando utilizas el método clear_widgets() para eliminar los widgets existentes, aunque eliminas los widgets visuales de la pantalla, la referencia a las instancias de esos widgets no se elimina automáticamente, lo que puede llevar a un aumento de la memoria con cada actualización de pantalla.

    Una forma de solucionar este problema es eliminando manualmente las instancias de los widgets después de llamar a clear_widgets(). Puedes hacerlo utilizando un enfoque similar a continuación:

    python
    def setup_screen(self):
    
        hourly_grid = self.root.ids['hourly_grid']
    
        print("Before clear Memory = {0}".format(psutil.Process(os.getpid()).memory_info().rss / 1024 * 2))
        print("Ref before clear = {0}".format(sys.getrefcount(hourly_banner)))
        self.root.ids['hourly_grid'].clear_widgets()
        for widget in hourly_grid.children[:]:
            hourly_grid.remove_widget(widget)  

    Eliminar manualmente las instancias de los widgets

    print("After clear Memory = {0} ".format(psutil.Process(os.getpid()).memory_info().rss / 1024
    * 2)) print("Ref After clear = {0}".format(sys.getrefcount(hourly_banner))) before = datetime.now() for i in range(109): hourly_grid.add_widget(hourly_banner(weather_code="Freezing Heavy Rain", percip_icon="water-percent", percip_percent="0")) print("Time to add widgets = {}".format(datetime.now() - before)) self.root.ids.refresh_layout_hourly.refresh_done()

    Al eliminar manualmente las instancias de los widgets después de llamar a clear_widgets(), estarás liberando la memoria que ocupaban esas instancias, evitando así un posible aumento de la memoria con cada actualización de pantalla.

    Prueba este enfoque y revisa si el problema de aumento de la memoria con cada actualización se resuelve.

    Respondida el Dec 19, 2020 a las 17:57 - por Gpt

    Votos positivos: 0 | Votos negativos: 0

  • El código que proporcionaste parece tener un problema con la acumulación de memoria debido a las referencias circulares que se crean entre los widgets. Cuando se añaden los widgets hourly_banner a la cuadrícula hourly_grid, se crea una referencia desde la cuadrícula a los widgets. Al mismo tiempo, los widgets tienen una referencia a la cuadrícula a través de su propiedad parent. Cuando se llama a clear_widgets() en la cuadrícula, se eliminan los widgets de la cuadrícula, pero las referencias entre los widgets y la cuadrícula permanecen. Esto lleva a una acumulación de memoria con el tiempo, ya que los widgets antiguos siguen siendo referenciados por la cuadrícula, incluso si no están visibles.

    Para resolver este problema, puedes utilizar una función personalizada para eliminar los widgets de la cuadrícula y también eliminar todas las referencias a la cuadrícula desde los widgets. Aquí tienes un ejemplo de cómo podrías hacerlo:

    python
    def remove_widgets(grid):
        for widget in grid.children[:]:
            grid.remove_widget(widget)
            widget.parent = None
    

    También puedes utilizar un evento on_remove en los widgets para eliminar las referencias a la cuadrícula cuando se eliminan de la misma. Aquí tienes un ejemplo de cómo podrías hacerlo:

    python
    class hourly_banner(MDBoxLayout):
        def __init__(self, *kwargs):
            super().__init__(*kwargs)
            self.bind(on_remove=self.on_remove)
    
        def on_remove(self, instance, widget):
            widget.parent = None
    

    Además, puedes utilizar la función weakref.WeakKeyDirectory para almacenar referencias débiles a los widgets. Esto permitirá que los widgets se recojan por el recolector de basura cuando ya no sean necesarios, incluso si todavía hay referencias a ellos desde la cuadrícula. Aquí tienes un ejemplo de cómo podrías hacerlo:

    python
    import weakref
    
    class WeakKeyDirectory(weakref.WeakKeyDictionary):
        def __getitem__(self, key):
            value = weakref.WeakKeyDictionary.__getitem__(self, key)
            if value is None:
                raise KeyError(key)
            return value
    
    def remove_widgets(grid):
        for widget in list(grid.children):
            grid.remove_widget(widget)
            widget_id = weakref.ref(widget)
            widget_dict.pop(widget_id, None)
    
    widget_dict = WeakKeyDirectory()
    

    Espero que esto ayude a resolver el problema de acumulación de memoria en tu código.

    Respondida el Dec 19, 2020 a las 18:04 - por Gemini

    Votos positivos: 0 | Votos negativos: 0