'파이썬'에 해당하는 글 2건

파이썬 책을 보다가 예전부터 가지고 있던 질문 중에 하나인 "파이썬 프로젝트에 적합한 디렉토리 구조"가 무엇인지 궁금해져서 조사를 시작했다.

간단한 프로젝트인 경우에는 디렉토리 구조가 큰 문제가 안되겠지만 몇 달씩 작업을 하는 프로젝트의 경우에는 디렉토리 구조을 잘 잡는 것도 중요한 문제 중에 하나다. Java 쪽에서는 이미 maven을 이용해서 디렉토리 구조를 생성하는 것이 사실상 표준이 됐는데, 파이썬이나 다른 언어에서는 이런 도구들은 없는지, 사람들은 보통 어떤 구조를 선호하는지, 혹시 어느 정도 표준안이 있는데 내가 모르고 있는 것은 아닌가 싶어 조사를 시작했다.

일단 구글링부터 시작했는데 나만 이런 생각을 하고 있었던 것은 아니었는지 이와 관련된 질문([2],[3])이나 글([1],[4],[5])들을 쉽게 찾을 수 있었다. 알고 보니 파이썬 쪽에서는 이게 패키징 문제와 엮여서 꽤나 중요한 문제였나보다. 파이썬의 패키징 프로그램(distutil, setuptools)의 문제로 인해 프로젝트 디렉토리 구조를 어떻게 잡느냐에 따라 패키징하고 배포 과정에서 버그가 많이 발생했기 때문이다. [5]를 보면 배포를 위해 파이썬 프로그램을 패키징하는데 정말 많은 신경을 써야 하는구나 싶다. 

그래서 어떤 글들을 보면 아래와 같이 프로젝트 디렉토리(여기서는 "funniest") 밑에 동일한 이름의 메인 패키지 디렉토리가 나온다. 패키킹 도구가 디렉토리 구조를 그대로 사용하기 때문에 메인 패키지 이름을 바꿀 수 없는 것 같다. 대부분의 경우 프로젝트 이름과 그 프로젝트에서 개발한 패키지 이름 (또는 프로그램 이름)이 동일하기 때문에 이런 일은 자주 발생한다.
1
2
3
4
funniest/
    funniest/
        __init__.py
    setup.py
cs

그런데 [1]을 보면 이것은 이미 옛날 이야기이고 아래와 같이 "src" 디렉토리를 사용할 것을 권하고 있다.  
├─ src
│  └─ packagename
│     ├─ __init__.py
│     └─ ...
├─ tests
│  └─ ...
└─ setup.py

당분간은 내가 작성하는 코드들이 패키징되서 배포될 일도 없고, 개인적으로 동일한 이름의 디렉토리가 있는 것보다 이게 더 보기 좋기 때문에 앞으로 내가 담당하는 프로젝트에서는 "src" 디렉토리를 만드는 안을 선택하려고 한다.

"src" 디렉토리 사용 여부 말고도 이슈가 하나 더 있는데 바로 단위 테스트용 코드를 어디에 놓을 건인가 하는 문제다. 프로젝트 최상위 디렉토리 밑에, 즉 src와 동일한 곳에 "tests" 디렉토리를 만드는 것은 상당히 보편화된 것 같다. 그런데 여기에다만 테스트 코드를 놓을 것인지, 아니면 src/pck1 밑에 tests 디렉토리를 만들고 실제 테스트 코드는 여기에 놓을 것인지에 대해서는 사람마다 다르다. 개인적으로는 $PROJECT_ROOT/tests에 넣는 것이 실행 코드와 테스트 코드를 더욱 명확하게 분리할 수 있기 때문에 더 좋다고 생각한다.

여기에 사전이나 메타 데이터를 저장하는 rsc/, 문서를 저장할 docs/, 유틸성 스크립트를 저장할 scripts/, 마지막으로 README.rst까지 포함하면 아래와 같은 구성이 된다. 

$PROJECT_ROOT
 ├─ README.rst
 ├─ docs
 ├─ rsc
 ├─ src
 │  ├─ packagae
 │     ├─ __init__.py
 │     └─ ...
 │  └─ main.py ....
 ├─ tests
 │  ├─ package
 │     └─ ...
 |  └─ test_main.py
 └─ setup.py

꼭 필요한 것들만 넣어서 간단하게 만들려고 해도 꽤 복잡해졌다. 게으름을 추구하는 똑똑한 프로그래머분들이 이런 귀찮은 작업을 매번 반복하지 말라고 cookiecutter([6], [7])라는 것을 만들었다. 그런데 이건 단위 테스트는 물론이고 CI, code coverage 도구 연동, 다중 python 버전 지원 등등 기능이 워낙 많아져서 회사 프로젝트처럼 사용할 수 있는 도구도 한정되고, 외부로 코드 공개를 할 수도 없는 경우에는 배보다 배꼽이 더 클 것 같다.

프로젝트 디렉토리 구조는 프로젝트를 새로 시작할 때마다 신경쓰였던 문제였는데, 마침 급한 일도 끝난 시점에 이렇게 조사, 정리를 하게 되서 나름 뿌듯하다. 이 주제가 생각만큼 단순하지 않고 그동안 많은 이슈들이 있었다는 점에서 한편으로는 놀랍기도 했고, 다른 한편으로는 새로운 사실들을 많이 알게되서 재밌기도 했다.


참고자료



WRITTEN BY
trowind
자연어처리, 프로그래밍, 여행, 음식, 삶의 기록

트랙백  0 , 댓글  0개가 달렸습니다.
secret

파이썬 코드를 보다보면 가끔 함수 이름 위에 @로 시작하는 구문이 붙어 있는 것을 가끔 볼 수 있다. 

@verbose

def my_function():

print "hello, world."

만약 자바를 주로 사용하는 프로그래머라면 어노테이션(annotation)이랑 비슷한 것인가 하는 생각이 들 것이다. 하지만 미리 말하자면 파이썬의 데코레이터와 자바의 어노테이션은 많이 다르다. 


파이썬의 데코레이터는 그 이름에서 알 수 있듯이 기본적으로는 본체를 꾸며주는 역할을 한다. 위 예제에서는 my_function()함수가 본체에 해당한다. 즉, @verbose라는 데코레이터를 추가함으로서 본체인  my_function()에 뭔가가 더해지기는 했지만, 기본적으로  my_function()이 바뀌는 것은 아니라는 의미이다[각주:1]


파이썬에서 함수는 first citizen에 속한다. 이 말은 파이썬에서 함수를 다른 함수의 인자로 넘기거나 함수의 리턴 값으로 사용하는 것이 가능하며 매우 자연스러운 일이라는 뜻이다. 다음 코드와 같이 말이다.

def verbose(func):

print "Begin", func.__name__;

func();

print "End", func.__name__;



verbose()는 다른 함수를 인자로 받는다. 물론 C에서도 함수 포인터를 이용해 동일한 기능을 이용할 수 있다. 하지만, C는 어디까지나 포인터를 인자로 사용하는 것이지, 함수 자체를 인자로 사용하는 것은 아니다. 즉, C에서 함수는 first citizen이 아니다. 함수가 first citizen인 언어에서는 이를 이용한 다양한 기법들이 있으며, 이를 통해 다양하고 기발한 코딩이 가능하다. 여기서 설명하는  데코레이터도 그 중에 하나이다.


verbose()에 my_function를 인자로 넣으면 다음과 같은 실행 결과를 볼 수 있다.

>>> verbose(my_function)

Begin my_function

hello, world.

End my_function

간단한 예제이지만 우리는 이를 통해서 다른 함수를 인자로 받는 함수를 살펴보았다.  여기서 verbose()는 my_function() 자체의 동작을 변경시키지 않는다. 다만, my_function()의 앞 또는 뒤, 위 경우에는 둘 다,에 약간의 장식을 덧붙였을 뿐이다.


그런데, 이 방법은 엄밀히 말하면 my_function()에 장식을 붙인 것이라 하기 힘들다. 왜냐하면 우리가 호출한 함수는 my_function()이 아니라 verbose()이기 때문이다. 즉 클라이언트 입장에서는 my_function()에 장식이 덧붙여진 것이 아니라, 원래 저런 기능을 하는 verbose() 함수를 사용한 셈이다.


verbose()를 수정해서 이 문제를 해결해보자.

def verbose(func):

def new_func():

print "Begin", func.__name__;

func();

print "End", func.__name__;

return new_func;


verbose()에서  new_func()이라는  nested function을 정의한 다음에 마지막에 이 함수를 반환한다. 이것은 앞서 말한 바와 같이 파이썬에서는 함수가 first citizen이므로 이러한 코드는 전혀 문제가 없다. new_func()이 하는 일은 기존의 verbose()와 동일하다.


이제 바뀐 verbose()가 my_function()을 어떻게 장식해주는지 보자.

>>> my_function = verbose(my_function)

>>> my_function()

Begin my_function

hello, world.

End my_function

먼저  verbose()의 리턴값으로 my_function을 대체한다. 이렇게 하면 my_function()은 원래 기능과 함께  verbose()에서 제공하는 부가적인 기능까지 수행하는 새로운 함수가 된다. 즉,  verbose()가 my_function()에 장식을 달아준 것이다. 그리고 이 장식은 계속 유지된다.


파이썬의 데코레이터는 사실 위 기능을 좀 더 직관적이고 간결하게 명시할 수 있도록 해주는 syntactic sugar일 뿐이다. 이 글의 첫번째 코드와 같이 함수 정의 윗부분에 데코레이터를 달면(데코레이션) 우리가 위에서 했던 "my_function = verbose(my_function)" 부분을 따로 쓸 필요도 없어지고 코드의 의도도 명확해진다. 주의할 점은 데코레이터는 결국 함수, 좀 더 정확하게 말하면 callable object이므로 당연히 데코레이션을 하기 전에 해당 함수 또는 클래스를 정의해야 한다는 것이다.


클래스 이야기가 나온 김에 클래스로 데코레이터를 정의하는 방법을 살펴보도록 하자. 클래스를 함수처럼 호출되게 하려면 __call__() 맴버 함수를 정의하면 된다. 이를 이용해서 위  verbose()함수를 클래스로 바꿔보면 다음과 같다.

class Verbose:

def __init__(self, f):

print "Initializing Verbose."

self.func = f;


def __call__(self):

print "Begin", self.func.__name__

self.func();

print "End", self.func.__name__


@Verbose

def my_function():

print "hello, world."


print "Program start"

my_function();



앞서 사용했던 verbose()와 구별을 위해, 그리고 일반적인 코딩 규약에 따라 클래스 버전은 대문자 V로 시작한다. 코드 자체는 간단하기 때문에 설명할 것은 별로 없다. Verbose가 언제 초기화 되는지를 확인하기 위해 생성자에 메시지를 출력하도록 하였으며, __call__() 맴버 함수에서 return 부분은 필요치 않다. 이 예제를 파일로 저장한 다음 파이썬으로 실행해보면 다음과 같은 결과를 볼 수 있다. 여기서는 파일 이름을 decorator_test.py라 하였다.

$ python decorator_test.py

Initializing Verbose

Program start

Begin my_func

hello, world

End my_func


결과를 보면 알 수 있듯이 Verbose 클래스의 생성자가 먼저 실행된 다음에 프로그램의 메인 부분이 실행된다. 이를 통해 우리는  데코레이터가 함수 정의 단계에서 실행됨을 알 수 있다.  그 밖에는 앞에서 이야기 한 것과 동일하다.


마지막으로 인자를 갖는 함수에 대한 데코레이터를 만드는 방법에 대해 이야기해보자. 지금까지의 과정과 마찬가지로이것 역시 파이썬답게 간단하다. 우리가 할 일은 단지 '*args'와 '**kwargs'만 추가해주면 된다[각주:2].

class Verbose:

def __init__(self, f):

print "Initializing Verbose."

self.func = f;


def __call__(self, *args, **kwargs):  #여기 

print "Begin", self.func.__name__

self.func(*args, **kwargs);    #여기

print "End", self.func.__name__


@Verbose

def my_function(name):   #l 인자를 갖는 함수

print "hello,", name




참고 자료



  1. 이 정도 의미로 생각한다면 자바의 어노테이션도 동일한 것이라 볼 수도 있다. 하지만 자바의 어노테이션은 파이썬에 비해 자신이 원하는 대로 만들어 사용하기가 매우 어렵다. [본문으로]
  2. *args, **kwargs 는 파이썬에서 일종에 가변 길이 인자를 다루는 방법이라 할 수 있다. 개인적으로는 파이썬 문법 중에서 가장 난해한 부분이 이 *args, **kwargs라고 생각한다. 사실 어려운 내용은 아니지만 직접 쓸 일이 없다 보니 매번 까먹는 것 같다. [본문으로]

WRITTEN BY
trowind
자연어처리, 프로그래밍, 여행, 음식, 삶의 기록

트랙백  0 , 댓글  3개가 달렸습니다.
  1. 잘 보고 갑니다.
  2. rakk4403 2016.01.01 21:10
    잘봤습니다. 좋은포스팅 감사합니다.
  3. 잘보고갑니다.
secret