본문 바로가기
  • ANALOG CODE
  • AnalogCode
개발

파이썬 Kivy: weakly-referenced object no longer exists 예외 상황

by 아날로그코더 2024. 8. 14.
반응형

파이썬 앱개발 프레임워크 Kivy를 사용하다가 생기는 문제들을 해결하면서 원인과 해결법을 찾아서 정리하였다. 위젯 (Widget)을 추가하고 삭제하다보면 아래 그림과 같이 weakly-referenced object no longer exists 예외가 발생하는 상황이 있는데 이유를 찾아보고 해결법을 알아보자.

 

 

 

Kivy 예외 발생 상황

위젯(Widget)의 remove_widget()add_widget() 함수를 사용하여 위젯을 표시하거나 숨기려고 한다. 아래의 간단한 예제를 보자.

아래 프로그램은 Remove 버튼을 누르면 Hello World 레이블 위젯이 사라지고 Add를 누르면 다시 보이게 하는 예제이다.

 

위 프로그램의 소스 코드는 아래와 같다.

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget

Builder.load_string('''
#:import gc gc

<TestWidget>:
    wid: label_wid
    
    # Label Widget
    Label:
        id: label_wid
        text: 'Hello World'
        font_size: '50sp'
        pos: (500, 300)
        
    # Add Button Widget
    Button:
        text: 'Add'
        pos: (100, 100)
        size: (200, 100)
        on_press: root.add_widget(root.wid)
        
    # Remove Button Widget
    Button:
        text: 'Remove'
        pos: (400, 100)
        size: (200, 100)
        on_press: root.remove_widget(root.wid)
        
    # Garbage Collection Widget
    Button:
        text: 'GC'
        pos: (700, 100)
        size: (200, 100)
        on_press: gc.collect()
''')

class TestWidget(Widget):
    pass

class MainApp(App):
    def build(self):
        return TestWidget()

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

 

소스는 간단하다. 테스트 상황을 반들기 위해 아래와 같이 4개의 위젯을 배치하였다. 

  • Label 위젯: Hello World를 표시하며 추가 및 삭제를 할 대상이 되는 위젯
  • Add Button 위젯: Label위젯을 추가해주는 역할
  • Remove Button 위젯: Label 위젯을 삭제해주는 역할
  • Garbage Collection 위젯: GC 상황을 만들어주기 위한 역할

 

테스트 방법

1. Remove 버튼 클릭

 그러면 Hello World가 표시되는 Label 위젯이 사라지게 된다. 

2. GC 버튼 클릭

GC(Garbage Collection)이 실행되어 가비지 콜렉터에 의해 Label Widget에 할당된 메모리가 수거되어 소멸된다.

 

GC는 프로그램 실행중에 언젠가는 수행되어질 수 있으나, 빠른 테스트를 위해 직접적으로 GC 상황을 만들어 주는 역할을 한다.

 

3. Add 버튼 클릭

소멸된 Label 위젯을 add_widget 함수를 통해 추가하려고 하여 예외가 발생하게 된다.

 

 

 

예외 발생 원인

발생 원인은 kv 언어의 아래 부분에 있다.

<TestWidget>:
    # 위젯의 id를 이용한 참조
    wid: label_wid
    
    # Label Widget
    Label:
        id: label_wid

 

wid는 Label 위젯의 id값 label_wid를 참조하고 있는데, 이것은 weakref (약한참조) 에 해당한다.

 

weakref (약한참조)

파이썬 문서에서 weakref에 대한 정의가 아래와 같이 되어 있다.

weakref (약한 참조)

객체에 대한 약한 참조만으로는 객체를 살아있게 유지할 수 없습니다: 참조대상에 대한 유일한 남은 참조가 약한 참조면, 가비지 수거는 자유롭게 참조대상을 파괴하고 메모리를 다른 용도로 재사용할 수 있습니다.

Python 문서 본문 보기

 

 

Kivy 문서에서 id 값은 위젯 자체가 아닌 weakref에 해당한다고 정의되어 있다.

An id is a weakref to the widget and not the widget itself. As a consequence, storing the id is not sufficient to keep the widget from being garbage collected.

Kivy 문서 보기

 

따라서 아무리 위젯의 id값을 참조하고 있더라도 이것은 어디까지나 weakref 이므로 remove_widget()을 해버리는 순간 참조카운트가 사라져서 언제든 가비지 콜렉터에 의해 소멸될 수 있는 것이다.

 

위의 예제에서는 단지 테스트를 빠르게 하기 위해 가비지 콜렉터를 바로 동작시키기 위한 버튼을 넣어서 GC를 명시적으로 수행해 주었을뿐이다.

 

이런식으로 코드를 짜면 remove_widget으로 제거된 위젯이 당장은 남아 있더라도 언젠가는 GC에 의해 위젯이 소멸되어 프로그램 실행중에 에러가 발생하게 되는 것이다.

 

해결방법

해결방법은 이미 Kivy 문서에 설명되어 있고 아주 간단하다. GC에 의해 소멸되는 것을 방지하기 위해 객체의 weakref가 아닌 객체 자체를 참조할 수 있도록 만들어 주면 되는 것이다.

 

아래 코드와 같이 wid가 참조한는 대상을 바꿔주기만 하면 된다.

<TestWidget>:
	# __self__ 를 이용하여 객체 자체를 참조하도록 함
    wid: label_wid.__self__

 

label_wid 에서 label_wid.__self__ 로 바꿔주기만 하였다. 

 

__self__ 는 해당 id 위젯 객체의 직접 참조를 의미하는 속성이다.

 

 

Kivy 소스코드에서 아래와 같이 정의되어 있다. 참고로 Widget class는 EventDispatcher를 상속한다.

cdef class EventDispatcher(ObjectWithUid):

    ...

    @property
    def __self__(self):
        return self

 

__self__는 객체 자신을 참조하도록 되어 있는 것이다.

 

 

이렇게 코드를 수정하고 테스트해보면 Remove를 하고 GC를 아무리하더라도 Label 위젯은 소멸되지 않고 다시 Add가 되는 것을 확인할 수 있다.

 

반응형

댓글