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

간단한 프로젝트인 경우에는 디렉토리 구조가 큰 문제가 안되겠지만 몇 달씩 작업을 하는 프로젝트의 경우에는 디렉토리 구조을 잘 잡는 것도 중요한 문제 중에 하나다. 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



  • 별점: 1/5
  • 평가: 제목과는 다르게 Spark에 대한 내용보다는 다른 내용들이 주를 이루고 있는 느낌. Python에서 데이터 분석을 위해 자주 사용되는 여러 패키지를 소개하다보니 정작 Spark에 대해서는 깊이있게 다루지 않고 있다. 나라면 다른 사람에게 추천하지 않겠다.




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

트랙백  0 , 댓글  1개가 달렸습니다.
  1. 좋은글 감사
secret


해커와 화가
국내도서
저자 : 폴그레이엄 / 임백준역
출판 : 한빛미디어 2014.01.06
상세보기



읽은 기간: 2015.12.28~30


연말이라 대부분 휴가를 떠나서 사무실이 무척 한가하다. 모처럼만에 사무실에서 여유를 부리며 인터넷에서 이것 저것을 찾아보다가 우연히 해커와 화가의 일부분을 인용한 글을 보게 되었다.


초등학교 시절에 선생님이 가르쳐준 대로 연필이 잡히지 않아서 괴로워했던 것처럼, 나는 오랫도안 이런 프로그래밍 방식에 대해서 남몰래 부끄러워했다. 하지만 내가 그 당시에 화가나 건축가 갈은 다른 창조자들이 일하는 방식을 알았더라면 내가 프로그래밍 하는 방식을 지칭하는 특별한 이름이 있다는 사실을 알 수 있었을 것이다. 그 이름은 바로 스케치이다. 적어도 내가 보기에 대학 시절에 내가 배운 프로그래밍 방식은 전부 잘못되었다. 소설가, 화가, 그리고 건축가가 그런 것처럼 프로그램이란 전체 모습을 미리 알 수 있는 것이 아니라 작성해 나가면서 이해하게 되는 존재이다.


한동안 내가 고민했던 문제에 대한 명쾌한 설명이었다. 곧바로 전체 글을 읽고 싶어서 이래저래 알아보다가 다행히 회사 도서관에서 바로 빌려볼 수 있었다. 이 책은 사실 프로그래밍 분야에서 고전으로 손꼽히는 책이다. 2005년도에 한빛미디어에서 출간되었다가 한동안 절판되었는데 다행히 작년에 다시 발매가 되었다. 만약 오늘 도서관에서 대출을 할 수 없었다면 퇴근길에 서점에 들려 구매했을 것이다.


이 책은 저자 폴 그레이엄이 자신의 홈페이지에 올린 컬럼 중에 몇 개를 선택해서 책으로 출간한 것이다. 이곳에는 책에 있는 글들 뿐만 아니라 최근까지 올라온 저자의 다른 글들을 볼 수가 있다. 물론 영어로.


이 책에서 그는 상당히 도발적이고 새로운 생각을 펼쳐내고 있다. 혹자의 평가처럼 어떤 부분은 다소 편향적인 부분도 있다. 이를테면 부의 분배에 관한 문제나 Lisp 언어의 우월성, 스타트업에 대한 (매우 매우) 긍적적인 시각 등등. 하지만 이러한 주장에는 자신 나름대로의 분명한 이유가 있고 이를 논리적으로 기술하였다. 그래서 그의 주장에 반대하는 사람에게도 이 책은 즐거운 지적 도전이 된다.


개인적으로 책이 너무 맘에 들어서 구매하기로 결정했다. 맘에 드는 부분들, 소개하고 싶은 부분들은 책을 구입해서 다시 한번 읽으면서 포스팅하려고 한다.


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

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

다익스트라 알고리즘은 워낙 유명하고, 많은 (설명)자료들이 있는 알고리즘이지만, 한번에 이해하기가 쉬운 알고리즘은 아니다.


다익스트라 알고리즘은 방향성 있는 그래프에서 임의의 두 노드 간의 최단 거리(간선의 가중치 합)이 가장 적은 경로를 찾는 알고리즘이다. 알고리즘의 기본 구조는 간단하다. 만약 내가 시작 노드 s부터 현재 노드 u까지의 최간 거리 d(u)을 알고 있고, u에서 다음 노드 v까지의 거리 w(u,v)가 주어진다면, s부터 v까지의 거리는 d(u) + w(u,v)가 될 것이다. 그런데, s에서 v까지 갈 때 꼭 u를 거치라는 법은 없다. 다른 경로를 통해서도 s에서 v에 도달할 수 있다. 이  렇게 다른 노드를 거쳐서 v까지 가는 거리 d(v)가 d(u)+w(u,v) 보다 길다면 d(v) 값을 새로 계산한 d(u)+w(u,v)로 갱신한다. 반대로 d(v)가 더 짧다면 이번에 계산한 d(u)+w(u,v)값은 폐기하면 된다.


아래는 위키피디아에 있는 다익스트라 알고리즘 의사코드를 약간 수정한 것이다. 여기서 u는 현재 노드를, v는 u와 연결된 다른 노드를 가리킨다. previous는 v노드로 갈 때 최단 거리가 되는 이전 노드 u를 저장한다. previous에 저장된 노드들을 역추적하면 최단 경로(shortest path)를 알아낼 수 있다.


function dijkstra(G, w, s)

# initialize d and previous

# d: distance from s

# previous: previous node whose distance is minimum

for each vertex v in V(G)

d[v] := infinity

previous[v] := undefined

# 시작 노드에서 시작 노드까지의 거리는 당연히 0

d[s] := 0

S := empty set

Q := set of all vertices


while Q is not empty

u := extract_min(Q)

S += u

for each edge(u, v):

# (다른 경로를 통해 이미 계산된) s에서 v까지의 거리보다 

# s에서 (최단거리로) u를 거쳐서 v로 가는 것이 더 짧다면

# d[v] 값을 이것으로 업데이트

if  d[v] > d[u] + w(u,v)

d[v] := d[u] + w(u,v)

previous[v] := u;


이 알고리즘에서 가장 햇갈리는 부분은 아마 다음에 거리를 계산할 노드 u를 선택하는 부분일 것이다. 시작 노드 s부터 방금 선택된 노드 u를 지나 다음 노드들 v까지의 거리는 d[v] := d[u] + w(u,v) 에 의해서 계산이 된다. 그 다음에 새로운 노드를 선택해서 동일한 과정을 진행해야 하는데, 이때 선택되는 새로운 노드는 extract_min()함수에 의해 결정된다. extract_min()함수는, 그 이름에서 알 수 있듯이, Q 노드 집합에서 s에서의 거리가 최소인 노드를 추출하는 함수이다. 


function extract_min(Q)

min = infinity;

next = null;

# d에 저장된 거리들 중에서 최소 거리인 노드 e를 찾음

for e in Q:

if  e not in S  && min < d[e]:

min = d[e];

next = e;

Q := Q - next;  #Q에서 next 노드 제거

return next;


extract_min()함수에서 선택된 노드는 Q 집합에서 S 집합으로 옮겨가게 된다. 여기서 S는 d[u]가 최단 경로로 판명된, 그래서 더이상 업데이트가 필요없는 노드들의 집합이다. 바꿔말하면 아직 S에 속하지 않은 노드들은 비록 d[u]가 계산되었다 하더라도 이것이 최단 거리인지는 아직 모르는 노드들인 것이다. 만약 시작 노드에서 모든 노드가 아닌 특정 노드 x까지의 최단 거리만 구하고 싶다면 extract_min()함수가 리턴하는 노드가 x일 때 알고리즘 수행을 멈추면 된다.


예제를 통해 지금까지 이야기한 것들을 정리해보자.

아래와 같은 그래프가 있다. 우리는 A에서 F까지의 최단 거리를 구하고 싶다고 하자. 초기의 S, Q, d 값들은 그림의 오른쪽과 같게 설정된다.






그림 1. 그래프 구조와 시작 상태


이제 루프를 돌면서 extract_min()로부터 현재 검사할 노드를 얻는다. 첫번째 루프에서는 당연히 시작 노드인 A가 리턴된다(d[A]만 값을 가지고 있으므로). 현재 노드 A(회색)에서 다음 노드들 B,C,D (붉은색)까지의 거리를 계산해 d 배열에 반영한다. 첫 번째 루프가 끝났을 때의 상태는 그림 2와 같다.



그림 2. 첫 번째 루프를 마쳤을 때의 상태


이제 두 번째 루프를 돌 때 어떤 일이 벌어지는지를 살펴보자. 먼저 extract_min()함수를 통해 다음에 검사할 노드를 알아내야 한다. S에 이미 속한 A노드를 제외한 나머지 노드들의 d값을 살펴보니 B가 최소값을 가지고 있다. 따라서 이번에는 B에서 다음 노드들까지의 거리를 계산한다. 이 그래프에서는 B에서 갈 수 있는 노드가 E밖에 없으므로 d[E]값만 계산하면 된다.



그림 3. 두 번째 루프를 마쳤을 때의 상태


이제 세 번째 루프를 돌 차례이다. 여기서 재미있는 일이 벌어진다. 이전과 마찬가지로 extract_min()함수를 이용해 다음에 검사할 노드를 찾는다. S에 있는 A, B노드를 제외하고 보니 D 노드가 가장 작은 값(d[D]=15)을 가지고 있다. 이제 D에서 이동할 수 있는 노드 C, F까지의 거리를 계산해보자. d[C]는 이미 30이라는 값을 가지고 있지만 d[D]+w[D,C] = 20이다. 즉, A에서 C로 직접 가는 것보다 A에서 D를 거쳐 C로 가는 것이 더 최단 거리인 것이다. 따라서 c[C]=20으로 업데이터한다. 또한 지금까지 infinty 값이었던 d[F]도 35로 업데이트된다. 여기서 주의할 점은 d[F]값을 계산하기는 했지만 아직 이것이 최단 거리인지는 모른다는 것이다. 이것을 보증하기 위해서는 extract_min()함수에서 F를 리턴할 때 까지 루프를 반복해야 한다.




그림 4. 세 번째 루프를 마쳤을 때의 상태


이제 다시 루프를 돌아보자. 이번에 검사할 노드는? 그렇다. extract_min()함수에서 C가 리턴될 것이다. C에서 갈 수 있는 노드는 F밖에 없다. d[F]는 이전 루프를 돌 때 이미 계산이 되었지만, 이번에 다시 계산해보니 C를 거쳐서 가면 거리가 25밖에 안되는 것을 알 수 있다(d[C] + w(C,F) = 25 ). 망설이지 말고 d[F]=25로 업데이트한다.


그림 5. 네 번째 루프를 마쳤을 때의 상태



이와 같은 과정을 계속 반복하게 되면 A에서 각 노드들까지의 최간 거리를 알 수 있게 된다.


시작 노드에서 모든 노드까지의 최단 거리를 알아내고자 할 때 위에서 설명한 다익스트라 알고리즘의 시간 복잡도는 O(n^2)이다. 이는 모든 노드에 대해서 최단 거리를 계산할 때 extract_min()함수에서 다시 한번 모든 노드를 선형 탐색하기 때문이다. 간선의 수가 적은 sparse graph의 경우에는 피보나치 힙을 이용해서 O(m + nlogn)까지 줄일 수 있다고 한다.




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

트랙백  0 , 댓글  2개가 달렸습니다.
  1. 정말 감사합니다. 많은 도움 되었구요 하나 오타가 있는 것 같아 혹시 몰라서 댓글 남기고 가봅니다.

    if e not in S && min < d[e]: 부분에서

    if e not in S && min > d[e]:로 수정을 하면 더 좋을 것 같습니다. ㅎㅎ
  2. 그렇네요. 어째 좀 이상하다 했습니다. 윗분 말씀대로 부등호 방향이 바뀌었네요.
    정말 좋은 포스팅 감사합니다.
secret

1) instantClinet 설치

  - http://www.oracle.com 에 가서 download에 있는 instant client 를 다운 받는다.

  - instant client는 버전이 여러가지가 있는데 오라클 서버와 버전이 같은 것으로 다운 받는다. (꼭 버전을 맞춰야 하는지는 확인 못함)

  - instantclient-basic-linux-x86-64-11.2.0.2.0.zip 을 다운 받는다. (리눅스가 몇 bit인지 확인)

  - instantclient-sdk-linux-x86-46-11.2.0.2.2.zip 을 다운 받는다.

  - basic을 먼저 푼 다음에 sdk를 풀면 instantclient_11_2 디렉토리가 생기고 그 안에 sdk 디렉토리가 생겨서 좀 더 깔끔하다.


2) cx_Oracle 패키지 다운

  - cx_Oracle은 파이썬에서 오라클을 사용할 수 있게 해주는 패키지이다

   - 우분투용 패키지는 없으므로 소스파일을 받는다


3) cx_Oracle 패키지 컴파일

  - ORACLE_HOME 환경 변수를 설정한다. ORACEL_HOME 환경 변수는 cx_Oracle을 빌드할 때만 필요하다.

     $> export ORACLE_HOME=instantclent_11_2_디렉토리위치

    -  다음 명령으로 심볼릭 링크를 생성한다.

     $> ln -s libclntsh.so.11.1 libclntsh.so


4) libaio 설치

   - libaio 라이브러리가 없으면 나중에 cx_Oracle을 사용할 때 에러가 발생한다. 

   - https://launchpad.net/ubuntu/+source/libaio/0.3.109-2ubuntu1 가서 libaio_0.3.109.orig.tar.gz 파일을 받아 컴파일, 인스톨을 수행한다.


 

5) cx_Oracle 빌드, 인스톨

  - cx_Oracle 디렉토리로 가서 다음 명령을 수행한다.

  $> python setup.py build

  $> sudo ORACLE_HOMEinstantclent_11_2_디렉토리위치 python setup.py install


6) 설치 확인

  - 파이썬에서 cx_Oracle을 사용해본다.

    >>> import cx_Oracle

    >>> con = cx_Oracle.connect("ID/암호@주소:1521/sid")



7) 한글이 깨지는 경우

 - DB에 저장된 한글 데이터가 깨져서 출력된다면 NLS_LANG 값을 세팅한다.


>>> import os;

>>> os.putenv("NLS_LANG", "KOREAN_KOREA.KO16KSC5601"); # 안되면 "UTF8"로 세팅


## 쿼리를 수행해  데이터베이스에서 레코드를 가져온 후에

>>> print unicode(result[3], "cp949")






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

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