[Tkinter] 함수가 왜 미리 실행될까?

😟 문제 상황

 

ui.py 파일 속 시작 버튼에 command를 연결해주는 과정에서 문제가 발생했다.
함수를 분리하기 위해 func.py 파일을 만든 후 함수를 연결을 했으나 프로그램이 실행하자마자 함수가 자동으로 실행된 것이다. 그 후에는 버튼을 눌러도 동작하지 않는 현상이 발생했다. 💦

 

아래는 문제의 코드이다.

# ui.py 파일 속 일부 코드
start_Button = Button(
	frame_run,
    text="시작",
    width=10,
    height=2,
    font=normal_font,
    command=lotto_start(list_file, game_combobox)
)


# func.py 파일 속 일부 코드
def lotto_start(list_file, game_combobox):
    print("시작 버튼입니다!")

 

해결하기 위해 인터넷을 검색해보니 함수 객체와 함수 호출에 대한 차이라는 것을 알게 되었다. 알게 된 내용을 토대로 글을 정리해보고자 포스팅을 하게 되었다.

 


✅ 함수 객체 VS 함수 호출

Python에서 함수는 하나의 값(객체)으로 다룰 수 있다.

def sample():
    print("안녕하세요!")

say_hello = sample  # 함수 객체를 변수에 저장
say_hello()        # => '안녕하세요!' 출력

 

이처럼 sample 함수 객체이고, sample() 함수 호출이다.

 


🎯 Tkinter command=는 함수 객체를 기대함

Tkinter에서 command 옵션은 버튼이 눌렸을 때 "나중에 실행할 함수"를 등록하는 역할을 한다.

 

그러므로 아래처럼 작성해야 올바른 동작을 한다.

Button(root, text="시작", command=lotto_start)  # 함수 객체만 전달

 

즉, 나는 command에 함수 호출을 하였기 때문에 문제가 발생한 것이였다. 왜냐하면 아래 코드와 같이 인자 값을 2개를 넘겨줘야하기 때문에 강제로 괄호()를 사용했기 때문.

# ❌ 이렇게 하면 즉시 실행됨
Button(root, text="시작", command=lotto_start(listbox, game_combobox))

 

이 코드는 실행될 때 lotto_start(listbox, game_combobox)가 즉시 호출되고 그 반환값이 command에 전달된다.
따라서 프로그램이 시작하자마자 함수가 실행되고 버튼을 눌러도 아무 동작이 없다.


✅ 해결 방법: lambda 또는 functools.partial

1. lambda 사용

Button(root, text="시작", command=lambda: lotto_start(listbox, game_combobox))

 

lambda는 익명 함수로 버튼을 눌렀을 때 lotto_start(listbox, game_combobox)가 실행되도록 한다.

 

2. functools.partial 사용

from functools import partial

Button(root, text="시작", command=partial(lotto_start, listbox, game_combobox))

 

partial은 인자가 있는 함수를 미리 정의해 두고 나중에 호출할 수 있도록 만들어준다.

 


✏️ 정리

작성 방식 의미 동작
command=func 함수 객체 전달 버튼 클릭 시 실행
command=func() 함수 호출 후 반환값 전달 즉시 실행됨
command=lambda: func(arg) 익명 함수로 감싸서 실행 지연 버튼 클릭 시 실행
command=partial(func, arg) 미리 인자 고정한 함수 전달 버튼 클릭 시 실행

 

나는 lambda 방식을 채택하였고 아래와 같이 작성하였다.

# ui.py 파일 속 일부 코드
start_Button = Button(
	frame_run,
    text="시작",
    width=10,
    height=2,
    font=normal_font,
    command=lambda: lotto_start(list_file, game_combobox)
)

# func.py 파일 속 일부 코드
from tkinter import *
import random

def lotto_start(list_file, game_combobox):
    list_file.delete(0, END)  # 시작 누르면서 동시에 초기화
    for i in range(int(game_combobox.get())):
        lotto = random.sample(range(1, 46), 6)
        lotto.sort()
        list_file.insert(END, lotto)

 

게임 수에 지정된 숫자(game_combobox) 만큼 로또 번호 6개를 랜덤으로 list_file 내부에 insert 한 모습이다.

 


✍️ 마무리

  • Tkinter에서 버튼을 누를 때 특정 동작이 실행되게 하려면 반드시 지연 실행이 가능한 함수 객체를 전달해야 한다.
  • 특히 인자를 전달할 때는 lambda partial을 사용하는 것이 필수
  • 이 개념은 Tkinter뿐 아니라 이벤트 기반 프로그래밍 전반에 걸쳐 자주 등장하니 꼭 익혀두자!