python trivia

개요

python 이해도를 높이기 위한 목적으로,

잘 만들어진 library 소스코드(tensorflow)를 보다가 특기할 만한 짧은 내용들을 간략히 소개한다.

 

__future__

compiler 지시자로 동작한다.

하위 호환성이 없는 기능을 미리 사용하기 위함이다.

링크 : PEP 236 – Back to the __future__

링크 : future statement

 

__all__

init.py 내에서 작성하는 variable 이다.

package 내의 공개 가능한 submodule 의 범위를 한정하기 위함이다.

복잡한 프로젝트의 구조와 별개로 사용자 인터페이스를 단순하게 하고 싶을 때 사용할 수도 있다.


# tensorflow\__init__.py

# tensorflow.python 패키지 하위의 submodule 들을 사용자가 접근할 때

# tensorflow.xxx 으로 사용 가능하도록 한다.

from tensorflow.python import *

링크 : importing * from a package

 

pylint

정적분석 도구다.

PEP8 스타일 가이드를 따르도록 도와준다.

pylint: 로 시작하는 주석으로  message 를 제어할 수 있다.

# pylint: disable=wildcard-import
from tensorflow.python import *
# pylint: enable=wildcard-import

링크 : PEP8 style guilde for python code

 

SWIG

python – native code wrapper 다.

같은 목적의 도구로 boost::python 가 있다.

둘 다 써보지 않아서 잘은 모르겠지만

SWIG 는 별도의 인터페이스 file 을 작성하여야 하는 번거로움이 있는 대신

python / tcl / perl / java /c# 등 다양한 타겟으로 인터페이스가 가능하다.

boost 빠인 나로선 boost::python 쪽에 더 마음이 간다.

windows 환경에서 python/oracle 연결

Prerequisite

윈도우 환경에서 python / oracle 연결하려면 사전에 준비할 것들이 좀 된다.

  1. python 버전에 맞는 VC Common tools
  2. Oracle Instant Client Package SDK

 

Python version 에 맞는 VC++ 및 VC Common tools 설치

python 버전에 따라 vc++ 버전이 다르다.

아래 표를 보고 해당하는 버전의

1) VC++ 재배포가능 패키지(https://www.microsoft.com/ko-kr/download/details.aspx?id=48145) 또는

2) Visual Studio(https://www.visualstudio.com/ko-kr/downloads/download-visual-studio-vs.aspx)

를 설치한다.

VC++ version
Python version
14 3.5
10 3.3, 3.4
9 2.6, 2.7, 3.0, 3.1, 3.2

 

프로그램 언어 -> Visual C++ -> Visual C++ Common Tools 를 설치한다.

 

Oracle Instant Client Package SDK

Instant Client Package – SDK(http://www.oracle.com/technetwork/topics/winx64soft-089540.html) 를 다운로드한다.

다운로드한 sdk의 경로를 시스템 PATH 환경변수에 등록한다.

 

oracle python interface 설치

상기 tool 들을 설치하면 cx_Oracle 패키지가 설치된다.

pip install cx_Oracle

 

Locale 맞추기

한글을 정상적으로 입출력 하려면 system locale과 db locale 을 일치시켜야 한다.

locale 일치는 반드시 db connection 이전에 완료되어야 한다.

1) 현행 DB locale 확인

locale
cur.execute("select userenv('LANGUAGE') from dual")

2) system locale 환경변수 등록

locale
os.putenv('NLS_LANG', 'AMERICAN_AMERICA.AL32UTF8')

rabbitmq reference

prerequisite

rabbitmq 설치 : https://www.rabbitmq.com/download.html

python 설치(다른 언어로 대체 가능하나, 예제는 python3.5임)

rabbitmqctl 사용법

ack 확인되지 않은 msg list

rabbitmqctl list_queues name messages_ready messages_unacknowledged

 

모든 queue 정보 삭제(=reset)

rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl start_app

 

매번 queue 삭제/리스타트하기 귀찮아서 python으로 짰다.

본인 환경에 맞도록 적당히 수정해서  쓰면 될 듯

pp = pprint.PrettyPrinter(indent=2, compact=True)
result = []
mqctlExec = '"C:\\Program Files\\RabbitMQ Server\\rabbitmq_server-3.6.3\sbin\\rabbitmqctl" {param}'
spExec = """for line in sp.Popen(mqctlExec.format(param='{param}'), shell=True, stdout=sp.PIPE, stderr=sp.PIPE).stdout:
    result.append(line)"""
exec(spExec.format(param='stop_app'))
exec(spExec.format(param='reset'))
exec(spExec.format(param='start_app'))
pp.pprint(result) 

 

 

basic queue

기본적으로 mq로 msg 전송/수신하기 위한 최소 설정

1) queue

2) channel

3) message body

basicSender
import pika
def main():
    con = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
    channel = con.channel()
    channel.queue_declare(queue='emc')
    cnt = 0
    while(True):
        a = input()
        cnt += 1
        msg = "no {number} Hello Basic Sender".format(number=cnt)
        channel.basic_publish(exchange='', routing_key='emc', body=msg)
        print(str(cnt) + " send success >>" + msg)
    con.close()
if __name__=="__main__":
    main()
basicReceiver
import pika
def callback(ch, method, properties, body):
    print(body)
    ch.basic_ack(delivery_tag=method.delivery_tag)
def main():
    con = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
    channel = con.channel()
    channel.queue_declare(queue='emc')
    channel.basic_consume(callback, queue='emc', no_ack=True)
    channel.start_consuming()
if __name__=="__main__":
    main()

문제점

메세지 유실

  • sender 영역 : rabbitmq 서비스가 재시작되면 메세지가 유실될 수 있다.
  • receiver 영역 : receiver가 받을 준비가 되어 있지 않으면 메세지가 유실될 수 있다.

 

durable queue

다음과 같이 message를 유실하지 않기 위한 안전장치를 마련한다.

  • queue(rabbitmq 서비스) : queue 를 durable = True 로 설정
  • message(sender 설정) : delivery mode를 persistence 로 설정
  • ack(receiver 설정) : message를 수신하였을 때, ack 를 송신하도록 설정

durable queue 주의점

만일 ack를 송신하지 않으면 message가 삭제되지 않으므로 주의한다.

 

durable = True 일 때 message가 삭제되지 않을 조건

queue의 durability 를 True로 설정하면 message 의 생명 주기는 해당 message의 delivery mode에 의하여 결정된다.

  • delivery mode 1 (=non persistent) : 서비스가 종료되면 message는 삭제된다.
  • delivery mode 2 (=persistent) : 서비스가 종료되더라도 삭제되지 않는다.
durableSender.py
import pika
def main():
    con = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
    channel = con.channel()
    channel.queue_declare(queue='emc', durable=True)
    cnt = 0
    while(True):
        a = input()
        cnt += 1
        msg = "no {number} Hello Basic Sender".format(number=cnt)
        channel.basic_publish(exchange='', routing_key='emc', body=msg,
                              properties=pika.BasicProperties(delivery_mode=2))
        print(str(cnt) + " send success >>" + msg)
    con.close()
if __name__=="__main__":
    main()
durableReceiver.py
import pika
def callback(ch, method, properties, body):
    print(body)
    ch.basic_ack(delivery_tag=method.delivery_tag)
def main():
    con = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
    channel = con.channel()
    channel.queue_declare(queue='emc', durable=True)
    channel.basic_consume(callback, queue='emc')
    channel.start_consuming()
if __name__=="__main__":
    main()

 

load balancing

아래 그림과 같이 2대의 receiver가 message를 받아 처리하는 구조를 가정해 보자.

 

rabbitmq의 기본 balancing 전략은 round-robin 이다.

만일 receiver 중 1대가 매우 바쁘면, 해당 receiver에 전달되어야 할 message는 제때 처리되지 못한 채 queue에 적체될 것이다.

 

busyReceiver.py
import pika
def busyFunction():
    while(True):
        a = input()
        print('i am busy. very busy.')
def callback(ch, method, properties, body):
    print(body)
    busyFunction()
    ch.basic_ack(delivery_tag=method.delivery_tag)
def main():
    con = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
    channel = con.channel()
    channel.queue_declare(queue='emc', durable=True)
    channel.basic_consume(callback, queue='emc')
    channel.start_consuming()
if __name__=="__main__":
    main()
//durableSender.py 로 message 를 10 개 발송
1 send success >>no 1 Hello Basic Sender
2 send success >>no 2 Hello Basic Sender
3 send success >>no 3 Hello Basic Sender
4 send success >>no 4 Hello Basic Sender
5 send success >>no 5 Hello Basic Sender
6 send success >>no 6 Hello Basic Sender
7 send success >>no 7 Hello Basic Sender
8 send success >>no 8 Hello Basic Sender
9 send success >>no 9 Hello Basic Sender
10 send success >>no 10 Hello Basic Sender
 
//durableReceiver.py 는 1,3,5,7,9 번째 메세지를 전달받는다.
b'no 1 Hello Basic Sender'
b'no 3 Hello Basic Sender'
b'no 5 Hello Basic Sender'
b'no 7 Hello Basic Sender'
b'no 9 Hello Basic Sender'
...busyReceiver 가 오랜시간(default 1분)동안 heartbeat을 주지 못해 connection closed 되면 unacked message 인 2,4,6,8,10 번째 메세지를 전달 받는다.
b'no 2 Hello Basic Sender'
b'no 4 Hello Basic Sender'
b'no 6 Hello Basic Sender'
b'no 8 Hello Basic Sender'
b'no 10 Hello Basic Sender'
//busyReceiver.py 는 바빠서 2번째 메세지를 받은 뒤 ack 를 보내지 못했다.
b'no 2 Hello Basic Sender'
i am busy. very busy.

 

qos 의 prefetch_count 를 설정하면 ack를 받지 못할 경우, 해당 receiver 로 message 를 발송하지 않는다.

busyReceiver.py
import pika
def busyFunction():
    while(True):
        a = input()
        print('i am busy. very busy.')
def callback(ch, method, properties, body):
    print(body)
    busyFunction()
    ch.basic_ack(delivery_tag=method.delivery_tag)
def main():
    con = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
    channel = con.channel()
    channel.queue_declare(queue='emc', durable=True)
    channel.basic_qos(prefetch_count=1)
    channel.basic_consume(callback, queue='emc')
    channel.start_consuming()
if __name__=="__main__":
    main()
//durableSender.py 로 message 를 10 개 발송
1 send success >>no 1 Hello Basic Sender
2 send success >>no 2 Hello Basic Sender
3 send success >>no 3 Hello Basic Sender
4 send success >>no 4 Hello Basic Sender
5 send success >>no 5 Hello Basic Sender
6 send success >>no 6 Hello Basic Sender
7 send success >>no 7 Hello Basic Sender
8 send success >>no 8 Hello Basic Sender
9 send success >>no 9 Hello Basic Sender
10 send success >>no 10 Hello Basic Sender
//balancedReceiver.py 는 바빠서 1번째 메세지를 받은 뒤 ack 를 보내지 못했다.
//prefetch-count 를 1로 설정하였기 때문에, ack를 1번 받지 못한 balancedReceiver에게는 다시 message를 전달하지 않는다.
b'no 1 Hello Basic Sender'
i am busy. very busy.
 
//durableReceiver.py 는 2,3,4,5,6,7,8,9 번째 메세지를 전달받는다.
b'no 2 Hello Basic Sender'
b'no 3 Hello Basic Sender'
b'no 4 Hello Basic Sender'
b'no 5 Hello Basic Sender'
b'no 6 Hello Basic Sender'
b'no 7 Hello Basic Sender'
b'no 8 Hello Basic Sender'
b'no 9 Hello Basic Sender'
b'no 10 Hello Basic Sender'
...busyReceiver 가 오랜시간(default 1분)동안 heartbeat을 주지 못해 connection closed 되면 unacked message 인 1 번째 메세지를 전달 받는다.
b'no 1 Hello Basic Sender'

 

cpython compiler design(작성 중)

출처 : PEP 339 design of the cpython compiler

개요

보통의 python 개발자라면 굳이 cpython 의 구현/설계 원리를 알 필요가 없으며, 실제로도 코딩에 하등 도움이 되지 않는다.

허나,

  • 본인의 성향이 오덕이고
  • 요즘 언어의 추세인 managed resource 의 메모리 관리가 궁금한 아재이고
  • python compiler/interpreter 개발에 기여하고 싶거나
  • compiler 수업을 듣는 학생이라면

공부할 가치가 있다. 다시 말하지만 본인이 생업에 바빠 python 코드를 찍어내야 하는 반도의 흔한 개발자라면 굳이 PEP 같은건 볼 필요가 없다. 그냥 language reference 만으로 충분하다.

어쨋든 코딩=밥벌이에 큰 도움은 되지 않으나, 본인이 python에 대한 깊이있는 이해를 하고 있다는 착각에 빠지는 효과가 있고, 요즘 모던한 언어들이 다양한 형태의 vm을 어떻게 대응하는지에 대한 추세를 가늠할 수 있으며, 학생 시절에나 배울 법한 컴파일러/언어 이론을 찾아보다 보면 뇌단련이 되는 긍정적인 효과가 있다.

python life cycle

python code 는 크게 다음과 같은 흐름으로 실행된다.

  1. src code 작성
  2. bytecode 변환(by compiler)
  3. 실행(by interpreter on vm)

등장인물의 면면을 살펴보면,

  • 내가 짠 src code
  • compiler
  • interpreter
  • virtual machine

으로 총 4가지 이다.

일반적으로 널리 쓰이는 cpython 의 경우, compiler/interpreter/vm 을 c 로 구현하였고, 자체 정의한 bytecode로 compile/실행한다.

반면, jython 의 경우는 java bytecode로 compile/실행 하기 때문에 vm 의 경우 jvm을 사용할 수 있다.

아래 그림을 보면 관계가 이해갈 것이다.

Jython's use of Java bytecode is depicted in this Python implementation diagram.

cpython/jython 외에도 다양한 python compiler/interpreter 구현이 있다.

  • pypy
  • ironpython
  • 기타등등

cpython compiler 구조

가장 common 한 cpython 의 compiler design 을 살펴보자.

위에 내용을 상기하면, compiler 는 src code -> bytecode 로 변환하는 역할이며,

cpython 이므로 c언어로 작성되었다.

관련 정보는 PEP(python enhancement proposals)-339 문서를 찾아보면 얻을 수 있는데, python 이라는 언어 자체가 구현체에 대한 스펙을 강제하지 않기 때문에 이 내용은 cpython 에만 해당한다는걸 염두에 두길 바란다.

src code 가 bytecode 로 변환되는 과정은 다음과 같다.

  1. src code
  2. parse tree(=Concrete Syntax Tree)
  3. AST(=Abstract Syntax Tree)
  4. CFG(=Control Flow Graph)
  5. bytecode

 

parse tree(concrete syntax tree)

 

cpython parser 는 LL parser 를 base 로 한다.

문법 규칙은 graminit.h 에 명세되어 있으며, type 매핑 코드는 token.h 에 정의되어 있다.

파서 :  https://github.com/python/cpython/blob/master/Parser/pgen.c

문법 규칙 : https://github.com/python/cpython/blob/master/Include/graminit.h

type 매핑코드 : https://github.com/python/cpython/blob/master/Include/token.h

parser 의 결과물은 CST 라 부르는 parse tree 이다. CST 는 변환과정을 거쳐 AST 로 바뀐다. 여기서 CST와 AST 같은 중간단계가 왜 필요한지, 차이점은 무엇인지 의문이 들 것이다.

CST vs AST

CST와 AST 의 설명을 언뜻 보면 잘 이해가 가지 않을 수도 있다.  이해를 돕기 위해 다음 아주 간단한 python code 를 parse tree 로 변환해 보도록 하자.


1+2

이 코드를 CST(=parse tree) 로 변환하면 아래와 같다.

cst.gv.png

간단한 산술연산인데도 CST로 변환하니 뭔가 복잡하다. CST 에는 파싱된 python code 에서 요구하는 모든 syntax 및 token 의 정보가 포함되어 있다. 따라서 간단한 코드 길이에 비해 CST 의 depth 는 장황해진다.

조금 더 이해를 돕기 위해 다음 code 도 CST 로 변환해보자.

 a=1 

cst.gv.png

 

bytecode 입장에서는 단지

  1. opcode 와
  2. operand 만 필요할 뿐이어서

CST에는 불필요한 정보들이 많다. 1+2 와 a=1 을 각각 AST 로 변환해보자.

1+2

ast.gv.png

a=1

ast.gv.png

 

참고로, CST와 AST 는 모두 형식적으로 Extended BNF 를 따른다. BNF 가 궁금하면 링크를 참조하기 바란다.

 

(todo) 메모리 관리 – PyArena / PyObject

AST(abstract syntax tree) : https://github.com/python/cpython/blob/master/Python/ast.c

pyArena : https://github.com/python-git/python/blob/master/Python/pyarena.c

CFG(control flow graph) : https://github.com/python/cpython/blob/master/Python/compile.c

bytecode : https://github.com/python/cpython/blob/master/Python/compile.c

(BNF, Backus Naur Form) : CST

(Deterministic Finite Automaton) : AST

(NonDeterministic Finite AUtomaton) : AST

(memory management 추가자료)

컴파일 시 필요한 메모리 리소스는 PyArena 라 불리는 memory pool (PyObject 의 linked list) 로 관리되며 대부분의 경우 신경쓰지 않아도 되나, PyObject 를 직접 관리할 때에는 명시적으로 PyArena 에 해당 PyObject 를 추가해야 한다.

As stated above, in general you should not have to worry about memory management when working on the compiler. The technical details have been designed to be hidden from you for most cases.

The only exception comes about when managing a PyObject. Since the rest of Python uses reference counting, there is extra support added to the arena to cleanup each PyObject that was allocated. These cases are very rare. However, if you’ve allocated a PyObject, you must tell the arena about it by calling PyArena_AddPyObject().