메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

파이썬 생명정보학 개론

한빛미디어

|

2002-12-07

|

by HANBIT

13,722

저자: 패트릭 오브라이언(Patrick O"Brien), 역 전순재

컴퓨터를 생물학적 연구에 사용하는 생명정보학(Bioinformatics)은 인간이 끊임없이 추구해온 목표들(생명의 신비를 벗기려는 시도) 중 하나이다. 비록 생명의 신비를 모두 알 수는 없을지라도 컴퓨터 덕분에 최소한 생명체 내부에서 일어나는 생물학적 과정의 상당부분을 이해할 수 있게 되었다. 사실 근래들어, 컴퓨터 프로그래밍은 생물학자들에게 조차 중대한 기술이자 거의 필수적인 기술이 되었으며 생물학적 연구에 필수적으로 컴퓨터를 사용하게 되었다.

본 기사 목적은 생명정보학 공동체 컴퓨터 프로그래밍 요구에 적합한 유용한 개발 언어로 파이썬을 소개하는데 있다. 여기에서는 생명정보학에 파이썬을 사용하면 얻게 되는 이점들을 알아 보겠다. 그리고 난 후, 실제로 작동하는 코드의 예제를 만들어 시연해보이겠다. 다음 기사에서는 파이썬을 활용하고 있는 상당히 유망한 생명정보학 프로젝트들을 살펴볼 것이다.

최소한의 배경지식

옛날부터 과학자들은 서로 협력하면서 연구결과를 개방적으로 이용해왔다. 그렇기 때문에 과학자들이 생물학적 처리과정의 연구에 컴퓨터 처리를 적용할 때가 되면 자연스럽게 독점 소프트웨어 보다는 오픈 소스 소프트웨어에 기울게 될 것이다. 제일 처음 생물학자들 사이에 인기를 얻은 오픈 소스 언어는 펄이었다. 펄은 강력한 텍스트 처리 능력 덕분에 생명정보학에 자리를 잡았다. 펄의 텍스트 처리 능력은 처음에 연속 데이터를 분석하는데 이상적으로 잘 맞았다. 그 덕분에, 펄은 생명정보학에 성공적으로 사용된 역사가 있으며 여전히 생물학적 연구에 아주 유용한 도구이다.

펄과 비교해, 파이썬은 상대적으로 늦게 생명정보학에 들어왔지만 꾸준히 그 인기를 더해 가고 있다. 인기의 비결은 다음과 같다.
  • 파이썬 코드의 가독성
  • 신속한 애플리케이션 개발 능력
  • 강력한 표준 라이브러리 기능
  • 소형 프로그램에서 대형 프로그램까지 모두 적용할 수 있는 신축성
파이썬은 복잡한 애플리케이션을 개발하는데 필요한 강력한 능력을 포기하지 않고서도 간단하고 쉽게 접근할 수 있도록 설계되었다. 다른 언어에서는 배우기도 어렵고 또 그러한 언어들로 작성된 프로그램을 이해하기가 어렵게 만드는 자질구레한 귀찮은 것들이 파이썬에는 없다. 깨끗하고 일관된 구문 덕분이다.

이러한 파이썬의 동적인 본질에 접근용이성 또한 추가된다. 예를 들어 파이썬에서는 변수들을 사용하기 전에 선언할 필요가 없으며, 같은 변수가 살아 있는 동안 서로 다른 종류의 객체들을 참조할 수 있다. 파이썬은 상호대화적으로 사용될 수도 있다. 각 명령어를 타이핑해 넣는 즉시 결과를 산출하는 상호대화 세션에서 어느 파이썬 모듈의 언어에도 사용자들은 쉽게 익숙해질 수 있기 때문이다.

파이썬은 또한 객체지향 스타일의 프로그래밍을 탁월하게 지원한다. 이 기사의 말미에 이 능력의 예 하나를 보여주겠지만, 기본 개념은 객체지향이 종종 프로그램 안에 데이터와 기능을 조직하는 보다 좋은 방법을 제공한다는 것이다. 생명정보학에 사용된 데이터와 분석 테크닉이 점점 복잡해짐에 따라 객체지향 특징의 가치는 더욱 높아졌다.

게다가 파이썬은 C, C++, 자바 그리고 포트란 같은 다른 언어로 작성된 시스템과 잘 통합된다. C가 가지는 주요 장점 중의 하나가 바로 속도이다. 프로그래머가 가능한 한 빨리 실행되는 알고리즘을 원한다면 C나 C++로 코딩하여 그 코드를 파이썬에 확장 모듈로 사용할 수 있게 만들면 된다. 그 프로그래머는 이런 확장 모듈과 순수한 파이썬 모듈을 구별하지 못한다. 이밖에도 포트란으로 코딩된 방대한 크기의 과학적 알고리즘을 파이썬 프로그램이 접근할 수 있도록 만들어 주는 유사한 유틸리티도 있다.

자바는 플랫폼 독립적인 웹 개발 언어로서 인기가 있다. 파이썬 인터프리터는 현재 두 가지 변형으로 사용할 수 있다. 버전 하나는 C로 작성되어 있고, 다른 버전은 Jython으로 알려져 있는데 Java로 작성되어 있다. Jython을 사용하면 자바 프로그래머는 파이썬 구문과 동적인 언어 특징들을 사용해서 프로그램을 작성할 수 있으며, 파이썬 프로그래머는 자바로 개발된 기존의 코드를 사용할 수 있다. 이러한 것들은 파이썬이 다른 언어로 작성된 기존의 코드를 활용하고 확장하는 수 많은 방식 중 그저 몇 가지 예에 불과하다.

그래서 펄이 생명정보학 공동체에서 자리를 더 잘 잡고 있지만, 파이썬이 인기를 얻으면서 많은 생물학자들과 생명정보학자들은 파이썬에도 관심을 두고 있다. 파이썬이 무엇을 줄 수 있는지 더 잘 이해하기 위해서, 그의 특징이 잘 드러나는 파이썬 예제 코드 몇 개를 살펴 보겠다. 그러나 먼저, 그 예제들에서 다루게 될 생물학의 기본에 대해서 알아보자.

생물학 기본

분자 생물학이 추구하는 여러 가지 목표 중 하나는 생명체 세포 안에서 일어나는 과정을 이해하는 것이다. 그리고 그러한 과정 중 하나가 바로 단백질 생성인데, 단백질은 모든 유기 생명체의 가장 기본을 이루는 요소라고 할 수 있다. 살아있는 생명체의 거의 모든 대사처리는 방대하고 복잡한 분자 구조를 이용하거나 영향을 받는다. 수 천 가지의 다양한 단백질이 있으며 우리는 그것들의 세부적 사항들을 이제 겨우 이해하기 시작했다. 여기에서 반드시 알아야 할 한가지 사실은 각 세포의 발생적 재료 안에 인코딩된 정보(DNA)에 의해서 단백질의 생성이 결정된다는 것이다.

DNA는 선형적 구조로 뉴클레오타이드(nucleotides)나 베이스(bases)라고 부르는 일련의 분자들로 구성된다. DNA에는 네 개의 뉴클레오타이드 즉, 아데닌(adenine), 시토신(cytosine), 구아닌(guanine), 티아민(thymine)으로 구성되어 있다. 이런 뉴클레오타이드들은 보통 그 이름의 대문자로 A, C, G 그리고 T로 표현된다. 실제로 DNA는 이런 뉴클레오타이드 두 줄이 그 유명한 이중 나선 꼴로 서로 엉켜서 구성된다.

한줄의 DNA 연속열은 각자의 베이스를 구별하여 일련의 알파벳 문자들로 표현될 수 있다. 예를 들어 ACCTTGGCACCT와 같이 말이다. 화학적인 인력작용 때문에, 뉴클레오타이드는 항상 쌍으로 나타나며, 또한 베이스 쌍(base pairs)으로 불리는데, 예를 들어 아데닌(A)은 항상 티아민(T)과 짝을 이루며, 시토신(C)은 항상 구아닌(G)과 짝을 이룬다. 이런 "베이스-매칭"이라는 특징 때문에 DNA 연속열에서 한 가닥만 알고 있더라도 모자란 가닥이나 반대의 가닥을 쉽게 알 수 있다.

어떻게 DNA가 단백질을 생성방법을 결정하는지 간단하게 살펴보면 다음과 같다. 유전자라고 부르는 한 섹션의 DNA에 단백질을 생성하는 방법이 인코딩된 정보가 담겨 있다. 전사(轉寫) 과정을 통하여, 두 개의 DNA가 유전자 분체(gene separate)를 꼬아 따라가면서, 유전자가 복사된다. 이런 한 가닥짜리 복사본을 RNA라고 부른다. 더 자세하게 말하면 메신저 RNA라고 부른다. 티아민(T) 대신에 뉴클레오타이드 우라실(U)이 나타난다는 점만 빼면 RNA는 원래의 유전자 배열과 동일하다.

일단 형성되면, 메신저 RNA는 리보좀(ribosome)이라고 알려져 있는 세포 속의 한 구조로 이동한다. 리보좀은 그 메신저 RNA를 나르면서, 그 배열에서 한 번에 세 개씩 뉴클레오타이드를 읽는다. 세개의 뉴클레오타이드로 이루어진 각 그룹을 코돈(codon)이라고 부르는데, 코돈은 20 종류의 아미노산중 어느 아미노산이 리보좀에 의해서 조립되어 단백질로 변형될지를 결정한다. DNA와 RNA처럼, 단백질은 선형 구조로 텍스트 문자열로 표현이 가능하다. DNA와 RNA가 4개의 문자 알파벳을 사용하지만, 단백질 배열에서 각 아미노산을 표현하려면 20개의 문자 알파벳이 필요하다.


Learning Python

참고 도서

Learning Python
Mark Lutz, David Ascher


파이썬 기본

이제 분자 생물학의 기본을 약간 다루어 보았으므로, 생물학적 연구 데이터를 처리하는데 필요한 파이썬의 특징을 살펴보겠다. DNA, RNA, 단백질은 모두 선형적 연속열로서 쉽게 컴퓨터에 적절한 방식으로 표현될 수 있다고 언급하였다. 파이썬은 연속열을 제대로 다루는데 필요한 여러 가지 내장 구조를 가지고 있다. 우리가 살펴볼 3가지 내장 구조는 문자열, 리스트 사전이다. 그러기 위해서는 먼저 파이썬 셸부터 간단하게 알아보고 넘어가자.

파이썬 코드를 실행하는데는 두 가지 다른 방식이 있다. 하나는 파이썬 코드를 담은 줄들을 텍스트 파일로 입력하고 그 파일을 .py라는 확장자를 붙여 저장하는 것인데 이 방식은 다른 언어에 경험이 있는 사람들이라면 누구나 친숙할 것이다. 그리고 나면 그 프로그램 파일은 여러분의 설정환경에 따라 운영체제의 프롬프트에서, 또는 그 파일을 더블-클릭해주면 실행될 수 있다. 다른 방식은 파이썬 셸에서 파이썬 인터프리터와 상호대화하는 것인데 여기에서 코드를 입력하고, 리턴키를 치면, 파이썬으로부터 그 즉시 응답을 받을 수 있다.

파이썬 셸은 파이썬 언어를 배우고 새로운 프로그래밍 개념을 습득하기에 뛰어난 환경이다. 심지어 그래픽적인 파이썬 셸도 있어서, 코드에 색상을 입혀주고, 타자할 때 자동완성 옵션 리스트가 튀어 나오며, 프로그램에서 현재 사용할 수 있는 모든 변수들을 화면에 표시해 주고, 그리고 다른 수 많은 방법으로 도와줄 것이다. 여기에서 사용할 파이썬 셸은 PyCrust라고 불리는데, 이 셸은 wxPython GUI 도구모음에 딸려온다.


참고 기사: 파이썬카드(PythonCard)와 파이크러스트(PyCrust)

파이썬 애플리케이션용으로 그래픽 사용자 인터페이스(GUI)를 개발하는 것은 종종 지루하고, 시간을 소비하며, 확실하지 못한 처리과정을 거친다. 이런 상황은 파이썬 프로그래머들이 파이썬을 사용한 대부분의 다른 소프트웨어 개발 측면들을 기술하는 것과는 정확하게 반대 양상이다. 이 기사에서 패크릭 오브라이언(Patrick O"Brien)은 그래픽 파이썬 셸인 PythonCard와 PyCrust가 어떻게 구이 개발 과정을 쉽게 만들어 주는지를 설명한다.



파이썬 셸을 시작하면, 파이썬 코드 한 줄을 입력하라는 프롬프트와 마주칠 것이다. 주 프롬프트는 ">>>"이다 (겹따옴표 없음). 만약 입력하려고 하는 파이썬 코드가 한 줄 이상이 필요하면, 연이은 줄들은 제 2 프롬프트인 ""으로 화면에 표시될 것이다. PyCrust에서 이것이 어떻게 보이는지 살펴보자.


[그림] PyCrust 셸의 최초 모습

PyCrust 셸에서 몇개의 파이썬 코드 예제를 입력한 후에는 다음과 같이 보일 것이다.


[그림] "dna"객체에 사용할 수 있는 메소들을 보여주는 팝업 리스트

파이썬 문자열

좀 더 자세하게 예제 코드를 살펴 보자. 처음에 한 일은 문자열을 하나 만들고 그것을 변수에 할당한 것이다. 파이썬에서 문자열은 일련의 문자열이다. 문자열 기호를 만들려면 그 문자열을 따옴표("), 겹따옴표(") 또는 삼중 따옴표(""" 또는 삼중 겹따옴표""")로 둘러싸면 된다. 예제에서는 문자열 기호 CTGACCACTTTACGAGGTTAGCdna라는 이름의 변수에 할당하였다.
>>> dna = "CTGACCACTTTACGAGGTTAGC"
그리고 나서 단순히 그 변수의 이름을 타이핑해 넣으면 파이썬이 그 변수의 값을 화면에 표시함으로써 응답한다. 그 값을 인용부호로 둘러싸서 그것이 문자열이라는 것을 상기시켜준다.
>>> dna
"CTGACCACTTTACGAGGTTAGC"
파이썬 문자열은 여러 내장 능력을 구비하고 있다. 그 중에 한 능력은 자신의 복사본을 모두 소문자로 바꾸어 반환하는 능력이다. 이런 능력들을 사람들은 메소드라고 알고 있다. 한 객체의 메소드를 요청하려면, 점 구문을 사용하자. 다시 말해 (이 경우에는 한 문자열 객체에 대한 참조점인) 그 변수의 이름을 타자하고 다음에 점 연산자(.)를 그 다음에 메소드의 이름을 타자한다. 그 다음에 열기괄호와 닫기괄호를 타자하라.
>>> dna.lower()
"ctgaccactttacgaggttagc"
지표화 연산자 s[i]를 사용하면 문자열의 일부에 접근할 수 있다. 지표화는 0에서 시작하며, 그래서 s[0]은 그 문자열에서 첫 번째 문자를 반환하고 s[1]은 두 번째 문자를 반환한다. 등등.
>>> dna[0] 
"C" 
>>> dna[1] 
"T" 
>>> dna[2] 
"G" 
>>> dna[3] 
"A" 
여기에 나온 스크린샷에서 마지막 줄은 PyCrust가 구비한 자동완성의 특징을 보여주는데, 한 객체 변수 뒤에 점이 타자되면 한 객체에서 유효한 메소드와 (그 특성들)의 목록이 화면에 표시된다. 보시다시피, 파이썬의 리스트가 가진 많은 내장 능력들을 파이썬 셸에서 시험해 볼 수 있다. 이제 다른 파이썬 연속열 형으로 리스트를 살펴 보자.

파이썬 리스트

파이썬 문자열이 문자에 제한되어 있는 반면, 파이썬 리스트는 제한이 전혀 없다. 파이썬 리스트는 다른 리스트 뿐만 아니라 임의의 파이썬 객체들을 담은 순서있는 연속열이다. 게다가 리스트에서 요소들을 제거하고 대체하고 삽입할 수 있다. 리스트는 각 괄호(square brackets) 안에 쉼표로 구분된 객체들의 연속열로 작성된다. 리스트 몇 개를 살펴 보고 리스트에 대해 수행가능한 연산 몇 개를 살펴 보자.
>>> bases = ["A", "C", "G", "T"]
>>> bases
["A", "C", "G", "T"]
>>> bases.append("U")
>>> bases
["A", "C", "G", "T", "U"]
>>> bases.reverse()
>>> bases
["U", "T", "G", "C", "A"]
>>> bases[0]
"U"
>>> bases[1]
"T"
>>> bases.remove("U")
>>> bases
["T", "G", "C", "A"]
>>> bases.sort()
>>> bases
["A", "C", "G", "T"]
이 예제에서는 우리가 베이스(bases)라고 부르는 한 개 짜리 문자열로 구성된 리스트를 하나 만들었다. 그리고나서 한 요소를 그 끝에 추가했고, 모든 요소들의 순서를 역으로 바꾸었으며, 지표 위치로 요소들을 열람하였다. 그리고 "U"라는 값을 가진 요소를 제거하였다. 리스트에서 요소 하나를 제거하려면 추가 정보를, 즉 리스트에서 제거하고자 하는 값을 remove() 메소드에 공급할 필요가 있다. 아래의 그림에서 볼 수 있듯이, PyCrust는 대부분의 연산에서 필수적인 것들을 알려주는 파이썬의 능력을 활용하여 호출 팁 팝업 창에서 그 정보를 보여준다.


[그림] "remove" 메소드의 사용법을 보여주는 툴팁

예를 들어 리스트 객체의 remove() 메소드와 같이 메소드를 가지는 객체들에 관하여 논의하였다. 그리고 어떻게 메소드가 주어진 과업을 수행하는지, 그리고 어떤 경우에는 결과를 반환하는지를 언급하였다. 파이썬은 함수라고 부르는 또다른 아주 비슷한 특징을 가지고 이다. 함수와 메소드 사이의 유일한 차이점이라면 함수는 특정한 객체에 관련되어 있지 않다는 것이다.

주의: 함수로 정의해야 할지 메소드로 정의해야 할지는 부분적으로 디자인 선택에 달린 문제이다. 사실, 아래에 여러 함수들을 만들고 있고, 파이썬의 객체지향 프로그래밍 지원을 보여주기 위한 방편으로 그 함수들을 메소드로 다시 정의하였다.

파이썬 함수

함수는 하나 이상의 값들에 대하여 연산을 수행하여 그 결과를 반환한다. 파이썬에는 자신만의 함수를 정의할 수 있는 능력뿐만 아니라 미리 정의된 함수들도 많이 딸려온다. len()은 한 연속열에서 항목의 개수를 반환한다. dir()은 한 객체의 속성을 표현하는 문자열 리스트를 반환한다. list()는 다른 어떤 연속열로부터 초기화된 새로운 리스트를 반환한다.
>>> dna = "CTGACCACTTTACGAGGTTAGC" 
>>> bases = ["A", "C", "G", "T"] 
>>> len(dna) 
22 
>>> len(bases) 
4 
>>> dir(dna) 
["__add__", "__class__", "__contains__", "__delattr__",  
"__doc__", "__eq__", "__ge__", "__getattribute__", "__getitem__",  
"__getslice__", "__gt__", "__hash__", "__init__", "__le__",  
"__len__", "__lt__", "__mul__", "__ne__", "__new__", "__reduce__",  
"__repr__", "__rmul__", "__setattr__", "__str__", "capitalize",  
"center", "count", "decode", "encode", "endswith", "expandtabs",  
"find", "index", "isalnum", "isalpha", "isdigit", "islower",  
"isspace", "istitle", "isupper", "join", "ljust", "lower",  
"lstrip", "replace", "rfind", "rindex", "rjust", "rstrip", "split",  
"splitlines", "startswith", "strip", "swapcase", "title",  
"translate", "upper"] 
>>> dir(bases) 
["__add__", "__class__", "__contains__", "__delattr__",  
"__delitem__", "__delslice__", "__doc__", "__eq__", "__ge__",  
"__getattribute__", "__getitem__", "__getslice__", "__gt__",  
"__hash__", "__iadd__", "__imul__", "__init__", "__le__", "__len__",  
"__lt__", "__mul__", "__ne__", "__new__", "__reduce__", "__repr__",  
"__rmul__", "__setattr__", "__setitem__", "__setslice__", "__str__",  
"append", "count", "extend", "index", "insert", "pop", "remove",  
"reverse", "sort"] 
>>> list(dna) 
["C", "T", "G", "A", "C", "C", "A", "C", "T", "T", "T",
"A", "C", "G", "A", "G", "G", "T", "T", "A", "G", "C"]
이제부터는 생물학적 연속열 데이터와 관련된 유용한 연산을 수행할 함수들을 정의하겠다.

사용자 정의 함수

다음은 파이썬에서 자신만의 함수를 만드는 과정이다. 첫 줄은 키워드 def로 시작한다. 다음에 그 함수의 이름과 (입력값이라고 예상되는) 괄호로 둘러싼 인수들이 따른다. 그리고 쌍점으로 끝난다. 다음에 따르는 줄들은 함수의 몸체를 구성하며 반드시 들여쓰기 되어야 한다. 만약 문자열 주석이 함수 몸체의 첫 줄에 나타나면 그 주석은 함수를 위한 문서의 일부가 된다. 함수의 마지막 줄은 결과를 반환한다.

함수 몇 개를 PyCrust 셸에서 정의해보자. 그리고 나면 약간의 샘플 데이터로 각 함수를 시험해 볼 수 있으며 그 함수에 의해서 반환된 결과를 볼 수 있을 것이다.
>>> def transcribe(dna):
...     """dna 문자열을 rna 문자열로 반환하라."""
...     return dna.replace("T", "U")
...
>>> transcribe("CCGGAAGAGCTTACTTAG")
"CCGGAAGAGCUUACUUAG"
이 예제에서 transcribe라고 부르는 함수 하나를 만들었는데 이 함수는 건네지는 문자열이 DNA 배열을 표현한다고 예상한다. 문자열은 replace() 메소드를 가지고 있는데 이 메소드는 한 문자가 출현할 때마다 다른 문자로 바꾸어서 원래 문자열의 복사본을 반환할 것이다. 한 줄의 DNA를 RNA로 일관되게 전사하는 방법을 3줄의 코드로 만들었다. 또다른 함수를 만들어 보자. reverse는 어떤가?
>>> def reverse(s):
...     """서열 문자열을 순서를 거꾸로 하여 반환하라."""
...     letters = list(s)
...     letters.reverse()
...     return "".join(letters)
...
>>> reverse("CCGGAAGAGCTTACTTAG")
"GATTCATTCGAGAAGGCC"
이 함수에는 새로 설명이 필요한 것들이 몇 개 있다. 먼저, 인수 이름을 "dna" 대신에 "s"라고 사용했다. 파이썬에서는 아무거나 마음에 드는 이름으로 인수를 명명해도 좋다. 예상된 값이나 의도에 근거하여 짧은 이름을 사용하는 것이 관례라면 관례이다. 그래서 문자열에 대하여 "s"를 사용하는 것은 파이썬 코드에서 아주 흔한 일이다. 이 예제에서 "dna" 대신에 "s"를 사용한 또다른 이유는 이 함수가 단순히 dna 배열을 표현한 문자열 뿐만 아니라 어느 문자열에도 올바로 작동하기 때문이다. 그래서 "s"가 "dna"보다 이 함수의 포괄적인 쓰임새를 더 잘 반영했다고 할 수 있다.

reverse 함수는 문자열 하나를 취해서, 그 문자열에 기반하여 리스트를 하나 만들고, 그 리스트의 순서를 반대로 하는 것을 볼 수 있다. 이제 그 리스트를 합쳐서 다시 문자열로 만들면 문자열을 반환할 수 있다. 파이썬의 문자열 객체는join() 메소드가 있는데 이 메소드는 리스트를 모아서 문자열로 만들고, 문자열 값으로 각 리스트 요소를 분리한다. 분리자(separator)로 특별히 문자를 쓰지 않을 것이기 때문에, 두 개의 인용부호로 표현된("" 또는 "") 빈 문자열로 join() 메소드를 사용하겠다.

DNA 배열의 나머지 짝을 계산하려면 4개의 베이스 중 각 베이스가 각기 자신의 짝에 짝짓기 되도록 할 방법이 필요하다. 그러기 위해서 사전이라고 부르는 파이썬의 또다른 연속열 구조를 사용하겠다.

파이썬 사전

파이썬 사전은 보통의 종이 사전과 똑같은 장점을 가지고 있다. 흔히 사용하는 사전에서 처럼 파이썬 사전으로 키(단어)와 관련된 값(정의)을 신속하게 찾을 수 있기 때문이다. 사전은 활괄호(curly braces)로 표현되며 쉼표로-구분된 키:값 쌍의 연속열을 담고 있다. 사전은 순서가 없다. 대신에 사전의 값들은 연속열에서 위치가 아니라, 키 값으로 접근된다. 사전이 지원하는 메소드를 살펴 보자.
>>> basecomplement = {"A": "T", "C": "G", "T": "A", "G": "C"}
>>> basecomplement.keys()
["A", "C", "T", "G"]
>>> basecomplement.values()
["T", "G", "A", "C"]
>>> basecomplement["A"]
"T"
>>> basecomplement["C"]
"G"
>>> for base in basecomplement.keys():
...     print "The complement of", base, "is", basecomplement[base]
...
The complement of A is T
The complement of C is G
The complement of T is A
The complement of G is C
>>> for base in basecomplement:
...     print "The complement of", base, "is", basecomplement[base]
...
The complement of A is T
The complement of C is G
The complement of T is A
The complement of G is C
이 예제에서는 for 루프의 개념도 소개되고 있는데, 이는 basecomplement 사전의 키들을 순회한다. 파이썬의 for 루프는 어느 연속열도 반복할 수 있다. 이 예제에서는 keys()가 반환한 리스트의 첫 번째 값을 base라는 이름의 변수에 할당하고, print 서술문을 실행한다. 그리고나서 그 리스트에 있는 각 하부연속 값들에 대하여 그 과정을 반복한다. 두 번째 for 루프의 예에서, 단순히 "for base in basecomplement"라고 지정하면 파이썬은 기본으로 basecomplement 사전의 키들을 루프 하는 것을 볼 수 있다.

사용자 정의 함수에 대하여 더 자세히

다음 예제는 complement 함수에서 사용해야 하는 다른 테크닉을 보여줄 것이다. 이 테크닉은 상대적으로 파이썬에서 새로운 기능인데, 리스트 내포(comprehensions)라고 부른다.
>>> letters = list("CCGGAAGAGCTTACTTAG")
>>> [basecomplement[base] for base in letters]
["G", "G", "C", "C", "T", "T", "C", "T", "C",
"G", "A", "A", "T", "G", "A", "A", "T", "C"]
리스트 내포는 리스트를 반환하고 for 루프와 비슷하게 작동하지만, 훨씬 더 간결하고 효율적인 형식이다. 이 경우에 리스트 내포를 사용하면 원래의 문자 리스트에서 각 베이스가 그 짝으로 대체된 새로운 리스트를 반환할 수 있다. 짝은 basecomplement 사전에서 열람하였다. 어떻게 이 모든 것을 하나로 합치는지 살펴보자.
>>> def complement(s):
...     """짝 서열 문자열을 반환하라."""
...     basecomplement = {"A": "T", "C": "G", "G": "C", "T": "A"}
...     letters = list(s)
...     letters = [basecomplement[base] for base in letters]
...     return "".join(letters)
...
>>> complement("CCGGAAGAGCTTACTTAG")
"GGCCTTCTCGAATGAATC"
이제 reverse 함수와 complement 함수가 있으므로, reversecomplement 함수를 위한 빌딩블록을 갖추었다.
>>> def reversecomplement(s):
...     """dna 문자열의 반대 짝(reverse complement)을 반환하라."""
...     s = reverse(s)
...     s = complement(s)
...     return s
...
>>> reversecomplement("CCGGAAGAGCTTACTTAG")
"CTAAGTAAGCTCTTCCGG"
다음 코드는 G와 C 베이스로 구성된 DNA 비율을 아는 데 쓸모가 있을 것이다. 문자열 객체는 count() 메소드를 가지는데 이 메소드는 문자열의 출현 빈도를 반환한다. 그 정보를 가지고 그 비율을 계산하는 것은 간단한 수학적 계산을 하는 것 만큼이나 쉬운 일이다.
>>> def gc(s):
...     """G+C로 구성된 dna 비율을 반환하라."""
...     gc = s.count("G") + s.count("C")
...     return gc * 100.0 / len(s)
...
>>> gc("CCGGAAGAGCTTACTTAG")
50.0
>>> gc("CCGGAAGAGCTTACTTAGTTA")
42.857142857142854
DNA는 3개의 문자 부분(코돈)으로 분할될 수 있기 때문에, 코돈 리스트를 반환하는 함수가 쓸모가 있을 것이다. DNA 문자열이 3개로 나누어 떨어지지 않는 경우에 또다른 간단한 수학적 계산으로 코돈의 마지막 점을 결정한다. range() 함수는 첫 점에서 마지막 점까지 이 경우에는 3씩 그 값을 증가하시키면서 숫자 리스트를 반환한다. 이런 수치계산적 진행은 문자열 슬라이싱과 함께 리스트 내포안에서 사용되어 3개의 문자열을 가진 리스트를 만들어 낸다.
>>> def codons(s):
...     """dna 문자열에 대하여 코돈 리스트를 반환하라."""
...     end = len(s) - (len(s) % 3) - 1
...     codons = [s[i:i+3] for i in range(0, end, 3)]
...     return codons
...
>>> codons("CCGGAAGAGCTTACTTAG")
["CCG", "GAA", "GAG", "CTT", "ACT", "TAG"]
문자열 슬라이싱(slicing)은 문자열 지표화(indexing)와 비슷하다. 문자열 하나를 열람하는 대신에, 문자열 슬라이싱은 시작위치에서부터 마지막 점 미만까지 한 구역의 문자열을 열람할 수 있도록 해준다. 그 구문은 s[i:j]인데, 여기에서 i는 시작 위치이고 j는 끝 위치이다. 그래서 s[0:3]은 지표 0, 1, 그리고 2에 위치한 문자열 하나를 반환한다.
>>> s = "CCGGAAGAGCTTACTTAG"
>>> s[0:3]
"CCG"
>>> s[3:6]
"GAA"
>>> s[6:9]
"GAG"
>>> s[9:12]
"CTT"
함수에 대해 마지막으로 흥미로운 사실 하나를 주목하자. 함수는 그 자체로 객체이다. 이는 곧 문자열과 리스트에 하던 것과 똑같이, 그의 속성들을 dir()을 사용하여 들여다 볼 수 있다는 뜻이다. 더 유용한 함수 객체의 속성 하나는 문서화 문자열이다. 이 문자열은 __doc__ 특성에 저장되어 있다.
>>> dir(transcribe)
["__call__", "__class__", "__delattr__", "__dict__", "__doc__",
"__get__", "__getattribute__", "__hash__", "__init__", "__name__",
"__new__", "__reduce__", "__repr__", "__setattr__", "__str__",
"func_closure", "func_code", "func_defaults", "func_dict",
"func_doc", "func_globals", "func_name"]
>>> transcribe.__doc__
"Return dna string as rna string."
이 마지막 예제가 이해가 안가더라도 걱정하지 말자. 이 예제를 보여주는 주요 의도는 파이썬이 아주 강력하고 일관성이 있다는 것을 강조하기 위해서였다. 파이썬에서 모든 것은 객체이며, 객체들의 내부를 바로바로 들여다 볼 수 있다는 것을 보여주기 위함이었다. 결과적으로 여러분은 파이썬을 익혀감에 따라, 처음에는 친숙하지 않던 객체가 맨 처음 사용할 때 예상하곤 했던 행동과 종종 똑같이 행동한다는 것을 알게 될 것이다. 이는 다른 프로그래밍 언어를 사용할 때는 경험하기 힘든 강렬한 느낌이다.

지금까지 문자열, 리스트, 사전, 그리고 함수와 같은 간단한 객체들을 만드는 법을 살펴 보았다. 이제는 고유한 특성과 메소드들을 가진 맞춤 객체를 정의하여 만드는 법을 살펴보겠다.

파이썬 클래스

독자적인 맞춤 클래스를 만들려면, 클래스라고 부르는 일종의 틀을 정의해야 한다. 파이썬에서는 class 서술문, 그 다음에 그 클래스의 이름과 쌍점을 사용하여 만들 수 있다. 이 다음에, 클래스의 몸체 정의에 이 클래스에 기초하는 모든 객체 실체들에서 사용하게 될 메소드와 특성들을 담는다.

지금까지 만든 모든 함수들을 살펴 보고 그 함수들을 DNA 클래스의 메소드로 다시 형변환해보자. 그 후에 DNA 클래스에 근거하여 DNA 객체 만드는 법을 살펴 보겠다. 파이썬 셸로부터 이 모든 것들을 할 수도 있겠지만, 이 코드를 bio.py 파일 안에 넣어 놓고 이 파일을 파이썬 셸에서 어떻게 사용하는지를 보여주겠다. 파이썬에서 모듈이라고 부르는 bio.py 파일의 내용은 다음과 같다.
class DNA:
    """문자열 연속열로 DNA를 표현하는 클래스."""
 
    basecomplement = {"A": "T", "C": "G", "T": "A", "G": "C"}
 
    def __init__(self, s):
        """DNA 실체를 문자열 s로 초기화하여 만들어라."""
        self.seq = s
     
    def transcribe(self):
        """rna 문자열로 반환하라."""
        return self.seq.replace("T", "U")
     
    def reverse(self):
        """dna 문자열을 역순서로 반환하라."""
        letters = list(self.seq)
        letters.reverse()
        return "".join(letters)
     
    def complement(self):
        """짝 dna 문자열을 반환하라."""
        letters = list(self.seq)
        letters = [self.basecomplement[base] for base in letters]
        return "".join(letters)
     
    def reversecomplement(self):
        """dna 문자열의 반대 짝(reverse complement)을 반환하라."""
        letters = list(self.seq)
        letters.reverse()
        letters = [self.basecomplement[base] for base in letters]
        return "".join(letters)
     
    def gc(self):
        """G+C로 구성된 dna 비율을 반환하라."""
        s = self.seq
        gc = s.count("G") + s.count("C")
        return gc * 100.0 / len(s)
 
    def codons(self):
        """dna 문자열에 대하여 코돈(codons) 리스트를 반환한다."""
        s = self.seq
        end = len(s) - (len(s) % 3) - 1
        codons = [s[i:i+3] for i in range(0, end, 3)]
        return codons
이 중 상당수는 기존의 함수에 기반을 두고 있기 때문에 익숙할 것이다. 클래스 정의에는 다루어 보아야 할 새로운 요소들 몇 개가 추가된다. 나머지를 더 자세하게 탐험하기 전에 이 새 클래스를 사용하는 법을 살펴보자.

마치 함수를 호출하듯이 그 클래스를 호출하여 객체 실체를 만든다. 제일 먼저 해야 할 일은 파이썬 셸이 이 클래스 정의를 인식하도록 만드는 것이다. 그렇게 하려면 DNA 클래스 정의를 bio.py 모듈로부터 임포트하면 된다. 그리고 나서 DNA 클래스의 실체를 하나 만들고, 초기화 문자열 값을 넘겨준다. 바로 그 시점부터 그 객체는 자신의 배열 값들을 추적하며, 단순히 그 객체에 대하여 정의된 메소드들을 호출하기만 하면 된다.
>>> from bio import DNA
>>> dna1 = DNA("CGACAAGGATTAGTAGTTTAC")
>>> dna1.transcribe()
"CGACAAGGAUUAGUAGUUUAC"
>>> dna1.reverse()
"CATTTGATGATTAGGAACAGC"
>>> dna1.complement()
"GCTGTTCCTAATCATCAAATG"
>>> dna1.reversecomplement()
"GTAAACTACTAATCCTTGTCG"
>>> dna1.gc()
38.095238095238095
>>> dna1.codons()
["CGA", "CAA", "GGA", "TTA", "GTA", "GTT", "TAC"]
클래스는 여러 객체 실체들을 만드는 데 사용되었던 일종의 틀처럼 행동하기 때문에, 클래스 메소드 내부에 메소드가 호출될 특정 객체 실체를 참조할 능력이 필요하다. 이런 필요성을 고려하여 파이썬은 자동으로 객체 실체를 각 메소드에 첫 번째 인자로 건넨다. 파이썬 공동체에서의 관례는 그 첫 인자를 "self"라고 이름 짓는 것이다. 그 때문에 DNA 클래스의 메소드 정의에서 첫 번째 인자가 모두 "self"이다.

또 주목해야 할 것은 __init__() 메소드이다. 파이썬은 클래스의 실체를 만들 때 이 특별하게 이름지어진 메소드를 호출한다. 예제에서 DNA.__init__은 문자열 인수를 받을 것이라고 예상하며 받은 인수는 객체 실체의 특성인 self.seq에 저장한다.

함수를 클래스 메소드로 바꾸면서 하나 더 변경이 되었다. complement() 메소드에서 basecomplement 사전 정의를 꺼내서 클래스 정의로 옮겼다. 클래스 정의의 일부분으로서, 사전은 메소드가 호출될 때 만들어 지는 것이 아니라 오직 한번만 만들어진다. 사전은 그 클래스의 모든 실체들이 공유하며 하나 이상의 메소드가 사용할 수 있다. 이점은 seq 특성과는 대조적인데, 이 seq 특성에는 각 객체 실체가 독자적으로 유일한 값을 가지게 될 것이다.

보시다시피, 클래스는 관련된 데이터와 기능을 그룹짓기 위한 효과적인 방법을 제공한다. DNA 실체를 몇 개 더 만들어 보고 파이썬 셸 세션을 끝내자.
>>> dna2 = DNA("ACGGGAGGACGGGAAAATTACTAGCACCCGCATAGACTT")
>>> dna2.codons()
["ACG", "GGA", "GGA", "CGG", "GAA", "AAT", "TAC", "TAG",
"CAC", "CCG", "CAT", "AGA", "CTT"]
>>> dna3 = DNA(dna1.seq + dna2.seq)
>>> dna3.reversecomplement()
"AAGTCTATGCGGGTGCTAGTAATTTTCCCGTCCTCCCGTGTAAACTACTAATCCTTGTCG"
>>> dna4 = DNA(dna3.reversecomplement())
>>> dna4.codons()
["AAG", "TCT", "ATG", "CGG", "GTG", "CTA", "GTA", "ATT",
"TTC", "CCG", "TCC", "TCC", "CGT", "GTA", "AAC", "TAC",
"TAA", "TCC", "TTG", "TCG"]
이토록 초보적인 클래스 정의를 가지고도 파이썬 셸에서 조작을 하면, 생물학적 데이터를 최소한의 구문적 부담으로 일관성 있고 깨끗하게 분석하는 파이썬의 잠재적인 능력을 볼 수 있을 것이다.

맺는말

파이썬은 인기있는 오픈 소스 프로그래밍 언어로서 생명정보학 공동체에 많은 것을 줄 수 있다. 물론 파이썬이 생명정보학에 늦게 발을 들여 놓았기 때문에 펄이 누리는 만큼의 큰 인기를 누릴 수 없을 수도 있다. 그렇지만 펄을 선택할 것인지 파이썬을 선택할 것인지 그 선택은 여러분의 자유이다. 파이썬은 생물학자와 전문 프로그래머에 모두에게 신뢰성 있고 유망한 선택을 제공한다. 이 기사로 여러분이 파이썬을 좀 더 자세히 살펴보는 기회가 되었기를 바란다.

추가 자원

지금까지 파이썬에 대해 살펴본 바가 마음에 든다면 아래 참고 사이트도 한번씩 방문해보기 바란다.
패트릭 오브라이언(Patrick O"Brien)은 독립 소프트웨어 개발자이자 강사로서, 파이썬 프로그래밍 전문가이다. 그는 PyCrust의 저자이자 PythonCard 프로젝트의 개발자, PyPerSyst 프로젝트의 리더로 활동하고 있다. 그의 메일주소는 pobrien@orbtech.com이다.
TAG :
댓글 입력
자료실

최근 본 책0