사용자 입력과 예외 처리

이번 장에서는 사용자가 질문을 하고 답하는 상호작용하는 방법에 대해서 알아봅니다.

  • 터미널 윈도우에서 다이얼로그에서 질문에 답하는 것을 해보고

  • 명령창에 사용자가 입력하고

  • 파일을 통해 데이터를 입출력하며

  • 그래픽 인터페이스를 통하여 사용자 입력을 배웁니다.

입출력

키보드 입력

다음 코드에서 C를 미리 지정하지 않고 사용자로부터 입력을 받아서 계산을 하려고 합니다.

C = 22
F = 9 / 5 * C + 32
print(F)

파이썬은 사용자 키보드로부터 입력을 받을 수 있도록 하는 input 함수를 제공합니다. input 함수의 출력값은 항상 문자열입니다.

다음과 같이 코드를 작성하고 실행하여 사용자 입력을 기다리는 부분이 생기고 입력하면 원하는 값이 문자열로 저장되는 것을 알 수 있습니다.

In [1]: C = input('C= ')
   ...: print(C)
   ...: print(type(C))
   ...: 
        33
        str

온도변환 코드를 다음과 같이 바꿔서 입력을 받을 수 있습니다.

In [2]: C = float(input('C= '))
   ...: F = 9 / 5 * C + 32
   ...: print(F)
   ...: 

input 함수의 반환값의 타입은 문자열이기 때문에 float를 이용해 실수형으로 변환해주어야 합니다.

명령창에서 입력

sys.argv 속성을 이용하여 스크립트 파일을 실행할 때 값들을 입력할 수 있습니다.

이것을 실습하려면 VSCode의 터미널에서 작업을 해야합니다.

  1. 다음과 같은 내용을 입력하고 c2f_cml.py라고 저장을 합니다.

    import sys
    
    C = float(sys.argv[1])
    F = 9.0 * C / 5 + 32
    print(F)
    
  2. 터미널에서 python c2f_cml.py 21이라고 입력하면 화씨 온도가 출력이 되는 것을 볼 수 있습니다. 여기서 주의할 것은 저장된 파일이 있는 디렉토리에서 명령을 실행시켜야 합니다.

sys.argv[0]python 명령어 다음에 나오는 c2f_cml.py 파일 이름이고 sys.argv[1]은 바로 다음에 나오는 21이 됩니다.

직접하기

  1. 다음 코드에서 tv0sys.argv를 이용해서 입력받아 계산하는 코드를 작성해보세요. 파일 이름은 ball2_cml.py라고 저장하세요.

    v0 = 5
    g = 9.81
    t = 0.6
    y = v0 * t - 0.5 * g * t ** 2
    print(y)
    

여러 개의 명령줄 인자들

다음과 같이 여러 개의 인자들을 입력받아 사용하려고 할 때

python add_all.py 1 3 5 -9.9
1 2 5 -9.9의 합은 -0.9입니다.

add_all.py 파일에 다음을 저장합니다.

import sys

s = 0
for arg in sys.argv[1:]:
  number = float(arg)
  s += number
print('{}의 합은 {:g}입니다'.format(sys.argv[1:], s))

문자열 메소드 join()을 이용해서 다음과 같이 바꿔 봅니다.

import sys

s = 0
for arg in sys.argv[1:]:
  number = float(arg)
  s += number
print('{}의 합은 {:g}입니다'.format(' '.join(sys.argv[1:]), s))

명령줄 입력할 때 빈 문자(스페이스)가 있으면 서로 다른 항목으로 인식합니다. 그렇다면 한 문장으로 입력할 문자열을 어떻게 해야할까요? 예를 들어 프로그램이름이 print_cml.py 라고 해보죠. sys.argv[1:]를 출력하면 다음과 같이 나옵니다.

python print_cml.py 21 하나의 문장을 입력하고 싶어요. 31.1
['21', '하나의', '문장을', '입력하고', '싶어요.', '31.1']

하나의 문자열로 입력하려면 따옴표를 이용하면 됩니다.

python print_cml.py 21 "하나의 문장을 입력하고 싶어요." 31.1
['21', '하나의 문장을 입력하고 싶어요.', '31.1']

사용자 입력 문자열을 객체화

eval 함수

eval 함수는 문자열을 입력받아 파이썬 식 expression으로 해석하여 결과를 파이썬 객체로 반환합니다.

In [3]: r = eval('1+2')
   ...: print('r: ', r)
   ...: print('type of r: ', type(r))
   ...: 
r:  3
type of r:  <class 'int'>

예를 들어 문자열 숫자 '2.5'도 실수형으로 변환시킵니다.

In [4]: r = eval('2.5')
   ...: print(r)
   ...: type(r)
   ...: 
2.5
Out[4]: float

다음과 같은 문자열도 리스트 객체로 변환시킵니다.

In [5]: r = eval('[1, 2, 3, 4]')
   ...: print(r)
   ...: type(r)
   ...: 
[1, 2, 3, 4]
Out[5]: list

튜플도 마찬가지입니다.

In [6]: r = eval('(1, 2)')
   ...: print(r)
   ...: type(r)
   ...: 
(1, 2)
Out[6]: tuple

함수도 변환합니다.

In [7]: import math
   ...: r = eval('math.sqrt(2)')
   ...: print(r)
   ...: type(r)
   ...: 
1.4142135623730951
Out[7]: float

따옴표로 감싼 문자열도 문자열로 변환합니다. 이때는 작은 따옴표와 큰 따옴표로 구분합니다.

In [8]: r = eval('"큰 따옴표로 문자열 객체 생성"')
   ...: print(r)
   ...: type(r)
   ...: 
큰 따옴표로 문자열 객체 생성
Out[8]: str

하지만 다음과 같이 하면 오류가 납니다. 왜냐면 문자열을 변수로 해석하기 때문입니다.

In [9]: r = eval('큰 따옴표 없는 문자열')
   ...: print(r)
   ...: type(r)
   ...: 
Traceback (most recent call last):

  File "C:\Users\dyoon\AppData\Roaming\Python\Python39\site-packages\IPython\core\interactiveshell.py", line 3441, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)

  File "<ipython-input-9-df4102f96b7c>", line 1, in <module>
    r = eval('큰 따옴표 없는 문자열')

  File "<string>", line 1
     따옴표 없는 문자열
      ^
SyntaxError: invalid syntax

사용자 입력에 응용

input 함수를 이용하여 표현식을 받을 때 eval은 유용하게 사용할 수 있습니다.

i1 = eval(input('Give input: '))
i2 = eval(input('Give input: '))
r = i1 + i2
print(f'{type(i1)} + {type(i2)} becomes {type(r)}\nwith value {r}')

Give input: 12
Give input: 31.
<class 'int'> + <class 'float'> becomes <class 'float'>
with value 43.0

다른 표현식을 입력해봅니다.

i1 = eval(input('Give input: '))
i2 = eval(input('Give input: '))
r = i1 + i2
print(f'{type(i1)} + {type(i2)} becomes {type(r)}\nwith value {r}')

Give input: [1, 2, 3]
Give input: [4, 5]
<class 'list'> + <class 'list'> becomes <class 'list'>
with value [1, 2, 3, 4, 5]

다음과 같이 문자열을 입력할 수 있습니다.

i1 = eval(input('Give input: '))
i2 = eval(input('Give input: '))
r = i1 + i2
print(f'{type(i1)} + {type(i2)} becomes {type(r)}\nwith value {r}')

Give input: '문자열 1'
Give input: "또다른 문자열"
<class 'str'> + <class 'str'> becomes <class 'str'>
with value 문자열 1또다른 문자열

수학 공식을 평가할 수 있습니다. 다음을 eval_formula.py로 저장합니다.

import math
import sys

formula = sys.argv[1]
x = eval(sys.argv[2])
result = eval(formula)
print('x= {}에서 {}를 계산하면 {}입니다.'.format(x, formula, result))

명령창에 다음과 같이 입력합니다.

python eval_formula.py "2 * math.sin(x) + 1" 3.14

파일 읽기

다음과 같은 내용을 현재 작업 디렉토리 안에 하위 폴더 src 밑에 data.txt 라는 이름으로 저장을 합니다.

안녕하세요.

다음은 data.txt 파일의 내용입니다.

줄단위로 읽기

파일을 읽는 것은 open 함수를 이용합니다. 모드를 r로 설정하면 읽는 모드가 되고 w로 설정하면 쓰기 모드가 됩니다.

infile = open('src\\data.txt', 'r')
infile.close()

파일을 열었으면 infile.close()를 이용하여 반드시 닫아서 자원 낭비를 막도록 합니다. 만일 인코딩 에러가 나면 다음과 같이 인코딩 encoding 옵션을 설정합니다.

infile = open('src\\data.txt', 'r', encoding='utf-8')

파일 내용을 읽는 기본적인 방법으로 for 문을 사용하는 것입니다.

In [10]: infile = open('src\\data.txt', 'r', encoding='utf-8')
   ....: for line in infile:
   ....:   print(line)
   ....: infile.close()
   ....: 
안녕하세요.



다음은 data.txt 파일의 내용입니다.

출력 결과에서 빈 줄이 원래보다 더 많은 것은 print 함수가 줄 끝에 항상 \n을 추가하기 때문입니다. 줄단위로 읽지 않고 한꺼번에 모든 줄을 읽어서 리스트로 저장할 수도 있습니다.

In [11]: infile = open('src\\data.txt', 'r', encoding='utf-8')
   ....: lines = infile.readlines()
   ....: infile.close()
   ....: print(lines)
   ....: 
['안녕하세요.\n', '\n', '다음은 data.txt 파일의 내용입니다.']

이것은 다음과 동일합니다.

lines = []
for line in infile:
  lines.append(line)

또는 리스트축약을 이용하면 다음과도 같습니다.

lines = [line for line in infile]

다음과 같은 내용을 src 디렉토리 밑에 numbers.txt로 저장합니다.

21.8
18.1
19
23
26
17.8

numbers.txt 파일의 숫자들을 읽어 평균을 계산해봅니다.

In [12]: numfile = open('src\\numbers.txt', 'r')
   ....: lines = [line for line in numfile]
   ....: numfile.close()
   ....: 
   ....: s = 0
   ....: for line in lines:
   ....:   s += float(line)
   ....: mean = s / len(lines)
   ....: print(mean)
   ....: 
20.95

리스트축약을 이용하면 다음과 같이 한 줄로 계산을 끝낼 수 있습니다.

In [13]: mean = sum([float(line) for line in lines]) / len(lines)
   ....: print(mean)
   ....: 
20.95

with 문을 이용한 파일 읽기

다음과 같이 with 문을 이용하여 파일을 읽을 수 있습니다. with 문을 이용하면 자동으로 파일을 닫습니다.

In [14]: with open('src\\data.txt', 'r', encoding='utf-8') as f:
   ....:   for line in f:
   ....:     print(line, end='')
   ....: 
안녕하세요.

다음은 data.txt 파일의 내용입니다.

read() 함수를 이용하여 파일 전체를 읽을 수 있습니다.

In [15]: infile = open('src\\data.txt', 'r', encoding='utf-8')
   ....: filestr = infile.read()
   ....: infile.close()
   ....: filestr
   ....: 
Out[15]: '안녕하세요.\n\n다음은 data.txt 파일의 내용입니다.'

결과를 보면 \n\n이 출력되는 것을 알 수 있습니다. 새로운 줄을 표시하는 특수문자열입니다. 이것을 print 문으로 출력하면 새로운 줄로 출력되는 것을 알 수 있습니다.

In [16]: print(filestr)
안녕하세요.

다음은 data.txt 파일의 내용입니다.

numbers.txt 파일을 읽어서 문자열 전체를 출력해봅니다.

In [17]: with open('src\\numbers.txt', 'r') as f:
   ....:   numstr = f.read()
   ....: 
   ....: numstr
   ....: 
Out[17]: '21.8\n18.1\n19\n23\n26\n17.8'

문자열 split() 메소드를 이용하여 새로운 줄을 기준으로 분리하여 리스트로 만들 수 있습니다.

In [18]: numstr.split()
Out[18]: ['21.8', '18.1', '19', '23', '26', '17.8']

split 메소드 인자로 구분 문자열을 넘겨주면 그 문자열을 기준으로 분리합니다. 기본은 빈 문자열을 기준으로 구분합니다. 다음은 ,를 구분 문자로 해서 분리한 것입니다.

In [19]: 'a, b, c, d'.split(',')
Out[19]: ['a', ' b', ' c', ' d']

숫자들의 합을 다음과 같이도 구할 수 있습니다.

In [20]: words = numstr.split()
   ....: nums = [float(word) for word in words]
   ....: mean = sum(nums) / len(nums)
   ....: print(mean)
   ....: 
20.95

문자와 숫자가 섞인 파일 읽기

다음과 같은 내용을 src\\rainfall.dat 라고 저장을 합니다.

Average rainfall(in mm) in Rome: 1188 months between 1782 and 1970
Jan 81.2
Feb 63.2
Mar 70.3
Apr 55.7
May 53.0
Jun 36.4
Jul 17.5
Aug 27.5
Sep 60.9
Oct 117.7
Nov 111.0
Dec 97.9
Year 792.9

파일을 읽어 매월, 강수량을 따로 저장하려고 합니다.

In [21]: def extract_data(filename):
   ....:   with open(filename, 'r') as infile:
   ....:     infile.readline()         # 첫 줄 건너뛰기
   ....:     months = []               # 월(month) 리스트
   ....:     rainfall = []             # 강수량 리스트
   ....:     for line in infile:
   ....:       words = line.split()
   ....:       months.append(words[0])     # 월 저장
   ....:       rainfall.append(words[1])   # 강수량 저장
   ....: 
   ....:   months = months[:-1]            # 마지막 줄 제외
   ....:   annual_avg = rainfall[-1]       # 년 평균 강수량
   ....:   rainfall = rainfall[:-1]        # 마지막 줄 제외
   ....:   return months, rainfall, annual_avg
   ....: 
   ....: months, values, avg = extract_data('src\\rainfall.dat')
   ....: print('월평균 강수량')
   ....: for month, value in zip(months, values):
   ....:   print(f'{month}, {value}')
   ....: print(f'년평균 강수량: {avg}')
   ....: 
월평균 강수량
Jan, 81.2
Feb, 63.2
Mar, 70.3
Apr, 55.7
May, 53.0
Jun, 36.4
Jul, 17.5
Aug, 27.5
Sep, 60.9
Oct, 117.7
Nov, 111.0
Dec, 97.9
년평균 강수량: 792.9

다음과 같이 더 간결하게 작성할 수도 있습니다.

extract_data 함수
def extract_data(filename):
  with open(filename, 'r') as infile:
    infile.readline() # 첫줄 무시
    data = [line.split() for line in infile]
  annual_avg = data[-1][1]
  data = [(m, float(r)) for m, r in data[:-1]]
  return data, annual_avg

직접하기

  1. 위에서 작성한 extract_data 함수를 수정하여 월평균 강수량의 합과 평균을 직접 구하여 반환하도록 프로그램을 작성하세요.

파일에 쓰기

다음과 같이 파일을 열 때 모드를 w 쓰기모드로 설정하면 파일에 쓸 수 있습니다.

outfile = open(filename, 'w')

또는 모드를 a로 하면 기존 파일의 뒤에 추가할 수도 있습니다.

outfile = open(filename, 'a')

다음과 같은 리스트를 파일에 표형식으로 저장하려고 합니다.

[[ 0.75, 0.29619813, -0.29619813, -0.75 ],
 [ 0.29619813, 0.11697778, -0.11697778,  -0.29619813],
 [-0.29619813, -0.11697778, 0.11697778,  0.29619813],
[-0.75, -0.29619813, 0.29619813, 0.75 ]]
In [22]: data = [[ 0.75, 0.29619813, -0.29619813, -0.75 ],
   ....:         [ 0.29619813, 0.11697778, -0.11697778, -0.29619813],
   ....:         [-0.29619813, -0.11697778, 0.11697778, 0.29619813],
   ....:         [-0.75, -0.29619813, 0.29619813, 0.75 ]]
   ....: 
   ....: with open('src\\tmp_table.dat', 'w') as f:
   ....:   for row in data:
   ....:     for col in row:
   ....:       f.write('{:14.8f}'.format(col))
   ....:     f.write('\n')
   ....: 

파일에 다음과 같이 저장됩니다.

 0.75000000    0.29619813   -0.29619813   -0.75000000
 0.29619813    0.11697778   -0.11697778   -0.29619813
-0.29619813   -0.11697778    0.11697778    0.29619813
-0.75000000   -0.29619813    0.29619813    0.75000000

예외 처리

다음과 같이 input 을 이용해서 나이를 입력을 받을 때 정수가 아닌 문자열을 입력하면 프로그램이 오류를 내며 종료됩니다.

정수형을 입력하는 곳에 안녕이란 문자열을 입력하여 오류를 냅니다.

age = int(input('나이를 입력하세요:'))
나이를 입력하세요:안녕
--------------------------------------------------------------------------
ValueError                                Traceback (most recent call ast)
<ipython-input-3-822aab42fea0> in <module>
----> 1 age = int(input('나이를 입력하세요:'))

ValueError: invalid literal for int() with base 10: '안녕'

예외가 난 곳 이후로는 더 이상 실행이 되질 않고 프로그램이 종료되어 결과를 얻을 수 없습니다. 이렇듯 잘못된 예외가 예상이 되는 곳에 예외 상황이 발생될 때 프로그램을 종료하지 않고 처리를 하고 다음 과정으로 넘어갈 수 있게 한 것이 try ... except 문장입니다.

try:
  <예외가 예상되는 문장들>
except:
  <예외가 발생했을  처리할 문장들>

위의 나이를 입력받는 곳에 예외처리를 해봅니다. 정수가 아닌 입력이 들어오면 예외가 발생되어 except 문을 처리합니다.

In [23]: try:
   ....:   age = int(input('나이를 입력하세요:'))
   ....:   print('나이: {}'.format(age))
   ....: except:
   ....:   print('숫자를 입력해주세요.')
   ....: 

예외에도 여러 가지 유형이 있습니다. 다음은 0으로 나눌 때 발생하는 ZeroDivisionError 입니다.

In [24]: 12 / 0
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-24-df982fec2812> in <module>
----> 1 12 / 0

ZeroDivisionError: division by zero

다음은 없는 인덱스를 참조할 때 발생하는 IndexError 입니다.

In [25]: lst = [1, 2]
   ....: lst[2]
   ....: 
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-25-8ae804f81707> in <module>
      1 lst = [1, 2]
----> 2 lst[2]

IndexError: list index out of range

여러 예외가 발생할 예외 유형에 따라 각각 다르게 처리할 수 있습니다.

다음은 두 숫자를 입력받아 나눗셈을 하는 코드입니다. 분모가 0이면 ZeroDivisionError를 발생시키고 숫자가 아닌 것이 입력되면 ValueError를 발생시켜 각각에 대응해서 처리를 하도록 한 것입니다.

In [26]: xy = input('두 숫자를 입력하세요:').split()
   ....: try:
   ....:   x, y = [float(num) for num in xy]
   ....:   print('x / y = {}'.format(x / y))
   ....: except ZeroDivisionError:
   ....:   print('0으로 나누는 예외가 발생했습니다')
   ....: except ValueError:
   ....:   print('잘못된 값을 입력했습니다. 두 숫자를 입력하세요.')
   ....: 

잘못된 문법에 대해선 SyntaxError를 발생시킵니다.

In [27]: forr i in [1, 2]:
   ....:   print(i)
   ....: 
  File "<ipython-input-27-cf113461a803>", line 1
    forr i in [1, 2]:
         ^
SyntaxError: invalid syntax

실수형 숫자와 문자열을 곱하면 TypeError가 발생됩니다.

In [28]: 1.2 * 'Hello'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-28-13d8404cc1dd> in <module>
----> 1 1.2 * 'Hello'

TypeError: can't multiply sequence by non-int of type 'float'

하지만 양의 정수와 문자열을 곱하기는 잘 작동합니다. 즉, 문자열을 정수배만큼 반복해서 더합니다.

In [29]: 10 * '안녕!'
Out[29]: '안녕!안녕!안녕!안녕!안녕!안녕!안녕!안녕!안녕!안녕!'

예외 발생 시키기

사용자가 필요할 때에 원하는 예외를 발생시킬 수 있습니다. 다음은 0보다 큰 숫자가 입력되면 예외를 발생시키는 문장입니다.

In [30]: x = 2
   ....: if x > 0:
   ....:   raise ValueError('0보다 작은 숫자를 입력하세요')
   ....: 
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-30-b7922d6b0887> in <module>
      1 x = 2
      2 if x > 0:
----> 3   raise ValueError('0보다 작은 숫자를 입력하세요')

ValueError: 0보다 작은 숫자를 입력하세요

그래픽 사용자 인터페이스

tkinter(Tk interface)는 Tk를 파이썬에서 사용할 수 있도록하는 표준 파이썬 GUI 모듈입니다. Tk는 GUI 프로그래밍을 할 수 있도록 하는 도구입니다.

Tk 프로그래밍에서 중요한 개념은 위젯(widget), 배치 관리(geometry management), 이벤트 처리(event handling)입니다. 위젯은 화면에 보이는 그래픽 성분들이고 배치관리는 이러한 위젯을 어느 부분에 위치시키고, 어떻게 보이게 할지를 관리하는 것입니다. 위젯들 간의 상호 작용을 어떻게 할 것인지를 정하는 것이 이벤트 처리입니다. 가령, 버튼을 눌렀을 때 무엇을 해야 하는지, 스크롤바를 움직였을 때 어떻게 보여야 하는지 등에 대해서 설정해야 합니다. 이러한 기능은 command 또는 event binding을 통해서 정할 수 있습니다.

import tkinter as tk

# 메인 창(윈도우)
root = tk.Tk()

# 섭씨 레이블
CUnitLabel = tk.Label(root, text='섭씨')
CUnitLabel.pack(side='left')

# 섭씨 입력
CEntry = tk.Entry(root, width=4)
CEntry.pack(side='left')

# 섭씨를 화씨로 변환
def calculate():
  C = float(CEntry.get())
  F = (9 / 5) * C + 32
  FLabel.config(text='{}'.format(F)) # 객체 생성후 변경하기 위해 config

# 변환 버튼
ComputeBtn = tk.Button(root, text=' 는 ', command=calculate)
ComputeBtn.pack(side='left', padx=4)

# 화씨 레이블
FUnitLabel = tk.Label(root, text='화씨')
FUnitLabel.pack(side='left')

# 화씨 출력 레이블
FLabel = tk.Label(root, width=4)
FLabel.pack(side='left')

# 메인 창의 이벤트 처리
root.mainloop()

위젯(widget)

위젯(window gadget)이란 화면에 보이는 그래픽 성분들 모두를 의미한다고 할 수 있습니다. 예를 들면 버튼, 텍스튼 입력, 레이블, 첵크 박스, 라디오 버튼, 콤보 버튼, 스크롤바, 프레임 등이 있습니다. 위젯은 콘트롤(control) 또는 창(window)이라고도 불립니다.

뿌리 창(root window)

모든 위젯은 계층 구조 형식으로 표현됩니다. GUI 프로그래밍은 뿌리(root) 위젯(또는 창)이 만들어지고 그 안에 원하는 모든 위젯들을 넣어서 만들어 집니다. 뿌리 창은 다음과 같이 생성합니다.

import tkinter as tk

root = tk.Tk()

root.mainloop()
  • tk.Tk()를 이용해서 뿌리 창 인스턴스를 생성합니다.

  • mainloop() 메소드는 뿌리 창의 이벤트를 처리하는 것으로 이 메소드가 있어야지만 창이 보이게 됩니다. 창 오른쪽 위에 있는 닫기 버튼을 클릭하면 mainloop()를 끝냅니다. 기본 뿌리 창은 아이콘, 제목, 축소, 확대, 닫기 버튼이 달려있는 창입니다.

위젯 만들기

위젯을 만들 때 뿌리(root) 창(또는 위젯)을 제외하고는 부모(parent or master) 위젯을 지정해주어야 합니다. 모든 위젯은 뿌리 위젯 안에 포함되어 만들어집니다. 부모 위젯 안에 포함되는 위젯을 자식(child or slave) 위젯이라고 부릅니다.

위젯을 만드는 문법은 다음과 같습니다.

위젯변수이름 = tkinter.위젯이름(부모위젯, **옵션)

다음은 루트 창에 레이블(label)과 버튼을 추가하는 예입니다.

import tkinter as tk

root = tk.Tk()

label = tk.Label(root, text='레이블 위젯입니다.')
label.pack()

button = tk.Button(root, text='버튼')
button.pack()

root.mainloop()
  • Label() 첫번째 인자 뿌리창은 레이블의 부모 위젯을 나타내고 두번째 인자 text는 레이블에 나타날 문자열을 의미합니다.

  • Button() 메소드의 첫번째 인자는 레이블과 마찬가지로 버튼 위젯이 포함될 부모 위젯을 가리키고 두번째 인자는 버튼에 표시될 문자열을 의미합니다.

  • pack() 메소드는 레이블과 버튼 위젯이 표시될 위치를 지정하는 것으로 배치 관리자에서 자세히 설명합니다.

모듈

가끔씩 이전에 만들었던 함수들을 사용할 필요가 생깁니다. 이럴때 할 수 있는 가장 먼저 떠오르는 것으로 코드를 복사하여 새로운 코드에 복사하는 것일 것입니다. 하지만 이러한 일도 지루하고 힘든 작업입니다. 이렇듯 자신이 만든 코드를 다음에 사용할 수 있도록 하는 것이 모듈(module)입니다.

다른 사람들이 만든 모듈을 불러와서 사용하는 방법을 알고 있습니다. 가령 수학 모듈 mathsin 함수를 사용하기 위해서는 다음과 같이 합니다.

import math

math.sin(math.pi / 2)

지금부터는 자신의 모듈을 만들어 사용해보겠습니다.

원리합계

\[A = A_0 \left( 1 + \frac{p}{360 \cdot 100} \right)^n\]

여기서 \(A_0\) 는 원금이고 \(p\) 퍼센트로 는 연이자율입니다. \(n\) 은 경과된 날 수를 의미합니다. 다음은 각각의 값을 계산한 것입니다.

\[\begin{split}A_0 &= A \left( 1 + \frac{p}{360 \cdot 100} \right)^{-n} \\ n &= \frac{\ln (A / A_0)}{\ln \left( 1 + \frac{p}{360 \cdot 100} \right)} \\ p &= 360 \cdot 100 \left[\left(\frac{A}{A_0}\right)^{1/n} - 1 \right]\end{split}\]

각각의 값을 구하는 함수를 만들어 src 디렉토리 밑에 interest.py 파일 안에 저장합니다.

import math

def present_amount(A0, p, n):
  return A0 * (1 + p / (360 * 100)) ** n

def initial_amount(A, p, n):
  return A * (1 + p / (360 * 100)) ** (-n)

def days(A0, A, p):
  return math.log(A / A0) / math.log(1 + p / (360 * 100))

def annual_rate(A0, A, n):
  return 360 * 100 * ((A / A0) ** (1 / n) - 1)

위의 모듈을 불러와서 사용해봅니다.

In [31]: import sys
   ....: sys.path.append('.')
   ....: 
   ....: from src import interest
   ....: 
   ....: A0 = 1
   ....: A = 2
   ....: p = 5
   ....: 
   ....: n = interest.days(A0, 2, p)
   ....: years = n / 365
   ....: print('원금의 2배가 된 해는 {:.1f}년 후입니다.'.format(years))
   ....: 
원금의 2배가 된 해는 13.7년 후입니다.

시험 구역

모듈이 올바르게 작동하는지를 시험하는 방법으로 간단하게 모듈 안에 시험 코드를 작성하고 실행해보는 방법이 있습니다.

if __name__ == '__main__':
  <시험할 문장들>

__name__은 다른 모듈에서 부를 때는 __name__을 포함하고 있는 모듈의 이름을 반환하고 모듈이 명령창에서 실행될 때는 __main__을 반환합니다.

다음과 같이 간단한 모듈 mymodule.py을 작성하여 src 디렉토리에 저장합니다. 이 모듈을 명령창에서 실행도 해보고, 다른 모듈에서도 import를 해보기를 바랍니다.

def add1(x):
  return x + 1

if __name__ == '__main__':
  print('프로그램이 실행되었습니다.')
  import sys
  print(add1(float(sys.argv[1])))

명령창에서 실행하려면 먼저 mymodule.py가 위치한 곳으로 이동한 후에, 다음과 같이 입력합니다.

python mymodule.py 100

import를 이용하려면 다음과 같이 하면 됩니다.

In [32]: from src import mymodule
   ....: 
   ....: mymodule.add1(100)
   ....: 
Out[32]: 101

연습문제

  1. 화씨를 사용자로부터 입력받아 섭씨를 출력하는 프로그램을 작성하시오.

  2. 위의 문제를 수정하여 명령창에서 실행하여 화씨를 입력받아 섭씨를 출력하는 프로그램을 작성하시오.

  3. 위 1번 문제를 수정하여 다음과 같은 내용을 담고 있는 파일 temperature.dat로부터 화씨를 입력받아 섭씨를 출력하는 프로그램을 작성하시오.

    온도 자료
    ----------
    
    화씨 온도: 67.5
    
  4. 다음과 같은 내용을 담은 파일 fdeg.dat를 읽어 화씨를 리스트로 저장한 후 화씨와 섭씨를 두 열로 출력하는 프로그램을 작성하시오.

    온도 자료
    ----------
    
    화씨 온도: 67.5
    화씨 온도: 66.0
    화씨 온도: 89.1
    화씨 온도: 37.9
    화씨 온도: 47.3
    화씨 온도: 66.2
    화씨 온도: 102.8
    
  5. 2번 문제를 수정하여 화씨가 입력이 안되었을 경우 발생할 수 있는 예외를 try-except 문을 이용하여 처리하는 프로그램을 작성하시오.

  6. 사용자로부터 입력을 받은 것에 대한 객체 타입과 값을 출력하는 프로그램을 작성하시오. 다음 5가지 타입을 입력하고 출력되는 것을 확인하시오.

    정수, 실수, 복소수, 리스트, 튜플
    
  7. 다음과 같은 내용을 아래 문제에 따라 저장하고 실행하세요.

    print("hello python!")
    
    1. 한글(HWP) 프로그램에서 위의 내용을 입력하고 hello.hwp 저장한 후 파이썬 명령창에서 실행해 보세요. 어떤 에러가 발생하는지 살피고 이유가 무엇인지를 말해보세요.

    2. 한글 프로그램에서 위의 내용을 다른 이름으로 저장을 눌러 텍스트 hello.txt 파일로 저장하고 명령창에서 실행해 보세요. 저장할 때 문자코드선택(인코딩)UTF-8으로 선택하세요. 어떤 일이 발생하는지를 살피고 이유를 말해보세요.

  8. \(y(t) = v_0t - \frac{1}{2}gt^2\) 을 계산하는 다음 프로그램

    v0 = 3; g = 9.81; t = 0.6
    y = v0 * t - 0.5 * g * t ** 2
    print(y)
    

    을 수정하여 t=?, v0=?을 출력하여 사용자로부터 입력받아 y 값을 출력하는 프로그램을 작성하시오.

  9. 위 문제를 수정하여 명령창에서 실행하여 실행 인자로 tv0 를 입력받아 출력하는 프로그램을 작성하시오.

  10. 위 9번 문제를 수정하여 실행 인자가 입력되지 않았을 때 예외 처리를 하여 사용자로부터 다시 입력받도록 프로그램을 작성하시오. 즉, except IndexError 구역에 input 함수를 이용해서 사용자로부터 누락된 입력을 받도록 프로그램을 작성하세요.

연습문제 풀이