[python] owyl 프로젝트를 python 3 에서 실행하자

owyl : A python behavior tree for implementing fast and flexible AI

owyl은 파이썬용 행동트리 구현체다. 행동트리를 이해하는 데 시각화된 예제가 필요하여 실행해보았다. (치즈샵에서 behavior tree 로 유일하게 검색된 이유이기도 하고..) 한동안 업데이트가 없는 것을 보니 앞으로도 없을 듯 하다. 약간의 감상평을 하자면 코루틴 없이 generator 로만 코딩을 해서 그런지 복잡하더라.

python 3 에서 실행해보기 위해 진행한 것을 기록한다. python 3 전용으로 실행하기 위해 살짝 변경 해놓은 소스를 이곳1에 올려놨음. for_python_3 브랜치를 이용하면 된다.

  1. examples/boids.py 를 python 3.4.3 에서 실행하면 pyglet2을 임포트할 수 없다는 에러가 발생
    (https://bitbucket.org/pyglet/pyglet/wiki/Home)
  2. pyglet 설치하자

    pip install pyglet

  3. 다시 boids.py 를 실행하면 cocos 를 임포트할 수 없다는 에러가 발생
  4. cocos3 를 설치하자

    pip install cocos2d

  5. 다시 boids.py 를 실행하면 rabbyt 를 임포트할 수 없다는 에러가 발생
  6. rabbyt4 를 설치하자

    pip install Rabbyt

  7. 다음의 에러를 출력하고 설치 실패

    File “<string>”, line 20, in <module>
    File “C:\Users\ADMINI~1\AppData\Local\Temp\pip-build-ukorogxl\Rabbyt\setup.py”, line 17
    print “*”*80
    ^
    SyntaxError: Missing parentheses in call to ‘print’

  8. Rabbyt 가 python 3.x 를 지원안해서 설치가 안되는 것. 홈페이지5를 가보니 더 이상 개발을 종료하였고, lib2d 를 대신 만들고 있는데 Rabbyt 와 호환이 안된다고 한다. 다행하게도 Ryex 라는 사람이 Rabbyt 의 python 3 포팅을 했다.
  9. https://github.com/Ryex/Rabbyt 로 다시 설치. github 에 있는 download zip 버튼의 링크를 install 뒤에 쓰면 된다.
    pip install https://github.com/Ryex/Rabbyt/archive/master.zip
  10. 다음의 에러를 출력하고 설치 실패

    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):
    File “<string>”, line 20, in <module>
    File “C:\Users\ADMINI~1\AppData\Local\Temp\pip-u1mmdmpo-build\setup.py”, line 6, in <module>
    from Cython.Distutils import build_ext
    ImportError: No module named ‘Cython’

  11. python 3 이상 버전은 cython이 필요하다. (from https://github.com/Ryex/Rabbyt )
  12. cython 설치하자

    pip install cython

  13. Ryex/Rabbyt 다시 설치

    pip install https://github.com/Ryex/Rabbyt/archive/master.zip

  14. 다음의 에러를 출력하고 설치 실패

    …위에 한참 있음 …
    File “C:\venv\for_owyl\lib\site-packages\setuptools\msvc9_support.py”, line 52, in query_vcvarsall
    return unpatched[‘query_vcvarsall’](version, *args, **kwargs)
    File “D:\Python343\Lib\distutils\msvc9compiler.py”, line 287, in query_vcvarsall
    raise ValueError(str(list(result.keys())))
    ValueError: [‘lib’, ‘path’, ‘include’]

  15. 각을 보니 windows sdk command shell 에서 진행을 해야 할 듯. 일단 나는 windows sdk 7.1 을 설치했으니 “Windows SDK 7.1 Command Prompt” 를 실행하여 다시 13을 실행 -> 설치됨
  16. examples/boids.py 를 실행하면 끝.
  1. https://github.com/kernel0/owyl.git
  2. pyglet: a cross-platform windowing and multimedia library for Python
  3. cocos : a 2D framework for games and multimedia
  4. rabbyt : A fast 2D sprite engine using OpenGL
  5. rabbyt website : http://arcticpaint.com/projects/rabbyt/

[python] python 2.7.9가 windows server 2008 R2 에 설치가 안되는 문제

문제 : 윈도우즈 인스톨러로 설치 시 인스톨러 패키지가 어쩌구 저쩌구 하면서 설치가 안됩니다. 이벤트 뷰어의 어플리케이션 로그에 아래와 같은 메시지가 나옵니다. 아참 이미 파이썬 3.4.3 이 설치 되어 있었어요.

Product: Python 2.7.9 — Error 1721. There is a problem with this Windows Installer package. A program required for this install to complete could not be run. Contact your support personnel or package vendor. Action: UpdatePip, location: D:\Python279\python.exe, command: -m ensurepip -U –default-pip

당장 해결법 : 설치 시에 pip 설치 체크를 풀고 진행합니다.

pip 를 설치하는 법 : https://pip.pypa.io/en/latest/installing.html#install-pip 를 참고

[python-asyncio] libuv, pyuv, aiouv

windows python asyncio 에서 기본 내장된 ProactorEventLoop 보다 빠른 event loop 는 없을까 고민하다가 nodejs 기반으로 유명하다는 libuv가 생각났다. python에는 libuv를 기반으로하는 pyuv가 있다. pyuv를 asyncio 에서 사용하려면 aiouv가 필요하다. 이것을 설치해보기로 했다.

업데이트 참고. 요약하자면 소득은 없는데(run_in_executor 로 실행하는 쪽이 ProactorEventLoop보다 눈에 띄게 느리더라. 우리쪽 문제일수도 있다.) windows 에서 설치는 불편했다.

업데이트 2 참고. pip aiouv 설치는 pyuv 가 없다고 하면서 안된다. 뭐 여기까진 괜찮다. pyuv 설치하자.
pip 로 pyuv 설치도 안된다. cp949 UnicodeError와 libuv 설치를 하려고 했는데 링킹이 안되니 어쩌고…아몰랑.  수동 설치 고고!

python 2.7, windows sdk 7.1 설치가 되었다고 가정하고 진행함. (python2.7은 libuv 빌드에 필요함. libuv 설치 스크립트에 c:\python27 이 하드코딩되어 있으니 python 3를 기본으로 사용하면 웬만하면 python2.7은 c:\python27 에 설치할 것)

pyuv 빌드 & 설치
1. github 에서 pyuv 소스 받기 https://github.com/saghul/pyuv/archive/v1.x.zip (pip install -d . pyuv 해서 받은 소스는 빌드 시 링킹에러가 발생한다. 같은 버전으로 알고 있는데 왜…)
2. cmd 열기
3. call “C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd” /release /x64
4. python setup.py build_ext –inplace –libuv-clean-compile
5. 원하는 venv 환경을 activate 하고 소스 폴더로 돌아온다.
6. python setup.py install 하면 venv 환경에 설치됨
7. 끝

aiouv 는 pyuv만 있으면 쉽다.
1. pip install https://github.com/saghul/aiouv/archive/master.zip (pip install aiouv 는 패키지가 없다면서 설치가 안됨 pypi 에서 검색이 되는데 패키지 명이 잘못됐나… 일단 이슈는 써봤는데… https://github.com/saghul/aiouv/issues/15)
2. 끝

업데이트 : 1000~ 2000 개의 redis set 하는 테스크로 시간 측정을 해봤을 때 aiouv.EventLoop가 윈도우즈에서 사용할 수 있는 ProactorEventLoop보다 40~60% 정도 빠르다. 테스트 중이다.

업데이트 2 : 언젠가부터 인지는 모르겠지만 windows python 3.4.3 x64 에서 간단히 설치된다. pip install pyuv 하면 된다

[python] libuv x64 release빌드하는 법

현재(2015-07-27) pip 에 등록된 pyuv 1.1.0을 빌드하기 위해선 libuv를 수동으로 빌드해야 하더라. pyuv 를 github에서 받아서 빌드하면 따로 libuv 를 빌드할 필요가 없다. 같은 버전인데 무슨차이인지 모르겠음 -_-;

libuv 빌드를 하려면 python2.x 가 필요하다. (visual studio sln vcxproj 파일을 python2.x 로 만든다)

1. libuv zip 다운로드 https://github.com/libuv/libuv/archive/v1.x.zip
2. 압축 풀고
3. cmd (command line 으로 git 실행할 수 있어야 하는 상태) 로 해당 폴더 이동 후 vcbuild.bat x64 release 실행
4. 배치파일이 실행되면서 vc project 를 만들어주는 gyp 프로젝트를 다운로드 받는다.
5. 실패 (python2.x 에서 설치하는 사람은 성공할 것임)

print 'Error running GYP'

6. 아.. 내 default python 이 3.4 다. cmd 에서 set PYTHON=PYTHON_2.X경로를 지정한다 ex) set PYTHON=c:\python27\python.exe
7. 다시 ‘vcbuild.bat x64 release’ 실행
8. uv.sln 이 생성됨
9. 이제 libuv 를 빌드하자 (Windows SDK 7.1 이 설치된 것을 가정한다. vs2010을 가정해서 뭔가 하는게 많은거 같다. 속 편하려면 vs2010 설치하자)
10. msbuild /p:PlatformToolset=Windows7.1SDK;Configuration=Release;Platform=x64 uv.sln
13. 끝

[python-asyncio] ProactorEventLoop의 sock_accept는 특정한 상황에서 처리할 수 없는 예외가 발생한다.

윈도우즈에서 사용할만한 EventLoop는 ProactorEventLoop 이 유일하다. (SelectorEventLoop은 1024개 이상의 소켓을 만들 수 없다.) ProactorEventLoop은 내부적으로 윈도우즈의 IOCP 를 사용한다. sock_accept 메소드가 IOCP의 AcceptEx를 호출할 때 미처 처리하지 못한 사례가 있다. sock_accept 구현상, 그 사례에서 발생하는 예외는 호출자가 처리하지 못한다. 결국 loop의 exception_handler가 불린다.

미처 처리하지 못한 사례란 다음과 같다.

요약하자면, 서버의 AcceptEx 가 완료되기 전에 backlog 에만 연결되어있던 클라이언트가 강제종료가 되면 ERROR_NETNAME_DELETED(OSError 64)가 발생한다는 것이다. 뭐 OSError Exception이 발생한다면 except 해주면 되잖아? 대략 아래와 같은 식으로 하면 잡히겠지 했다.

@asyncio.coroutine
def accept(loop, s):
    while True:
        try:
            yield from loop.sock_accept(s)
        except asyncio.CancelledError:
            logging.info("on socket accept failed - cancelled error")
        except OSError:
            logging.info("on socket accept failed - os error ")
        else:
            logging.info("on socket accept")

해당 상황에서 on socket accept failed – cancelled error 메시지가 나오긴 한다. 그럼 잡힌것 아닌가? 그런데 아래와 같은 출력도 함께 발생한다.

ERROR:asyncio:Task exception was never retrieved
future:  exception=OSError(22, '지정된 네트워크 이름을 더 이상 사용할 수 없습니다', None, 64, None)>
Traceback (most recent call last):
  File "d:\python342\Lib\asyncio\tasks.py", line 236, in _step
    result = next(coro)
  File "d:\python342\Lib\asyncio\windows_events.py", line 363, in accept_coro
    yield from future
  File "d:\python342\Lib\asyncio\futures.py", line 390, in __iter__
    return self.result()  # May raise too.
  File "d:\python342\Lib\asyncio\futures.py", line 277, in result
    raise self._exception
  File "d:\python342\Lib\asyncio\windows_events.py", line 560, in _poll
    value = callback(transferred, key, ov)
  File "d:\python342\Lib\asyncio\windows_events.py", line 351, in finish_accept
    ov.getresult()
OSError: [WinError 64] 지정된 네트워크 이름을 더 이상 사용할 수 없습니다

그래서 ProactorEventLoop 소스를 까봤다.

# windows_events.py 
def accept(self, listener):
    self._register_with_iocp(listener)
    conn = self._get_accept_socket(listener.family)
    ov = _overlapped.Overlapped(NULL)
    ov.AcceptEx(listener.fileno(), conn.fileno())
    def finish_accept(trans, key, ov):
        ov.getresult()
        # Use SO_UPDATE_ACCEPT_CONTEXT so getsockname() etc work.
        buf = struct.pack('@P', listener.fileno())
        conn.setsockopt(socket.SOL_SOCKET,
                        _overlapped.SO_UPDATE_ACCEPT_CONTEXT, buf)
        conn.settimeout(listener.gettimeout())
        return conn, conn.getpeername()
    @coroutine
    def accept_coro(future, conn):
        # Coroutine closing the accept socket if the future is cancelled
        try:
            yield from future
        except futures.CancelledError:
            conn.close()
            raise
    future = self._register(ov, listener, finish_accept)
    coro = accept_coro(future, conn)
    tasks.async(coro, loop=self._loop)
    return future

1. 8번째 줄 finish_accept 함수에서 ERROR_NETNAME_DELETED(OSError 64) exception이 발생한다.
2. 이 exception은 이미 생성한 23번 째 줄 future 객체의 exception으로 등록된다.(windows_events.py의 560번째줄 참고)
3. accept_coro 라는 coroutine 에서도 그 future 객체를 yield from 하고 있으니 exception이 발생한다.
4. accept_coro 에서는 OSError 에 대한 except 처리를 안하고 있으니 25번째 줄 코드로 생성된 task(coro task라고 부르자)에 exception으로 등록된다.
5. coro task 가 삭제될 때 exception이 따로 처리 되지 않으면 eventloop은 exception_handler를 호출한다.

exception_handler가 호출되면 안되나? 나는 exception_handler 가 호출되면 서버가 종료되도록 설계했다.1 exception_handler를 최후의 수단이라고 생각하며, 이 한계 내에서 처리가 되어야 한다.

이 문제를 근본적으로 해결하려면 accept_co coroutine 을 수정해야 한다

#요렇게?
@coroutine
    def accept_coro(future, conn):
        # Coroutine closing the accept socket if the future is cancelled
        try:
            yield from future
        except futures.CancelledError:
            conn.close()
            raise
        except OSError as exc:
            if exc.winerror == _overlapped.ERROR_NETNAME_DELETED:
                pass

이외에도 tasks.async 의 리턴 값을 future와 같이 호출자에게 리턴해서 coro task 의 add_done_callback 이라도 심을 수 있게 하던지…(아 이건 아니야…)

python asyncio가 수정되기 전까지는, 처음 링크건 IOCP 관련 포스팅에서 언급된 것처럼 AcceptEx 요청 개수를 늘려서 backlog 쪽에서 클라이언트가 대기하지 않는 방식을 사용하자. 난 이미 이 방식을 사용하고 있었는데 AcceptEx 요청 개수가 내가 원하는 순간 접속 시도 회수보다 작았기 때문에 문제가 발생했던 것으로 보인다. 제일 위에 언급했던 accept 함수를 아래와 같이 수정하면 될 것이다.

@asyncio.coroutine
def accept(loop, s):
    for i in range(2000):
        loop.create_task(accept_impl(loop, s))

@asyncio.coroutine
def accept_impl(loop, s):
    while True:
        try:
            yield from loop.sock_accept(s)
        except OSError:
            logging.error("oserror")

참고로 이 포스팅을 하기 위해 재확인차 작성했던 소스를 첨부한다.

https://gist.github.com/kernel0/400d040dcf2734457123

약간 설명하자면 listen 에 backlog 값을 크게 지정한다. (서버 소스의 accept방식이면 500정도만 되어도 잘 발생함)
더미 클라이언트로 여러 개의 소켓 접속 시도를 한다. 접속 시도 중에 더미 클라이언트 파이썬 프로세스를 강제 종료하면 문제가 재현된다.

  1. exception_handler가 호출될 때 서버를 다운 시키지 않으면 안되나?
    나는 안된다고 생각한다. 명시적으로 처리하지 못하는 exception이 있으면 서버가 다운되는 게 옳다고 생각한다. 서버 개발자들마다 철학(?)이 다를텐데 내 생각에는 exception을 뭉개버리면 서버 상태는 개발자가 예측할 수 없는 값을 갖고 뒹구르다가 또 다른 exception을 만들 수도 있고 그렇다면 뭐가 문제인지 모를 수도 있다. 또한 잘못된 데이터라도 들고 있다가 엉뚱한 값을 DB에 저장이라도 하면 헬게이트가 열리겠지.

[python] asyncio 지원하는 redis client 찾기

2개 있다. aioredis 와 asyncio_redis. asyncio_redis가 먼저나왔고 aioredis가 그 뒤로 나왔다. 첫번째 것이 괜찮았으면 두번째 것이 나왔겠냐 싶은데 일단….

aioredis 는 hiredis package 의존이 있다. hiredis 는 windows 지원을 하지 않는다. (workaround 가 있지만 개발자는 업데이트때 보장하지 않는다고 했다. 1 ) aioredis 개발자가 pure python fallback parser는 TBD라고 하니2 그냥 asyncio_redis 쓰다가 바꿔야겠다.

update : 쓰다보니 발견한 차이점이라면 asyncio_redis 는 zrangebyscore의 limit 옵션 구현이 안됐군 (참고 http://redis.io/commands/zrangebyscore)

update 2 : asyncio_redis 에 구현된 connection pool은 이미 사용하고 있는 connection을 할당하는 심각한 문제도 있다. (어떻게 재현하는 지는 다시 포스팅하겠음)

update 3: aioredis 개발자가 “hiredis parser가 짱 빠른거 같아 그냥 쓸래” 라는 것을 “님아 그럼 윈도우즈 사용자는 못씀!” 이라고 일단 징징거려서3 “i’ll consider this” 라는 대답도 받았으나 해당 이슈를 close 에서 안빼는 걸로봐서는 그다지 의지는 없는 듯 -_- 그래서 일단 내가 아쉬우니 asyncio_redis package용으로 간단한 ConnectionPool 만들었다. 이런일이 있을 때마다 windows python은 왕따 구나 싶은 생각이 자꾸 든다. 피해의식인가 =_=

update 4 : 못참는 용자들이 hiredis쪽에 windows support pull request를 넣고 있다. 신난다. 조만간 해결될 듯.

update 5 : 이제 aioredis 를 windows 에서도 쓸 수 있다. 2015-05-04 현재 pubsub 기능이 pypi 에 등록된 것에서는 작동하지 않지만(github 버전으로는 low-level api로 작동함) asyncio-redis 에 구현 되지 않은 command 옵션들도 지원하고 slaveof 명령어도 지원한다. 새 버전이 나오면 asyncio-redis 를 완벽하게 대체할 수 있을 것이다.

update 6 : asyncio는 2015-10-19 현재 pub/sub도 지원하고 redis 3.0 cluster는 버전업 준비중이다. 우리팀은 asyncio-redis를 삭제했다.

[python] nosetests 에서 내가 만든 모듈만 coverage 확인하는 법

내가 만든 프로젝트 최상단이 패키지로 묶여있지 않을 때 사용하는 방법

–cover-package=.

‘.’ 대신 패키지명을 입력하면 해당 패키지만 검사한다. (보통 설명에는 특정 패키지만 검사하는 것처럼 써있더라..)

[python] virtualenv 생성 시 tkinter 는 복사되지 않는 문제

matplotlib 를 사용하려고 하니 tcl 이 없다고 나옴. 잉? 예전에 python3.4.2 기본 설치하고 tkinter 작동시켜봤었는데 어떻게 된 일이지?

virtualenv 에는 os dependent 한 것들이 복사되지는 않나? 이 링크(https://bugs.launchpad.net/virtualenv/+bug/449537 )를 참고해서 확인 중이다. 해당 질문의 첫번째 댓글 단 아저씨 (https://bugs.launchpad.net/virtualenv/+bug/449537/comments/1) 말대로 python3.4.2 설치된 폴더에서 tcl 폴더만 virtualenv 로 가져오니 작동함.

좀 더 찾아봤더니 python3.4.2 에서 기본으로 제공하는 virtualenv 버전이 낮아서 (버전 1.x) 그런 것이었음.  virtualenv 의 버전을 업그레이드하고 (2015년 3월 현재 버전 12) 다시 해보면 된다.