콘텐츠로 건너뛰기
Home » Python » 페이지 6

Python

한글의 음소분리 문제

한글 문자열의 초/중/종성을 분리하는 예제를 포스팅한 적이 있는데, 이 때는 미처 알지 못했지만, 중요하게 놓친 문제가 있다. 그러니까 초/중/종성에 해당하는 자모와 각각의 낱자가 다른 글자라는 점이다.

예를 들어 ‘한'(U+D55C)자를 보자. 이 글자는 ‘ㅎ’, ‘ㅏ’, ‘ㄴ’ 의 세가지 자모로 분리된다. 이 때 ‘ㅎ’이 초성일 때와 종성일 때에는 같은 ‘ㅎ’으로 보이기는 해도, 같은 코드가 아니다. (그리고 지금 ‘ㅎ’으로 낱자로 쓰고 있는 이 글자 역시 같은 코드가 아니다.

‘ㅎ’을 표현하는 방식에는 다음 세 가지 방식이 있다.

  1. 낱자로서의 ‘ㅎ’ (U+314E)
  2. 초성 자모로서 ‘ㅎ’ (U+1112)
  3. 종성 자모로서 ‘ㅎ’ (U+11C2)

이는 유니코드에서는 완성형과 조합형 한글을 모두 지원하기 때문이다. ‘가’에서부터 ‘힣’에 이르는 자모로 조합가능한 모든 한글글자는 Hangul Syllables 블럭에 정의되어 있다. 그리고 이러한 Syllables를 조합하는데 사용되는 자모들은 모두 Hangul Jamo에 정의된다. 낱자로서의 자모는 Hangul Compatibility Jamo 에 정의되어 있다. 기존의 초성 구하기 코드에서 구하는 답은 자모 코드에 해당한다.

이 자모 코드는 기본적으로 서로 결합하여 완성된 글자(Hangul Syllables)를 구성하는데 쓰인다. 따라서 자모 문자의 코드값으로부터 문자를 얻어서 출력했을 때에는 각 자모 낱자의 출력결과와 구분할 수 없기는 하나 정확한 답이 아닌 것이다.

더 보기 »한글의 음소분리 문제

[Python] 클래스 이해하기

클래스를 설명할 때 흔히 쓰는 표현은 ‘클래스는 거푸집에 해당하고 객체는 그 거푸집으로 찍어내는 벽돌에 해당한다.’는 것이다. 물론 완전히 틀린 설명은 아닌데, 이 개념에서 출발해서 클래스를 이해하는 것은 객체와 클래스의 관계와 클래스를 어떻게 다룰 것인지 등 여러 관점을 정립하는데 많은 어려움을 유발한다.

이 글은 파이썬 초보자들이 클래스에 대해 접근하고 이해하는데 도움을 주고자 작성됐다.

모든 것은 객체이다.

파이썬에서 통용되는 가장 중요한 대전제는 모든 것이 객체라는 것이다. 1, 2와 같은 숫자값도 C처럼 원시값이 아니라 int 타입의 객체이다. 함수 역시 객체이고 모듈이나 패키지도 객체처럼 취급된다. 모든 것이 객체라면, 클래스 그 자체도 객체라는 말이된다. 그럼 이 시점에서 다시 한 번 되물어보자. 도대체 객체란 무엇인가?

더 보기 »[Python] 클래스 이해하기

Lock을 사용하는 스레드 동기화 방법

아래는 어떤 “counter”라는 자원을 두 스레드가 동시에 사용하려할 때, Lock을 사용하는 상황을 시각적으로 묘사한 것입니다. 두 워커 스레드 A, B 는 자원에 접근하기 전에 Lock을 획득하려고 시도합니다. 두 스레드 모두 락 객체의 .acquire()를 호출합니다. 이 때 (아마도 간발의 차이로) A 가 락을 획득하게 되었다고 가정하면, A에서 호출한 .acquire()는 즉시 리턴되어 A는 다음 코드를 진행하게 되고 여기서 counter를 사용합니다. 반면 B의 .acquire() 호출은 락을 획득할 때까지 대기하기 때문에 B의 진행 흐름은 여기서 멈추게 되고, A가 자원을 쓰는 동안 . . .… 더 보기 »Lock을 사용하는 스레드 동기화 방법

ZMQ + Asyncio 적용하기

파이썬에서 ZMQ를 사용할 때, asyncio를 사용할 수 있게 되었다. asyncio에 적용한다고 해서 크게 달라지는 것은 없고 소켓의 사용방법은 대동소이하다. (실제 IO 시점에 작업 전환이 일어날 수 있게 await를 붙이는 것 정도의 차이만 있다. 대략의 사용법을 정리해보면 다음과 같다.

더 보기 »ZMQ + Asyncio 적용하기

Selector를 사용한 소켓 멀티플렉싱

소켓을 사용하여 간단한 서버를 만들 때에는 서버 소켓을 생성하고, 이를 특정한 네트워크 포트에 바인드한 다음, listen() 메소드를 사용해서 해당 포트로 들어올 수 있는 접속 대기열의 크기를 지정합니다. 그런 다음 해당 소켓의 accept() 메소드를 사용해서 클라이언트 소켓을 생성하고, 이 클라이언트 소켓을 통해 클라이언트가 보낸 요청을 읽고, 그에 대한 응답을 보내게 됩니다.

서버 소켓은 클라이언트가 접속할 때마다 ‘서버가 사용하는 클라이언트 소켓’을 따로 생성하고 실제 통신은 두 클라이언트 소켓 사이의 peer-to-peer 방식의 대화가 됩니다. 따라서 하나의 서버 소켓은 여러 클라이언트의 접속을 받을 수 있습니다.

만약 다중 접속을 허용하는 소켓 서버를 파이썬에서 구현한다면 가장 쉬운 방법은 스레드를 사용하는 것입니다. 클라이언트 소켓을 인자로 받는 핸들러 함수를 하나 작성하고, 서버 소켓의 accept() 메소드가 리턴하는 시점에 핸들러 함수에게 클라이언트 소켓을 주고 새로운 스레드에서 작동하도록 시작해주면 됩니다.

여기까지의 작동 모델은 ‘동기식 소켓’을 사용합니다. 동기식 소켓은 send(), recv(), accept() 등의 동작이 모두 블럭되는 소켓입니다. 따라서 스레드가 소켓의 입출력을 기다리는 동안에는 다른 일을 할 수가 없습니다. 그래서 서버 소켓과 클라이언트 소켓들이 동시에 작동할 수 없으니 스레드를 사용하는 것이겠죠.

소켓 라이브러리는 이와 다른 비동기 소켓을 지원하고 있습니다. 비동기 소켓은 소켓을 바인딩하기 전에 sock.setblocking(False)를 명시해서 블록킹 모드를 논블록킹으로 변경해줍니다. 이렇게 만들어진 비동기 소켓을 소켓 API만으로 사용할 수는 없습니다. Python How To 문서는 select.select() 를 사용할 것을 추천합니다만, 이는 문서가 오래되었음을 감안해야 하며 실제 파이썬 공식문서는 보다 고수준으로 설계되어 사용하기 쉬운 selectors 모듈을 쓸 것을 추천하고 있습니다.

이 글에서는 selectors 모듈을 사용하여, 단일 스레드에서 하나의 소켓 서버가 여러 클라이언트의 요청을 처리하는 멀티플렉싱을 어떻게 구현하는지 소개하며, 셀렉터 사용 방법에 대해서 살펴보겠습니다.

더 보기 »Selector를 사용한 소켓 멀티플렉싱

ZMQ 예제 – Poller를 사용하여 종료 시점을 동기화하기

하나의 ZMQ 소켓은 여러 포트에 바인드하거나 커넥트할 수 있어서, 1:N의 연결을 쉽게 구성할 수 있습니다. 하지만 어떤 경우에는 이 다중 접속이 두 개 이상의 소켓을 사용하는 경우도 있습니다. 이런 경우 두 개의 소켓을 동시에 듣는 방법이 필요합니다. ZMQ소켓의 recv() 메소드는 블럭킹 함수이기 때문에 2개 이상의 소켓 중 데이터가 들어온 소켓을 처리하기 위해서는 소켓만으로는 처리할 수 없습니다. ZMQ는 이런 상황에 사용할 수 있는 Poller라는 수단을 제공합니다.

더 보기 »ZMQ 예제 – Poller를 사용하여 종료 시점을 동기화하기

Asyncio – 네트워크 입출력을 위한 스트림

asyncio는 네트워크 입출력을 위한 스트림이라는 타입을 제공하고 있다. 스트림은 네트워크 연결을 만들거나(클라이언트의 connect 동작) 서버를 시작하고(서버의 bind/listen 동작) 연결이 생성되면 해당 연결을 처리하는 핸들러의 인자로 넘겨지게 된다. 비동기 코루틴인 각각의 핸들러는 이 스트림을 이용해서 데이터를 읽거나 전송할 수 있다.

입출력 스트림은 내부적으로 소켓을 감싸고 있으며, 노출되는 API를 통해서 소켓을 기다리고 읽고 쓰는 일련의 작업을 상당히 고수준의 레벨에서 제공해주고 있다.

더 보기 »Asyncio – 네트워크 입출력을 위한 스트림

스레드의 시작 시점을 동기화하기

동시성 프로그래밍에서 동기화는 주로 한정된 자원을 두고 여러 스레드가 경쟁하지 않도록 락이나 세마포어를 사용해서 특정한 자원을 액세스하는 시점에서는 여러 스레드가 순차적으로 실행하도록 하는 것에 초점을 맞추고 있다. 하지만 이 외에도 각각의 스레드가 각자가 담당한 작업을 처리하기 위해 준비를 마치고, 다른 스레드의 준비를 기다렸다가 동시에 시작하도록 하는 기법도 필요하다. 이렇게 여러 스레드를 특정한 지점에서 기다리게 한 후 한 번에 깨워서 동시에 시작하게 하는 용도로 사용되는 동기화 프리미티브로는 이벤트와 배리어가 있다.

이런 기법이 가장 흔히 사용되는 경우로는 소켓 서버와 클라이언트를 하나의 스크립트에서 구현해서 스레드로 돌게 할 때이다. 서버의 소켓이 준비되기 전에 클라이언트들이 서버에 connect 될 수 없기 때문이다.

이벤트는 가장 단순한 동기화 프리미티브 중 하나로, 동시에 시작해야 하는 여러 스레드들이 “출발선”에서 이벤트 객체의 .wait() 메소드를 호출하고 대기상태에 들어가도록 한다. 그리고 어느 한 스레드에서 해당 이벤트 객체의 .set() 을 호출하면 해당 이벤트를 대기 중인 모든 스레드에서 wait() 메소드가 리턴되면서 각 스레드가 동시에 시작될 수 있다.

배리어도 비슷하게 여러 스레드를 기다리게하다 한 번에 깨우는 장치인데, 마치 정원이 다 차면 바로 출발하는 버스처럼 작동한다. 즉 이벤트를 기다리는 스레드들은 누군가가 깨워줘야 하는 것에 비해, 배리어 정해진 개수만큼의 스레드가 대기하게 되면 자동으로 해제되면서 동시에 깨어나게 된다.

더 보기 »스레드의 시작 시점을 동기화하기