파이쿠차 -오랜만의 파이썬 마을 모임!

다음 주(7월 27일-8월 2일)에 오랜만에 애자일 개발 워크샵 일로 1주일 동안 서울에 머물게 됐습니다. 그래서 서울 방문 기념으로 거의 1년 만에 파이썬 마을 모임을 한 번 하려고 합니다.

지난 번에 다른 행사의 부대행사로 파이썬 관련 세션을 하나 생각하면서, 자기가 파이썬을 쓰는 분야에 대해서 얘기하고 토론하는 걸 좀 생각해 봤었는데요. 최근에 페차쿠차라는 걸 보고 한 번 "내가 파이썬을 쓰는 곳/파이썬으로 만든 것"을 페차쿠차 형식으로 한 번 해 보면 어떨까 생각했습니다.

페차쿠차는 발표 포맷이 정해져 있는데요, 슬라이드 20장을 각 20초 씩 해서 총 6분 발표를 해야합니다. 보통 많은 사람들이 발표하는 곳 가면 거의 반 정도는 발표하는 사람을 제발 끌어내리고 싶은 유혹을 받는데 그런 걸 줄이기 위한 포맷이라고 하네요. 그래서 이번에는 20장은 보통 엄청 오래 한 분 아니라면 회사 기밀까지 나와야할 것 같아서, 12장 * 20초 해서 4분으로 하면 좋을 것 같네요. 중요한 것은 슬라이드는 자동으로 넘어가니까 한 슬라이드에서 시간을 마음대로 조절해서 쓸 수는 없다는 게 페차쿠차의 기본 원리라고 합니다. (다양한 분야에 활용하시는 분들이 참석하셔서 경험을 나누시면 좋겠습니다!)

혹시 장소나 시간에 대해서 좋은 생각이나 제안 (또는 장소제공) 등 남기실 말씀이 있는 분들은 알려주세요. ^^ 대략 인원 추산도 해야하니 참석하실 수 있는 분들 답글 남겨주시면 고맙겠습니다~

댓글 15 개 | 트랙백 0 개 (보낼곳) | 태그 python


인스턴스 복사를 직접 다루기

종종 한 인스턴스를 대량으로 복제해서 약간씩 바꿔야할 일이 있습니다. 주로 진화 알고리즘 계열이나 고치기 전/후를 비교해야하거나, 엄청나게 바꾼 뒤에 롤백을 해야한다는 등의 경우가 그런데요. 파이썬에서 복사는 기본 타입들은 각각 독특한 방법이 있지만, 보통은 copy모듈을 쓰면 간단합니다.

>>> x; copy.copy(x)
<__main__.X instance at 0x83992ec>
<__main__.X instance at 0x839970c>

그런데 철학적(?)인 분위기를 잡으며 경험이 조금 더 많은 사람이 쓱 나타나서 "너 깊은 복사랑 얕은 복사 알아?"라고 할 것 같은 느낌이 언제나 copy를 쓸 때마다 들곤 하죠; 깊은 복사(deep copy)는 참조하고 있는 객체 모두를 복사하기 때문에 속성으로 들어있는 놈들까지 별개의 인스턴스가 돼서 완전히 떨어집니다. 얕은 복사에서는 참조는 복사하지 않고 자신만 복사하기 때문에 리스트같은 것들이 공유가 되는 특징이 있습니다. 물론 얕은 복사가 훨씬 빠르고 자원을 효율적으로 쓰겠죠.

그런데 종종 클래스를 만들다 보면 어떤 것은 깊숙히, 어떤 것은 얕게 해야할 일이 발생하는데요. 그러면 copy.copy를 쓸 수도 없고 copy.deepcopy를 쓸 수도 없고 애매한 상황이 됩니다. 결국은 얕은 복사를 한 다음에 수동으로 깊은 복사를 해 주면 되겠죠. 그래서 좀 더 복사 과정을 자세히 다뤄보도록 하겠습니다.

우선 복사를 하려면 인스턴스를 새로 하나 만들어야 합니다. 여기서 발생하는 문제! __init__가 호출되면 복사라고 부르기가 애매하겠죠. 이미 초기화된 놈을 다시 초기화하게 되니까.. 그래서 __init__는 호출하지 않고 그냥 객체만 생성하는 방법을 써야하는데, 신형 클래스(new style class)냐 구형 클래스(old style class)냐에 따라서 방법이 다릅니다.

신형 클래스는 __new__를 쓰면 됩니다.

>>> class S(object):
...     def __init__(self): print "YAY!!!"
... 
>>> s = S()
YAY!!!
>>> s
<__main__.S object at 0x8399bec>
>>> s.__new__(S)
<__main__.S object at 0x8399b2c>

구형 클래스에서는 types.InstanceType을 쓰거나 types 모듈 불러오기가 귀찮으면 type(s)를 써서 만들 수 있습니다.

>>> class S:
...     pass
... 
>>> s = S()
>>> type(s)(S)
<__main__.S instance at 0x8399aac>
>>> s
<__main__.S instance at 0x8399a6c>

그래서 일부만 복사하는 놈을 만들어 보면

>>> class S(object):
...     def copy(self):
...             newobj = S.__new__(S)
...             newobj.a = self.a[:]
...             newobj.b = self.b
...             return newobj
... 
>>> s1 = S()
>>> s1.a = [1,2,3,4,5]
>>> s1.b = [6,7]
>>> s2 = s1.copy()
>>> s2.a[0] = -1
>>> s2.b[0] = -2
>>> s1.a, s1.b
([1, 2, 3, 4, 5], [-2, 7])
>>> s2.a, s2.b
([-1, 2, 3, 4, 5], [-2, 7])

이렇게 a는 복사되고, b는 공유되는 방식 copy()를 만들 수 있습니다.

사실 파이썬 자체에서 pickle 모듈같은 곳에서 저장/복원을 위한 속성 저장법이 있는데, copy 모듈도 같은 방법을 지원합니다.

>>> class S(object):
...     def __getstate__(self):
...             return self.a, self.b, self.c
...     def __setstate__(self, value):
...             self.a, self.b, self.c = value[0], value[1][:], value[2].copy()
... 
>>> s1 = S()
>>> s1.a, s1.b, s1.c = [1,2], [3,4], {'spam': 1, 'egg': 2}
>>> s2 = copy.copy(s1)
>>> s1.a[0] = s1.b[0] = -7
>>> s1.c['ham'] = 3
>>> s2.a, s2.b, s2.c
([-7, 2], [3, 4], {'egg': 2, 'spam': 1})
>>> s1.a, s1.b, s1.c
([-7, 2], [-7, 4], {'egg': 2, 'ham': 3, 'spam': 1})

__getstate____setstate__ 중 하나만 구현해도 상관은 없는데 그렇게 하면 리턴값이나 받아오는 값이 모두 딕셔너리가 돼야합니다. 위에서는 a는 그대로 참조만 가져오고, bc는 복사하는 방식으로 클래스 복사 방법을 지정해 줬습니다. 복사와는 크게 관련은 없지만 pickle에서 저장할 수 없는 속성들 (예를 들어 소켓 같은 것)이 들어있는 클래스는 __getstate__를 지정해 주는 방식으로 특정 속성을 저장에서 뺄 수 있습니다.

댓글 1 개 | 트랙백 0 개 (보낼곳) | 태그 python


파이썬 3의 한글

따로 말은 필요없고 직접 예제로!

>>> 합계 = sum
>>> 딸기, 바나나, 오이 = '딸기', '바나나', '오이'
>>> 가격 = {딸기: 300, 바나나: 500}
>>> 가격[오이] = 450
>>> 합계(가격[물건] for 물건 in [오이, 딸기, 딸기, 바나나, 오이, 바나나, 딸기])
2800
>>> 가격
{'딸기': 300, '오이': 450, '바나나': 500}
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', '가격', '딸기', '바나나', '오이', '합계']
>>> '전생에 백수였습니다'[2:7]
'에 백수였'

캬~~

관련 참조:

댓글 13 개 | 트랙백 0 개 (보낼곳) | 태그 python


"파이썬은 멀티코어 줘도 쓰잘데기가 없나요?"에 대한 파이썬 2.6의 대답

바야흐로 초딩도 멀티코어로 오락하는 시대가 오면서 이제 파이썬 GIL 공포가 많은 사람들을 위협하고 있었습니다. 그에 대한 해결책으로 예전부터 병렬 프로그래밍을 위한 여러가지 프레임워크, 예를 들어 MPI, CORBA, PyRO 같은 것들이 나왔지만, 다들 멋있게 모든 걸 포용하는 라이브러리를 만들다 보니 설치가 어렵거나 배우는데 한참 걸리는 게 결국 문제가 돼서 실제로 심각한 개발자 아니면 그냥 "CPU 1개면 충분해요"라고 눈을 반짝거리며 스스로 최면을 걸고 있었던 것이 사실입니다~

그래서 GIL의 파이썬 프로젝트 자체에서의 해결책으로는 공식적으로 결론에 도달한 것은 아니지만 Adam Olsen의 GIL없애기 프로젝트 같은 것도 있었는데, 이번에 몇몇 사람들의 강력한 후원으로 파이썬 2.6과 3.0부터 pyProcessing이 새 이름 multiprocessing 으로 표준 라이브러리로 들어오게 되었습니다.

pyprocessing의 다른 병렬 처리 라이브러리들에 대한 장점은 뭐니뭐니해도 표준 threading 모듈과 API가 같다는 점이겠죠. threading으로 기존에 짜 놓은 프로그램을 그냥 모듈 이름과 클래스 이름 아주 약간만 바꿔주면 쓰레딩 대신 멀티프로세싱을 사용하게 되어서 결국에는 멀티코어를 제대로 쓰는 프로그램이 됩니다. 실제로 쓰레딩같이 모든 변수를 공유하는 것은 아니고, 리턴값만 전달을 받기 때문에 정확히는 좀 다르다고 볼 수도 있지만, 뭐 "그렇게 짜면 왕변태"라고 선언하면 되겠죠. ㅎㅎ;

그래서 실제로 쓰는 모양을 보려고 옛날에 유행했던 정규식으로 소수 검사를 하는 걸 한 번 돌려 봤습니다.

from multiprocessing import Process as worker
import re, time

def isprime(n):
    convert = ''.join('1' for i in xrange(n))
    return not re.match(r'^1?$|^(11+?)\1+$', convert)

serial_st = time.time()
for i in range(10):
    isprime(1234567)
print 'Serial:', time.time() - serial_st

parallel_st = time.time()
processes = [worker(target=isprime, args=(1234567,)) for i in range(10)]
[p.start() for p in processes]
[p.join() for p in processes]
print 'Parallel:', time.time() - parallel_st

보시면 threading으로 만든 모듈과 join, start, worker 등의 사용법이 같아서 그냥 쉽게 바꾸실 수 있는데, 이렇게 돌려보면 대략 시간이 듀얼코어에서는 이렇게 나옵니다.

Serial: 18.2321028709
Parallel: 8.54528999329

대략 2배 정도 빨라졌죠~ 그냥 고전적인 던져주고 실행해서 리턴받는 방식 말고 보통 실제로 더 많이 쓰는 Queue 모델로 일꾼을 코어 개수만큼 돌려보면 이렇게 할 수도 있습니다.

from multiprocessing import Process, Queue
import re, time

q = Queue()

def isprime(n):
    convert = ''.join('1' for i in xrange(n))
    return not re.match(r'^1?$|^(11+?)\1+$', convert)

def primeworker():
    while True:
        n = q.get()
        isprime(n)
        print time.time() - parallel_st

parallel_st = time.time()
processes = [Process(target=primeworker) for i in range(2)]
[p.start() for p in processes]
for i in range(10):
    q.put(1234567)
[p.join() for p in processes]

시간은 대략 9초 정도 듭니다. 그리고 fd 넘겨주기를 지원하는 플랫폼들에서는 소켓도 받아다가 넘길 수가 있으니 네트워크 프로그램도 간단하게 멀티코어를 쓸 수 있습니다.

아주 간단하게 멀티코어를 쓸 수 있는 장점으로 표준 라이브러리로 도입이 되긴 했지만, 아직 문제가 몇 개 있는데요. 대표적으로 FreeBSD에서는 아직 POSIX 1003.1b 세마포어를 "제대로" 지원하지 않기 때문에 FreeBSD에서는 Queue나 Lock등과 관련된 것들을 하나도 쓸 수가 없습니다. (위 예제는 그래서 리눅스에서 테스트할 수 밖에 없었습니다;;)

그 외에 MPI 등의 "심각한" 분산 API들을 쓰던 프로그래머들은 이게 애들 장난이냐 하면서 없는 기능들을 지적할 수도 있는데, 아무래도 표준 라이브러리로써 아주 간단하고 기초적인 기능만 제공하는 것이 목적이고, 셋업이 복잡하거나 거대 프레임워크를 끌고 다니는 경우라면 표준에서 안정적으로 관리하기가 힘들겠죠. 그래서 multiprocessing을 도입하자 주장한 개발자는 이 모듈은 절대 다른 분산 관련 모듈을 쓰지 말라는 의미가 아니고 같이 쓰면 더욱 좋다고 강조합니다.

댓글 5 개 | 트랙백 0 개 (보낼곳) | 태그 python


파이썬 매킨토시 한중일 코덱

한동안 CJK 코덱은 잊고 살았는데, 올해 초에 엉뚱하게 코덱 관련 버그가 하나 떨어졌습니다. X-MAC-JAPANESE 코덱이 없다고 하면서 파이썬 설치가 안 돼요 라는 현상인데.. 파이썬 3.0에서는 빌드할 때 잠깐 빼고는 유니코드 코덱 없이는 소스 읽기 조차 안 될 정도가 됐기 때문에 시스템 설정에 있는 기본 인코딩이 안 맞으면 설치도 안 되고 잘 뜨지도 않는 현상이 발생된 것 같군요. 아니 무슨 90년대 유물인 MacJapanese가 아직 디폴트인지는 이해를 못하겠지만 말이죠;;

그래서 찾아보니까 다른 오픈소스 유니코드 관련 프로그램들은 전혀 지원하고 있지 않았지만, 펄이 유독 지원하고 있길래 어쩔 수 없이 펄보다는 나아야지 하는 오기로 (;;) 코덱 작업을 했습니다. 으흐 -ㅇ-;

CJK와 관련된 것은 MacKorean MacJapanese MacChineseSimp MacChineseTrad 이렇게 4가지인데 윈도우의 cp949 cp932 cp936 cp950과 마찬가지로 각각 euc-kr shift-jis gb2312 big5를 확장한 방식입니다. 그런데 확장을 MS보다 더 기상천외한 방법으로 유니코드 스펙을 최대한 활용한 덕에 이런 매핑까지도 등장합니다.

0xA34A  0xF864+0x005B+0x0031+0x0030+0x005D      # number ten in enclosing square, sans, shadowed

2바이트짜리 글자 하나를 무려 유니코드 5글자에 매핑을 하는 데다가, JIS X 0213에서 주로 많이 써서 골탕을 먹였던 중간에 잘라먹어도 말이 되는 글자들이 등장합니다.

0x31    0x0031  # DIGIT ONE
0xA542  0x0031+0x20DE   # DIGIT ONE + COMBINING ENCLOSING SQUARE
0xA341  0x0031+0x20DE+0xF87B    # DIGIT ONE + COMBINING ENCLOSING SQUARE, sans, shadowed

이런 경우에는 U+0031이 오면 0x31 한 글자, U+0031 U+20DE가 와도 0xA542 한 글자, U+0031 U+20DE U+F87B가 오면 0xA341 한 글자가 돼서 유니코드 1~3글자까지가 모두 다 2바이트가 돼버립니다. 그냥 단순 문자열에서는 별 문제가 없지만, 스트림 입출력 트랜스코딩에서는 이거 때문에 아주 미칠 지경이죠. 어흐흐;

그래서 그냥 C 코덱으로 매핑 덮어쓰기로 만들려니 너무 따분하고 이제 아무도 안 쓰는 인코딩 만들어 봐야 뭐하나 하는 생각에 예전에 생각해 뒀던, 딕셔너리 2개만 뿅 하고 던져주면 매핑 테이블을 덮어써서 새로운 코덱이 되는 방법으로 해 봤습니다. 예를 들어서

encode_map = { 
    u'a': 'b',   # replace original mapping
    u'b': None,  # invalidate original mapping
}
codec = _multibytecodec.create_extcodec(어쩌고 저쩌고..)

이렇게 해 주면 codec에 유니코드 a는 euc-kr에서 b로 인코딩해버리고, 유니코드 b는 모르니까 인코딩 안 해 준다고 에러내는 코덱이 나오게 됩니다. 이걸로 역슬래시 같이 논란이 있는 부분이나 용도에 따라서 좀 다르게 써야하는 만들어서 쓸 때 유용하지 않을까 생각해 봅니다~ -ㅇ-;

단, 이게 내부적으로 한 글자씩 조절을 하다보니 굉장히 비효율적이라 속도가 그렇게 빠르지는 않은 편이지만.. 그래도 요새 컴퓨터가 워낙 좋으니까.. ^_^;;

혹시 맥에서 파이썬 빌드가 잘 안 되셨던 분들은 패치를 받으셔서 한 번 해 보세요~ (아마도 별 문제가 없으면 파이썬 2.63.0에 들어갈 수 있겠죠. 펄보다는 나아야지.. ㅎㅎ;;;;;;;;;;;)

댓글 2 개 | 트랙백 0 개 (보낼곳)


이전 글 보기

누구?

장혜식 (Hye-Shik Chang)
내일을 사랑하는 소년(!)