블로그 이미지
자유로운설탕

calendar

1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30

Notice

2017. 12. 2. 22:35 보안

  파이썬에 대한 글을 마치고, 이제 두 번째로 보안에 대해서 이야기를 한번 해보려고 한다. 개인적으로 생각했을때 아직 잘 모르는 영역들도 많고, 관심은 있지만 공부를 안해서 발전이 거의 없는 영역들도 많다고 생각한다. 

 

  이 글에서는 보안을 처음 공부하거나, 공부는 하고 있지만 너무 범위가 넓고 어렵게 느껴지거나, 개발이나 시스템, 테스팅 같은 연관이 있는 일을 하는 사람들에게 보안이 생각보다 그렇게 어려운 분야는 아니며, 개발이나 테스팅, 시스템, 네트워크 쪽의 지식처럼 해당 분야의 패턴을 익혀두면 여러 다른 분야에서도 유용하게 쓸 수 있다는 것을 얘기해 보려 한다. 나아가 얼마나 잘 될지는 모르겠지만 보안의 어떤 부분이 보안을 어렵게 생각하게 만들게 되는지도 개인적인 경험에 비추어 설명해보고 싶다. 이전 파이썬 글과 마찬가지로 하나의 접근 방식이라는 관점으로 비판적으로 받아들이기를 바라며 역시 재밌는 시간이 되길 바라면서 글을 시작하려 한다.

 

 

[목차]

1. 보안을 바라보는 방법

2. 보안에서의 코드 읽기

3. 인젝션(Injection) 살펴보기

4. 암복호화

5. 클라이언트 코드

6. 업로드, 다운로드

7. 스크립트 문제

8. API

9. 설정, 패치, 하드닝

10. 설계문제

11. 스캐너 vs 수동 테스트

12. 자동화 잡

13. 리버싱과 포렌식

14. 모니터링 문제

15. 악성코드

16. 보안과 데이터


 

1. 들어가면서

  보안 공부를 어떻게 해야하느냐를 알고 싶어서 검색엔진을 찾아보면 해야될 일이 엄청 많아 보인다. C++와 같은 프로그래밍 언어부터 시작해서, 웹 및 시스템 프로그래밍, 객체지향 개념, 운영체제, 웹서버, 네트워크 장치들, 프로토콜, 암호학, 리버싱, 포렌식, 악성코드 등에 대해 하나하나 이해해야 하고, 나아가 오픈소스, 모의해킹, 보안 관련 툴 들을 이해해야 한다고 한다. 

 

  사실 위의 요소 중 하나로만 범위를 제한하게 되더라도 엄청 많은 하위 카테고리로 확장되게 된다. 예를 들면 웹은 CGI(Common Gateway Interface) 같은 초창기 서비스 구조로부터 ASP, PHP 언어 같은 스크립트 베이스의 언어, JAVA, NET 같은 좀 더 포말한 프로그래밍 형태의 웹으로 확장되고, 여러 타입의 서비스와 API 등도 포함되게 된다.

 

  네트워크도 이름도 낯선 OSI 7 계층 구조부터, TCP/IP, 방화벽, IPS, 스위치, 라우터 등의 여러 이슈들로 나누어지고, 그런 부분도 또 관련 장비의 밴더나 각 진화된 세대에 따라서 서로 다른 영역으로 갈라질 수 있다. 나머지 필수 기술 항목들도 역시 마찬가지로 많은 갈림길이 있다. 웹 서버들도 아파치, IIS, 톰캣 등 조금씩 특성이 다른 여러가지의 웹 서버들이 있고, 각 서버는 각각의 버전마다 설정이나 지원 기능 등에 따라 특성이 또 조금은 다르다고 볼 수 있으며, 다른 웹 언어들과도 연결된다.

 

  게다가 현대의 빠른 기술의 변화에 맞춰서, 빅데이터나 머신러닝등의 새롭게 보안이 필요해지는 영역과 언어들이 계속 쏟아져나오고 있다. 또한 좀 더 넓게 현실적으로 보면 IT 기술의 범위를 벗어나서 물리적인 영역이나, 작업 및 관리 프로세스, 심리적인 부분도 관련 되게 된다.

 

 

  개인적인 관점에서 또 하나 보안을 어려운 분야로 생각하게 하는 부분은, 보안 측면을 바라보는 시각이 매력적인 모의해킹이라는 공격적 측면과, 자격증 획득이라는 측면에 많이 중심이 치우쳐 있기 때문인 것 같다.

 

  보안 지식 전체가 몸을 건강하게 해주는 운동들의 구성이라면, 모의해킹은 프로격투기 선수의 실전 스파링 훈련과 같다고 본다. 모의해킹은 시스템을 구성하는 모든 제반 기술들에 대한 균형있는 검증을 통해서, 안전한 디자인이 이루어졌나를 확인하는 활동이라고 생각한다. 그래서 모의해킹 기법 관련 책에서 얘기하는 많은 백과사전 방식의 기법을 익혀서 대상에 대해서 테스트를 하고, 취약점을 찾거나 찾지 못한 결과에 따라 대상이 불안하거나, 안전하다고 판단하는 것은, 사실 그렇게 많은 의미는 없어 보인다. 중요한 것은 그러한 기법들을 보안 세계 쪽에서 정리하고 권장하게 된 배경을 이해하고, 점검 하려는 대상에 대해 기술적으로 정확하게 이해하여 적절한 기법을 적용하여 검증하는 부분인 것 같다.

 

  자격증 또한 해당 분야를 마스터(사실 사람 자체가 불완전 하며, 모든 분야에서 한 개인은 역사를 스쳐가는 디딤돌 같은 역활인지라 어떤 분야를 마스터했다는 말은 신기루 같은 것 같기도 하지만...)한 전문가를 보면서 행동을 따라하는 것과 비슷하다고 본다. 자격증에 나오는 지식은 현실의 많은 보안 지식을 모아서, 전문가들이 정리하고 체계화된 지식에 불과하다고 본다. 해당 지식 체계가 보안의 전체적인 모습을 정리하고, 체계적으로 접근을 하게 해주는 것은 부정할 수 없는 사실이지만, 해당 지식안에는 현실을 객관화한 지식은 있지만, 현실 자체는 없는 경우가 많다.

 

   지식 자체는 손가락이고, 그 손가락이 가르키는 곳을 봐야지 의미가 있지 손가락 자체를 아무리 외어도 의미는 없다고 본다(자격증 책 본문의 시험용 암기 내용보다는, 각 챕터의 뒤에 있는 그 내용이 나오게 된 근거인 참고 서적이나 관련 링크들에 대한 포괄적인 이해가 사실 더 중요한것 같아 보이지만, 시험공부를 위해서 기본적으로 외우거나 이해해야 할 내용이 엄청 많기 때문에 그런 부분까지 차분히 읽을 분위기는 되지 않는다). 훌륭한 전문가의 행동을 따라하는 것보다, 그 행동을 일으키게 한 마음의 흐름을 이해해야지만, 실제 해당 전문가의 노하우의 그림자라도 익힐수 있게 되지 않을까 싶다.

 

 

 

2. 보안이 어렵게 느껴지는 이유

  우리가 어떤 것을 배우려면, 우리가 어떤 것을 배우고 싶어 하는지를 우선 명확히 알아야 하는 아이러니가 있다(파이썬 글 21교시의 5 orders of ignorance 를 참고). 보안이란 분야가 어떤 것 인지에 대해서 많은 사람들이 서로 다른 정의를 가지고 접근하겠지만, 개인적인 관점에서 가장 주요한 부분은 데이터의 흐름을 따라가는 것이라고 본다. 데이터의 관점에서 한정해 보면 개발은 데이터가 설계된 길로 가게하는 것이고, 테스팅은 데이터가 주어진 길로 제대로 가는지를 증명하는 일이며, 보안은 데이터가 주어진 길에서 납치나 회유되지 않는지 살펴보는 일이라고 볼수 있다고 생각한다. 

 

  또한 보안은 일반적인 프로그래밍이나 테스팅 보다는 우리의 일상생활이나 시스템 바깥의 물리적, 프로세스적 환경의 관리까지 생각하기 때문에 조금 더 범위가 넓게 된다. 여담이지만 점점 개인정보보호 분야의 영역과 보안의 영역이 많이 겹치게 되고 있는데, 개인정보는 (개인의) 중요한 데이터를 보호하는 분야이고, 보안은 중요한 데이터를 보호 하는 분야이기 때문에, 어쩔 수 없이 기술적인 영역에 대해서 관심사는 살짝 다르지만 상당 부분 겹치게 되어있다고 본다.

 

 

  그럼 데이터의 흐름을 따라가려면 어떻게 해야 할까? 데이터가 보여지거나, 지나가거나, 처리되거나, 저장되는 모든 영역을 잘 이해해야 한다. 컴퓨터 안의 CPU, 레지스터부터, 메모리, 디스크 내의 바이너리 형태의 데이터, 운영체제, 프로세스, 프레임워크, 응용 프로그램, 웹서버, 네트워크(프로토콜), 서버, 관리프로그램, 사용자 프로그램, 사용자, 외부 장치, 업무 프로세스 또는 은행의 비밀번호를 기억하고 있는 고객들에 이르기 까지 데이터가 현실의 물리적 형태, 0과 1의 형태의 바이너리 형태로 존재하거나, 레지스트 값, 변수, 메모리 값, 프로그램 내의 변수, 설정 값, 입력 값, 전송 값 등 모든 머무는 영역에 대해서 올바르게 이해해야 한다. 결론적으로 얘기하면 앞의 많은 기술의 요소 부분을 이해해야만 보안을 잘 할 수 있는 것이 아니고, 꺼꾸로 보안을 잘하기 위해서, 그러기 위해서 데이터를 이해하기 위해서, 데이터의 흐름을 이해하고 적절히 보호하기 위하여 해당 기술들을 익혀야 한다고 본다. 

 

  이러한 끝이 잘 보이지 않는 넓은 범위의 기술 범위들은, 앞의 파이썬 글에서 얘기했던 프로그래밍의 배경지식과 비슷하다고 본다. 보안은 보안의 원리 자체가 어려운게 아니라(뭐 쉽다는 의미는 아니고, 배경지식을 잘 안다면 생각보다는... 이라는 말이지만... 반대로 얘기하면 배경지식을 모르면 실제보다 과도하게 어려워 보인다) 이러한 배경지식의 균형있는 습득이 어려우며, 프로그래밍에서의 게임 프로그래밍의 위치와 비슷하게, IT계의 종합 예술 영역 같은 성격을 띄고 있다고 생각한다. 그래서 보안은 데이터를 제대로 보고 흐름을 따라가기 위해서 많은 배경 지식이 필요하게 된다. 프로그래밍도 그러한 배경 지식 중 하나이고, 앞서 얘기한 많은 분야들도 그래서 필요하게 된다. 그리고 모든 분야가 마찬가지겠지만 모르는 분야나, 새로운 기술을 계속 따라가야 하는 어려움도 있게 된다.

 

 

  예를 들어 보안을 모의해킹 같은 공격의 관점으로 봐서, 아주 뛰어난 도둑이 있다고 가정을 해보자. 해당 도둑은 사회공학적 기법을 이용해 사람들을 속여 정보를 얻거나, 원하는 행동을 하게 하는 것도 능숙하고, 담을 타거나 빠르게 이동하기 위한 신체능력도 뛰어나며, 일반 자물쇠나, 생체인식등과 같은 현존하는 모든 자물쇠의 이해에 능통한 상태로 그러한 기법들을 이용하여 원하는 목표를 놓친적이 없다고 하자.

 

  그런데 어느날 열쇠의 주인이 아니면 절대 풀수 없다는 양자역학을 이용한 자물쇠(무슨 자물쇠인지는 모르지만 그런 어려운게 생겼다고 해보자)가 발명되면서 주요 보물들이 해당 자물쇠에 의해서 보호되게 되었다. 이제 도둑이 계속 목표를 달성하려면 어떻게 해야될까? 해당 양자역학적 자물쇠를 이해하여 속일 수 있는 방법을 찾는 수 밖에 없다(미션 임파서블 영화를 생각하면 된다. 물론 영화에서처럼 어떤 자물쇠로 잠겼는 지에 상관없이 사람이나 환경을 이용하여 사회공학적 기법으로 푸는 방법도 있긴 하겠지만 말이다. 기술은 기술을 운영하는 사람의 스마트함에 비례해 효과가 있으니 말이다).

 

  또는 반대로 여러분이 자물쇠가 얼마나 안전한지 체크를 해주는 보안 전문가고, 해당 양자역학적 자물쇠를 사용하는 사람으로부터 해당 자물쇠가 얼마나 안전한지 검증을 해달라고 요청을 받았다고 해보자. 새로운 자물쇠의 안전을 검증하려면 해당 자물쇠의 원리를 우선 이해해야만 한다. 물론 해당 자물쇠를 이해 못한다고 기존의 뛰어난 도둑(악의적인 해커)이 뛰어나지 않은 것은 아니고, 뛰어난 보안 전문가가 전문가가 아니게 되는 것은 아니다. 하지만 해당 분야에서 전문가 명성을 계속 유지하기 위해서는 새로운 기술을 이해하여, 자신이 기존 체계로 부터 이해하고 있는 패턴을 적용하여 공략점을 찾거나, 외부의 공격에 안전한 설계인가를 증명하지 않으면 안된다.

 

 

  다른 예로 당신이 모든 사기 유형을 마스터한 유명한 사기꾼이고, 새로운 매력적인 파생 상품이 나와서 해당 부분을 이용해서 사기를 치고 싶을 때도, 마찬가지로 대상에 대한 이해가 필요하게 될것 이다(사기는 상대방에 대한 이해와 공감을 바탕으로 한, 9개의 진실과 1개의 거짓으로 이루어진 행위라고 생각한다).

 

  현실의 보안 영역으로 내려오면, 윈도우 보안의 전문가인 당신이 리눅스, IOS, 안드로이드의 보안에 대해 체크하고, 적절한 가이드를 하려면 해당 OS 환경을 잘 이해하는 수밖에 없다. 마찬가지로 새로운 데이터베이스, 프레임워크나, 빅데이터 기술에 대한 보안을 체크하려면 해당 분야를 잘 이해할 수 밖에 없다(물론 관련 영역에 대한 스캐너 같은 여러 솔루션을 사용해서 해결하는 방법도 있겠지만, 원리와 무슨 일을 왜 수행 하는지 정확히 모르는 블랙박스 형태의 툴에 의존하는 것은 보안 일을 하는 사람으로서는 너무 순진한 행동이라고 본다). 물론 기존의 다른 유사한 분야에 대한 풍부한 이해는 새로운 분야에 대해서도 많은 부분 패턴으로 적용될 가능성이 높지만, 그것은 새로운 분야를 제대로 이해한 후에야 완전하게 발휘될 수 있는 능력일 것이다(물론 대상의 빠른 이해에도 일정부분은 도움은 된다고 본다). 새로운 분야에서 데이터가 어떻게 흘러가는지를 확실히 이해할 수 없다면, 기존에 익힌 보안에 대한 패턴들을 어디에 적용할지 몰라서 손가락만 빨고 있게 될 수도 있다(아마 아는척 사기를 치지 않는 이상은 말이다)

 

[거짓말 잘하는 비결 - 동아사이언스]

http://m.dongascience.donga.com/news.php?idx=6462

 

 

  또한 보안은 언제나 사실에 기반한 마술과 비슷하다. 취약점은 이해하기 어렵거나 신비로울 수는 있지만, 항상 사실(코드나 설계)에 기반하여 일어나는 것임은 분명하다(그래야 방어의 설계도 가능한 것일테고...). 보안 취약점은 기술의 빈 틈을 파고 들어가는것이며, 기술 자체의 안전한 구성원으로 위장하여 들어가, 데이터베이스 등 외부 요소나 기술 자체에 영향을 미치는 것을 의미한다.

 

  웹 보안에서 가장 흔하게 나오는 SQL Injection 같은 부분은, 만약 여러가지 데이터베이스의 명령어나 관리 기술에 두루 능통하다면, 정말 빠르게 공격과 방어에 대한 원리를 이해할 수 있다(A piece of cake라고 하고 싶다) . 그런데 만약 데이터베이스를 모르는 상태에서 SQL Injection 을 이해하려 한다면, 보안 자체의 측면보다는 데이터베이스와 SQL 문장의 이해의 늪에 빠져서 시간도 많이 걸리고 불완전하게 단편적으로 이해하고 넘어갈 가능성이 높다.

 

  비슷하게 자바스크립트를 기반으로 한 XSS 나 CSRF 같은 취약점 패턴 등도 자바스크립트를 얼마나 잘 이해하고 있느냐에 이해의 난이도와 깊이가 달려있고, 커맨드 인젝션이나, XML 인젝션 등도 얼마나 시스템 명령어나, 프로그램의 내부 구조 및 관련 함수들, XML 구조를 잘 이해하느냐에 중요 포인트가 있다(물론 특정 취약점은 프로그램의 다른 요소와 믹스되어서 효과가 증폭되거나 경감되는 부분도 있긴 때문에 한 기술의 타입으로 단순화 하기는 힘들지만 말이다). 해당 부분에 대한 이해가 충분하다면 보안 이라는 패턴을 적용해 어떻게 해당 기술이 악용이 되고, 방지할 수 있는지를 쉽게 이해할 수 있게 된다. 나아가 그 방어 방식의 한계 및 제약과, 한계에 따른 리스크를 다른 수단으로 보완하여 경감해야 된다는 사실도 인지할 수 있게 된다. 해킹이란게 시스템을 속이는 것이라 얘기되지만, 그 앞에는 사실 시스템을 (잘 이해하고) 속이는 것 이라는 중요한 말이 숨어 있다고 생각한다.

 

  그럼 이 글에서는 맘대로긴 하지만 (기술적인) 보안 공부를 다음과 같이 정의해 보도록 하겠다. "데이터의 흐름을 이해하기 위해서, 데이터가 흘러가는 공간을 구성하고 있는 기술에 대해서 이해하는 과정". 앞으로의 시간들은 쓰는 사람의 지식의 한계 때문에, 그러한 부분의 아주 깊은 곳까지는 안내하진 못하겠지만, 데이터의 흐름을 이해하기 위해 이용되는 여러가지 방법들을 살펴보는 방식으로 진행될 것 같다. 하지만 뭐 항상 그렇지만 진행되다보면 원래 의도와 다르게 흘러가기는 한다...

 

 

 

3. 보안의 공격과 방어 측면의 차이

  프로그래머들에게 조금 미안하긴 하지만 개인적으로 IT 쪽의 많은 분야에서 그다지 바람직하지는 못하다고 생각되는 흐름 중 하나는, 프로그래밍 경험이 있느냐에 대한 부가가치를 너무 크게 잡는 다는 부분이라고 본다. 물론 QA나 보안이나, 시스템, 빅데이터 등의 여러 분야에 대해서 프로그래밍 능력이 상당한 도움이 되는 것은 맞다.

 

  하지만 프로그래밍 실력을 미리 갖추고 일을 시작 하는게 중요한 거라기 보다는, 해당 업무의 특정 레벨에 올라가게 될때 자연스럽게 업무의 효율성과 확장성을 위해서 자연스럽게 필요성이 생기게 되는 것이라고 본다. 무언가 귀찮거나 반복되서 의미 없는 일을 줄이기 위해 위해서는 어쩔 수 없이 자동화의 힘을 빌려야 하고, 그러다 보면 조금씩 자연스럽게 프로그램이란 세계에 발을 들이게 된다. 물론 그 전에도 API 사용이나, 명령어 스크립트 작성 등 기본적인 프로그래밍 지식이 있으면 좀더 적응이 용이한 부분들도 분명히 있다. 

 

  개인적으로는 프로그래밍 능력이 있느냐가 원천적으로 있느냐가 중요하다기 보다는, 업무상 자연스럽게 필요하게 되서 프로그래밍 능력을 습득하게 된다는 것이 좀더 맞을 것 같다. 물론 해당 부분에 대한 부분은 금방 휘리릭 습득되는 부분은 아니기 때문에 직업의 초기부터 조금씩 꾸준히 노력하는 것이 맞아보인다. 추가로 분야에 따라 프로그래밍 이란 것도 각 분야에 적절한 스타일로 다르게 접근 되기 때문에 쓰이는 포커스가 다른 경우도 많다. 제일 중요한 것은 해당 분야의 도메인 지식을 기반으로 적절하게 프로그래밍 언어의 능력을 쓰는 것인것 같다.  파이썬 글 17교시의 머신러닝 부분에서 프로그래밍이 어떤 식으로 쓰이고 있는지와 비슷하게 보면 된다고 할까? 표현하긴 좀 어렵지만 프로그래밍은 프로그래머 들에게만 생기는 직업적 기술이라기 보다는 약간 공공재 적인 기술적 영역이라고 보고 싶다. 그래서 두려워 하지 말고 일단 뛰어들어 보는 것을 추천한다. 시간을 들여 익숙해 지면 야매 정도는 되니까...

 

 

 보안 쪽도 비슷한 흐름이 있다고 보는데, 웹어플리케이션 보안 부분을(특히 모의해킹이라고 칭해지는 부분) 특수한 인력이 특수한 절차를 밟아서 획득할 수 있는 완전한 검증 방식으로 생각한다는 부분이다. 모의해킹은 표면적인 효과 이외에 생각할 점이 많은 분야라고 본다. 

 

  우선 수행하는 사람의 능력에 많이 좌우된다. 보안 분야도 개발이나 테스팅과 비슷하게 사람들마다 접근하는 전략과 효율성의 차이가 많이 나고, 기술 및 도메인에 대한 이해도도 틀리며, 그 차이가 외부에서 볼때 확실히 구분되기 힘들다. 인력이나 도메인에 많은 영향을 받아 어떤 기술셋을 가진 사람이 어떤 분야를 점검하느냐도 중요하고, 어떤 언어나, 개발프로세스를 거쳐 만든 도메인을 만나느냐에 대한 운도 작용하는 것 같다. 또 점검 후 점검한 영역에서 실제 사고가 나거나, 비슷하거나 더 상위 수준(또는 비슷하지만 시간에 쫒기지 않은 한가한)의 인력이 충분한 시간을 가지고 크로스 체크 하기 전에는 효과를 증명하기 힘든 경우도 종종 있는 듯 싶다. 또 이해한 만큼만 점검이 가능하기 때문에, 시행자의 대상의 이해 정도에 의해서도 많이 차이가 나고, 일반적으로 리소스나 범위 등의 여러가지 사유로 소스 기반이 아닌 블랙박스나 그레이박스 기반, 그리고 제한된 시간 프레임 내에서 진행되기 때문에, 구성되어 있는 자원이나 소스에 대한 완전한 커버리지를 가지긴 힘든 경우도 많은것 같다.

 

  그리고 대부분의 경우 증명보다는 시나리오 기반으로 진행되기 때문에, 해당 시나리오를 벗어난 전체적인 보안성 측면에 대해서는 보장(Assureance)하지는 못한다고 보는게 맞을 것 같다. 또한 불행하게도 점검한 소스나 환경은 계속 변할 수도 있기 때문에, 해당 점검 시점 후 시간이 지나게 되면 다시 코드의 안전함을 보장하지 못한다는 모순에 빠지게 된다(개발자중 한명이 모의해킹이 완료된 후의 코드 베이스에 취약한 코드를 하나 넣었다면 어떻게 될까?).

 

  마지막으로 시스템이 너무 기초적인 보안 설계가 안 되어 있다면 중요한 취약점들을 다수 발견할 수는 있겠지만, 해당 취약점에 대부분의 시간을 쏟고, 진행이 막히게 되어, 더 미묘한 내부를 들여다 보지 못할 수도 있다. 뭐 중요한 기본 취약점들을 찾았다는 의미는 있긴 하지만, 해당 취약점들에 갈 길이 막혀 깊이 있는 취약점은 찾지 못할 수 있다. 기본적인 기능의 동작도 제대로 안되서 중요도가 높은 버그를 잔뜩 올렸지만, 해당 가능에 대해 깊이 있는 테스트를 하지 못하는 QA 테스트와 비슷한 상황이라고 보면 된다. 해당 경우는 모의해킹이 중요한게 아니라 시스템이나 프로세스를 기초부터 개선 하는게 더 중요할지도 모르는 상황이라고 본다. 분명히 모의해킹은 보안쪽의 중요한 요소이기도 하고, 수술에 꼭 필요한 잘 드는 메스이기는 하지만 은총알은 아니여서, 환자의 상태나 다른 검사 장비들과, 의사에 판단에 의해 잘 조합하여 사용해야 효과가 큰 것 같다.

 

  그래서 모의해킹은 어떻게 보면 공부를 열심히 하고 보게되는 고3의 수능 같은 평가 활동 같다고 보고 있다. 테스팅을 아무리 하더라도 최초부터 없는 품질을 얻을 수는 없는 것과 마찬가지로(중요한 버그들을 발견하고 고치는 행위자체가 반드시 품질을 보장하진 못한다 파이썬 머신러닝 파트에서 얘기했던 garbage in garbage out 과 마찬가지로...), 모의해킹으로 애초부터 들어 있지 않은 보안성을 잡을 수는 없다. 그런 믿음은 이미 다 지어진 집에서 발견된 균열들을 급하게 메우면서 집이 안전하길 바라는 것과 비슷하다. 사실은 설계 부터의 과정에서 뭔가 어긋나서 생긴 문제인데도 말이다. 

 

  보안성은 시험 전에 한참 꾸준히 해왔던 공부 같이, 여러 요구사항의 수집에서 시작하여, 안전한 설계, 개발의 과정 중에 이루어지는 여러 관점의 보안적 리뷰와 체크에서 생기는 것이라고 보는게 좀더 근본적일 것 같다. 모의해킹은 어플리케이션 보안에 대한 오랜 동안의 업계 사람들의 시각과 기술적 노하우, 취약점에 대한 개인의 감, 실제 경험들이 총 집성된 평가 체계라고 생각하지만, 역으로 그런 기본 역량을 충분히 갖춘 사람들이 평가를 해야지 충분한 의미가 있다는 단점도 분명히 있다는 것을 잊으면 안된다(물론 절차적인 면죄부라는 느낌이 좀 있긴 하다...) 

 

 

  그럼 해킹, 모의해킹과 같은 공격과 시큐어 코딩과 같은 방어의 차이는 무엇일까? 우선 둘의 공통점은 공격하고자 하는 대상과 방어하고자 하는 대상이 먼저 얘기한 데이터라는 것이다. 두 가지의 차이점은 , IT를 무협과 비교하는 걸 별로 선호하는 편은 아니지만, 무협만화에 나오는 빠른 검과 느린 도의 대결과 비슷하다고 본다(멋진 캐릭이 나오는 게임 또는 만화의 삽화를 넣고 싶긴 하지만, 저작권 문제로.. 무협 만화 좋아하는 사람은 열혈강호의 담화린(빠른칼)과 도제(느리지만 무거운 도)가 싸우는 장면을 상상해 보면 어떨까 싶다^^). 공격은 시스템의 헛점을 노리고 다양한 영향을 미치려고 계속 변화하면서 수행되는 반면, 방어는 사실 기본기에 충실해서 느리지만 핵심을 지키면서(靜中動) 공격으로 들어온 외부 코드들을 무용지물로 만들게 된다고 본다.

 

 

  그럼 느리지만 무거운 도에 해당하는 방어를 코드로 예를 들면 어떻게 될까? 개인적인 생각에는 아래와 같이 (외부에 입력된 변수로 부터 영향을 받을 가능성이 있는) 내부에서 사용할 변수의 타입과 범위를 체크하는 코드가 아닐까 싶다. 아래와 같이 정확한 정수 타입과 필요한 수의 범위를 제한하게 되면, (아래와 같은 숫자 변수에 대한 코드에 경우에는) 어떠한 인젝션 시도에도 안전한 코드가 될 것 같다.

1
2
3
4
5
6
ext_var = "union all ... --"
 
if type(ext_var) is int and ext_var > 0 and ext_var <= 10:
    print ("use variable");
else:
    print ("not safe");
cs

 

   해당 방어 코드를 모르는 공격자는 공격을 위해서 많은 화려한 기법을 사용할테고, 그 중에 똑똑한 공격자는 금새 잘 막혀 있다는 걸 알아채고, 시간낭비 없이 다른 취약한 변수나 코드들을 찾아 다니게 될 것이다. 좀 더 나아가면 해당 부분은 관련 방어를 지원하는 프레임워크를 쓰거나, 해당 프로그래밍 언어에서 지원하는 범용적인 보안 함수를 사용하는게 더 맞겠지만, 여기서는 방어하는 무거운 도(刀)에 대한 극단적인 이미지를 나타내기 위해서 아래와 같은 코드로 제시해 보았다. 문자열 변수 등과 같이 좀더 상세하게 따져가며 저울질할 부분들은 나중에 해당 주제에 대해서 다루면서 얘기하면 될 것 같다.

 

  파이썬 코드의 실행을 모를 경우 파이썬 2교시 글을 참고하면 된다

c:\Python\code>python variable_check.py
not safe

 

 

  한가지 더 인터넷에서 많이 언급되는 얘기를 생각해 보면, 공격의 대표적인 방식인 모의해킹의 기법을 이해해야만, 코드의 방어를 어떻게 해야 할지 제대로 알수 있다는 부분이 있다. 해당 부분은 맞는 얘기기는 하지만, 다른 한편으로 조금 더 생각해 볼  부분이 있다고 본다.

 

  방어 측면에서의 모의해킹의 이해란, 모의해킹에서 사용하는 기법이나 다양한 툴의 원리와 사용의 이해의 측면 보다는, 해당 공격으로 일어나는 데이터들의 변화에 초점을 맞추는 것이 맞을 듯 싶다(이 부분은 보안 테스팅을 위해 코드를 읽을 때도 비슷하게 적용된다. 물론 취약점의 원리를 이해하면 자동으로 툴이 무엇을 하려 하는지를 어느정도 이해하게 되겠지만 말이다..). 어차피 방어 측면에서는 공격과 같이 현란한 데이터의 변경으로 맞설 수는 없기 때문에(물론 뭐 요즘의 OS 등에서 실행파일이 로드되는 메모리 번지를 계속 변경 시킨다든지, 솔루션 등에서 머신러닝 등을 이용해서 비 정상적인 통계 패턴을 계속 추적한다든지 하는 등은 공격과 비슷한 다이나믹한 방어로 볼수 있긴 하겠지만), 일반적으로는 공격의 원리에 대치되는 배치로 대응하는 수밖에 없다. 공방 게임에서 공격 유닛에 대한 방어는, 해당 유닛에 상응하는 방어 건물의 배치로 이루어지는 것과 비슷하지 않을까 싶다. 그 방어 건물이 우리가 자주보게 되는 "시큐어 코딩 가이드" 같은 거고 말이다.

 

 물론 모의해킹의 아주 미묘한 부분까지 이해하여, 관련 전문가 수준으로 알게되면 분명 더 좋은 방어를 하게 될듯 싶다(물론 공격 뿐만 아닌 방어 관점의 이슈들도 충분히 잘 안다는 가정하에 얘기이다). 

 

  보안에 대한 분야는 사실 너무 넓은 분야이고, 모든 존재하는게 사실 보안과 연관이 있고, 그 안에 서식하고 있는 많은 요소들도, 계속 살아있는 생태계 처럼 변하고 있기 때문이다. 지금 해당 영역에서 완벽하더라도 앞으로 계속 따라기기 위해서 해야 될 공부가 너무 많다. 실제로 외부에서 보기엔 완벽해 보이는 사람들도, 좀 더 자세히 들여다 보면 생각보다는 완벽하지 못한 경우가 많고, 그것은 비단 보안 뿐만 아니라 모든 지식의 영역이 마찬가지인듯 싶다.

 

 

  

4. 마무리 하면서

  앞으로 글을 진행 하면서 앞에서 한 얘기들을 좀더 자세히 설명하기 위해서, 몇 가지 방향을 잡고 진행하려 한다. 우선 공격보다는 방어에 필요한 부분을 위주로 설명할 예정이며, 공격을 얘기할 경우는 방어 측면에서 이해가 필요한 공격의 측면을 중심으로 얘기하려고 한다. ASP, Python, PHP 와 같은 스크립트 언어와, 자세히는 모르지만 .NET 이나, JAVA 같은 주제를 잘 이해시켜 줄 수 있는 언어를 가지고, 가능한 실제 움직이는 코드를 구현하고자 한다. 그리고 피들러와 같은 관찰 및 조작 할수 있는 툴을 통해 데이터가 흘러가고, 변조 되는 부분을 시연하고, 방어를 위한 코드를 제시하며, 해당 방어의 원리와 제한 들에 대해서 얘기하려 한다. 

 

  비교적 잘 설명이 가능할 거리고 생각하는 웹 기술을 중심으로 이야기를 시작하며(사실 웹이나 다른 분야나 넓게 보면 기본적인 보안 패턴은 비슷하다고 생각하지만 아직은 크게 자신은 없다^^), 후반에 일반 어플리케이션에 대한 등에 대한 얘기도 얇은 지식이나마 정리해 볼까 한다. 이 글의 목표는 읽은 사람들이 각 보안 영역에 대해서 어떻게 접근해야 겠다는 스스로의 생각을 가지게 되고, 여러 보안 가이드를 볼 때, 해당 가이드를 작성한 사람이, 어떤 관점에서 그러한 방어 전략을 제시했는지에 대한 이해의 기반을 가지게 하는 것이다. 

 

  물론 앞의 파이썬 글과 마찬가지로 각 주제들에 대해 깊은 부분 까지 설명하기에는 스스로 아는 것이 적다고 생각하고, 시간의 제한도 받기 때문에 "보안에 대한 이런 저런 주제를 가볍게 다루는 기술적 산문집" 이라고 기대하고 읽어주면 좋을 듯하다. 내용 중 잘못됬거나 의견이 다른 부분은 댓글을 달아 알려주심 감사할듯 싶다.

 

그럼 재미있는 보안공부가 되길 바라며...

posted by 자유로운설탕
2017. 8. 19. 23:28 프로그래밍

  이제 개인적으로 소프트웨어를 바라 보는 관점에 영향을 주었던 글을 하나 소개하면서, 구글로 공부하는 파이썬 시리즈를 마무리 하려 한다.

 

 

[목차]

0. 왜 파이썬 공부에 구글을 이용하는게 좋은가?

1. 언어를 바라보는 방법. 파이썬을 어떻게 바라봐야 할까?

2. 파이썬 설치와 환경, 버전 선택 하기의 이유.

3. 만들고자 하는 기능을 모르는 조각으로 나눠 조사해 보기

4. 데이터 베이스에서 내용 가져와 출력하기

5. 암호화 모듈을 이용해 암복호화 해보기

6. 퍼즐 조각들을 합쳐보기

7. 엑셀 파일 사용해 보기 -> 부록 : fuction 을 이용해서, 코드 정리해 보기

8. 정규표현식을 왜 사용해야 할까? 언어속의 미니 언어 정규표현식 살펴보기

9. 입력과 결과를 GUI 화면과 연결해 보기

10. Whois API 이용해 보기

11. 웹페이지 호출해 내용 파싱 하기(BeautifulSoup 그리고 한계)

12. 자동화 - 웹 자동화(with Selenium)

13. 자동화 - 윈도우즈 GUI 자동화(with pywinauto)

14. 자동화 - 작업 자동화

15. 수학 라이브러리 살펴보기

16. 그래픽 라이브러리 살펴보기

17. 머신러닝에서의 파이썬의 역활

18. 웹 프로그래밍 - Legacy Web

19. 웹 프로그래밍 - Flask 살펴보기(feat. d3.js)

20. 웹 프로그래밍 - Django 살펴보기

21. 정리 - 이런저런 이야기

 

 

[추가]

현재 모바일에서 코드(colorscript 이용)가 나온 후에 글 색깔이 흐려져서 읽기가 어렵네요. 티스토리 개편하고 현재 스킨이 너무 예전것이라서 그런가도 싶긴한데, 수정이 쉽진 않을것 같아서 일단 웹에서는 정상적으로 보이니 웹을 이용해 주세요. 조만간 수정해 보도록 하겠습니다.

감사합니다.

 

 

[The five orders of ignorance]

  저도 몇년전에 누군가에게 소개 받았던 글이긴 하지만, 'Communications of the ACM' 저널에 실렸던 'The five orders of ignorance(무지의 5계층)' 라는 소프트웨어 개발에 대한 글을 소개하려 한다(아래 URL 안의 링크를 클릭하면 pdf 로 된 전체 글을 다운받을 수 있다).

http://www.corvusintl.com/CACM002-5OI.htm

 

  약간의 개인적인 해석을 추가해 요약하자면 아래와 같다.

 

  인류가 가지고 있는 지식을 저장하는 매체는 DNA, 뇌, 하드웨어, 책을 거쳐 현재는 소프트웨어라는 형태가 생겨나게 되었다. 소프트웨어는 뇌처럼 공간이 제한되어 있거나, 쉽게 변하지 않고, 책처럼 수동적이지도 않으며(뭐 읽는 사람에게 감성과 상상력을 일으킨다는 측면에서는 그렇지 않을지도 모르지만...), 하드웨어나, DNA 처럼 느린 변화를 가지거나, 유연성이 부족하지도 않다.

 

  우리가 소프트웨어 제품을 만든다는 것은, 특정한 제품 자체를 만드는것이 아닌 우리가 알고 있는 지식들을 저장하는 매체를 구현하는 행위라는 것이다. 그래서 소프트웨어의 잘 알려진 지식들은 프레임워크나, IDE 를 통해서 자동으로 코드를 만들어 낼 수 있는 반면에, 다른 특정한 부분들은, 직접 해당 지식을 획득하여 구현해 내야 한다.

 

  예를 들어 현재의 파이썬 시리즈를 다 읽은 시점에서, 새로운 파이썬 프로그램을 만든다고 해보자. 그 중에 어떤 부분들은 우리가 이미 알고 있는 지식을 재사용하여 쉽게 구현할 수 있으며, 만약 새로운 프로그램의 주요한 부분들이 기존에 지식들과 유사하다면, 정말 쉽게 짧은 시간에 새로운 프로그램을 만들 수 있을 것이다. 그런데 만약에 우리가 현재 다루지 않았던 기능을 가진 프로그램을 만들려 한다면 해당 분야에 대한 지식을 추가로 획득해야 한다. 추가적인 지식이 필요한 경우인 경우라도 다른 유사한 프로그래밍 경험을 통해서, 해당 지식의 영역(예를 들어 쓰레드 구현)을 경험해 본적이 있다면 프로그래밍 언어의 차이점만을 잘 극복한다면 백지에서 시작하는 것보다 좀더 쉽게 원하는 지식을 획득할 수 있을 것이다. 아마도 최악의 경우는 어떻게 해당 부분을 해결해야 할지에 대한 방법이 전혀 감이 잡히지 않는 경우일 것이다.

 

  이런 관점에서 볼때 소프트웨어 개발은 지식을 획득하는 활동인 동시에, '무지(ignorance)' 를 줄이거나 제거하는 활동으로 바라볼 수 있다. 이 글에서는 이러한 측면에서 무지의 계층을 5단계로 나누어 제시한다.

  • 0 계층 - 무지의 결핍(Lack of Ignorance) : 무언가를 오랬동안 경험해 왔기 때문에 그것에 대해 잘 아는 상태이다. 오랫동안 보트를 타봤다면 어느 정도 항해에 대해서 잘 안다고 얘기할 수 있는 것처럼 말이다.
  • 1 계층 - 지식의 결핍(Lack of Knowledge) : 무언가를 모른다는 것을 정확히 알고 있는 상태이다. 예를 들어 러시아어를 못한다는 것을 안다면, 러시아어를 공부하기 위해 학원을 다니거나 책을 봐야한다는 것을 명확히 알수 있다.
  • 2 계층 - 의식의 결핍(Lack of Awareness) : 무엇을 모른다는 것 자체를 인지하지 못하고 있는 상태이다. 무엇을 모르는지 자체를 모르기 때문에, 무엇을 해야 하는지도 모르는 상태 이다.
  • 3 계층 - 프로세스의 결핍(Lack of Process) : 인지하지 못하는 무지를 알수 있게 해주는 수단 자체가 없는 상태를 얘기한다. 그것은 특정한 프로세스의 부재 일수도 있고, 특정한 환경의 부재일 수도 있다.
  • 4 계층 - 약간 유머 같긴 하지만, 무지의 5계층 자체를 모르는 단계라고 한다. 이 글을 읽게 되면 이 단계에서 벗어나는 셈이라고 한다.

 

  개인적으로는 이 글에 공감이 가서 소프트웨어를 바라보는 관점이 많이 바뀌게 되었고 설명은 하기 어렵지만 왠지 좀더 대상을 편하게 바라보게 되었다. 소프트웨어에 대한 여러 다양한 기술 업무들이 비슷하게 무지를 줄이고, 숨어있는 무지들을 찾아내는 활동이라는 생각이 들었으며, 결국 소프트웨어에 담겨있는 내용은 지식이기 때문에, 소프트웨어이외의 다른 분야들의 좋은 접근 방법이나 개념들도 무형적 지식이라는 측면에서 비슷하게 연결이 되는게 아닐까하는 생각도 들었다. 스타크래프트의 'Black Sheep Wall' 처럼 무지의 영역을 한순간에 밝혀주는 마법의 치트키는 없겠지만, 자신이 모르는 필요한 부분들을 계속 발견하고 해당 영역들을 채우는 것이, 소프트웨어에 관한 직업을 가져가면서 스트레스를 덜 받는 방법이 아닌가 싶다.

 

 

 

 

[마무리 하면서]

  이 글의 목적은 읽는 사람을 파이썬에 대해서 어느 수준까지 끌어가는 것은 아니다(개인적으로 그럴만한 능력도 안된다고 생각하고...). 오히려 프로그래밍이란 생각보다 모호한 일이며, 다른 사람들이 미리 만들어 놓은 수많은 지식과 접근법들을 조합하여 문제를 해결하는, 정답이 여러개이며 완벽한 정답은 없는 선택적 영역이라는 것을 얘기하고 싶었다. 또한 코드를 만드는 과정에서 무언가가 막혔을때, 검색 엔진을 이용해 문제를 찾아서 해결하는 과정에서 일어나는 '헤멤'이라는 활동을 실제로 보여주고 싶기도 했다(뭐 다른 사람은 다른 방식으로 헤멜 수는 있을 것 같다). 그리고 조금 욕심을 내자면 각 주제 영역에 대해서 스스로 공부할 수 있게, 조금 먼저 길을 걸어본 입장에서 해당 지식들에 대한 접근 방법을 안내를 하고 싶었다. 얼마나 해당 부분을 충족 시켰는지 자신은 없지만, 목표로 했던 이야기들은 다 한 것 같아서 여기서 일단 글을 멈추려고 한다. 다들 너무 부담 갖지는 말고 천천히, 하지만 꾸준히 공부 하시기를 바란다.

 

 

 

 

2017.8.20 by 자유로운설탕
cs

 

  

 

 

posted by 자유로운설탕
2017. 8. 6. 00:30 프로그래밍

  이번 시간에는 앞 시간의 플라스크(flask) 시간에 이어서, 파이썬을 대표하는 웹 프레임워크로 알려져 있는 장고(Django) 를 살펴보는 시간을 가지려고 한다. 플라스크를 살펴볼 때와 비슷한 방식으로 공식 메뉴얼을 기준으로 전체적인 장고의 구조에 대해서 살펴보고, 플라스크에서 구현했던 2개의 예제(MSSQL 테이블 표시, d3.js 그래프 그리기)를 장고 환경에서 똑같이 구현하는 과정을 보여 줌으로서, 플라스크와는 어떤 다른 측면들이 있는지를 설명하려고 한다. 

 

 

 

[목차]

0. 왜 파이썬 공부에 구글을 이용하는게 좋은가?

1. 언어를 바라보는 방법. 파이썬을 어떻게 바라봐야 할까?

2. 파이썬 설치와 환경, 버전 선택 하기의 이유.

3. 만들고자 하는 기능을 모르는 조각으로 나눠 조사해 보기

4. 데이터 베이스에서 내용 가져와 출력하기

5. 암호화 모듈을 이용해 암복호화 해보기

6. 퍼즐 조각들을 합쳐보기

7. 엑셀 파일 사용해 보기 -> 부록 : fuction 을 이용해서, 코드 정리해 보기

8. 정규표현식을 왜 사용해야 할까? 언어속의 미니 언어 정규표현식 살펴보기

9. 입력과 결과를 GUI 화면과 연결해 보기

10. Whois API 이용해 보기

11. 웹페이지 호출해 내용 파싱 하기(BeautifulSoup 그리고 한계)

12. 자동화 - 웹 자동화(with Selenium)

13. 자동화 - 윈도우즈 GUI 자동화(with pywinauto)

14. 자동화 - 작업 자동화

15. 수학 라이브러리 살펴보기

16. 그래픽 라이브러리 살펴보기

17. 머신러닝에서의 파이썬의 역활

18. 웹 프로그래밍 - Legacy Web

19. 웹 프로그래밍 - Flask 살펴보기(feat. d3.js)

20. 웹 프로그래밍 - Django 살펴보기

21. 정리 - 이런저런 이야기

 

 

 

 

[들어가면서]

  우선 시작하기 전에 혹시 19교시의 플라스크(flask)에 대해서 보지 않은 상태라면, 먼저 해당 내용을 보고 오기를 바란다. 장고를 살펴본 결과 플라스크와 대비되는 부분을 짚어가며 설명하면 효율적이라는 생각이 들었다. 플라스크에서 개념을 설명했던 url routing(url 라우팅), static files(정적 파일), rendering templates(템플리트 랜더링)의 개념은 거의 동일 하게 사용되기 때문이다. 또 템플릿 소스도 일부만 수정해 그대로 사용할 계획이다. 이번 시간에는 ORM, MVT(MVC), ADMIN 등 장고에 특화된 개념만 추가로 설명할 계획이다. 개인적으로 플라스크를 살펴볼때 보다 2~3배 정도의 시간이 장고에 소요됬었고, 플라스크를 이해한 상태라면 장고도 비교적 접근하기 수월하다는 생각이 들었다.

 

 

[한글 문서 여부]

  역시 플라스크와 비슷하게, 구글을 찾다보면 아래의 한글 페이지가 있긴한데, 제목까지만 다 번역되어있고, 실제 내용은 튜토리얼 까지만 번역되어 있다. 다만 튜토리얼에 전체 구조를 이해시키는 핵심적인 얘기들이 많으므로, 튜토리얼을 한글로 보며 대충 돌아가는 상황을 파악후, 영문 문서들을 그담에 보길 권장한다.

https://django-document-korean.readthedocs.io/ko/master/

 

  또 다른 튜토리얼 레벨의 괜찮은 한글 문서는 구글에서 'django sample' 로 찾으면 나오는 아래의 장고걸 사이트 문서이다. 개인적으로 봤을때 체계적으로 잘 정리되어 있어 처음에 개념을 잡는데 도움이 됬었다. 파이썬 버전도 3.5 버전 기준이라 문법에 위화감도 없다.

https://tutorial.djangogirls.org/ko/django_start_project/

 

  그리고 마지막으로 영문 예제 사이트는 아래 사이트가 괜찮았다. 여기도 마찬가지로 3.5.x 버전이고, 위의 3개 사이트의 내용을 보면 대충 장고 사이트가 어떻게 돌아가는지에 대해 전체적으로 감이 잡히기 시작할 것이다.

https://scotch.io/tutorials/build-your-first-python-and-django-application

 

 

[Django Documentaton 보기]

  구글에서 'django document' 라고 검색하면 아래의 문서가 나오는데, 현재 최신이 1.11 버전(이게 '일점 일일' 버전이 아니라 '일점 십일' 버전이다. 첨에 이것땜에 구글을 검색하때 1.7, 1.8 같은 미래 버전이 왜 있지 하고 좀 헷깔렸다--;)의 공식 메뉴얼 페이지를 볼수 있다.

https://docs.djangoproject.com/en/1.11/

 

  'First steps' 안에 있는 Overview, Installation, Tutorial 에는 장고를 이용해 사이트를 세팅하는 것부터, 라우팅(URLconfs. 라고 보통 말한다)을 설정하는 법(urls.py), 모델을 만들어 해당 구조를 데이터베이스 및 어드민 기능과 싱크(migration) 시키는 법, 뷰와 템플릿을 사용하여 표시하고, 폼을 전송하여 받아 처리하고, 정적(static) 파일들을 설정하고 접근하는 법에 대한 전체적인 흐름을 보여준다.

 

  'The model layer' 에서는 모델을 상세하게 다루어, 모델에 대해 어떻게 클래스로 정의하고, 정의된 모델들을 지원하는 메쏘드들을 이용해 쿼리를 요청해 값을 가져오고(QuerySet), 데이터베이스와 모델을 어떻게 싱크시키고(Migrations), 모델을 벗어난 커스텀 쿼리를 데이터베이스에 어떻게 날리며(Raw SQL), 데이터베이스별로 모델을 적용하는데 필요한 여러가지 참고사항과 주의사항들을 얘기한다.

 

  'The view Layer' 에서는 어떻게 라우팅을 구성 하며(URLconfs), 어떻게 요청(request)을 하고받아서, 모델에 대한 검색을 지원하는 QuerySet 를 이용하여 결과를 가져와서, 어떻게 응답을 하는지(Requsest and response objects), 파일 업로드를 구현하는 법(File upload), 내장된 뷰(generic view)를 이용하는 방법(Built-in display view) 등을 다룬다.

 

  'The template layer' 에서는 템플릿에서 여러가지 장고에서 지원하는 템플릿 지원 로직들을 사용하여 표현하는 방법을 얘기한다(for 라든지 url 이라든지 여러 문법들을 사용하는 방법을 얘기하는데, 앞의 플라스크와 비슷하지만 조금 더 확장된 기능이라고 보면 될듯 하다) 
 

  이후 'Forms' 에서 폼을 넘기고 처리하는 부분을, 'The Admin' 에서 모델과 연관되어 자동으로 업데이트 되는 관리 페이지를 커스터 마이즈해 사용하는 부분을, 'Security' 에서 잘 알려진 보안 이슈들에 대응하는 설계를 구현하는 방법을, 'Common Web application tools' 에서 웹사이트를 개발하면서 주로 만나게 되는 세션, 캐싱, 스태틱 파일, 사이트맵 등등에 대한 구현을 지원하는 기능들을 얘기한다. 그 외의 섹션에서는 유니코드라든지, 로케일이라든지, 개발, 테스팅 방법이라든지 하는 여러 내용들을 다루고 있다.

 

 

  위에서 얘기했던 내용들을 이해한대로 그려보면 아래의 그림과 같다. 브라우저가 폼이나 API를 통해 요청을 하면, 장고의 웹 모듈이(WGSI - 메뉴얼에 이 기능은 테스트에만 사용하고 운영시에는 아파치 등을 연동해 쓰라고 명시되어 있다) 요청을 받아, 라우팅(URLConfs) 기능을 통해 해당되는 뷰의 콜백 함수에 전달한다.

 

  모델은 ORM(Object-relational mapping)이라는 패턴 기법을 이용해 데이터베이스를 가상의 프로그래밍적 객체로 모델링하여 정의하고, migrate 명령어를 이용해, 실제 데이터베이스에 테이블을 만들거나, 수정하여, 장고와 데이터베이스 사이의 구조를 싱크(뭐 migration 이 한 방향의 의미긴 하지만 넓게 보면 싱크 개념인것 같다) 시킨다. 또한 데이터 베이스에 SQL 문을 날리듯, 모델에서 QuerySet 이라는 검색용 메써드를 제공해, ORM 객체로부터 데이터를 조회해오게 한다(select, where, order by, join 등을 실제 비슷하게 구현한다. 앞에 진행했던 시간을 생각해 보면, 'Panda' 가 가상의 메모리 객체(dataframe)를 만들어서 비슷한 행동을 했었다).

 

  양 쪽을 싱크하는 과정에서 어드민에서 필요한 몇몇 테이블도 데이터베이스 안에 들어가게 되고, Admin 쪽에는 모델에서 정의한 ORM 객체들을 살펴보고, 데이터를 넣거나, 수정하거나, 지우거나 하는 관리 행동을 할수 있는 기본적인 인터페이스가 자동으로 싱크되어 구현된다.

 

  이러한 모델에서 만들어진 ORM 개체들은 뷰에서 QuerySet 을 이용하여 호출되어, 사용자가 요청한 조건에 적합하도록 가공되며, 템플릿과 조합되어 동적인 UI를 생성해 제공되거나, Json 응답 등으로 템플릿과 상관없이 독립된 형태로 사용자에게 응답을 줄 수 있다. 템플릿은 기본적으로 HTML 베이스로 구성된 응답(response)을 위한 기본 문서이며 여러가지 장고에서 제공되는 템플릿용 지원 기능 들과 CSS, Javascript 같은 스태틱 파일들을 이용하여 적절한 UI 를 구성하여 사용자에게 처리결과를 보여 주게 된다. 

 

  위의 그림중에 대부분의 요소들은 앞의 레거시 웹이나, 플라스크(flask) 살펴보기 시간에 얘기했던 주제들이거나, 추후 메뉴얼을 보면서 자세히 항목들을 살펴봐야 될 주제들 같아서, 이 시간에는 ORM 과 그에 연관된 주제들(QuerySet, Migration), 그리고 장고의 뼈대를 구성하는 MVT 라는 패턴 구조에 대해서 설명한 후 나머지 플라스크와의 자잘한 차이는, 실제 샘플을 구현하면서 중간 중간 얘기하려 한다.

 

 

[ORM]

  ORM 은 앞에서도 얘기했지만, 데이터베이스의 테이블의 구조와 관계를 클래스와 프로퍼티(속성), 메쏘드를 이용해, 객체의 관계로서 모델링 하는 기법이다. 구글에서 'orm pros and cons' 나 'orm 장단점' 이라고 검색하면, 아래와 같은 많은 페이지가 나오면서 난상 토론을 보게 되는데,

 

https://stackoverflow.com/questions/494816/using-an-orm-or-plain-sql

https://stackoverflow.com/questions/35955020/hibernate-orm-framework-vs-jdbc-pros-and-cons

https://gs.saro.me/#!m=elec&jn=718

http://layered.tistory.com/entry/ORM%EC%9D%80-%EC%95%88%ED%8B%B0%ED%8C%A8%ED%84%B4%EC%9D%B4%EB%8B%A4-ORM-is-an-antipattern

https://okky.kr/article/286812


 

  ORM 사용에 대한 판단은 각각 하는게 맞겠지만, 몇 가지 생각해 봐야할 것 같은 부분들이 있다. 우선 1번째는 'ORM' 은 'ODBC' 나 Selenium 의 'webdriver' 처럼 원소스 멀티유즈를 표방한다. 그러다 보니, 같은 모델이 여러 데이터베이스와 100% 호환된다는 보장은 사실 힘들것 같다(메뉴얼의 'The model layer' 의 뒷 부분이 이런 차이들과, 모델로서 해결하기 힘든 경우 raw sql 을 사용하는 부분들을 가이드 하고 있다). 데이터베이스마다 미묘한 문법차이나, 설계 차이가 있을 수 있고, 데이터베이스 버전 별로 100% 호환이 되도록 충분히 지원하고 테스트 되었다고 보기 힘들 수 있다. 마치 셀레늄에서 webdriver 의 종류에 따라 서로 다른 브라우저 끼리의 동작이 미묘하게 달랐던 것 처럼 말이다. 그래서 가능한 ORM 을 쓸 경우, 해당 프레임워크가 가장 기본으로 지원하는 데이터베이스를 사용하는게 업데이트도 계속 지원되고, 호환이 안되어 곤란한 일이 안 생길 것 같다(예를 들어 Django 는 PostgreSQL, MySQL, Oracle, Sqlite3 를 공식적으로 메뉴얼에서 다룬다).

 

  2번째는 ORM 이 데이터베이스에서 구조와 성능을 위해서 지원하는 주요 기능 요소들을 충분히 모델상에서 포함하고 있는지에 대해서이다. 장고에서 기본적으로 하나의 모델 객체는 하나의 실제 테이블에 마이그레이션이되어 매칭 되는 구조인데, 실제 복잡한 사이트에서 효율적인 설계가 해당 방식으로 100% 이루어질 수 있는지를 체크해 봐야한다(현실에서는 수많은 테이블이 서로 join 등으로 엃혀있는 관계를 가진 경우가 많아서, ORM 모델로 구현시 가독성이나, 복잡도가 감당할만 한지 등을 따져봐야 할 것 같다)

 

  3번째는 레거시 데이터베이스를 사용하는 경우이다. 관계형 데이터베이스의 특성상 많은 테이블들이 서로 연관을 가지면서 파편화 되있을 가능성이 많은데, 해당 부분을 현재의 모델로 흡수하여 구현하게 되면, DB프로시저 등을 생산해서 추가적인 중계가 필요할 수도 있고, 그런 경우엔 장고의 ORM 에서 구현한 장점 중 하나인 마이그레이션 기능을 원활히 사용할수 없을 가능성이 높아진다. 

 

  4번째는 DBA 인력들과의 협업이다. 개발 쪽에서 독자적으로 데이터베이스를 관리하고 책임지는 구조라면 모르겠지만(뭐 요즘 유행인 DevOps 등의 조직에선 모르겠다), DB 관련 팀 쪽에서 데이터베이스의 성능, 테이블 스키마 관리 등을 책임 지고 있다면, 만들어진 모델을 검증하기 위해서, DBA 인력이 장고의 ORM 을 구현한 클래스들과 QuerySet 구성을 이해하고, 해당 부분이 실제 현재의 데이터베이스에 어떤 영향을 주는지 모델의 변경이 생길때마다 매번 검증을 해야하는 상황이 생기게 되는데, 이런 방식이 가능한 시나리오인지는 의문이 생긴다.

 

  마지막으로는, 장고 쪽에 마이그레이션 기능을 위해, 데이터베이스의 스키마를 자동으로 바꿀 수 있는 권한을 주는 부분이 보안적으로 적절한가에 대한 고려와, Admin 의 요구사항이 경험적으로 단순히 모델 객체에 데이터를 넣거나 편집하는 것으로 단순하게 이루어지진 않기 때문에 자동화된 어드민이 실제 얼마나 유용할까에 대한 의문이다.

 

  결론적으로 적절히 우호적인 환경에서는 장고의 ORM 을 사용해 데이터베이스를 마이그레이션 하여 관리하는 것도 좋지만, 해당 경우 발생할 여러가지 반대 급부를  생각해 봐야 하며, ORM 을 사용하는 것 자체가 개발자에게 데이터베이스를 덜 이해해도 된다는 면죄부가 되는 것은 아니라는 것을 얘기하고 싶다. 오히려 개인적인 생각에는 ORM 을 사용해 모델을 설계해서 테이블과 싱크 시키고 싶다면, 장고의 모델과 QuerySets, 마이그레이션 쿼리, 객체지향 설계를 잘 이해하고, 동시에 사용하는 데이터베이스의 여러 성능 요소를 결정하는 미묘한 특징들에 대해서 잘 이해해야지만 충분히 규모가 커져도 유지보수가 가능한 좋은 설계가 나오지 않을까 싶다. 뭐 그냥 개인적인 생각이라는 것을 꼬리로 단다.

 

 

[MVT]

  MVT(Model, View, Template) 패턴은, 기존에 많이 쓰이는 용어가 MVC 이니 두 개를 비교해 보면서 살펴보도록 하자, 구글에서 'mvc vs mvt' 로 검색하면 아래의 페이지가 나온다.

https://stackoverflow.com/questions/6621653/django-vs-model-view-controller

 

If you’re familiar with other MVC Web-development frameworks, such as Ruby on Rails, you may consider Django views to be the controllers and Django templates to be the views.

 

  해당 내용 중 위와 같은 내용이 있는데, MVC 의 model 은 장고에서 설명한 구조와 거의 같이 데이터베이스와 매핑되는(사실 어떻게 보면 웹 프로그램이라서 그렇지 원래 ORM 은 굳이 매핑되는 대상이 데이터베이스일 필요는 없는것 같다) 부분을 얘기하고, 컨트롤러(controller)는 장고나 플라스크의 url 요청을 받아 해당되는 함수에 연결해 주는 라우팅 부분을 얘기한다. 그리고 뷰(View)는 실제 모델로부터 데이터를 받아서 보여주는 역활을 한다.

 

  그래서 장고의 경우 위의 그림에서 그렸듯이, 뷰에 URLconfs 기능이 있기 때문에 MVT의 뷰는 컨트롤러 개념을 가지고 있다고 하고(사실 뷰에서 템플릿을 사용안하고 바로 응답값을 줄수도 있기때문에, 템플릿과 뷰 개념을 같이 가지고 있다고 보는것도 맞을듯 싶다), 실제 상으로 템플릿에서 모든 결과를 보여주기 때문에 MVT 의 템플릿은, MVC 의 뷰와 같다고 말하는 것이다. 사실 좀 말장난 같은 요소가 있으며, 기능이 어느 편에 붙었는지에 상관없이 전체적인 기능 요소들은 거의 동일 하므로, 두 개가 사실상 같은 개념이라고 봐도 무방할듯 싶다(언어로 따짐 사투리 관계인데 뭐가 표준어인지는 모르겠다^^;).

 

 

[사전 준비 - Django 설치]

  파이썬 3를 지원하므로 pip 명령어로 설치하면 된다.

c:\Python\code>pip install django
Collecting django
...
Successfully installed django-1.11.4

 

 

 

 

[DB 에서 데이터 불러와 HTML 테이블로 보여주기]

  그럼 본격적으로 장고를 이용해서 앞의 시간에 플라스크로 구현해 봤던, 4교시에서 만들었던 예제를 응용한, MSSQL 데이터베이스에서 데이터를 불러다 HTML 테이블 형태로 웹에 표시하는 부분을 구현해 보기로 하자. ORM 모델을 무시하고, 4교시와 비슷하게 pymssql 모듈을 사용하여 모델을 무시하고 호출하는 것도 가능하겠지만, 그러면 장고의 모델 부분을 살펴 볼수 있는 기회가 없어지기 때문에 ORM 을 최대한 이용하도록 구현을 해보려고 한다.

 

 

[장고의 모델에서 MSSQL 을 지원하게 해주는 모듈 찾기]

  우선 공식 메뉴얼에는 MSSQL 지원에 대해서 언급된 내용이 없으므로, 구글에서 'django mssql' 이라 검색해서, 아래의 페이지를 찾는다.

https://django-mssql.readthedocs.io/en/latest/

 

  스토어드 프로시저(stored procedure)도 호출할 수 있고, 메뉴얼이랑 사용법등이 그런데로 괜찮은듯 한데, 지원하는 버전을 보니, django 1.8 까지 지원한다. 적용하면 돌아갈것 같긴한데 이젠 지원이 끊긴듯 해서 찜찜하다. 그래서 다시 다른 페이지를 찾다보니 아래 레딧 페이지에 'django-pyodbc-azure' 를 쓰라는 조언이 있다.

https://www.reddit.com/r/Python/comments/4iq7zb/django_19_with_mssql_as_backend/

 

  그래서 다시 구글을 검색해, 해당 모듈의 깃허브 페이지로 가니, 장고 1.11 최신버전과 MSSQL 2016 까지 지원해 주는, 현재 활발히 유지되고 있는 모듈이 있다.

https://github.com/michiya/django-pyodbc-azure

 

 

  그럼 설치 가이드에 있는 것처럼, pyodbc 와 django-pyodbc-azure 를 각각 설치해 보자(ODBC 개념은 4교시때 간단히 설명했다)

c:\Python\code>pip install pyodbc
Collecting pyodbc
...
Successfully installed pyodbc-4.0.17


c:\Python\code>pip install django-pyodbc-azure
Collecting django-pyodbc-azure
...

Successfully installed django-pyodbc-azure-1.11.0.0

 

 

 

[이미 만들어진 스키마를 장고 모델로 가져오는 방법 찾기]

  이제 모듈이 설치되었는데, 모듈이 잘 동작하는지 보려면, 장고 모델을 만들어서 마이그레이션으로 MSSQL 데이터베이스와 싱크를 해야될 것 같다. 그런데 다시 supermarket 테이블에 대한 모델을 만들어, 마이그레이션을 시키고 다시 장고의 어드민이나 SQL Management Studio 를 통해 데이터를 넣고 하는게 귀찮기도 하고, 기존 레거시 데이터베이스를 사용하는 경우 모델을 어떻게 구성하나도 궁금하기도 해서 구글에 'django pre existing database' 라고 검색해 아래의 메뉴얼 페이지를 찾았다. 근데 다행히도 설정파일에 연결문자열만 맞춰 놓으면, manage.py 의 inspectdb 명령어를 사용하면, 데이터베이스의 테이블을 자동으로 읽어와서,  해당 되는 테이블 구조에 맞는 model.py 파일을 만들어 준단다. 그럼 조금 뒤에서 모델을 만들때 이 기능을 사용하기로 해본다.

https://docs.djangoproject.com/en/1.11/howto/legacy-databases/


 

[프로젝트 만들기]

  일단 앞의 플라스크와 마찬가지로 가상환경을 쓸 필요는 없을 듯해 virtul env 설정은 생략하고, 앞의 장고걸의 예제들을 따라해 보다보니 설정 문제인지 현재 환경에서(windows 10) 일부 명령어 실행에 에러가 나서, 첨에 소개한 아래의 영문 설명 페이지를 기준으로 virtual env 설정 하는 부분과 migration 하는 부분만 제외하고 적당히 따라해 보기로 했다.

https://scotch.io/tutorials/build-your-first-python-and-django-application

 


  먼저 프로젝트를 생성한다. 아래의 명령어를 치면, c:\python\code 밑에 djangoweb 이라는 프로젝트를 만든다. 뭐랄까 GUI 메뉴를 사용하는 방식은 아니지만, 이제 부터는 비주얼 스튜디오나 이클립스 등으로 프로젝트를 만드는 느낌으로 따라오면 된다.
c:\Python\code>django-admin startproject djangoweb

 

  이렇게 되면 메뉴얼에도 나오지만 아래와 같이 c:\python\code 폴더 아래 djangoweb 이란 폴더가 생성되면서, 아래와 같은 서브폴더와 파일 구조를 가지게 된다.

1
2
3
4
5
6
7
djangoweb/
    manage.py
    djangoweb/
        __init__.py
        settings.py
        urls.py
        wsgi.py
cs

 

  뭐 각각의 파일이 모두 나름의 역활은 있지만, 자세한건 메뉴얼을 보고, 현재 시간에 의미 있는 요소들만 얘기하면,

1) mamage.py - 어플리케이션(프로젝트 안에서 실제 돌아가는 프로그램 모듈 1개를 얘기한다)을 만들거나, 데이터베이스에 모델 정보를 마이그레이션 하거나 하는 등의 여러 프로젝트의 관리에 필요한 기능들을 모아논 장고 전용의 작은 파이썬 프로그램이라고 보면 된다.

2) settings.py - 프로젝트 전반에 필요한 설정들을 저장한 파일. 데이터베이스 연결 문자열, 사용하는 어플리케이션들의 정의, 로케일, 스태틱 파일 경로, wsgi(web server gateway interface - 사용자로부터의 웹 요청을 처리하는 모듈) 지정 등등이 들어가 있다고 보면 된다/

3) urls.py - 1차 라우팅 설정이 들어 있다. 1차라고 하는 이유는 나중에 어플리케이션을 만들면 또 그 안에 라우팅 설정이 또 있다. 들어온 요청을 각 어플리케이션에 분배하는 1차 라우팅 단계라고 보면 된다.

 

 

[프로젝트 안에 어플리케이션 만들기]

  그 담에는 어플리케이션을 만들기 위해서, 'cd' 명령어를 이용하여 새로 만들어진 'djangoweb' 폴더 안으로 이동하여 'supermarket' 테이블에서 조회해서 보여줄 어플리케이션을 생성하는 명령어를 실행 한다(뭐 복잡해 보인다고 생각할지 모르지만, 어차피 장고를 사용하려면 정해져 있는 방식이라서 그대로 따라해야 되는 부분이다).

c:\Python\code>cd djangoweb
c:\Python\code\djangoweb>python manage.py startapp supermarket

 

  이 후에는 내부의 폴더에 supermarket 폴더가 생기며 안에 담긴 파일들은 아래와 같다.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
djangoweb/
    manage.py
    djangoweb/
        __init__.py
        settings.py
        urls.py
        wsgi.py
    supermarket/
        __init__.py
        admin.py
        apps.py
        migrations/
            __init__.py
        models.py
        tests.py
        views.py
cs

 

  역시 이번 시간에 주로 의미가 있는 파일들만 언급하면,

1) models.py - ORM 모델들이 클래스로 정의되어 있는 파일이다. 현재는 빈 어플리케이션을 만들었기 때문에 안을 보면 내용이 비어있다.

2) views.py - 뷰가 정의되어 있는 파일이다.

-> 위의 생성된 리스트를 보면 좀 이상하다라고 생각할수 있는데, 'templates' 폴더나, 어플리케이션 용 'urls.py' 파일은 안보인다. 뒤에서 보겠지만, 해당 파일은 수동으로 생성해 주어야 된다.

 

 

[프로젝트 파일 설정에 어플리케이션 추가]

  가이드에 나와 있는데로, 프로젝트의 셋팅 파일에, 새로 만든 supermarket 어플리케이션을 인식할 수 있도록 추가해 주자. c:\Python\code\djangoweb\djangoweb\settings.py 파일에서 아래 부분을 추가해준다. 

1
2
3
4
5
6
7
8
9
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'supermarket',
]
cs

 

 

[프로젝트 라우팅에 어플리케이션 쪽 라우팅 추가]

  그 다음엔 '프로젝트의 urls.py' 를 수정해 요청이 들어왔을때, 요청의 처리를 'supermarket 어플리케이션' 쪽에 위임하도록 해보자. c:\Python\code\djangoweb\djangoweb\urls.py 파일에서 아래 import 문과, supermarket.url 항목 부분을 추가해 준다. url 쪽이 플라스크와 하나 다른건 8교시때 배웠던 정규표현식으로 파싱을 한다는 것이다. 플라스크 처럼 사용하려면 '^원하는문자열$'(시작과 끝 사이에 원하는 문자열만 있음) 패턴으로만 url 을 정의해 쓰면 될듯 하다. 아래에서는 ^(시작하자마자) 후에 바로 supermarket.urls 로 라우팅 되기 때문에 결국 supermarket 쪽으로 전체 라우팅 제어권을 넘기는 것과 마찬가지가 된다. 

1
2
3
4
from django.conf.urls import include
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^', include('supermarket.urls')),
cs

 

 

[어플리케이션 라우팅 파일 만들기]

  자 그럼 이제 프로젝트에서 supermarket 어플리케이션으로 던진 경로를 해석해주는 어플리케이션 라우팅 부분이 있어야 한다. c:\Python\code\djangoweb\supermarket\urls.py 을 메모장에서 만들어서 아래의 내용을 넣어 utf-8 인코딩으로 저장한다(혹시 한글 주석을 달려면 utf-8로 파일이 저장되어야 장고 실행시 에러가 안 난다). 안의 내용은 일단 아직 뭐가 뭔지 잘 모르기 때문에, 위의 튜토리얼의 예제 내용을 그대로 넣어보자. 웹 서버 루트 경로(^$-시작과 끝 사이에 아무 값도 없음)를 호출했을 경우, 앞서 잠시 얘기한 장고에서 제공하는 generic view 를 사용하여 보여주는 형태 이다(뭐 장고가 잘 돌아가는지만 보려 하는거니 상세한 문법은 넘어가기로 하자)

1
2
3
4
5
6
from django.conf.urls import url
from supermarket import views
 
urlpatterns = [
    url(r'^$', views.HomePageView.as_view()),
]
cs

 

 

[어플리케이션 뷰 파일에 샘플 뷰 추가하기]

  자 그럼 이제 어플리케이션에서 해당 되는 라우팅에 대한 샘플 뷰를 만들어 보자. c:\Python\code\djangoweb\supermarket\views.py 파일에서 아래의 내용을 추가한다. 대충 문법을 보면 generic view 중 TemplateView 라는 것을 이용해 index.html 이라는 템플릿 파일을 지정했다.

1
2
3
4
5
6
7
from django.shortcuts import render
from django.views.generic import TemplateView
 
# Create views
class HomePageView(TemplateView):
    def get(self, request, **kwargs):
        return render(request, 'index.html', context=None)
cs

 

 

 

[어플리케이션 뷰 파일에 해당 되는 템플릿 만들기]

  마지막으로 템플릿 파일을 만들면 된다. c:\Python\code\djangoweb\supermarket\ 폴더에 templates 폴더를 생성한다(c:\Python\code\djangoweb\supermarket\templates\). 이후 해당 폴더안에 c:\Python\code\djangoweb\supermarket\templates\index.html 을 만들어 저장한다. 역시 한글 주석을 위해서는 utf-8 인코딩으로 저장하자.

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>장고샘플</title>
    </head>
    <body>
        <h1>동작함</h1>
    </body>
</html>
cs

 

 

[샘플 페이지 실행]
  그럼 다시 c:\Python\code\djangoweb 경로에서 아래의 명령어를 실행해 보자(manage.py 를 실행 시켜야 해서 실행 경로가 맞아야 한다)

c:\Python\code\djangoweb>python manage.py runserver
Performing system checks...
You have 13 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
...

Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
[05/Aug/2017 17:30:55] "GET / HTTP/1.1" 200 222

 

  뭐 기본 기능에 대한 마이그레이션이 안됬으니 어쩌니 얘기가 나오긴 하지만, 해당 부분은 무시하고 일단 웹페이지를 띄워, http://127.0.0.1:8000/ 을 호출하면 아래와 같이 웹페이지가 동작함을 볼 수 있다.

 

 

  참고로 현재 까지의 파일 구조는 아래와 같다. 추가된 파일과 폴더들은 색으로 표시했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
djangoweb/
    manage.py
    djangoweb/
        __init__.py
        settings.py
        urls.py
        wsgi.py
    supermarket/
        __init__.py
        admin.py
        apps.py
        migrations/
            __init__.py
        models.py
        tests.py
        templates/
            index.html
        urls.py
        views.py
cs

 

 

[MSSQL 연결 문자열 추가하기]

  MSSQL 연결 문자열을 추가하기 위해(앞의 'django-pyodbc-azure' git 페이지의 문서를 참고했다), 프로젝트 내의 c:\Python\code\djangoweb\djangoweb\settings.py 을 열어 DATABASE 설정 부분을 디폴트인 sqlite3 에서 MSSQL 로 수정한다.  
<원본>

1
2
3
4
5
6
DATABASES = {
    'default': {
        'ENGINE''django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}
cs

 

<수정>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
DATABASES = {
    'default': {
        'ENGINE''sql_server.pyodbc',
        'NAME''mytest',
        'USER''pyuser',
        'PASSWORD''test1234',
        'HOST''localhost',
        'PORT''1433',
 
        'OPTIONS': {
            'driver''ODBC Driver 13 for SQL Server',
        },
    },
}
cs

 

 

[모델 자동으로 만들기]

  앞에서 봤던 레거시 테이블을 자동으로 만드는 inspectdb 명령어를 이용하여 model.py 파일을 만들어 본다.

https://docs.djangoproject.com/en/1.11/howto/legacy-databases/

 

 

  일단 제대로 연결 문자열이 설정됬나 보기 위해서 아래의 명령어를 실행해 본다. 잘은 모르겠지만, 대충 출력된 내용을 보니 해당 데이터베이스안에 기존에 만들어 놓은 'Play' 와 'supermarket' 테이블을 가져오고, 필드 이름들도 정상적으로 파싱해 가져오는거 같다.
c:\Python\code\djangoweb>python manage.py inspectdb
# This is an auto-generated Django model module.
....

class Play(models.Model):
...

    class Meta:
        managed = False
        db_table = 'play'


class Supermarket(models.Model):
    itemno = models.IntegerField(db_column='Itemno', blank=True, null=True)  # Field name made lowercase.
...
    price = models.IntegerField(db_column='Price', blank=True, null=True)  # Field name made lowercase.

    class Meta:
        managed = False
        db_table = 'supermarket'

 

 

 그럼 models.py 파일을 만들어 내기 위해서, 아래의 명령어를 실행한다. inspectdb 를 하여 출력된 내용을 리다이렉션('>') 을 이용해서 기존의 자동 생성된 models.py 파일을 덮어쓰기 한다.

c:\Python\code\djangoweb>python manage.py inspectdb > supermarket\models.py

 

 

  확인을 하고 싶으면 c:\Python\code\djangoweb\supermarket\models.py 파일을 열어보면, 테이블의 구조가 아래와 같이 model 파일로 자동으로 만들어 진다. 내용을 보면 우리가 클래스는 배운적이 없지만 걍 내용을 담는 박스라고 생각하자. 클래스 안에 각 디비의 컬럼들이 정의 되어 있는데, 앞의 소문자로 된 itemno, categoty 등이 나중에 QuerySet 등에서 명시되어 사용되게 된다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# This is an auto-generated Django model module.
# You'll have to do the following manually to clean this up:
#   * Rearrange models' order
#   * Make sure each model has one field with primary_key=True
#   * Make sure each ForeignKey has `on_delete` set to the desired behavior.
#   * Remove `managed = False` lines if you wish to allow Django to create, modify, and delete the table
# Feel free to rename the models, but don't rename db_table values or field names.
from __future__ import unicode_literals
 
from django.db import models
 
 
class Play(models.Model):
    original = models.CharField(max_length=30, blank=True, null=True)
    encrypted = models.CharField(max_length=200, blank=True, null=True)
    decrypted = models.CharField(max_length=30, blank=True, null=True)
 
    class Meta:
        managed = False
        db_table = 'play'
 
 
class Supermarket(models.Model):
    itemno = models.IntegerField(db_column='Itemno', blank=True, null=True)  # Field name made lowercase.
    category = models.CharField(db_column='Category', max_length=20, blank=True, null=True)  # Field name made lowercase.
    foodname = models.CharField(db_column='FoodName', max_length=30, blank=True, null=True)  # Field name made lowercase.
    company = models.CharField(db_column='Company', max_length=20, blank=True, null=True)  # Field name made lowercase.
    price = models.IntegerField(db_column='Price', blank=True, null=True)  # Field name made lowercase.
 
    class Meta:
        managed = False
        db_table = 'supermarket'
 
cs

 

 

[마이그레이션 - 일단 생략]

  굳이 여기서 쓸것도 아니고, 모델과 데이터베이스를 계속 싱크 시킬 것도 아니기 때문에 마이그레이션 명령을 돌리는건 생략 하지만, 저도 에러 땜에 이것저것 찾아보다 한번 돌려봤다. 마이그레이션 명령어를 실행하게 되면 아래와 같이 어드민 관련 테이블이나, 장고에서 지원되는 세션 같은 기능에서 사용하는 기본 테이블들이 데이터베이스안에 만들어지게 된다. 실제로 안돌려도 되니 참고만 하자(뭐 돌려도 상관은 없지만 말이다).

c:\Python\code\djangoweb>python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying admin.0001_initial... OK
....
  Applying auth.0008_alter_user_username_max_length... OK
  Applying sessions.0001_initial... OK

 

 

[inspectdb 를 통한 마이그레이션 성공 여부 확인 하기]
  실제 페이지를 모두 만들어서, 만들어진 모델의 Queryset 이 정상으로 돌아가는지 확인 하려면, 만드는 도중에 또 다른 실수를 할지 모르므로, 실제 사이트를 만들기 전에 아래의 장고걸 사이트에서 나온데로, 장고쉘 기능을 이용해서(파이썬 쉘과 비슷하다고 보면 된다), 모델이 잘 생성됬는지 확인해 본다.

https://tutorial.djangogirls.org/ko/django_orm/

 

 

일단 쉘을 실행 하자.

c:\Python\code\djangoweb>python manage.py shell

 

  잘 실행되서 '>>>' 프롬프트가 나오면 아래의 명령어를 넣어 슈퍼마켓 모델을 가져오고, 슈퍼마켓 모델에서 모든 내용들을 다 가져와 보자

>>> from supermarket.models import Supermarket
>>> Supermarket.objects.all()
  File "C:\python\lib\site-packages\sql_server\pyodbc\base.py", line 545, in execute
    return self.cursor.execute(sql, params)
django.db.utils.ProgrammingError: ('42S22', "[42S22] [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]\ufffd\ufffd \ufffd\u0338\ufffd 'id'\ufffd\ufffd(\ufffd\ufffd) \ufffd\u07f8\ufffd\ufffd\u01fe\ufffd\ufffd\ufffd\ufffd\u03f4\ufffd. (207) (SQLExecDirectW)")

 

 

  흠 그런데 이상한 SQL 에러가 난다. 근데 에러 파일의 위치를 보면('pyodbc\base.py') 장고쪽은 아니고, pyodbc 쪽 에러같아서 mssql 모듈쪽은 이상없는것 같아 일단 안심은 된다. 어떤 에러인지 찾기 위해서 구글에서 'pyodbc 42S22 error' 라고 검색해서 아래 페이지를 찾아서 보니, 컬럼이 없는 경우의 에러인 것 같다. 그러고 보니 위의 에러에서 'id' 라는 컬럼이름 같은 항목이 보인다. 

https://stackoverflow.com/questions/36202976/pandas-with-pyodbc-nan-error-42s22-error-attribute-qnan-not-found-31

 

 

  그런데 supermarket 테이블에는 'id' 라는 컬럼 자체를 만든적이 없으니 이상하다고 생각하던 중, 예전에 장고 메뉴얼을 읽다가, 마이그레이션 설명 쪽에서 모델에 primary 키(겹치지 않는 유일한 값을 가진 필드 속성)가 없으면, 자동으로 테이블에 'id' 라는 이름으로 자동으로 숫자가 증가하는 primary 키를 만든다는 내용을 스쳐가듯 본 듯한 기억이 났다. 해당 테이블에는 당연히 primary 키 설정이 없었었고, 그래서 장고가 마이그레이션한 테이블처럼 id 필드가 무조건 있으리라 생각하고, 데이터베이스에 select 쿼리를 보낼 때 'select id, ...' 를 하여 에러가 났나보다.

https://docs.djangoproject.com/en/1.11/topics/db/models/

https://docs.djangoproject.com/en/1.11/topics/db/models/#automatic-primary-key-fields

 

 

  그럼 2가지 해결책이 있을 듯 하다. 하나는 현재의 supermarket 테이블에, 자동으로 숫자가 증가되는(auto increment) id 필드를 추가하는 방법이 있고, 다른 하나는 기존 supermarket 테이블을 삭제하고 다시 만들면서, 특정한 필드를 primary 키로 지정하여 생성하는 것이다(개인적으로 두 방식 모두 해봤는데 둘 다 잘 해결되긴 했다). 여기선 간단하게 가기위해서, supermarket 테이블에 id 컬럼을 하나 추가하자. 구글에서 'mssql add column autoincrement' 라고 검색하여 아래 페이지를 찾았다.

https://stackoverflow.com/questions/4862385/sql-server-add-auto-increment-primary-key-to-existing-table

 

     ALTER TABLE dbo.YourTable
     ADD ID INT IDENTITY

 

  아래의 명령어를 4교시에 설명한 MSSQL Managment Studio 를 실행해 쿼리 입력 창에서 mytest db 를 대상으로 실행한다(SSMS 사용법이 기억이 잘 안나면 4교시를 참고한다)

1
2
3
4
5
6
7
8
9
10
use mytest
go
ALTER TABLE dbo.supermarket
   ADD id INT IDENTITY
 
ALTER TABLE dbo.supermarket
   ADD CONSTRAINT PK_supermarket
   PRIMARY KEY(id)
 
select * from dbo.supermarket(nolock)
cs


  명령 실행후 supermarket 테이블을 셀렉트 한 내용을 보면 아래와 같이 숫자가 증가하는 id 값이 추가로 생겼다.

 

 

  이후 다시 장고 쉘에서 같은 명령어를 입력해 보면 정상적으로 결과를 가져온다. 값이 아니라 오브젝트 자체를 가져오기 때문에 안의 내용은 표시되진 않지만, 설치한 mssql 용 장고 모듈이 잘 동작되는 것을 확인 했으니 이제 실제 코드를 만들면 될듯 하다.
>>> from supermarket.models import Supermarket
>>> Supermarket.objects.all()
<QuerySet [<Supermarket: Supermarket object>, <Supermarket: Supermarket object>, <Supermarket: Supermarket object>, <Supermarket: Supermarket object>]>

 

 

  ※ 참고로 기존 테이블에 id 를 추가하는 게 싫어서 테이블을 재생성 하고 싶으면, 아래와 같은 itemno 에 primary 키 속성이 있는 테이블을 만들고, 데이터를 다시 채워 넣음 된다(물론 그전에 'drop table' 명령어로 기존 supermarket 테이블은 지워야 한다)

1
2
3
4
5
6
7
CREATE TABLE [dbo].[supermarket](
    [Itemno] [intNOT NULL PRIMARY KEY,
    [Category] [char](20NULL,
    [FoodName] [char](30NULL,
    [Company] [char](20NULL,
    [Price] [intNULL
)
cs


 

[supermarket 용 url 만들기]

  아까 샘플 url 을 만든것 처럼,  c:\Python\code\djangoweb\supermarket\urls.py 파일에 아래와 같이 supermk url 을 추가 한다.

1
2
3
4
5
6
7
from django.conf.urls import url
from supermarket import views
 
urlpatterns = [
    url(r'^$', views.HomePageView.as_view()),
    url(r'^supermk$', views.supermk),
]
cs

 

 

[supermarket 용 view 만들기]

  마찬가지로 c:\Python\code\djangoweb\supermarket\views.py 파일의 기존 내용의 마지막에, 아래의 supermk 뷰를 추가한다(따라오시다 헷깔리시는 경우는 나중에 전체 소스를 맨뒤의 부록 섹션에 첨부할테니 그걸 참고하시기 바란다). 예전 플라스크에서 구현한 것과 비슷한 구조로 Supermarket 모델에서 모든 값을 가져오고(supers = Supermarket.objects.all()), 이후에 가져온 데이터를(supers)을 super.html 과 같이 랜더링 한다.   

1
2
3
4
5
from .models import Supermarket
 
def supermk(request):
    supers = Supermarket.objects.all()
    return render(request, 'super.html', {'supers': supers})
cs

 

 

[supermarket 용 template 만들기]

  마찬가지로 c:\Python\code\djangoweb\supermarket\templates\super.html 파일을 메모장으로 utf-8 인코딩으로 저장해 만들면서 아래의 코드를 넣는다. 역시 플라스크와 비슷하게

'{{ }}' 와 '{% %}' 를 사용하여 루프를 돌리면서 <td> 태그 안에 각 컬럼 값을 넣게 된다(템플릿 엔진이 같은가도 싶다).

1
2
3
4
5
6
7
8
9
10
11
<table border="1" cellpadding="5" cellspacing="5">
{% for super in supers %}
   <tr>
      <td>{{ super.itemno }}</td>
      <td>{{ super.category }}</td>
      <td>{{ super.foodname }}</td>
      <td>{{ super.company }}</td>
      <td>{{ super.price }}</td>   
   </tr>
{% endfor %}
</table>
cs

 

 

[supermarket 페이지 결과 보기]

  아까 runserver로 웹서버를 실행한 상태로 두었다면 소스의 변경사항이 자동으로 반영되었을 테고(플라스크도 이랬다), 종료했다면 아래의 명령어를 다시 쳐서 실행한다) 

c:\Python\code\djangoweb>python manage.py runserver

 

  이제 브라우저를 띄워 http://127.0.0.1:8000/supermk 를 실행 하면, 아래와 같이 플라스크로 구현한 것과 비슷한 화면을 볼수 있다.

 

  현재까지의 트리는 아래와 같다. 추가된 파일은 색으로 표시했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
djangoweb/
    manage.py
    djangoweb/
        __init__.py
        settings.py
        urls.py
        wsgi.py
    supermarket/
        __init__.py
        admin.py
        apps.py
        migrations/
            __init__.py
        models.py
        tests.py
        templates/
            index.html
            super.html
        urls.py
        views.py
cs

 

 

 

 

[D3.js 에서 Json 데이터 URL 를 호출해 웹으로 그래프 보여주기]
  이 예제를 만들기 위해 플라스크에 있는 예제를 전환하면서 또 한바탕 헤메긴 했었다. 헤멘 부분이 플라스크와 장고의 차이를 보여주기 때문에 일부는 말로 설명하고 일부는 해결 과정을 보여주려고 한다.

 

 

[d3.js 샘플 페이지 찾아보기]

  일단 플라스크 때 구현한 코드를 장고로 옮기려면 두 가지가 필요하다. 첫번째는 d3.js 를 사용하는 템플릿 코드의 일부를 장고에 맞추어 변경해야 할 것 같고, 또 d3.js 에서 호출했던 json 데이터를 반환하는 URL 을 장고로 구현해야 한다.

 

  구글에서 'django d3.js json' 을 찾아서 아래 페이지를 보니 2개의 힌트가 있다. 첫번째는 d3.js 에서 url 을 호출 하는 방식이다. urlconfs 파일에서 url 을 정의 할때 'name' 속성을 이용해 정의하고, d3.js 에서 'url' 문법을 이용해 경로를 호출 한다. 또 json 데이터를 만들어 내는 play_count_by_month 함수는 QuerySet 을 이용해 결과를 가져와서 JsonResponse 함수를 이용해 json 응답을 생성한다(JsonResponse(list(data), safe=False)). 'safe=False' 옵션이 있는 이유는, JsonResponse는 dictionary 형태의 데이터만 기본적으로 중계하고, 다른 데이터 형일 경우는 'safe=False' 옵션을 넣어야만 형변환 에러가 안난다. 

https://stackoverflow.com/questions/26453916/passing-data-from-django-to-d3

 

 

  좀더 자세히 json 을 반환하는 것을 살펴 보려고 추가적인 페이지도 찾아보았다.  JsonResponse 를 사용하는게 1.11 버전에서는 적절해 보인다.

https://simpleisbetterthancomplex.com/tutorial/2016/07/27/how-to-return-json-encoded-response.html
https://docs.djangoproject.com/en/dev/ref/request-response/#jsonresponse-objects

 

 

[urlconf 파일 수정]

  우선 c:\Python\code\djangoweb\supermarket\urls.py 파일에 아래와 같이 'data' 와 'd3sample' 경로를 추가한다. 'data' url에는 아까 샘플에서 봤듯이 name 속성이 추가됬다.

1
2
3
4
5
6
urlpatterns = [
    url(r'^$', views.HomePageView.as_view()),
    url(r'^supermk$', views.supermk),
    url(r'^data$', views.data, name='data'),
    url(r'^d3sample$', views.d3sample),
]
cs

 

 

[view 파일 수정]

  다음으로 c:\Python\code\djangoweb\supermarket\views.py 파일을 수정한다. data 함수 안의 내용은 거의 플라스크때 json 형태로 만든 샘플 데이터를 최종으로 JsonReponse 에 넘기는 변경만 했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import json
import numpy as np
from django.http import JsonResponse
def data(request):     
    x = np.array(['2017-07-10''2017-07-11''2017-07-12''2017-07-13''2017-07-14'])
    y = np.array([58.1353.9867.0089.7099.00])
 
    myData = json.dumps([{"date": x[i], "close": y[i]} for i in range(5)])
    return JsonResponse(myData, safe=False)
 
 
 
def d3sample(request):
    return render(request, 'd3sample.html', context=None)
cs

 

 

[templete 파일 생성]

  메모장을 열어서 예전 플라스크 때의 코드를 복사해 json URL 호출 하는 부분만 샘플에서 참고한 내용을 기준으로 변경 후에, 인코딩을 utf-8 로 하여, c:\Python\code\djangoweb\supermarket\templates\d3sample.html 파일로 저장한다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
<!DOCTYPE html>
<meta charset="utf-8">
<style> <!-- 그래프 요소들의 스타일 지정 -->
body { font: 12px Arial;}
path { 
    stroke: steelblue;
    stroke-width: 2;
    fill: none;
}
.axis path,
.axis line {
    fill: none;
    stroke: grey;
    stroke-width: 1;
    shape-rendering: crispEdges;
}
 
</style>
<body>
 
 
<!-- 라이브러리 로딩. 내부에서 돌리려면 다운받아서 static 폴더에서 읽어와야 할듯 -->    
<script src="http://d3js.org/d3.v3.min.js"></script>
 
<script>
// 그래프 좌표 공간 설정
var margin = {top: 30, right: 20, bottom: 30, left: 50},
    width = 600 - margin.left - margin.right,
    height = 270 - margin.top - margin.bottom;
 
// 그래프 범위
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
 
// 축 정의
var xAxis = d3.svg.axis().scale(x)
    .orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
    .orient("left").ticks(5);
 
// 그래프 선 정의
var valueline = d3.svg.line()
    .x(function(d) { return x(d.date); })
    .y(function(d) { return y(d.close); });
    
// 캔버스 객체 생성
var svg = d3.select("body")
    .append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
    .append("g")
        .attr("transform"
              "translate(" + margin.left + "," + margin.top + ")");
 
// 2017-07-01 식으로 데이터를 해석하게 지정함
var parseDate = d3.time.format("%Y-%m-%d").parse;
 
// 전달 받은 데이터를 이용해서 그래프를 그린다.
var callback = function (data) {
 
    data.forEach(function(d) {
        d.date = parseDate(d.date);
        d.close = +d.close;
    });
 
    // 실데이터에 맞춰 그래프 범위 지정
    x.domain(d3.extent(data, function(d) { return d.date; }));
    y.domain([0, d3.max(data, function(d) { return d.close; })]);
 
    // 선 그리기.
    svg.append("path")
        .attr("class""line")
        .attr("d", valueline(data));
 
    // x축 그리기?
    svg.append("g")
        .attr("class""x axis")
        .attr("transform""translate(0," + height + ")")
        .call(xAxis);
 
    // y 축 그리기?
    svg.append("g")
        .attr("class""y axis")
        .call(yAxis);
 
};
 
 
// django 에서 만든 http://127.0.0.1/data 를 호출하여 json 데이터를 가져와 callback 함수 호출
d3.json("{% url "data" %}",  callback);
 
</script>
</body>
cs

 

 

[에러를 만나다]

  그런데 브라우저를 열어 http://127.0.0.1:8000/d3sample 을 호출하여 보니 그래프가 표시되지 않는다. 피들러를 띄워 확인해 보니 d3sample url 이 호출되고, 이후 /data url 까지 잘 호출한다.

 

 

 

  그래서 json 형태로 넘어오는 데이터를 확인하기 위해 브라우저에서 http://127.0.0.1:8000/data url 을 호출해보니, 파일을 저장하라는 창이 뜬다. 뭔가 이상하긴 하다. 해당 json 파일을 c:\python\code 폴더에 저장해서 메모장으로 열어 보니, 아래와 같이 (") 문자앞에 역슬래시(\) 가 들어가 이스케이프 처리가 되어있다(아마 여기서는 안전하지 못한 코드의 경우 JsonResponse 메써드가 방어를 하는 차원인거 같긴하다). 

1
2
3
4
5
"[{\"close\": 58.13, \"date\": \"2017-07-10\"}, 
{\"close\": 53.98, \"date\": \"2017-07-11\"}, 
{\"close\": 67.0, \"date\": \"2017-07-12\"}, 
{\"close\": 89.7, \"date\": \"2017-07-13\"}, 
{\"close\": 99.0, \"date\": \"2017-07-14\"}]"
cs

 

 

  그래서 브라우저에서 F12키를 눌러서, 예전에 배운 IE 개발자 도구를 열은 후, 'F5'키를 눌러서 http://127.0.0.1:8000/data 페이지를 다시 로딩 한다.  개발자 도구창에서 '콘솔' 탭을 클릭한 후 동그란 !표 아이콘을 클릭해 보니, 아래와 같이 개체가 상이하여 루프 메서드를 실행할 수 없다는 자바스크립트 에러가 난다.

 

 

  에러 메시지 밑의 라인 링크('d3smaple (75,5)')를 클릭하면, 해당되는 소스 라인에 가게 되는데, 아무래도 json 데이터를 받아온 내용이 담겨있는 data 변수가 기존 플라스크 때와는 다르게 json 형식으로 잘 해석이 안 된것 같다(아마 위의 \" 이스케이프 처리 때문인 것 같긴하다)

 

 

  그래서 자바스크립트 안에 'document.write(data)' 구문을 넣어 data 변수를 뿌려보니 아래와 같이 파일로 저장했을때는 보였던 escape 문자(\) 는 d3.json 함수를 통과하면서 없어진듯 하고, 정상적으로 뿌려짐을 볼수 있다(해당 디버그 코드들은 최종 샘플에 주석처리해 놓았으니 참고만...). 문법적으로 json 형태는 맞는거 같아, 그렇다면 왠지 이스케이프 문자를 d3.json 이 처리는 해줬지만, json 데이터라고 생각을 안해 변환을 안해 준거 같다는 생각이 든다. 

1
2
// d3.json 으로 받아온 값을 담고 있는 data 변수의 값을 찍어봄
document.write(data)
cs

 

 

 

  그래서 data 변수의 타입을 확인해 보기로 했다. 구글에서 'javascript print typeof' 로 검색하니, 아래의 페이지에서 type 을 문자열로 반환하는 함수를 얻게 된다.

https://stackoverflow.com/questions/7390426/better-way-to-get-type-of-a-javascript-variable

1
2
3
4
5
6
7
8
// 데이터 타입을 string 형태로 반환하는 함수
function typeOf (obj) {
  return {}.toString.call(obj).split(' ')[1].slice(0-1).toLowerCase();
}
 
// 에러추적 2 : data 의 형태를 뿌려보니,string 이라고 나옴
var datatype = typeOf(data);
document.write(datatype);
cs

 

 

  데이터 타입을 확인하니 'string' 이라고 나온다.

 

 

   JsonResponse 에서 " 문자를 이스케이프 처리하는게 확실히 문제는 문제인거 같다. 처음에는 json object 를 dict 로 변환해 볼까를 이리저리 궁리했지만 샘플의 json object 는 2차원이고, dict 는 1차원이라서 뭔가 억지로 변환하여 문제를 풀면 d3sample.html 코드도 이것저것 바꿔줘야 할 것 같아서 망설이다가 data 변수의 타입이 string 이지만 실제 내용은 json 데이터 형태인건 맞으니, string 을 json 객체로 변환하면 결과대로 나오지 않을까 하는 생각이 들었다. 구글에 'javascript string to json' 이라고 찾아서, 아래의 페이지를 찾는다. 해당 코드를 이용하여 변환 후에, 데이터 타입을 화면에 뿌려본다.

http://jekalmin.tistory.com/entry/string%EC%9D%84-json%EC%9C%BC%EB%A1%9C-json%EC%9D%84-string%EC%9C%BC%EB%A1%9C-%EB%B3%80%ED%99%98

1
2
3
4
5
// string 을 json 형태로 변환하여 뿌려보니, list 라고 나옴
// 확인 후 변환 코드는 남겨두어, 넘어온 data 값을 변환하여 사용함.
data = JSON.parse(data);
var datatype = typeOf(data);
document.write(datatype);
cs

 

  위와 같이 변환한 후 데이터 형을 다시 확인해보니 array 라고 나온다. 지금 보니 이래서 구글에의 샘플 페이지의 view 함수의 JsonResponse 호출부분에서 데이터를  list 형태로 바꾼건가 싶기도 하다. 뭐 여튼 변환하는 코드만 주석 처리 하지 않고 남겨두어 사용해 본다.

 

 

   최종 코드는 아래와 같고, 디버깅 코드 등으로 변경한 내용은 다른 색으로 표시해 놓았다. 브라우저의 개발자 도구를 잘쓰면 이렇게 덕지덕지 덜 넣어도 될듯하긴 하다. 보시면 결국 넘어온 데이터를 'data = JSON.parse(data);' 를 이용해 json array 형태로 바꾸어줌 간단히 해결나는 상황이다(밑의 typeof 함수는 디버깅 용이다). 해당 내용으로 d3sample.html 내용을 수정해 준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
<!DOCTYPE html>
<meta charset="utf-8">
<style> <!-- 그래프 요소들의 스타일 지정 -->
body { font: 12px Arial;}
path { 
    stroke: steelblue;
    stroke-width: 2;
    fill: none;
}
.axis path,
.axis line {
    fill: none;
    stroke: grey;
    stroke-width: 1;
    shape-rendering: crispEdges;
}
 
</style>
<body>
 
 
<!-- 라이브러리 로딩. 내부에서 돌리려면 다운받아서 static 폴더에서 읽어와야 할듯 -->    
<script src="http://d3js.org/d3.v3.min.js"></script>
 
<script>
// 그래프 좌표 공간 설정
var margin = {top: 30, right: 20, bottom: 30, left: 50},
    width = 600 - margin.left - margin.right,
    height = 270 - margin.top - margin.bottom;
 
// 그래프 범위
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
 
// 축 정의
var xAxis = d3.svg.axis().scale(x)
    .orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
    .orient("left").ticks(5);
 
// 그래프 선 정의
var valueline = d3.svg.line()
    .x(function(d) { return x(d.date); })
    .y(function(d) { return y(d.close); });
    
// 캔버스 객체 생성
var svg = d3.select("body")
    .append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
    .append("g")
        .attr("transform"
              "translate(" + margin.left + "," + margin.top + ")");
 
// 2017-07-01 식으로 데이터를 해석하게 지정함
var parseDate = d3.time.format("%Y-%m-%d").parse;
 
// 전달 받은 데이터를 이용해서 그래프를 그린다.
var callback = function (data) {
 
    // 에러추적 1 : 데이터를 뿌려보니 \ 문자도 제거되고, 정상적인 json 문법인거 같음
    // document.write(data)
    
    // 에러추적 2 : data 의 형태를 뿌려보니,string 이라고 나옴
    // var datatype = typeOf(data);
    // document.write(datatype);
 
    // string 을 json 형태로 변환하여 뿌려보니, list 라고 나옴
    // 확인 후 변환 코드는 남겨 넘어온 data 값을 변환하여 사용함.
     data = JSON.parse(data);
    // var datatype = typeOf(data);
    // document.write(datatype);
 
    // 첨에 이런 에러가 남 (개체가 'forEach' 속성이나 메서드를 지원하지 않습니다.)
    data.forEach(function(d) {
        d.date = parseDate(d.date);
        d.close = +d.close;
    });
 
    // 실데이터에 맞춰 그래프 범위 지정
    x.domain(d3.extent(data, function(d) { return d.date; }));
    y.domain([0, d3.max(data, function(d) { return d.close; })]);
 
    // 선 그리기.
    svg.append("path")
        .attr("class""line")
        .attr("d", valueline(data));
 
    // x축 그리기?
    svg.append("g")
        .attr("class""x axis")
        .attr("transform""translate(0," + height + ")")
        .call(xAxis);
 
    // y 축 그리기?
    svg.append("g")
        .attr("class""y axis")
        .call(yAxis);
 
};
 
 
// flask 에서 만든 http://127.0.0.1/data 를 호출하여 json 데이터를 가져와 callback 함수 호출
d3.json("{% url "data" %}",  callback);
 
// 데이터 타입을 string 형태로 반환하는 함수
function typeOf (obj) {
  return {}.toString.call(obj).split(' ')[1].slice(0-1).toLowerCase();
}
 
</script>
</body>
cs

 

 

  이후 브라우저를 열어 http://127.0.0.1:8000/d3sample 를 호출하면 플라스크 때와 같이 그래프가 정상적으로 출력된다.

 

 

 참고로 앞의 코드에서 d3sample.html 이 아닌, 뷰 파일 쪽을 고치려면, 딕션너리 데이터가 리스트 안에 담긴 형태로 만들어서, JsonResponse 함수로 전달하면 됩니다.

 

 

[마무리 하면서]

  이렇게 해서 플라스크에서 구현했던 예제들을 장고에서 구현해 보면서, 장고란 프레임워크의 여러가지 면들에 대해 살펴보았다(덤으로 javascript 도 약간 배웠다). 생각보다는 장고는 DB에 마이그레이션도 해주고, 어드민도 자동 생성하는 듯, 마법사 모드에 가까운 부분도 있는것 같으며, 확실히 플라스크 보다는 체계적인 구조를 갖춘 듯 하다. 좀 뭐랄까 사용하는 규칙이 엄격하다고 할까... 장고를 사용하는게 괜찮아 보인다면 이제부터 메뉴얼을 찬찬히 훝어보거나, 관련 책을 하나 사서 보거나, 구글을 검색하면서 필요한 부분을 찾아 공부하면 될듯 싶다. 3섹션동안 웹 쪽을 다루긴 했지만 파이썬 기능에 초점을 두었기 때문에, 웹 프로그래밍 책들에서 많이 다루는 게시판이나, 로그인, 파일 업로드, CSS 등으로 화면 꾸미기와 같은 주제들은 다루지 않았기 때문에, 초보시라면 공부해야 될 부분들은 앞으로도 많을 것이다. 여기서는 legacy web, flask, django 세 가지가 각각 나름대로 비슷하면서도 다른 배경을 가지고 살아왔다는 것을 이해 한다면 성공일 것 같다. 이렇게 해서 legacy web 에서 시작하여 flask 와 django 를 살펴본 파이썬의 웹 프로그래밍에 대한 여정을 마치려 한다. 나름 처음 생각 했던것 보다는 잘 정리된 듯도 싶다 --;

 

 

 

[부록]

  전체 코드의 트리는 아래와 같고 소스들은 따라하시다 꼬일때를 대비해서, 참고하시라고 압축 파일로 첨부했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
djangoweb/
    manage.py
    djangoweb/
        __init__.py
        settings.py
        urls.py
        wsgi.py
    supermarket/
        __init__.py
        admin.py
        apps.py
        migrations/
            __init__.py
        models.py
        tests.py
        templates/
            index.html
            super.html
            d3sample.html
        urls.py
        views.py
cs

 

 

[첨부 zip 파일]

djangoweb.zip

 

 

 

 

 

2017.8.7 by 자유로운설탕
cs

 

  

 

 

 

 

 

posted by 자유로운설탕
2017. 7. 20. 15:44 프로그래밍

  이번 시간에는 파이썬 웹 프레임워크로 많이 쓰이는 경량화 웹 프레임워크라고 불리우는 플라스크(flask) 를 살펴보는 시간을 가지려고 한다. 메뉴얼을 기반으로 전체적인 플라스크의 구조에 대해서 살펴보고, 지난 legacy web 시간에 구현했었던 DB의 테이블을 조회해 HTML 테이블로 표현하는 예제, Javascript 계의 matplotlib 이라 할수 있는 D3.js 와 결합하여 json 데이터를 가져다가 그래프를 그려주는 예제, 마지막으로 matplotlib 을 이용해서 파이썬 코드 기반의 그래프를 생성하여 HTML 문서에 포함 시키는 총 예제 3가지를 소개하려고 한다. 

 

 

 

[목차]

0. 왜 파이썬 공부에 구글을 이용하는게 좋은가?

1. 언어를 바라보는 방법. 파이썬을 어떻게 바라봐야 할까?

2. 파이썬 설치와 환경, 버전 선택 하기의 이유.

3. 만들고자 하는 기능을 모르는 조각으로 나눠 조사해 보기

4. 데이터 베이스에서 내용 가져와 출력하기

5. 암호화 모듈을 이용해 암복호화 해보기

6. 퍼즐 조각들을 합쳐보기

7. 엑셀 파일 사용해 보기 -> 부록 : fuction 을 이용해서, 코드 정리해 보기

8. 정규표현식을 왜 사용해야 할까? 언어속의 미니 언어 정규표현식 살펴보기

9. 입력과 결과를 GUI 화면과 연결해 보기

10. Whois API 이용해 보기

11. 웹페이지 호출해 내용 파싱 하기(BeautifulSoup 그리고 한계)

12. 자동화 - 웹 자동화(with Selenium)

13. 자동화 - 윈도우즈 GUI 자동화(with pywinauto)

14. 자동화 - 작업 자동화

15. 수학 라이브러리 살펴보기

16. 그래픽 라이브러리 살펴보기

17. 머신러닝에서의 파이썬의 역활

18. 웹 프로그래밍 - Legacy Web

19. 웹 프로그래밍 - Flask 살펴보기(feat. d3.js)

20. 웹 프로그래밍 - Django 살펴보기

21. 정리 - 이런저런 이야기

 

 

 

 

[들어가면서]

  우선 시작하기 전에 얘기하고 싶은 것은, 프레임워크란 부분에 너무 많은 기대는 하지는 말라고 하고 싶다. 프레임워크란 해당 분야의 좋은 관행(best practice)들과, 지원용 라이브러리들을 모아 놓은 범용적인 틀 같은 것이여서 개발에 대한 방법론, 자주 쓰는 기능 라이브러리, 안전한 설계 이슈 등 많은 부분에 도움을 줄 수 있겠지만, 그 범용적인 부분이 현재 자기가 만들고자 하는 특정한 프로그램에 적합 하다는 보장은 못해주며, 자신이 만드려는 프로그램에 맞게 커스터마이즈 하기 위해서는 많은 노력이 추가로 들어가게 될 것 이다. 마치 집을 짓는데, 기본 재료와 반듯한 땅, 숙련된 기술공을 제공해 준다 해도, 해당 지원 부분이 내가 원하는 집을 짓는데 필요한 디테일의 전부는 절대 못 된다는 것이다. 해당 프레임워크가 지원하는 언어를 충분한 깊이로 이해하고, 해당 분야(웹, 디비, 시스템, 빅데이터 등등)에 대해 충분히 이해한 상태에서 사용해야, 사용하려는 프레임워크에 대한 이해도도 깊어지고, 적절하게 스스로나 해당 프레임워크가 의도한 대로 사용이 가능할 것이다. 추가로 비슷하거나 상이한 다른 프레임워크들에 대해서도 핵심을 잘 이해하고 있다면, 선택한 프레임워크의 장, 단점에 대해서 객관적으로 바라보고 선택에 대한 트레이드오프를 잘 따져볼 수 있을 것 같다

 

 

[한글 문서 여부]

  Flask 에 대한 메뉴얼 문서는 구글을 찾다보면 한글문서가 있긴 한데, 현재 0.12 버전이 나온상태에서, 파이썬 3 지원이 안됬던 0.11 개발기간 당시의 버전 문서의 번역본이고, 번역이 완전히 다 되진 않은 상태라서, 처음 접할때 전체적인 맥락에 대한 살펴보기 용으로만 쓰라고 권하고 싶다. 또 구글에서 검색하다보면 나오는 간단한 예제들을 구현해 놓은 여러 한글 블로그들도 많으니, 본격적으로 영문 문서를 보기 전에 미리 사전 지식을 쌓아놓으면 좀 더 읽기 수월해 질 듯 하다.

http://flask-docs-kr.readthedocs.io/ko/latest/index.html

 

 

[Flask vs Django]

  그럼 우선 파이썬에서 보통 많이 얘기되는 두 개의 웹 프레임워크를 한번 비교해 보자. 구글에서 'flask vs django' 라고 넣고 검색해 보면 아래와 같은 여러 비교한 페이지들이 나온다.

 

[플라스크와 장고 비교 글 - 이 사람은 플라스크를 더 편하다고 생각한다.] 

https://www.codementor.io/garethdwyer/flask-vs-django-why-flask-might-be-better-4xs7mdf8v

[플라스크, 장고, 피라미드 비교 - 밑에 것은 번역 글]

https://www.airpair.com/python/posts/django-flask-pyramid

http://kmc5500.tistory.com/162

 

  대충 요약해 보면, flask 는 웹 프레임워크가 필요한 최소한의 기능만 제공 하고, 나머지는 외부 모듈이나, 개발자에게 구현을 하도록 유도하는 편이라고 하고, Django 는 중, 대규모의 사이트를 목적으로 만들어져 일반적인 웹사이트 개발에 필요한 풀 패키지를 지원하는 편이라고 한다. 글 들에서도 나오지만 판단은 각자 스스로 하는게 맞을 것 같고, 이번 시간에는 flask 가 표방하는 최소한의 웹 프레임워크가 어떤 의미인지 살펴보도록 하겠다.

 

 

[Flask Documentaton 보기]

  구글에 'flask documentation' 이라고 검색하면 아래의 최신 0.12 버전의 프레임워크에 대한 설명이 있는 링크를 얻을 수 있다.

http://flask.pocoo.org/docs/0.12/

 

  예전 수학 라이브러리 볼때 처럼, 쭉 목차를 살펴 보면, 먼저 'User Guide' 에는 Installation(설치) 부분이 있고(개인적으로 여러 버전을 운영할 일은 없을 듯 해서 vitualenv는 고려하지 않았다),  Quick Start 섹션에 기본적으로 기본적인 flask 웹 기동이나, url 을 파싱해 해당되는 파이썬 함수에 전달하는 라우팅, css 나 img 같은 정적인 파일에 접근하는 방법, 템플릿을 꾸며서 원하는 웹 화면을 보여주는 방법, request 된 데이터를 받아 처리하는 방법 등이 설명되어 있다. 그리고 Tutorial 에는 세팅 방법과, DB 를 조회해 화면에 보여주는 예제가 있는 것 같고, Templates 에는 여러가지 템플릿 제어 방법에 대한 설명, 한참 아래의 Patterns for Flask 에는 여러가지 플라스크로 웹을 구성하는 권장 기법들이 있다. 그 밑의 'API Reference''Additional Notes' 는 위의 사용자 가이드가 익숙해 졌을 때 추가적인 정보를 보기 위해 살펴보는게 맞을 거 같다(개인적으로 flask 를 둘러보면서 예제를 구현해 보려고 했을때 진행했던 흐름이다).

 

 

[Routing]

  실용적으로 보이는 프레임워크긴 하지만, 실제 웹코드 구현에 들어가기 전에 legacy 하고 차이가 나는 부분에 대해서 설명 후에 들어가려 한다(혹시 웹 프로그래밍에 대해서 익숙하지 않은 상태라면 앞 교시인 Legacy Web 파트를 꼭 읽어 보시고 오길 권한다). 먼저 routing 와 static files 에 대해서 생각해보자. 현재 가상현실, 증강현실이 주목받는 세상이지만, 컴퓨터 자체가 어느 정도는 해당 개념들의 표본이 아닌가 싶다. 컴퓨터 내의 파일이라는 개념에 대해 우리는 동영상, 그림파일, 문서파일, 음악파일 등을 자연스럽게 사용해와서 실체화된 것이라고 생각하지만, 실제로는 메모리나 디스크 상에 구분된 숫자에 불과하다는 사실은 맞을 것이다. 그 정보를 운영체제가 해석을 해서, 폴더나 파일로 구분하여 인식하고, 응용 프로그램이 해당 데이터를 전달 받아 우리가 볼 수 있도록 화면에 출력하거나, 소리로 출력하여 노래를 듣거나, 문서를 보거나 하는 것일 것이다. 일례로 오피스가 설치되 있지 않은 컴퓨터라면 doc, xls 파일은 아무 의미가 없는 파일일 것이다. 예컨데 특정한 처리를 해주는 로직을 만나야지만 우리가 파일이라고 믿는 것들이 의미가 생긴다는 것이다.

 

  비슷하게 우리가 legacy web 에서 'test.asp', 'hey.php' 같은 파일들이, 웹 프로그램 확장자를 가진 파일이여서, IIS, Apache 같은 웹 서버에서 해당 파일이 실행이 된다고 믿어왔던 것도 어떤 측면에서는 관념적인 것에 불과할지도 모른다. 역으로 얘기하면 어떤 이름과 확장자를 가진 URL 의 호출이 웹서버에 주어질 때, 그것을 어떻게 해석하냐는 폴더와 파일이라는 물리적인 요소에 달려있는게 아니라, 웹 서버가 요청을 어떻게 해석을 하냐는 논리적인 요소에 달려있다고 볼수 있다 

 

  이 부분이 최근의 웹 서버 모듈에서 볼수 있는 'routing' 이라는 개념이다. 클라이언트가 url 주소에 hey.php 를 요청하든 hey 를 요청하든, 웹 서버 모듈만 지원을 한다면, 해당 파일이라는 형태에 국한되지 않고 해석을 해,  파일 경로를 찾은 후 처리하는 대신에 특정한 로직(함수)으로 직접 전달할 수 있다. 즉 파일과 디렉토리 기반으로 움직이던 웹 서버의 동작을, URL 경로 규칙에 기반한 함수와의 연결 로직으로 추상화(혹은 일반화) 시켰다고 봐도 될 듯 싶다(그래서 개인적으로 확장자를 가진 웹에만 익숙하다가, 어느날 확장자가 없는 처음 웹을 만났을 때 웹서버 내에서 url 에 해당하는 실제 파일의 경로를 찾을 수가 없어서 당황했었던 기억이 난다). 결국 이렇게 되면 기존 웹에서 의미가 있었던 디렉토리와 파일명, 확장자는 모두 의미가 없는 껍데기가 된다. 이러한 URL 과 내부 기능과의 직접적인 연결을 하는 방식을, 네트워크에서 패킷을 적절한 경로로 안내하는 라우터의 역활을 차용해서 routing 이라고 명명한 것 같다.

 

 

[Static Files]

  같은 맥락에서 보면 이제 프로그램 확장자 파일이 아닌 css, jpeg 같은 정적인 파일에 대해서도 기존 웹과 같이 url 기반의 디렉토리와 파일이름 경로로 접근하기는 좀 힘들어 지게 됬다. 왜냐면 이젠 웹서버 모듈은 기존 웹과 같이 웹루트 폴더 기준의 트리 구조로 된게 아니라, 기본으로 라우팅이 되는 논리적 레벨의 매핑이기 때문이다. 그러한 논리적 레벨의 매핑을 특정 디렉토리를 기반으로한 물리적(이것도 앞에서 얘기했듯이 넓게보면 가상이긴 하지만) 매핑으로 잠시 변환하는 기능이, static files 이라고 보면 될것 같다. (flask 에서는 dynamic web, static web 이라는 표현으로 설명한다)

 

 

[Rendering Templates]

  예제를 구현하기 필요한 마지막 개념은 Rendering Templates 섹션이다. 사실 template 란건 특정 UI 의 재사용을 위한 개념으로 많이 사용되기는 하는데, flask 에서는 꼭 재사용이 아니더라도 UI 를 표현하기 위해선 하나의 템플릿(UI 를 표현한다는 측면과 재사용 적인 측면의 의미를 동시에 가졌다고 볼 수 있을 듯 싶다)을 사용해야 된다고 생각하면 된다. dynamic web 인 flask 의 어플리케이션 모듈 쪽에서 표현에 사용할 데이터를 준비한 후, 해당 데이터를 지정한 템플릿에 연관해서 처리를 한다. 이러한 구조가 HTML 과 어플리케이션 로직을 분리하기 위해서 라고 볼 수도 있겠지만, 사실 웹 자체의 베이스가 동적이여서 페이지 개념이 없어졌기 때문에, 최종적으로 페이지를 동적 프로그램 코드내에서 생성하게 되면, 이번엔 반대로 프로그램 코드에 UI코드가 섞이게 되어 의미가 없게 되니, 어쩔수 없이 최종으로 UI 를 표시하는 부분을 템플릿이라는 개념으로 다시 떼어낸 것도 같다. 마치 Javascript 와 HTML 을 event 속성이 연결하였듯이, 템플릿 또한 순수한 html 코드는 아니고, 앞의 ASP 의 <% %> 코드와 비슷하게, 전달된 데이터들을 템플릿 사이에 적절히 끼워주는 방식으로 구현된다(해당 작업을 렌더링이라고 표현하는 듯 하다).

 

 

[사전 준비 - Flask 설치]

파이썬 3를 지원하므로 pip 명령어로 설치하면 된다.

c:\Python\flaskweb>pip install flask

Collecting flask
....
Installing collected packages: flask
Successfully installed flask-0.12.2

 

 

  c:\python 폴더에 flaskweb 폴더(이 폴더 이름은 다른 아무 이름이나 된다)를 만든다. 다시 flaskweb 폴더내에 templates 폴더(이 폴더 이름은 약속된 이름이기 때문에 꼭 이 이름으로 만들어야 된다)를 만든다.

 

 

 

 

[DB 에서 데이터 불러와 HTML 테이블로 보여주기]

  첫번째 예제로 4교시에서 만들었던 예제를 응용해, MSSQL Server 에서 데이터를 불러 HTML 테이블로 출력하는 예제를 보이려고 한다. 여기에서 기본적인 routing, template 를 다루는 코드가 나오니 예제로 개념을 익히면 된다. 18교시와 마찬가지로 4교시에 만든 supermarket 테이블과 python 코드를 그대로 재사용 하려고 한다(4교시를 안해보셔서 환경이 없는 분들은 MSSQL Server 설치와 테이블 생성을 하시고 오셔야 한다).

 

 

[App Code 구현]

  해당 방식 구현을 위해 구글에서 'flask db to html table' 이라고 검색해서, 아래 3개의 페이지를 얻었다.

[DB 데이터를 템플릿에 넘기는 방식을 볼수 있음 - 템플릿 출력 부분 코드가 명확히 표현되 있진 않다.] 

https://stackoverflow.com/questions/29525758/data-from-sqlite-to-an-html-table-in-a-flask-page

[템플릿 출력 하는 코드가 명확히 나옴]
https://stackoverflow.com/questions/42040379/creating-an-html-table-with-database-values-in-flask

[메인 페이지에 SQL 초기화와 조회 관련 코드를 어떻게 배치할까에 대한 힌트]
https://stackoverflow.com/questions/38540256/flask-python-mysql-how-to-pass-selected-data-though-a-for-loop-and-return-it

 

 

  앞의 사용자 메뉴얼의 튜토리얼을 읽어 전체적인 분위기를 파악 후, 아래 3개의 코드들을 참조하고, 4교시에 만들어 놓았던 파이썬 코드를 결합시킨 최종 코드는 아래와 같다. 간단히 설명하면, flask 는 웹서버와 웹어플리케이션 기능을 같이 실행하는데(운영 단계에선 아파치 등과 연계하는게 맞을 듯은 싶다), 127.0.0.1 의 포트 5000번으로 서비스가 된다. 'sqltable' 경로가 호출되어 'showsql()' 함수가 시작이 되면, 지정된 SQL 문을 실행하여, templates 폴더에 있는(flask 의 몇 안되는 미리 약속되 있는 폴더이다) 'myweb.html' 파일과 DB 에서 가져온 전체 결과값(fetchall)을 지닌 'rows' 변수를 (아마 list)를 이용해서 렌더링 하여(rendering) 결과를 표시한다. 아래 코드는 결과를 표현해주는 템플릿 코드가 아직 만들어지지 않았기 때문에 아직은 반쪽의 코드라고 볼 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from flask import Flask
from flask import render_template
import pymssql
 
# MSSQL 연결 하기 
conn = pymssql.connect(server='localhost', user='pyuser', password='test1234', database='mytest')
cursor = conn.cursor()
 
# flask web 실행
app = Flask(__name__)
 
# 'sqltable' 이라는 URL 인자를 'showsql' 이라는 함수로 연결한다. 
@app.route("/sqltable")
def showsql():
    # SQL 문을 실행하여 supermarket 테이블에서 데이터를 가져온다.
    cursor.execute('SELECT Itemno, Category, FoodName, Company, Price FROM supermarket(nolock);')
    # 가져온 모든 데이터를 mytable.html 파일과 함께 랜더링 하여 표현한다.
    return render_template('myweb.html', rows = cursor.fetchall())
 
# 이 웹서버는 127.0.0.1 주소를 가지면 포트 5000번에 동작하며, 에러를 자세히 표시한다. 
if __name__ == "__main__":
    app.run(host='127.0.0.1',port=5000,debug=True)
cs

 

  위의 코드를 메모장에 넣고, 파일형식을 '모든파일', 인코딩을 'utf-8' 로 선택하여, c:\python\flaskweb 폴더에 myweb.py 로 저장한다.

 

 

[Templates Code 구현]

  다음은 myweb.html(이름은 호출하는 .py 파일의 이름과 달라도 무방하다) 템플릿 파일이다. 뭔가 전 시간의 ASP 코드 흐름과 비슷하지 않나 싶다. '<%' 대신 '{%' 로 파이썬 코드임을 표시하고, 안의 문법이 'vbscript' 대신 '파이썬' 문법인 차이같다.  사용자 메뉴얼을 보면 해당 템플릿을 표현하는 방식은 외부 모듈인 'Jinja2' 라는 템플릿 모듈을 차용했다라고 나온다. 대충 로직을 살펴 보면 테이블 외형을 뿌려주고('<table>태그') SQL 조회 결과를 한 줄씩 루프를 돌면서(for row in rows), '<tr>' 태그를 뿌려주고, 그 안에서 다시 해당 줄의 컬럼들을 선택하며 돌면서(for data in row), '<td>' 태그안에 그 값('{{data}}')을 넣어준다. 해당 부분은 ASP 코드랑도 비슷하고, 예전의 7교시 엑셀 시간에 배웠던 엑셀 파일로의 출력과도 비슷한 루프 구조를 가진다.

1
2
3
4
5
6
7
8
9
<table border="1" cellpadding="5" cellspacing="5">
{% for row in rows %}
    <tr>
    {% for data in row %}
        <td>{{ data }}</td>
    {% endfor %}
    </tr>
{% endfor %}
</table>
cs

 

  해당 코드를 역시 메모장에 복사하여, c:\python\flaskweb\templates 폴더에 myweb.html 로 저장한다(html 에 한글을 넣으려면 utf-8 인코딩으로 저장해야 에러가 안난다.)

 

 

  그럼 모든 코드가 구현되었고 c:\python\flaskweb\ 으로 이동하여, 아래와 같이 myweb.py 파일을 실행 한다.

c:\Python\flaskweb>python myweb.py
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 288-594-455
 * Running on
http://127.0.0.1:5000/ (Press CTRL+C to quit) 

 

  이후 브라우저를 열어 http://127.0.0.1:5000/sqltable 이라고 치면, myweb.py 상에서 sqltable 에 해당하는 showsql() 함수를 실행하여(일부러 독립된 요소라는 것을 보여주기 위해 이름들을 다 다르게 했다), 조회한 데이터를 templates 폴더에 있는 myweb.html 와 함께 랜더링 하여 아래와 같이 html 테이블을 보여준다(지금은 자연스럽게 보이지만 여기까지 올때까지의 몇 번의 시행착오 과정은 생략했다). 이 예제를 통해 기존 파이썬 로직들이 flask 라는 웹 서버 겸, 어플리케이션 모듈 프레임워크를 통해서 웹형식으로 표현되게된 흐름(flow)을 캐치하셨음 한다.

 

 

 

 

[D3.js 에서 Json 데이터 URL 를 호출해 웹으로 그래프 보여주기]

  D3.js 는 앞에서도 얘기했지만, 자바스크립트 쪽의 matplotlib 같은 시각화 라이브러리이다. 인기가 많은 편인 듯 해서, 디자인 강화에 중점을 둔 c3.js 등의 d3.js 기반의 라이브러리들도 있는 듯 하다. 해당 라이브러리의 컨셉은 목적상 matplotlib 과 역시 비슷하다(사실 모든 시각화 라이브러리가 비슷 한듯 하다). csv 등의 파일이나, 데이터를 반환하는 api 형태의 url 부터 json 데이터를 가져와서, 데이터 형을 잘 맞춰서, 원하는 그래프를 그려주는 라이브러리 함수에 공급한다. 그럼 보통 svg 형식으로 그래프를 그려(브라우저에서 HTML 문서안에 백터 그림을 나타내는 표준으로, svg와 canvas 두 가지 표준이 있다) 브라우저에 표시해 준다.

 

 

[App Code 구현]

  해당 페이지를 만들어 보기 위해 구글에서 'flask d3', 'd3 simple example', 'd3 simple date' 를 조회해서 아래의 3개의 페이지를 참고했다.

[전체적인 개념]
https://github.com/dfm/flask-d3-hello-world

[실제 동작 하는 코드]
http://bl.ocks.org/d3noob/b3ff6ae1c120eea654b5

[xxxx-xx-xx 형식의 날짜 데이터를 D3 에서 파싱하기 위해서]
https://stackoverflow.com/questions/13654609/draw-d3-simple-line-chart-with-an-array

 

 

  해당 페이지들의 예제들은 데이터 생성 로직이 조금 복잡하여, 주제에 집중하기 위해 17교시 머신러닝 예제 만들때 처럼 아래의 간단한 numpy 데이터를 임의로 만들었다.

1
2
3
   # 데이터 지정
    x = np.array(['2017-07-10''2017-07-11''2017-07-12''2017-07-13''2017-07-14'])
    y = np.array([58.1353.9867.0089.7099.00])
cs

 

 

  위의 예제들을 조합해서 정리한 프로그램쪽 코드는 아래와 같다. routing 경로가 2개로 늘어났다는 점만 제외하면, 나머진 다 앞에서 다루어 봤던 코드들이다. 조금 낯설은 코드는 json.dumps 명령어를 이용해서, json 데이터를 만들어 내는 부분이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import json
import flask
import numpy as np
 
app = flask.Flask(__name__)
 
# d3sample 을 호출했을때의 템플릿 설정
@app.route("/d3sample")
def showsample():
    return flask.render_template("d3sample.html")
 
# D3에서 가져갈 data url을 호출하면 반환할 json 데이터 만들어 내기 
@app.route("/data")
def data():
     
    # 데이터 지정
    x = np.array(['2017-07-10''2017-07-11''2017-07-12''2017-07-13''2017-07-14'])
    y = np.array([58.1353.9867.0089.7099.00])
 
    # 리스트를 json 데이터로 변환
    return json.dumps([{"date": x[i], "close": y[i]}
        for i in range(5)])
 
# 앞과 비슷한데 조금 틀려만 보임
if __name__ == "__main__":
    port = 5000
    app.debug = True
    app.run(port=port)
cs

 

  위의 코드를 메모장에 넣고, 파일형식은 '모든파일', 인코딩은 'utf-8' 로 선택하여, c:\python\flaskweb 폴더에 myweb_d3.py 로 저장한다(json.dumps 코드의 결과가 궁금 하시면 'http://127.0.0.1/data' 를 브라우저에서 호출해 본다).

 

 

[Templates Code 구현]

  그 다음은 랜더링에 사용할 d3samlpe.html 템플릿 파일이다. 안의 코드는 html 코드 보다는 d3.js 라이브러리를 사용하기 위한 자바스크립트 코드로 가득 차있다. 코드의 흐름을 보면 맨 아래 'd3.json("/data", callback)' 함수에서 'http://127.0.0.1/data' 경로를 호출하여 'json 형태의 데이터'를 얻어와서, 'callback' 함수에 넘겨준다. 'callback' 함수에서는 넘어온 데이터를 'd3.js 라이브러리' 함수에 입력하여 'svg 그래프'를 그린다. 상세한 코드들은 혹시 해당 라이브러리를 이용할 일이 있음(어차피 그래프 종류가 많아 각각 쓰임을 이해해야 한다) 이해하면 되고 여기선 flask 를 설명하는 목적이니 대충 주석과 흐름만 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
<!DOCTYPE html>
<meta charset="utf-8">
<style> <!-- 그래프 요소들의 스타일 지정 -->
body { font: 12px Arial;}
path { 
    stroke: steelblue;
    stroke-width: 2;
    fill: none;
}
.axis path,
.axis line {
    fill: none;
    stroke: grey;
    stroke-width: 1;
    shape-rendering: crispEdges;
}
 
</style>
<body>
<!-- 라이브러리 로딩. 내부에서 돌리려면 다운받아서 static 폴더에서 읽어와야 할듯 -->    
<script src="http://d3js.org/d3.v3.min.js"></script>
 
<script>
 
// 그래프 좌표 공간 설정
var margin = {top: 30, right: 20, bottom: 30, left: 50},
    width = 600 - margin.left - margin.right,
    height = 270 - margin.top - margin.bottom;
 
// 그래프 범위
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
 
// 축 정의
var xAxis = d3.svg.axis().scale(x)
    .orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
    .orient("left").ticks(5);
 
// 그래프 선 정의
var valueline = d3.svg.line()
    .x(function(d) { return x(d.date); })
    .y(function(d) { return y(d.close); });
    
// 캔버스 객체 생성
var svg = d3.select("body")
    .append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
    .append("g")
        .attr("transform"
              "translate(" + margin.left + "," + margin.top + ")");
 
// 2017-07-01 식으로 데이터를 해석하게 지정함
var parseDate = d3.time.format("%Y-%m-%d").parse;
 
// 전달 받은 데이터를 이용해서 그래프를 그린다.
var callback = function (data) {
    data.forEach(function(d) {
        d.date = parseDate(d.date);
        d.close = +d.close;
    });
 
    // 실데이터에 맞춰 그래프 범위 지정
    x.domain(d3.extent(data, function(d) { return d.date; }));
    y.domain([0, d3.max(data, function(d) { return d.close; })]);
 
    // 선 그리기.
    svg.append("path")
        .attr("class""line")
        .attr("d", valueline(data));
 
    // x축 그리기?
    svg.append("g")
        .attr("class""x axis")
        .attr("transform""translate(0," + height + ")")
        .call(xAxis);
 
    // y 축 그리기?
    svg.append("g")
        .attr("class""y axis")
        .call(yAxis);
 
};
 
// flask 에서 만든 http://127.0.0.1/data 를 호출하여 json 데이터를 가져와 callback 함수 호출
d3.json("/data", callback);
 
</script>
</body>
cs

 

  해당 코드를 역시 메모장에 복사하여, c:\python\flaskweb\templates 폴더에 utf-8 인코딩으로 d3sample.html 로 저장한다(한글 주석이 있어 utf-8 인코딩이 아니면 rendering 과정에서 에러가 난다).

 

 

  그럼 모든 코드가 구현되었고 c:\python\flaskweb\ 으로 이동하여, 아래와 같이 myweb_d3.py 파일을 실행 한다.

c:\Python\flaskweb>python myweb_d3.py
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 288-594-455
 * Running on
http://127.0.0.1:5000/ (Press CTRL+C to quit)

 

  이후 브라우저를 열어 http://127.0.0.1:5000/d3sample 이라고 치면, myweb_d3.py 상에서 d3sample 에 해당하는 showsample() 함수를 실행하여, d3sample.html 템플릿을 호출하면, 해당 템플릿 내에서 http://127.0.0.1:5000/data URL 을 호출하여, json 데이터를 받아서 D3.js 라이브러리를 이용해서 브라우저 화면에 아래와 같이 그래프를 출력하게 해준다(뭐 HTML 페이지에서 자바스크립트 d3.js 라이브러리를 이용해서 그래프를 그리는 것도 rendering 이라고 표현해도 된다).

 

 

  앞에서 설명한 방식으로 실제 동작하는지 확인하기 위해 이전 시간에 배운 피들러를 띄워 페이지 호출을 관찰해 보면, 아래와 같이 /d3sample 과 /data 가 차례로 호출되는 것을 볼 수 있다. 앞의 /d3sample 은 브라우저 주소창에서, 뒤의 /data 는 템플릿 랜더링 과정에서 호출한 것이다.

 

 

 

 

[matplotlib 그래프를 웹 페이지에 보여주기]

  마지막 예제는 위와 비슷한 데이터를 matplotlib 으로 그리고, HTML 페이지안에 해당 그림을 <img> 태그 형태로 삽입하는 예제이다.

 

 

[App Code 구현]

  예제를 구현하기 위해 구글에서 몇가지 샘플을 실행해 봤는데, python 2.x 대의 예제라서 라이브러리가 안 맞아 안 돌아 가거나, 3.x 대 예제인데, 실제로 에러는 안 나는데 이미지는 안 나오는(엑박표시) 경우가 많았었다. 몇 개의 예제를 검토해 본 바로는, 보통 두 가지 방식으로 구현 되는데, 첫째는 그려진 이미지를 static 폴더에 img 파일로 실제 물리적으로 저장한 후, html 템플릿 페이지를 띄워, 해당 이미지를 html 템플릿 내에 static 형식으로 포함하는 방법이 있고, 둘째는 <img> 태그 경로에 이미지를 생성하는 flask url 을 지정하여, 이미지를 마임(MIME) 데이터로 전달받아 브라우저에서 표시해 주는 방식이 있다. 여튼 결과적으로 'python 3 flask matplotlib html' 라고 검색해서 아래 2번째 방식으로 구현하는 예제를 찾았다.

http://dataviztalk.blogspot.kr/2016/01/serving-matplotlib-plot-that-follows.html

 

 

  해당 코드를 기반으로 데이터 생성만 간략화한 버전이 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
from io import BytesIO
from flask import Flask, render_template, send_file, make_response
import flask
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
import numpy as np
import matplotlib.pyplot as plt
 
app = flask.Flask(__name__)
 
# mypic 을 호출하면 mypic.html 로 렌더링 한다.
@app.route('/mypic')
def mypic():
    return flask.render_template("mypic.html")
 
# matplotlib 그래프 파일을 생성하여 소켓 통신으로 보내준다. 
@app.route('/plot')
def plot():
 
   # 그림판 준비
    fig, axis = plt.subplots(1)
    
   # 데이터 준비
    y = [1,2,3,4,5]
    x = [0,2,1,3,4]
 
    # 그리기
    axis.plot(x,y)
    canvas = FigureCanvas(fig)
    
    # 그려진 img 파일 내용을 html 랜더링 쪽에 전송한다.
    img = BytesIO()
    fig.savefig(img)
    img.seek(0)
    return send_file(img, mimetype='image/png')
 
 
if __name__ == '__main__':
    port = 5000
    app.debug = True
    app.run(port=port)
cs

 

  위의 코드를 메모장에 넣고, 파일형식은 '모든파일', 인코딩은 'utf-8' 로 선택하여, c:\python\flaskweb 폴더에 myweb_mat.py 로 저장한다.

 

 

[Templates Code 구현]

  그 다음은 mypic.html 템플릿 파일이다. 내부 코드는 엄청 간단해서 이미지 태그를 만들면서, 이미지 소스(src) 위치를 '/plot' 으로 지정한다. 그럼 http://127.0.0.1:5000/plot 을 읽어오며 실행되게 되어 해당 데이터가 MIME 데이터로 html 쪽에 전달되어 결합된다(사실 이런 방식은 처음 보는 거라서 좀 신기하긴 하다).

1
2
3
4
5
6
7
8
9
10
<html>
  <head>
    <title>image</title>
  </head>
  <body>
    matplotlib 으로부터 만들어진 이미지
    <p>
    <img src="/plot" alt="Image Placeholder">
  </body>
</html>
cs

 

  해당 코드를 역시 메모장에 복사하여, c:\python\flaskweb\templates 폴더에 utf-8 인코딩으로 mypic.html 로 저장한다.

 

 

  그럼 모든 코드가 구현되었고 c:\python\flaskweb\ 으로 이동하여, 아래와 같이 myweb_mat.py 파일을 실행 한다.

c:\Python\flaskweb>python myweb_mat.py
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 288-594-455
 * Running on
http://127.0.0.1:5000/ (Press CTRL+C to quit)

 

 

  이후 브라우저를 열어 'http://127.0.0.1:5000/mypic' 이라고 치면, myweb_mat.py 상에서 'mypic' url 에 해당하는 'mypic()' 함수를 실행하여, 'mypic.html' 템플릿을 호출하면, 해당 템플릿 내에서 'http://127.0.0.1:5000/plot' 경로를 호출하여, MIME 데이터로 이미지 스트림을 받아서 그림을 표시해 준다. 

 

 

 

 

 

[마무리 하면서]

  이렇게 해서 flask 프레임워크에 대해 간단히 살펴보는 시간이 끝났다. 'routing', 'dyanamic vs static web(static files)', 'rendering templetes' 가 주요 키워드인것 같다. 이전 시간에 legacy web 을 길게 설명하고, 이 시간에 flask 와 legacy web 을 비교하면서 얘기했던 방식이, 쉬운 이해에 도움이 되면 좋겠다. 여러 파이썬 웹 프레임워크에 대한 비교 글들의 끝에서 언급되지만 프레임워크의 선택은 개인의 일하는 스타일의 취향과, 구현하려는 목표에 해당 프레임워크가 추구하는 방향이 얼마나 적합한가에 따라 달라질 것이다. 실제 구현하려다 보면, 전체 구현하려는 대상에 비교해 프레임워크가 지원해 주는 기능은 정말 꼭 필요한 최소한의 기능 밖에 없는 것도 같긴 하다(물론 essential 한 부분이긴 하지만...). 

 

   추가로 d3.js 를 연계한 예제에서 봤듯이 웹의 많은 기능이 자바스크립트 기반에서 움직이기 때문에, 파이썬 로직으로 모든걸 해결하려는 것보다는, 파이썬 웹 어플리케이션 모듈 쪽에서는 데이터를 가공하여 제공하고, 실제 웹 쪽 UI 구현은 자바스크립트 라이브러리를 이용해 구현하면 효율이 좋은 경우도 많을 것 같다(웹의 1/3 쯤은 자바스크립트의 세상이고, d3.js 를 이해하기 위해서는 javascript 와 그래픽 라이브러리에 익숙하면 유리하기 때문에, 웹 전체에 연관되는 분야를 공부해 균형을 맞춰 놓는 것도 중요한 듯 하다). 다행인 점 하나는 'd3.js' 예제에서 봤듯이, 파이썬에서 배웠던 'matplotlib' 같은 목적이 비슷한 라이브러리를 사용해본 경험이, 많은 부분에서 비슷하게 적용 된다. 지금 이 시간이 기반이 되어 다음 시간인 Django 프레임워크에 대해서도 적절하게 설명할 수 있게 되길 바라며, Flask 살펴보기 시간을 마친다.  

 

 

 

 

2017.7.21 by 자유로운설탕
cs

 

  

 

 

 

 

 

 

 

 

posted by 자유로운설탕
2017. 7. 2. 19:54 프로그래밍

  이번 시간에는 파이썬 웹 프레임워크인 Flask, Django 를 살펴보기 전에 웹을 구성하는 기초적인 부분들에 대해서 살펴보려고 한다. HTML, CSS, Javascript, Web Server(IIS), Web Language(ASP), Ajax 에 대해 개념을 간단히 설명하고, 간단한 예제를 만들어 시연해 보면서, 일반적인 웹 환경이 어떻게 구성되어 있는지를 살펴보며, 다음 시간에 얘기할 비교적 최신 개념인 MVC(model view controller)나 Url Rewriting(Routing) 설명을 위한 사전 지식을 쌓아 놓으려 한다. 다만 위 하나하나의 분야는 이렇게 블로그 한 챕터에 담기에는 각각 수 권의 책으로 따로 분리해야 할 만큼 넓은 분야라서, 가볍지만 필요한 개념은 이해할 수 있을 정도로 설명을 진행해 보려고 한다. 만약 기존에 ASP, PHP, JSP 등으로 웹 페이지를 만들어 봤던 분들은 이번 장은 대충 넘겨보거나, 생략하여도 될듯 하다.

 

 

 

[목차]

0. 왜 파이썬 공부에 구글을 이용하는게 좋은가?

1. 언어를 바라보는 방법. 파이썬을 어떻게 바라봐야 할까?

2. 파이썬 설치와 환경, 버전 선택 하기의 이유.

3. 만들고자 하는 기능을 모르는 조각으로 나눠 조사해 보기

4. 데이터 베이스에서 내용 가져와 출력하기

5. 암호화 모듈을 이용해 암복호화 해보기

6. 퍼즐 조각들을 합쳐보기

7. 엑셀 파일 사용해 보기 -> 부록 : fuction 을 이용해서, 코드 정리해 보기

8. 정규표현식을 왜 사용해야 할까? 언어속의 미니 언어 정규표현식 살펴보기

9. 입력과 결과를 GUI 화면과 연결해 보기

10. Whois API 이용해 보기

11. 웹페이지 호출해 내용 파싱 하기(BeautifulSoup 그리고 한계)

12. 자동화 - 웹 자동화(with Selenium)

13. 자동화 - 윈도우즈 GUI 자동화(with pywinauto)

14. 자동화 - 작업 자동화

15. 수학 라이브러리 살펴보기

16. 그래픽 라이브러리 살펴보기

17. 머신러닝에서의 파이썬의 역활

18. 웹 프로그래밍 - Legacy Web

19. 웹 프로그래밍 - Flask 살펴보기(feat. d3.js)

20. 웹 프로그래밍 - Django 살펴보기

21. 정리 - 이런저런 이야기

 

 

 

 

[들어가면서]

  왜 파이썬 공부 관련 글에서 하나의 챕터를 따로 빼내 HTML, Javascript, CSS 등의 다른 웹 언어와 지금은 유행이지난 스크립트형 웹 프로그래밍 언어인 ASP 를 언급하려 하냐면, 다음 시간을 위해 Flask 와 Django 를 살펴보다보니, 처음 웹을 접하는 사람들이 접근하기는 꽤 어려운 구조라는 생각이 들었기 때문이다. 아마 Django 같은 프레임 워크를 이해하기 위해서는 기존 Legacy Web 에 대한 지식은 기본으로 갖춘 상태에서, Url Rewriting, MVC 같은 비교적 최신의 개념들에 대한 이해가 추가적으로 선행되야 할 듯 싶다. 물론 파이썬도 어느정도 익숙해 졌다고 가정하고 말이다.

 

  다른 웹 프로그래밍 언어 등에서 기존 웹을 어느정도 경험해 본 사람들은 해당 쪽 방식과 접근 방식이 다른 부분 위주로 비교해 가면서 적응하면 되겠지만, 웹 프로그래밍 세상에 처음으로 들어온 사람들은 해당 프레임워크를 배울때 웹의 일반적인 지식들이 한꺼번에 같이 쏟아져 들어오기 때문에, 아마도 뭐가 프레임위크에 대한 얘긴지, 뭐가 일반적인 웹 기술에 대한 얘기인지 혼란에 빠질 듯 싶다. 또 해당 방식이 기존 방식에 대해 어떤 장단점을 가지고 있는 건지를 알지 못하고 맹목적으로 받아들일 수도 있다. 앞의 시간하고 비교하면 웹 자동화 프레임워크인 selenium 을 사용하고 싶은데 웹 동작을 담당하는 HTML 이나 자바스크립트를 이해 못한 상태에서 막연히 배우는 것보다도 더 힘들지 않을까 싶다. 

 

  또한 구조적인 프레임워크는 분명히 여러 장점을 가지고 있는건 맞지만, 초보자의 입장에서 봤을때는 ASP 와 같은 Legacy Web Language은 아무래도 URL 이 바로 웹 페이지 파일 자체와 일치되는 1:1 관계의 직관성을 제공하여 접근하기가 좀더 쉽지 않을까 싶다. 또한 ASP, PHP 같은 스크립트 언어들은 컴파일 과정이 없이 바로 결과를 볼수 있어서 .NET 이나 JAVA 같은 컴파일 형 언어보다는 에러를 쉽게 만나고 수정해 볼수 있다(개인적으로는 수많은 에러를 만나고 해결하는 과정이 프로그램을 배우는데는 아주 중요한 경험이라고 생각한다). 그래서 flask 나 Django 에 대한 컨셉 설명을 용이하게 해보기 위해 앞서 머신러닝 챕터 진행 전에 수학, 그래픽 라이브러리를 소개하여 분리할 수 있는 개념을 떼어낸 것과 비슷한 일을 시도하려고 한다.

 

 

[IIS, ASP 에 대해서]

  참고로 여기서 잠시 다루는 웹 스크립트 언어인 ASP 는 현재 글을 진행하는 환경인 윈도우즈10 홈 버전이면 프로그램 추가/제거를 이용해서, IIS(Internet Information Server-아파치 같은 윈도우즈쪽 MS 웹 서버임)를 설치해서 비교적 간단히 사용이 가능하다. ASP 는 PHP, JSP 와 거의 기능적으로 비슷하다고 봐도 될듯 하다(뭐 두 언어는 계속 발전해 와서 .NET 으로 전략적으로 마이그레이션 하면서 버려진 ASP 와는 갭이 크다고 말하시는 분들도 있겠지만, 개인적으로 생각하기에는, 서로들 좋아 보이는 점들을 한참 차용했기 때문에 일반적인 기능 범위는 비슷하고 문법 측면만 틀리다고 생각한다. 물론 어떻게든 비슷하게 구현을 할수 있다는 얘기지, 더 이상 새로운 문법 구조나 라이브러리가 지원 되지 않기 때문에 난이도가 같다는 것은 아니다).

 

  지금 생각하면 좀 낯설지만 예전엔 위의 3개 언어가 웹프로그래밍 언어의 패권을 다투기 위해 경쟁하던 시대도 있었었다. 지금은 뭐 더 많은 웹프로그래밍 언어들이 경쟁하는 춘추전국시대에 있는듯 하며, 사실 고수준 언어가 많은 부분을 모듈화 해서 관리해 주지만, 어떤 언어를 쓰냐보다는 어떻게 설계하여 쓰느냐가 더 중요한듯도 싶긴한다.

 

  윈도우즈 7의 경우는 그때의 MS 라이센스 정책의 방향 땜에 홈 버전에서는 IIS 설치가 안되고, 프로페셔널 버전에서만 지원되니, 혹 윈도우즈 7 홈 환경으로 강좌를 따라오고 있는 분이라면, 눈으로 코드 흐름만 살펴 보셔야 할듯 싶다. 개념을 설명하기 위해 관련 코드를 만든것이기 때문에 그러셔도 무방하다(의사코드 대신 ASP 를 사용했다고 봐도 좋을듯 싶다)

 

 

 

 

[웹은 어떻게 동작하는가?]

  웹 브라우징은 기본적으로 아래 그림과 같이 브라우저와 웹 서버가 중심이 되어 일어나는 행위이다. 웹서버는 우리가 많이 아는 IIS(ASP, .NET), 아파치(PHP), 톰캣(JSP) 부터 node.js(Javascript), 파이썬 자체 웹서버 등 다양하다. 브라우저 주소창에 웹페이지 주소를 입력하거나, 또는 결제창에서 결제 버튼을 누르거나, 특정 페이지에서 다음 버튼을 누르거나 할때, 브라우저가 웹 서버에 명시적으로 요청을 보낸다. 해당 요청은 패킷이라는 조그만 신호 단위에 담겨서, 네트워크 카드를 통해서, 인터넷 세상으로 나가게 된다. 

 

  인터넷 세상에서는 라우터와 스위치라는 장치를 통해서 해당 되는 주소(정확하게는 DNS 서버를 통해 얻어온 IP)가 가리키는 사이트로 이동되게 된다. 그럼 해당 서버는 그 요청을 받아서, 포트에 대기(listen)하고 있는 있는 웹 서버(예를 들면 아파치) 프로그램에게 전달하게 된다. 해당 웹 서버 프로그램은 해당 요청의 form 요소등에 대해서 프로그래밍 로직을 적용하여 DB의 내용을 조회하거나, 저장하거나 한후 최종 처리 결과를 HTML 형식으로 꾸며 사용자 브라우저에게 다시 보내준다. 사용자 브라우저는 해당 정보를 구조<tag>에 맞게 적절히 해석하여 사용자에게 그래픽 적인 웹 페이지 화면으로 보여준다. 브라우저에서 특정한 옵션을 설정하는 경우 브라우저와 네트워크 카드 사이에서 웹 프록시 형태의 프로그램이 패킷을 중계하는 일도 있는데, 그게 우리가 사용해본 fiddler 같은 HTTP 패킷을 보는 툴의 동작 원리이다.

 

 

 

 

[웹을 구성하는 언어들]

  웹에서 사용되는 언어들은 보통 어느 측면에서 사용되느냐에 따라 클라이언트 언어(브라우저)와 서버(웹 서버 프로그램) 언어로 나눠볼 수 있다. 클라이언트 언어는 HTML, CSS, Javascript, AJAX 같은 언어로 이루어져 있고, 서버 언어는 JAVA, .Net 같이 컴파일이 되어 동작하는 언어와 ASP, PHP, JSP, Python 같은 스크립트 형태(사실 이것도 실시간 컴파일이라고 봐야된다. 그리고 PHP 도 컴파일 해서 사용이 되는 것 같기도 하고, JSP 도 뒷단은 자바 class 파일을 호출하는 경우도 많은 듯 하니 사실은 구분이 조금 묘하긴 하다)로 이루어진 언어로 이루어져 있다(요즘은 Javascript로 동작하는 서버 환경인 Node.js 가 나오는 등 서버와 클라이언트 언어라는 절대적인 구분이 점점 모호해 져가는 듯은 하다. 파이썬도 웹과 시스템 양쪽에서 쓸수 있듯이 말이다). 또 DB쪽 언어인 SQL(Structed Query Lanauage) 언어도 있다. 브라우저는 클라이언트 언어들을 이용해 사용자의 액션에 반응하거나, 그래픽적인 화면 UI을 보여주고, 사용자의 입력들을 받아 form 이나 json 등에 담아서 서버 쪽으로 전달한다. 서버 언어는 전달된 클라이언트의 데이터들을 미리 작성된 프로그램 로직에 맞춰 처리하여, DB에 저장하거나 하며, 이후 클라이언트 언어 형태로 브라우저에게 적절히 응답을 주게된다.

 

 

  HTML(Hyper Text Markup Language) 은 우리가 매일 클릭하는 링크(hyperlink)와, 페이지 구조를 담고 있는 태그(markup)로 이루어진 언어이다. CSS(Cascade Sytle Sheet)는 초기 HTML 로부터 디자인 속성들을 따로 분리해낸 언어라고 볼 수 있다. Javascript는 초창기의 정적인 HTML위에 event 속성과의 협업을 통한 사용자와의 상호작용으로 생명을 불어 넣어주었다고 할수 있으며, HTML을 개념적으로 구조화한 DOM(Document Object Model) 객체를 이용하여 HTML 요소들을 조작한다. 우리가 웹에서 보는 모든 동적인 동작들이 Javascript 의 출현 덕분에 일어난다고 보면 되며, 파이썬과 비슷할 정도로 다재다능하고 복잡한 언어이며, Node.js 의 출현 덕분에 서버 쪽 언어로도 사용되게 됬다. Ajax(Asynchronous JavaScript and XML)는 자바스크립트로 만들어진, 멈춰진 HTML 페이지 뒤에서 리퀘스트를 날릴 수 있는 라이브러리 묶음이라고 생각하면 될것 같고, HTML 의 <form> 을 이용하지 않고도 json, xml, text 등의 데이터 형태를 이용하여 브라우저 뒤에서 비동기적으로 통신하는 것을 지원한다.

 

  서버쪽의 JAVA나 .NET 등의 컴파일 언어의 경우 사용전 빌드 과정이 꼭 필요하고, 초기 공통 바이너리 로딩 등에 부하가 걸린다고 하지만, 일반적으로 초기 로드 동작이 끝나면 스크립트 형식의 언어보다는, 메모리를 이용해 좀 더 자원을 효율적으로 공유한다고 한다. SQL(Structed Query Language) 은 MSSQL, Oracle, MySQL 등에 쿼리를 날리는 공통 표준으로 실제로는 SQL서버 종류별로 문법이 조금씩 차이는 있으며, 사용자의 요청에 따라 서버 쪽 프로그램에서 데이터를 조회하거나 저장하는 데 사용한다.

 

 

 

 

[HTML 살펴보기]

  HTML 은 아래의 그림 처럼, 하이퍼링크와 태그로 이루어진 언어이다. Markup 은 문서의 활자나 구조를 잡아주는 것을 얘기하는데, HTML 에서도 비슷하게 구조를 잡아주는 요소의 의미를 가지게 된다. HTML 은 밑의 로봇 그림처럼 헤더(header)와 바디(body)라는 것을 가지게 되는데(HTML5 에는 푸터-footer도 있긴 하던데, 어찌 봄 전체 구분 구조자체가 많이 바뀌었으니 여기서는 무시하자), 헤더에는 문서에 대한 여러가지 배경정보(제목, 작성자 등)들이 들어가고, 바디안에 우리가 실제 브라우저에서 보는 화면들이 들어간다고 보면 된다.

 

  그림을 보면 <html> 태그안에 <head> 와 <body> 태그 쌍이 있고, 제목(<title>)을 좀 큰 글자(<h1>)로 보여주고, 한칸을 띈후(<p>), '본문' 이라고 적힌 HTML 문서가 브라우저에서 열리면 해당 정의된 대로 화면에 표시되는 것을 볼 수 있다.

 

 

  브라우저가 HTML 을 해석하는 것은 사실 이미지뷰어 프로그램이나 메모장이 하는 일과 비슷하다. 이미지 뷰어 프로그램이 읽은 이미지에 대해 이미지 종류, 압축방식(jpeg, png 등), 좌표와 색정보에 따라서 화면에 뿌려주거나, 메모장이 텍스트 파일내에 있는 문자, 줄바꿈 기호, 탭(우리 눈에는 글자들이 탭으로 구분된 것으로 보이지만, 실제의 텍스트 파일 내부에는 아스키 코드 09 같은 특별한 기호로 사실 정의되어 있다)을 해석해 우리에게 보여주듯, 브라우저가 HTML 형태의 정보를 받으면 헤더, 바디에 있는 여러 태그 정보들을 분석해서, 화면에 우리가 볼 수 있도록 표시해 주는 것이다(이를 HTML 랜더링 이라고 말한다) 

 

 

 

 

  그럼 HTML의 모든 태그를 다 볼순 없으니 대표적인 몇개만 살펴보자

 

[1- TABLE 태그]

  11교시에서 잠시 다루었지만, 테이블은 아래와 같은 기본 구조를 가진다. 가장 바깥은 <table> 태그로 쌓여 있고, <th> 는 맨위에 있는 제목 필드라고 보면 되고(테이블에서는 옵션 태그라 없어도 무방), <tr> 은 엑셀의 row 같이 테이블의 한 행을 나타내고, <td> 는 하나의 입력 칸인 셀(cell)을 얘기한다. 그래서 테이블의 구조는 <table> 태그 안에 줄을 나타내는 <tr> 태그들이 쭉 있고, 각 <tr> 태그 안에 칸을 나타내는 <td> 태그들이 들어가 있는 단순한 구조이다. 근데 이 단순한 구조로 이것저것 다양한 형태의 테이블을 만들거나 페이지의 구조를 잡다보니 은근 분석하기 어려울 정도로 복잡해 질 때도 많다.

 

 

 

  가장 간단한 구조의 테이블 예제는 아래와 같다. <table> 태그가 맨 밖에 있고, 선(border) 굵기가 1 사이즈를 가진다. <th> 태그안에 제목인 '과자'와 '초콜릿'이  들어있고, 줄을 나타내는<tr> 태그가 두개 있는데, 하나에는 '파이, 카카오45%' 가, 나머지 하나에는 '머랭, 카카오100%'  가 각각 <td> 태그안에 나눠 담겨 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<table border=1>
  <tr> 
     <th>과자</th>    
     <th>초콜릿</th>  
  </tr>  
  <tr>    
     <td>파이</td>
     <td>카카오45%</td>
  </tr>  
  <tr>   
     <td>머랭</td>
     <td>카카오100%</td>  
  </tr>
</table>
cs

 

  c:\python\code 폴더에, 파일형식을 '모든 파일'로 선택하고 table.html (또는 table.htm) 이라고 저장한다. 이후 탐색기에서 해당 파일을 더블클릭해 실행하면 브라우저가 뜨면서 아래와 같이 지정한 테이블이 표시된다.

 

 

  테이블에는 아래와 같은 스타일을 나타내는 속성들이 있다(속성 중 주요한 일부만 표시함). HTML을 복잡하게 생각하지 말고, 우리가 많이 쓰는 워드나, 한글 등에서 작성하는 문서를 브라우저가 이해할 수 있게 태그로 표현한다고 생각하면 된다. 글자를 크게하거나, 오른쪽 정렬을 하거나, 표의 색을 정하거나, 셀의 여백 값을 정하거나 하는 부분들을 아래와 같은 태그 내 속성에 넣어 넣어서 해결한다고 생각해 보면, 속성이 이렇게 많은 이유를 이해할 수 있을 것이다.

 

  그럼 일부 속성을 사용해 보자. 속성 이름들은 <table> 같은 하나의 태그에서만 독점해 쓰이는게 아니고, 비슷하게 속성을 정의할 다른 태그들이 있다면 동일한 이름으로 사용된다. 아래에서는 <th> 태그 내에 배경색(bgcolor : 색은 'yellow' 와 같은 예약된 영어이름이나, '# + 16진수 숫자' 을 이용해 R, G, B 로 표현 가능하다)을 입히고, '파이'와 '머랭'가 들어간 셀의 사이즈를 200 pixel로 늘이고, 특히 '파이'가 들어간 셀은 가운데 정렬을 한다.  

1
2
3
4
5
6
7
8
9
10
11
12
13
<table border=1>
  <tr>
     <th bgcolor=#FF22CC>과자</th>    
     <th bgcolor=yellow>초콜릿</th>
  <tr>    
     <td width=200px align = center>파이</td>
     <td>카카오45%</td>
  </tr>  
  <tr>   
     <td width=200px>머랭</td>
     <td>카카오100%</td>  
  </tr>
</table>
cs

 

  위와 마찬가지로 c:\python\code 폴더에 table2.html 에 저장하여, 브라우저로 실행하면 아래와 같다. 

 

 

  이번엔 맨 마지막에 <tr> 행을 하나 추가하며, 내부의 두개의 셀을 합쳐보자. 해당 역활을 해주는 속성이 'colspan'(column span:컬럼 폭)이다. 이것을 2라고 해주면 위의 테이블을 기반으로 해서 2개의 셀을 세어서, 밑에 하나로 합쳐 표시해 준다(처음 테이블을 만들어 이것저것 해보면 colspan, rowspan{위아래합치기} 개념이 복잡한 테이블에서는 조금 헷깔리긴 했었다)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<table border=1>  
  <tr>
     <th>과자</th>    
     <th>초콜릿</th>  
  </tr>  
  <tr>    
     <td>파이</td>
     <td>카카오45%</td>
  </tr>  
  <tr>   
     <td>머랭</td>
     <td>카카오100%</td>  
  </tr>
  <tr>   
     <td colspan=2>비고:살찌는거조심</td>
  </tr>
</table>
cs

 

   c:\python\code 폴더에 table3.html 에 저장하여, 브라우저로 실행하면 아래와 같다. 

 

 

 

[2- FONT 태그]

  다음으로 <font> 태그는 아래와 같다. 해당 태그 사이에 들어가 있는 문장의 색, 크기, 폰트 등을 정의 한다(역시 워드의 글자 스타일을 생각해보면 된다).

1
2
3
4
5
6
<html>
  <body>
    <font size="5" color="blue">첫번째 폰트</font>
    <font face="궁서체" color="green">두번째 폰트</font> 
  </body>
</html>
cs

 

  c:\python\code 폴더에 font.html 에 저장하여, 브라우저로 실행하면 아래와 같다. 

 

 

 

[3- Form 태그]

  세 번째 태그인 폼(<form>) 은 사용자가 입력한 데이터를 서버로 전송하기 위한 요소이다. 우리가 검색 페이지에서 검색어를 넣고 '검색하기 버튼'을 누르거나, 여러 결제 옵션을 선택하고 '결제하기 버튼'을 눌렀을때, 우리가 입력하거나 라디오버튼 등으로 선택한 값들을 서버 쪽으로 묶어 전송하는 역활을 하는 태그가 <form> 이다. 서버 쪽으로 데이터를 날릴때는 HTML 전체 데이터가 아니라 이 <form> 안에 담긴 데이터만 날아간다(물론 이 설명 부분은 요즈음에 와서는 json 이나 xml 을 데이터 형식으로 주로 쓰는 AJAX 와 같은 비동기 방식이나, .net의 viewState 같은 새로운 전송 역활을 하는 형식들이 생겨서 예전같이 절대적이진 않는듯 하다).

 

 

  밑의 그림에 나타난것 처럼 폼은 <form> 이라는 태그로 감싸져 있고, 그 안에 여러가지 사용자의 입력을 받는 태그들이 들어가게 된다(이 부분도 윈도우 GUI 화면 요소들을 떠올리면 쉽게 이해갈 것이다. <form>은 윈도우즈의 다이얼로그 박스와 비견 될듯하다). 폼안에 들어가는 태그는 txt 형태의 필드, 입력값을 와일드 카드로 가려주는 password 형태의 필드, 라디오 버튼, 체크 박스, 셀렉트 박스 등의 다양한 요소 들이 있다. 사용자가 type="submit" 으로 속성이 지정된 버튼을 누르게 되면, 폼의 action 속성에 지정된 URL이 호출되며 폼내 정보들이 전달된다. 

 

  아래의 소스는 위의 그림의 소스를 옮겨놓은 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<form name=“basic" action="http://127.0.0.1/demo_form_action.asp" method="get">
이름: <input type="text" name="myname">
<br><br>
아이디: <input type="text" name="id">
<br>
패스워드: <input type="password" name="pwd">
<br><br>
<input type="radio" name="address" value="seoul">서울
<br>
<input type="radio" name="address" value="busan">부산
<br><br>
<input type="checkbox" name="hobby" value="gym">취미는 헬스
<br>
<input type="checkbox" name="hobby" value="book">취미는 독서
<br><br>
한달 용돈 : 
<select name="pocketmoney">
   <option value="100000">10만원</option>
   <option value="200000">20만원</option>
 </select>
<br><br>
<input type="submit" value="전송"> 
 
</form> 
cs

 

  c:\python\code 폴더에 form.html 로 저장하여 브라우저로 띄워보자. '전송'버튼을 누르면 지금은 없는 페이지를 호출 하기 때문에, 에러가 나긴 할 것이다.

 

  그럼 피들러를 이용해 샘플 페이지에서 전송 버튼을 눌렀을때 폼이 어떻게 날아가는지 실제로 봐보도록 해보자(피들러는 10교시 때 설치 및 기초 사용법을 설명했다). 밑의 피들러 그림을 보면 왼쪽 url 항목에 우리가 지정했던 demo_form_action.asp 파일이 있고(상대 경로로 지정했기 때문에 앞의 도메인과 폴더 부분은 127.0.0.1의 루트 폴더 그대로 이다), 파일이름 뒤에 물음표와 함께 form 안에 담겨있는 태그 요소들이 name 속성을 기준으로 'myname=Hello', 'id=freesugar' 식으로 값이 어사인되어 전송되는 것이 보인다(예전에 웹페이지 파싱 시간에 잠시 얘기했지만 폼 및 폼 내부의 태그들은 name 속성을 기준으로 구별된다).

 

 

  조금 더 보충해서 설명하면, 우리가 주소창에 주소를 입력하여 구글 웹사이트에서 특정 웹 폴더내에 있는, 특정 파일(test.html)을 요청하여 가져오는 것처럼, 페이지내에서 submit 버튼을 눌렀을 때는 폼 태그 내의 action 에 정의되어 있는 URL 경로를 호출하면서 form 안에 지정된 값을 모두 모아서 전송을 한다(해당 부분은 브라우저가 알아서 해준다)

 

 

 

[4-EVENT]

  event 속성의 설명은 여기서 진행하진 않고 Javascript 와 뗄수 없는 관계니 뒤쪽 Javascript 섹션에서 설명 하려고 한다.

 

 

 

[HTML 마무리]

  그럼 이런 다양한 HTML 태그들과 속성들은 어떻게 접근해야 될까? 추천 하는 방법은 '헤드 퍼스트 html' 같은 가벼운 책을 한권 읽어보거나(개인적으로 헤드퍼스트 시리즈가 있으면 워밍업 용으로 먼저 본다. 대신 안의 낱말 맞추기나 퀴즈는 시간도 걸리고 쪽지시험 같아서 잘 안 푸는 편이다. 다만 저자가 다 다르기 때문에 시리즈 별로 품질이 차이가 좀 있다). HTML 책에 돈을 들이기 아까운 분은, 아래의 w3school 사이트의 샘플을 보거나, 구글 검색을 통해 필요한 태그를 조금씩 봐도 된다. 어차피 웹 프로그래밍 공부를 하다보면 태그는 계속 찾아 볼수 밖에 없다.

   https://www.w3schools.com/html/default.asp

 

  하지만 아마도 초보자 분은 w3school 나 구글에서 뭘 봐야할지를 모를 것 같기 때문에, 책이나 웹상의 HTML 관련 블로그나 관련 무료 강의를 보기를 추천한다(대신 이 방식은 a b c 로 진행되는 경우가 많아서 따라가다가 지칠 수도 있다). 그리고 무엇보다 중요한 것은 어느정도 알 것 같은 느낌이 들면, 직접 원하는 UI의 웹 페이지를 만들어 보면서 벽에 부딫치고, 해결해 보는 것이 좋다.

 

  HTML 은 브라우저로 모든 소스를 볼수 있기 때문에, 디자인이 좋은 페이지의 궁금한 요소들을 뜯어 보는 것도 좋다(11교시에서 설명한 브라우저 개발자 도구의 '요소보기'는 웹의 보고싶은 부분을 뜯어보는데 아주 편리한 도구이다). 어느 정도 공부를 해서 잘 안다고 생각해도 막상 웹페이지를 만들어 보거나, 현실 웹의 소스를 보기 힘들도록 꼬아놓은 자바스크립트, CSS, HTML 을 보게되면 한숨이 나올때가 있을 것이다. 쉽게 안되는게 아쉽지만 이런 공부는 'no pain, no gain' 이기 때문에 어쩔수 없다.

 

  HTML 은 4.01 표준과 5.0 이 있는데, 5.0 은 정적인 4.01 환경에서 좀더 동적인 웹을 위한 확장 킷이라고 봐도 될것 같다(개인적으로 게임의 확장팩 같다고 생각한다). 그래서 일단 4.01 위주로 공부한 후 5.0 내용을 보는 것이 좀 더 효율적일 것 하다(초보자분이 HTML 공부한다고 HTML5 책을 덜컥 사버리면 아마 맨붕이 올지도 모른다). HTML 태그가 워낙 잡다한게 많기 때문에(MS워드의 잘 안쓰는 잡다한 기능들과 같다고 보면 된다) 먼저 공부를 추천하는 기초 html 요소들을 아래에 정리해 놨으니 참고 하시길 바란다. 개인적으로 아래 정도만 알고 조금 헤메보면  beautifulsoup 같은 웹 라이브러리를 사용해 일반적인 웹페이지의 HTML을 파싱할 정도는 될 거라고 생각한다 . 물론 다음에 언급할 자바스크립트와 CSS는 HTML과 실과 바늘의 관계라고 볼수 있어 크롤링 등을 위해 페이지에 대한 분석을 잘 하고 싶다면 세 가지 언어를 비슷한 레벨로 수준을 맞춰 놓는게 좋다(어찌봄 원래 하나일 걸 3개로 나눴다고 봐도 된다). 거기다 웹프로그래밍 언어까지 얹어 배우게 되면(아마 자연스럽게 DB도 배우게 될테고) 웹 기술에 대한 전체적인 기초 그림이 완성이 된다고 생각한다.

 

[HTML 추천 태그 및 개념]

  • 기본구조용: <head>, <body>, <br>, <table>, <font>, <a>, <b>, <h1>~<h6>, <hr>, <i>, <p>, <title>, <meta>, <ol>, <ul>, <li>,
  • 프레임 태그: <frame>, <frameset>, <iframe>
  • 이미지 관련: <image>, <map>, <area>
  • 미묘한 구조의 확장: <div>, <span>
  • 외부와의 연결: <object>, 폼: <form>, <input>, <textarea>, <select>, <option>
  • 주석: <!-- -->
  • URL, 절대경로, 상대경로
  • (form 에 관련된) get, post 인자 개념
  • (자바스크립트를 배우는 초입인) event 속성

 

 

 

 

[CSS]

  CSS 는 문법으로 세세히 들어가면 무척 복잡해지는거 같긴 하지만, 간단하게 컨셉만 얘기하면, HTML 에서 각 태그의 디자인 속성들을 독립시켜 읽고 쓰거나, 관리하기 편하게 만든 것이라고 생각한다. 이렇게 무언가를 분리시켜 관리가 편하게 만드는 것은 다음에 나올 MVC 나 객체지향 프로그램, 함수 같은 요소의 공통점인것 같다.  아무래도 복잡히 꼬인 실타래 코드 보다는, 정리되고 분리되어 명확한 코드가 파악도 잘 되고 유지보수도 쉬울 테니까 말이다. 

 

 

  예를 들어 아래와 같은 HTML 코드가 있다면, 단순한 기본 모양의 테이블 이겠지만,

1
2
3
4
5
6
7
8
9
10
<table> 
  <tr>     
     <th>과자</th>    
     <th>초콜릿</th>  
  </tr>  
  <tr>    
     <td>파이</td>
     <td rowspan=2>카카오45%</td>
  </tr>  
</table>
cs

 

  아래와 같은 <style> 태그 안에 담긴 CSS 형식으로 <table>, <td>, <th> 의 디자인 속성을 정의한 파일이 있다면,

1
2
3
4
5
6
7
8
9
10
11
<style>
table, td, th
 {
 border:1px solid green;
 }
 th
 {
 background-color:green;
 color:white;
}
</style>
cs

 

  위의 두 개의 서로 다른 코드를(HTML+CSS) 합쳐서, 아래와 같이 하나의 html 파일로 만들면 서로 독립된 HTML 과 CSS 가 같이 연합해 동작하게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<style>
table, td, th
 {
 border:1px solid green;
 }
 th
 {
 background-color:green;
 color:white;
}
</style>
 
<table> 
  <tr>     
     <th>과자</th>    
     <th>초콜릿</th>  
  </tr>  
  <tr>    
     <td>파이</td>
     <td rowspan=2>카카오45%</td>
  </tr>  
</table>
cs

 

  c:\python\code 폴더에 css.html 로 저장하여 브라우저로 열어보자. <style> 태그안에 CSS 형식으로 정의한 스타일들이, 공간적으로는 분리되어 있는 HTML 코드에 적용되어 아래와 같이 꾸며진다(물론 이 부분은 브라우저가 소스를 파싱하여 적용해주는 것이다).

 

 

  위와 비슷한 표현을 하는 HTML 코드 형식으로만 이루어진 아래의 코드와 비교해 보면, 디자인 속성의 분리라는 점이 얼마나 코드를 깔끔하게 정리해 주는지 볼수 있다. 만약 복잡한 HTML 페이지의 디자인을 수정 시 아래처럼 각각 태그마다 디자인이 정의된 코드를 수정하는 것보다는, 위의 CSS 스타일로 분리된 코드를 수정하는 편이 좀 더 쉽고, 편리할 것 같지 않은가 싶다. CSS 는 HTML 의 디자인 작업을 개념적으로 분리시키고, 중복 코드를 제거해 쉽고 명확하게 만들어 준 측면이 있는 것 같다(HTML5 에는 아래 대부분의 디자인 속성을 안 쓰고 CSS 스타일을 사용하게 하는듯 하다).

1
2
3
4
5
6
7
8
9
10
<table border="1"> 
  <tr>     
     <th border="1" bgcolor="green"><font color=white>과자</font></th>    
     <th border="1" bgcolor="green"><font color=white>초콜릿</font></th> 
  </tr>  
  <tr>    
     <td border="1">파이</td>
     <td rowspan=2 border="1">카카오45%</td>
  </tr>  
</table>
cs

 

 

[CSS 마무리]

  CSS 에는 많은 디자인 요소들이 있고, class 를 지정하여 특정한 디자인을 선택해 적용하거나, css selector 같은 주제도 있다.  자세한 부분은 관련 블로그나 책을 한권 훝어 보는 걸 권장한다.

 

 

 

 

 

[Javascript]

  자바스크립트를 설명하려면, HTML 파트에서 설명을 뒤로 미뤘던, HTML 과 자바스크립트를 연결 해주는 요소인 '이벤트(event)' 에 대해 설명해야 한다. 이벤트는 아래 그림과 같이 윈도우즈 프로그램을 움직이게 하는 이벤트 개념이, 브라우저 내의 DOM 객체를 대상으로 구현된 것으로 봐도 될듯 한다. 윈도우즈 운영체제에서 사용자의 키보드, 마우스의 움직임이 어떤 프로그램 창의 어떤 사용자 컨트롤에서 발생했는 지에 따라 이벤트를 발생시켜 처리를 한다면, 브라우저 내에서도 사용자들의 여러 키보드, 마우스 액션이 HTML 페이지내 DOM 의 어떤 요소에서 일어났는지에 따라서, 해당되는 이벤트를 일으켜 자바스크립트를 이용해 처리하게 만드는 구조이다. 아래에 종종 볼수 있는 HTML 이벤트들을 정리해봤다.

 

 

  해당 이벤트가 동적인 웹을 구성하는데 어떤 역활을 하는지 처음 보는 분들은 감이 안 잡힐 듯도 싶어서, 대표적인 적용 예들을 밑에 표시했다(구글이 onchange 인지, onkeyup 일지는 잘 모르겠다^^). 밑의 예에서 유추해 보면 웹에서 UI가 사용자 동작에 따라서 반응하는 부분은 대부분 이런 이벤트+자바스크립트의 도움으로 이루어진다고 보면 될 것이다.

 

 

[자바스크립트 예제 1]

  그럼 간단한 자바스크립트 예제를 2개만 보자. 아래의 코드를 간단히 설명하면 하단에 input box가 두개 있고, 박스 내를 클릭하면 'onfocus' 이벤트가 발생 하며, 위쪽 input box 의 이벤트는 배경을 노란색으로 바꾸어주는 setSytle1 자바스크립트 함수에, 아래쪽 input box 는 파란색으로 바꿔주는 setStyle2 함수에 연결되어 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<html>
 
<head>
  <script>
     function setStyle1(x)
     {
       document.getElementById(x).style.background="yellow";
     }
 
     function setStyle2(x)
     {
       document.getElementById(x).style.background="blue";
     }
  </script>
</head>
 
<body>
 
<p>Color Change</p>
First name: <input type="text" id="fname" onfocus="setStyle1(this.id)"><br>
Last name: <input type="text" id="lname" onfocus="setStyle2(this.id)">
 
</body>
</html>
cs

 

  위의 파일을 c:\python\code 폴더에, colorchange.html 이라고 저장하고, 더블 클릭해 브라우저로 열어본다. 로컬 파일에서 자바스크립트가 돌아가려 하기 때문에, '차단된 콘텐츠 허용' 경고가 브라우저 하단에 뜰텐데, 이 경우는 딱히 위험한건 아니니 클릭해서 허용을 해줘야 자바스크립트 코드가 동작한다.

 

  처음엔 둘다 하얀 입력 박스인데, 각각 클릭하여 포커스를 주고 나면 아래와 같이 색이 바뀌게 된다.

 

 

 

[자바스크립트 예제 2]

  2번째는 조금 더 복잡한 예제를 해보자. 쇼핑몰 들에서 종종 볼수 있는 메뉴에 마우스를 오버하면 해당 하위 메뉴가 뜨는 예제이다. 이번엔 과정을 보여주기 위해 처음부터 전체 코드를 제시 하지 않고 HTML(원래는 css 와 구분되는게 더 낫겠지만), Javascript, event 각각의 코드를 소개하고 이후 합쳐서 동작을 보려고 한다.

 

  먼저 디자인을 나타내는 HTML 코드이다. 애니매이션이 1초에 수십장의 그림을 사람에게 연속으로 보여줘서 실제 움직이는 것처럼 속이는 것처럼, 자바스크립트도 비슷하게 여러 트릭을 통해 사람을 눈을 피해 표현하는 경우들이 많다(개인적으로 별로 우아하게 느껴지는 코드는 아니다). 아래를 보면 맨 위의 id 가 'mainCate' 인 <td> 태그가 상위 메뉴인 '과자' 를 보여주는 셀이고, 그 안을 보면 id 가 'subCateMenu' 인 '파이와 머랭' 정보를 담고 있는 <div> 태그가 하나 들어가 있는데, 속성들을 잘보면 뒤 쪽에 숨김 속성(display:none;)이 있다. 그래서 첨에는 <div> 태그 안에 있는 '파이와 머랭' 은 안보이고, 보는 사람의 눈에는 상위 메뉴인 '과자'만 보이게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<table width="100" border="0" cellpadding="0" cellspacing="0">   
    <tr><td align="left" id="mainCate">
       <div id="subCateMenu" style="width: 260px; position: absolute; margin-left: 120px; border: 
3px solid rgb(100, 200, 100); padding: 10px; z-index: 10000; display: none; background: rgb(255, 255, 255);">
          <table width="100%" border="0" cellspacing="0" cellpadding="0">
              <tr>
                 <td>                          
                       <div style="width:115px; border-bottom:1px solid;"><a href=“test1">파이</a></div>
                       <div style="width:115px; border-bottom:1px solid;"><a href=“test2">머랭</a></div>     
                </td>
              </tr>
          </table>
         </div>
        과자
        </td>
    </tr>
</table>
cs

 

 

  다음으로 동적인 움직임을 구현해 주는 자바스크립트 코드를 보자. showMenu 와 showSubCateMenu가 함수가 있는데, showMenu 가 하위메뉴가 나타날때, 상위메뉴인 '과자'가 들어있는 셀의 색을 바꿔주는 역활을 하고(backgroundColor), showSubCateMenu 가 숨겨놓은

'파이와 머랭'이 들은 <div> 태그를 보여준다(disaplay=""). 나머지 2개의 hide 계열 함수들 view의 반대의 역활을 해서 원래 상태로 돌려주는 역활을 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script language="javascript">
<!--
  function showMenu(td){
    td.style.backgroundColor = "#444444";
    td.style.color="#ffffff";
  }
 
  function hideMenu(td){
    td.style.backgroundColor = "#ffffff";
    td.style.color="#555555";
  }
 
  function showSubCateMenu(i) {
    document.getElementById("subCateMenu").style.zIndex = 10000;
    document.getElementById("subCateMenu").style.display = "";
  }
 
  function hideSubCateMenu(i) {
    document.getElementById("subCateMenu").style.display = "none";
  }
 
//-->
</script>
cs

 

 

  여기까지 오더라도, 마지막에 빠진 고리가 있다. 지금으로서는 HTML 하고 자바스크립트가 서로의 존재를 모른다는 것이다. 이것은 앞에서 얘기했던 'event' 요소가 연결해준다. '과자'가 들어있는 <td> 태그 안에 넣을 이벤트들은 아래와 같다. 이렇게 되면 마우스를 '과자' 셀위에 올리면(onmouseover) show 계열 함수들을 실행해 메뉴를 보여주고 색을 바꾸며, '과자' 셀을 벗어나면(onmouseout) hide 메뉴를 사용해서 원복한다.

1
2
onmouseover="javascript:showSubCateMenu(); showMenu(this);" 
onmouseout="javascript:hideSubCateMenu(); hideMenu(this);"
cs

 

 

  그럼 위의 HTML, Javascript, event 세 가지 코드를 결합한 최종 코드는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<script language="javascript">
<!--
  function showMenu(td){
    td.style.backgroundColor = "#444444";
    td.style.color="#ffffff";
  }
 
  function hideMenu(td){
    td.style.backgroundColor = "#ffffff";
    td.style.color="#555555";
  }
 
  function showSubCateMenu(i) {
    document.getElementById("subCateMenu").style.zIndex = 10000;
    document.getElementById("subCateMenu").style.display = "";
  }
 
  function hideSubCateMenu(i) {
    document.getElementById("subCateMenu").style.display = "none";
  }
 
//-->
</script>
 
 
<table width="100" border="0" cellpadding="0" cellspacing="0">   
    <tr><td align="left" id="mainCate" onmouseover="javascript:showSubCateMenu(); showMenu(this);" 
onmouseout="javascript:hideSubCateMenu(); hideMenu(this);">
       <div id="subCateMenu" style="width: 260px; position: absolute; margin-left: 120px; border: 
3px solid rgb(100, 200, 100); padding: 10px; z-index: 10000; display: none; background: rgb(255, 255, 255);">
          <table width="100%" border="0" cellspacing="0" cellpadding="0">
              <tr>
                 <td>                          
                       <div style="width:115px; border-bottom:1px solid;"><a href=“test1">파이</a></div>
                       <div style="width:115px; border-bottom:1px solid;"><a href=“test2">머랭</a></div>     
                </td>
              </tr>
          </table>
         </div>
        과자
        </td>
    </tr>
</table>
 
cs

 

  c:\python\code 에 menu.html 로 저장한 후, 브라우저로 열어본다. 아래와 같이 '과자' 가 들어간 셀 부분에 마우스를 올리거나 내렸을때 서브 메뉴인 '파이와 머랭'이 모양이 나왔다 사라지는 메뉴 동작을 볼수 있을 것이다.

 

  그리고 위에서 자바스크립트 코드에 나온 document.getElementById("subCateMenu") 같은 부분들은 자바스크립트가 HTML 코드에 접근할때 쓰는 DOM(Document Object Model)의 개념이 들어가 있다. 우리가 앞에서 beautifulsoup, 이나 selenium 을 사용할때 의식은 안 했지만 자연스럽게 해당 개념을 이용했다고 볼수 있다.

 

 

 

[Javascript 마무리]

  그럼 마지막으로 자바스크립트를 공부하려면 어떻게 할까? 개인적으로 자바스크립트는 깊이 들어가게 되면 파이썬하고 비슷한 깊이의 복잡도를 가진다고 본다(서버 쪽으로 눈을 돌리면 node.js 같은 서버 언어로도 사용되고 있고 말이다). 그래서 되도록 처음엔 너무 깊은 쪽으로는 가지 않도록, 쉬운 책이나 블로그를 보면서 개념을 잡은 후, 이후 웹 프로그래밍 공부를 하면서 궁금한 부분을 만났을때마다 구글 등을 찾아서 개념을 이해하는 것을 추천한다. 자바스크립트 라이브러인 jQuery 를 많이 사용하긴 하나, 실제 웹페이지들은 jQuery 와 일반 자바스크립트 두 가지 코드가 공존하고 있기 때문에 jQuery 는 응용편이라고 생각하고 접근하는 게 나을듯 싶다. 

 

  일단 기초가 잡히면 무엇을 모르는지와 공부해야할 방향을 스스로 알수 있게 되고, 집밥 백선생에서 요리하기 전에 재료를 섞음 어떤 맛이 될지 상상해 보라는 말 같이, 무언가를 덥석 구체적으로 습득하는 것도 좋지만, 그 전에 이것을 습득하게 되면 어떻게 될까를 잠시 생각해 보는 것도 나쁘진 않은듯 하다. 공부할수 있는 시간은 한정되어 있기 때문에, 가야될 방향을 정확하게 잡는것도 중요하다. 사실 이 강의의 의미도 구체적인 구현 지식의 전달 보다는 각 주제에 대한 접근방식과 개념을 전달하는 것이라고 생각하고 있다. 

 

 

 

 

 

[Web Server]

  이제 슬슬 후반부로 들어간다. 웹 프로그래밍을 공부하려면, 기본적으로 웹 서버의 존재를 이해해야 한다. 웹서버는 단순하게 얘기하면 특정 포트로 요청이 오기를 기다리고 있는 서비스 프로그램이다. 사용자(또는 다른 프로그램일수도 있고)로부터 요청이 들어오면, 적당한 프로그램적 처리를 한 후, 다시 요청한 쪽에 HTTP 형태로 결과를 돌려준다. 웹 서버는 사실 2개의 모듈로 나뉘어져 있다고 생각하는 게 좋다. HTTP 통신을 받거나 응답해 주는 순수 웹 모듈과, 받은 데이터를 특정한 언어에 기반해서 처리해 주는 프로그래밍 모듈로 구성되어 있다고 볼수 있다.

 

  예를 들어 순수 아파치 웹서버는 PHP 와 HTML 을 처리할 수 있는 반면, 아파치-톰캣은 아파치에 톰캣 프로그래밍 모듈이 얹어져 있어 JSP 파일의 처리가 가능하다(심지어 성능이 얼마나 나올지는 모르겠지만, IIS 에도 PHP 모듈을 설치해 PHP 웹서버로 사용할 수도 있다). 파이썬도 flask 같은 샘플을 보면 샘플 프로그램을 띄울때 웹서버가 같이 실행되어 프레임워크 뒤로 숨겨져 있어서 잘 안보이이긴 하지만, 웹서버 모듈이 있어야지만 사용자의 요청에 응답할 수 있다. flask 나 Django 도 아파치와 연동해서, 클라이언들과 주고받는 처리는 검증된 아파치 서버가 해주고, flask 나 django 는 뒤에서 웹어플리케이션 모듈로서만 동작하게도 할수 있는거 같다(이렇게 보면 모든 웹서버의 꿈이 어떤 언어라도 연결해 중계해 주고 싶은것인가 싶기도 하다).  

 

 

 

[IIS 설치]

  그럼 다음 섹션에서 ASP 를 돌려보기 위해서 윈도우 10에서 기본으로 지원하는 IIS 를 설치해서 샘플페이지를 하나 호출해 보자.

 

  먼저 IIS를 설치해 보자. '윈도우키+x' 를 누른후 옆 쪽에 나타나는 메뉴에서, '프로그램 및 기능' 메뉴를 선택한다(설치 부분을 설명하기 위해 잠시 전 uninstall 을 했지만, 이전 시간에서 소개한 예전 윈도우 스타일로 시작메뉴를 보여주는 'classic shell' 을 깔았을 경우는 '제어판>프로그램>프로그램및 기능' 으로 가면 된다).

 

  왼쪽에서 '윈도우즈 기능 켜기/끄기' 를 선택한다.  

 

  이후 인터넷 정보 서비스에서 1) 'World Wide Web 서비스' 를 체크하고, 2) 하위 메뉴에서 '응용 프로그램 개발기능>ASP' 를 선택한다. 3) 그리고 웹 관리 도구' 도 체크한다(나머진 그냥 디폴트로 두면 된다). 체크가 다 되었음 확인 버튼을 누른다. 잠시 기다림 IIS 가 설치가 된다.

 

 

 

['Hello ASP' 샘플 페이지 실행]

  이제 메모장으로 샘플 ASP 파일을 하나 만들건데, 기본 사용자 권한으로는 IIS 의 웹루트 폴더인 c:\inetpub\wwwroot\ 폴더에 파일을 쓰지 못하게 되어 있기 때문에, 메모장을 관리자 권한으로 실행해야 된다. '윈도우+x' 키를 눌러서, 왼쪽 메뉴에서 '검색'을 선택한다. 검색 창이 나오면 '메모장' 이라고 찾는다. '메모장' 아이콘이 나오면 마우스 오른쪽 버튼을 눌러서 컨텍스트 메뉴를 띄워 '관리자 권한으로 실행' 을 선택한다. (매번 이게 귀찮다면 wwwroot 폴더 '속성'의 '보안탭'에서 현재 로그인한 사용자에게 '모든 권한'을 주면 파일 저장이 가능해진다) 

 

  이후 메모장에 아래와 같이 입력한다.

1
2
3
<%
   Response.Write "Hello ASP"
%>
cs

 

  해당 내용을 c:\inetpub\wwwroot 폴더에 파일형식을 '모든 파일'로 하여 'test.asp' 로 저장한다. 이후 브라우저를 띄워 주소창에 http://localhost/test.asp 를 입력한다(localhost 나 127.0.0.1 은 현재 컴퓨터의 주소를 나타낸다). 그럼 아래와 같이 ASP 로 만든 간단한 문서가 IIS의 ASP 모듈에게 해석되어 브라우저에게 전달되어 화면에 보이게 된다.

 

 

 

 

[ASP 로 DB 조회하여 HTML 테이블로 출력하는 샘플 만들기]

  그러면 ASP 웹페이지를 하나 만들어 보자. 데이터베이스의 테이블을 조회 해서 결과를 화면에 HTML 테이블 형태로 뿌려주는 페이지를 만드려고 한다. 여러모로 시간을 아끼기 위해서^^; 4교시때 설치 및 세팅해 놓았던 SQL Server 와 그때 만들어 놓은 supermarket 테이블을 그대로 이용한다(4교시를 안보신 분들은 4교시에 소개된 MSSQL Server 설치 및 테이블 생성을 하시던지, 귀찮으시면 의사코드라 생각하시고 눈으로만 보고 동작을 이해하셔도 된다).

 

  4교시에 만든 테이블은 아래와 같은 테이블 이였다. 4 or 7교시 때 해당 테이블의 내용을 가져오는 파이썬 코드를 만들어 봤었는데, 그때 로직을 기억을 더듬어 보면 1) 먼저 DB 정보를 입력해 연결하고, 2) 각 행을 루프를 돌며 하나씩 읽어오면서, 3) 화면에 프린트를 하거나 엑셀로 저장했다. ASP 코드도 사실 이와 엄청 비슷한 흐름을 가진다(어차피 비슷한 스크립트 언어이기도 하고, 언어들은 서로 영향을 받아 유사한 경향이 있기 때문이다. 코드를 비교해 보시면 파이썬 코드쪽이 좀더 현대적이여서 흐름이 세련된 느낌을 받을 것이다). 작성할 ASP 코드에서는 1) DB 정보를 입력해 연결하고 2) 각 행을 하나씩 읽어 오면서, 3) HTML 테이블을 출력하면서 <td> 태그안에 해당되는 컬럼을 하나씩 넣어준다. 

 

 

 

[ASP 샘플 코드 만들어 보기]

  그럼 ASP 를 공부하는 시간은 아니기 때문에 만들어진 예제를 바로 보자. 원래는 웹프로그래밍 예제라고 하면, 사용자가 입력한 내용을 폼으로 전송해, 해당 값을 서버에서 받아 SQL 조건에 조합해 넣어 특정 itemno 의 상품을 조회해 오거나 하는 등의 예제가 좀더 현실적이겠지만, 단순함과, 이전에 만든 파이썬 코드와 비교할 수 있도록 하기 위해 전달 받는 인자가 없이 테이블의 모든 정보를 조회해 오는 방식의 예제를 만들었다.

 

  ASP, PHP, JSP 같은 스크립트 코드를 처음 보시면 낯설수도 있겠지만, ASP 에서는 <% %> 안에 든 내용이 VBscript 문법의 순수 ASP 코드이고, 코드를 보면 거의 파이썬으로 구현했던 예제와 비슷하게 진행이 된다. ASP 를 배울 것은 아니니 코드의 상세 문법을 보지 말고 주석 위주로 흐름만 보길 바란다(참고로 ASP 는 대소문자를 안가린다).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<%@ Language=VBScript %>
 
<%
   ' 연결 문자열 정의
   strMyTest = "Provider=SQLOLEDB; Data Source=localhost; Initial Catalog=mytest; User Id=pyuser; Password=test1234"
   Set objConn = Server.CreateObject("ADODB.Connection")
   objConn.Open strMyTest 
 
   ' 실행할 SQL 정의
   strSQL = "select itemno, category, foodname, company, price from supermarket s(nolock)"
 
    ' 쿼리 실행 하여 결과 얻어옴
   Set rtnRow = objConn.Execute(strSQL)
%>
 
 
<html>
   <head>
      <title>supermarket</title>
   </head>
 
   <body>       
      <p>supermarket 상품</p>
      <table border=1>
         <tr>
            <td>번호</td>
            <td>카테고리</td>
            <td>종류</td>
            <td>상품이름</td>
            <td>가격</td>       
         </tr>    
 
<%
   'DB 에서 조회한 행이 끝이 아니라면 루프를 돌리면서 각 컬럼을 <td> 태그안에 끼워 넣는다.
   Do while Not rtnRow.EOF 
%>
 
         <tr>
            <td><%=rtnRow("itemno")%></td>
            <td><%=rtnRow("category")%></td>
            <td><%=rtnRow("foodname")%></td>
            <td><%=rtnRow("company")%></td>
            <td><%=rtnRow("price")%></td>
         </tr>
                        
<%
   'rsList의 내용을 다음 결과 행으로 이동하며 Do 문을 반복한다.
   rtnRow.MoveNext
   Loop
%>
 
      </table>
</body>          
 
<%
   '열었던 연결 닫기
   objConn.Close
   Set objConn=Nothing
%>
cs

 

  그럼 위의 내용을 샘플파일과 비슷하게 관리자 계정으로 실행한 메모장에 붙여넣기 하여 c:\inetpub\wwwroot 폴더에 'supermarket.asp' 이름으로 저장을 하자. 이후 브라우저 주소창에서 http://localhost/supermaket.asp 을 호출한다. 그럼 아래와 같이 DB에서 supermarket 데이터를 가져와서 HTML 테이블로 정리하여 보여주는 페이지가 나오게 된다. ASP 가 처음이신 분은 처음 파이썬으로 DB 를 조회했을 때처럼 조금은 신기한 느낌을 받지 않을까 싶다.

 

 

  참고 1:  여기서 잠시 위의 코드의 구조를 보면, 우리가 배운 HTML 과 ASP 의 프로그램 코드가 하나의 파일에 섞여 있어서, 보기 조금 힘든 것 같지 않는가 싶다(뭐 익숙해짐 편한면도 있긴하다). 나중에 Django, JAVA 나 .NET 같은 언어에서 사용하는 MVC 개념을 통해, 마치 CSS를 통해 디자인 코드를 분리해 낸 것처럼, 이런 레거시 웹 프로그램에 섞여있는 프로그램 코드와 HTML 코드를 개별 요소로 분리해 다루려는 시도를 하게 된다.

 

  참고 2: 현재 기본 설정으로는 에러가 발생시 어떤 에러가 났는지 상세하게 보여주지 않는다. SQL 에러같은 상세한 에러를 보기를 원하는 경우는 아래의 웹사이트를 참고하여 세팅한다.

http://ooz.co.kr/172

 

  참고 3: 그리고 하나 더 노파심에 얘기할 것은, 위의 예제 코드는 소스를 간단히 만들기 위해서, SQL Injection(DB에 공격자가 임의의 쿼리를 삽입할 수 있는 웹취약점) 에 노출되어 있다. 저 방식으로 페이지를 만들어서는 안된다^^; 궁금하신 분은 구글에 'sql injection defense asp' 라고 찾아보심 된다. 요점은 stored procedure 같이 preparedstatement 타입으로 쿼리를 호출하고, 입력 데이터들을 validation 하고, 어플리케이션 DB계정의 권한을 최소화 하고 등등 이다. 현대 웹이 좋은 이유 중 하나는 이러한 패턴을 프레임워크 자체에서 막아주는 경우가 많다.

 

 

 

 

[Ajax]

  그럼 오늘의 마지막 주제인 Ajax에 대해 얘기해 보자. 개인적으로 모든 현대의 세련되고 편리한 웹페이지들은 이 Ajax 의 활약으로 이루어진게 아닌가 싶다. 구글의 검색어 추천이나, 여러 사이트 들의 사용자의 액션에 반응하는 부드러운 UI 의 이면에는, 웹 브라우저의 뒤에서 열심히 데이터를 요청해 나르고 있는 Ajax 가 존재한다. Ajax(Asynchronous JavaScript and XML) 의 비동기적이라는 의미는, 아마도 기존 웹 페이지 간의 명시적인 호출을 동기적이라고 가정했을때, 페이지가 정지해 있는 상태에서 뒷단에서 Ajax 라이브러리를 이용해 데이터를 교환하는 행위를 비동기적이라고 바라보는 관점인 것 같다. Ajax 는 자바스크립로 구현된 라이브러리로 단순하게 봐도 괜찮을 듯 싶다.

 

 

  Ajax 의 간단한 예는 아래 그림의 구글 검색어 추천 기능이다. 사용자가 'python' 이라고 입력하는 동안 계속 추천하는 검색어를 바꿔가면서 보여준다.

 

  해당 추천 검색어가 나오는 과정을 fiddler 로 살펴 보게되면, 아래와 같이 www.google.co.kr 도메인에서 /complete/search? 페이지를 호출하는 6개의 요청('p' 'y' 't' 'h' 'o' 'n' 각각 입력에 따라 총 6개의 요청이 날아간다)을 볼수 있다. 그 중 맨 마지막 요청 항목을 클릭해 보면, 폼안의 'q' 인자 안에 우리가 입력했던 'python' 이라는 글자가 넘어가고, 밑 쪽 보면 결과값으로 'python' 에 해당하는 추천 검색어들이 'json 데이터 형식'으로 담겨 응답으로 오게 된다. 브라우저는 해당 json 내의 값을 적당히 풀러서, HTML 페이지의 DOM 개체에 넣어서, 위의 그림과 같은 추천 검색어 들을 보여주게 된다.

 

 

 

[Ajax 샘플 코드 만들어 보기]

  그럼 실제로 간단한 Ajax 샘플을 하나 만들어보자. 우선 전송되는 데이터 타입은 json 이 아닌 평문으로 간단하게 구현 하려고 한다(ASP 가 좀 구식언어이고, .NET 이 나오면서 업데이트가 없이 버려져서, json 같은 최신 데이터 구조를 파싱하기가 까다로운 점도 이유이다). 아래 코드에 최대한 주석을 달아 놓았다(// 는 자바스크립트 주석이고, <!-- --> 는 HTML 주석이다).

 

  위쪽의 자바스크립트에는 Ajax 리퀘스트가 정의되어 있다(처음 보는 분들은 조금 사용하는 구조가 낯설어 보일테지만, 처음 Ajax 를 만든 사람이 저렇게 사용하라고 정해 놓은거라서, 파이썬에서 SQL 연결 할때 사용하는 모듈의 문법대로 구현 해야하는 것과 같다고 보면 된다. jQuery 등의 라이브러리를 사용하면 또 그 쪽에서 시키는 스타일대로 하면 된다). 하단 HTML 에 정의된 사용자가 입력한 <input> 값을 읽어와서, ajax_sub.asp 페이지를 호출(폼의 action과 비슷하다고 보면 된다)하면서 'no' 인자에 사용자 입력값을 넣어서 보내고, 결과가 반환되어 오면 그 값을 아래 HTML 에 정의된 <span> 태그에 innerHTML 속성을 이용해서 넣는다

 

  하단 HTML 에는 버튼 속성을 가진 <input> 태그가 있는데, 자바스크립트의 getMenu 함수가 onclick 이벤트를 통해 연결되어 있다. 사용자가 번호를 입력하고 버튼을 클릭하면, getMenu 함수가 실행되면서 Ajax 요청을 실행하는 도미노 형태의 구조이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<%@ Language=VBScript %>
 
<script>
function getMenu() {
   var xhttp;
   // 사용자가 입력한 값을 id 를 통해 가져온다.
   var menuNo = document.getElementById("menuNo").value;
   
   // 새로운 ajax 요청을 만든다
   xhttp = new XMLHttpRequest();
   // 요청에 대해 응답이 정상으로 올때까지 기다려서 
   xhttp.onreadystatechange = function() {
      if (xhttp.readyState == 4 && xhttp.status == 200) {
         // span 태그내에 응답으로 온 텍스트 값을 살짝 끼워 넣는다.
         document.getElementById("menuName").innerHTML = xhttp.responseText;
      }
   }
   // 실제 요청하는 페이지는 ajax_sub.asp 페이지 이고, get 인자로 no 에 사용자가 입력한 값을 넣는다.
   xhttp.open("GET""ajax_sub.asp?no="+menuNotrue);
   xhttp.send();
}
</script>
 
<html>
   <head>
      <title>ajax 샘플</title>
   </head>
<body>
   <table>
      <tr>    
         <td> 메뉴 번호: </td>
         <!-- 사용자가 입력하는 값 -->
         <td width=120> <INPUT id="menuNo" size="10" type="text" value=""> </td>    
           <td width=200> 
               <!-- 버튼을 누르면 getMenu 함수를 실행 한다 -->
               <input type="button" value="해당되는 메뉴 찾기" onclick="getMenu()">
               <!-- 나중에 응답 값을 끼워 넣을 span 태그. 첨에는 아무 내용도 없다 -->
               : <span id="menuName"></span>
           </td>
         </tr>
   </table>         
</body>
</html>
cs

 

  앞의 ASP 예제들과 마찬가지로 해당 코드를 메모장에 붙여놓고, c:\inetpub\wwwroot에 ajax_main.asp 라고 일단저장한다.

 

 

  그 다음은 위의 페이지에서 호출하는 대상인 ajax_sub.asp 페이지를 만들어야 한다. 보통 이 호출 당하는 쪽은 데이터만 받고 보내면 되기 때문에 API 같은 형태로 많이 구현 되어있다. 구현한 코드는 아주 간단하다. 넘어온 no 값을 받아서(사용자가 입력한 값이다), 1 이면 'pizza', 2 이면 'pasta', 그 이외의 숫자면 'drink' 를 반환(response) 해준다(참고로 값이 없을 때의 에러처리는 안되서, 값을 안 넣음 아무 결과도 넘어오지 않는다).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<%@ Language=VBScript %>
 
<%
    ' request 값 받기
   menuNo = request("no")
    
   ' 넘어온 메뉴 번호에 해당하는 메뉴이름을 반환해 준다..
   Select Case menuNo
      Case 1
         Response.Write("pizza")
      Case 2
         Response.Write("pasta")
      Case Else
         Response.Write("drink")
   End Select
%>
cs

 

  해당 코드도 메모장에 붙여놓고, c:\inetpub\wwwroot에 ajax_sub.asp 라고 저장한다. 

 

 

  이후 브라우저를 열어서 http://localhost/ajax_main.asp 라고 주소창에 입력한다. 아래의 화면이 나오면 메뉴번호에 1을 넣고, '해당되는 메뉴찾기' 버튼을 클릭하면, 페이지 뒤에서 Ajax 기능을 이용해 ajax_sub.asp 에 인자를 넘겨 데이터를 조회해서, 'pizza' 를 받아 옆에 표시하게 된다.

 

 

  Ajax 호출하는 과정을 피들러로 살펴보게 되면 앞에 봤던 구글 검색어 추천과 비슷하게, 숨겨진 호출(request)이 보인다. 이제 Ajax 를 통한 비동기적인 호출이 어떤 원리로 일어나고, 어떻게 보이는 건지 대충 감을 잡으시리라 생각한다.

 

 

 

 

[마무리 하면서]

  그럼 이렇게 되어 가벼운 깊이지만 꽤 길게 진행됬던 Legacy Web 편을 마치려 한다(개인적으로 소주제별 균형을 맞추기 가장 힘들었던 시간 중 하나일듯 싶다). 뒤돌아 보면 자잘하게 많았던 HTML(form, event), CSS, Javascript, Web Server, Web Language, Ajax 같은 기초 요소들이 우리가 보고 있는 웹을 지탱하고 있다는걸 볼수 있었다. 이런 'Legacy Web' 에 대한 기초가 잘 잡혀 있다면 파이썬이든, 다른 언어로든 웹프로그래밍을 공부할때 배워야 할 주제들이 한결 가벼워지게 된다고 생각한다. 앞에서 얘기했던 프로그래밍을 배울 때의 외적요소들을 미리 알고 있을 때처럼 말이다.

 

  혹시 위의 주제들을 잘 모르는 상태에서 파이썬으로 만드는 웹을 공부하고 싶다면, flask 나 Django 프레임워크를 공부하는 중간 중간에 꼭 각각의 주제에 대한 쉬운 책 한권 정도는 보기를 추천한다. 개인적으로 프레임워크는 웹의 구성요소들을 잘 배치할 수 있도록 돕는 껍데기 역활에 불과하지 않는가도 싶다. 다음 시간에는 이런 Legacy Web의 요소들이 파이썬의 Flask 라는 프레임워크에 어떻게 녹여져 소개되고 있는지, 가벼운 맘으로 체크해 보도록 해보자.

 

 

 

 

2017.7.15 by 자유로운설탕
cs

 

 

posted by 자유로운설탕
2017. 6. 18. 15:49 프로그래밍

  이번 시간에는 머신러닝에 대한 이런저런 생각들을 얘기하고, 이전 시간에 배웠던 numpy, scipy, 그리고 요즘 주목 받고 있는 텐서플로우(tensorflow) 라이브러리를 이용해서, 머신러닝 계의 구구단 이라고 할수 있는 최소제곱법(Least Square Fit)으로 데이터에 맞는 직선을 추정하는 샘플 3가지를 실행해 보려고 한다.

 

 시작하기전에 먼저 양해를 얻고 싶은 점은 스스로 생각했을 때 머신러닝에 대해서는 아주 조금 밖에는 알고 있지 못하다는 점이다. 비유하자면 수영을 해서 강을 건너야 하는 상황인데, 아직 강가에서 손가락만 살짝 담구고 '물이 얼마나 찬가' 하고 체크하는 레벨이라고 볼수 있다. 그래서 여기서 얘기한 얘기들이 틀릴 수도 있기 때문에, 내용을 최대한 비판적으로 받아 들이라고 권고를 드린다.

 

 

[목차]

0. 왜 파이썬 공부에 구글을 이용하는게 좋은가?

1. 언어를 바라보는 방법. 파이썬을 어떻게 바라봐야 할까?

2. 파이썬 설치와 환경, 버전 선택 하기의 이유.

3. 만들고자 하는 기능을 모르는 조각으로 나눠 조사해 보기

4. 데이터 베이스에서 내용 가져와 출력하기

5. 암호화 모듈을 이용해 암복호화 해보기

6. 퍼즐 조각들을 합쳐보기

7. 엑셀 파일 사용해 보기 -> 부록 : fuction 을 이용해서, 코드 정리해 보기

8. 정규표현식을 왜 사용해야 할까? 언어속의 미니 언어 정규표현식 살펴보기

9. 입력과 결과를 GUI 화면과 연결해 보기

10. Whois API 이용해 보기

11. 웹페이지 호출해 내용 파싱 하기(BeautifulSoup 그리고 한계)

12. 자동화 - 웹 자동화(with Selenium)

13. 자동화 - 윈도우즈 GUI 자동화(with pywinauto)

14. 자동화 - 작업 자동화

15. 수학 라이브러리 살펴보기

16. 그래픽 라이브러리 살펴보기

17. 머신러닝에서의 파이썬의 역활

18. 웹 프로그래밍 - Legacy Web

19. 웹 프로그래밍 - Flask 살펴보기(feat. d3.js)

20. 웹 프로그래밍 - Django 살펴보기

21. 정리 - 이런저런 이야기

 

 

 

 

[들어가면서]

  여러 웹사이트와 블로그들을 돌아다녀본 결과, 보통 두 가지 입장이 눈에 많이 뜨이는 것 같다.  한 측면은 수학이나 통계학에 대한 경험을 보유하고 있는 상태에서, R이나 매트랩 등의 연구 목적의 툴을 쓰다가, 좀더 일반적인 머신러닝 프레임윅에 관심을 가져 길을 가다보니, 파이썬 같은 범용 프로그래밍 언어를 해야될 필요성을 느끼게 되어 본격적으로 프로그래밍의 세계에 발을 들여 놓게 되는 경우 이다.

 

  다른 한 측면은 프로그래밍, DB, 보안, 시스템 등의 기술적인 업무 등을 하는 입장에서 머신러닝 책이나 강의를 찾아 보다보니, 원리를 설명하는데 사용하는 선형대수, 통계 그리고 RNN 같은 낯선 이론들을 만나게 되고, 책이나 강의를 이것저것 봐도 머신러닝 라이브러리를 작동 시키는 부분은 대충 따라 할 수 있을 것 같은데, 해당 도메인에 대한 부족한 이해 때문에 전체적인 그림이 그려지지 않고, 어떤 원리로 그런 일들이 가능한지, 어떤 데이터를 가져다 어떻게 가공해야 하는지 대해서는 여전히 모호한 상태임을 느끼게 되어, 수학이나 통계 분야에 대한 공부를 시작하는 경우일 것이다(저같은 경우는 후자 그룹에 속하는 경우인거 같다).

 

  그런데 사실 그렇게 가지지 못한 지식에 대한 대한 니즈나, 동경에서 공부를 시작한다고 해도, 만족할 만한 끝은 좀처럼 잘 보이지 않게 된다. 왜냐하면 그 동안에 관심이 없었던 상대의 영역의 본질에 접근하기는 쉽지 않기 때문이다. 다른 분야의 예를 들자면 캐릭터를 잘 만들거나 그림을 잘 그리기 위해서 3D 모델링 프로그램, 일러스트레이터, 페인터 등의 그래픽 관련 프로그램을 마스터 하려는 것과 비슷한 접근이라고 느껴진다. 아무리 해당 툴의 메뉴얼을 달달 외고, 기능을 잘 이해해서 쓴다고 해도, 그러한 행위의 본질이라고 할수 있는 미학적, 공간적인 감각을 기르는 경험들이 부재한다면, 그러한 노력이 올바른 결실을 맺긴 힘들다고 본다.

  

  마찬가지로 현재 올렸던 글들을 계속 읽어오신 분들은 공감하셨겠지만, 프로그래밍 이란 것은 단지 가시적으로 보이는 코드란 측면에 국한된 지식은 아니다. 외국어를 배울때 그 나라의 문화를 같이 이해해야 자연스럽고 맥락에 맞는 표현을 할수 있게 되는 것처럼, 머신러닝을 받치고 있는 프로그래밍 지식은 파이썬이란 언어 자체에서 출발하여 해당 언어가 동작하는 OS, 네트워크 등의 여러 주변환경과, 데이터를 담는 빅데이터 시스템 등 기술 생태계 전체가 포괄적으로 엮여 있다고 생각한다. 그래서 스펙트럼이 넓을 뿐만 아니라, 인기가 좋아 급속히 성장하며 확장되는 기술 영역들이 의례 그렀듯이 자료들이 초보자들에게 그다지 친절하게 정리되어 있진 않고 계속 변하기 때문에 접근 난이도가 높은 편인 것 같다. 물론 서로 다른 문화가 그렇듯이, 서로 다른 분야 사이에도 자신의 분야의 경험을 기반으로 유추할 수 있는 비슷한 공통 개념들도 많긴 하지만 말이다.

 

  잘은 모르지만 반대로 프로그래밍 영역과 비슷하게 짐작해 보면, 수학이나 물리, 통계 등의 분야도 단순히 보이는 이론과 수학적 지식들이 전부는 아닐 것 같다. 그 학문을 오래 접했던 사람들만이 가질 수 있는 특유의 사고 방식과 문제에 대한 접근법, 데이터와 숫자를 보는 시야나 감을 얻는다는 것은 단순히 통계나 선형대수 책을 공부하는 것과는 좀 많이 다른 일이 아닌가 하는 생각이 든다. 

 

  그래서 어떤 측면에서는 한 사람이 모든 부분을 다 잘 알순 없기 때문에, 효율성을 위해서는 데이터를 다루는 직군, 모델과 알고리즘을 다루는 직군, 프로그램을 관리하는 직군을 따로 나누어 조직해야 한다고 하는 주장도 있지만, 뭐 그런 경우라도 서로서로 상대방이 하는 일을 이해하면서 일을 하면 효율적이기도 하고, 이해 타산을 버리고 순수하게 공부하는 현 시점에서는 무시하기로 하자. 이 시간엔 조금이라도 머신러닝이라는 분야가 정밀한 숫자, 연관된 수학, 복잡한 통계이론을 차지하고도 합리적인 활동으로 보일 수 있는지 생각해보자.

 

 

 

 

[머신러닝이 하는 일에 대해 상상해보기]

  우선 어떻게 기계가 학습을 할수 있다는 것을 직관적으로 이해할 수 있을까? 기계가 학습을 한다는 것은 기계가 사람처럼 알고리즘을 만드는 작업, 즉 프로그래밍을 할수 있다는 얘기다. 예전부터 자동으로 프로그래밍을 짜는 일은 인간의 창조적인 능력의 한 부분이라고 믿어왔고, 여러가지 쉽고 자동화된 프로그래밍 툴을 만들려는 시도가 많이 실패 해 온것으로 알고 있는데, 모두 인간의 자만이였을뿐일까? 개인적인 생각으로는 기계가 학습하여 프로그래밍을 한다고 하는 부분은 사람이 프로그래밍을 하는 부분과는 다르면서도 동일한 모순적인 측면이 있다고 본다.

 

 

  예를 들어 하나의 프로그래밍 로직에 대해 생각해 보기 위해, '1교시 언어를 바라보는 방법'에서 보았던 아래 그림을 다시 한번 보자.

 

  당연한 얘기 같지만 모든 목적을 가진 프로그램은 입력과, 출력을 가지고 움직인다(실행 시점에 인자가 전달되지 않는 프로그램도 실행 시 시스템과 입력과 출력을 하고 있다고 볼수 있을 것이다). 그럼 프로그래머가 작성한 프로그램이 내부적으로 입력에 항상 3을 곱한 후 1을 더 해서 결과를 보여주는 프로그램이라고 해보자. 초등학교 수학식으로 나타내면 'y=3x+1' 일 것이다. 파이썬 식으로 나타내면 아래와 같다.

1
2
3
4
.... 입력으로 x 가 들어옴.
 
= 3x + 1
print (y)
cs

 

  근데 우리가 해당 로직을 이해할 수 없는 상태에서(곱하기가 엄청 고차원 적인 수학이라고 가정해 보자), 사람들이 해당 프로그램을 사용해서 쌓인 충분히 많은 입력-출력 쌍 데이터가 있다고 해보자. 예를 들면 (1, 4), (4, 13), ... (10000, 30001) 같은 형태일 것이다. 해당 데이터를 관찰해 보면 결과 데이터의 경우는 입력 데이터에 사람이 만든 어떤 프로그램 로직(여기서는 x*3 +1 )이 들어가 만들어지는 것이라고 볼수 있을 것이다.

 

  그럼 반대로 만약 어떤 임의의 시스템이 x 입력을 받아 y를 출력을 나타내는 것을, 위의 수집한 데이터에 대해서 최대한 높은 '옳음'으로 처리할 수 있다면, 해당 시스템의 속이 어떻게 생겼는지 상관없이, 해당 시스템은 사람이 작성한 위의 프로그램과 근사적으로(어쩌면 극한적으로) 동일한 자동화 프로그램이라고 할 수 있을 것이다(모든 사칙연산 데이터를 그대로 흉내내는 머신러닝 모델을 상상해 보자. 사람이 만든 계산기와 비슷하다 볼수 있지 않겠는가?).  

 

  즉 데이터 측면만을 보면 충분한 규모의 객관적으로 수집된 입력과 출력 데이터가 있고, 그 안에 현상을 왜곡하는 가짜 데이터가 무시할 만큼만 존재하고(또는 모델내에 그러한 데이터를 걸러내 무시하는 안전장치가 있어도 되고), 그 데이터들이 뭔가 사람이나 현상의 의미있는 활동을 나타낼 가능성이 있다면, 해당 데이터에는 사람이 논리적으로 파악하긴 힘들 수도 있지만, 그 데이터 쌍들을 만들어낸 로직을 포함하고 있다고 볼수 있지 않을까 싶다. 

 

 

  충분한 규모의 객관적인 입력과 출력 데이터 쌍은 비즈니스, 데이터 분석과, 빅데이터의 도움으로 이루어 진다고 보면 될것 같고, 가짜 데이터의 제거 부분은 비즈니스, 데이터 분석, 노이즈에 강한 모델 등을 포함해 모든 동원할수 있는 방법이 다 포함 될수 있을 것 같고, 입출력 데이터들 자체에 숨어있는 로직이나 패턴을 적절한 필터를 써서 참기름 짜듯 뽑아내는 것이 여러 머신러닝 알고리즘과 모델의 앙상블이라고 생각해보면 어떨까 싶다.

 

  그래서 사람이 가진 논리적 기술로 데이터를 가공하지 않고, 머신러닝 알고리즘(물론 이것도 넓은 범위에선 논리인것 같다)이 데이터 안의 로직이나 패턴을 추출해 내는 과정을 '기계학습'이라고 명명 지은게 아닌가 싶다. 로직을 짜내는 과정에서, 해당 머신러닝 필터는 데이터에 커스터 마이즈된 특정한 모양(모델이 데이터에 커스터마이즈 된 부분을 얘기하는지, 데이터에 독립된 필터를 얘기하는지는 좀 아리송 하긴 하긴 하지만. 현재로서는 왠지 모델 자체에 데이터의 숨은 로직의 특성도 포함되는거 같다)을 가진다. 이후 이 특정한 모양으로 커스터 마이즈된 필터를 향후 들어오게 되는 같은 타입의 새 데이터에 적용하게 되면, 사람이 프로그래밍 하지 않아도 기존 결과 데이터와 비슷한 로직의 영향을 받은, 결과 데이터를 만들어 내게 되지 않을까 싶다.

 

  그 후 일반적으로 만들어진 모델을 검증하기 위해서, 보통 전체 데이터셋을 트레이닝셋과 검증셋으로 적당히 나누어 트레이닝셋으로 훈련을 시킨 후에 검증셋으로 실제 경기를 진행 하게 한다. 트레이닝 셋은 일종의 원어민 회화 이고, 검증셋은 실제 다른 외국인에게 배운걸 시도 하는거라고 생각해봄 이상할까--; 

 

 

 

 

[몇가지 문제 들]

  머신러닝이 입력과 결과 데이터 사이에 숨어있는 로직을 뽑아내는 작업이라는 것이 어느정도 맞다는 가정에서, 몇가지 머리가 아픈 모호한 문제들이 발생할 수 있다.

 

 

  1번째 문제는 'garbage in garbage out' 이다. 어떤 우수한 머신러닝 모델이라도 좋은 데이터를 공급해 주어야 좋은 알고리즘을 추출해 준다는 것이다. 정신건강의학과 의사가 상담을 하는데, 환자가 거짓말만 늘어 놓는다면 어떻게 될까? 아무리 실력있는 의사라도 환자의 마음의 병의 원인에 대해서 잘못된 판단을 하게 될것 이다(물론 정말 뛰어나다면 거짓말을 한다는 것 자체를 눈치채고 진실을 얘기하도록 유도할 수도 있겠지만^^). 그럼 이러한 가짜 데이터들은 왜 들어 가게 될까?

 

  우선 충분히 신뢰할 만큼 데이터 양이 많지 않거나 특정 군으로 편향될 수도 있다. 우리가 매번 선거 시기에 듣는 여론 조사용 표본 데이터의 중요성이다. 게임센터를 들어가는 학생 10명의 의견을 듣고 우리나라 전체 학생이 어떤 생각을 가지고 있다라고 말하는 것은 의미 없을 것이다. 근데 사실 현실적으로 충분히 좋을 정도라는 표현은 참 추정하기 힘든 일인거 같다. 충분히 좋을 정도의 개발, 충분히 좋을 정도의 테스트 같이, 잘은 모르지만 통계적으로 의미 있는 충분한 데이터 라는 것도 약간은 바라보는 관점에 따라 애매한 영역이지 않을까 생각해 본다.

 

  다른 원인은 노이즈이다. 만약 누군가 사실을 들키는 것이 창피해서 속마음과는 다른 가짜 선택을 했다면? 데이터를 취합하는 과정에서 잘못된 정보들이 우연히 섞여졌다면? 쇼핑몰에서 연령대별 분석을 하는데 회원들의 아이디들을 가족이 모두 공유해서 사용하고 있다면?(뭐 예를 들어서 회원제인 코스트코 같은데는 지인이 부탁한 물건을 사다주는 사람도 많을거 같다) 해커가 들어와 정보를 본인에게 유리하게 몰래 변경했다면? 해당 데이터의 형태가 애초부터 랜덤적인 선택 요소가 표함되어 있다면? 등등.. 다양한 일들이 생길 수도 있다. 또는 아래의 알렉사 기사처럼 TV 에서 나온 음성 같은 외부 테이터를 잘못 받아들여 엉뚱한 판단을 하게 될지도 모른다.(뭐 홍채나 얼굴 인식이 사진으로 된다든지 하는 것도 비슷한 경우일 것이다) 

http://thegear.co.kr/13718

 

   

  2번째 문제는 데이터 무더기 에서 학습을 위해 제공하기 위해 골라낸 요소들이, 정말 해당 현상을 제대로 설명하는 인자인가 하는 문제이다. 어떤 인자는 혼동만 주는 필요 없는 인자일 수도 있고, 어떤 중요한 인자는 빠져있을 지도 모른다. 최악의 경우는 현재 수집을 안하고 있거나, 현실적으로 수집을 못하는 요소일 지도 모른다. 데이터를 제대로 이해 못하고 머신러닝 모델에게 전달한다는 것은, 사람이 비즈니즈 룰도 이해를 못하고 프로그래밍을 하는 것과 그닥 차이가 없을 것이다. 로직은 돌아 갈테지만 아마도 아무 의미도 없거나 재앙일 것이다.

 

  또는 데이터의 범위를 잘못 잡은 학습을 할수도 있다. 예를 들의 앞의 예가 아래와 같은 로직으로 동작하는데, 현실로 수집가능한 데이터가 10000 까지 였다면, 해당 데이터로 학습된 모델은 10000 을 넘어가는 데이터를 만날때 커다란 재앙을 안겨줄 지도 모른다(버퍼 오버플로우 처럼 말이다).

1
2
3
4
if input <= 10000:
    우리가 수집한 데이터(0~10000)가 적용된 로직
else:
    새로운 타입의 데이터가 생성됨
cs

 

  비슷하게 여론 조사 결과 조작이나, 통계의 여러 부작용 처럼, 데이터의 어떤 집합, 성질을 선택 하느냐에 따라서 의도된 답을 선택하거나 실제와는 다른 결과를 보여줄 수도 있을 듯 싶다. 

 

 

 3번째 문제는 학습된 모델을 실제 현실에 적용할수 있느냐 하는 문제이다. 자율자동차와 같이 운전자의 보호냐 보행자의 보호냐를 선택하는 문제일 수도 있고, 99.9999% 의 정확도라도 false-positive(안전하다고 판단했지만 실제로는 위험함) 가능성이 존재하는 한 실수에 대한 윤리적 문제가 일어날 수 있다. 예로서 머신러닝 기반의 의료검사를 신뢰 하다가 드문 케이스 때문에 암을 놓쳤다고 해보자. 시스템의 정확도가 엄청 높고, 해당 경우의 데이터가 트레이닝 데이터에 포함되지 못한 정말 운이 없는 경우기 때문에 어쩔수 없다라고 할수 있겠는가? 이런 분야라면 의사가 먼저 체크 후 괜찮다고 판단한 환자를 머신러닝으로 2차 체크 하여, 의사의 실수를 줄이는 방식으로 이용하는 등의 사람들이 납득할 수 있는 합리적인 기술 적용 프로세스가 필요하다. 그래서 어떤 사람들은 머신러닝을 기존 기술의 대체제가 아닌 보완제의 관점에서 접근하기도 한다.

 

 

  마지막 문제는 아마도 모델의 오차와 별개로 우리가 가지고 있는 데이터 자체가 제공된 시점부터 이미 실제 진실과는 조금은 차이가 날 수 있을지 모른다는 것이다. 사람들의 해당 데이터를 만들게된 모든 이유를 다 이해한다면 모르지만, 그런 전지전능함을 가지지 못한 우리는 데이터에 담긴 fact 만을 믿어, 중요한 사실을 놓칠지도 모른다. 마치 우리가 매일 만나는 사람이 겉모습과는 다른 속마음을 가질 수 있는 것과 비슷하게 말이다. 또한 해당 진실은 해당 시간대에서만 유효했을 수도 있다. 시간이 흐름에 따라 다른 영향을 받을 수도, 또한 아예 해당 데이터는 시간에 따라 변화를 하는 데이터일 수도 있으니 말이다.

 

 

  그래서 알파고가 바둑이나 스타크래프트 같은 분야를 선택한건 아주 영리한 선택이라고 생각한다. 현실의 데이터가 왜곡되듯이 바둑기사가 불리하다고 바둑알을 속이거나 바둑판을 엎는 일 따위는 없을 테니까 말이다. 또한 바둑과 같은 테이블 게임은 경우의 수는 무한대일지 모르겠지만, 바둑 룰과 선의 공간, 그리고 승리라는 목적으로 닫힌 세상이라는 부분은 그 무한대 성을 많이 제한해 주며, 데이터의 무결성을 보장하는데 오픈된 분야보다 많이 유리하다고 생각한다(게임이란건 사람의 이해 가능성을 전제로 성립될 수 있기 때문에 무한에 가깝다곤 하지만 어째든 제한은 있다고 본다. 다만 해당 부분은 효율적인 승리의 관점에서 제한된다는 거지, 바둑의 철학적 관점에 대한 제한을 얘기하는 것은 아니다). 그것은 다음에 도전할 지도 모른다는 스타크래프트 같이 현재 머신러닝이 강세를 보이고 있는 게임 분야도 비슷할 듯 싶다. 아무리 자유도가 높더라도 게임 분야는 사람이 흥미를 가지도록 방향이 제한되고, 추출해야될 로직들이 머물고 있는 데이터들에게 비교적 안전한 닫힌 세상이라고 생각한다(뭐 그렇다고 쉽다는 얘기가 아니라 real world 에 비해서 상대적으로 그렇다는 의미다. 그리고 부루마블 같은 주사위와 황금열쇠라는 랜덤요소를 가지는 게임 같은건 좀 다른 문제일 것이다). 어떤 기사의 마지막에서 살짝 언급했듯이 알파고의 승리의 숨은 주역은 뒤에 적절한 장르와 데이터를 선택하고, 적절한 알고리즘으로 학습시킨 과학자 들이라는 말이, 물위를 헤엄치는 오리의 바쁜 발을 떠올리게 된다.

 

 

 

 

[기술적인 미래]

  이왕 이것저것 얘기를 한 김에, 앞으로의 머신러닝 기술이 어떻게 될까도 추측해 보자. 빅데이터 분야의 진행을 기반으로 추측해보면, 처음에 나온 하둡이 hive 나 pig 같은 좀더 쉬운 작업을 가능케 하는 프레임윅들로 감싸지듯이, 현재 같이 하나하나 이해하며 초기 값 및 모델, 제어 요소들을 조정해, 좀더 빠르고 높은 정확도의 결과를 얻으려 하는 부분은 점차 통합되고 자동화된 로직으로 대체될 가능성이 높을 듯 하다. 군웅할거중인 머신러닝 프레임윅들도 한두개의 강력한 범용 프레임 윅과, 몇개의 특수 프레임윅들로 정리되지 않을까 생각한다(안 쓰는 애들은 버려지니...). 여튼 머신러닝 기법을 사용하는 방법은 점점 쉬워지며 블랙박스로 감싸질 것 같다. 이런 부분이 많은 강의 하는 분들이, 관련 수학을 깊게 몰라도 머신러닝을 배우는덴 무리가 없다고 강조하는 측면인 듯 싶다. 우리가 기계공학을 몰라도 자동차를 운전할수 있듯이 말이다.

 

  물론 머신러닝 분야의 알고리즘 개발에 발을 담그고 있는 분들이나, 좀더 디테일하며 컨트롤을 원하는 분들은 껍데기 안쪽을 다루기 위해서 좀 더 깊게 이해하려 하는게 맞을 수도 있지만, 어느 정도 깊이로 이해하려 하는지는 좀 생각해 볼 문제 같다. 깊이 문제도 수학식이나 통계 공식의 논리적 정확성의 증명에 너무 집착하다보면 원래 그 식이 얘기하려고 했던 이미지나, 본질을 놓칠수도 있으니까 말이다(공부하기 싫어서 하는 얘긴 아니다^^). 수학은 연산보다는 그 연산이 무엇을 의미하는지를 이해하는게 더 중요한 듯도 싶다. 그리고 아마도 커다란 메이저 회사들이 필요한 프로그래밍 기술들을 머신러닝 프레임윅 안 쪽에 쉬운 인터페이스로 구현해 놓음으로서 현재의 프로그래밍 성격의 장벽은 점점 낮게 될 것 같다. 물론 어려운건 마찬가지 겠지만 현재보다 상대적으로 쉬울수 있다는 얘기긴 하다. 또한 개인이나 자그마한 기업들은 유용한 데이터 자체를 수집하기 힘들수도 있기 때문에 일반적인 분야에 대해서는 1+1 상품처럼 데이터와 학습된 머신러닝을 같이 묶어서 제공하는 부분들도 늘어날 것 같다. 지금 한참 관심을 받고 있는 음성이나 이미지 인식 분야 같은 인류의 공공재 분야에서 말이다.

 

 

  그리고 반대로 생각해 보면 미래에도 개선되기 힘들 것 같은 부분들이 있다. 우리가 비즈니스를 이해하고, 적정한 테이터를 디자인해 수집하고, 학습에 필수적인 값들을 뽑아내고, 머신러닝이 추출해낸 반투명한(뭐 RNN 같은 신경망 이론인 경우는 가중치 들이 왜 그런지 사람은 이해하기 힘들 듯 하다) 로직들이 맞는지를 이해하고, 오차의 본질을 이해해 모델을 조정하는 능력이다(원석을 감정하는 능력이라고 할까..). 또 문제를 파악하고 적절한 기법(알고리즘과 모델)을 선택하는 것도 오랜기간은 수학적, 통계적 기법을 잘 아는 사람들의 손을 대부분 필요로 하지 않을까 싶다. 물론 프레임 윅들이 여러가지 시각화 툴을 통해, 해당 작업을 많이 도와줄 것은 같지만 말이다. 또 아마 지금의 장미빛 전망의 끝이 지나면, 자동화된 프로그램이 못하는 분야에 대해 다른 해결을 원하는 새로운 장르가 생겨날 것도 같다(어차피 현재의 로직으로도 해결 못하는 문제는 많으니까. 왠지 머신러닝은 로직을 벗어난 로직이라 그러한 부분을 일부 해결해 줄것은 같다). 

 

  현재 트레닝셋과 검증 셋으로 나누어, 리그레이션 테스트와 비슷하게 신뢰도를 확인 하는 방법은, 뭔가 약간 너무 낙관주의적인면(출처가 같은 트레이닝과 검증 데이터가 무조건 옳다고 가정하는 측면에서)도 있다고 느껴지기 때문에, 수집을 포함한 전체적인 데이터 케어 관점에서 추출된 로직에 대해 테스트 및 검증 하거나, 구성된 시스템의 여러 부분과 보안적인 측면을 검토하는 검증 활동 영역도 활성화 되지 않을까 싶다. 다만 머신러닝으로 추출된 로직은 프로그래머가 만든 로직과 다르게 명확히 알수 없으므로(그래서 앞에서 반투명이라고 표현했다), 모순적으로 알고리즘과 모델을 이해해야 최소한의 검증이 가능하므로 기존에 일반적인 테스팅이나 보안 활동을 하던 사람들이 접근하기는 장벽이 높은 분야 같기도 하다.


  결과적으로 머신러닝은 데이터를 기반으로 현대의 많은 과학 분야가 그렇듯이 완전한 해에 대한 실용적인 근사로 나아가는 로직이다(퍼펙트할수 있는 예외는 일부 게임과 같이 닫힌 세상의 데이터 일 것이다). 프로그래머들이 프로그래밍 언어를 통해 기호로 분리해 놓은 논리 기호들을 녹여 다시 새로운 기호를 만드는 느낌이라고 할까? 그래서 머신러닝을 바라볼때는 수학이나 통계학을 기호의 학문이 아닌 알고리즘을 구성할 레고의 블록(블랙박스적 도구)으로 바라보는 접근법도 어떨까 싶다. 실제로 우리가 파이썬을 할때도 모든 세세한 라이브러리의 내부 로직까지 모르면서 사용하는 것과 비슷하게 말이다.

 

  왠지 헤메고 있는 사람을 더 복잡하게 만드는 어설픈 얘기들을 늘어놨는지도 모르겠지만^^;, 이런저런 블로그를 돌아다니다가 알게되어 읽어봤던 몇 가지 괜찮았던 머신러닝의 '주변영역'을 건드리는 책들을 소개하면서 생각의 나래를 마무리 하려 한다. 혹시 위의 생각에 조금은 일리가 있다고 생각하시는 분들은, 아래의 책들과 함께, 아래 링크의 유투브의 유명한 '모두를 위한 딥러닝 강좌' 를 보시고, 이후 스스로 자기가 갈 공부 방향을 선택하시면 될것 같다.

https://www.youtube.com/playlist?list=PLlMkM4tgfjnLSOjrEJN31gZATbcj_MpUm

 

  • 틀리지 않는 법 : 수학적 사고의 힘 (현실을 심리학, 경제학으로 해석하는 대신 수학으로 해석해 보며 수학적 접근법의 한계도 얘기 한다. 은근 그 과정에서 머신러닝의 한계 또한 생각해 보게 되는 듯 하다)
  • 화이트헤드의 수학이란 무엇인가(앞의 수학 라이브러리의 복소수 파트에 대해 아이디어를 얻은 책이다. 왜 이런 수학 장르가 생겨나게 됬을까를 찬찬히 생각하게 하는 책 같다. 수학과 별로 안 친한 저와 비슷한 레벨이면 이해가 안가서 살짝 넘겨야 되는 수학적 개념들이 좀 있다)
  • 마스터 알고리즘 (머신러닝에 대한 얘기다. 해당 분야를 오래 경험해온 전문가가 세상에는 5종류의 머신러닝에 대한 접근방식들이 있고, 그들을 조화시킬 궁극의 알고리즘이 있을수 있다는 얘기를 한다. 이 책을 읽게 되면 머신러닝 책들이 왜 그렇게 여러 주제의 잡학 사전처럼 구성되어들 있는지 이성적이면서 감성적으로 수긍이 되게된다)
  • 헤드 퍼스트 통계학 (통계를 직관적으로 설명하려는 시도를 한다. 뒤로 갈수록 직관성이 사라지고 수학만 남는 아쉬움이 있다--;)
  • How to solve it (어떻게 문제를 풀어나가느냐에 대한 책이다. 문제 풀이에 대한 접근 방법을 얘기하는 책이고, 학생들에게 어떻게 수학을 가르치느냐에 대해 얘기하는 책이지만, 어려워서 이해가 안가는 문제들이 종종 있음에도 읽을 가치가 있다. 이 수업의 방향이 이 책 영향을 좀 받은듯 하다^^;) 
  • CODE 코드 : 하드웨어와 소프트웨어에 숨어 있는 언어(이건 프로그래밍 공부가 의미 없게 느껴지는 분들을 위해서 덤으로~ 불빛 신호에서 컴퓨터가 만들어지기 까지의 과정을 설명한다. 중간에서 길을 잃어 필름이 끊기더라도 읽어볼 가치가 있다고 본다. 비 전공자한테는 생각보다 어려울지도 모르지만, 해당 책의 저자가 비전공자도 읽을 수 있도록 노력해 쓴 책이고 파이썬 공부를 하고자 하는 의지 정도만 있음 가능할 듯 싶다.)

 

[몸풀기 - 공학 라이브러리의 푸리에 변환]

   머신러닝을 하다 갑자기 이름도 낯선 푸리에 변환이란 것이 나오게되서 이상하게 생각할지 모르겠지만, 이 라이브러리를 소개하는 이유는, 머신러닝 라이브러리가 특별한 형태의 라이브러리라는 생각을 버리게 하고 싶어서 이다. 그리고 지금 소개하는 라이브러리를 잘 보면 왠지 머신러닝 라이브러리랑 닮아있다는 느낌도 있어서 이다. 그래도 맘에 안든다면 opencv 라는 이미지 라이브러리(정확하게 얘기함 컴퓨터 비전 쪽 라이브러리)를 살펴보는 부록 챕터라고 생각해도 좋다.

 

  푸리에 변환은 보통 전자기파 쪽에서 많이 쓰는 이론으로, 저도 수학적으론 잘 모르지만, 모든 주기를 가지는 신호는 사인과 코사인의 합으로 분리해 나타낼 수 있다는 이론이다(우리가 쓰는 스마트폰의 통신들도 이런 이론들 땜에 가능하게 됬다) 그러다보니 분리된 사인, 코사인 함수들의 주기를 보면, 해당 신호가 어떤 주파수(1초에 몇번 지그재그로 움직이나. 2.6G 와이파이의 경우 1초에 2억 6천번 파형이 움직인다)들을 포함하고 있는지 알게된다. 뭐 주기가 없는 신호도 무한대의 주기를 가졌다고 가정해서 변환하는 신묘함도 있다.

 

  근데 묘하게 이걸 그림에 적용하면, 그림에 분포되 있는 명암 요소에 따라서 주파수 영역으로 변환할 수 있다(사실 정확히 설명하기 위해 이리저리 사이트 들을 찾아봤는데, 그림의 평균적인 명암값 으로부터의 차이를 가지고 판단하는지, 경계 값과의 변화정도 인지 기준을 잘 모르겠다. 아시는 분은 댓글에 좀 --;). 여튼 그렇게 그림에 푸리에 변환을 적용해 정리하면, 그림과 동일한 2차원 영역의 주파수 그림이 나오는데, 중심 부분에 가까울 수록 그림에서 주로 면이라고 얘기하는 색이 비슷한 공간속의 점들의 정보를 가지고 있고, 중심에서 멀어질 수록 그림의 외곽선 이라고 부르는 경계를 구분해 주는 점들의 정보를 가지고 있다. 

 

  그래서 그림을 읽어와 푸리에 변환을 해서 나온 네모난 주파수 평면에서, 가운데 원 영역의 값을 0 값으로 덮어 지워버리면(보통 마스킹 한다고 한다. 밑의 그림 처럼 까맣게 정보를 없앴다), 그림의 외곽선 정보만이 남는 효과를 가져오게 된다(약하게 삭제하면 sharpening effect 가 되는듯 하다). 그것은 뭐 포토샵 같은 전문 프로그램에선 더 정밀한 로직을 쓰겠지만, 이 로직을 적용하면 어떤 그림을 적용하든 동일한 윤곽선 추출 효과를 가져온다(마치 학습된 머신러닝 로직이 데이터에 동일한 효과를 가져오는 것처럼 말이다)

 

 

 

  그럼 예제를 보자(요건 머신러닝 예제가 아니라 구글을 헤맨 과정은 생략한다^^). 일단 컴퓨터에 있는 사진 등을 복사해도 좋고 적당한 이미지가 없다면 무료 사진 사이트인 https://pixabay.com/ 로 가서 이미지를 하나 다운 받아서, c:\python\code 폴더에 mypic.jpg 라고 저장한다.

 

  그리고 opencv 를 설치해야 되는데 불행하게도 python 3.5 버전은 pip 인스톨이 안되는 듯하다. https://www.solarianprogrammer.com/2016/09/17/install-opencv-3-with-python-3-on-windows/ 사이트에서 안내하는 데로, http://www.lfd.uci.edu/~gohlke/pythonlibs/ 사이트로 가서, opencv_python-3.2.0-cp35-cp35m-win_amd64.whl 를 다운받아(가끔 파일이 이름이 바뀌어서 그런데 cp35 가 파이썬 3.5란 의미이고, amd64가 64비트 윈도우이다.) c:\python\code 에 넣고, 아래와 같은 명령어로 whl 파일을 설치했다. whl 파일이 무언가는 예전 시간에 설명했다~

(참고로 어떤 블로그 보다보니 python 3.6 버전에서는 pip 로 그냥 설치가 되는걸 보긴 했다)

 

c:\Python\code>pip install opencv_python-3.2.0-cp35-cp35m-win_amd64.whl
Processing c:\python\code\opencv_python-3.2.0-cp35-cp35m-win_amd64.whl
Installing collected packages: opencv-python
Successfully installed opencv-python-3.2.0

 

 

 그럼 예제를 위해 만든 소스를 구글에서 'python opencv fft low pass filter image' 라고 찾아서, 돌려보다 보니 python 2를 기준으로 만든거라서 나누기 코드 부분에서 문제가 생겨서(나누기 후 결과를 integer 로 취급할거냐 float 로 취급할거냐가 버전이 올라가면서 달라졌다), 아래의 사이트를 찾아, 코드를 조금 수정했다.

[소스 사이트]

http://docs.opencv.org/3.0-beta/doc/py_tutorials/py_imgproc/py_transforms/py_fourier_transform/py_fourier_transform.html

 

[에러 메시지와 해결 사이트]

TypeError: slice indices must be integers or None or have an __index__ method 에러

https://stackoverflow.com/questions/28272322/typeerror-slice-indices-must-be-integers-or-none-or-have-an-index-method

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import cv2
import numpy as np
from matplotlib import pyplot as plt
 
# 이미지를 읽어옴
img = cv2.imread('mypic.jpg',0)
 
# 푸리에 변환 하고 이런저런 맞춤
= np.fft.fft2(img)
fshift = np.fft.fftshift(f)
magnitude_spectrum = 20*np.log(np.abs(fshift))
 
# 작은 마스크를 만들어서 푸리에 변환한 영역의 가운데를 지워 버림(하이패스 필터)
rows, cols = img.shape
crow,ccol = rows//2 , cols//2
fshift[crow-30:crow+30, ccol-30:ccol+30= 0
f_ishift = np.fft.ifftshift(fshift)
 
# 다시 이미지로 역변환
img_back = np.fft.ifft2(f_ishift)
img_back = np.abs(img_back)
 
# 그림으로 보여줌.
plt.subplot(131),plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(132),plt.imshow(magnitude_spectrum, cmap = 'gray')
plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
plt.subplot(133),plt.imshow(img_back, cmap = 'gray')
plt.title('Image after HPF'), plt.xticks([]), plt.yticks([])
 
plt.show()
 
print ("ok")
 
cs

 

 

  언제나 처럼 c:\python\code 폴더에 'python opencv_fft_sample.py' 란 이름으로, 파일형식은 '모든 파일'을 선택하고,  인코딩은 'utf-8' 로 저장하고 실행한다.

c:\Python\code>python opencv_fft_sample.py
ok

 


  위와 같이 그림이 세개 나오는데 왼쪽이 원본 이미지, 가운데의 빛이 가운데 모인듯한 이미지가 푸리에 변환을 통해 주파수 영역으로 변환된 이미지, 오른쪽이 주파수 영역의 가운데 데이터를 삭제하여(코드안에 로직이 있다). 다시 이미지로 복구하였을때, 주로 경계선 요소들만 남기고 평면 요소들이 사라진 경우이다.

 

  보시면 푸리에 변환이 왠지 머신러닝과 비슷한 느낌이 들지 않는가?(머신러닝은 사실 데이터를 적절히 해석해 내부 특징을 얻을 수 있는 모든 알고리즘 들을 가져다 쓴다고도 할수 있다) 차이라면 데이터에서 로직을 추출하진 않는다. 하지만 데이터를 넣으면(디지털 이미지 또한 좌표 기반의 데이터 이다) 임의의 처리를 해서 데이터에 특정한 특징을 노출해준다는 부분에서 왠지 비슷하다는 느낌이 들어 굳이 이 시점에 소개했다. 그럼 실제 머신러닝 로직으로 가보자.

 

 

 

 

[최소제곱법 이란]

  많은 머신러닝 및 데이터 분석 책에서, 제일 처음에 나오는게 회귀분석이고(과거의 데이터를 기반으로 미래의 데이터를 예측함), 그 중 가장 먼저 나오는게 최소 제곱법 같다(이후는 최우추정법, 베이즈 같은 낯선 이론들이 나온다). 디테일한 의미를 다 알진 못하지만 개념적으로만 근사한다면, 해당 컨셉은 제공되는 데이터들로부터 가장 가까운 어떤 선(직선 or 곡선 : 사실 직선은 곡선의 한 형태 일 것이지만)을 찾기위해서, 선과 데이터 들의 수직 거리를 제곱한 값들의 합이 제일 작아지는 선을 나타내는 방정식의 상수들을 찾는 것이다. 즉 직선이라고 가정한다면 y = ax + b 의 a, b 값을 계속 특정 방향으로 조금씩 변화해 가면서, 각 점과의 거리를 제곱한 합이 제일 작아지는 지점을 찾는다(엑셀의 추세선이라고 봐도 될것 같다). 뭐 자세한 내용은 관련 책이나 블로그 등에서 보시라고 하고 살짝 넘어간다 --; 

    

 

 


[numpy 에서의 최소제곱법 구현]

  원래 numpy 는 계획에 없었는데, 찾다보니 있어서 다른 라이브러리와 비교를 위해서 넣게 되었다.구글에서 'linear least square fit python' 라고 찾아 아래의 페이지를 찾는다.

https://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.lstsq.html

 

  이후 적당히 편집하여, y = 3x + 1 의 공식에 맞도록 x, y 값을 바꿔 넣었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import numpy as np
import matplotlib.pyplot as plt
 
# x, y 좌표 지정(y = 3x + 1), 방정식 모델 지정
= np.array([1257])
= np.array([471622])
= np.vstack([x, np.ones(len(x))]).T
 
# 선형대수 라이브러리의 least squre 호출
slope, intercept = np.linalg.lstsq(A, y)[0]
print("기울기:", slope, ", 절편:", intercept)
 
# 기존 값을 점으로, 찾은 기울기를 선으로 그린다.
plt.plot(x, y, 'o', label='Original data', markersize=10)
plt.plot(x, slope*+ intercept, 'r', label='Fitted line')
plt.legend()
plt.show()
cs

 

  앞 시간의 예제들을 시연해 봤다면 이미 라이브러리들을 설치되어 있을테니(아니라면 설치 방법은 15, 16교시를 참고한다), c:\python\code 폴더에 'numpy_least_sample.py' 이름으로, 파일형식은 '모든 파일'을 선택하고,  인코딩은 'utf-8' 로 저장하고 실행한다(지금보니 utf-8 로 저장하면 한글 주석이 달리는 경우에도 상단에  '#-*- coding: utf-8 -*-' 를 안 넣어도괜찮다). 정상적으로 해를 찾아 기울기 3, 절편 1 을 찾게 된다.

 

c:\Python\code>python numpy_least_sample.py
기울기: 3.0 , 절편: 1.0

 

 

 

 

[scipy 에서의 최소제곱법 구현]

  이번에는 과학 라이브러리인 scipy 를 이용해 보자. 구글에 'linear least square fit python scipy' 라고 찾아 아래의 2개 페이지를 찾아 적당히 믹스 했다. 전체적인 흐름은 numpy 와 거의 같다고 보면 될 듯 하다.

http://www2.warwick.ac.uk/fac/sci/moac/people/students/peter_cock/python/lin_reg/
https://docs.scipy.org/doc/scipy-0.19.0/reference/generated/scipy.stats.linregress.html

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
 
# 데이터 지정
= np.array([1257])
= np.array([471622])
 
# 데이터에 맞는 값 찾기, Slope: 기울기, Intercept: 절편
slope, intercept, r_value, p_value, std_err = stats.linregress(x,y)
print ("Slope and intercept", slope, intercept)
print ("R-squared", r_value**2)
 
# 데이터를 점으로, 찾은 선과 같이 화면에 표시
plt.plot(x, y, 'o', label='original data')
plt.plot(x, intercept + slope*x, 'r', label='fitted line')
plt.legend()
plt.show()
cs

 

  c:\python\code 폴더 'scipy_least_sample.py' 이름으로, 파일형식은 '모든 파일'을 선택하고,  인코딩은 'utf-8' 로 저장하고 실행한다.

c:\Python\code>python scipy_least_sample.py
Slope and intercept 3.0 1.0
R-squared 1.0

 

  뭐 결과가 같으니 이미지도 동일하다.

 

 

 

 

[tensorflow 에서의 최소제곱법 구현]

  마지막으로 tensorflow 인데, 운이 좋게도 금년에 텐서플로우가 64비트 윈도우 버전의 python 3.5 까지 지원하게 되서 시연이 가능하게 됬다. 근데 뭐 아직 윈도우즈는 텐서플로우에게 관심의 바깥 같은 느낌이 좀 들어서, 텐서플로우를 사용하는 사람들은 보통 리눅스나 맥환경에서 많이 쓰는 것은 같다. 

 

  먼저 pip 를 이용해서 텐서플로우를 설치한다(꼭 윈도우 64비트에, 파이썬 3.5 대 버전이여야 가능하다!. 구글을 찾다 보니 새로 빌드를 해서 32비트에 설치해 쓰는 사람들도 있다고는 한다)

c:\Python\code>pip install tensorflow
Collecting tensorflow
  Downloading tensorflow-1.2.0-cp35-cp35m-win_amd64.whl (21.2MB)
...
Successfully installed backports.weakref-1.0rc1 bleach-1.5.0 html5lib-0.9999999 markdown-2.2.0 protobuf-3.3.0 tensorflow-1.2.0 werkzeug-0.12.2 wheel-0.29.0

 

 

  이후 구글에 'tensorflow least squares matplotlib' 라고 검색하여 아래의 두 페이지를 찾았다.

https://gist.github.com/tomonari-masada/ed2fbc94a9f6252036eea507b7119045
https://github.com/aymericdamien/TensorFlow-Examples/blob/master/examples/2_BasicModels/linear_regression.py

 

  위의 페이지의 예제가 좀더 간단하게 보이지만 텐서플로우에서 생성된 변수(x, y_)를 matplotlib 의 넘겼을때 plot 에서 타입 에러가 난다. numpy 리스트 형태를 넘겨야 괜찮다. 그리고 두번째 예제가 좀 모범답안 같이 전체적인 사용 플로우를 잘 보여주는거 같아서 두번째 예제에서 훈련 후 검증 테스트를 하는 코드 부분만 제거하고, 적당히 편집하면 아래와 같다. (상세 로직은 어차피 텐서플로우에서 하라는데로 하는거니, 전체적인 코드의 흐름을 보자 )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import tensorflow as tf
import numpy
import matplotlib.pyplot as plt
rng = numpy.random
 
# 파라매터들 변수 조정 수치, 전체 실행 수, 몇 번마다 화면에 로그를 보여주는지
learning_rate = 0.01
training_epochs = 1000
display_step = 50
 
# 훈련용 데이터 지정
train_X = numpy.asarray([1257])
train_Y = numpy.asarray([471622])
n_samples = train_X.shape[0]
 
# 텐서플로우 변수들 만들기
= tf.placeholder("float")
= tf.placeholder("float")
 
= tf.Variable(rng.randn(), name="weight")
= tf.Variable(rng.randn(), name="bias")
 
# 모델 만들기 'y = Wx + b' 를 정의한다.
pred = tf.add(tf.multiply(X, W), b)
 
# least square 공식을 이용하여 최소값을 만들 요소를 지정한다. 
cost = tf.reduce_sum(tf.pow(pred-Y, 2))/(2*n_samples)
 
# 기울기를 보정하는 경사하강법이란것을 사용하고, 비용을 최소화 하는 방향으로 학습 한다.
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost)
 
# 초기화?
init = tf.global_variables_initializer()
 
# 텐서플로우 기동
with tf.Session() as sess:
    sess.run(init)
 
    # 데이터 넣기
    for epoch in range(training_epochs):
        for (x, y) in zip(train_X, train_Y):
            sess.run(optimizer, feed_dict={X: x, Y: y})
 
        # 50번 마다 로그 뿌려서 찾는 값 변화를 보여 줌.
        if (epoch+1) % display_step == 0:
            c = sess.run(cost, feed_dict={X: train_X, Y:train_Y})
            print("Epoch:"'%04d' % (epoch+1), "cost=""{:.9f}".format(c), \
                "W=", sess.run(W), "b=", sess.run(b))
 
    # 완료 되면 결과 출력
    print("Optimization Finished!")
    training_cost = sess.run(cost, feed_dict={X: train_X, Y: train_Y})
    print("Training cost=", training_cost, "W=", sess.run(W), "b=", sess.run(b), '\n')
 
    # 찾은 결과 그래프로 보여주기
    plt.plot(train_X, train_Y, 'ro', label='Original data')
    plt.plot(train_X, sess.run(W) * train_X + sess.run(b), label='Fitted line')
    plt.legend()
    plt.show()
cs

 

    c:\python\code 폴더에 'tensorflow_least_sample.py' 이름으로, 파일형식은 '모든 파일'을 선택하고,  인코딩은 'utf-8' 로 저장하고 실행한다.

 

c:\Python\code>python tensorflow_least_sample.py
Epoch: 0050 cost= 0.031498514 W= 3.08722 b= 0.53246
Epoch: 0100 cost= 0.023787469 W= 3.07587 b= 0.593684
Epoch: 0150 cost= 0.017966598 W= 3.06593 b= 0.64688
Epoch: 0200 cost= 0.013570149 W= 3.0573 b= 0.693111
Epoch: 0250 cost= 0.010249456 W= 3.0498 b= 0.73329
Epoch: 0300 cost= 0.007741358 W= 3.04328 b= 0.768208
Epoch: 0350 cost= 0.005847037 W= 3.03761 b= 0.798554
Epoch: 0400 cost= 0.004416286 W= 3.03269 b= 0.824928
Epoch: 0450 cost= 0.003335610 W= 3.02841 b= 0.847849
Epoch: 0500 cost= 0.002519361 W= 3.02469 b= 0.867768
Epoch: 0550 cost= 0.001902884 W= 3.02146 b= 0.88508
Epoch: 0600 cost= 0.001437244 W= 3.01865 b= 0.900125
Epoch: 0650 cost= 0.001085538 W= 3.01621 b= 0.913202
Epoch: 0700 cost= 0.000819894 W= 3.01409 b= 0.924565
Epoch: 0750 cost= 0.000619259 W= 3.01224 b= 0.934442
Epoch: 0800 cost= 0.000467722 W= 3.01064 b= 0.943025
Epoch: 0850 cost= 0.000353270 W= 3.00925 b= 0.950485
Epoch: 0900 cost= 0.000266819 W= 3.00804 b= 0.956967
Epoch: 0950 cost= 0.000201530 W= 3.00698 b= 0.962601
Epoch: 1000 cost= 0.000152215 W= 3.00607 b= 0.967497
Optimization Finished!
Training cost= 0.000152215 W= 3.00607 b= 0.967497

 

  데이터를 보면 1000번을 찾으면서 50번마다 화면에 현재 상태를 출력하라고 했으니 (1000/50) 번 해서 20개의 로그가 화면에 표시된 것을 볼수 있다. Epoch(찾기 반복 횟수), cost 및 optimizer(해 찾기의 목표가 되는 기준 값과, 기준 값을 어떻게 사용할 것인지. 여기서는 최소제곱값 함수가 가장 작아지는 값을 기울기 하강법으로 찾는다). W(기울기) b(절편) 값의 변화를 보면 점점 횟수가 늘수록 기울기와 절편 값들이 의도했던 3과 1로 수렴해 가는 것을 볼 수 있다.

 

  이렇게 비교해 보면 뭔가 전문적인 머신러닝 라이브러리들은 위의 numpy, scipy 같은 일반 선형대수 라이브러리 처럼 결과만 그냥 보여주기 보다는, 모델을 지정하고(pred = tf.add(tf.multiply(X, W), b)), 잘된 학습의 판단기준도 정하고(cost = tf.reduce_sum(tf.pow(pred-Y, 2))/(2*n_samples)), 학습 과정을 살펴보며(로그) 조정하는 듯 체계적인 접근 방식을 유도하고 있다. 뭐 제가 모르는 다른 기능 들도 많겠지만, 꼭 개발쪽의 유닛테스트 프레임윅이 하는 역활과 비슷한 느낌이 든다(저런 프레임윅들은 관련 기능과 함께 조금 논란의 여지는 있지만 'best practice' 를 동시에 제시한다)

 

  matplotlib 으로 그린 그래프 화면은 원래 정답(3, 1)과 비교함 소수점 약간 차이라서, 앞의 2개 그래프와 구분이 안되서 생략했다. 설명의 일관성을 위해 정답을 제한해 놓은게 맘에 안드신다면, 아래와 같이 소스에서 train_X, train_Y 의 배열 값들을 임의의 값으로 바꾸거나 배열 데이터 갯수를 늘여보면 좀더 점들의 중심에 위치하고자 하는 선의 모양을 볼 수 있을 듯 하다. 머신러닝 및 텐서플로우에 이론과 기능에 대한 더 깊은 내용들은 관련 커뮤니티의 강좌나 책을 참고하시면 될듯 하다.

1
2
train_X = numpy.asarray([135912])
train_Y = numpy.asarray([45101813])
cs

 

 

 

[마치면서]

  자 그럼 마무리를 위해 먼 길을 돌아 처음 목적으로 돌아갈 시간이 됬다. 머신러닝에서의 파이썬의 역활은 무엇일까? 개인적인 생각으로는 numpy, panda, matplotlib 과 같이 입출력 데이터를 선, 후처리 할 수 있는 기능을 머신러닝 라이브러리들에게 무료로 제공해 주며, 더 나아가서 세계 공용어인 영어의 역활과 비슷하게 머신러닝과 관련된 여러 관련 라이브러리들이 서로 대화와 협력을 나눌 수 있도록 중재하는 역활을 한다고 하고 싶다. 아마 다른 언어에 의해서 뒤집어 지긴 힘들듯한 이런 중요성 때문에 머신러닝을 배우려는 분들은 꼭 파이썬을 조금씩, 하지만 꾸준히 관심을 가지는게 맞을 듯 싶다.

 

  그리고 다시 한번 얘기하지만 머신러닝 라이브러리는 최근 화려한 스포트라이트와 지원을 받고 있어 그렇지, 어쩌면 원래는 일반 공학 라이브러리와 같은 평범한 라이브러리 출신 일지도 모른다. 그래서 아마도 현재 유행되는 머신러닝 장르 이외에도 데이터의 특별한 측면을 노출해주는 기존의 평범한 알고리즘들이 재조명되어 새로운 머신러닝의 장르를 열수도 있을 것이다. 

 

  또한 앞의 푸리에 변환 라이브러리의 사용과 같이, 사용자가 가진 라이브러리의 동작 원리와 적용 대상에 대한 이해의 깊이에 따라서 그 유용성이 달라지게 된다. 그러므로 머신러닝 프레임윅들의 사용법이나 트레이닝, 오버피팅, 모델, 학습율, 초기값, 오차 등의 방법론들에 집중하는 것도 좋지만, 그것은 어쩌면 그림자만을 쫓는 행위일수도 있으므로, 파이썬을 공부할 때와 마찬가지로 그 기술의 배경이 되는 여러가지의 주변 요소들에 대해서도 교양이라고 생각하고 꾸준히 관심을 가지도록 해보자. 모든 진실은 데이터 안에 숨어있다는 것을 잊지말고...(데이터도 가끔 거짓말을 하긴 하지만 말이다.)

 

 

 

[보충]

1) 이런저런 블로그를 보다보니 푸리에 변환도 컴퓨터 비전 측면에서 머신러닝 쪽에 포함된다고 하는 얘기가 있다. 그럼 예제가 엉뚱한건 아니게 되서 더 다행이다~

 

2) 한 분이 numpy 예제를 보고 "파이썬에선 함수가 2개의 리턴이 가능해요?" 하고 물어보셨다. 그러려니 하고 별 생각 없이 넘어갔던 저였지만--;, 궁금해서 함 찾아봤다. google 에서 'python function multiple return' 으로 찾으면 여러 스타일이 나오는데, 가장 간단한 샘플을 보인다.

https://stackoverflow.com/questions/354883/how-do-you-return-multiple-values-in-python

1
2
3
4
def f():
    return 12
x, y = f()
print ("x = ",x, ",y = ", y)
cs

 

c:\Python\code>python return_test.py
x =  1 ,y =  2

 

3) 아래 글도 괜찮은 듯 해서 참고 자료로 링크를 건다.

머신러닝속 수학(번역)

https://mingrammer.com/translation-the-mathematics-of-machine-learning

 

 

2017.6.25 by 자유로운설탕
cs

 

  

 

 

 

 

 

 

posted by 자유로운설탕