본문 바로가기

Etc/Python

#10 모듈, 패키지, 예외처리

 모듈



 모듈은 함수나 변수, 클래스를 모아놓은 파일로 c언어의 헤더파일과 유사한 느낌이다.


 모듈 만들고 불러오기


 모듈을 이해하기위해 간단히 모듈을 만들고 불러와보자


# mod1.py def sum(a, b): return a + b


 여태 해왔던 것들과 다름없는 이 파일. 모듈을 mod1.py로 특정 디렉토리에 저장한다. ( -> C:\Python)

이제 이렇게 sum함수가 들어가있는 모듈을 파이썬으로 불러와서 쓰려면 다음과 같이 모듈저장한 디렉토리에서 python(대화형인터프리터)실행한다.


C:\Users> cd C:\Python C:\Python> python Python 3.5.1 (v3.5.1:37a07cee5969, Dec 6 2015, 01:54:25) [MSC v.1900 64 bit (AM... Type "help", "copyright", "credits" or "license" for more information. >>>

 반드시 모듈을 저장한 디렉토리에서 진행해야한다. (예에서는 C:\Python)


 자 이제 사용해보면 모듈import를 이용해 불러오고 모듈안의 함수사용해본다.

>>> import mod1 >>> print(mod1.sum(3,4)) 

7

 단, mod1.py가 아닌 mod1으로 확장자는 빼야한다.


 import는 이미 만들어진 파이썬 모듈을 사용할 수 있게 불러오는 명령어이다. mod1.py의 sum함수를 쓰려면 위의 예시처럼 "모듈이름.함수이름"으로 사용하면 된다. 여기서 import현 디렉토리, 파이썬 라이브러리가 저장된 디렉토리에 있는 모듈만 사용할 수 있다.


 import의 기본 사용법은 그냥모듈 이름을 써주면 된다. : import 모듈이름(:확장자를 제거한 모듈 이름)


 모듈 함수를 사용하는 또 다른 방법


 하다보면 귀찮게 mod1.sum 이런식으로말고 그냥 sum으로 사용하고 싶기도 할 것이다. 이럴때는 "from 모듈이름 import 모듈함수"를 쓰면 된다.


>>> from mod1 import sum

>>> sum(3, 4) 7


 만약 함수가 sum 뿐만아니라 div 등이 있다면 어떻게 해야할까.

여러개를 추가하는 것은 콤마로 이어 쓰는 것과, 모두를 추가하기위해 *을 쓰는 방법이 있다.


 1. 콤마로 여럿 이어쓰기

from mod1 import sum, safe_sum


 2. *로 모두를 추가하기.

from mod1 import *

 *는 리눅스에서 와일드카드를 연상케하는 데 자세한건 이후 정규표현식에서 배울것이다.


 if __name__== "__main__" : 


 이게 무엇인가 싶지만 만약 name이 main이면 뒤에 붙어있을 내용을 실행해주는것같은데 결론만 말하자면 맞다.


 이것이 필요한 까닭은 다음과같은 문제에서 일어난다. 자 일단 mod1.py에 다음처럼 출력문을 추가한다.

# mod1.py

def sum(a, b): return a+b def safe_sum(a, b): if type(a) != type(b): print("더할수 있는 것이 아닙니다.") return else: result = sum(a, b) return result print(safe_sum('a', 1)) print(safe_sum(1, 4)) print(sum(10, 10.4))


 이제 이것을 위에서 했던것과같이 C:\Python에 있다면 다음처럼 실행할 수 있다.


C:\Python>python mod1.py

더할 수 있는 것이 아닙니다. None 5 20.4


 하지만 이제 이것을 모듈로 import하면 문제가 생기는게 출력문이 import만 해도 실행되버리는 것이다.

C:\WINDOWS> cd C:\Python

C:\Python>python >>> import mod1 더할 수 있는 것이 아닙니다. None 5 20.4


 그래서 출력문앞에 위에서 봤던 문장을 붙여준다.


if __name__ == "__main__":

print(safe_sum('a', 1)) print(safe_sum(1, 4)) print(sum(10, 10.4))

 이렇게 출력문을 감싸면 파일을 직접실행하면 if가 참이되지만 모듈을 불러오면 if가 거짓이되어 출력이 안된다. 테스트해보면 실제로 import로는 출력이 안된다.


 클래스, 변수 등을 포함한 모듈


 여태한 모듈은 함수만 넣었지만 변수나 클래스도 넣고 데리고 올 수 있다.


# mod2.py PI = 3.141592 class Math: def solv(self, r): return PI * (r ** 2) def sum(a, b): return a+b if __name__ == "__main__": print(PI) a = Math() print(a.solv(2))  

print(sum(PI , 4.4))

 보면 변수, 클래스, 함수 그리고 if문으로 출력문을 감싸놓은 것을 모두 포함한 모듈을 볼 수 있다.


 import에 성공했다면 클래스나 변수는 다음처럼 사용할 수 있다.


>>> print(mod2.PI)

3.141592

>>>

>>> a = mod2.Math()

>>> print(a.solv(2))

12.566368

>>>

>>> print(mod2.sum(mod2.PI, 4.4)) 7.541592


 이제까지 모듈을 불러올때 대화형 인터프리터를 사용했지만 이는 바로바로 확인하기 위해서이고 그냥 파일에 작성해서 사용할 수 있다.

# modtest.py

import mod2 result = mod2.sum(3, 4) print(result)




 패키지


 일단 패키지는 dot(.)을 이용해 파이썬 모듈을 계층적(디렉토리 구조)으로 관리할 수 있게 해준다. 예를 들어 모듈명이 A.B인 경우 A는 패키지명, B는 A패키지의 B 모듈이 되는 것이다.


 파이썬 패키지는 말은 어렵게 느껴질지 모르겠지만 디렉토리안에 존재하는 여러 파이썬 파일을 연결해주는 고리라는 느낌으로 받아들이면 좋을 것 같다.


 가상의 game 패키지의 예

game/

__init__.py sound/ __init__.py echo.py wav.py graphic/ __init__.py screen.py render.py play/ __init__.py run.py test.py


 game, sound, graphic, play는 디렉토리명이고 .py 확장자를 가지는 파일은 파이썬 모듈이다. game 디렉토리가 이 패키지의 루트 디렉토리이고 sound, graphic, play는 서브 디렉토리이다. (*__init__.py는 조금 특이한 용도로 사용되는데 이는 뒤에 다룰 것이다.)


#이렇게 패키지 구조로 파이썬 프로그램을 만들면 보기 편하기 때문에 공동작업, 유지보수에 유리하다. 간단한 프로그램을 만드는게 아니라면 이러한 구조로 설계하도록 노력하는 것이 좋다.


 패키지 만들고 실행하기


 패키지 기본 구성 요소 준비하기


 0. C:/python 이라는 디렉토리 밑에 game 및 서브 디렉토리들을 만들고 .py 파일들을 다음과 같이 만들어볼것이다.

C:/Python/game/__init__.py C:/Python/game/sound/__init__.py C:/Python/game/sound/echo.py C:/Python/game/graphic/__init__.py C:/Python/game/graphic/render.py


 1. 일단 각 디렉토리의 __init__.py 파일은 만들어 놓기만 하고 내용은 일단 비워놓는다.


 2. echo.py 파일을 다음처럼 만든다.

# echo.py def echo_test(): print ("echo")


 3. render.py 파일은 다음처럼

# render.py def render_test(): print ("render")


 4. 이제 이렇게 만들고 있는 game 패키지를 참조할 수 있도록 도스 창에서 set 명령을 이용해 PYTHONPATH 환경 변수에 C:/Python 디렉토리를 추가한 뒤 Interactive shell(파이썬 인터프리터)를 실행한다.

C:\> set PYTHONPATH=C:/Python

C:\> python Python 3.5.1 (v3.5.1:37a07cee5969, Dec 6 2015, 01:54:25) [MSC v.1900 64 bit (AM... Type "help", "copyright", "credits" or "license" for more information. >>>


 이제 기본 구성요소 준비는 끝났다.



 패키지 안의 함수 실행하기


 이제 이렇게 만든 패키지를 이용해서 echo.py파일의 echo_test라는 함수를 실행해 보도록 한다. 패키지 안의 함수를 실행하는 방법은 3가지다.

( 아래 예제들은 import 예제이므로 하나의 예제를 실행하고나서 다음 예제를 할땐 반드시 인터프리터를 종료후 재 실행해야한다. Ctrl+z)


 1) echo 모듈을 import해 실행

>>> import game.sound.echo >>> game.sound.echo.echo_test() echo


 2) echo 모듈있는 디렉토리까지를 from ... import해 실행

>>> from game.sound import echo >>> echo.echo_test() echo


 3) echo 모듈의 echo_test 함수를 직접 import해 실행

>>> from game.sound.echo import echo_test >>> echo_test() echo


 +) 하지만 다음 처럼 echo_test 함수를 사용하는 건 불가능하다 

>>> import game >>> game.sound.echo.echo_test() Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'module' object has no attribute 'sound'


 import game을 수행하면 game 디렉토리의 모듈 또는 game 디렉토리의 __init__.py 에 정의된 것들만 참조할 수 있다.


 +) 또 다음처럼 echo_test 함수를 사용하는 것도 불가능하다.

>>> import game.sound.echo.echo_test Traceback (most recent call last): File "<stdin>", line 1, in <module> ImportError: No module named echo_test

 도트 연산자(.)를 사용해서 import a.b.c 처럼 import할때 가장 마지막 항목인 c는 반드시 모듈 또는 패키지여야만 한다. 



  __init__.py 의 용도


 __init__.py 파일은 해당 디렉토리가 패키지의 일부임을 알려주는 역할을 한다. 만약 game, sound, graphic 등 패키지에 포함된 디렉토리에 __init__.py 파일이 없다면 패키지로 인식되질 않는다.

(※ python3.3 부터는 __init__.py 파일이 없어도 패키지로 인식이 되지만 하위 버전 호환을 위해 생성해주는 것이 바람직하다.)



  all의 용도


 다음을 따라 해보자.

>>> from game.sound import *
>>> echo.echo_test()
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
NameError: name 'echo' is not defined

 오류가 발생했다. 분명 game.sound 패키지에서 모든 것 (*)을 import했으니 echo 모듈을 사용할 수 있어야 할 것 같은데 echo라는 이름이 정의되지 않았다며 NameError(이름 오류)가 발생했다.

 이렇게 특정 디렉토리의 모듈을 *를 이용해 import할 땐 다음과 같이 해당 디렉토리의 __init__.py 파일에 __all__ 라는 변수를 설정하고 import할 수 있는 모듈을 정의해 주어야 한다.

# C:/Python/game/sound/__init__.py
__all__ = ['echo']

 여기서 __all__ 이 의미하는 것은 sound 디렉토리에서*기호를 이용하여 import할 경우 이곳에 정의된 echo 모듈만 import된다는 의미이다.

(※ 착각하기 쉬운데 from game.sound.echo import * 는 __all__과 상관없이 무조건 import된다. 이렇게 __all__ 과 상관없이 무조건 import되는 경우는 from a.b.c import * 에서 from의 마지막 항목인 c가 모듈인 경우이다.)

위와 같이  __init__.py 파일을 변경한 후 위 예제를 수행하면 원하던 결과가 출력되는 것을 확인할 수 있다.

>>> from game.sound import *
>>> echo.echo_test()
echo


 relative 패키지


 만약 graphic 디렉토리의 render.py 모듈이 sound 디렉터리의 echo.py 모듈을 사용하고 싶다면 어떻게 해야 할까? 다음과 같이 render.py를 수정하면 가능하다.

# render.py
from game.sound.echo import echo_test
def render_test():
    print ("render")
    echo_test()

from game.sound.echo import echo_test라는 문장을 추가하여 echo_test() 함수를 사용할 수 있도록 수정했다.

이렇게 수정한 후 다음과 같이 수행해 보자.

>>> from game.graphic.render import render_test
>>> render_test()
render
echo

이상 없이 잘 수행된다.

위 예제처럼 from game.sound.echo import echo_test와 같이 전체 경로를 이용하여 import할 수도 있지만 다음과 같이 relative하게 import하는 것도 가능하다.

(※ 이 기능은 Python 2.5부터 지원되기 시작하였다.)

# render.py
from ..sound.echo import echo_test

def render_test():
    print ("render")
    echo_test()

 from game.sound.echo import echo_test가 from ..sound.echo import echo_test로 변경되었다. 여기서  ..은 리눅스 등에서 흔히 봐왔을텐데 역시나 부모 디렉토리를 의미한다. graphic과 sound 디렉토리는 동일한 깊이(depth)이므로 부모 디렉토리( ..)를 이용하여 위와 같은 import가 가능한 것이다.

relative한 접근자에는 다음과 같은 것들이 있다.

 .. : 부모 디렉터리

  . : 현재 디렉터리

 ..과 같은 relative한 접근자는 render.py와 같이 모듈 안에서만 사용해야 한

다. 파이썬 인터프리터에서 relative한 접근자를 사용하면 "SystemError: cannot perform relative import"와 같은 오류가 발생한다.


 예외처리



 파이썬은 오류를 관리할 수 있게 해주는 구문이 존재한다.


 오류 예외 처리 기법


 try, except문


try:

... except [발생 오류[as 오류 메시지 변수]]: ...


 try 블록 수행 중 오류가 발생하면 except 블록이 수행된다.


except [발생 오류 [as 오류 메시지 변수]]:


 위의 except 구문을 보면 [ ] 기호로 나타냈는데, 이 기호는 괄호 안의 내용을 생략할 수 있다는 관례적인 표기법이다. 

즉. except 구문은 다음처럼 3가지로 사용할 수 있다.


 1. try, except만 쓰는 방법

try:

... except: ...

 이 경우는 오류 종류에 상관없이 오류가 발생하기만 하면 except 블록을 수행한다.


 2. 발생 오류만 포함한 except문

try:

... except 발생 오류: ...

 이 경우에는 오류가 발생했을 때 except문에 미리 정해 놓은 오류 이름과 일치할 때만 except 블록을 수행한다.


 3. 발생 오류와 오류 메세지 변수까지 포함한 except문

try:

... except 발생 오류 as 오류 메시지 변수: ...

 이 경우는 두 번째 경우에서 오류 메세지의 내용까지 알고 싶을 때 사용하는 방법이다.


 이 방법은 다음의 예와같이 사용 할 수 있다.

try: 4 / 0 except ZeroDivisionError as e: 

print(e)

 ( 파이썬 2.7 경우엔 except ZeroDivisionError as e: 대신 except ZeroDivisionError, e:와 같이 사용해야 한다.)


 이를 실행하면 4/0을 수행하려다 ZeroDivisionError가 발생해 except 블록이 실행되고 e라는 오류 메세지를 다음과 같이 출력한다.


결과값 : division by zero


 try .. else


 try문은 else절을 지원한다. else절은 예외가 발생하지 않은 경우에 실행되며 반드시 except절 바로 다음에 위치해야한다.

(※ else절은 else 블록과 같은 뜻이다.)


 다음 예시를 보자

try: f = open('foo.txt', 'r') except FileNotFoundError as e: print(str(e)) else: data = f.read() 

f.close()


 만약 foo.txt라는 파일이 없다면 except절이 수행되고 foo.txt파일이 있다면 else절이 수행될 것이다.


 try .. finally


 try문은 finally절도 사용할 수 있는데 이름과 같이 finally절은 try문 수행 도중 예외 발생 여부에 상관없이 끝에서 항상 수행된다. 보통 finally절은 사용한 리소스를 close해야 할 경우에 사용한다.


f = open('foo.txt', 'w') try: # 무언가를 수행한다. finally

f.close()

 foo.txt라는 파일을 쓰기 모드로 연 후에 try문이 수행된 후 예외 발생 여부에 상관없이 finallly에서 f.close()로 열린 파일을 닫는다.


 여러개의 오류 처리


try:

... except 발생 오류1: ... except 발생 오류2: ...

 

 여러개의 오류는 단순히 와 같이 처리하면 되며 아래와 같이 사용 할 수 있다.

 

try: a = [1,2] print(a[3]) 4/0 except ZeroDivisionError: print("0으로 나눌 수 없습니다.") except IndexError: 

print("인덱싱 할 수 없습니다.")

 위를 실행하면 IndexError가 먼저 발생하기에 ZeroDivisionError는 발생하지 않는다.


try: a = [1,2] print(a[3]) 4/0 except ZeroDivisionError as e: print(e) except IndexError as e: 

print(e)

 위처럼 오류 메세지를 e를 이용해 출력하면  "list index out of range"라는 오류 메세지가 출력될 것이다.


 이렇게 2개 이상의 오류를 동시에 처리하기 위해서는 아래처럼 괄호로 묶으면 편하다.

try: a = [1,2] print(a[3]) 4/0 except (ZeroDivisionError, IndexError) as e: 

print(e)


 오류 회피


 try .. except에 pass를 이용해 오류를 다음처럼 회피할 수 있다.

try:

f = open("나없는파일", 'r') except FileNotFoundError: pass


 <+ 오류 만들기 +>



'Etc > Python' 카테고리의 다른 글

#11 내장함수, 외장함수  (0) 2017.01.12
#09 클래스  (0) 2016.12.29
#08 함수, 파일 입출력  (0) 2016.11.30
#07 제어문 (if, while, for)  (0) 2016.11.28
#06 자료형의 참과 거짓, 변수  (0) 2016.11.27