pythonware.com

[출처] http://www.pythonware.com/library/tkinter/introduction/

products :::library :::search :::daily Python-URL!

An Introduction to Tkinter

Next

An Introduction to Tkinter

Fredrik Lundh

Copyright © 1999 by Fredrik Lundh


Table of Contents
Preface
I. Introducing Tkinter
1. What's Tkinter?
2. Hello, Tkinter
Running the Example
Details
3. Hello, Again
Running the Example
Details
More on widget references
More on widget names
4. Tkinter Classes
Widget classes
Mixins
5. Widget Configuration
Configuration Interface
Backwards Compatibility
6. Widget Styling
Colors
Fonts
Text Formatting
Borders
Cursors
7. Events and Bindings
Events
8. Application Windows
Base Windows
Menus
Toolbars
Status Bars
9. Standard Dialogs
Message Boxes
Data Entry
10. Dialog Windows
Grid Layouts
Validating Data
II. Tkinter Reference
11. The BitmapImage Class
When to use the BitmapImage Class
Patterns
Methods
Options
12. The Button Widget
When to use the Button Widget
Patterns
Methods
Helpers
Options
13. The Canvas Widget
When to use the Canvas Widget
Concepts
Patterns
Methods
Options
14. The Canvas Arc Item
Methods
Options
15. The Canvas Bitmap Item
Bitmaps
Methods
Options
16. The Canvas Image Item
Methods
Options
17. The Canvas Line Item
Methods
Options
18. The Canvas Oval Item
Methods
Options
19. The Canvas Polygon Item
Methods
Options
20. The Canvas Rectangle Item
Methods
Options
21. The Canvas Text Item
Methods
Options
22. The Canvas Window Item
Methods
Options
23. The Checkbutton Widget
When to use the Checkbutton Widget
Patterns
Methods
Options
24. The DoubleVar Class
When to use the DoubleVar Class
Patterns
Methods
25. The Entry Widget
When to use the Entry Widget
Concepts
Patterns
Methods
Options
26. The Font Class
Patterns
Methods
Functions
Options
27. The Frame Widget
When to use the Frame Widget
Patterns
Methods
Options
28. The Grid Geometry Manager
When to use the Grid Manager
Patterns
Methods
Options
29. The IntVar Class
When to use the IntVar Class
Patterns
Methods
30. The Label Widget
When to use the Label Widget
Patterns
Methods
Options
31. The Listbox Widget
When to use the Listbox Widget
Patterns
Methods
Options
32. The Menu Widget
When to use the Menu Widget
Patterns
Methods
Options
33. The Menubutton Widget
When to use the Menubutton Widget
Patterns
Methods
Options
34. The Message Widget
When to use the Message Widget
Patterns
Methods
Options
35. The Pack Geometry Manager
When to use the Pack Manager
Patterns
Methods
Options
36. The PhotoImage Class
When to use the PhotoImage Class
Patterns
Methods
Options
37. The Place Geometry Manager
When to use the Place Manager
Patterns
Methods
Options
38. The Radiobutton Widget
When to use the Radiobutton Widget
Patterns
Methods
Options
39. The Scale Widget
When to use the Scale Widget
Patterns
Methods
Options
40. The Scrollbar Widget
When to use the Scrollbar Widget
Patterns
Methods
Options
41. The StringVar Class
When to use the StringVar Class
Patterns
Methods
42. The Text Widget
When to use the Text Widget
Concepts
Patterns
Methods
Options
43. The Toplevel Widget
When to use the Toplevel Widget
Methods
Options
44. Basic Widget Methods
Configuration
Event processing
Event callbacks
Alarm handlers and other non-event callbacks
Window management
Window Related Information
Miscellaneous
Tkinter Interface Methods
Option Database
45. Toplevel Window Methods
Visibility Methods
Style Methods
Window Geometry Methods
Icon Methods
Property Access Methods
Index

Next

'파이썬 프로그래밍' 카테고리의 다른 글

Tkinter GUI 프로그래밍  (0) 2011.04.24
Tkinter로 생각하기  (0) 2011.04.24
TKINTER 요약  (0) 2011.04.24
UltraEdit Python 설정  (0) 2009.06.28
파이썬 데몬 만들기  (0) 2009.04.07

[원문] http://coreapython.hosting.paran.com/tutor/tutgui.htm

Tkinter GUI 프로그래밍

무엇을 다룰 것인가?
  • 기본 GUI 구축 원리
  • 기본 창부품
  • 간단한 Tkinter 프로그램 구조
  • GUI 그리고 OOP, 그 환상적인 궁합
  • Tkinter의 대안 wxPython

이번 주제에서는 일반적인 관점에서 어떻게 GUI 프로그램이 조립되는지 그리고 어떻게 파이썬 고유의 GUI 툴킷인 Tkinter로 GUI를 구축할 수 있는지 살펴보겠습니다. 이 장의 의도는 완벽한 Tkinter 참조서나 완벽한 자습서에 있지 않습니다. 이미 아주 좋은 자세한 자습서가 파이썬 웹 사이트에 링크되어 있습니다. 이런 자습서가 오히려 여러분을 기본적인 GUI 프로그래밍으로 이끌어 줄 것입니다. 기본적인 GUI 구성요소를 소개해주고 어떻게 사용하는지 가르쳐 드리겠습니다. 어떻게 객체 지향 프로그래밍이 GUI 어플리케이션을 조직하는데 도움을 줄 수 있는지 살펴보겠습니다.

GUI 원리

사실 여기에서 프로그래밍에 관해서 새로 배울 것은 아무것도 없습니다. GUI 프로그래밍은 정확하게 다른 모든 종류의 프로그래밍과 같습니다. 연속열과 회돌이 그리고 분기와 모듈을 예전처럼 똑 같이 사용할 수 있습니다. 차이점이라면 GUI 프로그래밍에서는 보통 툴킷(Toolkit)을 써야 하고 그 툴킷 판매상이 설계한 프로그램 설계의 패턴을 따라야 한다는 것입니다. 새로운 툴킷마다 자신만의 API와 디자인 규칙이 있으며 프로그래머는 이런 것들을 배울 필요가 있습니다. 이 때문에 대부분의 프로그래머는 여러 언어에서 사용가능한 몇 가지 툴킷만으로 표준화를 시도합니다 - 새로운 툴킷을 배우는 일은 새로운 프로그래밍 언어를 배우는 것보다 훨씬 더 어려운 경향이 있습니다!

대부분의 윈도우즈 프로그래밍 언어는 툴킷이 따라옵니다 (보통 아주 원시적인 툴킷이 얇은 껍질에 둘러싸여 윈도우즈 시스템 자체에 내장되어 들어가 있습니다). Visual Basic과 Delphi(Kylix) 그리고 Visual C++/.NET이 이런 예입니다.

자바는 자신만의 그래픽 툴킷을 가진다는 점에서 다릅니다. 이를 Swing이라고 부르는데 자바가 실행되는 곳이면 어디서든 실행됩니다 - 거의 모든 플랫폼에서 실행됩니다!

어느 운영 체제에나 사용할 수 있는 툴킷도 있습니다 (유닉스, 맥, 윈도우즈...). 이런 것들은 보통 포장자가 있어서 여러 언어에서 사용할 수 있습니다. 어떤 것은 상업적이지만 대부분은 무료입니다. 그 예로는 GTK와 Qt 그리고 Tk가 있습니다.

모두 웹 사이트가 있습니다. 몇 가지 예를 살펴봅시다:

  • wxPython은 C++로 작성된 wxWidgets 툴킷의 파이썬 버전이다.
  • PyQt는 Qt 툴킷으로서 대부분의 언어에 "바인딩"이 있다.
  • pyGTK는 GTK+ 툴킷(Gimp 개발의 부산물)으로서 GTK+는 리눅스 공동체에서 널리 사용되는 오픈소스 프로젝트이다.

Qt와 GTK는 대부분의 리눅스 어플리케이션에서 사용되며 상업적이 아니면 무료입니다 (즉, 이익을 목적으로 프로그램을 팔지 말아야 합니다). Qt는 상업적 목적으로 사용하고 싶다면 상업적 라이센스도 제공할 수 있습니다. GTk는 라이센스가 Gnu GPL이어서 특별한 조건이 있습니다.

표준 파이썬 그래픽 툴킷은 (함께 따라오는) Tkinter로서 Tk에 기반하며 좀 오래된 다중-OS 툴킷입니다. 이 툴킷을 아주 자세하게 살펴보겠습니다. Tk는 파이썬뿐만 아니라 Tcl과 Haskell 그리고 Perl에서도 사용가능합니다.

Tk의 원리는 다른 툴킷과 약간 다릅니다. 그래서 또다른 인기있는 파이썬(그리고 C/C++)용 GUI 툴킷을 아주 간략하게 살펴보고 마치겠습니다. 이 툴킷이 접근법이 더 전통적입니다. 그러나 먼저 일반적인 원리를 알아봅시다:

이미 여러 번 언급했듯이 GUI 어플리케이션은 거의 언제나 본성상 사건 주도적입니다. 무슨 뜻인지 기억이 나지 않는다면 다시 돌아가 사건 주도적 프로그래밍 주제를 복습하세요.

이미 여러분은 사용자로서 GUI에 익숙하리라고 가정하겠습니다. 그리고 프로그래머의 관점에서 어떻게 GUI 프로그램이 작동하는지에 초점을 두겠습니다. 여러 개의 창과 MDI 인터페이스 등등이 있는 거대하고 복잡한 GUI를 작성하는 법에는 자세하게 들어가지 않습니다. 약간의 라벨과 버튼 그리고 텍스트 상자와 메시지 상자가 있는 창 한개짜리 어플리케이션을 만들기 위해 필요한 기본지식에 주력하겠습니다.

무엇보다도 어휘를 점검할 필요가 있습니다. GUI 프로그래밍은 독자적인 프로그래밍 용어 집합이 있습니다. 가장 자주 사용되는 용어를 아래 표에 기술하겠습니다:

용어설명
창(Window)어플리케이션이 통제하는 화면의 한 구역. 창은 보통 사각형이지만 어떤 GUI 환경에서는 다른 모양도 허용한다. 창은 다른 창을 포함할 수 있으며 보통 GUI 콘트롤 하나가 창 그 자체로 취급된다.
콘트롤(Control)콘트롤은 어플리케이션을 제어하는데 사용되는 GUI 객체이다. 콘트롤은 특성이 있고 보통 사건을 만들어낸다. 보통 콘트롤은 어플리케이션 수준의 객체에 상응한다. 그리고 사건은 상응하는 객체의 메쏘드에 묶인다. 그런 식으로 사건이 일어나면 객체는 자신의 메쏘드중 하나를 실행한다. GUI 환경은 보통 사건을 메쏘드에 묶는 메커니즘을 제공한다.
창부품(Widget)종종 눈에 보이는 콘트롤로 제한된다. (타이머 같은) 어떤 콘트롤은 주어진 창에 연관될 수 있지만 눈에 보이지 않는다. 창부품은 콘트롤의 하위 세트로서 눈에 보이며 사용자나 프로그래머가 조작할 수 있다. 앞으로 다룰 창부품은 아래와 같다:
  • 프레임(Frame)
  • 라벨(Label)
  • 버튼(Button)
  • 텍스트 엔트리(Text Entry)
  • 메시지 박스(Message boxes)

이 주제에서는 다루지 않지만 다른 곳에서는 사용되는 창부품은 아래와 같다:

  • 텍스트 박스(Text box)
  • 라디오 버튼(Radio Button)
마지막으로, 전혀 다루지 않는 창부품은 아래와 같다:
  • 캔버스(Canvas) - 그림 그리기 용
  • 체크 버튼(Check button) - 다중 선택용
  • 이미지(Image) - BMP, GIF, JPEG 그리고 PNG 이미지 보여주기
  • 리스트박스(Listbox) - 리스트 처리용!
  • 메뉴버튼(Menu/MenuButton) - 메뉴 만들기
  • 스케일/스크롤바(Scale/Scrollbar) - 위치 지정
프레임(Frame)다른 창부품을 함께 무리짓는데 사용되는 창부품. 종종 한 프레임이 창 전체를 나타내는데 사용되고 그 안에 또 프레임이 들어간다.
조감(Layout)콘트롤은 특정 형태의 조감에 맞추어 프레임 안에 배치된다. 레이아웃은 여러 방식으로 지정이 가능하다. 화면 좌표를 이용하여 픽셀 단위로 지정하거나, 다른 구성요소에 대하여 상대 좌표(전후좌우 등등)를 이용하거나, 격자나 테이블 정렬을 이용하기도 한다. 좌표 시스템은 이해하기 쉽지만 창의 크기가 바뀌거나 하면 관리하기가 어렵다. 초보자가 좌표 기반의 조감으로 작업하려면 크기가-바뀌지 않는 창을 사용하기를 권장한다.
자손(Child)GUI 어플리케이션은 계통적인 창부품/콘트롤으로 구성되는 경향이 있다. 어플리케이션 창을 담당하는 최상위 수준의 프레임은 하위프레임을 담고 순서대로 이 하위프레임 안에 또 프레임이나 콘트롤이 담긴다. 이런 콘트롤들은 눈에 보이게 트리 구조로 각 콘트롤마다 하나의 부모가 여러 자손을 가진다. 실제로 이런 구조는 위젯에 의하여 명시적으로 저장되는 것이 보통이다. 그래서 프로그래머, 즉 구이 환경 그 자체는 종종 한 콘트롤과 그의 모든 자손에게 공통적인 조치를 수행하여 통제할 수 있다.

그릇 트리

GUI 프로그래밍을 이해하는 가장 중요한 원칙은 그릇 계통도의 개념을 이해하는 것입니다. 다시 말해 위젯은 트리에 담깁니다. 트리는 최상위 수준의 위젯이 전체 인터페이스를 제어하는 구조입니다. 다양한 자손 위젯이 있습니다. 차례로 각 자손은 자신만의 자손을 가질 수도 있습니다. 사건은 자손 위젯에 도착하고 자손 위젯이 처리할 수 없으면 그 사건은 그의 부모에게 건네지고 등등 최상위 수준으로 건네집니다. 비슷하게 위젯을 그리라는 명령어가 주어지면 그 명령어는 계속해서 그의 자손까지 내려가고, 그리하여 최상위 위젯에 건네어진 그리기 명령어는 전체 어플리케이션을 다시 그릴 것입니다. 반면에 버튼에 건네어진 그리기 명령어는 버튼 자체만 다시 그릴 것입니다.

사건은 트리를 타고 올라가고 명령은 트리를 타고 내려 온다는 이런 개념은 어떻게 GUI가 작동하는지 프로그래머 수준에서 이해하는 열쇠입니다. 위젯을 만들 때 왜 언제나 부모 위젯을 지정할 필요가 있는지 이해할 수 있습니다. 그래야 자신이 그릇 트리에서 어디에 위치하는지 알기 때문입니다. 이 주제에서 만들 간단한 어플리케이션을 위한 그릇 트리를 다음과 같이 보여드릴 수 있습니다:

GUI Containment tree

이를 보면 최상위 수준의 위젯에 Frame이 하나 있어서 가장 바깥쪽 창의 테두리를 표현합니다. 차례로 그 안에 프레임이 두 개 더 있습니다. 한 프레임에는 Text Entry 위젯이 있고 또 하나에는 어플리케이션을 제어하기 위해 두 개의 Button이 사용됩니다. 나중에 GUI를 구축하게 되면 이 다이어 그램을 다시 참조하겠습니다.

일반적인 위젯 여행

이 섹션에서는 파이썬 상호대화 모드를 사용하여 간단한 창과 위젯을 만들어 보겠습니다. IDLE 그 자체가 Tkinter 어플리케이션이므로 IDLE 안에서 Tkinter 어플리케이션을 실행하는 것은 신뢰할 수 없습니다. 물론 IDLE을 편집기로 이용하여 파일을 만들 수 있지만 반드시 실행은 OS 명령어 프롬프트에서 해야 합니다. 파이썬윈(Pythonwin) 사용자는 Tkinter 어플리케이션을 실행할 수 있습니다. 왜냐하면 파이썬윈은 윈도우즈 전용 GUI 툴킷인 MFC를 사용하여 구축되었기 때문입니다. 그렇지만 파이썬윈조차도 Tkinter 어플리케이션의 행위를 예측할 수 없습니다. 결과적으로 운영체제에서 파이썬 프롬프트를 사용하겠습니다.

>>> from Tkinter import *

Tkinter 프로그램이라면 제일 먼저 요구되는 조건입니다 - 위젯의 이름을 반입합니다. 물론 모듈 형태로 반입할 수 있지만 Tkinter를 컴포넌트 이름마다 앞에 타자하는 일은 얼마 못가 귀찮아집니다.

>>> top = Tk()

이는 위젯 계통도에서 최상위 수준 위젯을 만듭니다. 다른 모든 위젯은 이의 자손이 됩니다. 빈 창이 새로 나타났습니다. 빈 타이틀 바에 Tk 로고와 표준 콘트롤 버튼 집합이 있습니다 (최소화, 최대화 등등). 이제 어플리케이션을 구축하면서 컴포넌트를 이 창에 추가하겠습니다.

>>> dir(top)

['_tclCommands', 'children', 'master', 'tk']

dir 함수는 인자에 무슨 이름이 들어 있는지 보여줍니다. 이 함수는 모듈에도 사용할 수 있지만 이 경우 Tk 클래스의 실체인 top 객체의 내부를 알아보는데 사용했습니다. top의 속성중에서 특히 children 속성과 master 속성에 주목하세요. 이 속성들은 위젯 그릇 트리를 가리키는 링크입니다. _tclCommands 속성에 주목하세요. 이 속성은 기억나시겠지만 Tkinter가 Tcl/Tk 위에 구축되었기 때문입니다.

>>> F = Frame(top)

Frame 위젯을 만듭니다. 차례로 그 안에 자손 콘트롤/위젯이 담깁니다. Frametop을 자신의 첫 (이 경우에는 유일한) 매개변수로 지정합니다. 그리하여 Ftop의 자손 위젯이 될 것이라고 알립니다.

>>> F.pack()

Tk 창이 이제 추가된 Frame 위젯의 크기로 줄어 들었습니다 - 현재 비어 있으므로 창은 이제 아주 작습니다! pack() 메쏘드는 레이아웃 관리자를 요청합니다. 이른바 패커 관리자는 간단한 레이아웃에 사용하기 쉽습니다. 그러나 레이아웃이 복잡해지면 약간 어색해 집니다. 지금 당장은 사용하기 쉽기 때문에 패커 관리자에 주력하겠습니다. 어플리케이션에서 위젯은 꾸려 넣지 않으면 (또는 다른 레이아웃 관리자를 이용하지 않으면) 눈에 보이지 않습니다.

>>> lHello = Label(F, text="Hello world")

터미널에서 상호대화적으로 Tkinter를 연습할 때 문자열이 깨져서 알아 볼수 없는 경우가 있다. 그 이유는 터미널 인코딩이 mbcs (euc-kr, cp949)인 반면에 Tkinter는 인코딩이 utf-8로서 서로 다르기 때문이다. 해결 방법은 다음과 같다.

  • 첫 째 유니코드로 넘겨준다: lHello = Label(F, text=unicode("Hello world",'mbcs'))
    터미널 인코딩은 알지만 GUI의 인코딩을 모를 경우 사용한다.
  • 둘 째 직접 변환해서 넘겨준다: lHello = Label(F, text="Hello world".encode('utf-8'))
    GUI의 인코딩은 알지만 터미널 인코딩을 모를 경우 사용한다.

파일에 저장하여 실행할 경우는 반드시 파일 첫 줄이나 두 번째 줄에 다음과 같이 삽입하고 utf-8로 저장한다. 이미 파일에 입력된 문자의 인코딩이 utf-8이므로 위와 같은 변환을 거칠 필요가 없다.

  • # -*- coding: utf-8 -*-

파일에 저장하여 실행하는 경우가 바람직하다. 결론적으로 유니코드는 출력에 목적이 있는 것이 아니라 내부처리에 목적이 있는 것이다. 앞의 방법은 파이썬 내부에서 처리가 가능하여 파이썬 3.0에서는 아예 unicode() 함수가 없다.

여기에서 Label 클래스의 새로운 객체 lHello를 만듭니다. 부모 위젯은 F이고 text 속성은 "Hello world"입니다. Tkinter 객체의 구성자는 매개변수가 많은 경향이 있으므로 (각각 기본 값이 있음) 보통 인자를 Tkinter 객체에 건네는 이름붙은 매개변수 테크닉을 사용합니다. 객체는 아직 꾸려 넣지 않았기 때문에 보이지 않습니다.

마지막 요점으로 이름짓기 관례의 사용에 주목하세요: 라벨에 대하여 Hello 이름 앞에 소문자 l을 두었습니다. 이를 보면 그의 목적이 생각 나기 때문입니다. 대부분의 이름짓기 관례처럼 이는 개인적인 취향이지만, 도움이 될 것이라 생각합니다.

>>> lHello.pack()

이제 볼 수 있습니다. 다음과 같이 보일 것입니다:

Window with Label

객체 구성자에게 매개변수를 건네면 라벨에 예를 들어 폰트와 색상같은 다른 특성을 지정할 수도 있습니다. Tkinter 위젯의 configure 메쏘드를 사용하면 상응하는 특성에 접근할 수 있습니다. 다음과 같이:

>>> lHello.configure(text="Goodbye")

메시지가 바뀌었습니다. 쉽군요, 그렇지 않습니까? configure 메쏘드는 여러 특성을 한 번에 바꾸고 싶을 때 특히 좋은 테크닉입니다. 왜냐하면 특성 모두를 인자로 건넬 수 있기 때문입니다. 그렇지만, 한 번에 하나의 특성만 바꾸고 싶다면, 위에 한 것 같이 그 객체를 사전처럼 취급할 수 있습니다. 다음과 같이:

>>> lHello['text'] = "Hello again"

이 편이 더 짧고 더 이해하기 쉽습니다.

라벨은 아주 무미건조한 위젯입니다. 읽기-전용 텍스트만 화면에 보여줄 수 있을 뿐입니다. 물론 색상과 폰트 그리고 크기는 지정할 수 있습니다. (실제로 간단한 그래픽을 보여주는데에도 사용할 수 있지만 여기에서는 그에 관하여 신경쓰지 않겠습니다).

또다른 객체 유형을 살펴보기 전에 할 일이 한 가지 더 있습니다. 그것은 창의 제목을 설정하는 일입니다. 최상위 수준 위젯인 top의 메쏘드를 사용합니다:

>>> F.master.title("Hello")

top을 직접 사용할 수도 있지만, 나중에 보시듯이 프레임의 마스터 특성을 통하여 접근하는 것이 일반적인 테크닉입니다.

>>> bQuit = Button(F, text="Quit", command=F.quit)

여기에서 새로운 버튼 위젯을 만듭니다. 버튼에는 "Quit" 라벨이 있고 F.quit 명령어와 연관되어 있습니다. 메쏘드 이름을 건넸음을 주목하세요. 메쏘드를 뒤에 괄호를 추가해서 호출하지 않습니다. 이는 파이썬 함수 객체를 건네야 한다는 뜻입니다. 여기에서처럼 Tkinter가 제공하는 내장 객체일 수도 있고 또는 직접 정의한 다른 함수도 될 수 있습니다. 함수나 메쏘드는 인자를 받지 않습니다. quit 메쏘드는 pack 메쏘드처럼 바탕 클래스에 정의되어 있으며 모든 Tkinter 위젯이 상속받습니다. 그러나 보통 어플리케이션의 최상위 수준 창에서 호출됩니다.

>>>bQuit.pack()

또 한 번 pack 메쏘드가 버튼을 보여줍니다.

>>>top.mainloop()

Tkinter 사건 회돌이를 시작합니다. 이제 >>> 프롬프트가 이제 사라졌습니다. 그를 보면 Tkinter가 이제 제어권을 가졌다는 사실을 알 수 있습니다. Quit 버튼을 누르면 프롬프트가 돌아옵니다. 이로서 command 옵션이 작동한다는 것이 증명되었습니다. 창이 닫힐 것이라고 예상하지 마세요. 파이썬 인터프리터는 여전히 실행되고 있으며 오직 주회돌이 기능만 끝냈을 뿐입니다. 파이썬이 종료하면 다양한 위젯이 파괴됩니다 - 실제 프로그램에서 이는 보통 주회돌이가 종료하면 즉시 파괴됩니다!

Pythonwin이나 IDLE에서 이것을 실행하면 약간 결과가 다를 수 있습니다. 그렇다면 지금까지의 명령어를 파이썬 스크립트 안으로 타자해 넣고 OS 명령어 프롬프트에서 실행하세요.

실제로 어쨌든 그를 시험해 볼 좋은 기회입니다. 무엇보다 그것이 바로 대부분의 Tkinter 프로그램이 실제로 작동하는 방식입니다. 지금까지 연구한 바와 같이 핵심 명령어들을 사용하세요:

#!/usr/bin/python# -*- coding: utf-8 -*-# 반드시 파일은 utf-8로 저장할 것 - 중요from Tkinter import *# 창 자체를 설정한다top = Tk()F = Frame(top)F.pack()# 위젯을 추가한다lHello = Label(F, text="안녕하세요")lHello.pack()bQuit = Button(F, text="그만하기", command=F.quit)bQuit.pack()# 회돌이를 실행한다top.mainloop()

top.mainloop 메쏘드를 호출하면 Tkinter 사건 회돌이가 시작되어 사건을 만들어 냅니다. 이 경우 잡고 싶은 유일한 사건은 버튼 눌림 사건이며 이 사건은 F.quit 메쏘드에 연결되어 있습니다. 차례로 F.quit은 어플리케이션을 종료하고 이 번에는 파이썬도 종료하므로 창도 닫힙니다. 시험해 보세요. 다음과 같이 보일 것입니다:

Label with Button

레이아웃 연구

고지: 이제부터는 >>> 프롬프트에서 명령어로 보여드리기 보다 파이썬 스크립트 파일로 예제를 보여드리겠습니다.

이 섹션에서는 창 안에서 Tkinter가 어떻게 위젯의 위치를 정하는지 살펴보고 싶습니다. 이미 Frame과 Label 그리고 Button 위젯을 보셨습니다. 그 모든 것들이 이 섹션에서 필요합니다. 앞 예제에서 위젯의 pack 메쏘드를 사용하여 부모 위젯 안에서 그의 위치를 정합니다. 기술적으로 우리가 하는 일은 Tk의 꾸림자인 레이아웃 관리자를 요청합니다. 조감 관리자의 역할은 프로그래머가 제공하는 힌트에 기반하여 게다가 사용자가 제어하는 창의 크기와 같은 제약에 의거하여 위젯에 대하여 최상의 레이아웃을 결정하는 것입니다. 어떤 레이아웃 관리자는 창 안에서 정확한 위치를 사용합니다. 보통은 픽셀 단위로 지정되며 비주얼 베이직 같은 윈도우즈 환경에서는 흔히 이렇게 지정합니다. Tkinter에는 Placer 레이아웃 관리자가 포함되어 있으며 place 메쏘드를 통하여 위치를 지정할 수 있지만 이 자습서에서는 다루지 않겠습니다. 왜냐하면 더 지능적인 관리자가 보통 더 좋은 선택이기 때문입니다. 창이 크기가 변할 때 무슨 일이 일어나는지 프로그래머가 신경쓰지 않는 편이 더 좋으니까요.

Tkinter의 가장 단순한 레이아웃 관리자는 그 동안 사용해 온 꾸림자(packer)입니다. 패커는 기본으로 그냥 위젯을 차곡차곡 쌓아 올립니다. 보통 위젯이라면 별로 원하는 바가 아니겠지만 프레임으로 어플리케이션을 구축하면 프레임을 하나하나 쌓아 올리는 것이 상당히 합리적인 접근법입니다. 그러면 패커 또는 기타 레이아웃 관리자를 이용하여 각 프레임 안에 적절하게 다른 위젯들을 넣을 수 있습니다. 이 예제가 작동하는 모습은 사례 연구 주제에서 볼 수 있습니다.

그렇지만, 간단한 패커조차도 선택사항을 여러가지 제공합니다. 예를 들어 side 인자를 제공하면 위젯들을 수직 말고 수평으로 정렬할 수 있습니다. 다음과 같이:

lHello.pack(side="left")bQuit.pack(side="left")

위젯들이 왼쪽으로 가고 그리하여 첫 위젯 (라벨)은 외쪽 벽에 바짝 붙어 나타나고, 다음에 다음 위젯 (버튼)이 옵니다. 위의 예제에서 줄을 수정하면 다음과 같이 보일 것입니다:

Left packed widgets

"left""right"로 바꾸면 라벨은 오른쪽에 바짝 붙어 나타나고 버튼이 그 왼쪽에 나타납니다. 다음과 같이:

Right packed widgets

위젯이 한데 포개어져 있기 때문에 별로 멋지지 않습니다. 패커는 또 이에 대처하기 위한 매개변수를 제공합니다. 가장 쉽게 사용하는 방법은 Padding이며 수평 패딩(padx)과 수직 패딩(pady)으로 지정됩니다. 이 값들은 픽셀단위로 지정됩니다. 예제에 수평 패딩을 추가해 봅시다:

lHello.pack(side="left", padx=10)bQuit.pack(side='left', padx=10)

다음과 같이 보일 것입니다:

Horizontal padding

창의 크기를 변경하면 위젯이 또다른 위젯에 상대적으로 위치를 유지하지만 창의 중앙에 머무는 것을 볼 수 있습니다. 위젯들을 왼쪽에 꾸려 넣었는데 왜 그런가? 그 대답은 위젯들을 프레임 안에 꾸려 넣었지만 그 프레임은 변이 지정되지 않은 채로 꾸려 넣었졌기 때문입니다. 그래서 패커의 기본 값이 상단 중앙에 위치합니다. 위젯을 창의 올바른 변에 머물게 하고 싶으면 프레임도 적절한 변에 꾸려 넣을 필요가 있습니다:

F.pack(side='left')

창의 수직 크기를 변경해도 위젯들이 중앙에 머무는 것을 주목하세요 - 역시 그것이 패커의 기본 행위입니다.

padx와 pady를 직접 가지고 놀아 보세요. 값을 다르게 주고 조합해 주면서 등등 그 효과를 알아 보세요. 여러분에게 과제로 남깁니다. 패커를 사용하면 sidepadx/pady가 위젯의 위치에 상당히 유연함을 알 수 있습니다. 여러가지 옵션이 있습니다. 각 옵션은 미묘하게 모습을 제어합니다. 자세한 것은 Tkinter 페이지를 참조하세요.

두 가지 레이아웃 관리자가 있습니다. 하나는 grid이고 다른 하나는 placer라고 부릅니다. pack() 대신에 grid 관리자를 사용하려면 grid()를 호출하고 placer 관리자를 사용하려면 place()를 호출하면 됩니다. 각자 자신만의 옵션 집합이 있으며 여기에서는 패커만 다룰 생각이므로 자세한 것은 Tkinter 자습서와 참조서를 보셔야 하겠습니다. 핵심 요점은 grid가 (놀랍게도!) 구성요소들을 창 안에서 격자 안에 정렬한다는 것입니다 - 이는 예를 들어 텍스트 입력 상자가 줄줄이 늘어선 대화상자에 유용할 수 있습니다. placer 사용자는 픽셀 단위로 좌표를 고정하거나 창 안에서 상대적인 좌표를 사용합니다. 후자에서 컴포넌트는 창과 더불어 크기가 바뀌는데 언제나 수직 공간의 75%를 차지합니다. 이는 복잡한 창 디자인에 유용할 수 있습니다. 그러나 미리-심사숙고해서 계획할 필요가 있습니다 - 모눈 종이와 연필 그리고 지우게를 강력하게 추천합니다!

프레임과 패커로 겉모습 통제하기

프레임 위젯은 실제로 몇 가지 유용한 특성이 있습니다. 무엇보다 논리적 프레임 안에 컴포넌트를 모아 두면 아주 좋습니다. 그러나 어떤 경우는 볼 수도 있었으면 하는 경우가 있습니다. 프레임은 라디오 버튼이나 체크 박스 같은 무리지어진 콘트롤에 특히 유용합니다. 프레임은 다른 많은 Tk 위젯처럼 relief 특성을 제공하여 이 문제를 해결합니다. Relief 특성은 여러 값을 가질 수 있습니다: sunken, raised, groove, ridge 또는 flat이 그것입니다. sunken 값을 대화 박스에 사용해 봅시다. 그냥 Frame 생성줄을 다음고 같이 바꾸면:

 F = Frame(top, relief="sunken", border=1) 

고지 1: 테두리도 제공할 필요가 있습니다. 주지 않으면 Frame은 움푹(sunken) 들어가지만 테두리는 보이지 않습니다 - 아무 차이도 보이지 않습니다!

고지 2: 테두리 두께에 인용부호를 붙이지 않았음을 주목하세요. 옵션 주위에 언제 인용부호를 붙일지 언제 생략할지 아는 것은 Tk 프로그래밍의 혼란스러운 측면 중의 하나입니다. 일반적으로 숫자나 한 개짜리 문자라면 인용부호를 생략할 수 있습니다. 숫자와 문자열이 섞여 있으면 인용부호가 필요합니다. 대소문자의 사용도 마찬가지입니다. 불행하게도 쉬운 해결책은 없습니다. 시행 착오를 통해 배워야 합니다. 파이썬은 종종 유효한 옵션 목록을 에러 메지시에 보여줍니다!

프레임은 창을 채우지 않음을 주목하세요. 당연히 fill이라고 부르는 패커 옵션으로 고칠 수 있습니다. 프레임을 꾸릴 때 그리하여 다음과 같이 합니다:

F.pack(fill="x")

이는 수평으로 채웁니다. 프레임이 전체 창을 채우도록 하고 싶다면 또 fill='y'를 사용하면 됩니다. 이는 아주 흔히 요구되므로 특별한 채우기 옵션으로 BOTH가 있으므로 다음과 같이 타자할 수 있습니다:

F.pack(fill="both")

스크립트를 실행한 최종 결과는 이제 다음과 같이 보입니다:

Sunken Frame

위젯 더 추가하기

이제 텍스트 Entry 위젯을 살펴봅시다. 이는 친숙한 한 줄짜리 텍스트 입력 박스입니다. 더 섬세한 Text 위젯의 메쏘드와 많은 부분을 공유합니다. 텍스트 위젯은 여기에서 다루지 않습니다. 엔트리 위젯을 사용하여 단순히 사용자가 타자하는 것들을 나포하고 요구에 따라 그 텍스트를 지우겠습니다.

다시 "Hello World" 프로그램으로 돌아가 텍스트 엔트리 위젯을 프레임 안에 추가하고 그 안에 타자한 텍스트를 지울 수 있는 버튼을 추가하겠습니다. 이는 엔트리 위젯을 만들고 사용하는 법 뿐만 아니라 사건 처리 함수를 정의하고 그것을 위젯에 연결하는 법도 보여줄 것입니다.

#!/usr/bin/python# -*- coding: utf-8 -*-# 반드시 파일은 utf-8로 저장할 것 - 중요from Tkinter import *# 먼저 사건 처리자를 만든다.def evClear():  eHello.delete(0,END)# 최상위 수준 창/프레임을 만든다.top = Tk()F = Frame(top)F.pack(expand="true")# 이제 텍스트 엔트리가 있는 프레임을 만든다.fEntry = Frame(F, border=1)eHello = Entry(fEntry)fEntry.pack(side="top", expand="true")eHello.pack(side="left", expand="true")# 마지막으로 버튼이 있는 프레임을 만든다. # 강조하기 위해 이를 움푹 들여 넣는다.fButtons = Frame(F, relief="sunken", border=1)bClear = Button(fButtons, text="텍스트 지우기", command=evClear)bClear.pack(side="left", padx=5, pady=2)bQuit = Button(fButtons, text="그만하기", command=F.quit)bQuit.pack(side="left", padx=5, pady=2)fButtons.pack(side="top", expand="true")# 이제 사건 회돌이를 실행한다.F.mainloop()

bClear 버튼에 command 인자로 사건 처리자의 이름(evClear)을 괄호없이 한 번 더 건넸습니다. 이름짓기 관례의 사용에 주목하세요. evXXX는 사건 처리자를 상응하는 위젯에 연결합니다.

프로그램을 실행하면 결과는 다음과 같습니다:

Entry and button controls

텍스트 엔트리 박스 안에 무엇인가 타자하고 "텍스트 지우기" 버튼을 치면 내용이 다시 삭제됩니다.

사건 묶기 - 위젯을 코드에 묶는 방법

지금까지 파이썬 함수를 GUI 사건에 연관시키기 위해 버튼의 명령어 특성을 사용하였습니다. 가끔 더 명시적으로 제어하고 싶은 경우가 있습니다. 예를 들어 특정 키 조합을 잡고 싶으면 bind 함수를 사용하여 사건과 파이썬 함수를 함께 명시적으로 함께 묶거나 (엮습니다).

이제 핫 키를 정의하겠습니다 - CTRL-c - 위의 예제에 있는 텍스트를 지우기 위해서 말입니다. 그렇게 하려면 CTRL-C 키 조합을 Clear 버튼과 같은 사건 처리자에 묶을 필요가 있습니다. 불행하게도 예상치 못한 단점이 있습니다. 명령어 옵션을 사용할 때 지정된 함수는 인자를 받지 못합니다. bind 함수를 사용하여 같은 일을 하려면 묶이 함수는 반드시 인자를 하나 받아야 합니다. 이 때문에 evClear를 호출하는 매개변수를 하나 가진 새로운 함수를 만들 필요가 있습니다. 다음을 evClear 정의 뒤에 추가하세요:

def evHotKey(event):    evClear()

그리고 다음 줄을 Entry 위젯의 정의 뒤에 추가하세요:

eHello.bind("<Control-c>",evHotKey) # 키 정의는 대소문자에 민감하다 

프로그램을 다시 실행하면 이제 버튼을 누르거나 Ctrl-c를 타자해서 텍스트를 지울 수 있습니다. bind를 사용하면 마우스 클릭 같은 것들을 잡을 수 있습니다. 즉 초점(Focus)을 잡거나 놓을 수 있으며 심지어 창을 보이게 만들 수도 있습니다. 이에 관한 더 자세한 정보는 Tkinter 문서를 참조하세요. 보통 가장 어려운 부분은 사건 기술의 형식을 알아내는 것입니다!

짧은 메시지

MessageBox를 이용하여 사용자에게 짧은 메시지를 보고할 수 있습니다. 이는 Tk에서 아주 쉽습니다. 아래에 보여주는 바와 같이 tkMessageBox 모듈을 기능을 사용하여 완성됩니다:

import tkMessageBoxtkMessageBox.showinfo("Window Text", "A short message") 

에러도 있습니다. 다양한 showXXX 함수를 통하여 경고, Yes/No 그리고 OK/Cancel 상자로 얻을 수 있습니다. 에러는 다양한 아이콘과 버튼으로 구별됩니다. 뒤의 두 상자는 showXXX 대신 askXXX를 사용하며 어느 버튼을 사용자가 눌렀는지 값을 돌려줍니다. 다음과 같이:

res = tMessageBox.askokcancel("Which?", "Ready to stop?")print res

다음은 Tkinter 메시지 상자들입니다:

Info box Error box Yes/No box

어플리케이션을 객체로 감싸기

GUI를 프로그래밍할 때 전체 어플리케이션을 클래스로 포장하는 것이 일반적입니다. 이는 질문을 불러 일으킵니다. 어떻게 Tkinter 어플리케이션의 위젯을 이 클래스 구조에 맞추는가? 두 가지 선택이 있습니다. 어플리케이션 자체를 Tkinter Frame의 하위 클래스로 만들거나 멤버 필드에 최상위 창을 가리키는 참조점을 저장합니다. 후자의 접근법이 다른 툴킷에서 가장 많이 사용됩니다. 그래서 여기에서 이 접근법을 사용하겠습니다. 전자의 접근법이 작동하는 모습을 보고 싶으면 뒤로 돌아가 사건 주도적 프로그래밍 주제에 있는 예제를 살펴보세요. (그 예제도 놀랍도록 다재다능한 Tkinter Text 위젯의 기본 사용법을 보여줍니다.)

위의 예제를 OO 구조로 변환하겠습니다. 엔트리 필드와 Clear 버튼 그리고 Quit 버튼을 이용하겠습니다. 먼저 Application 클래스를 만들고 구성자 안에서 GUI 시각적 부품들을 조립합니다.

결과로 나온 Frame을 self.mainWindow에 할당하고, 그리하여 클래스의 다른 메쏘드들은 최상위 수준의 Frame에 접근할 수 있습니다. 접근할 필요가 있는 (Entry 필드 같은) 다른 위젯들도 마찬가지로 Frame의 멤버 변수에 할당됩니다. 이 테크닉을 이용하면 사건 처리자는 어플리케이션 클래스의 메소드가 되며 모두 (물론 이경우는 아무것도 없지만) self 참조를 통하여 어플리케이션의 다른 데이터 멤버에 접근할 수 있습니다. 이렇게 하면 GUI와 밑에 깔린 어플리케이션 객체가 흠없이 통합됩니다:

#!/usr/bin/python# -*- coding: utf-8 -*-# 반드시 파일은 utf-8로 저장할 것 - 중요from Tkinter import *     class ClearApp:   def __init__(self, parent=0):      self.mainWindow = Frame(parent)      # 엔트리 위젯을 만든다.      self.entry = Entry(self.mainWindow)      self.entry.insert(0,"파이썬 만세!")      self.entry.pack(fill=X)            # 이제 버튼 2 개를 추가하고, grooved 효과를 준다.      fButtons = Frame(self.mainWindow, border=2, relief="groove")      bClear = Button(fButtons, text="지우기",                       width=8, height=1, command=self.clearText)      bQuit = Button(fButtons, text="이제 그만",                       width=8, height=1, command=self.mainWindow.quit)      bClear.pack(side="left", padx=15, pady=1)      bQuit.pack(side="right", padx=15, pady=1)      fButtons.pack(fill=X)      self.mainWindow.pack()      # 제목을 설정한다.      self.mainWindow.master.title("파이썬")         def clearText(self):      self.entry.delete(0,END)      app = ClearApp()app.mainWindow.mainloop()

다음은 그 결과입니다:

OOP version

결과적으로 모습이 앞의 구현과 놀랍도록 비슷합니다. 물론 아래 프레임을 조작해서 깔끔하게 홈이 파이도록 마감했고 버튼에 너비를 주어서 아래의 wxPython 예제와 더 비슷하게 보이게 만들었습니다.

물론 메인 어플리케이션만 객체로 쌀 수 있는 것은 아닙니다. Frame을 기반으로 클래스를 만들어 그 안에 버튼의 표준 집합을 담고 그 클래스를 대화상자 창을 구축하는데 재사용할 수 있습니다. 심지어 전체 대화상자를 만들어서 그를 여러 프로젝트에 사용할 수도 있습니다. 또는 표준 위젯을 상속받아 그 능력을 확장할 수 있습니다 - 아마도 상태에 따라 색상을 바꾸는 버튼을 만들수 있을 것입니다. 이 방식대로 파이썬 메가 위젯(PMW)이 구축되었습니다. PMW는 Tkinter 확장으로서 내려 받을 수 있습니다.

대안 - wxPython

다른 GUI 툴킷이 많지만 가장 인기 있는 툴킷은 wxPython입니다. 이 툴킷은 이번에는 C++ 툴킷인 wxWidgets를 포장한 것입니다. wxPython는 일반적으로 GUI 툴킷중에서 Tkinter보다 훨씬 더 모범적입니다. Tk보다 "바로 사용할 수 있는" 표준적인 기능을 더 많이 제공합니다 - 예를 들어, 풍선 도움말과 상태 바 등등은 Tkinter에서 손수 처리해 주어야 합니다. wxPython을 사용하여 위의 간단한 "Hello World" 예제를 다시 만들어 보겠습니다.

wxPython은 자세하게 들어가지 않겠습니다. 어떻게 wxPython이 작동하는지 더 알고 싶다면 wxPython 웹사이트에서 패키지를 내려 받으세요.

일반적 용어로 wxPython에 작업틀이 정의되어 있기 때문에 창을 만들고 그 창을 콘트롤로 채우며 그리고 그 콘트롤에 메쏘드를 묶을 수 있습니다. 완전히 객체 지향적이므로 함수가 아니라 메쏘드를 사용해야 합니다. 예제는 다음과 같이 보입니다:

import wx# --- 맞춤 프레임을 정의한다. 이는 메인 창이 된다 ---class HelloFrame(wx.Frame):   def __init__(self, parent, id, title, pos, size):        wx.Frame.__init__(self, parent, id, title, pos, size)        # 올바른 배경을 얻으려면 패널이 필요하다.        panel = wx.Panel(self)        # 이제 텍스트와 버튼 위젯을 만든다.        self.tHello = wx.TextCtrl(panel, -1, "Hello world", pos=(3,3), size=(185,22))        bClear = wx.Button(panel, -1, "Clear", pos=(15, 32))        self.Bind(wx.EVT_BUTTON, self.OnClear, bClear)        bQuit = wx.Button(panel, -1, "Quit", pos=(100, 32))        self.Bind(wx.EVT_BUTTON, self.OnQuit, bQuit)       # 다음은 사건 처리자이다.   def OnClear(self, event):       self.tHello.Clear()          def OnQuit(self, event):       self.Destroy()# --- 어플리케이션을 정의한다 ---# 모든 wxPython 프로그램은 어플리케이션 클래스를# 반드시 wx.App으로부터 상속받아 정의해야 한다.class HelloApp(wx.App):   def OnInit(self):       frame = HelloFrame(None, -1, "Hello", (200,50), (200,90) )       frame.Show(True)       self.SetTopWindow(frame)       return True# 실체를 만들고 사건 회돌이를 시작한다HelloApp().MainLoop()

다음과 같이 보입니다:

wxPython Hello program

작업틀에 의하여 호출되는 메쏘드에 사용된 이름짓기 관례에 주목하세요 - OnXXXX. 또 사건을 위젯에 묶는데 사용된 EVT_XXX 상수에 주목하세요 - 이렇게 이름 지어진 상수들이 한 가득 있습니다. wxPython은 방대한 위젯이 있습니다. Tkinter보다 훨씬 더 많이 있으며 아주 섬세한 GUI를 구축할 수 있습니다. 불행하게도 사용되는 좌표가 위치 배정 체계에 기반하는데 이는 잠시만 지나면 아주 귀찮아집니다. Tkinter의 패커와 아주 비슷한 체계를 사용하는 것도 가능하지만 문서화가 잘 되어 있지 않습니다.

말이 나온 김에 흥미로운 사실을 지적하고 싶습니다. 이 예제와 위의 아주 비슷한 Tkinter 예제가 모두 실행 코드의 줄 개수가 대략 같습니다 - Tkinter: 19줄, wxPython: 21줄.

결론적으로 텍스트 기반의 도구에 재빠르게 GUI 전방 모듈을 만들고 싶다면 Tkinter가 최소의 노력으로 만족시켜 줄 수 있습니다. 완전히 특징을 갖춘 크로스 플랫폼 GUI 어플리케이션을 구축하고 싶다면 wxPython을 더 자세하게 살펴보세요.

다른 툴킷에는 MFC와 .NET이 있습니다. 물론 좀 허술한 curses도 있습니다. 이는 일종의 텍스트 기반 GUI입니다! Tkinter에서 배운 많은 지식이 이런 모든 툴킷에 적용되지만 각자 나름의 개성이 있고 약점이 있습니다. 하나를 골라서 익히고 GUI 디자인의 멋진 세계를 즐겨 보세요. 마지막으로 많은 툴킷에 그래픽 GUI 구축 도구가 있다는 사실을 언급하고 싶습니다. 예를 들어 Qt는 Blackadder가 있고 GTK는 Glade가 있습니다. wxPython은 Python Card가 있습니다. 파이썬 카드는 전체적인 wxPython GUI 구축 과정을 간소화시켜 줍니다. 또한 무료 GUI 빌더인 Boa Constructor도 얻을 수 있습니다. 물론 여전히 알파 배포 상태이긴 하지만 말입니다. 심지어 Tkinter용 GUI 구축기도 있습니다. 이른바 SpecTix 로서 이전의 Tcl 도구에 기반하여 Tk 인터페이스를 구축하지만, 파이썬을 포함하여 여러 언어로 코드를 만들어 낼 수 있습니다. 또한 Tkinter용으로 개선된 위젯 집합이 있습니다. 이른바 Tix라고 하는데 최근에 표준 파이썬 라이브러리에 추가되어 (그리고 또다른 인기있는 애드-인은 파이썬 메가 위젯(PMW)입니다) 기본적인 Tkinter 집합과 wxPython 등등에 의하여 제공되는 집합 사이의 틈새를 메꾸어 주고 있습니다.

이 정도로 마치겠습니다. 이 장은 Tkinter 참조 페이지가 목적이 아니며 여러분을 시작시키는 정도로 충분합니다. 파이썬 웹 페이지의 Tkinter 섹션에서 다른 Tkinter 자원으로 가능 링크들을 살펴보세요.

또한 Tcl/Tk 사용법에 관하여 여러 책이 있습니다. 적어도 하나는 Tkinter에 관한 책입니다. 그렇지만 사례 연구에서 Tkinter에 다시 돌아오겠습니다. 사례 연구에서는 배치 모드 프로그램을 GUI에 싸 넣어서 사용성을 개선하는 방법을 보여줍니다.

기억해야 할 것
  • GUI 콘트롤을 창부품(widgets)이라고 한다.
  • 창부품은 그릇 계통도 안에서 조립된다.
  • GUI 툴킷마다 다양한 창부품 집합을 제공한다. 물론 어느 툴킷에나 있을 것이라고 예상되는 기본 집합은 있다.
  • 프레임으로 관련 창부품을 무리짓고 재사용가능한 GUI 구성요소의 토대를 형성할 수 있다.
  • 사건 처리 함수나 메쏘드는 그 이름을 창부품의 command 특성에 연결하여 창부품과 연관시킨다.
  • OOP는 창부품 그룹에 상응하는 객체를 만들고 사건에 상응하는 메쏘드를 만들어서 GUI 프로그램을 상당히 단순화 시켜 줄 수 있다.

앞으로HOME다음으로

이 페이지에 관하여 질문이나 제안이 있다면 저에게 이메일을 보내 주세요: alan.gauld@btinternet.com

'파이썬 프로그래밍' 카테고리의 다른 글

tkinter 소개  (0) 2011.09.04
Tkinter로 생각하기  (0) 2011.04.24
TKINTER 요약  (0) 2011.04.24
UltraEdit Python 설정  (0) 2009.06.28
파이썬 데몬 만들기  (0) 2009.04.07

[원문] http://coreapython.hosting.paran.com/GUI/Thinking%20in%20Tkinter.htm

Tkinter로 생각하기

작성 스티픈 퍼그(Stephen Ferg) (steve@ ferg.org)
revised: 2005-07-17
한글판 johnsonj 2008.04.10

이 파일에는 Tkinter로 생각하기 시리즈에 필요한 파일이 모두 들어있다.

이 파일을 작은 폰트로 인쇄하면, 세로 모드로 인쇄해도 코드 줄 끝이 잘리지 않는다. 줄 끝이 잘림을 감수하고 인쇄하려면, 더 작은 폰트로 인쇄하든가 아니면, 가로 모드로 인쇄하시기를 바란다.

인쇄 페이지는 인쇄 설정에 따라 다르지만 대략 40에서 60 페이지 사이이다. 인쇄 모양이 어떨지 그리고 페이지가 어느 정도 되는지 알아 보려면, 실제로 파일을 인쇄하기 전에 인쇄 미리보기를 하시기를 바란다.

GUI 프로그래밍의 기본 개념

  • 주제: "Tkinter로 생각하기"
  • 저자 : 스티픈 퍼그(Stephen Ferg) (steve@ferg.org)

"Tkinter로 생각하기"에 관하여

본인은 여러 책을 통해 Tkinter를 배우고 있는 중이다. 그런데 생각보다 어려웠다.

문제는 책의 저자들이 성급하게 Tkinter 도구상자에 있는 위젯들을 모두 설명하려고 한다는 것이다. 실제로는 기본적인 개념을 전혀 설명해 주지 않았다. 그들은 "Tkinter로 생각하는 법"을 가르쳐 주지 않았다.

다음에 Tkinter로 생각하는 법을 설명해주는 몇가지 짧은 프로그램을 소개하려고 한다. 그 프로그램들을 통하여 Tkinter에서 사용할 수 있는 모든 유형의 창부품과 속성 그리고 메쏘드를 낱낱히 나열하지는 않겠다. 또 Tkinter 전반에 관한 개론을 제공하지도 않겠다. 단지 Tkinter의 기본적인 개념을 이해시킬 정도로만 여러분을 이끌 생각이다.

주로 Tkinter 꾸리기 (즉"꾸림자(packer)") 위치 관리자(geometry manager)에 대한 연구에 주력하겠다. 격자관리자(grid)나 배치관리자(place)에 관한 연구는 제외한다.

네개의 기본적인 구이-프로그래밍 작업

사용자 인터페이스를 개발하려면 꼭 해야 할 표준적인 작업이 있다..

1) 어떻게 UI가 보여야 하는지 지정해야 한다. 다시 말해, 사용자의 컴퓨터 스크린에 무엇이 보일지 결정하는 코드를 작성해야 한다.

2) UI가 어떤 일을 하기를 원하는지 지정해야 한다. 다시 말해, 프로그램이 할 일을 루틴으로 작성해야 한다.

3) "모습"과 "할 일"을 연관지워 주어야 한다. 다시 말해, 코드를 작성해야 한다. 사용자의 컴퓨터에 보이는 것과 그 프로그램이 할 일을 지정한 루틴과 연관시켜야 한다.

4) 마지막으로, 사용자로부터 입력을 앉아서 기다리는 코드를 작성해야 한다.

몇가지 Gui-프로그래밍 용어

GUI (그래픽 사용자 인터페이스) 프로그래밍은 특별한 용어가 다음과 같은 기본적인 작업과 연관된다.

1) GUI의 모습을 지정하기 위해 "창부품(widgets)"의 모습과 그의 공간적 위치 관계를 기술한다 (즉, 한 위젯과 다른 위젯과의 상하 관계나, 좌우 관계). "창부품(widget)"이라는 용어는 "그래픽 사용자 인터페이스 구성부품"에 대한 일반적인 용어가 된 의미 없는 단어다. 창부품에는 창, 버튼, 메뉴, 메뉴 항목, 아이콘, 드롭-다운 리스트, 스크롤 바, 등등이 포함된다.

2) GUI의 처리를 실제적으로 담당하는 루틴을 "역호출 처리자(callback handlers)" 또는 "사건 처리자(event handlers)"라고 부른다. "사건(Events)"이란 키보드 누름이나 마우스 클릭 같은 입력 사건을 말한다. 이런 루틴을 "처리자(handlers)"라고 부르는데 그 이유는 그런 이벤트를 "처리하기" (즉, 응답하기) 때문이다.

3) 사건 처리자를 한 위젯과 연관짓는 것을 "묶기(binding)"라고 부른다. 대략적으로 말해, 묶기 과정에는 세가지 다른 일이 관련되어 있다:

  • (a) 사건의 유형 (예를 들어, 왼쪽 마우스 버튼 클릭, 또는 키보드에서 ENTER 키 누름)과
  • (b) 창부품 (예, 버튼) 그리고
  • (c) 사건-처리자 루틴.

예를 들어, (a) 왼쪽 마우스 버튼의 한번-클릭이 (b) 화면 위의 "CLOSE" 버튼/창부품에 일어나는 것을 (c) "closeProgram" 루틴에 묶을 수 있다. 이 루틴은 창을 닫고 프로그램을 끝낸다.

4) 앉아서 입력을 기다리는 코드를 "사건 회돌이(event loop)"라고 부른다.

사건 회돌이에 관하여

영화를 보면, 작은 마을이면 어디나 창문가에서 그저 바라 보면서 하루 종일 시간을 보내는 여유로운 할머니가 있기 마련이다. 그 할머니는 지나가는 모든 것들을 본다. 물론 본 것들이 모두 관심의 대상은 아니다 -- 그저 사람들이 거리에서 이리 저리 움직일 뿐이다. 그러나 그 중에 어떤 것은 관심을 끈다 -- 거리 건너 신혼 부부 집에서 큰 싸움이 일어 나거나 한다면 관심이 안 갈 수 없다. 관심의 사건이 일어나면, 그 할머니는 즉시 전화를 들고 그 소식을 경찰이나 이웃에게 전한다.

사건 회돌이는 이 할머니와 많이 닮았다. 사건 회돌이는 사건이 일어나는 것을 낱낱이 모두 보면서 시간을 보낸다. 대부분의 사건은 관심의 대상이 아니다. 그런 사건을 보더라도, 아무 일도 하지 않는다. 그러나 뭔가 흥미로운 사건을 보면 -- 사건 처리자가 그 사건에 묶여 있기 때문에, 관심의 대상인 사건을 보면 -- 즉시 사건 처리자를 불러서 해당 사건이 일어났음을 알린다.

프로그램의 행위

다음 프로그램을 보면 쉽게 사용자-인터페이스의 세계로 들어 갈 수 있다. 이런 기본적인 개념이 아주 간단한 프로그램에 어떻게 구현되어 있는지 보실 수 있다. 다음 프로그램은 Tkinter나 기타 어떤 형태의 GUI 프로그래밍도 사용하지 않는다. 그냥 콘솔에 메뉴를 띄워 간단한 키보드 입력을 받을 뿐이다. 그렇지만, 보시다시피, 사용자-인터페이스 프로그래밍에서 요구하는 네 개의 기본적인 일을 하고 있다.

[revised: 2003-02-23]

프로그램 소스 코드 tt000.py

#----- task 2:  사건 처리자 루틴을 정의한다 ---------------------def handle_A():    print "틀렸군요! 다시 해보세요!"def handle_B():    print "정답입니다!  트릴리엄(Trillium)은 꽃 종류입니다!"def handle_C():    print "틀렸어요! 다시 해보세요!"# ------------ task 1: 화면의 모습을 정의한다 ------------print "\n"*100   # 화면을 깨긋하게 정리하라print "            아주 도발적인 추측 게임"print "========================================================"print "해답을 기호로 누른다음, ENTER 키를 치세요."printprint "    A.  동물"print "    B.  식물"print "    C.  광물"printprint "    X.  이 프로그램 종료"printprint "========================================================"print "'Trillium'이란 어떤 종류의 것입니까?"print# ---- task 4: 사건 회돌이. 영원히 회돌이하면서, 사건들을 관찰한다. ---while 1:    # 다음 사건을 관찰한다    answer = raw_input().upper()    # -------------------------------------------------------    # Task 3: 흥미로운 키보드 사건을 그의 사건 처리자에    # 연관시킨다.  간단한 형태의 묶기임.    # -------------------------------------------------------    if answer == "A": handle_A()    if answer == "B": handle_B()    if answer == "C": handle_C()    if answer == "X":        # 화면을 정리하고 사건 회돌이를 빠져 나간다        print "\n"*100        break    # 다른 사건의 모두 관심의 대상이 아니며, 모두 무시된다

가장 단순한 Tkinter 프로그램

가장 단순한 Tkinter 프로그램 -- 서술문 단 세개!

앞서 지난 프로그램에서 언급한 네 개의 기본적인 GUI 작업중에서, 이 프로그램은 오직 한 가지 일을 한다 -- 사건 회돌이를 실행한다.

(1) 첫 번째 서술문에서 Tkinter를 반입하여, 사용가능하도록 만든다. 반입의 형태("from Tkinter import *")를 주목하자. Tkinter로 부터 오는 어떤 것이든 접두사 "Tkinter"로 자격을 부여하지 않겠다는 뜻이다.

(2) 두 번째 서술문에서 "최상위(toplevel)" 창이 생성된다. 기술적으로, 두 번째 서술문이 하는 일은 클래스 실체 "Tkinter.Tk"를 만드는 것이다.

이 최상위 창은 Tkinter 어플리케이션에서 제일 높은- 수준의 GUI 구성요소이다. 관례적으로, 최상위 창은 보통 이름이 "root"이다.

(3) 세 번째 서술문에서 "root" 객체의 "mainloop" (즉, 사건 회돌이) 메쏘드가 실행된다. 주 회돌이가 실행되면, 루트에 사건이 일어나기를 기다린다. 사건이 일어나면, 바로 처리되고 회돌이는 계속해서 진행하면서, 다음 사건이 일어나기를 기다린다. 회돌이는 루트 창에 "소멸(destroy)" 사건이 일어날 때까지 계속된다. "destroy" 사건은 창을 닫는 사건이다. 루트가 소멸되면, 창은 닫히고 사건 회돌이는 종료한다.

프로그램의 행위

다음 프로그램을 실행하면, (Tk 고마워) 최상위 창이 자동으로 나타나는데 창을 최소화하거나 최대화하며 닫는 창부품으로 장식되어 있다. 시험해 보자 -- 실제로 작동할 것이다.

"close" 창부품을 (제목 막대의 오른 쪽에서, 상자 안의 "x"를) 클릭하면 "destroy" 사건이 일어난다. 소멸 사건은 주 사건 회돌이를 종료시킨다. "root.mainloop()" 이후로는 서술문이 없으므로, 프르그램은 더 이상 아무 일도 하지 않고, 끝난다.

[revised: 2003-02-23]

프로그램 소스 코드 tt010.py

from Tkinter import * ### (1)root = Tk()           ### (2)root.mainloop()       ### (3)

꾸려 넣기(packing)

이제 네가지 주 GUI 작업 중에서 또 하나를 건드려 보자 -- GUI의 모습을 지정해 보자.

다음 프로그램에서는 Tkinter 프로그래밍의 세 가지 주요 개념을 소개한다:

  • * GUI 객체를 만들어서 그의 부모 객체와 연관시키기
  • * 꾸려 넣기(packing)
  • * 그릇(containers) 대 창부품(widgets)

이제부터는 그릇 구성요소와 창푸품을 구별하겠다. 앞으로 쓸 용어에서, "창부품(widget)"이란 (보통) 눈에 보이고 일을 하는 GUI 구성요소이다. "그릇(container)"은 대조적으로 그냥 -- 바구니 같은 -- 그릇으로서 거기에다 창부품을 넣을 수 있다.

Tkinter에서는 수 많은 그릇이 제공된다. "그림판(Canvas)"은 그리기 어플리케이션을 위한 그릇이다. 가장 많이 사용되는 그릇은 "틀(frame)"이다.

틀(Frames)은 Tkinter에서 "Frame"이라고 부르는 클래스로 제공된다. 다음과 같은 표현은:

	Frame(myParent)

Frame 클래스의 실체가 생성되고 (다시 말해, 틀을 만든다), 그 프레임 실체를 그의 부모인 myParent에 연관시킨다. 또다른 방식으로 바라 본다면: 자손 프레임을 myParent 구성요소에 덧붙이는 표현식으로 보아도 좋다.

그래서 이 프로그램에서 다음 서술문은 (1):

	myContainer1 = Frame(myParent)

그의 부모가 myParent (즉, root)인 프레임을 생성하고, 거기에 "myContainer1"이라는 이름을 부여한다. 다시 말해, 그릇을 만들어 주어서 거기에 창부품을 넣을 수 있다. (이 프로그램에서는 아무 창부품도 넣지 않겠다. 다음 프로그램에서 그렇게 해 보자.)

부모/자손 관계는 여기에서 시각적 관계가 아니라 논리적 관계임에 주목하자. 이 관계는 소멸 사건 같은 것들을 지원하기 위해 존재한다 -- (루트 같은) 부모 구성요소가 소멸할 때, 누가 자기의 자손인지 알아서 자신이 소멸하기 전에 그 자손을 죽일 수 있도록 하기 위해서다.

(2) 다음 서술문은 myContainer1을 "꾸려 넣는다(packs)" .

     myContainer1.pack()

간단히 말해, "꾸려넣기(packing)"란 한 GUI 구성요소와 그의 부모 사이의 시각적 관계를 설정하는 처리과정이다. 꾸려 넣지 않으면, 보이지 않는다.

"Pack"에는 Tkinter "pack" 위치 관리자에 관련되어 있다. 위치 관리자(geometry manager)란 본질적으로 시각적으로 그릇과 창부품이 어떻게 보여야 하는지 Tkinter에게 말해주는 API이다. Tkinter는 세가지 위치 관리자를 지원하는데: pack과 grid 그리고 place가 그것이다. 사용하기 쉽기 때문에, Pack과 (약간 범위는 작지만) grid가 널리 사용된다. "Tkinter로 생각하기"에서 모든 예제는 pack 위치 관리자만 사용한다.

그래서 다음에 Tkinter 프로그래밍에서 자주 나타나는 기본적인 패턴이 있다.

  • (1) (창부품이나 그릇의) 실체는 생성되면, 그의 부모와 연관된다.
  • (2) 그 실체는 꾸려넣어진다(packed).

프로그램 행위

다음 프로그램은 앞의 예제와 아주 흡사하다. 단 보기가 좀 어려운데, 그 이유는...

틀은 신축적이다

틀은 기본적으로 그릇이다. 그릇의 안쪽을 -- 말 그대로 그릇 안쪽의 "공간"인 -- "cavity"라고 불리운다. ("Cavity"는 Tkinter가 Tk로부터 가져온 기술적인 용어이다.)

이 cavity는 "잘 늘어난다". 즉 고무 줄처럼 신축성이 좋다. 틀에 최소나 최대 크기를 지정하지 않는 한, 공간(cavity)은 늘어나거나 줄어들어서 그 틀 안에 무엇이 놓이든 잘 적응한다.

앞 프로그램에서는 그 안에 아무 것도 넣지 않았기 때문에, 루트는 기본 크기로 자신을 화면에 표시했다.

그러나 다음 프로그램에서는 "무언가"를 루트의 cavity 안에 집어 넣겠다 -- Container1을 그 안에 집어 넣었다. 그래서 루트 틀은 줄어들어서 Container1의 크기에 적응한다. 그러나 Container1 안에 아무 창부품도 넣지 않았기 때문에, 그리고 Container1에 대해 최소 크기도 지정하지 않았기 때문에, 루트의 공간은 줄어들어 하나도 없게 된다. 그 때문에 아래 제목 막대에서는 아무것도 볼 수 없다.

다음 프로그램에서는 창부품과 기타 그릇들을 Container1에 집어 넣어 보겠다. 어떻게 Container1이 늘어나서 적응하는지 보실 수 있다.

[revised: 2003-02-24]

프로그램 소스 코드 tt020.py

from Tkinter import *root = Tk()myContainer1 = Frame(root)  ### (1)myContainer1.pack()         ### (2)root.mainloop()

창부품 꾸리기

다음 프로그램에서는 처음으로 창부품을 만들어서, 그것을 myContainer1에 집어 넣는다.

(1) 만든 그 창부품은 버튼이다 -- 다시 말해, Tkinter의 "Button" 클래스의 실체이다. 다음 서술문은:

		button1 = Button(myContainer1)

버튼을 생성하고, 거기에 "button1"이라는 이름을 부여하고, 그것을 그의 부모인 myContainer1이라고 부르는 그릇 객체에 연관시킨다.

(2)(3) 창부품들은 속성이 여러개 있는데, 지역 이름공간 사전에 저장되어 있다. 버튼 창부품이 가진 속성으로 크기, 전경색과 배경색, 표시할 텍스트, 테두리의 모양 등등을 제어한다. 이 예제에서는 button1에 딱 두개의 속성만 설정하겠다: 배경색과 텍스트를 설정해 보겠다. 버튼 사전에 "text"와 "background" 키로 값을 설정하면 된다.

		button1["text"]= "Hello, World!"		button1["background"] = "green"

(4) 물론, button1을 꾸려 넣어야 한다.

		button1.pack()

몇가지 유용한 기술적 용어

그릇과 그 안에 담긴 창부품 사이의 관계를 종종 "부모/자손" 관계로 지칭한다. 또 "주인/노예" 관계라고 부르기도 한다.

프로그램의 행위

이 프로그램을 실행하면, Container1에 이제 "Hello, World!"라는 텍스트가 붙은 녹색 버튼이 담겨 있을 것이다. 거기를 클릭하더라도, 아무 일도 일어나지 않는데, 왜냐하면 버튼이 클릭되었을 때 해 줄 일을 아직 지정하지 않았기 때문이다. (나중에 그렇게 해 보자.)

지금까지는 예전과 같이, 제목 막대에 있는 CLOSE을 눌러서 창을 닫아야 한다.

myContainer1가 늘어나서 button1에 어떻게 적응하는지 주목하자.

[revised: 2002-10-01]

프로그램 소스 코드 tt030.py

from Tkinter import *root = Tk()myContainer1 = Frame(root)myContainer1.pack()button1 = Button(myContainer1)      ### (1)button1["text"]= "Hello, World!"    ### (2)button1["background"] = "green"     ### (3)button1.pack()	                    ### (4)root.mainloop()

클래스 구조의 사용

클래스 구조 사용하기

다음 프로그램에서 Tkinter 어플리케이션을 클래스 집합으로 구성하는 법을 보여주겠다.

다음 프로그램에서 MyApp라는 클래스를 추가하고 앞 프로그램에서 코드의 일부를 그의 구성자 (__init__) 메쏘드 안으로 이동시켰다. 다음과 같이 구조화된 프로그램에서는 세가지 다른 일을 한다:

(1) 코드에서 정의된 클래스(MyApp)에는 보여질 구이의 모습이 정의된다. 원하는 GUI의 모습과 더불어 그것으로 하고자 하는 일이 정의된다. 다음 코드는 클래스의 구성자 (__init__) 메쏘드로 이동되었다. (1a)

(2) 프로그램이 실행되면, 제일 먼저 하는 일은 클래스의 실체를 하나 만드는 일이다. 실체를 만들어내는 서술문은 다음과 같다.

   myapp = MyApp(root)

클래스의 이름이 "MyApp"임에 주목하자 (대문자에 주목) 그리고 실체의 이름은 "myapp"이다 (소문자에 주목).

또 다음 서술문은 "root"를 MyApp의 구성자 메쏘드(__init__)에 인자로 건네고 있음을 주목하자. 구성자 메쏘드는 "myParent" 이름 아래의 루트를 인식한다. (1a)

(3) 마지막으로, 루트에서 주회돌이를 실행한다.

왜 어플리케이션을 클래스로 구성하는가?

프로그램에 클래스 구조를 사용하는 이유 하나는 프로그램을 제어하기가 더 좋기 때문이다. 클래스로 구축되어 들어간 프로그램은 보통 -- 특히 아주 큰 프로그램이라면 -- 그렇지 않은 프로그램에 비해 훨씬 더 이해하기가 쉽다.

더 중요하게 고려해야 할 점은 어플리케이션을 클래스로 구축하면 전역 변수를 사용하지 않아도 된다는 것이다. 결과적으로, 프로그램이 커질수록, 사건 처리자가 서로 정보를 공유하기를 바랄 것이다. 한 가지 방법은 전역 변수를 사용하는 것이지만, 이것은 아주 난잡한 테크닉이다. 좀 더 좋은 방법은 실체 변수 (즉, "self." 변수를) 사용하는 것이다. 그러기 위해서는 어플리케이션을 클래스로 구조화시켜야 한다. 이 문제를 나중에 프로그램으로 탐험해 보겠다.

언제 클래스 구조를 도입해야 할까

앞에서 Tkinter 프로그램을 위한 클래스 구조 표기법을 소개하였다. 설명하기 위해 그리고 다른 주제로 나아가기 위해서 말이다. 그러나 실제 개발에서는 다르게 처리하고 싶을 수도 있다.

많은 경우, Tkinter 프로그램은 단순한 스크립트로 시작한다. 모든 코드는 줄 단위이다. 앞서 우리 프로그램 같이 말이다. 그러다가, 어플리케이션을 새롭게 이해할수록, 프로그램은 자라난다. 얼마 지나지 않아 코드가 넘쳐나고 전역 변수를 사용하기 시작할 수 있는데... 아마도 전역 변수가 엄청 많아질 것이다. 프로그램은 이해하고 수정하기에 점점 더 어려워진다. 그런 일이 일어나면, 프로그램을 분해할 때가 된 것이다. 다시 말해 클래스를 사용하여 재구조화해야 한다.

반면에, 클래스에 익숙하다면, 그리고 프로그램의 최종 모습을 잘 이해하고 있다면, 처음부터 클래스를 사용하여 프로그램을 구조화해도 좋다.

그러나 한편으로 (다시 처음으로 되돌아 가 본다면?), 개발 과정에서 초기에는 (게릿 뮐러(Gerrit Muller)가 지적한 바와 같이) 종종 사용해야 할 최적의 클래스 구조를 알지도 못한다 -- 초기 개발 과정에서는 문제와 해결책을 충분히 이해조차 하지 못한다. 클래스를 너무 일찍 사용하면 이해가 점점 어려워지고 결국 분해가 더욱 요구되며 그저 코드만 어지럽히는 불필요한 구조가 도입될 수 있다.

그래서 그 문제는 개인적인 취향과 경험 그리고 환경에 많이 관련된다. 당신에게 맞다고 느껴지는 대로 하자. 그리고 -- 어떤 방법을 사용하든 -- 두려워하지 말고 필요할 때 신중하게 분해하자.

프로그램 행위

다음 프로그램을 실행하면, 앞의 프로그램과 모습이 정확하게 똑 같다. 기능에 전혀 변화가 없다 -- 단지 코드가 구조화 되었을 뿐이다.

[revised: 2003-02-23]

프로그램 소스 코드 tt035.py

from Tkinter import *class MyApp:                         ### (1)    def __init__(self, myParent):      ### (1a)        self.myContainer1 = Frame(myParent)        self.myContainer1.pack()        self.button1 = Button(self.myContainer1)        self.button1["text"]= "Hello, World!"        self.button1["background"] = "green"        self.button1.pack()root = Tk()myapp = MyApp(root)  ### (2)root.mainloop()      ### (3)

속성 설정하기

(1) 앞의 프로그램에서 버튼 객체 button1을 만들어서, 그의 텍스트와 배경색을 아주 직접적인 방법으로 설정한다.

		self.button1["text"]= "Hello, World!"		self.button1["background"] = "green"

다음 프로그램에서 세개의 버튼을 더 Container1에 추가하는데, 약간 방법이 다르다.

(2) button2에 대하여, 처리과정은 본질적으로 button1과 같지만, 버튼의 사전에 접근하는 대신에 버튼에 내장된 "configure" 메쏘드를 사용한다.

(3) button3에서는 method가 여러 키워드 인자를 받을 수 있어서 하나의 서술문에 여러 옵션을 설정할 수 있음을 볼 수 있다.

(4) 앞의 예제에서는 버튼을 설정하는 일이 두-단계의 과정을 거쳤다: 첫 단계로 버튼을 만든 다음 그의 특성을 설정한다. 그러나 버튼을 만들 때 바로 특성을 지정하는 것이 가능하다. "Button" 창부품은 (다른 모든 창부품과 마찬가지로) 첫 인자가 그의 부모라고 생각한다. 이는 위치 인자이지, 키워드 인자가 아니다. 그러나 그 다음부터는 원한다면 여러개의 키워드 인자를 추가해서 창 부품의 특성을 지정할 수 있다.

프로그램의 행위

다음 프로그램을 실행하면, Container1에 이제 원래의 초록색 버튼 말고도 세개의 버튼이 더 들어 있을 것이다.

myContainer1가 늘어나서 어떻게 이런 버튼에 적응하는 것에 주목하자.

버튼이 차곡차곡 쌓이는 것에도 주목하자. 다음 프로그램에서는 왜 이런 식으로 서로 정렬되는지 알아보고 그리고 다르게 정렬하는 법을 살펴 보겠다.

프로그램 소스 코드 tt040.py

from Tkinter import *class MyApp:    def __init__(self, parent):        self.myContainer1 = Frame(parent)        self.myContainer1.pack()        self.button1 = Button(self.myContainer1)        self.button1["text"] = "Hello, World!"   ### (1)        self.button1["background"] = "green"     ### (1)        self.button1.pack()        self.button2 = Button(self.myContainer1)        self.button2.configure(text="Off to join the circus!") ### (2)        self.button2.configure(background="tan")               ### (2)        self.button2.pack()        self.button3 = Button(self.myContainer1)        self.button3.configure(text="Join me?", background="cyan")  ### (3)        self.button3.pack()        self.button4 = Button(self.myContainer1, text="Goodbye!", background="red") ### (4)        self.button4.pack()root = Tk()myapp = MyApp(root)root.mainloop()

정렬하기

지난 프로그램에서는 두 개의 버튼이 서로 위아래로 쌓여 있는 것을 보았다. 그렇지만, 옆으로 나란히 보이는게 좋겠다. 다음 프로그램에서는 pack()으로 할 수 있는 일을 살펴보겠다.

(1) (2) (3) (4)

꾸리기는 구성요소의 시각적 관계를 제어하는 방법이다. 그래서 이제 pack "side" 옵션을 사용하여 버튼을 나란히 배치해보자. "side" 인자를 pack() 서술문에 건네면 되는데, 예를 들면:

		self.button1.pack(side=LEFT)

LEFT는 (RIGHT와 TOP 그리고 BOTTOM과 마찬가지로) 사용자에게 친숙한 상수로서 Tkinter에 정의되어 있다. 다시 말해, "LEFT"는 실제로는 "Tkinter.LEFT"이다 -- 그러나 Tkinter를 반입했던 방식 때문에, 접두사로 "Tkinter."를 공급할 필요가 없다.

지난 프로그램에서 왜 버튼은 수직으로 쌓이는가

기억하시겠지만, 지난 프로그램에서는 "side" 옵션을 전혀 지정하지 않고 그냥 버튼을 꾸려 넣었고, 때문에 버튼은 서로 위아래로 쌓여 꾸려졌다. 그것은 기본 "side" 옵션이 "side=TOP"이기 때문이다.

그래서 button1을 꾸려 넣으면, myContainer1의 안쪽 공간 위쪽에 배치된다. 그 때문에 myContainer1에 대하여 남는 공간이 button1 아래에 위치한다. 다음 button2를 꾸려 넣었다. 공간의 위쪽에 꾸려 넣어졌는데, 이것은 공간이 button1 바로 아래에 위치한다는 뜻이다. 그리고 공간은 이제 button2 아래에 위치한다.

버튼을 다른 순서로 꾸려 넣었다면 -- 예를 들어, button2를 먼저 꾸린다음, button1을 꾸려 넣었다면 -- 위치가 반대로 되어서, button2가 위에 배치되었을 것이다.

그래서, 보시다시피, GUI의 겉모습을 통제하는 한 가지 방법은 그릇 안에 창부품을 꾸려 넣는 순서를 제어하는 것이다.

약간 기술적인 용어 -- "동선(orientation)"

"수직적" 동선은 TOP과 BOTTOM이 포함된다. "수평적" 동선은 LEFT와 RIGHT가 포함된다.

창부품과 그릇을 꾸려 넣을 때, 두 가지 동선을 섞는 것도 가능하다. 예를 들어, 한 버튼은 수직적 동선으로 (예, TOP) 꾸려 넣고 다른 버튼은 수평적 동선 (즉, LEFT)로 꾸려 넣을 수 있다.

그러나 한 그릇 안에서 이런 식으로 동선을 섞어 쓰는 것은 좋은 생각이 아니다. 동선을 섞어 쓰면, 최종적으로 어떻게 보일지 예측하기 힘들고, 창크기를 조절할 때 GUI가 일그러지는 모습을 보고 놀랄 수도 있다.

그래서 좋은 디자인 습관은 같은 그릇 안에서 절대로 동선을 섞어 쓰지 않는 것이다. 복잡한 GUI를 다루는 방법은, 실제로 여러 동선을 사용하고 싶으면, 그릇 안에 그릇을 내포시키는 것이다. 나중 프로그램에서 이 주제를 다루어 보겠다.

프로그램의 행위

다음 프로그램을 실행하면, 이제 버튼 두 개가 나란히 보일 것이다.

[revised: 2002-10-01]

프로그램 소스 코드 tt050.py

from Tkinter import *class MyApp:    def __init__(self, parent):        self.myContainer1 = Frame(parent)        self.myContainer1.pack()        self.button1 = Button(self.myContainer1)        self.button1["text"]= "Hello, World!"        self.button1["background"] = "green"        self.button1.pack(side=LEFT)	### (1)        self.button2 = Button(self.myContainer1)        self.button2.configure(text="Off to join the circus!")        self.button2.configure(background="tan")        self.button2.pack(side=LEFT)	 ### (2)        self.button3 = Button(self.myContainer1)        self.button3.configure(text="Join me?", background="cyan")        self.button3.pack(side=LEFT)	  ### (3)        self.button4 = Button(self.myContainer1, text="Goodbye!", background="red")        self.button4.pack(side=LEFT)	  ### (4)root = Tk()myapp = MyApp(root)root.mainloop()

사건 묶기

이제 버튼에게 일을 시킬 시간이다. 지난 두 개의 또는 (최초의 네가지) 기본적인 GUI 작업에 관심을 돌려보자 -- 하나는 프로그램에게 실제 작업을 시킬 사건 처리자 루틴을 작성하는 것이고, 다른 하나는 그 사건 처리자 루틴을 창부품과 사건에 묶는 것이다.

다음 프로그램에서는 앞 프로그램에서 만든 버튼을 모두 포기하고 오직 두 개의 버튼만 담긴 단순한 상황으로 돌아갔음에 주목하자: "OK" 버튼과 "Cancel"만 있다.

첫 프로그램을 연구할 때 기억하시겠지만, 기본적인 GUI 작업중의 하나는 "묶기(binding)"이다. "묶기(Binding)"란 다음과 같은 객체들 사이의 관계 또는 연결을 정의하는 과정이다 (보통 다음과 같은데):

  • * 창 부품
  • * 사건 유형과
  • * "사건 처리자"

"사건 처리자(event handler)"는 사건이 일어났을 때 처리하는 메쏘드 또는 서브루틴이다. [자바에서 사건 처리자는 "청취자(listeners)"라고 부르는데, 나는 이게 마음에 든다. 왜냐하면 그것이 무엇을 하는지 정확하게 제시하기 때문이다. -- 사건을 "청취한다" 그리고 그에 반응한다.]

Tkinter에서 이런 묶기를 만드는 방법은 bind() 메쏘드를 이용하는 것인데 이 메쏘드는 모든 Tkinter 창부품에 갖추어져 있다. bind() 메쏘드를 다음과 같은 서술문의 형태로 사용한다:

	widget.bind(event_type_name, event_handler_name)

이런 종류의 묶기를 "사건 묶기(event binding)"라고 부른다.

[사건 처리자를 창 부품에 묶는 다른 방법이 있는데 이를 "명령어 묶기(command binding)"라고 부르며 앞으로 두 프로그램에서 살펴 보겠다. 그러나 지금 당장은 사건 묶기를 살펴보자. 사건 묶기가 무엇인지 이해했으면, 명령어 묶기도 설명하기가 쉽다.]

시작하기 전에 혼란스러운 점 하나를 지적할 필요가 있다. "버튼"이라는 단어는 완전히 다른 두 가지를 지칭하기 위해 사용될 수 있다: (1) 하나는 버튼 창부품이고 -- 컴퓨터 화면에 표시되는 GUI 구성요소이다 -- (2) 또 하나는 마우스의 버튼이다 -- 손가락으로 누르는 버튼을 말함. 혼란을 피하기 위해서 그냥 "버튼"이라고 하기 보다 "버튼 창부품" 또는 "마우스 버튼"이라고 특칭해서 둘을 구별하겠다.

(1) button1 창부품에 일어난 "<Button-1>" 사건(왼쪽 마우스 버튼의 클릭)을 "self.button1Click" 메쏘드에 묶는다. button1에 왼쪽 마우스 버튼 클릭이 일어나면 self.button1Click() 메쏘드가 요청되어 그 사건을 처리한다.

(3) "bind" 연산에 지정되지는 않았지만, 두 개의 인자가 self.button1Click()에 건네지는 것에 주목하자. 물론 첫 인자는 "self"인데, 이는 파이썬의 모든 클래스 메쏘드에 언제나 첫 인자로 건네진다. 두 번째 인자는 사건 객체이다. 이런 식으로 (즉, bind() 메쏘드를 사용하여) 사건을 묶는 테크닉은 언제나 묵시적으로 사건 객체를 인자로 건넨다.

Python/Tkinter에서 사건이 일어나면 사건 객체의 형태를 취한다. 사건 객체는 대단히 유용한데, 그 이유는 모든 종류의 유용한 정보와 메쏘드를 함께 가지고 다니기 때문이다. 사건 객체를 조사하면 어떤 종류의 사건이 일어났는지와 그 사건이 일어난 객체 그리고 기타 유용한 정보들을 알아낼 수 있다.

(4) 그래서, button1이 클릭되면 어떤 일이 일어나기를 원하는가? 이 경우 아주 간단한 일을 시킨다. 그냥 그의 색상을 초록색에서 노란색으로 그리고 다시 원래대로 바꾼다.

(2) button2 ("Goodbye!" 버튼)에는 실제로 좀 그럴 듯한 일을 시켜보자. 그에게 창을 닫도록 시켜 보겠다. 그래서 button2에 일어나는 왼쪽-마우스 클릭을 button2Click() 메쏘드에 묶었고, 그리고

(6) button2Click() 메쏘드에게 myapp의 부모창인 루트 창을 파괴하도록 시켰다. 이렇게 하면 루트 아래의 모든 자식과 자손이 물결치듯이 연달아 파괴된다. 요약하면, GUI의 모든 부분이 소멸된다.

물론 이렇게 하려면 myapp는 자신의 자손이 누구인지 알아야 한다. 그래서 (7) 코드를 그의 구성자에 추가하여 myapp가 그의 부모를 기억하도록 만든다.

프로그램의 행위

다음 프로그램을 실행하면 버튼이 두 개 보인다. "OK" 버튼을 클릭하면 색깔이 바뀐다. "Cancel" 버튼을 클릭하면 어플리케이션이 닫힌다.

GUI가 열리고, 키보드에서 TAB 키를 누르면, 키보드 초점이 두 버튼 사이를 오가는 것이 보일 것이다. 그러나 키보드에서 ENTER/RETURN 키를 눌러봐야 아무 일도 일어나지 않는다. 그 이유는 키보드 사건이 아니라 오직 마우스 클릭만을 버튼에 묶었기 때문이다. 다음으로 키보드 사건을 버튼에도 묶어 보겠다.

마지막으로, 한 버튼의 텍스트가 다른 버튼의 텍스트보다 짧기 때문에, 두 버튼이 크기가 다르다는 것에 주목하자. 이는 약간 보기에 좋지 않다. 다음 프로그램에서 이 문제를 고쳐 보겠다.

[revised: 2002-10-01]

프로그램 소스 코드 tt060.py

from Tkinter import *class MyApp:    def __init__(self, parent):        self.myParent = parent  ### (7) 부모, 즉 루트를 기억시킨다        self.myContainer1 = Frame(parent)        self.myContainer1.pack()        self.button1 = Button(self.myContainer1)        self.button1.configure(text="OK", background= "green")        self.button1.pack(side=LEFT)        self.button1.bind("<Button-1>", self.button1Click) ### (1)        self.button2 = Button(self.myContainer1)        self.button2.configure(text="Cancel", background="red")        self.button2.pack(side=RIGHT)        self.button2.bind("<Button-1>", self.button2Click) ### (2)    def button1Click(self, event):    ### (3)        if self.button1["background"] == "green": ### (4)            self.button1["background"] = "yellow"        else:            self.button1["background"] = "green"    def button2Click(self, event):  ### (5)        self.myParent.destroy()     ### (6)root = Tk()myapp = MyApp(root)root.mainloop()

초점이란 무엇인가

앞 프로그램에서는 마우스로 클릭하면 버튼에게 일을 시킬 수 있었는데, 키보드에서 키를 눌러서는 일을 시킬 수 없었다. 다음 프로그램에서는 마우스 사건뿐만 아니라 키보드 사건에도 반응시키는 법을 다루어 보겠다.

먼저, "입력 초점(input focus)" 또는 그냥 단순하게 "초점(focus)"이라는 개념이 필요하다.

그리스 신화를 잘 알고 계시다면 (디즈니사의 만화 영화 "허큘레스"를 보셨다면) 아마도 운명의 여신(Fates)을 기억하실 것이다. 운명의 여신은 세명의 여신으로서 인간의 운명을 주관한다. 인간의 운명은 그 여신들의 손아귀에 놓인 실타래였다. 줄을 끊으면, 사망한다.

운명의 여신에 관하여 인상적인 것은 셋 사이에 오직 하나의 눈 만을 공유한다는 것이다. 그 하나의 눈을 가진 여신은 모든 것들을 보고서, 다른 두 여신에게 본 것을 말해 주어야 했다. 만약 그 눈을 서로 주고 받을 수 있었다면, 번갈아서 볼 수 있었을 텐데 말이다. 물론, 그 눈을 훔칠 수 있다면, 다른 여신과 교섭을 할 때 확실한 패를 가진 셈이다.

"초점(Focus)"은 GUI 위의 창부품들에게 키보드 사건을 볼 수 있도록 해준다. 그 한 눈과 운명의 여신들과의 관계는 초점과 GUI 위의 창부품들과의 관계와 같다.

한 순간에 오직 한 창부품만 초점을 가진다. 그리고 "초점을 가진" 창부품만 키보드 사건을 보고 반응할 수 있다. 창 부품에 "초점을 설정하는 일"은 그 초점을 창부품에 부여하는 것이다.

예를 들어, 다음 프로그램에서는 GUI에 버튼이 두 개 있는데: "OK"와 "Cancel"이 그것이다. 키보드에서 RETURN 버튼을 쳤다고 해보자. "Return" 키눌림 사건이 "OK" 버튼에 보여지고 (즉 전송되고), 사용자가 선택을 승인했음을 알려줄 것인가? 그렇지 않으면 "Return" 사건이"Cancel" 버튼에 보여지고 (즉 전송되고), 사용자가 처리를 취소했음을 보여줄 것인가? 그것은 "초점"이 어디에 있는가에 달려있다. 다시 말해, 어느 버튼에 "초점"이 있는가에 달려있다.

한 여신에서 다른 여신에게로 건네지는, 운명의 여신의 눈처럼 초점은 한 GUI 창부품에서 다른 창부품으로 건네질 수 있다. 초점을 한 창부품에서 다른 창부품으로 이동시키거나 건네는 방법은 여러가지가 있다. 한가지 방법은 마우스로 이동시키는 것이다. 한 창부품에 "초점을 설정하려면" 그 위젯에 왼쪽 마우스 버튼을 클릭하면 된다. (최소한 "click to type" 모델이라고 불리우는 이 모델은 윈도우즈와 매킨토시 Tkinter에서 작동하는 방식이다. "focus follows mouse" 관례를 사용하는 시스템도 있는데 이 시스템에서는 마우스 아래에 있기만 하면 그 창부품은 자동으로 초점을 가지며, 클릭할 필요가 없다. Tk에서 같은 효과를 얻으려면 tk_focusFollowsMouse 프로시저를 사용하면 된다.)

초점을 설정하는 또다른 방법은 키보드로 하는 것이다. 초점을 받을 수 있는 창부품들은 만들어진 순서대로 ("순회 순서") 모두 환형 리스트 에 저장된다. 키보드에서 TAB 키를 치면 초점이 현재 위치(위치가 없을 수도 있음)에서 리스트에 있는 다른 창부품으로 이동한다. 리스트의 끝에서, 초점은 다시 처음으로 이동한다. SHIFT+TAB을 치면 초점은 앞으로가 아니라 반대로 이동한다.

GUI 버튼이 초점을 가지면, 초점을 가졌다고 점선 상자가 그 버튼의 텍스트 둘레에 보인다. 다음은 그것을 보는 방법이다. 앞의 프로그램을 실행하자. 프로그램이 시작되면, GUI가 나타나고, 두 버튼 어느 곳에도 초점이 없으므로, 점선 상자가 보이지 않는다. 이제 TAB 키를 쳐 보자. 점선 상자가 왼쪽 버튼 둘레에 보일 것이다. 초점이 주어졌다는 뜻으로 말이다. 이제 TAB 키를 치고 또 쳐보자. 초점이 다른 버튼으로 어떻게 이동하는지 보일 것이다. 마지막 버튼에 도달하면, 다시 처음 버튼으로 빙 둘러서 되돌아 온다. (프로그램에서는 오직 버튼이 두 개만 보이므로, 그 효과는 초점이 두 버튼 사이를 왔다갔다 점프하는 것이 전부이다.)

(0) 다음 프로그램에서는 OK 버튼에게 처음부터 초점을 부여할 생각이다. 그래서 "focus_force()" 메쏘드를 사용하는데, 이 메쏘드는 초점이 OK 버튼에 가도록 만든다. 다음 프로그램을 실행하면 OK 버튼에 시작할 때부터 초점이 주어진 것을 보실 것이다.

지난 프로그램에서는 버튼이 키보드 사건에만 반응했다 -- TAB 키 누름 -- 이로서 초점이 두 버튼 사이를 왔다갔다 이동했다. 그러나 키보드에서 ENTER/RETURN 키를 쳐보면 아무일도 일어나지 않는다. 그것은 키보드 사건이 아니라 오직 마우스 클릭만을 버튼에 묶었기 때문이다.

다음 프로그램에서는 키보드 사건을 버튼에 묶어 보겠다.

(1) (2) 키보드 사건을 버튼에 묶는 서술문은 아주 간단하다 -- 마우스 사건을 묶는 서술문과 형태가 똑같다. 유일한 차이점은 사건의 이름이 키보드 사건의 이름이라는 것이다 (이 경우에는 "<Return>").

키보드에서 RETURN 키를 누르거나 왼쪽 버튼을 클릭해도 똑 같은 효과를 얻고 싶다. 그래서 같은 사건 처리자를 두 가지 다른 종류의 사건에 묶었다.

다음 프로그램은 여러 유형의 사건을 (버튼 같은) 하나의 창부품에 묶을 수 있음을 보여준다. 또 여러 <창부품, 사건>을 조합하여 같은 사건 처리자에 묶을 수 있다.

(3) (4) 버튼 위젯은 여러 종류의 사건에 응답하며, 이제 사건 객체로부터 정보를 열람하는 법을 보여줄 수 있다. 사건 객체들을 (5) "report_event" 루틴에 건네겠다. 이 루틴은 (6) 그 사건 객체의 속성으로부터 얻은 정보를 인쇄해 줄 것이다.

이 정보를 콘솔에 인쇄하려면 이 프로그램을 콘솔 창에서 (pythonw가 아니라) python으로 실행해야 함을 주의하자.

프로그램의 행위

이 프로그램을 실행하면, 버튼이 두 개가 보인다. 왼쪽 버튼을 클릭하거나, 또는 왼쪽 버튼에 키보드 초점이 있을 때 RETURN 키를 누르면 색깔이 바뀐다. 오른쪽 버튼을 클릭하거나, 오른쪽 버튼이 키보드 초점을 가지고 있을 때 RETURN 키를 누르면 어플리케이션이 종료된다. 키보드 사건이나 마우스 사건 어느 것이라도 발생한 시간과 그 사건을 설명해주는 메시지가 인쇄된다.

[Revised: 2002-09-26]

프로그램 소스 코드 tt070.py

from Tkinter import *class MyApp:    def __init__(self, parent):        self.myParent = parent        self.myContainer1 = Frame(parent)        self.myContainer1.pack()        self.button1 = Button(self.myContainer1)        self.button1.configure(text="OK", background= "green")        self.button1.pack(side=LEFT)        self.button1.focus_force()         ### (0)        self.button1.bind("<Button-1>", self.button1Click)        self.button1.bind("<Return>", self.button1Click)  ### (1)        self.button2 = Button(self.myContainer1)        self.button2.configure(text="Cancel", background="red")        self.button2.pack(side=RIGHT)        self.button2.bind("<Button-1>", self.button2Click)        self.button2.bind("<Return>", self.button2Click)  ### (2)    def button1Click(self, event):        report_event(event)        ### (3)        if self.button1["background"] == "green":            self.button1["background"] = "yellow"        else:            self.button1["background"] = "green"    def button2Click(self, event):        report_event(event)   ### (4)        self.myParent.destroy()def report_event(event):     ### (5)    """그의 속성에 기반하여, 사건의 설명을 인쇄한다.    """    event_name = {"2": "KeyPress", "4": "ButtonPress"}    print "Time:", str(event.time)   ### (6)    print "EventType=" + str(event.type), \        event_name[str(event.type)],\        "EventWidgetId=" + str(event.widget), \        "EventKeySymbol=" + str(event.keysym)root = Tk()myapp = MyApp(root)root.mainloop()

명령어 묶기

앞의 프로그램에서 "사건 묶기(event binding)"를 소개했다. 사건 처리자를 창 부품에 묶는 방법이 한 가지 더 있다. 이른바 "명령어 묶기(command binding)"라는 것으로서 다음 프로그램에서 살펴보자.

명령어 묶기(Command Binding)

기억하시겠지만 지난 프로그램에서는 "<Button-1>" 마우스 사건을 버튼 창부품에 묶었다. "버튼"은 "ButtonPress" 마우스 사건에 대한 또다른 이름이다. 그리고 "ButtonPress" 마우스 사건은 "ButtonRelease" 마우스 사건과 다르다. "ButtonPress" 사건은 마우스 버튼을 누르는 행위지, 그 버튼에서 손을 떼는 행위가 아니다. "ButtonRelease" 사건은 마우스 버튼에서 손을 떼는 행위이다 -- 버튼이 다시 올라 온다.

마우스 ButtonPress와 마우스 ButtonRelease를 구별할 필요가 있는데 "끌어 떨구기(drag and drop)"같은 특징을 지원하기 위해서이다. ButtonPress를 GUI 구성요소에 두고, 그 구성요소를 다른 곳으로 끌고 간 다음, 마우스 버튼을 놓아서 새로운 위치에 "떨구어" 놓는다.

그러나 버튼 창부품은 끌어 떨구기가 되는 종류의 것이 아니다. 버튼을 끌어 떨굴 수 있다고 생각한다면, 버튼 창부품에 ButtonPress를 하고, 마우스 포인터를 화면 위 다른 곳에 끌고 와서, 마우스 버튼을 놓으면 된다. 이는 우리가 버튼 창부품이 눌렸다고 "push" (또는 -- 기술적 용어로 -- "요청(invocation)")으로 간주하는 종류의 행위가 전혀 아니다. 버튼 창부품이 눌렸다고 간주하려면, 사용자가 버튼 창부품에 ButtonPress를 행하고, -- 마우스 포인터를 그 창부품으로부터 전혀 옮기지 않고 -- ButtonRelease가 행해져야 한다. *그것이* 바로 버튼 눌림이라고 간주될 것이다.

이는 앞의 프로그램에서 사용했던 것보다 좀 더 복잡하게 버튼 요청을 표기하는 법이다. 앞에서는 그냥 단순하게 사건 묶기를 사용하여 "Button-1" 사건을 버튼 창부품에 묶었다.

다행스럽게도 이렇게 좀 더 복잡하게 창부품 요청을 표기하는 법을 지원하는 또다른 형태의 묶기가 있다. 이른바 "명령어 묶기(command binding)"라고 부르는데 창부품의 "command" 옵션을 사용하기 때문이다.

다음 프로그램에서는 주석이 달린 줄들을 잘 보시고 (1) 그리고 (2) 어떻게 명령어 묶기가 이루어지는지 살펴보자. "command" 옵션을 사용하여 button1을 "self.button1Click" 사건 처리자에 묶었고, button2를 "self.button2Click" 사건 처리자에 묶었다.

(3) (4)

사건 처리자의 정의를 살펴보자. 주목할 것은 -- 앞 프로그램의 사건 처리자들과는 다르게 -- 사건 객체를 인자로 예상하지 않는다는 것이다. 그것은 사건 묶기와는 다르게 명령어 묶기가 자동으로 사건 객체를 인자로 건네지 않기 때문이다. 물론, 이것은 의미가 있다. 명령어 묶기는 하나의 사건을 처리자에 묶지 않고 여러 사건을 한 처리자에 묶는다. 예를 들면, 버튼 창부품에 대하여 ButtonPress 다음에 ButtonRelease가 따르는 패턴을 처리자에게 묶었을 때, 사건을 그의 사건 처리자에게 건넨다면, 어느 사건이 건네질 것인가: ButtonPress인가 아니면 ButtonRelease인가? 어느 쪽도 정답이 아니다. 이는 명령어 묶기가 사건 묶기와는 다르게 사건 객체를 건네지 않기 때문이다.

이 차이점을 다음 프로그램에서 좀 자세하게 살펴보겠다. 그러나 지금은 프로그램을 실행해보자.

프로그램의 행위

다음 프로그램을 실행하면, 앞의 프로그램에서와 버튼은 모습이 똑 같이 보이겠지만, 행위는 다르다.

버튼 하나에 마우스 ButtonPress을 실행하고 그 행위를 비교해보자. 예를 들어, 화면 위에 마우스 포인터를 이동시켜서 "OK" 버튼 창부품이 위치한 곳으로 가보자. 다음 왼쪽 마우스 버튼을 눌러보자. 절대로 마우스 버튼을 놓으면 안된다.

앞의 예제에서 이렇게 하면, button1Click 처리자가 즉시 실행되고 메시지가 인쇄되었다. 그러나 이 프로그램에서는 아무 일도 일어나지 않는다... 마우스 버튼을 놓기 전까지는 말이다. 마우스 버튼을 놓으면, 메시지가 인쇄된다.

차이점이 또 있다. 스페이스바, 그리고 RETURN 키를 눌러서 행위를 비교해 보자. 예를 들어, TAB 키를 사용하여 초점을 "OK" 버튼에 둔 다음, 스페이스바나 RETURN 키를 눌러보자.

("OK" 버튼이 "Return" 키눌림 사건에 묶여 있는) 앞의 프로그램에서는 스페이스바를 누르면 아무 효과도 없지만 RETURN 키를 누르면 색깔이 바뀌었다. 다음 프로그램에서는 반면에 그 행위가 정반대이다 -- 스페이스바를 누르면 버튼이 색깔을 바꾸지만, RETURN 키를 누르면 아무 효과도 없다.

다음 프로그램에서 이런 행위들을 살펴보겠다.

[revised: 2002-10-01]

프로그램 소스 코드 tt070.py

from Tkinter import *class MyApp:    def __init__(self, parent):        self.myParent = parent        self.myContainer1 = Frame(parent)        self.myContainer1.pack()        self.button1 = Button(self.myContainer1, command=self.button1Click) ### (1)        self.button1.configure(text="OK", background= "green")        self.button1.pack(side=LEFT)        self.button1.focus_force()        self.button2 = Button(self.myContainer1, command=self.button2Click)  ### (2)        self.button2.configure(text="Cancel", background="red")        self.button2.pack(side=RIGHT)    def button1Click(self):  ### (3)        print "button1Click event handler"        if self.button1["background"] == "green":            self.button1["background"] = "yellow"        else:            self.button1["background"] = "green"    def button2Click(self): ### (4)        print "button2Click event handler"        self.myParent.destroy()root = Tk()myapp = MyApp(root)root.mainloop()

명령어 묶기와 사건 묶기의 차이

앞의 프로그램에서는 "명령어 묶기(command binding)"를 소개하고 사건 묶기와 명령어 묶기 사이의 차이점을 지적하였다. 다음 프로그램에서는 이런 차이점들을 좀 더 상세하게 탐험해보자.

"command"는 어떤 사건에 묶이는가?

앞 프로그램에서는 TAB 키를 사용하여 초점을 "OK" 버튼에 두면, 스페이스바를 눌러서 버튼이 색깔을 바꾸도록 할 수는 있지만, RETURN 키를 누르면 아무 효과도 없었다.

그 이유는 버튼 창부품에 대하여 "command" 옵션에 마우스-사건 인지 뿐만 아니라 키보드-사건 인지가 제공되기 때문이다. 창 부품이 기다리는 키보드 사건은 RETURN 키가 아니라 스페이스바이다. 그래서, 명령어 묶기로, 스페이스바를 누르면 OK 버튼이 색깔을 바꾸지만, RETURN 키는 아무 효과가 없다.

이 행위는 (적어도 윈도우즈가 배경인 나에게는) 이상해 보인다. 그래서 여기에서 교훈은 명령어 묶기를 사용할 생각이라면 정확하게 무엇에 묶고 싶은지 잘 이해하는 것이 좋다는 것이다. 다시 말해, 무슨 키보드/마우스 사건이 명령어를 호출하는지 정확하게 이해하는 것이 좋은 생각이다.

불행하게도 이 정보의 믿을 만한 근원지는 Tk 소스 코드 그 자체이다. 좀 더 정보가 필요하다면 Tk (브렌 웰치(Brent Welch)의 "Practical Programming in Tcl and Tk"가 특별히 좋다) 또는 Tkinter에 관한 책을 보자. Tk 문서는 산만하지만, 온라인으로 볼 수 있다. Tcl의 8.4 버전에 대한 온라인 문서는 다음에서 볼 수 있다:

      http://www.tcl.tk/man/tcl8.4/TkCmd/contents.htm

또 모든 창부품이 "command" 옵션을 제공하는 것은 아니라는 것을 알아야 한다. 다양한 버튼 창부품들 (라디오버튼이나 체크 버튼 등등)은 모두 제공된다. 다른 것들은 비슷한 옵션 (예를 들어 scrollcommand)을 제공하기도 한다. 그러나 실제로 각각의 창부품을 조사해야만 명령어 묶기가 지원되는지 알 수 있다. 그러나 사용할 창부품에 대하여 "command" 옵션을 잘 배워두도록 하자. GUI의 행위가 개선되고, 코더로서의 여러분의 삶이 더 편안해질 것이다.

사건 묶기와 명령어 묶기를 함께 사용하기

지난 프로그램에서 명령어 묶기가 사건 묶기와는 다르게 자동으로 사건 객체를 인자로 건네지 않는다는 것을 알았다. 이 때문에 삶이 조금 더 고단해질 수 있다. 사건 묶기와 명령어 묶기를 *모두* 사용하여 사건 처리자를 창부품에 묶고 싶다면 말이다.

예를 들어, 다음 프로그램에서 버튼이 스페이스바 뿐만 아니라 RETURN 키를 눌러도 반응하면 정말 좋겠다. 그러나 그렇게 하려면, <Return> 키보드 사건에 사건 묶기를 사용해야 한다. 앞의 프로그램에서 했던 것처럼 말이다. (1)

문제는 명령어 묶기가 사건 객체를 인자로 건네지 않는다는 것이다. 그러나 사건 묶기는 건네준다. 그래서 어떻게 사건 처리자를 작성해야 하는가?

이 문제에는 여러가지 해결책이 있다. 그러나 가장 간단한 방법은 두 개의 이벤트 처리자를 작성하는 것이다. "진짜" 사건 처리자(2)는 명령어 묶기가 사용하는 것이 될 것이다. 명령어 묶기는 사건 객체를 예상하지 않는다.

다른 사건 처리자(3)는 그냥 진짜 사건-처리자에 대한포장자가 될 것이다. 이 포장자는 사건-객체 인자를 예상하지만, 그것을 무시하고 진짜 사건-처리자를 호출한다. 포장자에게 진짜 사건-처리자와 똑같은 이름을 부여하지만, 단 뒤에 "_a" 접미사를 덧붙였다.

프로그램의 행위

이 프로그램을 실행하면, 그 행위는 앞의 프로그램과 똑 같다. 단 이제 버튼이 스페이스바 뿐만 아니라 RETURN 키에도 반응한다는 점만 빼고 말이다.

[revised: 2002-10-01]

프로그램 소스 코드 tt075.py

from Tkinter import *class MyApp:    def __init__(self, parent):        self.myParent = parent        self.myContainer1 = Frame(parent)        self.myContainer1.pack()        self.button1 = Button(self.myContainer1, command=self.button1Click)        self.button1.bind("<Return>", self.button1Click_a)    ### (1)        self.button1.configure(text="OK", background= "green")        self.button1.pack(side=LEFT)        self.button1.focus_force()        self.button2 = Button(self.myContainer1, command=self.button2Click)        self.button2.bind("<Return>", self.button2Click_a)    ### (1)        self.button2.configure(text="Cancel", background="red")        self.button2.pack(side=RIGHT)    def button1Click(self):  ### (2)        print "button1Click event handler"        if self.button1["background"] == "green":            self.button1["background"] = "yellow"        else:            self.button1["background"] = "green"    def button2Click(self): ### (2)        print "button2Click event handler"        self.myParent.destroy()    def button1Click_a(self, event):  ### (3)        print "button1Click_a event handler (a wrapper)"        self.button1Click()    def button2Click_a(self, event):  ### (3)        print "button2Click_a event handler (a wrapper)"        self.button2Click()root = Tk()myapp = MyApp(root)root.mainloop()

정보 공유하기

지난 프로그램에서는 사건 처리자에게 실제로 일을 시키는 방법들을 알아보았다.

다음 프로그램에서는 사건 처리자 사이에 정보를 공유하는 법에 관하여 잠깐 살펴보자.

사건-처리자 함수 사이에 정보 공유하기

사건 처리자에게 어떤 일을 시키고 그 결과를 다른 사건 처리자와 공유하고 싶은 다양한 상황이 있다.

일반적인 패턴은 어플리케이션에 두 세트의 창부품이 있다는 것이다. 한 세트의 창부품은 일정한 정보를 만들고 선택하며 다른 세트의 창부품은 그 정보를 가지고 일을 한다.

예를 들어, 한 창부품에서 사용자는 파일 리스트로부터 파일을 선택하고, 다른 창부품들은 고른 그 파일에 대하여 다양한 연산을 할 수 있다 -- 파일을 열고 삭제하거나 복사하고 이름 바꾸기 등등.

또는 한 세트의 창부품은 어플리케이션에 다양한 환경 구성을 설정하고, 또다른 세트는 (SAVE와 CANCEL 옵션을 제공하는 버튼들) 디스크에 그런 설정을 저장하거나 또는 저장하지 않고 버릴 수 있다.

또는 한 세트의 창부품은 실행하고자 하는 프로그램에 대하여 매개변수들을 설정하고 또다른 창부품은 (RUN이나 EXECUTE 같은 이름을 가진 버튼들) 그런 매개변수를 가지고 프로그램을 시작시킬 수 있다.

또는 나중에 같은 함수를 호출할 때 정보를 건네기 위하여 사건-처리자 함수를 요청할 필요가 있을 수 있다. 그냥 두 가지 다른 값으로 변수를 이리 저리 바꾸는 사건 처리자를 생각해 보자. 변수에 새로 값을 할당하려면, 사건 처리자는 지난 번 실행될 때 그 변수에 어떤 값이 할당되었는지 알아야 한다.

문제점

여기에서 문제는 각 사건 처리자가 별도의 함수라는 것이다. 각 사건 처리자는 자신만의 지역 변수가 있고 이 변수들은 다른 사건-처리자 함수와 공유하지 않으며, 심지어 나중에 호출되는 자신과도 공유하지 않는다. 그래서 문제는 이것이다 -- 자신의 지역 변수를 공유할 수 없다면, 어떻게 사건-처리자 함수가 다른 처리자와 데이터를 공유할 수 있는가?

물론 해결책은 공유될 변수들이 그 사건 처리자 함수에 지역적이지 않으면 된다. 즉, 그 변수들을 사건 처리자 함수의 *바깥에* 저장해야 한다.

해결책 1 -- 전역 변수를 사용하기

공유를 위한 한가지 테크닉은 (공유하고자 하는) 변수들을 전역적으로 만드는 것이다. 예를 들면, 각 처리자에서 myVariable1와 myVariable2를 바꾸거나 볼 필요가 있다면, 다음과 같은 서술문을 배치하면 된다:

		global myVariable1, myVariable2

그러나 전역 변수를 사용하는 것은 잠재적으로 위험하다. 그리고 일반적으로 지저분한 프로그래밍이라고 눈총을 받는다.

해결책 2 -- 실체 변수를 사용하기

좀 더 깔끔한 테크닉은 "실체 변수" (즉, "self." 변수)를 사용하여 사건 처리자 사이에 공유할 정보를 유지하는 것이다. 이렇게 하려면 물론 어플리케이션이 그냥 인-라인 코드가 아니라 클래스로 구현되어야 한다.

이것이 바로 (앞서 이 시리즈 글에서) 어플리케이션을 클래스로 바꾼 까닭이다. 앞에서 이미 해 두었기 때문에, 이 시점에서 우리의 어플리케이션은 이미 실체 변수를 사용할 기반구조를 갖추고 있다.

다음 프로그램에서는 아주 단순한 정보를 기억하고 공유해 보겠다: 요청된 마지막 버튼의 이름을 말이다. 그 정보를 "self.myLastButtonInvoked"라고 부르는 한 실체 변수에 저장한다. [### 1 주석 참조]

그리고 실제로 이 정보를 기억하고 있는지 보여주기 위해, 버튼 처리자가 호출될 때마다, 다음 정보를 인쇄한다. [### 2 주석 참조]

프로그램의 행위

다음 프로그램은 버튼을 세 개 보여준다. 이 프로그램을 실행하고, 버튼을 아무거나 클릭하면, 그 이름과 클릭되었던 앞의 버튼 이름이 화면에 나타난다.

어떤 버튼도 어플리케이션을 닫지 않음에 주목하자. 그래서 닫을 생각이라면 CLOSE 창부품(제목 막대 오른쪽 상자 안의 "X"아이콘)을 눌러야 된다).

[revised: 2002-10-05]

프로그램 소스 코드 tt076.py

from Tkinter import *class MyApp:    def __init__(self, parent):        ### 1 -- 시작시에, 아직 어떤 버튼 처리자도 요청하지 않았다.        self.myLastButtonInvoked = None        self.myParent = parent        self.myContainer1 = Frame(parent)        self.myContainer1.pack()        self.yellowButton = Button(self.myContainer1, command=self.yellowButtonClick)        self.yellowButton.configure(text="YELLOW", background="yellow")        self.yellowButton.pack(side=LEFT)        self.redButton = Button(self.myContainer1, command=self.redButtonClick)        self.redButton.configure(text="RED", background= "red")        self.redButton.pack(side=LEFT)        self.whiteButton = Button(self.myContainer1, command=self.whiteButtonClick)        self.whiteButton.configure(text="WHITE", background="white")        self.whiteButton.pack(side=LEFT)    def redButtonClick(self):        print "RED    button clicked.  Previous button invoked was", self.myLastButtonInvoked  ### 2        self.myLastButtonInvoked = "RED"  ### 1    def yellowButtonClick(self):        print "YELLOW button clicked.  Previous button invoked was", self.myLastButtonInvoked ### 2        self.myLastButtonInvoked = "YELLOW" ### 1    def whiteButtonClick(self):        print "WHITE  button clicked.  Previous button invoked was", self.myLastButtonInvoked ### 2        self.myLastButtonInvoked = "WHITE" ### 1print "\n"*100 # 화면을 정리하는 간단한 방법print "시작..."root = Tk()myapp = MyApp(root)root.mainloop()print "... 완료!"

명령어 묶기를 더 자세히

다음 프로그램에서 탐험해 볼 것은 ...

명령어 묶기의 고급 특징을 더 자세히

tt075.py 프로그램에서는 "command" 옵션을 사용하여 사건 처리자를 창부품에 묶었다. 예를 들어 그 프로그램에서 다음 서술문은

    self.button1 = Button(self.myContainer1, command=self.button1Click)

button1Click 함수를 button1 창부품에 묶는다.

그리고 사건 묶기를 사용하여 버튼을 <Return> 키보드 사건에 묶었다.

    self.button1.bind("", self.button1Click_a)

앞의 프로그램에서는 사건 처리자들이 두 개의 버튼에 대하여 완전히 다른 기능을 수행했다.

그러나 상황이 다르다고 가정해 보자. 버튼이 여러개이고, 그 모든 버튼은 본질적으로 *같은* 유형의 행위를 촉발시켜야 된다고 가정해 보자. 그런 상황을 처리하는 가장 좋은 방법은 모든 버튼에 대하여 사건들을 단 하나의 사건 처리자에 묶는 것이다. 각 버튼은 같은 처리자 루틴을 호출한다. 그러나, 서로 다른 인자들을 건네어 무엇을 할지 지시한다.

그것이 바로 다음 프로그램에서 하고자 하는 것이다.

명령어 묶기

다음 프로그램에서는 보시다시피 두 개의 버튼이 있으며, "command" 옵션을 사용하여 그 버튼 모두를 같은 사건 처리자에 묶는다 -- 즉, "buttonHandler" 루틴에 말이다. 그 buttonHandler 루틴에 인자를 세 개 건넨다: (button_name 변수에) 버튼의 이름과 숫자 하나 그리고 문자열을 건넨다.

    self.button1 = Button(self.myContainer1,    	command=self.buttonHandler(button_name, 1, "Good stuff!")    	)

무거운 어플리케이션이라면 buttonHandler 루틴은 물론 무거운 일을 하겠지만, 이 프로그램에서는 그냥 받은 인자들을 인쇄한다.

사건 묶기

명령어 묶기는 이 정보로 충분하다. 사건 묶기는 어떤가?

아마도 <Return> 사건에 사건 묶기를 하는 두 줄에 주석 처리를 한 것을 눈치채셨을 것이다.

  # self.button1.bind("", self.buttonHandler_a(event, button_name, 1, "Good stuff!"))

다음은 문제의 첫 징조이다. 사건 묶기는 자동으로 사건 인자를 건넨다. -- 그러나 그 사건 인자를 인자 리스트에 포함시키는 방법은 쉽지 않다.

나중에 이 문제로 다시 돌아 와야 할 것이다. 지금은 그냥 프로그램을 실행하고 무슨 일어 나는지 지켜보자.

프로그램의 행위

코드를 살펴보면, 다음 프로그램은 아주 그럴듯 해 보인다. 그러나 실행해 보면 제대로 일을 하지 않는다. buttonHandler 루틴이 GUI가 화면에 표시되기도 전에 요청된다. 실제로, 두 번이나 호출된다!

아무 버튼이나 왼쪽-마우스-클릭을 하면, 아무 일도 일어나지 않는다 -- "eventHandler" 루틴이 호출되지 *않는다*.

이 프로그램을 닫는 유일한 방법은 제목 막대 오른쪽에 있는 (상자 안의 "X") "close" 아이콘을 누르는 것이다.

그래서 이제 프로그램을 실행하고 무슨 일이 일어나는지 지켜보자. 그러면 다음 프로그램에서는 왜 그런 일이 일어나는지 보실 수 있다.

[revised: 2003-02-23]

프로그램 소스 코드 tt077.py

from Tkinter import *class MyApp:    def __init__(self, parent):        self.myParent = parent        self.myContainer1 = Frame(parent)        self.myContainer1.pack()        button_name = "OK"        self.button1 = Button(self.myContainer1,            command=self.buttonHandler(button_name, 1, "Good stuff!"))        # self.button1.bind("<Return>", self.buttonHandler_a(event, button_name, 1, "Good stuff!"))        self.button1.configure(text=button_name, background="green")        self.button1.pack(side=LEFT)        self.button1.focus_force()  # Put keyboard focus on button1        button_name = "Cancel"        self.button2 = Button(self.myContainer1,            command=self.buttonHandler(button_name, 2, "Bad  stuff!"))        # self.button2.bind("<Return>", self.buttonHandler_a(event, button_name, 2, "Bad  stuff!"))        self.button2.configure(text=button_name, background="red")        self.button2.pack(side=LEFT)    def buttonHandler(self, arg1, arg2, arg3):        print "    buttonHandler routine received arguments:", arg1.ljust(8), arg2, arg3 	def buttonHandler_a(self, event, arg1, arg2, arg3):        print "buttonHandler_a received event", event        self.buttonHandler(arg1, arg2, arg3)print "\n"*100 # 화면을 정리하라print "tt077 프로그램 실행시작."root = Tk()myapp = MyApp(root)print "사건 회돌이를 시작할 준비."root.mainloop()print "사건 회돌이 실행 완료."

역호출 함수

지난 프로그램에서 실행의 흐름을 살펴보면, 이런 의문이 제기된다: "도대체 무슨 일이 일어나고 있는거야??!! "buttonHandler" 루틴이 버튼마다 실행되잖아, 심지어는 사건 회돌이가 시작되기도 전에 말이야!!"

그 이유는 다음과 같은 서술문에서

  self.button1 = Button(self.myContainer1,       command = self.buttonHandler(button_name, 1, "Good stuff!"))

역호출 함수로 사용할 것이 무엇인지 요구하지 않고, buttonHandler 함수를 호출하고 있기 때문이다. 의도한 바는 아니지만, 실제로 그렇게 하고 있는 것이다.

주목할 것

  • * buttonHandler는 함수 객체이다. 그리고 역호출 묶기로 사용 가능하다.
  • * buttonHandler() (반괄호에 주의)는 반면에 "buttonHandler" 함수를 실제로 호출하는 것이다.

다음과 같은 서술문이 실행되면

  self.button1 = Button(self.myContainer1,       command = self.buttonHandler(button_name, 1, "Good stuff!"))

실제로 "buttonHandler" 루틴이 호출된다. buttonHandler 루틴이 실행되어, 메시지가 인쇄되고, 호출의 결과가 반환된다 (이 경우에는 None 객체임). 그러면 버튼의 "command" 옵션은 그 호출의 결과에 묶인다. 간단히 말해, 그 명령어가 "None" 객체에 묶인다. 그 때문에 버튼을 클릭하더라도, 아무일도 일어나지 않는 것이다.

해결책은 있는가?

그래서... 해결책은 무엇인가? 사건-처리자 함수를 매개변수로 재사용할 방법이 있는가?

물론 있다. 일반적으로 알려진 테크닉은 두 가지이다. 하나는 파이썬에 내장된 "람다(lambda)" 함수를 사용하는 것이다. 다른 하나는 "함수내포기법(currying)"이라고 부른다.

다음 프로그램에서는 람다로 작업하는 법을 살펴보겠다. 그리고 그 다음 프로그램에서 함수내포기법(currying- 역주: 하스켈 카레에서 따옴)을 살펴보겠다.

람다와 함수내포기법이 어떻게 작동하는지 설명하지 않는다 -- 너무 복잡하고 우리의 목표와 너무 동떨어져 있다. 우리의 목표는 Tkinter 프로그램을 작동시키는 것이다. 그래서 그냥 그것들을 블랙 박스로 취급하고자 한다. 그 작동 방식에 관해서는 언급하지 않는다 -- 오직 그것들과의 작동 방식에 관해서만 다루겠다.

그래서 람다를 살펴보자.

명령어 묶기(Command Binding)

처음에, 다음 서술문은 작동할 것이라고 생각했지만:

  self.button1 = Button(self.myContainer1,  	command = self.buttonHandler(button_name, 1, "Good stuff!")  	)

... 알고보니 생각대로 작동하지 않았다.

원하는 방식대로 하려면 서술문들을 다음과 같이 재작성해야 한다:

  self.button1 = Button(self.myContainer1,       command = lambda       arg1=button_name, arg2=1, arg3="Good stuff!" :       self.buttonHandler(arg1, arg2, arg3)       )

사건 묶기

람다는 사건 묶기를 매개변수로도 만들어주니 즐겁다. 다음과 같이 하는 대신에:

     self.button1.bind("",     	self.buttonHandler_a(event, button_name, 1, "Good stuff!"))

(이는 작동하지 않는데, 사건 인자를 인자 리스트에 포함시킬 방법이 없기 때문이다), 람다를 사용하여 다음과 같이 작성할 수 있다:

		# 사건 묶기 -- 사건을 인자로 건넨다		self.button1.bind("",			lambda			event, arg1=button_name, arg2=1, arg3="Good stuff!" :			self.buttonHandler_a(event, arg1, arg2, arg3)			)

[여기에서 "event"는 변수 이름이 아니다 -- 파이썬의 키워드나 기타 어떤 것도 아니다. 이 예제에서는 사건 인자에 대하여"event"라는 이름을 사용한다. 그러나 이 테크닉을 연구하면서 "e"라는 이름은 사건 인자에 사용하고, 원한다면 그냥 쉽게 "event_arg"로 불러도 좋다.]

람다를 사용하면 얻는 멋진 특징중 하나는 (원한다면) 그냥 사건 인자를 건네지 않아도 된다는 것이다. 사건 인자를 건네지 않으면, self.buttonHandler 함수를 간접적으로 self.buttonHandler_a 함수를 통하여 호출하는 대신에 직접 호출할 수 있다.

이 테크닉을 보여주기 위해 button1에 했던 것과는 다르게 button2에 사건 묶기 코드를 작성해 보겠다. 다음은 button2로 하고자 하는 것이다:

		# 사건 묶기 -- 사건을 인자로 건네지 않음		self.button2.bind("",			lambda			event, arg1=button_name, arg2=2, arg3="Bad  stuff!" :			self.buttonHandler(arg1, arg2, arg3)			)

프로그램의 행위

프로그램을 실행하면 원하는 그대로 행위한다.

키보드에서 TAB 키를 눌러서 키보드 초점을 OK 버튼에서 CANCEL 버튼으로 다시 반대로 옮길 수 있다는 것에 주목하자.

특히, 키보드에서 <Return> 키를 눌러서 OK 버튼에 요청해 보아야 한다. OK 버튼을 <Return> 키를 눌러서 요청하면, buttonHandler_a 함수를 거치고, 그로부터 메시지도 받는데, 거기에 건네진 사건에 관한 정보가 인쇄된다.

어떤 경우든, 버튼 창부품을 클릭하든 키보드에서 <Return> 키를 눌러서 창부품에 요청하든, buttonHandler 함수에 건네진 인자들이 멋지게 인쇄된다.

[revised: 2003-02-23]

프로그램 소스 코드 tt078.py

from Tkinter import *class MyApp:    def __init__(self, parent):        self.myParent = parent        self.myContainer1 = Frame(parent)        self.myContainer1.pack()        #------------------ BUTTON #1 ------------------------------------        button_name = "OK"        # 명령어 묶기        self.button1 = Button(self.myContainer1,            command = lambda            arg1=button_name, arg2=1, arg3="Good stuff!" :            self.buttonHandler(arg1, arg2, arg3)            )        # 사건 묶기 -- 사건을 인자로 건넴        self.button1.bind("<Return>",            lambda            event, arg1=button_name, arg2=1, arg3="Good stuff!" :            self.buttonHandler_a(event, arg1, arg2, arg3)            )        self.button1.configure(text=button_name, background="green")        self.button1.pack(side=LEFT)        self.button1.focus_force()  # Put keyboard focus on button1        #------------------ BUTTON #2 ------------------------------------        button_name = "Cancel"        # 명령어 묶기        self.button2 = Button(self.myContainer1,            command = lambda            arg1=button_name, arg2=2, arg3="Bad  stuff!":            self.buttonHandler(arg1, arg2, arg3)            )        # 사건 묶기 -- 사건을 인자로 건네지 않음        self.button2.bind("<Return>",            lambda            event, arg1=button_name, arg2=2, arg3="Bad  stuff!" :            self.buttonHandler(arg1, arg2, arg3)            )        self.button2.configure(text=button_name, background="red")        self.button2.pack(side=LEFT)    def buttonHandler(self, argument1, argument2, argument3):        print "    buttonHandler routine received arguments:" \            , argument1.ljust(8), argument2, argument3 	def buttonHandler_a(self, event, argument1, argument2, argument3):        print "buttonHandler_a received event", event        self.buttonHandler(argument1, argument2, argument3)print "\n"*100 # 화면 정리print "tt078 프로그램 시작."root = Tk()myapp = MyApp(root)print "사건 회돌이 실행 준비."root.mainloop()print "사건 회돌이 실행 완료."

함수내포기법

앞의 프로그램에서는 인자를 사건-처리자 함수에 건네기 위해 람다에 관련된 테크닉을 살펴보았다. 다음 프로그램에서는 "함수내포기법(currying)"이라고 부르는 다른 테크닉을 살펴보겠다.

함수내포기법(Curry)에 관하여

가장 단순한 의미에서, 함수내포기법은 함수를 사용하여 다른 함수를 구성하는 테크닉이다.

함수내포기법은 기능적 프로그래밍에서 빌려온 테크닉이다. 더 자세히 알고 싶다면, "파이썬 요리책"에서 여러 요리법을 보실 수 있다:

	http://aspn.activestate.com/ASPN/Python/Cookbook/

다음 프로그램에 사용된 curry 클래스는 스코트 데이비드 다니엘스(Scott David Daniels)의 요리법으로서 "curry -- associating parameters with a function"이라는 이름으로 http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52549에서 보실 수 있다.

람다를 연구하던 때와 같이 어떻게 함수내포기법이 작동하는지 설명하지 않는다. 그냥 카레 클래스를 블랙 박스로 취급하겠다. 작동 방식에 관해서는 자세히 언급하지 않는다 -- 오로지 그것으로 작업하는 법에 관해서만 연구한다.

Curry -- 사용하는 법

카레(그 테크닉)를 사용하는 방법은 "curry" 클래스를 프로그램에 포함시키거나, 파이썬 파일에서 반입하는 것이다. 다음 프로그램에서는 카레 코드를 직접 프로그램에 포함시키겠다.

처음에는 다음 서술문이 self.buttonHandler를 self.button1의 명령어 옵션에 묶을 수 있다고 생각했겠지만, 생각대로 작동하지 않음을 알았다.

  self.button1 = Button(self.myContainer1,       command = self.buttonHandler(button_name, 1, "Good stuff!"))

카레를 사용하여, 다음과 같이 서술문을 다시 작성하면 된다:

  self.button1 = Button(self.myContainer1,       command = curry(self.buttonHandler, button_name, 1, "Good stuff!"))

보시다시피, 코드는 보이는 그대로 이해된다. self.buttonHandler 함수를 호출하는 대신에, 카레 객체 (즉, curry 클래스의 실체)를 만들어서, self.buttonHandler 함수를 그의 첫 인자로 건넨다. 기본적으로 카레 객체는 건넨 함수의 이름을 기억한다. 그래서 (카레 객체가) 호출되면, -- 이번에는 -- 생성될 때 주어진 함수를 호출한다.

사건 묶기

채드 네쩌(Chad Netzer)는 사건 묶기를 매개변수화 하는데 사용할 수 있는, 함수내포기법과 비슷한 테크닉을 고안했다. [이 코딩 테크닉은 파이썬 버전이 2.0 이상이어야 한다는 것에 주의하자.] 여기에는 "event_lambda" 함수가 사용된다.

카레에서와 같이 event_lambda를 사용하려면, 프로그램에 "event_lambda" 함수에 대한 코드를 포함시키든지, 파이썬 파일에서 반입해야 한다. 다음 프로그램에서는 프로그램에 event_lambda 함수 코드를 직접 포함시키겠다.

	# ----------  함수를 위한 코드: event_lambda --------	def event_lambda(f, *args, **kwds ):		"람다를 더 예쁜 인터페이스로 싸 넣은 도움자 함수."		return lambda event, f=f, args=args, kwds=kwds : f( *args, **kwds )

event_lambda 함수를 사용할 수 있으면, 이제 self.buttonHandler를 <Return> 키보드 사건에 묶고, 거기에 몇가지 인자를 건넬 수 있다. 다음은 그 방법이다:

	self.button1.bind("",		event_lambda( self.buttonHandler, button_name, 1, "Good stuff!" ) )

event_lambda의 작동 방식에 대하여 너무나 호기심이 넘친다면, button2에 대한 코드를 보는 것이 좀 더 보기에 쉽다.

button2에 대하여, 두-단계의 처리과정이 있다. 첫째는 event_lambda 함수를 호출하는 것이다.

	event_handler = event_lambda( self.buttonHandler, button_name, 2, "Bad  stuff!" )

event_lambda 함수가 호출되면, 람다를 사용하여 새로운 이름없는 ("익명의") 함수 객체를 만든다.

		lambda event, f=f, args=args, kwds=kwds : f( *args, **kwds )

그 이름 없는 함수 객체는 실제로 우리가 요청하고자 하는 함수( 다음 프로그램에서 "self.buttonHandler"인 "f")와 event_lambda 함수를 호출할 때 지정했던 인자들을 위한 포장자이다.

그러면 event_lambda 함수는 새로운 익명 함수를 돌려준다.

event_lambda가 익명 함수를 돌려주면, 거기에 이름을 붙인다: "event_handler"라고 말이다.

	event_handler = event_lambda( self.buttonHandler, button_name, 2, "Bad  stuff!" )

그 다음, 두 번째 단계에서 <Return> 사건을 "event_handler" 함수에 묶는다.

	self.button2.bind("", event_handler )

익명 함수에 대하여 '사건'은 그저 위치 보유자 인자일 뿐이며 버려져서 사용되지 않는다. 오직 위치 인자들 (args)과 키워드 인자들(kwds)만 버튼-처리자 루틴에 건네진다.

아이고! 머리털이 하얕게 다 세어 버렸어요!!

다음은 꼼수이다. 그러나 머리텉이 하얕게 다 세도록 그 작동 방식을 모조리 이해할 필요가 있다고 생각하지 말자. 사용하기 위해서 어떻게 "curry"와 "event_lambda"가 작동하는지 이해하려고 하지 말자. 그저 그것들을 블랙박스처럼 간주하고... 작동 방식에 신경쓰지 말고 그냥 쓰자.

Lambda 대 Curry 그리고 Event_lambda -- 어느 것을 사용해야 하는가?

자...

  • * curry와 event_lambda를 요청하는 코드가 상대적으로 직관적이며 짧고 간단하다.
    나쁜점은 그것들을 사용하려면 프로그램에 코드를 삽입하거나 반입해야 한다는 것이다.
  • * 대조적으로 람다는 파이썬에 내장되어 있다 -- 반입하기 위해 특별히 해야 할 것이 없다; 그냥 거기에 있을 뿐이다.
    약점은 그것을 사용하는 코드가 길고 약간 혼란스럽다는 것이다.

그래서 선택은 여러분의 몫이다. "자기돈 돈 가지고 자기가 사는데" 누가 뭐라고 할까. 가장 친숙한 것을 사용하자. 그리고 작업에 가장 적당하다고 여겨지는 것을 사용하자.

이 이야기가 주는 진정한 교훈은 다음과 같다...

파이썬은 강력한 언어이다. 파이썬은 수 많은 도구를 제공해 주어서 사건을 처리하는 역호출 함수를 만드는데 사용할 수 있다. "Tkinter로 생각하기"는 기본적인 개념을 소개하는 것이 목표이지, 테크닉의 백과사전이 되고자 하는 것이 아니다. 그래서 여기에서는 그런 방법들 중에 몇가지 만을 탐험해 볼 수 있다. 그러나 파이썬으로 기술이 익어갈 수록 자신감이 더 해지고 좀 더 유연성이 필요해짐에 따라, 파이썬에서 좀 고급의 특징을 사용하다 보면, 꼭 필요한 역호출 함수를 만들 수 있을 것이다.

프로그램의 행위

다음 프로그램을 실행하면, 앞의 프로그램과 정확하게 똑같이 행위한다. 프로그램의 행위를 하나도 바꾸지 않았다. 그냥 프로그램의 코딩 방식만 바뀌었을 뿐이다.

[revised: 2003-02-23]

프로그램 소스 코드 tt079.py

from Tkinter import *# ---------- code for class: curry (begin) ---------------------class curry:    """from Scott David Daniels'recipe    "curry -- associating parameters with a function"    in the "Python Cookbook"    http://aspn.activestate.com/ASPN/Python/Cookbook/    """    def __init__(self, fun, *args, **kwargs):        self.fun = fun        self.pending = args[:]        self.kwargs = kwargs.copy()    def __call__(self, *args, **kwargs):        if kwargs and self.kwargs:            kw = self.kwargs.copy()            kw.update(kwargs)        else:            kw = kwargs or self.kwargs        return self.fun(*(self.pending + args), **kw)# ---------- code for class: curry (end) ---------------------# ---------- code for function: event_lambda (begin) --------def event_lambda(f, *args, **kwds ):    """A helper function that wraps lambda in a prettier interface.    Thanks to Chad Netzer for the code."""    return lambda event, f=f, args=args, kwds=kwds : f( *args, **kwds )# ---------- code for function: event_lambda (end) -----------class MyApp:    def __init__(self, parent):        self.myParent = parent        self.myContainer1 = Frame(parent)        self.myContainer1.pack()        button_name = "OK"        # 명령어 묶기 -- 함수내포기법 사용        self.button1 = Button(self.myContainer1,           command = curry(self.buttonHandler, button_name, 1, "Good stuff!"))        # 사건 묶기 -- event_lambda 도움자 함수 사용        self.button1.bind("<Return>",            event_lambda( self.buttonHandler, button_name, 1, "Good stuff!" ) )        self.button1.configure(text=button_name, background="green")        self.button1.pack(side=LEFT)        self.button1.focus_force()  # Put keyboard focus on button1        button_name = "Cancel"        # 명령어 묶기 -- 함수내포기법 사용        self.button2 = Button(self.myContainer1,            command = curry(self.buttonHandler, button_name, 2, "Bad  stuff!"))        # 사건 묶기 -- event_lambda 도움자 함수를 두 단계로 사용        event_handler = event_lambda( self.buttonHandler, button_name, 2, "Bad  stuff!" )        self.button2.bind("<Return>", event_handler )        self.button2.configure(text=button_name, background="red")        self.button2.pack(side=LEFT)    def buttonHandler(self, argument1, argument2, argument3):        print "    buttonHandler routine received arguments:", \            argument1.ljust(8), argument2, argument3    def buttonHandler_a(self, event, argument1, argument2, argument3):        print "buttonHandler_a received event", event        self.buttonHandler(argument1, argument2, argument3)print "\n"*100 # 화면 정리print "tt079 프로그램 실행 준비."root = Tk()myapp = MyApp(root)print "사건 회돌이 시작 준비."root.mainloop()print "사건 회돌이 실행 완료."

조감 테크닉

지난 몇 프로그램에서, 사건 처리자를 창부품에 묶는 테크닉을 많은 시간을 들여 살펴 보았다.

다음 프로그램에서는 다시 GUI의 겉모습을 만드는 주제로 돌아가겠다 -- 창부품을 설정하고 그이 겉모습과 위치를 제어하는 문제로 되돌아 가보자.

Gui의 조감을 제어하는 세가지 테크닉

GUI의 일반적으로 조감을 통제하는데 세 가지 테크닉이 있다.

  • * 창부품 속성
  • * pack() 옵션
  • * 그릇(틀)의 내포

다음 프로그램에서는 창 속성과 pack() 옵션의 설정을 통하여 겉모습을 통제해보자.

수 많은 버튼과 작업할 생각이다. 그 버튼들을 담고 있는 틀과 함께 말이다. 앞 프로그램에서는 그 틀을 "myContainer1"이라고 불렀다. 여기에서는 그 이름을 약간 설명적인 이름인 "buttons_frame"으로 바꾸겠다.

다음 섹션에서 숫자는 소스 코드에 달린 주석의 번호를 가리킨다.

(1) 먼저, 모든 버튼이 너비가 같도록 하기 위해, "width" 속성에 모두 같은 값을 설정한다. "width" 속성은 Tkinter의 "Button" 창부품에 한정되는 것에 주목하자 -- 모든 창부품이 width 속성을 가지는 것은 아니다. 또 주목할 것은 width 속성이 문자 단위로 지정된다는 것이다 (예를 들어, 픽셀이나 인치 또는 밀리미터 단위가 아니다). 가장 긴 라벨인 ("Cancel")에 문자가 여섯개 담기므로, 버튼들에 대하여 그 너비를 "6"으로 지정했다. (1)

(2) 이제 버튼들에 패딩을 덧댄다. 패딩(Padding)은 텍스트와 버튼의 테두리 사이에 텍스트 주위를 둘러싼 여백이다. 버튼의 "padx" 속성과 "pady" 속성을 지정하면 된다. "padx"는 X-축을 따라 수평적으로 왼쪽에서 오른쪽으로 덧댄다. "pady"는 Y-축을 따라 수직적으로 위에서 아래로 덧댄다.

수평 패딩을 3 밀리리터로 지정하고 (padx="3m") 수직 패딩을 1 밀리미터로 지정하겠다 (pady="1m"). 주목하자. (수치인) "width" 속성과는 다르게, 이 속성들은 따옴표로 둘러 싸인다. 그 때문에 뒤에 접두사로 "m"을 붙여서 패딩 단위를 지정하는 것이다. 그래서 패딩 길이를 숫자보다는 문자열로 지정해야 한다.

(3) 마지막으로, 버튼이 든 그릇(buttons_frame)에 약간 패딩을 덧대어 보겠다. 그릇에는 네개의 패딩 속성을 지정할 수 있다. "padx"와 "pady"는 그 틀 주위 (바깥)을 둘러싼 패딩을 지정한다. "ipadx"와 "ipady"는 ("internal padx" 그리고 "internal pady") 내부의 패딩을 지정한다. 내부 패딩은 그 그릇 안에 든 각 창부품을 둘러싼 패딩이다.

틀에 속성으로 패딩을 지정하지 않고, 꾸림자에게 옵션을 건네고 지정하고 있음에 주목하자. (4). 보시다시피, 패딩은 약간 혼란스럽다. 틀은 내부 패딩이 있지만 버튼 같은 창부품은 내부 패딩이 없다. 어떤 경우에는 패딩이 창부품의 속성이지만, 다른 경우에는 pack()에 대한 옵션으로 지정된다.

프로그램의 행위

다음 프로그램을 실행하면, 두 개의 버튼이 보인다. 그러나 이제는 크기가 같다. 버튼의 옆면이 버튼-텍스트에 꽉 끼지 않는다. 그리고 버튼은 멋지게 테두리가 여백으로 둘러 싸여 있다.

[revised: 2003-02-23

프로그램 소스 코드 tt080.py

from Tkinter import *class MyApp:    def __init__(self, parent):        #------ 조감을 제어하기 위한 상수들 ------        button_width = 6      ### (1)        button_padx = "2m"    ### (2)        button_pady = "1m"    ### (2)        buttons_frame_padx =  "3m"   ### (3)        buttons_frame_pady =  "2m"   ### (3)        buttons_frame_ipadx = "3m"   ### (3)        buttons_frame_ipady = "1m"   ### (3)        # -------------- 상수 끝 ----------------        self.myParent = parent        self.buttons_frame = Frame(parent)        self.buttons_frame.pack(    ### (4)            ipadx=buttons_frame_ipadx,  ### (3)            ipady=buttons_frame_ipady,  ### (3)            padx=buttons_frame_padx,    ### (3)            pady=buttons_frame_pady,    ### (3)            )        self.button1 = Button(self.buttons_frame, command=self.button1Click)        self.button1.configure(text="OK", background= "green")        self.button1.focus_force()        self.button1.configure(            width=button_width,  ### (1)            padx=button_padx,    ### (2)            pady=button_pady     ### (2)            )        self.button1.pack(side=LEFT)        self.button1.bind("<Return>", self.button1Click_a)        self.button2 = Button(self.buttons_frame, command=self.button2Click)        self.button2.configure(text="Cancel", background="red")        self.button2.configure(            width=button_width,  ### (1)            padx=button_padx,    ### (2)            pady=button_pady     ### (2)            )        self.button2.pack(side=RIGHT)        self.button2.bind("<Return>", self.button2Click_a)    def button1Click(self):        if self.button1["background"] == "green":            self.button1["background"] = "yellow"        else:            self.button1["background"] = "green"    def button2Click(self):        self.myParent.destroy()    def button1Click_a(self, event):        self.button1Click()    def button2Click_a(self, event):        self.button2Click()root = Tk()myapp = MyApp(root)root.mainloop()

그릇의 내포

다음 프로그램에서는 그릇(틀)의 내포를 살펴보겠다. 일련의 틀을 만들고 서로 안에 내포시켜 넣어 보겠다: bottom_frame과 left_frame 그리고 big_frame을 말이다.

다음 틀에는 아무 것도 담기지 않는다 -- 어떤 창부품도 없다. 보통 틀은 신축적이기 때문에 줄어들어서 크기가 없다. 그러나 "height" 속성과 "width" 속성을 제공하면 최초 크기를 지정할 수 있다.

프레임 모두에 너비나 높이 아무 것도 지정하고 있지 않다. 예를 들면 myContainer1에 대하여, 아무 것도 지정하지 않았다. 그러나 그의 자손에는 너비와 높이를 제공했으므로, 그 그릇은 늘어나서 자손들의 축적된 높이와 너비에 맞게 적응한다.

나중 프로그램에서는 창부품들을 이런 틀에 배치하는 법을 살펴보겠다. 그러나 이 프로그램에서는 그냥 틀을 만들고 거기에 서로 다르게 크기와 위치 그리고 배경색을 주어보겠다.

나중에 다시 계속해서 나올 세 개의 틀에는 테두리를 툭 튀어 나오게 만들었다: bottom_frame과 left_frame 그리고 right_frame. 다른 틀들은 (예를 들면 top_frame과 buttons_frame에는) 테투리가 주어지지 않는다.

프로그램의 행위

이 프로그램을 실행하면, 서로 다른 틀들이 다른 배경색을 가지고 나타난다.

[revised: 2005-07-15]

프로그램 소스 코드 tt090.py

from Tkinter import *class MyApp:    def __init__(self, parent):        self.myParent = parent        ### Our topmost frame is called myContainer1        self.myContainer1 = Frame(parent) ###        self.myContainer1.pack()        #------ 조감을 제어하는데 필요한 상수들 ------        button_width = 6      ### (1)        button_padx = "2m"    ### (2)        button_pady = "1m"    ### (2)        buttons_frame_padx =  "3m"   ### (3)        buttons_frame_pady =  "2m"   ### (3)        buttons_frame_ipadx = "3m"   ### (3)        buttons_frame_ipady = "1m"   ### (3)        # -------------- 상수 끝 ----------------        ### myContainer1 안에 수직적 (위/아래) 동선을 사용하겠다.        ### myContainer1 안에 먼저 buttons_frame을 만든 다음.        ### top_frame과 bottom_frame을 만든다.        ### 다음이 그 데모 틀이다.        # 버튼 틀        self.buttons_frame = Frame(self.myContainer1) ###        self.buttons_frame.pack(            side=TOP,   ###            ipadx=buttons_frame_ipadx,            ipady=buttons_frame_ipady,            padx=buttons_frame_padx,            pady=buttons_frame_pady,            )        # 상단 틀        self.top_frame = Frame(self.myContainer1)        self.top_frame.pack(side=TOP,            fill=BOTH,            expand=YES,            )  ###        # 하단 틀        self.bottom_frame = Frame(self.myContainer1,            borderwidth=5,  relief=RIDGE,            height=50,            background="white",            ) ###        self.bottom_frame.pack(side=TOP,            fill=BOTH,            expand=YES,            )  ###        ### left_frame와 right_frame이라는 틀을 두 개 더         ### top_frame 안에 배치하겠다.        ### top_frame 안에 수평적 (좌/우) 동선을 사용한다.        # left_frame        self.left_frame = Frame(self.top_frame, background="red",            borderwidth=5,  relief=RIDGE,            height=250,            width=50,            ) ###        self.left_frame.pack(side=LEFT,            fill=BOTH,            expand=YES,            )  ###        ### right_frame        self.right_frame = Frame(self.top_frame, background="tan",            borderwidth=5,  relief=RIDGE,            width=250,            )        self.right_frame.pack(side=RIGHT,            fill=BOTH,            expand=YES,            )  ###        # 이제 버튼들을 buttons_frame에 추가한다        self.button1 = Button(self.buttons_frame, command=self.button1Click)        self.button1.configure(text="OK", background= "green")        self.button1.focus_force()        self.button1.configure(            width=button_width,  ### (1)            padx=button_padx,    ### (2)            pady=button_pady     ### (2)            )        self.button1.pack(side=LEFT)        self.button1.bind("<Return>", self.button1Click_a)        self.button2 = Button(self.buttons_frame, command=self.button2Click)        self.button2.configure(text="Cancel", background="red")        self.button2.configure(            width=button_width,  ### (1)            padx=button_padx,    ### (2)            pady=button_pady     ### (2)            )        self.button2.pack(side=RIGHT)        self.button2.bind("<Return>", self.button2Click_a)    def button1Click(self):        if self.button1["background"] == "green":            self.button1["background"] = "yellow"        else:            self.button1["background"] = "green"    def button2Click(self):        self.myParent.destroy()    def button1Click_a(self, event):        self.button1Click()    def button2Click_a(self, event):        self.button2Click()root = Tk()myapp = MyApp(root)root.mainloop()

꾸림자

창 크기를 조절하면 Tkinter와 작업할 때 곤혹스런 경험을 한다. 이런 상황을 상상해 보자. 반복적으로 개발할 것이라고 생각하고서, 처음에는 조심스럽게 틀의 너비와 높이를 지정한다. 테스트해 보고 작동하는지 확인한다. 그러면 다음 단계로 나아가 그 틀에 버튼을 몇개 붙인다. 다시 테스트해 보지만, 이제는 놀랍게도 Tkinter가 마치 틀에 처음부터 "높이"와 "너비"가 지정되지 않았다는 듯이 행동한다. 그리고 그 틀은 버튼을 짝 달라 붙어 감싼다.

무슨 일이 일어난 걸까 ???!!!

음, 꾸림자의 행위에 일관성이 없다. 또는 다음과 같이 말할 수도 있겠다: 꾸림자의 행위는 수 많은 상황적 요인에 따라 달라진다. 꾸림자는 그릇이 비어 있다면 크기 요청을 존중하지만, 그릇에 창부품이 담기면, 그릇의 신축성이 전면으로 나오게 된다 -- 그릇에 대한 "높이"와 "너비" 설정은 무시되며 그릇의 크기는 가능하면 밀접하게 창부품을 둘러싸도록 조정된다.

실제로는 창부품이 담기면 그릇의 크기를 제어할 수 없다.

제어할 수 있는 것은 전체 루트 창의 최초 크기이며, 이것은 창 관리자의 "geometry" 옵션으로 제어한다.

(1)

다음 프로그램에서는 geometry 옵션을 사용하여 작은 틀을 둘러싼 멋지고 큰 창을 만들어 보겠다.

(2) 다음 프로그램에서 사용하는 "title" 옵션이 창 관리자 메쏘드이기도 하다는 사실에 주목하자. "Title"은 창의 제목 막대에 있는 텍스트를 제어한다.

또 창 관리자 옵션은 선택적으로 앞에 "wm_" 접두사를 두어 지정할 수도 있다는 것에 주목하자. 예를 들어 "wm_geometry"와 "wm_title"과 같이 말이다. 다음 프로그램에서는 단지 두 방식 모두가 가능함을 보여주기 위해 "geometry"와 "wm_title"을 사용한다.

프로그램의 행위

다음 프로그램은 네 개의 창을 연이어서 띄운다.

주목하자. 창을 닫으려면 제목 막대의 오른쪽에 있는 상자 안에서 "X"라는 "close" 창부품을 눌러야 한다.

1 번의 경우, 높이와 너비가 지정되면 틀이 어떻게 보이는지 보여준다 -- 그리고 주목 -- 아무 창부품도 담겨 있지 않다.

2 번의 경우, 창부품이 추가되면 정확하게 같은 객체가 어떻게 보이는지 보여준다 (우리의 경우 버튼이 세개 붙는다). 틀이 짝 줄어들어서 세개의 버튼에 들러 붙는다.

3 번의 경우, 빈 틀이 어떻게 보이는지 또 보여준다. 단 이번에는 geometry 옵션을 사용하여 창의 크기를 전체적으로 제어한다. 창의 커다란 회색 필드 안에 틀의 작은 파란색 배경이 보인다.

4 번의 경우, 버튼을 세 개 가진 틀을 볼 수 있는데, 이 번에는 틀의 크기를 geometry 옵션으로 지정한다. 창의 크기가 3 번의 경우와 같음에 주목하자. 그러나 (2 번의 경우와 같이) 틀은 버튼 주위로 들러 붙고, 틀의 파랑 배경이 전혀 보이지 않는다.

[revised: 2002-10-01]

프로그램 소스 코드 tt095.py

from Tkinter import *class App:    def __init__(self, root, use_geometry, show_buttons):        fm = Frame(root, width=300, height=200, bg="blue")        fm.pack(side=TOP, expand=NO, fill=NONE)        if use_geometry:            root.geometry("600x400")  ### (1) Note geometry Window Manager method        if show_buttons:            Button(fm, text="Button 1", width=10).pack(side=LEFT)            Button(fm, text="Button 2", width=10).pack(side=LEFT)            Button(fm, text="Button 3", width=10).pack(side=LEFT)case = 0for use_geometry in (0, 1):    for show_buttons in (0,1):        case = case + 1        root = Tk()        root.wm_title("Case " + str(case))  ### (2) Note wm_title Window Manager method        display = App(root, use_geometry, show_buttons)        root.mainloop()

꾸림 옵션

다음 프로그램에서는 틀 안에서 조감을 통제하는 여러 pack() 옵션을 살펴보겠다:

  • * side
  • * fill
  • * expand
  • * anchor

다음 프로그램은 이 시리즈에서의 다른 프로그램과 다르다. 다시 말해, 어떤 특징을 어떻게 코딩했는지 이해하기 위해 소스 코드를 읽을 필요가 없다. 단지 프로그램을 실행하기만 하면 된다.

이 프로그램의 목적은 꾸림 옵션의 결과를 보여주는 것이다. 프로그램을 실행하면 다양한 꾸림 옵션을 설정하고 다양하게 옵션을 조합한 효과를 관찰하실 수 있다.

꾸림 옵션 아래에 숨겨진 개념들

그릇 안에서 (즉, 틀로) 창부품의 모습을 어떻게 제어하는지 알고 싶다면, 꾸림 위치 관리자가 정렬 방법으로 "cavity" 모델을 사용한다는 것을 기억해야 한다. 다시 말해, 각 그릇에는 공간이 담겨 있고, 그 그릇에 창부품들을 꾸려 넣는다.

한 그릇 안에서 구성요소의 표현과 위치에 관하여 언급하려면, 세 가지 개념을 이해하는게 좋다:

  • * 요구되지 않은 공간 (즉, cavity)
  • * 요구되었지만 사용되지 않은 공간
  • * 요구되었고 사용된 공간

버튼 같은 창부품을 꾸려 넣으면, 언제나 공간의 네 모서리중 하나를 따라서 꾸러진다. pack "side" 옵션은 어느 모서리를 사용할지 지정한다. 예를 들어, "side=LEFT"라고 지정했다면, 그 창부품은 공간의 왼쪽 모서리에 꾸려 넣어진다 (즉 위치를 잡는다).

창부품이 한 모서리를 따라 꾸려 넣어질 때, 전체 모서리를 요구한다. 실제로 요구한 공간을 모두 사용하지는 않을 지라도 말이다. X라고 부르는 작은 버튼을 다음 다이어그램과 같이 커다란 공간의 왼쪽 모서리를 따라 꾸려 넣어 보자.

요구 미사용공간 (미요구)
요구 사용됨(X)
요구 미사용

(요구되지 않은 지역인) 공간은 이제 그 창부품의 오른쪽으로 간다. 창부품 X는 왼쪽 모서리 전체를 요구한다. 한 줄이면 들어가기에 충분히 넓은 공간이다. 그러나 창부품 X는 작기 때문에, 실제로는 오직 요구한 전체 영역에서 작은 부분만 사용한다. 그 작은 부분만 자신을 나타내기 위해 사용한다.

보시다시피, 창부품 X는 자신을 표시하기에 필요한 만큼만 공간을 요구했다. 옵션으로 "expand=YES"을 지정하면, 가능한 모든 지역을 요구한다. 공간의 어느 부분도 요구되지 않은 지역으로 남지 않는다. 그렇다고 하더라도 그것이 창부품 X가 전 영역을 *사용할 것이라는* 뜻은 아니다. 여전히 필요한 만큼만 작은 부분을 사용할 것이다.

창부품이 자신이 사용할 공간보다 더 많이 요구하면, 두 가지 선택이 있다:

  • * 요구되지 않은 공간으로 이동할 수 있거나,
  • * 또는 요구되지 않은 공간으로 자랄 수 있다.

요구되지 않은 공간을 사용하도록 창부품을 키우고 싶다면, "fill" 옵션을 사용하면 되는데, 이 옵션은 창부품에게 사용되지 않은 공간을 채울만큼 커질 수 있는지 그리고 어느 방향으로 자랄 수 있는지 알려준다.

  • * "fill=NONE" 커질 수 없다는 뜻이다.
  • * "fill=X" X-축을 따라 (즉, 수평적으로) 클 수 있다는 뜻이다.
  • * "fill=Y" Y-축을 따라 (즉, 수직적으로) 클 수 있다는 뜻이다.
  • * "fill=BOTH" 수직 수평으로 모두 자랄 수 있다는 뜻이다.

요구되지 않은 공간에 이동시키고 싶다면, "anchor" 옵션을 사용하면 된다. 이 옵션은 창부품에게 요구된 공간에서 어디에 위치할지 알려준다. anchor 옵션의 값은 나침반과 같다. "N"은 "north"를 의미하며 (즉, 요구된 지역의 상단 중앙에 위치한다). "NE"는 "northeast"를 의미하며 (즉 요구된 지역의 우상 모서리에 위치함), "CENTER"는 요구된 지역의 바로 중앙에 위치한다는 뜻이다. 등등.

프로그램 실행하기

이제, 프로그램을 실행하자. 코드를 읽을 필요가 없다. 그냥 프로그램을 실행하고 세 개의 데모 버튼으로 다양하게 꾸림 옵션을 실험해 보자.

버튼 A의 틀에서는 수평으로 공간이 흐른다 -- 틀이 버튼보다 높이가 크지 않다.

버튼 B의 공간은 수직으로 흐른다 -- 틀이 버튼보다 너비가 넓지 않다.

그리고 버튼 C의 틀에서는 공간이 거대하다 -- 버튼보다 높이 너비 모두 크다 -- 놀기에 넓다.

특정 설정 아래에서 버튼의 모습이 조금이라도 이상하게 보인다면, 왜 그 버튼이 그렇게 보이는지 추측해 보자.

그리고 마지막으로....

유용한 디버깅 팁

꾸려 넣기는 복잡한 일이다. 다른 창부품과 관련하여 창부품의 위치를 결정하는 일은 부분적으로 먼저 꾸려진 다른 창부품들이 어떻게 꾸려 넣어졌는가에 달려 있기 때문이다. 다시 말해, 다른 창부품들이 왼쪽을 기준으로 꾸려 넣어졌으면, 다음 창부품 안에 꾸려 넣어질은 공간은 오른쪽에 꾸려 넣어질 것이다. 그러나 공간의 위쪽에 꾸려 넣어졌다면, 다음 창부품이 꾸려 넣어질 공간은 그 아래가 될 것이다. 모두 아주 혼란스럽다.

다음은 유용한 디버깅 팁이다. 조감을 배치하다가 문제에 부딛쳤다면 -- 즉 예상대로 일이 진행되지 않으면 -- 각 그릇에 (즉, 각 틀에) 배경색을 다르게 지정하자. 예를 들어:

  • bg="red" 이나
  • 또는 bg="cyan"
  • 또는 bg="tan"

...또는 노랑이나 파랑, 또는 빨강 등등으로 말이다.

이렇게 하면 틀들이 실제로 어떻게 정렬되는지 볼 수 있다. 눈에 보이는 것들이 문제를 해결하는 실마리가 되는 경우가 많다.

[revised: 2004-04-26]

프로그램 소스 코드 tt100.py

from Tkinter import *class MyApp:    def __init__(self, parent):        #------ 버튼의 조감을 제어하는데 사용되는 상수들 ------        button_width = 6        button_padx = "2m"        button_pady = "1m"        buttons_frame_padx =  "3m"        buttons_frame_pady =  "2m"        buttons_frame_ipadx = "3m"        buttons_frame_ipady = "1m"        # -------------- 상수 끝 ----------------        # Tkinter 변수들을, 라디오 버튼이 통제하도록 설정한다        self.button_name   = StringVar()        self.button_name.set("C")        self.side_option = StringVar()        self.side_option.set(LEFT)        self.fill_option   = StringVar()        self.fill_option.set(NONE)        self.expand_option = StringVar()        self.expand_option.set(YES)        self.anchor_option = StringVar()        self.anchor_option.set(CENTER)        # -------------- 상수 끝 ----------------        self.myParent = parent        self.myParent.geometry("640x400")        ### 최상위 틀은 myContainer1이라고 불리운다        self.myContainer1 = Frame(parent) ###        self.myContainer1.pack(expand=YES, fill=BOTH)        ### myContainer1 안에 수평적 (좌/우) 동선을 사용하겠다.        ### myContainer1 안에 control_frame과 demo_frame을 만든다.        # 제어 틀 - 기본적으로 데모 틀을 제외하면 이게 본체이다.        self.control_frame = Frame(self.myContainer1) ###        self.control_frame.pack(side=LEFT, expand=NO,  padx=10, pady=5, ipadx=5, ipady=5)        # control_frame 안에 헤더 라벨과         # 상단에 buttons_frame 그리고        # 하단에 demo_frame을 만든다        myMessage="This window shows the effects of the \nexpand, fill, and anchor packing options.\n"        Label(self.control_frame, text=myMessage, justify=LEFT).pack(side=TOP, anchor=W)        # 버튼 틀        self.buttons_frame = Frame(self.control_frame) ###        self.buttons_frame.pack(side=TOP, expand=NO, fill=Y, ipadx=5, ipady=5)        # 데모 틀        self.demo_frame = Frame(self.myContainer1) ###        self.demo_frame.pack(side=RIGHT, expand=YES, fill=BOTH)        ### 데모 틀 안에 top_frame과 bottom_frame을 만든다.        ### 이 틀들이 데모 틀이 된다.        # 상단 틀        self.top_frame = Frame(self.demo_frame)        self.top_frame.pack(side=TOP, expand=YES, fill=BOTH)  ###        # 하단 틀        self.bottom_frame = Frame(self.demo_frame,            borderwidth=5, 	relief=RIDGE,            height=50,            bg="cyan",            ) ###        self.bottom_frame.pack(side=TOP, fill=X)        ### 이제 top_frame 안에 left_frame과 right_frame이라는        ### 틀을 두 개 더 배치하겠다. top_frame 안에        ### 수평적 (좌/우) 동선을 사용하겠다.        # 좌측 틀        self.left_frame = Frame(self.top_frame,	background="red",            borderwidth=5, 	relief=RIDGE,            width=50,            ) ###        self.left_frame.pack(side=LEFT, expand=NO, fill=Y)        ### 우측 틀        self.right_frame = Frame(self.top_frame, background="tan",            borderwidth=5, 	relief=RIDGE,            width=250            )        self.right_frame.pack(side=RIGHT, expand=YES, fill=BOTH)        # 이제 해당 틀에 각각 버튼을 배치한다.        button_names = ["A", "B", "C"]        side_options = [LEFT, TOP, RIGHT, BOTTOM]        fill_options = [X, Y, BOTH, NONE]        expand_options = [YES, NO]        anchor_options = [NW, N, NE, E, SE, S, SW, W, CENTER]        self.buttonA = Button(self.bottom_frame, text="A")        self.buttonA.pack()        self.buttonB = Button(self.left_frame, text="B")        self.buttonB.pack()        self.buttonC = Button(self.right_frame, text="C")        self.buttonC.pack()        self.button_with_name = {"A":self.buttonA, "B":self.buttonB, "C":self.buttonC}        # 이제 the buttons_frame의 하부틀을 몇 개 만든다.        self.button_names_frame   = Frame(self.buttons_frame, borderwidth=5)        self.side_options_frame   = Frame(self.buttons_frame, borderwidth=5)        self.fill_options_frame   = Frame(self.buttons_frame, borderwidth=5)        self.expand_options_frame = Frame(self.buttons_frame, borderwidth=5)        self.anchor_options_frame = Frame(self.buttons_frame, borderwidth=5)        self.button_names_frame.pack(  side=LEFT, expand=YES, fill=Y, anchor=N)        self.side_options_frame.pack(  side=LEFT, expand=YES, anchor=N)        self.fill_options_frame.pack(  side=LEFT, expand=YES, anchor=N)        self.expand_options_frame.pack(side=LEFT, expand=YES, anchor=N)        self.anchor_options_frame.pack(side=LEFT, expand=YES, anchor=N)        Label(self.button_names_frame, text="\nButton").pack()        Label(self.side_options_frame, text="Side\nOption").pack()        Label(self.fill_options_frame, text="Fill\nOption").pack()        Label(self.expand_options_frame, text="Expand\nOption").pack()        Label(self.anchor_options_frame, text="Anchor\nOption").pack()        for option in button_names:            button = Radiobutton(self.button_names_frame, text=str(option), indicatoron=1,                value=option, command=self.button_refresh, variable=self.button_name)            button["width"] = button_width            button.pack(side=TOP)        for option in side_options:            button = Radiobutton(self.side_options_frame, text=str(option), indicatoron=0,                value=option, command=self.demo_update, variable=self.side_option)            button["width"] = button_width            button.pack(side=TOP)        for option in fill_options:            button = Radiobutton(self.fill_options_frame, text=str(option), indicatoron=0,                value=option, command=self.demo_update, variable=self.fill_option)            button["width"] = button_width            button.pack(side=TOP)        for option in expand_options:            button = Radiobutton(self.expand_options_frame, text=str(option), indicatoron=0,                value=option, command=self.demo_update, variable=self.expand_option)            button["width"] = button_width            button.pack(side=TOP)        for option in anchor_options:            button = Radiobutton(self.anchor_options_frame, text=str(option), indicatoron=0,                value=option, command=self.demo_update, variable=self.anchor_option)            button["width"] = button_width            button.pack(side=TOP)        self.cancelButtonFrame = Frame(self.button_names_frame)        self.cancelButtonFrame.pack(side=BOTTOM, expand=YES, anchor=SW)        self.cancelButton = Button(self.cancelButtonFrame,            text="Cancel", background="red",            width=button_width,            padx=button_padx,            pady=button_pady            )        self.cancelButton.pack(side=BOTTOM, anchor=S)        self.cancelButton.bind("<Button-1>", self.cancelButtonClick)        self.cancelButton.bind("<Return>", self.cancelButtonClick)        # 버튼에 초기 위치를 설정한다        self.demo_update()    def button_refresh(self):        button = self.button_with_name[self.button_name.get()]        properties = button.pack_info()        self.fill_option.set  (  properties["fill"] )        self.side_option.set  (  properties["side"] )        self.expand_option.set(  properties["expand"] )        self.anchor_option.set(  properties["anchor"] )    def demo_update(self):        button = self.button_with_name[self.button_name.get()]        button.pack(fill=self.fill_option.get()            , side=self.side_option.get()            , expand=self.expand_option.get()            , anchor=self.anchor_option.get()            )    def cancelButtonClick(self, event):        self.myParent.destroy()root = Tk()myapp = MyApp(root)root.mainloop()

한글판 johnsonj 2008.04.11

'파이썬 프로그래밍' 카테고리의 다른 글

tkinter 소개  (0) 2011.09.04
Tkinter GUI 프로그래밍  (0) 2011.04.24
TKINTER 요약  (0) 2011.04.24
UltraEdit Python 설정  (0) 2009.06.28
파이썬 데몬 만들기  (0) 2009.04.07

[원문] http://coreapython.hosting.paran.com/GUI/TkinterSummary.html

Tkinter 요약

작성 러셀 오웬(Russell Owen).
한글판 johnsonj 2008.04.19 토

이 글은 Tkinter를 위한 간편 참조서이다. 이 글의 목표는 적절한 참고 재료를 보충하기 위한 것이지, 대체하고자 하는 것이 아니다. 단순한 것에서부터 비밀스러운 것까지 모두 다룬다. 본인이 필요한 정보를 찾으면서 힘들다고 생각했기 때문에 새로운 주제를 덧붙이고자 한다.

기본적인 Tk 어플리케이션

import Tkinter	root = Tkinter.Tk()	# 인터페이스를 설정하고, 시작:	root.mainloop()

표준 창부품들

기본 창부품: Toplevel, Frame, Button, Checkbutton, Entry, Label, Listbox, OptionMenu, Photoimage, Radiobutton, Scale.

고급 창부품: Canvas, Text. 이 창부품들의 내용물에 이름표를 붙이면 객체처럼 행위한다.

고난도 창부품: Menu, Menubutton, Scrollbar.

Tk 변수들: StringVar, IntVar, DoubleVar, BooleanVar. 특정한 위젯에 필요한 데이터를 담아 놓은 그릇으로서, 다른 객체들이 자신의 내용에 쉽게 접근할 수 있어서 편리하며 자신의 데이터가 바뀌면 역호출을 촉발시킬 수 있다.

기타 모듈들 ("import Tkinter"로 적재되지 않음)

tkFont에는 Font 클래스가 정의되어 있어서 폰트를 구성할 수 있고 폰트의 이름들을 얻을 수 있다. 한 창부품의 폰트를 Font 객체에 설정하고, Font를 바꾸면 바로바로 창부품이 갱신된다.

FileDialog에는 FileDialog, LoadFileDialog, SaveFileDialog이 정의되어 있다. 다음은 그 예이다:

fdlg = FileDialog.LoadFileDialog(root, title="Choose A File")	fname = fdlg.go() # 선택 인자들: dir_or_file=os.curdir, pattern="*", default="", key=None)	if file == None: # 사용자 취소함

tkColorChooser에는 askcolor(initialcolor)이 정의되어 있는데, 이 메쏘드는 사용자가-선택한 색상을 돌려준다.

tkSimpleDialog에는 askinteger(title, prompt, initialvalue, minvalue, maxvalue), askfloat 그리고 askstring이 정의되어 있다.

소스 코드를 조사해 보자. .../Lib/lib-tk에서 다른 사용법들을 찾아보자.

위치 관리

꾸림자

pack(side="top/right/bottom/left", expand=0/1, anchor="n/nw/w...", fill="x/y/both")

  • 기본적으로, 창부품은 꾸러미의 가운데 위치한다; 닻(anchor)을 사용하여 이것을 변경하자.
  • 기본적으로, 창부품은 꾸러미를 채우도록 커지지 않는다; expand와 fill을 사용하여 이를 변경하자.

격자 관리자

grid(row, column, rowspan=?, columnspan=?, sticky="news", ipadx=?, ipady=?, padx=?, pady=?)

rowconfigure(row, weight=?, minsize=?, pad=?)

columnconfigure(column, weight=?, minsize=?, pad=?)

  • 각 행과 열은 자신 안에서 가장 작은 요소에 크기가 맞도록 줄어든다.
  • 기본적으로, 창부품은 셀안에서 가운데에 위치하고 셀을 다 채우도록 커지지 않는다; sticky를 사용하여 이를 바꾸자.
  • 기본적으로 행과 열은 사용자가 창의 크기를 변경하더라도 커지지 않는다; 0이 아닌 값을 rowconfigure와 columnconfigure를 사용하여 설정하면 이를 바꿀 수 있다.
  • Rowconfigure와 columnconfigure는 또한 일반적인 문제를 해결하는데에도 도움이 된다: 늘이기(spanning)는 행과 열의 크기를 증가시킨다. 너비가 각각 20 픽셀 30 픽셀인 컬럼이 있다고 해보자. 또 기존의 컬럼을 더 키우지 않고 "넓은 창부품"을 100 픽셀 너비로 격자처리하고 싶다고 해보자. 단순히 두 컬럼을 차지하도록 격자처리하면 두 창부품이 함께 총 100 픽셀만큼 늘어난다 (25 픽셀을 더 얻어서, 각각 45픽셀 55 픽셀이다). 넓은 객체가 컬럼을 두 개 차지하도록 (남는 픽셀들을 다른 여러 컬럼에 분산시켜서) 격자 처리하면 문제가 줄어들지만 문제가 해결되지는 않는다. 해결책은 넓은 창부품이 추가로 적어도 한개의 컬럼만큼 더 늘어나게 하고 columnconfigure를 사용하여 그 늘어난 컬럼에 비율(weight)을 1이 되도록 설정하는 것이다.

사건과 호출

사건

사건은 문자열로 기술된다: "<modifiers-type-qualifier>".

사건 유형은 다음과 같다:

  • mouse (수식키 B1, 등등. 어느 마우스 버튼인지 지정한다):
    • ButtonPress (약자로 Button으로 표기, 수식자 Double)
    • ButtonRelease
    • Enter
    • Leave
    • Motion
  • keyboard (수식키에는 Control, Shift, Meta...등등 포함)
    • KeyPress (약자로 Key로 표기)
    • KeyRelease
  • window (Tk에서 창은 창부품이다):
    • Configure (창 크기 조절, 이동, 등등.)
    • Expose
    • FocusIn
    • FocusOut
    • Map
    • Unmap

경고: 어느 창부품이 어느 사건을 볼지는 플랫폼에 따라 달라진다.

KeyPressd와 KeyRelease에 대한 자격부여자는 keysyms이다. 문자와 숫자는 그대로 사용된다. 그러나 구두점과 기타 모든 키는 특별한 이름이 대소문자에 민감하게 사용되는데 여기에는: comma, period, dollar, asciicircum, numbersign, exclam, Return, Escape, BackSpace, Tab, Up, Down, Left, Right... 등등이 포함됨. 잘 모르겠으면, 아래에 주어진 예와 같이 대화 쉘에서 테스트해 보자.

창부품이 사건을 볼 때 역호출을 촉발시키려면, bind를 사용하자:
widget.bind(event, callback)

  • 역호출은 정확하게 인자를 event 하나만 받는다:
  • 사건에는 다음과 같은 속성이 포함된다 (완벽한 목록이 아님):
    • keysym: the keysm, 위에 기술한 바와 같이 (KeyPress 사건과 KeyRelease 사건)
    • height, width (구성 사건)
    • serial: 사건의 일련 번호 (모든 사건)
    • type: 사건 유형을 나타내는 숫자 (모든 사건)
    • time: 사건이 일어난 시간 (모든 사건)
    • widget: 사건을 받은 창부품 (모든 사건)
    • x, y: 창부품에 대한 마우스 포인터의 상대 위치 (마우스 사건과 거의 모든 사건)
    • x_root, y_root: 루트 창에 대한 마우스 포인터의 상대 위치 (마우스 사건과 거의 모든 사건)
  • 사건 전파를 막으려면, 역호출에서 "break"라는 문자열을 돌려주자; 기본 창부품의 행위를 오버라이드 하고 싶다면 필요할 것이다.
  • 다른 유형/수준의 묶기가 있다. 예를 들면 한 사건을 특정 클래스의 모든 실체에 묶을 수 있다 (예를 들어, 모든 버튼). 더 자세한 정보는 Tkinter 개론 참조하자.

다음은 키 눌림에 대하여 키 심볼을 보여주는 예제이다:

	#!/usr/local/bin/Python	"""타자할 때마다 각 키눌림에 대하여 keysym을 보여준다."""	import Tkinter		root = Tkinter.Tk()	root.title("Keysym Logger")		def reportEvent(event):	print 'keysym=%s, keysym_num=%s' % (event.keysym, event.keysym_num)		text  = Tkinter.Text(root, width=20, height=5, highlightthickness=2)		text.bind('<KeyPress>', reportEvent)		text.pack(expand=1, fill="both")	text.focus_set()	root.mainloop()

프로토콜 처리자 WM_DELETE_WINDOW

사건 역호출 파괴는 너무 늦게 촉발되기 때문에 창부품을 미처 청소하지 못하거나 창 파괴를 막을 수 없다. 이렇게 하려면 WM_DELETE_WINDOW 프로토콜 처리자를 사용하자. (그러나 어플리케이션이 종료할 때 청소하려고 한다면, 대신에 표준 파이썬 라이브러리에 있는 "atexit"을 사용하자; 그게 더 간단하다!) 이렇게 하면 기본 행위가 바뀐다. 그러므로 창을 없애고 싶다면 손수 파괴해야 한다. 역호출 함수는 아무 인자도 받지 않는다.

toplevel.protocol("WM_DELETE_WINDOW", callback)

두가지 프로토콜이 있다: WM_SAVE_YOURSELF와 WM_TAKE_FOCUS가 그것인데 본인도 그것이 무엇인지 잘 모른다.

창부품 명령어들

버튼 같은 창부품은 "command" 매개변수를 지원한다. 이 역호출은 인자를 받지 않는다. 이런 방법은 버튼이 눌렸을 때 역호출을 촉발시키기에는 별로 좋은 방법이 아니다. 왜냐하면 같은 일을 하는데 단 한번의 마우스 사건이란 없기 때문이다. "command" 역호출은 역호출 함수에게 아무 데이터도 보내지 않기 때문에 문제가 된다. 그러나 역호출 싸개를 이용하면 쉽게 상황을 바로 잡을 수 있다.

변수 추적하기

Tk 변수가 변하면 역호출을 촉발시키려면, trace_variable을 사용하자:
traceName = tkvar.trace_variable(mode, callback)

  • Mode는 "r"이나 "w" 또는 "u"중 하나이다 (read, write or update)
  • 역호출 함수는 인자를 세개 받는다: varName (Tk 변수의 이름), index (언제나 ''?), mode
  • 역호출 함수는 그 변수의 내용물을 바꿀 수 있다. 또다른 역호출 함수를 촉발시키지 않고서도 말이다.
  • 이름으로 변수를 얻거나 수정하려면, anywdg.getvar(varName)나 anywdg.setvar(varName, value)를 사용하자
  • 역호출 함수를 삭제하려면, tkvar.trace_delete(traceName)를 사용하자
  • 더 자세한 정보는 Tkinter Folklore를 참조하자.

After: 시간을 설정한 사건과 애니메이션

예를 들어 애니메니션을 위하여, 일정시간을 지연시킨후 역호출을 촉발시키려면, after를 사용하자:
widget.after(timems, callback, arg1, arg2...)

  • arg1, arg2... 는 0개 이상의 선택적인 추가 인자들을 뜻한다
  • 역호출함수는 지정된 것만, 선택적인 인자들을 추가로 받는다.
  • 역호출 함수는 일정시간 후에 호출될때 단 한번만 호출된다.

파일/소켓 처리자

문제는 사건 처리자를 묶지 않고서 어떻게 소켓을 통하여 통신하는가이다 (특히 데이터를 읽는 법). 그렇지 못하다면 아주 비효율적이다. 선택은 여러가지이다:

파일 처리자

파일이나 소켓에 읽거나 쓸 데이터가 있으면 파일 처리자는 역호출 함수를 부를 것이다. 파일 처리자는 간단하며 Tkinter에 통합이 잘 되어 있다. 불행하게도 윈도우즈에서는 작동하지 않는다 (적어도 Python 2.3.4 그리고 Windows XP에서는 그렇다). 그러나 프로그램이 unix 또는 MacOS X에서 실행된다면, 고려해볼 가치가 있는데 너무 사용하기 쉽기 때문이다.

wdg.tk.createfilehandler(file_or_socket, mask, callback)
wdg는 Tkinter 창부품이면 아무거나 된다 (당장 쓸 것이 없다면, 새로 만들어 보자).

  • Mask: 다음을 OR 연산한 것: Tkinter.READABLE, Tkinter.WRITABLE, Tkinter.EXCEPTION
  • 역호출함수는 인자를 정확하게 두 개 받는다: file_or_socket, mask
  • 파일은 처리자를 오직 하나만 가질 수 있다. createfilehandler를 다시 호출하더라도 그냥 예전 처리자를 대체한다.
  • 처리자를 아주 제거하려면, deletefilehandler(file_or_socket)를 호출하자
  • 잠깐만 처리자를 제거하려면, createfilehandler(file_or_socket, None)를 호출하자. 파일 처리자를 삭제하고 다시 만드는 것보다 이 편이 더 좋다.
  • 2003-05-06에 표기법을 바꾸었다. 예전에 tkinter를 사용했던 방법을 바꾸었다; 예전 방법은 파이썬 2.2.x에서는 잘 작동했지만 파이썬 2.3에서는 망가졌다 (적어도 tcl이 쓰레드를 지원하도록 구축되었다면 말이다). 새로운 방법은 파이썬 2.3과 2.2.2와 잘 작동하며 아마도 그 보다 이전 버전이라도 잘 작동할 것 같다.

Tcl 소켓

Tcl 소켓은 완벽하게 크로스-플랫폼을 지원한다. 그러나 tcl 코드가 좀 필요하기 때문에 약간 사용하기 어렵다. 그래도, 아주 어려운 것은 아니며 완벽한 크로스-플랫폼 코드를 얻기 위해 노력해 볼 가치가 있다. tcl 소켓을 사용하는 방법에 관한 예를 보려면, 본인의 RO 꾸러미를 내려받아 RO.Comm.TkSocket을 살펴보자. 또 다음 매튜 신세라(Matthew Cincera)가 게시한 글도 참조하다. 본인도 이 글을 읽고 시작했다 (스테판 빌랜드(Stephane Beland)에게 감사함).

Twisted 작업틀

Twisted 작업틀은 자유 라이브러리로서 플랫폼에 독립적이며 여러 다양한 GUI 툴킷과 작동한다 (실제로는 GUI 툴킷이 전혀 요구하지 않는다). 평판이 좋다. 솔직히 직접 사용해 본 바가 없지만, 언젠가는 옮겨갈 생각이다.

역호출 싸개 (함수내포 함수)

정상적으로 주어지는 데이터 말고도, 데이터를 추가로 역호출 함수에 보내고 싶은 경우가 있다. 예를 들면 버튼 창부품은 자신의 명령어 역호출에 전혀 인자를 보내지 않는다. 그러나 역호출 함수 하나를 여러 버튼에 사용하고 싶다면, 어느 버튼이 눌렸는지 알아야 할 필요가 있다.

이를 처리하는 방법은 먼저 역호출 함수를 정의하고 나서 그 역호출 함수를 창부품에 건네고 기타 필요한 정보를 포함시키는 것이다. 불행하게도 대부분의 언어들처럼 파이썬은 (함수가 정의될 때 정보가 알려지는) 이른 묶기와 (함수가 호출될 때 정보가 알려지는) 나중 묶기를 혼용하면 제대로 처리하지 못한다. 개인적으로 쉽고 깔끔한 해결책을 제시하라면 다음과 같다:

  • 원하는 데이터를 모두 인자로 취하도록 역호출 함수를 작성하자.
  • 역호출 싸개 클래스를 사용하여 호출가능 객체를 만들자. 이 객체에 함수와 기타 인자들이 저장되며 호출될 때 제대로 일을 한다. 다른 말로 하면, 저장된 데이터와 거기에 호출자가 공급한 데이터로 함수가 호출된다.

아래에 보여주는 예제가 이해에 도움이 되었으면 한다.

여기에서 사용한 역호출 싸개는 RO.Alg.GenericCallback에 있는데, 이는 RO 꾸러미에서 얻을 수 있다. 키워드 인자를 처리하지 못하지만 간단하게 만든 버전을 아래에 예제로 제시한다. 모든 shim 코드는 기반이 스코트 데이비드 다니엘스(Scott David Daniels)가 만든 파이썬 요리법인데, 그는 이를 "함수 내포시키기(currying a function)"라고 부른다 (아무래도 이 용어가 "역호출 싸개(callback shim)" 보다 더 일반적인 것 같다).

	#!/usr/local/bin/Python	"""역호출 싸개의 사용법을 보여주는 예제"""	import Tkinter		def doButton(buttonName):	"""내가 원하는 역호출 함수. 역호출 싸개가 필요하다	Button 명령어 역호출에서는 인자를 아무것도 받지 않기 때문이다.	"""	print buttonName, "pressed"		class SimpleCallback:	"""역호출 싸개를 만들어라. 스콧 다니엘스(Scott David Daniels)가 만든 코드에 기반함	(이 코드는 키워드 인자도 처리함).	"""	def __init__(self, callback, *firstArgs):		self.__callback = callback		self.__firstArgs = firstArgs		def __call__(self, *args):		return self.__callback (*(self.__firstArgs + args))		root = Tkinter.Tk()		buttonNames = ("Button 1", "Button 2", "Button 3")	for name in buttonNames:	callback = SimpleCallback(doButton, name)	Tkinter.Button(root, text=name, command=callback).pack()		root.mainloop()

인덱싱

Text 창부품

  • "line.col" line = 줄 번호, 1부터 시작 (!); col = 컬럼 # 0부터 시작함
  • "current" 마우스 포인터 아래에 있는 문자
  • "insert" 커서 뒤의 문자
  • "end" 마지막 문자 바로 다음까지
  • "@x,y" 지정된 화면 좌표 아래에 있는 문자
  • "tag.first", "tag.last" 지정된 태그의 첫 문자에서부터 그 이후의 마지막 문자까지

다음 문자열을 추가하면 바꿀 수 있다:

  • " +/- n chars"
  • " +/- n lines"
  • " linestart"
  • " linend"
  • " wordstart"
  • " wordend"

예를 들어 "1.0 lineend"는 첫 줄의 끝까지 가리킨다

Entry 창부품

  • 정수는 0부터 시작함 (또는 문자열 표현)
  • "end", "insert", "@x,y" (위의 Text 창부품 참조)
  • "anchor" (또다른 선택 방법), "sel.first", "sel.last"

창부품으로부터 정보 얻기

창부품으로부터 구성 정보를 열람하는 방법은 여러가지가 있다:

  • 창부품을 사전처럼 취급하자: aWidget["text"]. (주의: 이 방법은 정보를 설정하고 얻는데 모두 작동한다.)
  • cget 메쏘드를 사용하자: aWidget.cget("text").
  • 인자 없이 configure 메쏘드를 호출하면 한꺼번에 사전으로 받을 수 있다: aWidget.configure().

어떤 경우든지 각 설정은 문자열로 반환된다. 이 때문에 아주 골치거리가 될 수 있다. 예를 들면 불리언 값은 "0"이나 "1"이 된다. (두가지 모두 파이썬에서는 논리적으로 참이다). Tk 변수나 창부품 같은 정상적인 Tkinter 객체를 열람하려고 하면 문제는 더욱 악화된다. 다음 예제에 문제를 보여준다 (그렇지만 그런 간단한 상황이라면 이미 오리지널 Tk가 있다) 해결책 몇 가지를 아래에서 연구해 보았다:

import Tkinterroot = Tkinter.Tk()aVar = Tkinter.StringVar()aLabel = Tkinter.Label(textvar = aVar)aLabel["textvar"]  'PY_VAR0'root.setvar(aLabel["textvar"], "foo")aLabel.getvar(aLabel["textvar"])  'foo'str(aLabel)  '.8252000'root.nametowidget(str(aLabel))  <Tkinter.Label instance at 0x7dea60>aLabel.master  <Tkinter.Label instance at 0x7dea60>root.master  None	

해결책은 무엇을 열람하려고 하는가에 달려 있다:

  • StringVar같은 변수일 경우, 변수의 이름을 알고 있다면 그 변수를 읽거나 쓰려면 getvar(name_of_var) 그리고 setvar(name_of_var, new_value)를 사용하면 된다. 또한 원래의 변수처럼 작동하는 Tkinter 변수를 만드는 것도 가능하다. 그러나 보기에 좋지 않으므로 대신에 getvar와 setvar를 사용하시기를 권한다 (즉 원래의 Tkinter 변수에 보관하자). 정말 그렇게 하고 싶다면, 먼저 새로 Tkinter 변수를, 예를 들어 v = Tkinter.StringVar()와 같이 만들고 그의 _name 특성에 다음과 같이 설정하자: v._name = name_of_variable. 여기에서 문제는 새로 만들어진 Tk 변수를 사용하지도 않으며 이것이 Tkinter의 변수의 특성에 있는지 제대로 문서화되어 있지 않다는 것이다. (쓰레기 수집 문제가 있기 때문에, 안전한지 확신조차 할 수 없다.)
  • (버튼 같이) 화면에 나타나는 창부품의 경우. 창부품의 이름을 안다면, nametowidget(name_of_wdg)를 사용하여 그것을 Tkinter 객체로 변환할 수 있다. 또한, wdg.master를 사용하면 창부품의 그릇을 열람할 수 있다 (이것은 창부품의 이름 말고 Tkinter 창부품을 돌려준다.).
  • tkFont.Font 객체의 경우 (Tk의 "named font"에 상응함). tkFont.Font 객체를 만들어서 창부품의 폰트를 설정하면, tkFont.Font 객체를 조작하자 마자 그 창부품의 폰트가 바뀐다. 이는 사용자-설정 같은 것에 아주 간편하다. tk named 폰트의 이름을 안다면, 그 이름 인자를 지정하고 exists=True로 지정하면 그에 기반하여 새로운 tkFont.Font 객체를 만들 수 있다. (exists=False라면, 새로운 tk named 폰트가 생성된다. 이 폰트는 tkFont.Font 객체가 사라지면 소멸되니 조심하자!).
  • 주의: getvar와 setvar 그리고 nametowidget은 어떤 Tkinter 창부품에도 호출될 수 있다. 간편하게도 any_widget.getvar(name_of_var)이 있다.

힌트

힌트 일반:

  • 스크롤바를 꾸리고 난 후에 창부품을 달자. 그러면 창이 너무 줄어들어서 모두 나타낼 수 없을 경우에도, 스크롤바가 여전히 보일 것이다.
  • 복잡하게 위치를 잡으려면, 틀을 두어 창부품을 모아두자; 꾸림자나 격자관리자에 너무 집착하는 것보다 이렇게 하는 것이 보통 더 쉽고 더 안전하다. 그러나 각 창부품에 올바로 그릇을 사용하도록 조심하자. 그렇지 않으면 혼란에 빠질 것이며, 심지어 격자관리자와 꾸림자가 서로 다툴수도 있다(무한 회돌이에 빠짐).

다음은 Tkinter에서 흔히 빠질 수 있는 몇가지 함정이다. 그리고 피하는 방법이다:

  • 역호출 함수는 반드시 반괄호 없이 지정되어야 하고 그리하여 인자가 없다. 반괄호를 지정하면, 파이썬은 그 함수를 (GUI이 설정된 대로) 그냥 한 번만 호출하고, 반환된 값이 역호출 함수로 사용된다. 이것은 초보자가 아주 흔히 빠지는 함정이다. 그 한가지 이유는 사용자가 종종 그 역호출 함수에 인자를 함게 지정하고 싶어하기 때문이다. 이를 해결하는 아주 멋진 방법은 역호출 싸개(callback shim)를 사용하는 것이다.
  • 같은 그릇 안에 꾸림자와 격자관리자 함께 절대로 사용하지 말자. 이렇게 하면 무한 회돌이에 빠진다! 창을 그리는 동안에 어플리케이션 크기가 줄면, 제일 먼저 이것을 점검하자. 또 찾기도 힘들다. 문제가 생기면, 각 창부품에 대하여 지정한 그릇을 주의깊게 살펴보자. 그릇을 잘못 지정했을 수도 있고 그릇을 지정하는 것을 깜빡했을 수도 있다 (기본은 root로 지정되어 있다).
  • 연관된 Tkinter 객체가 사라지면, 어떤 Tcl/Tk 객체들은 (창부품은 미포함) 삭제된다. 이 때문에 종종 혼란이 일어난다. 몇가지 예를 들면 다음과 같다:
    • 이미지: 이미지나 아이콘이 Tk 창부품으로부터 사라진다면, 그 이미지에 대한 Tkinter 표현에 대하여 참조점을 추적유지 하지 않았기 때문일 것이다.
    • 변수 (StringVar, 등등.): 라디오 버튼이 모두 설정이 사라지면, 그 변수가 여전히 존재하는지 점검해 보자. 변수에 관하여 내가 아는 한 그것이 가장 눈에 띄는 논란거리이다. Entry textvariable를 잃어 버리더라도, 적어도 기존의 텍스트는 그대로 있다. Trace_variable은 변수와 함수 모두에 대하여 참조점을 유지하는 것 같다. 그래서 따로 참조를 유지할 필요가 없다.
    • tkFont.Font 객체: 이것들은 tcl이 폰트에 이름붙인 Tkinter 표현이며 위젯 모듬 단위로 폰트를 구성하기에 유용하다. 나중에 그 창부품들을 다시 환경구성하고 싶다면 참조점을 유지하자. 그렇지 않으면 그냥 안전하게 tkFont.Font 객체가 알아서 하게 두어도 된다. 왜냐하면 그 창부품들이 지난 환경구성값들을 유지하고 있기 때문이다.
    • 주의: 한 창부품에 이미 삭제된 변수나 기타 항목을 질의한다면, 그 삭제된 항목의 전 이름이 반환되지, 그 항목이 삭제되었다고 가르쳐 주지 않는다. 그래서 이 문제를 디버깅하는 일은 어려울 수 있다. 한가지 시도해 볼 만한 것은 그 이름을 새로운 Tkinter 객체로 변환해 보는 것이다 (예를 들어 root.getvar(name) 또는 tkFont.Font(name, exists=True)); 이는 tcl 객체가 삭제되면 실패할 것이다.
  • Tkinter에는 반드시 주 쓰레드에서부터 접근해야 한다. (다시 말해, 더 정확하게 말하면, mainloop를 호출한 쓰레드에서부터 접근해야 한다). 이를 어기면 별별 해괴한 증상들이 야기 된다. 이 때문에 멀티-쓰레딩과 Tkinter를 조합해 쓰기가 아주 어렵다. 본인이 발견한 유일하게 안전한 방법은 폴링(polling)이다(즉, 주 회돌이에서 after를 사용하여 쓰레드가 작성한 threading Queue를 폴링하면 된다). 쓰레드가 event_create를 사용하면 주 회돌이와 안전하게 통신할 수 있을 것 같지만, 이 방법은 안전하지 않다.
  • 인자의 기본 값으로 수정가능 객체를 절대로 사용하지 말자 (리스트나 사전 같은 객체). (함수가 내부적으로 그 인자를 조작하면) 기본 값은 너무 쉽게 바뀔 수 있다. 그리하여 def foo(alist=[]):...는 앞으로 엄청난 재앙이 일어나기를 기다리는 짓이다. 이 문제를 피하려면, 기본 값에 None을 사용한 다음, 내부적으로 None을 테스트하고 그것을 []로 바꾸자 (또는 {} 기타 무엇이든).

좀 희귀한 Tkinter 함정들:

  • 변수(Variable)를 추적한 결과로 코드를 실행하면 그리고 그 코드의 결과가 또다른 변수(Variable)에 설정되면, 그 "다른 변수(Variable)"는 코드가 실행을 마칠 때까지 설정되지 않을 수도 있다. Text.search를 실행하고서 count 변수를 사용하고자 할 때 이것을 발견하였다 (이것을 버그로 보고하였지만, 혹 특징일지도 모르겠다.)

자원

  • 프레드릭 룬트(Fredrik Lundh)의 Tkinter 개론은 시작하기에 좋은 곳이다. 불행하게도, 미완성이며 아직도 그래 보인다.
  • 알렉스 마르텔리(Alex Martelli)의 "Python in a Nutshell"은 Tkinter를 간결하지만 깔끔하게 소개하고 있다. 또한 아주 훌륭한 파이썬 참조 책이다; 나의 견해로 파이썬 프로그래머라면 "Python in a Nutshell" 그리고 데이비드 비즐리(David Beazley)의 "Python Essential Reference"가 꼭 있어야 할 것이다.
  • Tkinter Folkore는 본인이 Tkinter에 관하여 좋은 문서를 찾으면서 어렵게 모아 놓은 문서이다.
  • Tcl/Tk는 Tkinter와 아주 비슷하고 (그리고 Tkinter에 관한 정보가 너무 부족하므로) Tcl/Tk 참조서를 가까이 하면 아주 도움이 될 것이다. 얻을 수 있는 자원을 모두 알지는 못한다. 그러나 내가 주로 참조하는 자원 두 가지는 다음과 같다:
  • 존 그레이스(John Grayson)이 지은 Python and Tkinter Programming은 현재로 Tkiner에 관하여 유일하게 전념한 책이다. 그러나 Tkinter를 희생하고 Pmw(Python MegaWidgets)에 많은 지면을 할애하고 있으니 주의하자. 또한, 중요한 정보가 숨어있거나 빠져 있다. 그러나 Tk로 작업을 많이 한다면 꼭 필요할 것이다. 본인은 보통 제일 먼저 이 책을 참조하고, 여전히 의문이 남아 있으면 Tcl/Tk 참조서를 본다 (위 참조).
  • Tkinter용 코드를 읽고 같은 디렉토리 안에 있는 파일들을 참조하자. 좋은 코드를 많이 발견하실 수 있고 문제를 해결할 수도 있다. (Tkinter 디렉토리를 찾으려면 Tkinter를 반입한 다음 Tkinter.__file__을 인쇄해보자).

러셀 오웬(Russell Owen) 작성. 한글판 johnsonj 2008.04.11 Last updated 2006-10-20 (트로엘스 터켈슨(Troels Therkelsen)의 제안을 포함시켜 힌트 섹션을 개정함. 자원 섹션을 개정함). 이 문서는 자유롭게 배포가 가능하지만 판매용으로는 사용할 수 없다.

'파이썬 프로그래밍' 카테고리의 다른 글

Tkinter GUI 프로그래밍  (0) 2011.04.24
Tkinter로 생각하기  (0) 2011.04.24
UltraEdit Python 설정  (0) 2009.06.28
파이썬 데몬 만들기  (0) 2009.04.07
파이썬으로 재귀하향 파서 만들기(2)  (0) 2009.03.02

한동안 eclipse에서 PyDev를 사용했었는데 dynamic language의 특성상 suggestion을 정말 잘 못해서 다시 UltraEdit으로 돌아왔다.

단축키로 프로그램을 실행하기 위해서는

Advanced -> Tool Configuration 에서

여기에서 %modify% 를 넣으면 입력 파라미터를 물어보고,
생략하면 물어보지 않게 된다.



Syntax Highlighting은

Advanced -> Configuration 에서

Language 14 정도 빈 것을 선택하고

밑에 wordlist 파일을 Open 한다.


열린 파일의 제일 뒤에 syntax 를 붙여야 한다.

suntax는 http://www.ultraedit.com/downloads/extras.html 에서 받는다.

현재는 2.5까지 있으므로 할수 없이 Python 2.5를 클릭하면 텍스트 파일이 열린다.

전체를 copy & paste로 붙여 넣는다.


저장하고 나면 syntax highliting이 되는 것을 알 수 있다.


'파이썬 프로그래밍' 카테고리의 다른 글

Tkinter로 생각하기  (0) 2011.04.24
TKINTER 요약  (0) 2011.04.24
파이썬 데몬 만들기  (0) 2009.04.07
파이썬으로 재귀하향 파서 만들기(2)  (0) 2009.03.02
파이썬으로 재귀하향 파서 만들기(1)  (0) 2009.03.02

파이썬 데몬 만들기

http://cafe.naver.com/cafepython/54

출처: python cookbook p229, Forking a Daemon Process on Unix


pid = os.fork()
if pid > 0:
# exit first parent
sys.exit(0)

#decouple from parent environment

os.chdir("/")
os.setsid()
os.umask(0)

# do second fork
pid = os.fork()
if pid > 0:
sys.exit(0)

#
# daemon business code here
#

이와 관련된 설명은 Advanced Programming In The Unix Environment에서 찾을 있습니다. 모호한 부분을 말씀해 주시면 원문을 올려드리죠. ^^

Daemon Coding Rules(Advanced Programming In The Unix Environment p417)

1. fork() 호출하고 부모를 종료(exit)한다. 작업에는 가지의 이유가 있다.

관련 코드

pid = os.fork()
if pid > 0:
# exit first parent
sys.exit(0)


첫째, 만약 데몬이 명령으로 시작했다면, 부모를 종료(terminate)하는 것은 쉘이 명령이 완료(done)되었다고 생각하게 만든다.

둘째, 자식은 부모의 프로세스 그룹 아이디를 상속받고, 프로세스 아이디를 얻는다. 그래서 우리는 자식이 프로세스 그룹 리더가 아니라는 것을 보장할 있다.

2. 새로운 세션을 생성하기 위해 setsid() 호출한다. 프로세스는 새로운 세션의 세션 리더이자 프로세스 그룹의 리더가 된다, 그리고 컨트롤링 터미널과의 연관이 없어진다.

관련 코드

os.setsid()


3. 루트 디렉토리는 현재 작업 디렉토리를 변경해라. 부모로부터 상속받은 현재 작업 디렉토리는 마운트된 파일 시스템일지도 모른다. 데몬은 시스템이 재부팅될 까지 존재한다, 만약 데몬이 마운트된 파일 시스템에 머문다면, 파일 시스템은 언마운트될 없다. 생략...

관련 코드

os.chdir("/")

4. 파일 모드 생성 마스크를 0으로 설정한다. 상속받은 파일 모드 생성 마스크는 특정한 퍼미션을 허락하지 않고 있을지도 모른다. 만약 데몬 프로세스가 파일을 생성하고자 한다면, 특정 퍼미션으로 설정하기를 원할 것이다. 예를 들어, 만약 그룹 읽기와 쓰기가 가능하도록 파일을 생성하고자 한다면, 파일 모드 생성 마스크는 이들 퍼미션의 마스크를 다를 꺼놔야 것이다.

관련 코드

os.umask(0)

5. 생략...

'파이썬 프로그래밍' 카테고리의 다른 글

TKINTER 요약  (0) 2011.04.24
UltraEdit Python 설정  (0) 2009.06.28
파이썬으로 재귀하향 파서 만들기(2)  (0) 2009.03.02
파이썬으로 재귀하향 파서 만들기(1)  (0) 2009.03.02
파이썬에서 한글쓰기  (0) 2008.11.24

파이썬으로 재귀하향 파서 만들기(2)

제공 : 한빛 네트워크
저자 : Paul McGuire
역자 : 주재경
원문 : Building Recursive Descent Parsers with Python

[이전 기사 보기]
파이썬으로 재귀하향 파서 만들기(1)

예제 프로그램

NaCl, H2O, C6H5OH와 같은 화학 공식을 처리할 필요가 있는 프로그램을 생각해 보자. 이 경우 화학 공식 문법은 하나 혹은 그 이상의 심벌을 가질 것이고 각각의 뒤에는 선택적으로 정수가 올 수 있다. BNF형식으로 이는 다음과 같다.
integer       :: '0'..'9'+cap           :: 'A'..'Z'lower         :: 'a'..'z'elementSymbol :: cap lower*elementRef    :: elementSymbol [ integer ]formula       :: elementRef+
pyparsing모듈은 Optional과 OneOrMore 클래스로 이 개념을 처리한다. elementSymbol의 정의는 2개의 인자를 갖는 생성자 Word를 사용한다. 첫 번째 인자는 시작 문자를 나타내며 첫 번째 인자 이후 뒤 따르는 문자를 나타낸다.
caps       = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"lowers     = caps.lower()digits     = "0123456789"element    = Word( caps, lowers )elementRef = element + Optional( Word( digits ) )formula    = OneOrMore( elementRef )elements   = formula.parseString( testString )
이것으로 이 프로그램은 아래의 공식을 적절할 토큰으로 처리하는 적절한 토큰화 프로그램이 된다. Pyparsing의 기본 동작은 일치하는 하위 문자열의 하나의 리스트 내에서 파싱된 모든 토큰을 리턴하는 것이다.
H2O -> ['H', '2', 'O']C6H5OH -> ['C', '6', 'H', '5', 'O', 'H']NaCl -> ['Na', 'Cl']
당연히 리스트 형태로 이 결과를 출력하기에 앞서 이 리턴된 결과로 뭔가를 하고자 할 것이다. 주어진 화학 공식에 대해 분자의 무게를 계산하고자 한다고 가정해 보자. 프로그램은 어딘가에 화학 기호와 여기에 대응되는 원자 무게를 정의해야 한다.
atomicWeight = {    "O"  : 15.9994,    "H"  : 1.00794,    "Na" : 22.9897,    "Cl" : 35.4527,    "C"  : 12.0107,    ...    }
결과를 좀더 구조화 시켜 리턴 받고자 하는 경우 파싱된 화학 기호와 관련 양을 좀더 논리적인 그룹으로 만드는 것이 좋다. 다행히도 pyparsing모듈은 이러한 목적에 맞는 Group클래스를 제공한다. elementRef선언을 elementRef = element + Optional( Word( digits ) )에서 다음과 같이 변경한다.elementRef = Group( element + Optional( Word( digits ) ) ) 화학적인 기호로 그룹화된 결과를 얻을 수 있다.
H2O -> [['H', '2'], ['O']]C6H5OH -> [['C', '6'], ['H', '5'], ['O'], ['H']]NaCl -> [['Na'], ['Cl']]
마지막으로 할 내용은 Optional클래스 생성자의 기본 인자를 사용하여 elementRef의 양을 나타내는 부분을 기본 값으로 나타내도록 하는 것이다.
elementRef = Group( element + Optional( Word( digits ),                                 default="1" ) )
모든 elementRef는 한 쌍의 값을 리턴한다. Element의 화학 기호화 그 element의 원자 번호가 이에 해당하며 크기가 주어지지 않은 경우 그 값은 1이 된다. 이제 테스크 공식은 element기호와 이에 대응하는 양을 리턴한다.
H2O -> [['H', '2'], ['O', '1']]C6H5OH -> [['C', '6'], ['H', '5'], ['O', '1'], ['H', '1']]NaCl -> [['Na', '1'], ['Cl', '1']]
마지막 단계는 각각에 대해 원자 무게를 계산하는 것이다. parseString을 호출한 후 파이썬 코드 한줄을 추가하면 된다.
wt = sum( [ atomicWeight[elem] * int(qty)                     for elem,qty in elements ] )
결과는 아래와 같다.
H2O -> [['H', '2'], ['O', '1']] (18.01528)C6H5OH -> [['C', '6'], ['H', '5'], ['O', '1'], ['H', '1']]        (94.11124)NaCl -> [['Na', '1'], ['Cl', '1']] (58.4424)
예제 2는 pyparsing 프로그램 전체를 나타내고 있다.

예제 2
from pyparsing import Word, Optional, OneOrMore, Group, ParseExceptionatomicWeight = {    "O"  : 15.9994,    "H"  : 1.00794,    "Na" : 22.9897,    "Cl" : 35.4527,    "C"  : 12.0107    }    caps = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"lowers = caps.lower()digits = "0123456789"element = Word( caps, lowers )elementRef = Group( element + Optional( Word( digits ), default="1" ) )formula = OneOrMore( elementRef )tests = [ "H2O", "C6H5OH", "NaCl" ]for t in tests:    try:        results = formula.parseString( t )        print t,"->", results,    except ParseException, pe:        print pe    else:        wt = sum( [atomicWeight[elem]*int(qty) for elem,qty in results] )        print "(%.3f)" % wt========================H2O -> [['H', '2'], ['O', '1']] (18.015)C6H5OH -> [['C', '6'], ['H', '5'], ['O', '1'], ['H', '1']] (94.111)NaCl -> [['Na', '1'], ['Cl', '1']] (58.442)
파서를 이용하여 얻게 되는 좋은 점 중의 하나는 입력 텍스트에 대해 수행하는 타당성 검토이다. wt변수 계산에서 qty문자열이 모두 숫자임을 검사해야 할 필요가 없다는 점에 혹은 올바르지 않은 인자로 인해 발생하는 ValueError를 검사할 필요가 없다는 점에 주의할 것 .

HTML 분석기

마지막 예제로 간단한 HTML 분석기를 만들어 보자. 이것은 파서 표현식에 점수를 매기는 것과 같은 포괄적인HTML 파서는 아니다. 다행히도 대부분의 웹페이지에서 핵심적인 데이터를 추출하기 위한 완벽한 HTML 문법정의가 필요한 것은 아니다. 특히 CGI가 잗동으로 생성한 부분이나 다른 응용 프로그램에 대한 부분이 그렇다.

이 예제는 특정 웹 페이지에 맞춰 제작된 작은 파서로 데이터를 추출한다. 이 경우에 이 페이지는 NIST가 유지하고 있는 가용한 네트워크 타임 프로토콜(NTP)서버 리스트이다. 이 루틴은 어떤 NTP서버가 현재 가용한지를 나타내는 좀더 큰NTP클라이언트 프로그램 한 부분이 될 수도 있다.

HTML분석기를 만들기에 앞서 어떤 종류의 HTML 텍스를 처리하고자 하는지를 알아야 한다. 웹사이트를 방문하고 리턴되는 HTML소스를 보면 웹페이지에는 HTML테이블 내에 NTP서버의 IP주소와 이름이 나열되어 있다.

이름IP주소위치
time-a.nist.gov129.6.15.28NIST, Gaithersburg, Maryland
time-b.nist.gov129.6.15.29NIST, Gaithersburg, Maryland

이 테이블을 위해 HTML은 NTP서버 데이터를 만들기 위해 <table>, <tr>, <td>태그를 사용한다.
<table border="0" cellpadding="3" cellspacing="3" frame="" width="90%">                <tr align="left" valign="top">                        <td><b>Name</b></td>                        <td><b>IP Address</b></td>                        <td><b>Location</b></td>                </tr>                <tr align="left" valign="top" bgcolor="#c7efce">                        <td>time-a.nist.gov</td>                        <td>129.6.15.28</td>                        <td>NIST, Gaithersburg, Maryland</td>                </tr>                <tr align="left" valign="top">                        <td>time-b.nist.gov</td>                        <td>129.6.15.29</td>                        <td>NIST, Gaithersburg, Maryland</td>                </tr>       ...
이 테이블은 아주 커다란 HTML의 한 부분이다. Pyparsing을 사용하여 주어진 파서 표현식과 일치하는 텍스트를 스캔할 수 있고 전체 입력 텍스트의 한 부분에만 일치하는 파서 표현식을 정의할 수도 있다.

프로그램은 서버의 IP주소와 위치를 추출하므로 문법의 초점을 테이블의 열 방향에만 집중 시킬 수 있다.비 정상적이긴 하지만 패턴과 일치하는 값을 추출하고자 할 수도 있다.
<td> IP address </td> <td> location name </td>
너무 일반적인 표현식은 두 번째의 2개의 열 대신에 첫 번째의 2개의 열에 대응하기 때문에 <td>, </td><td>, </td>만큼 일반적인 것과 단지 일치하는 것 이상의 좀더 세부적인 규정을 원한다. 대신에 페이지의 테이블 데이터와 일치하지 않는 부분을 제거하는 좁은 의미의 탐색을 가능하게 하는 규정적인 IP주소를 사용하라.

IP주소를 만들기 위해서는 정수를 정의하고 그 다음에 사이에 마침표를 가진 네 개의 정수를 조합한다.
integer   = Word("0123456789")ipAddress = integer + "." + integer + "." + integer + "." + integer
HTML태그 <td>와 </td>를 매칭시킬 필요가 있으며 각각에 대해 파서 요소를 정의한다.
tdStart = Literal("<td>")tdEnd   = Literal("</td>")
일반적으로 <td>태그는 정렬,색깔 등에 대한 속성 구분자를 포함하기도 한다. 그러나 이것은 범용 목적의 파서가 아니라 다행하게도 복잡한<td> 태그를 사용하지 않는 단지 이 웹 페이지에 해당하는 특수 목적으로 작성된 것이다. Pyparsing의 최신 버전은 오프닝 태그에 있는 속성 구분자를 지원하는 HTML태그에 대해 도움을 주는 메서드를 가지고 있다.

마지막으로 서버위치와 일치하는 몇 가지의 표현식이 필요하다. 이것은 실제로 알파벳 데이터, 콤마, 마침표 혹은 숫자를 포함하는지 않는지를 알 길이 없는 자유롭게 만들어진 텍스트이다. 그래서 가장 간단한 선택은 종결 태그</td>에 모든 것을 다 받아들이는 것이다.pyparsing은 이런 종류의 문법 요소를 위해 SkipTo라는 이름을 가진 클래스를 가지고 있다.

이제 타임서버의 텍스트 패턴 정의에 필요한 모든 것을 가지게 되었다.
timeServer = tdStart + ipAddress + tdEnd +                  tdStart + SkipTo(tdEnd) + tdEnd
데이터를 추출하기 위해 timeserver.scanString을 호출하라. 이 함수는 입력 텍스트에 대해 일치하는 각각의 경우에 대해 시작과 마지막 문자열 위치와 일치하는 토큰을 만들어 내는 생성 함수 이다.

예제 3
from pyparsing import *import urllib# NTP서버에 대한 기본적인 텍스트 패턴 정의integer = Word("0123456789")ipAddress = integer + "." + integer + "." + integer + "." + integertdStart = Literal("<td>")tdEnd = Literal("</td>")timeServer =  tdStart + ipAddress + tdEnd + tdStart + SkipTo(tdEnd) + tdEnd# 타임서버의 리스트를 가져온다nistTimeServerURL = "http://tf.nist.gov/service/time-servers.html"serverListPage = urllib.urlopen( nistTimeServerURL )serverListHTML = serverListPage.read()serverListPage.close()for srvrtokens,startloc,endloc in timeServer.scanString( serverListHTML ):    print srvrtokens
예제 3을 실행하면 다음과 같은 토큰 데이터를 얻는다.
[' <td>', '129', '.', '6', '.', '15', '.', '28', '</td> ', ' <td>', 'NIST, Gaithersburg, Maryland', '</td> '][' <td>', '129', '.', '6', '.', '15', '.', '29', '</td> ', ' <td>', 'NIST, Gaithersburg, Maryland', '</td> '][' <td>', '132', '.', '163', '.', '4', '.', '101', '</td> ', ' <td>', 'NIST, Boulder, Colorado', '</td> '][' <td>', '132', '.', '163', '.', '4', '.', '102', '</td> ', ' <td>', 'NIST, Boulder, Colorado', '</td> ']:
이 결과를 살펴보면 몇 가지를 바로 알 수 있다. 파서는 IP주소를 구분 마침표와 하위필드를 가진 각각의 토큰으로 기록한다는 것이 한 가지 이다. Pyparsing이 이 필드를 한 개의 문자열 토큰으로 조합하기 위해 파싱하는 동안 몇 가지 일을 할 수 있다면 참 좋을 것이다. Pyparsing Combine클래스는 이 일을 한다. IP주소에 대해 리턴되는 하나의 문자열 토큰을 얻기위해ipAddress정의를 다음과 같이 수정한다.
ipAddress = Combine( integer + "." + integer + "." + integer + "." + integer )
두 번째로 알 수 있는 것은 결과가 테이블의 열을 표시하는 HTML 태그를 열고 닫는 것을 포함하고 있다. 파싱이 진행되는 동안 이 태그가 중요한 반면에 태그 그 자체는 추출된 데이터에 관심을 두지 않는다. 리턴되는 토큰 데이터에서 이 들을 무시하려면 suppress메서드를 사용한다.
tdStart = Literal("<td>").suppress()tdEnd   = Literal("</td>").suppress()
예제 4
from pyparsing import *import urllib# NTP서버에 대한 기본적인 텍스트 패턴 정의integer = Word("0123456789")ipAddress = Combine( integer + "." + integer + "." + integer + "." + integer )tdStart = Literal("<td>").suppress()tdEnd = Literal("</td>").suppress()timeServer =  tdStart + ipAddress + tdEnd + tdStart + SkipTo(tdEnd) + tdEnd# 타임서버 리스트를 가져온다nistTimeServerURL = "http://tf.nist.gov/service/time-servers.html"serverListPage = urllib.urlopen( nistTimeServerURL )serverListHTML = serverListPage.read()serverListPage.close()for srvrtokens,startloc,endloc in timeServer.scanString( serverListHTML ):    print srvrtokens
프로그램 예제4를 실행하여 리턴되는 토큰은 부분적으로 개선되었다.
['129.6.15.28', 'NIST, Gaithersburg, Maryland']['129.6.15.29', 'NIST, Gaithersburg, Maryland']['132.163.4.101', 'NIST, Boulder, Colorado']['132.163.4.102', 'NIST, Boulder, Colorado']
마지막으로 이 토큰에 결과를 더하면 속성 이름으로 이들을 엑세스 할 수 있다. 이것을 하는 가장 손 쉬운 길은 timeServe정의 내에 있다.
timeServer = tdStart + ipAddress.setResultsName("ipAddress") + tdEnd         + tdStart + SkipTo(tdEnd).setResultsName("locn") + tdEnd
이제 for루프로 깔금하게 마무리 하고 사전에 데이터 형에 있는 한 멤버처럼 이들을 엑세스 할 수 있다.
servers = {}for srvrtokens,startloc,endloc in timeServer.scanString( serverListHTML ):    print "%(ipAddress)-15s : %(locn)s" % srvrtokens    servers[srvrtokens.ipAddress] = srvrtokens.locn
예제 5 에는 최종 실행 프로그램이 있다.

예제5
from pyparsing import *import urllib# NTP서버에 대한 기본적인 텍스트 패턴 정의 integer = Word("0123456789")ipAddress = Combine( integer + "." + integer + "." + integer + "." + integer )tdStart = Literal("<td>").suppress()tdEnd = Literal("</td>").suppress()timeServer = tdStart + ipAddress.setResultsName("ipAddress") + tdEnd +              tdStart + SkipTo(tdEnd).setResultsName("locn") + tdEnd# 타임서버 리스트 가져오기nistTimeServerURL = "http://tf.nist.gov/service/time-servers.html"serverListPage = urllib.urlopen( nistTimeServerURL )serverListHTML = serverListPage.read()serverListPage.close()servers = {}for srvrtokens,startloc,endloc in timeServer.scanString( serverListHTML ):    print "%(ipAddress)-15s : %(locn)s" % srvrtokens    servers[srvrtokens.ipAddress] = srvrtokens.locnprint servers
이제 성공적으로 NTP서버, IP주소 그리고 프로그램 변수를 성공적으로 추출해서 NTP클라이언트 프로그램은 파싱된 결과를 사용할 수 있다.

결론

Pyparisng은 재귀하향 파서를 만드는 기본적인 프레임 워크를 제공한다. 입력 문자열 스캐닝의 오버헤드 기능에 주의, 일치하는 않는 표현식 처리, 일치하는 것 중 가장 긴 것 선택하기, 콜백함수 호출, 그리고 파싱된 결과를 리턴하는 것이 이 파서의 내용에 속한다. 이로 인해 개발자는 문법 디자인과 이에 대응하는 토큰 처리 구현에만 집중할 수 있다.조합자로서의 pyparsing의 특징으로 인해 개발자는 응용프로그램을 간단한 토큰화 프로그램에서 복잡한 문법 처리기로 확장할 수 있다. 당신의 다음 파싱 프로젝트로 이를 활용하는 것은 아주 훌륭한 방법이다.

pyparsing 다운받기


저자 Paul McGuire는 Alan Weber & Associates에서 제조시스템 수석컨설턴트로 있다. 여가 시간에 SourceForge의 pyparsing프로젝트를 관리한다.

역자 참고

파싱 및 문법 구성에 대한 일반적인 내용을 참고하기 위해서는 lex와 yacc(개정판)이 많은 도움이 된다.


역자 주재경님은 현재 (주)세기미래기술에 근무하고 있으며 리눅스, 네트워크, 운영체제 및 멀티미디어 코덱에 관심을 가지고 있습니다.
* e-mail : jkjoo@segifuture.com

Copyright © 2009 Hanbit Media, Inc.

파이썬으로 재귀하향 파서 만들기(1)

제공 : 한빛 네트워크
저자 : Paul McGuire
역자 : 주재경
원문 : Building Recursive Descent Parsers with Python

파싱이란 무엇인가? 파싱이란 의미를 추출하기 위해 일련의 심벌을 처리하는 것을 말한다. 전형적으로 이것은 문장에서 단어를 읽어 이로부터 의미를 끌어내는 것을 의미한다. 응용프로그램이 텍스트로 된 데이터를 처리해야 할 때 파싱로직 형태의 뭔가를 사용해야 한다. 이 로직은 텍스트 문자와 문자 그룹(단어)를 스캔하며 정보나 명령을 추출하기 위해 문자 그룹을 인지한다.

소프트웨어 파서는 일반적으로 특정 형태의 텍스트를 처리하기 위해 만들어진 특수 목적의 프로그램이다. 이 텍스트는 보험이나 의료계에서 사용하는 알 수 없는 기호로 이루어진 문자 일수도 있고, C 헤더 파일에 있는 함수 선언, 그래프의 상호간 연결을 보여주는 node-edge에 대한 설명, 웹페이지의 HTML태그, 혹은 네트워크 설정을 위한 명령어, 3D 이미지 수정이나 회전을 위한 명령어 일 수 있다. 각각의 경우 파서는 문자 그룹과 패턴의 특정 집합을 처리한다. 이 패턴 집합을 파서의 문법이라 한다.

예를 들면, 문자 Hello, World!를 파싱할 때 일반적인 패턴을 따르는 인사말을 파싱하고 싶을 것이다. Hello, World!는 인사말 단어 Hello로 시작한다. 아주 많은 인사말이 있다. Howdy, Greetings, Aloha, G’day등. 그러므로 단지 한 단어의 인사말로 시작하기 위해서는 아주 좁은 의미의 인사말 문법을 정의 할 수 있다. 콤마 문자가 뒤에 온 다음 인사말과 인사하는 대상이 한 단어로 온다. 마지막으로 감탄부호와 같은 종료 구두점이 인사말을 끝낸다. 이 인사 문법은 대략 아래와 같다.(::는 ~로 구성되어 있다는 의미이다)
word           :: group of alphabetic characterssalutation     :: wordcomma          :: ","greetee        :: wordendPunctuation :: "!"greeting       :: salutation comma greetee endPunctuation
이것은 BNF형태이다. 기호를 표현하는 데는 아주 많은 방법이 있다.

BNF형태의 내용으로 원하는 문법을 정의 했으면 실행 가능한 형태로 이 것을 변환해야 한다. 일반적인 방법이 재귀 하향 파서를 만드는 것이다. 이 파서는 저 수준의 함수를 호출하는 고 수준의 함수와 문법의 터미널을 읽어 들이는 기능을 정의한다. 함수는 현 파싱 위치에서 일치하는 패턴이 있으면 일치하는 토큰을 리턴하고 실패하는 경우 예외를 발생시킨다.

Pyparsing이란?

Pyparsing은 재귀 하향 파서를 쉽고 빠르게 작성하도록 돕는 파이썬 클래스 라이브러리 이다. 아래에 Hello, World!보기에 대한 파싱 보기가 있다.
from pyparsing import Word, Literal, alphassalutation     = Word( alphas + "'" )comma          = Literal(",")greetee        = Word( alphas )endPunctuation = Literal("!")greeting = salutation + comma + greetee + endPunctuation
Pypasring의 여러 기능은 개발자가 텍스트 파싱 기능을 빠르게 개발하도록 돕는다.
  • 문법은 파이썬을 따른다. 그러므로 문법을 정의한 별도의 파일이 필요치 않다.
  • 추가적인 어떤 문법도 필요없다. And에 대한 +, Or에 대한 ^, 첫 매치에 대한 | 그리고 Not에 대한 ~은 예외이다.
  • 어떤 특별한 코드 생성 단계도 없다.
  • 파서 요소 사이에 나타나는 스페이스와 주석은 암묵적으로 아무 처리도 하지 않는다. 무시 가능한 텍스트에 표시를 해서 문법을 복잡하게 할 필요가 없다.
pyparsing문법은 Hello, World!뿐만 아니라 아래의 문장 중 그 어느 것도 파싱을 한다.
  • Hey, Jude!
  • Hi, Mom!
  • G'day, Mate!
  • Yo, Adrian!
  • Howdy, Pardner!
  • Whattup, Dude!
예제1은 완전한 Hello, World!파서와 파싱된 결과를 보여주고 있다.

예제 1
from pyparsing import Word, Literal, alphassalutation     = Word( alphas + "'" )comma          = Literal(",")greetee        = Word( alphas )endPunctuation = Literal("!")greeting = salutation + comma + greetee + endPunctuationtests = ("Hello, World!", "Hey, Jude!","Hi, Mom!","G'day, Mate!","Yo, Adrian!","Howdy, Pardner!","Whattup, Dude!" )for t in tests:        print t, "->", greeting.parseString(t)===========================Hello, World! -> ['Hello', ',', 'World', '!']Hey, Jude! -> ['Hey', ',', 'Jude', '!']Hi, Mom! -> ['Hi', ',', 'Mom', '!']G'day, Mate! -> ["G'day", ',', 'Mate', '!']Yo, Adrian! -> ['Yo', ',', 'Adrian', '!']Howdy, Pardner! -> ['Howdy', ',', 'Pardner', '!']Whattup, Dude! -> ['Whattup', ',', 'Dude', '!']
Pyparsing은 조합자(Combinator)이다

pyparsing모듈로 먼저 문법의 기본적인 것들을 정의한다. 그 다음 전체 문법 문장에 대한 다양한 분기를 위해 좀더 복잡한 파서 표현으로 이것들을 조합한다. 다음과 같이 관계를 정의하여 이것들을 조합한다.
  • 문법 내에서 어떤 표현이 뒤 따라야 하는가? 예를 들면 키워드 if 다음에는 괄호로 묶여진 불리언 표현식이 뒤 따른다.
  • 문법 내에서 어떤 표현이 특정 지점에서 올바른 대체표현인가? 예를 들면 SQL 명령어는 SELECT, INSERT, UPDATE, 혹은 DELETE로 시작할 수 있다.
  • 어떤 표현이 선택적인 표현인가? 예를 들면 전화번호는 선택적으로 괄호로 된 지역번호 뒤에 올 수 있다.
  • 어떤 표현이 반복적인가? 예를 들면 XML태그는 0혹은 그 이상의 속성을 가질 수 있다.
복잡한 문법이 수십 개 혹은 수백 개의 문법 조합을 포함하고 있다 할지라도 대부분의 파싱은 단지 몇 개의 정의로 쉽게 수행된다. 생각을 조직화 하고 파서를 디자인 하는데 BNF형태로 문법을 기술하는 것이 도움이 된다. 이것은 또한 pyparing의 함수와 클래스로 문법을 구현하는 당신의 작업을 추적하는 데도 도움을 준다.

간단한 문법 정의하기

대부분의 문법 중 가장 작은 부분을 차지하는 것은 전형적으로 문자열에 대한 정확한 매칭이다. 아래에 전화번호 파싱을 위한 간단한 BNF가 있다.
number      :: '0'.. '9'*phoneNumber :: [ '(' number ')' ] number '-' number
이것은 전화번화 내에서 괄호와 대시(-)를 찾기 때문에 이 구두점 표시를 위해 간단한 토큰을 정의 할 수 있다.
dash   = Literal( "-" )lparen = Literal( "(" )rparen = Literal( ")" )
전화번호 내에서 숫자를 정의하기 위해서 다양한 길이의 문자열을 처리할 필요가 있다. 이를 위해서는 Word토큰을 사용하라
digits = "0123456789"number = Word( digits )
숫자 토큰은 숫자로 나열되는 문자로 구성된 연속된 열과 일치할 것이다. 즉 이것은 숫자로 구성된 단어이다.(알파벳으로 구성되는 전형적인 단어와는 반대로) 이제 전화번호에 대한 각각의 문자열을 얻었으므로 이제 And 클래스를 사용하여 이것들을 문자열로 만들 수 있다.
phoneNumber =     And( [ lparen, number, rparen, number, dash, number ] )
이것은 읽기에 자연스럽지 않다. 다행히도 pyparsing모듈은 좀더 쉽게 각 파서 요소를 조합하기 위한 연산자 메서드를 정의한다. 좀더 읽기 쉬운 정의는 And를 위해 +를 사용한다.
phoneNumber = lparen + number + rparen + number + dash + number
좀더 쉽게 하기 위해 +연산자는 묵시적으로 문자 그대로 변환되는 파서 요소와 문자를 묶는다. 이것이 읽기에 더 쉽다.
phoneNumber = "(" + number + ")" + number + "-" + number
최종적으로 전화번호의 첫 부분에 지역 번호가 선택적임을 나타내기 위해 pyparsing의 Optional클래스를 사용한다.
phoneNumber = Optional( "(" + number + ")" ) + number + "-" + number
문법 사용하기

문법을 정의한 후 다음 단계는 파싱할 텍스트에 적용하는 것이다. pyparsing표현은 주어진 문법으로 입력 텍스트를 처리하기 위해 3가지의 메서드를 지원한다.
  • parseString은 입력 문자열의 내용을 판독하고 문자열을 파싱하고, 그리고 각 문법 구조에 대한 하위 문자열과 문자열의 조합을 리턴하는 문법을 사용한다.
  • scanString은 단지 입력 문자열과 일치할 수 있는 문법을 사용하며 이 문법은 일치 검사를 위해 문자열을 스캔하고 입력 문자열 내에 시작점과 마지막 지점 그리고 일치된 토큰을 포함하는 튜플을 리턴한다.
  • transformStirng은 scanString의 변종이다. 이것은 일치할 때 마다 수정되는 일치한 토큰의 변화에 대응하며 최초 입력 텍스트가 표현하는 하나의 문자열을 리턴한다.
Hello, World!파서는 parseString을 호출하고 바로 파싱된 결과인 토큰을 리턴한다.
Hello, World! -> ['Hello', ',', 'World', '!']
비록 이것이 토큰 문자열의 간단한 리스트로 보이긴 하지만 pyparsing은 ParseResults 오브젝트를 사용하여 데이터를 리턴한다. 위 예제에서 결과 값들은 파이썬의 리스트 데이터처럼 보인다. 사실 리스트 데이터처럼 단지 결과에 순서를 붙일 수도 있다.
print results[0]print results[-2]
는 다음과 같이 나타난다.
HelloWorld
parseResult를 이용하여 각 문장에 이름을 정의할 수도 있다. 이것은 파싱된 텍스트와 비트열을 반복적으로 검사하는 것을 더욱 쉽게 해 준다. 이것은 특히 문법이 선택적인 부분을 포함하고 있을 때 유용하다. 이 문법은 리턴된 토큰 리스트의 길이와 오프셋을 변경할 수 있다.
salute  = Word( alphas+"'" ).setResultsName("salute")greetee = Word( alphas ).setResultsName("greetee")
마치 리턴된 결과의 속성인 것처럼 대응되는 토큰을 참조할 수 있다.
print hello, "->", results    print results.saluteprint results.greetee
위의 내용은 다음과 같이 나타난다.
G'day, Mate! -> ["G'day", ',', 'Mate', '!']G'dayMate
위 결과는 당신이 작성한 파싱 프로그램의 가독성과 유지 보수성을 향상 시키는데 커다란 도움을 준다.

전화번호 문법의 경우 차례대로 전화번호 리스트를 가지고 있는 입력 문자열을 파싱한다. 다음과 같다.
phoneNumberList = OneOrMore( phoneNumber )data            = phoneNumberList.parseString( inputString )
이것은 pyparsing의 ParseResult 오브젝트 형태로 데이터를 리턴할 것이고 이 오브젝트는 입력 전화번호의 모든 리스트를 가지고 있다.

Pyparsing은 delimitedList와 같은 유용한 표현을 가지고 있으며 입력이 콤마로 구분되는 전화번호 리스트인 경우 간단하게 phoneNumberList를 다음과 같이 변경할 수 있다
phoneNumberList = delimitedList( phoneNumber )
이것은 전과 같은 전화번호 리스트를 리턴한다. dellimiteList는 모든 표현식과 문자열을 지원한다. 콤마 구분자가 가장 일반적이어서 기본으로 사용된다.

단지 전화번호만 가지고 있는 문자열 대신 우편번호, 주소, 메일주소, 전화번호를 가지고 있는 경우 scanString을 사용하는 전화번호로 확장해야 한다. scanString은 파이썬 생성 함수이며 for 루프, 리스트 , 생성 표현식 내에서 이것을 사용해야 한다.
for data,dataStart,dataEnd in     phoneNumber.scanString( mailingListText ):    .    .    # 전화번호 토큰으로 뭔가를 한다,     # data변수에 리턴된다    .    .
마지막으로 같은 메일링 리스트를 가지고는 있지만 잠재저인 전화 판매원으로부터 전화번호를 숨기고자 하는 경우 모든 전화번호를 (000)000-0000문자열로 변환하는 파서 동작을 추가하여 문자열을 변환 할 수 있다.
phoneNumber.setParseAction( replaceWith("(000)000-0000") )sanitizedList =     phoneNumber.transformString( originalMailingListText )
적절한 입력에 대한 동작이 정의 되지 않은 경우

Pyparsing은 주어진 파서 요소에 대해 일치하는 텍스트가 없어질 때까지 입력을 처리한다. 예상치 못한 토큰이나 문자를 만나고 이에 적절한 처리가 없는 경우 pyparsing은 parseException을 발생 시킨다. parseException은 기본으로 진단 메시지를 출력하며 이 메시지에는 라인번호, 칼럼, 텍스트 라인과 주석문이 포함된다.

파서에 Hello, World?라는 문장이 입력되는 경우 다음과 같은 예외를 만나게 된다.
pyparsing.ParseException: Expected "!" (at char 12), (line:1, col:13)
이를 바탕으로 입력문자열을 수정하거나 문장에 대해 좀더 관대한 문법을 작성할 수 있다. 위의 경우에 올바른 문장 종결자로 물음표를 지원하면 된다.


역자 주재경님은 현재 (주)세기미래기술에 근무하고 있으며 리눅스, 네트워크, 운영체제 및 멀티미디어 코덱에 관심을 가지고 있습니다.
* e-mail : jkjoo@segifuture.com

Copyright © 2009 Hanbit Media, Inc.

파이썬에서 한글쓰기

2007/09/27 23:19 in Programming/Python

한글이 들어있는 파이썬 소스를 윈도우 커맨드 창(또은 리눅스 쉘)에서 그냥 실행하면 이런 에러가 뜹니다

E:\python>python int2base.py
File "int2base.py", line 11
SyntaxError: Non-ASCII character '\xc5' in file int2base.py on line 11, but no encoding declared; see
http://www.python.org/peps/pep-0263.html for details

이때는 파이썬 소스의 처음부분(1,2번째 줄) # -*- coding: cp949 -*- 을 추가하면 됩니다

추가로 유닉스 계열 운영체제를 사용할때는 #!/usr/bin/python를 첫줄에 붙이면 실행파일 속성을 주고 바로 파이썬코드를 실행할수 있습니다

ex)
#!/usr/bin/python
# -*- coding: cp949 -*-
print "
한글사용가능" #주석도 한글로 쓸수 있어요

파이썬으로 하는 웹 클라이언트 프로그래밍

저자: 데이브 워너(Dave Warner), 역 전순재

웹 클라이언트 프로그래밍은 웹에서 정보를 찾게 도와주는 강력한 테크닉이다. 웹 클라이언트는 (웹 주소 앞에 붙은 http) 하이퍼 텍스트 전송 프로토콜[1]을 사용하여 웹 서버로부터 데이터를 열람하는 프로그램 모두를 말한다. 웹 브라우저는 클라이언트이다. 웹 크롤러(crawler) 역시 클라이언트이다. 이 프로그램은 웹을 자동적으로 돌아다니면서 정보를 수집한다. 웹 클라이언트를 사용하면 웹에서 다른 사람들이 제공하는 서비스들을 이용할 수 있으며 웹 사이트에 역동적인 특징들을 추가할 수도 있다.

개발자들이 사용하는 툴박스에는 자연스럽게 웹 클라이언트 프로그래밍이 들어있다. 펄(Perl) 열성팬들은 이미 수년간 웹 클라이언트 프로그래밍을 이용해 왔다. 이런 웹 클라이언트 프로그래밍은 파이썬으로 처리하면 편리성과 유연성이 더욱 높은 수준에 이른다. 여기에 필요한 모든 기능들은 모듈 3개로 해결할 수 있다. HTTPLIB, URLLIB, 그리고 더 새로워진 XMLRPCLIB가 바로 그것들이다. 진정한 파이썬 스타일로, 각 모듈은 기존의 모듈 위에 구축되어 애플리케이션에 견고하면서도 잘 디자인된 기반을 제공한다. XMLRPCLIB는 다음에 논하기로 하고 본 기사에서는 첫 번째 모듈 두 개에 대해 다루겠다.

우리가 볼 예제에서는 미어캣(Meerkat)을 사용하겠다. 이럴 경우 여러분이 필자와 같은 생각을 가지고 있다면 시간을 들여 오픈 소스 공동체의 동향과 개발 상황들을 추적해서 경쟁력을 확보할 것이다. 미어캣(Meerkat)은 이 작업을 훨씬 더 쉽게 만들어주는 도구이다. 미어켓은 오픈 와이어 서비스(open wire service)로서 오픈 소스 컴퓨팅과 관련된 방대한 양의 정보를 수집하고 정리한다. 미어캣의 브라우저 인터페이스는 유연하고 맞춤가능하지만, 웹 클라이언트 프로그래밍을 사용하면 우리는 이 정보를 훓어보고, 추출하는 것은 물론이고 나중에 사용하기 위해 오프 라인에 저장할 수도 있다. 우리는 먼저 HTTPLIB를 상호대화적으로 사용하여 미어켓에 접근할 것이다. 그리고 나서 URLLIB를 통해 미어켓의 개방 API(Meerkat's Open API)에 접근해 들어가 맞춤가능한 정보 수집도구를 만들어 볼 것이다.

HTTPLIB

HTTPLIB는 소켓(socket) 모듈을 살짝 감싼 포장자(wrapper)이다. 앞에서 언급한 3개의 라이브러리 중에서 웹 사이트에 접근할 때 가장 제어가 쉬운 모듈이 HTTPLIB이다. 그렇지만 과업을 달성하기 위해서는 추가 작업을 더 해야만 제대로 제어할 수 있다. http 통신규약(protocol)은 "정보를 저장하지 않기(stateless)" 때문이다. 따라서 이전의 요구는 전혀 기억하지 않는다. 각 요구에 대해 여러분은 HTTPLIB 객체를 새롭게 구성하여 웹 사이트에 접속해야 한다. 요구들은 웹 서버와 대화를 형성하고 웹 브라우저를 흉내낸다. 라엘 돈페스트(Rael Dornfest)의 개방 API를 사용해서 미어켓에 접속해 보자. 그리고 어떤 결과를 얻는지 살펴 보자. 대화는 일련의 서술문들을 구축함으로써 시작된다. 먼저 원하는 작업이 무엇인지 서술한다. 그리고 나서 웹 서버에게 여러분을 식별시킨다.
>>> import httplib>>> host = 'www.oreillynet.com'>>> h = httplib.HTTP(host)>>> h.putrequest('GET', '/meerkat/?_fl=minimal')>>> h.putheader('Host', host)>>> h.putheader('User-agent', 'python-httplib')>>> h.endheaders()>>>
GET 요청은 어느 페이지를 받기 원하는지 서버에게 전달한다. 호스트 헤더(Host header)는 질의하고자 하는 도메인 이름을 서버에게 전달한다. 현대적인 서버들은 HTTP 1.1을 사용하여 여러 도메인을 같은 주소에서 사용할 수 있다. 만약 서버에게 어떤 도메인 이름을 원하는지 알려주지 않는다면, 여러분은 '302' 출력전환(redirection) 응답을 반환 코드로 얻게 될 것이다. 사용자-에이전트 헤더(User-agent header)는 서버에게 여러분이 어떤 종류의 클라이언트인지 알려 준다. 그래야만 서버는 여러분에게 보낼 수 있는 것과 없는 것이 무엇인지를 이해할 수 있기 때문이다. 이것이 웹 서버가 요구를 처리하기 위해 필요한 정보이다. 다음으로 여러분은 응답을 요구한다.
>>> returncode, returnmsg, headers = h.getreply()>>> if returncode == 200:  #OK...         f = h.getfile()...         print f.read()...
이렇게 하면 현재의 미어켓 페이지를 간략한 형태(minimal flavor)로 출력할 것이다. 응답 머리부와 응답 내용은 개별적으로 반환되며, 이렇게 하면 반환된 데이터의 문제를 해결하거나 해석하는데 모두 도움이 된다. 만약 응답 머리부를 보고 싶다면, print headers를 사용하면 된다.

HTTPLIB 모듈은 소켓 프로그래밍의 기계적인 면을 구별해준다. 게다가 HTTPLIB 모듈은 버퍼링을 위해 파일 객체를 사용하기 때문에 친숙하게 데이터 조작에 접근할 수 있지만 더욱 강력한 웹 클라이언트 애플리케이션을 위한 빌딩 블록이나 문제가 생긴 웹사이트와 상호 대화를 나누기 위한 빌딩 블록으로 더 잘 맞는다. HTTPLIB 모듈이 가지는 유용한 디버그 능력은 두 영역 모두에 도움을 준다. 객체 초기화 후에 어느 곳에서나
h.set_debuglevel(1) 메소드를 호출하면 HTTPLIB에 접근할 수 있다. (예제에서는 다음의 h = httplib.HTTP(host) 라인이다). 디버그 수준이 1에 설정되어 있으면 HTTPLIB 모듈은 getreply()을 호출한 결과들과 요청들을 화면에 응답할 것이다.

파이썬의 상호대화적인 특성 덕분에 즐겁게 HTTPLIB를 사용하여 웹 사이트를 분석할 수 있다. 이 모듈을 익히면 웹 사이트의 문제점들을 진단하기 위한 강력하고 유연한 도구를 가지게 되는 것이다. 또 시간을 가지고 HTTPLIB에 대한 소스를 살펴보라. 200줄도 안되는 코드임에도 불구하고, HTTPLIB를 사용하면 빠르고 쉽게 파이썬으로 소켓 프로그래밍을 시작할 수 있다.

URLLIB

URLLIB는 HTTPLIB에서 발견되는 기능에 대해 세련된 인터페이스를 제공한다. URLLIB 모듈은 웹 사이트를 분석하는 것보다는 데이터 그 자체를 찾아 내는데 가장 유용하게 사용된다. 다음 코드는 URLLIB를 사용해서 위와 똑같은 상호작용을 한다. (주의: 마지막 줄을 화면 출력을 위해 두 줄로 쪼개었지만, 여러분의 스크립트에서는 나누지 말 것!)
>>> import urllib>>> u = urllib.urlopen('http://www.oreillynet.com/meerkat/?_fl=minimal')
이것이 다이다! 한 줄로 미어켓(Meerkat)에 접근해서 데이터를 얻었으며, 그 데이터를 임시 저장소에 보관했다. 해더 정보에 접근하려면
>>> print u.headers
그리고 전체 파일을 보려면
>>>print u.read()
그러나 이것이 전부는 아니다. URLLIB는 HTTP뿐만 아니라 FTP, Gopher, 심지어는 같은 방식으로 지역 파일에도 접근할 수 있다. 이 모듈이 제공하는 많은 유틸리티 기능에는 url 해석하기, 문자열을 url-안전 형태로 코드전환(encode)하기, 그리고 한참 긴 데이터 전송 중에 진행 표시를 제공하기가 있다.

미어켓을 사용하는 예제 하나

한 그룹의 고객(client)들이 있는데 그들이 최신 리눅스 소식을 이메일로 꾸준히 받아보기를 바라고 있다고 상상해보자. 우리는 짧은 스크립트를 작성할 수 있다. URLLIB를 사용하여 이 정보를 Meerkat으로부터 얻는다. 링크의 목록을 구축한다. 그리고 그 링크들을 나중에 전송하기 위해 파일에 저장한다. 미어켓(Meerkat)의 저자인 라엘 돈페스트(
Rael Dornfest)는 미어켓 API를 통해 우리 대신 대부분의 작업을 이미 완성해 놓았다. 남아있는 것은 요구를 구성하고, 링크를 해석하며, 나중에 전송하기 위해 그 결과를 저장하는 것 뿐이다.

단지 이것 때문에 사람들이 미어캣으로 전향하는 것일까? 이러한 "정보받기(passive)" 서비스를 제공하면 사람들은 그 정보를 한가할 때 볼 수 있다. 그리고 그 정보를 골라서 친숙한 형식(예를 들어 이메일)으로 저장할 수 있다. 월요일 아침에 메일함에서 뉴스들이 도착하기를 기다리기만 하면, 한 주간 "말려 올라간" 정보들을 하나도 놓치지 않을 것이다.

미어캣의 간략한 형식(minimal flavor)은 기사가 15개로 제한되므로 데이터를 놓칠 가능성을 줄이기 위해 우리는 스크립트를 (즉, Unix의 cron 작업 또는 NT의 AT 명령어를 사용하여) 매 시간 실행시킬 것이다. 여기에 우리가 사용할 url이 있다 (주의: 우리는 이 줄을 두 개의 줄로 나누어 화면에 표시했다. 이 URL을 사용한 결과는
여기에서 볼 수 있다).
http://www.oreillynet.com/meerkat/?p=5&t=1HOUR&_fl=minimal&_de=0&_ca=0&_ch=0&_da=0
이 코드는 지난 한 시간 동안에 있었던 모든 리눅스 이야기들(profile=5)을 끌어 와서, 데이터를 간략한 형식(minimal flavor)으로 보여준다. 설명도 없고, 범주정보도 없으며, 채널 정보, 데이터 정보도 없다. 우리는 또한 정규 표현식 모듈의 도움을 받아 링크 정보를 추출하고 출력결과를 추가 모드로 열려진 파일 객체로 방향전환할 것이다.

결론

우리는 겨우 이 모듈들의 표면만을 건드려 보았다. 웹 클라이언트 작업에 사용할 수 있는 것 말고도 파이썬에는 다른 많은 네트워크 프로그래밍 모듈을 사용할 수 있다. 웹 클라이언트 프로그래밍은 특히 방대한 양의 계산표형 테이터를 다룰 때 유용하게 사용할 수 있다. 최근의 한 전자 데이터 교환(EDI) 프로젝트에서 우리는 웹 클라이언트 프로그래밍을 사용하여 거추장스러운 독점 소프트웨어 패키지를 우회하였다. 갱신된 가격 정보를 웹으로부터 직접 얻어서 데이터베이스에 집어 넣었다. 그렇게 함으로써 우리는 많은 시간을 절약하고 좌절감을 극복할 수 있었다.

웹 클라이언트 프로그래밍은 웹 사이트의 구조와 견고성을 테스트하는 데에도 유용하게 사용될 수 있다. 일반적으로는 죽은 링크들을 점검하는 방법으로 사용된다. 표준 파이썬 배포본에는 이것에 대한 완전한 예제가 딸려온다. 이 예제는 URLLIB에 기초한다. Tk-기반의 프론트 엔드
[2] 모듈인 웹체커(Webchecker)는 파이썬 배포본의 tools 하부디렉토리 아래에서 찾아볼 수 있다. 또다른 파이썬 도구인 린봇(Linbot)은 URLLIB 모듈의 기능을 개선해 준다. 린봇으로 여러분은 웹 사이트의 문제를 모두 해결할 수 있다. 웹 사이트들이 점점 더 복잡해짐에 따라 웹 사이트의 질을 확인하기 위해서는 다른 웹 클라이언트 애플리케이션들이 필요하게 될 것이다.

웹 클라이언트 프로그래밍에는 함정이 하나 있다. 여러분의 프로그램은 페이지의 형식이 조금만 변경되어도 영향을 받는다. 반드시 웹 사이트가 오늘 데이터를 출력하는 방식이 내일도 그대로 유지된다고 장담할 수는 없다. 페이지의 형식이 바뀌면 프로그램도 바뀌어야 한다. 사람들이 XML에 그렇게 흥분하는 이유 중 하나가 바로 이것 때문이다. 웹에서 데이터에 태그를 붙여 의미를 주면 형식은 중요하지 않게 된다. XML 표준이 진화하고 범세계적으로 인정됨에 따라, 훨씬 더 쉽게 그리고 튼튼하게 XML 데이터를 처리하게 될 것이다.

우리가 여기에서 다룬 도구들에는 약간의 제한이 있다. HTTPLIB 모듈과 URLLIB모듈은 클라이언트-기반 작업에는 탁월하지만 오직 한 번에 한 개의 요청만을 처리할 수 있기 때문에 서버를 구축하는데 사용해서는 안된다. 비동기적인 처리방법을 제공하기 위해 샘 러싱(Sam Rushing)은 멋진 도구모음을 구축하였다. 이 도구모음은 asyncore.py 를 포함하여 표준 파이썬 배포본에 딸려 온다. 이 접근법을 사용하는 가장 강력한 예제는 조프(
ZOPE)이다. 조프는 애플리케이션 서버로서 샘 러싱(Sam Rushing)의 메듀사 엔진(Medusa engine)을 사용하여 구축한 빠른 http 서버를 포함하고 있다.

다음 기사에서는 XML과 웹 클라이언트 프로그래밍을 어떻게 XMLRPCLIB 모듈에 결합하는지에 대해 논의해볼 생각이다. XML을 사용하면 미어켓(Meerkat) API로부터 더욱 많은 기능을 짜낼 수 있다.
각주
[1] Hyper Text Transfer Protocol
[2] front end: 프론트 엔드
예) GUI는 front end 이며 구현된 기능들은 back end이다.

데이브 워너(Dave Warner)는 Federal Data Corporation사의 선임 프로그래머이자 데이터베이스 관리자(DBA)이다. 그는 P자로 시작하는 언어(Python, Perl, PowerBuilder)로 관계형 데이터베이스에 접근하는 방법을 연구하고 있다.

Copyright © 2008 Hanbit Media, Inc.

+ Recent posts