'Decorator'에 해당되는 글 3건

  1. REST API 서버 제작기 (4) - db연동 및 decorator, contextlib
  2. contextlib
  3. decorator와 closure (2)

REST API 서버 제작기 (4) - db연동 및 decorator, contextlib

REST API 서버 제작기 (4) - decorator 및 db연동

최근, 블로그에 글을 컴팩트하게 쓰기로 마음먹으면서 이 시리즈(?)를 왜 쓰는가에 대한 회의가 좀 있지만 일단 시작한 것이니 만큼 끝까지 쓰기로 한다. 시작했으면 끝을 봐야지. 내용이 좀 짧아지는건 어쩔 수 없다.

decorator

khanrc: decorator와 closure
khanrc: decorator (2) - extension

before_filter로 만들어 두었던 것을 decorator로 변경했다. 유저 토큰 검사는 데코레이터와 굉장히 잘 어울린다.

# decorator.
def usertoken_required(func):
    @wraps(func)
    def decorated(*args, **kwargs):
        user_token = request.headers.get('user_token')
        cur.execute("""SELECT * FROM USER WHERE user_token='%s'""" % user_token)
        user = cur.fetchone()

        if user is None:
            return Response(response="user_token is wrong!", 
                            status=401)

        argspec = getargspec(func)
        if argspec[2] is not None: # kwargs를 받는 함수에게만 전달
            kwargs['user_info'] = user
        return func(*args, **kwargs)

    return decorated

mongodb

khanrc: mongoDB
그간 써보고 싶었던 mongodb를 써 보았다. 위 링크를 보면 알겠지만 mongokit이라고 ORM도 있는데, ORM을 그다지 선호하지 않아 pymongo를 사용하였다. flask의 extension인 flask-pymongo가 존재하는데 별로 필요 없어 보인다.

from flask.ext.pymongo import PyMongo
import pymongo

app = Flask(__name__)
app.config['MONGO_DBNAME'] = 'newsup'
# app.config['MONGO_HOST'] = 'localhost' # expect localhost by default.
mongo = PyMongo(app)

def getSeq(name):
    ret = mongo.db.ikeys.find_and_modify(
            query={ '_id' : name },
            update={ '$inc': { 'seq': 1 } },
            new=True
        )

    return ret.get('seq')

@app.route('/users', methods=['POST'])
def add_user():
    user_token = request.headers.get('user_token')
    if user_token is None:
        return Response(response="user_token is None",
                        status=401)

    try:
        ret = mongo.db.users.insert({
            '_id': user_token,
            'user_id': getSeq('user_id')
        })

        if ret == False:
            return "fail!!!!"
    except pymongo.errors.DuplicateKeyError, e:
        return Response(response="{error_code:1, error_msg:'중복된 유저가 있음'}",
                        status=200)
    except:
        print "치명적인 에러 in add_user, insert user_token: " + str(sys.exc_info())
        return Response(response="{error_code:-1}", mimetype='application/json', status=200)


    return Response(response="{error_code:0}", mimetype='application/json', status=201)

getSeq()는 auto_increment를 구현한 것. ikeys 컬렉션에 seq를 저장해 놓고 매 호출마다 증가시킨다.

다만 위처럼 하면 json 인식이 제대로 안 되는데, response를 저렇게 쓰면 안 된다.

return Response(response="""{"error_code":-1}""", mimetype='application/json', status=200)

이렇게 string""로 감싸줘야 한다.

mysql (mariadb)

python-mysql 모듈도 굉장히 다양하게 존재한다. mysql에서 공식적으로 지원하는 mysql.connector, 가장 많이 쓰는 것 같은 mysqldb 등등. 이 모듈들의 성능 비교를 한 포스트가 있다.
Comparing python/mysql drivers

성능이 좋다고 하고, 많이들 사용하는 mysqldb를 사용해 보기로 했다. mysqldb는 mysql의 C API를 직접적으로 사용하는 _mysql모듈의 래퍼다.

install

참고: Is MySQLdb hard to install?

sudo apt-get install build-essential python-dev libmysqlclient-dev
sudo pip install MySQL-python

단, 우리는 mariadb이기 때문에 libmysqlclient-dev가 아니라 libmariadbclient-dev를 설치해야 한다.
http://stackoverflow.com/questions/22949654/mysql-config-not-found-when-installing-mysqldb-python-interface-for-mariadb-10-u 참고.

sudo apt-get install libmariadbclient-dev
sudo pip install MySQL-python

이러면 설치가 된다.

usage

import MySQLdb as mdb
import _mysql_exceptions
import sys

con = mdb.connect(host='host', user='user', passwd='passwd', db='db')
cur = con.cursor()

try:
    p = 'hello11'
    s = """INSERT INTO USER (user_token) VALUES ('%s')""" % (p)
    print s
    cur.execute(s)
    con.commit()
except _mysql_exceptions.IntegrityError, e:
    print "IntegrityError : ", e
    con.rollback()
except:
    print "error!! rollback!!" + str(sys.exc_info())
    con.rollback()

cur.execute("""SELECT * FROM USER""")
print cur.fetchall()

처음에 이렇게 코딩을 했더니, 처음엔 잘 되다가 시간이 지나니 OperationalError: (2006, 'MySQL server has gone away') 이런 에러가 났다. 구글링을 해 보니 connection이 끊겨서 나는 에러라고 하더라. 생각해 보니 저렇게 한번 커넥트를 해주면 되는게 아니라, 매 요청시마다 커넥트를 해 주고 끝나면 닫아줘야 한다. 커넥션이 타임아웃되어 에러가 났던 것.

그래서 매 요청마다 커넥트와 클로즈를 해주자니 너무 코드가 지저분해진다. 그래서 contextlib을 쓰기로 했다.

contextlib

khanrc: contextlib 참고.

위 링크에도 나와있지만

from contextlib import contextmanager

@contextmanager
def db_connect():
    con = mdb.connect(host, user, passwd, db)
    cur = con.cursor()

    try:
        yield (con, cur)
    finally:
        cur.close()
        con.close()

with db_connect() as (con, cur):
    p = """ '%s' """ % user_token
    s = """INSERT INTO USER (user_token) VALUES (%s)""" % (p)
    cur.execute(s)
    con.commit()

요렇게 쓴다.

contextlib

contextlib

데코레이터와 마찬가지로 파이썬에서 지원하는 강력한 기능중 하나. 파이썬을 쓰다보면 with라는 키워드를 볼 수 있는데, 이에 관련된 라이브러리가 바로 contextlib이다. with는 시작과 끝이 있는 경우에 사용하는데, file이나 database처럼 open 또는 connect 후 close가 필요한 경우가 대표적이다.

intro

import time

class demo:
    def __init__(self, label):
        self.label = label

    def __enter__(self):
        self.start = time.time()

    def __exit__(self, exc_ty, exc_val, exc_tb):
        end = time.time()
        print('{}: {}'.format(self.label, end - self.start))

with demo('counting'):
    n = 10000000
    while n > 0:
        n -= 1

# counting: 1.36000013351

보면 알겠지만 __enter__with문이 시작할 때, __exit__는 끝날 때 실행된다.

이제 이걸 contextlib을 사용해서 간단하게 바꿀 수 있다.

from contextlib import contextmanager
import time

@contextmanager
def demo(label):
    start = time.time()
    try:
        yield
    finally:
        end = time.time()
        print('{}: {}'.format(label, end - start))

with demo('counting'):
    n = 10000000
    while n > 0:
        n -= 1

# counting: 1.32399988174

yieldwith문이 감싸는 코드를 실행시킨다. yield를 통해서 오브젝트를 전달할 수 있다.

closing

앞에서 말한 것처럼, with문은 close와 함께 많이 활용되고 이를 위해 closing이란 게 있다.

from contextlib import closing
import MySQLdb

con = MySQLdb.connect("host", "user", "pass", "database")
with closing(con.cursor()) as cur:
    cur.execute("somestuff")
    results = cur.fetchall()

    cur.execute("insert operation")
    con.commit()

con.close()

db connect할 때 이렇게 많이 쓰이는 것 같다.

apply

from contextlib import contextmanager

@contextmanager
def mysql_connect():
    con = mdb.connect(host, user, passwd, db)
    cur = con.cursor()

    try:
        yield (con, cur)
    finally:
        cur.close()
        con.close()

with mysql_connect() as (con, cur):
        cur.execute("""SELECT * FROM USER""")
        tu = cur.fetchall()
        con.commit()

이렇게 적용하는 것이 가장 심플해 보인다. yield의 용법과 2개 이상의 오브젝트를 전달하는 방법도 참고하자.

참고

Python - 수준 있는 디자인 패턴 (Advanced Design Patterns in Python)

python-docs: 27.7. contextlib — Utilities for with-statement contexts : 공식 문서
MySQLdb & closing

'Python' 카테고리의 다른 글

logging  (0) 2014.10.29
setup.py vs requirements.txt  (0) 2014.10.28
contextlib  (0) 2014.10.12
Errors and Exceptions  (0) 2014.10.09
decorator (2) - extension  (0) 2014.10.06
decorator와 closure  (2) 2014.10.04

decorator와 closure

decorator

데코레이터는 파이썬의 강력한 문법 중 하나다. 파이썬에 입문해서 이것저것 좀 하다 보면 여기저기서 많이 볼 수 있다. 데코레이터를 한 마디로 정리하자면, 함수를 래핑하여 앞뒤에서 전처리와 후처리를 하는 기능 이라고 할 수 있다. 파이썬에서는 이 데코레이터 기능을 간편하게 지원한다.

글이 쓸데없이 장황해졌는데, 윗 부분은 개념 설명이고 in python부터 보아도 무방하다.

function: first class object

파이썬에서, 함수는 first class 오브젝트다. 다시 말해 변수와 함께 동등한 레벨의 객체로 취급된다. 따라서 우리는 자유자재로 변수처럼 함수를 인자로 넘길 수 있다. 대표적으로 sorted함수를 사용할 때를 생각해보자.

>>> student_tuples = [
        ('john', 'A', 15),
        ('jane', 'B', 12),
        ('dave', 'B', 10),
]
>>> sorted(student_tuples, key=lambda student: student[2])   # sort by age
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

closure

파이썬은 function closure를 지원한다.

>>> def outer():
...     x = 1
...     def inner():
...         print x # 1
...     return inner
>>> foo = outer()
>>> foo()
1
>>> foo.func_closure
(<cell at 0x...: int object at 0x...>,)

즉 이런 경우다. foo는 inner를 리턴받았고, inner에서 사용하는 x는 inner 바깥의 outer에 있기 때문에 foo를 호출하는 시점에서는 x가 존재하지 않아야 한다. 그런데, 위에서 보이듯이 잘 실행된다. 이게 바로 function closure다. 펑션 클로저는 그 함수가 정의될 때 자신을 감싸고 있는 namespace가 어떻게 생겼는지 기억한다는 의미다. foo의 func_closure로 그 함수를 감싸고 있는 scope의 변수들을 볼 수 있다.

간단하게 말하면, 어떠한 함수를 객체로 받을 때 그 함수를 감싸는 scope의 변수들 또한 같이 가져간다는 의미다. 따라서 이러한 것도 가능하다:

>>> def outer(x):
...     def inner():
...         print x # 1
...     return inner
>>> print1 = outer(1)
>>> print2 = outer(2)
>>> print1()
1
>>> print2()
2

decorator

데코레이터는, 결론부터 말하자면, 이름 그대로 함수를 데코레이트 해준다. 함수를 인자로 받아 꾸며주는 기능을 지원한다. 바꿔 말하면 함수를 래핑하는 기능이라고도 할 수 있겠다.

def verbose(func):
    def new_func():
        print "Begin", func.__name__
        func()
        print "End", func.__name__
    return new_func

def my_function():
    print "hello, world."

>>> my_function = verbose(my_function)
>>> my_function()
Begin my_function
hello, world.
End my_function

verbose라는 데코레이터를 통해 my_function을 데코레이트했다. 원래는 hello, world만 출력하는 함수였지만 이젠 데코레이트되어 앞뒤로 시작과 끝을 출력한다.

in python

사실 지금까지는 이론이라고 할 수 있고, 이제부터가 진짜 코드 레벨이다. 파이썬 데코레이터라고 하면 바로 @를 의미한다. 파이썬 2.4에서 추가되었다고 한다. 파이썬 코드를 보다보면 아래와 같은 코드들을 종종 볼 수 있다.

@verbose
def my_function():
    print "hello, world."

이는 verbose라는 데코레이터로 my_function이라는 함수를 데코레이트 해준다는 것을 의미한다. 이 함수를 실행하면 같은 결과를 볼 수 있다.

>>> my_function()
Begin my_function
hello, world.
End my_function

헌데, my_function에 파라메터가 있으면 어떡하지? 위에서 했던 걸 생각해보면, 그냥 파라메터를 넣어 주면 된다.

def verbose(func):
    def new_func(name):
        print "Begin", func.__name__
        func(name)
        print "End", func.__name__
    return new_func

@verbose
def my_function(name):
    print "hello,", name

>>> my_function("hi")
Begin my_function
hello, hi
End my_function

*args, **kwargs

그런데 이렇게 되면 파라메터가 하나 있는 함수에 대해서만 이 데코레이터를 사용할 수 있다. 함수의 시작과 끝을 알리는 데 파라메터의 개수가 무슨 상관이란 말인가? 이런 쓸데없는 제약을 없애기 위해 사용할 수 있는 것이 바로 *args**kwargs다. 둘 다 지정되지 않는 파라메터들을 받지만, **kwargs는 딕셔너리로서 이름이 지정된 파라메터들을 받는다.

>>> def foo(x, *args, **kwargs):
...     print x
...     print args
...     print kwargs
>>> foo(1, 2, 3, 4, 5, y=2, z=3)
1
(2, 3, 4, 5)
{'y': 2, 'z': 3}

이를 이용하면 데코레이터를 최종적으로 확장할 수 있다.

def verbose(func):
    def new_func(*args, **kwargs):
        print "Begin", func.__name__
        func(*args, **kwargs)
        print "End", func.__name__
    return new_func

class

다른 함수들과 마찬가지로, 데코레이터 함수도 클래스로 구현할 수 있다.

class Verbose:
    def __init__(self, f):
        print "Initializing Verbose"
        self.func = f

    def __call__(self, *args, **kwargs):
        print "Begin", self.func.__name__
        self.func(*args, **kwargs)
        print "End", self.func.__name__


@Verbose
def my_function(name):
    print "hello,", name

실행 해 보면 결과는 동일하게 나온다.

참고

파이썬 데코레이터 이해하기 : 이론 위주 설명
파이썬 데코레이터 (decorator): 기초편 : 코드레벨 설명

'Python' 카테고리의 다른 글

Errors and Exceptions  (0) 2014.10.09
decorator (2) - extension  (0) 2014.10.06
decorator와 closure  (2) 2014.10.04
String option - u와 r  (0) 2014.09.21
한글 in the dictionary (feat. pretty)  (0) 2014.09.17
Python 2.x 한글 인코딩  (0) 2014.09.10