SQL에서 AS 절은 SELECT 절의 expression에 별명을 붙여주는 역할을 한다. SQLAlchemy에서는 컬럼 객체의 label 메소드를 통해 labeled column을 얻어내는 방식으로 AS 절을 표현할 수 있다.

TblUsers.pw 컬럼을 password라는 이름으로 aliasing하여 SELECT했으므로 row 객체에서의 속성 이름도 pw가 아니라 password가 된다. 이렇게 aliasing된 expression을 다른 곳에서 사용하려면, filter 등에 string expression으로 표현하면 된다.

무조건 string expression을 써야 하는 것은 아니다. aliasing되었더라도 filter(func.length(TblUsers.pw) > 16)같은 표현식을 쓸 수 있다. 그러나 aliasing이라는 것 자체가 어떠한 expression에 별명을 지어주는 것이기 때문에, 위처럼 단지 filter에 문자열을 전달하는 것으로 alias된 이름을 통한 쿼리를 표현할 수 있다는 것이다.

함수

aliasing은 SELECT 절에서 함수를 사용할 때 유용하다. SQLAlchemy에서는 SQL의 함수들이 sqlalchemy.sql.expression.func 모듈에 준비되어 있다.


'Python 계열 > SQLAlchemy' 카테고리의 다른 글

Column.like, Column.ilike, not_, ~expr  (0) 2019.02.12
특정 컬럼만 SELECT  (0) 2019.02.12
limit  (0) 2019.02.12
query 객체가 실제로 쿼리를 실행하는 시기  (0) 2019.02.12
Multiple Primary Key  (0) 2018.10.18

HTTP HEAD 메소드는 특정 자원을 GET 메소드로 요청하는 경우에 어떤 헤더들이 반환되는지를 요청한다. Amazon S3 API의 경우 객체에 대해 HEAD 요청을 전송하면 응답에 Content-Length가 함께 전달될텐데, 이를 통해 굳이 GET 요청으로 객체를 읽지 않고도 객체의 용량을 알아낼 수 있다. boto3에서는 head_object라는 이름의 메소드로 bucket에서 특정 key에 해당하는 객체에 HEAD 요청을 보낼 수 있다.

head_object 메소드는 객체의 메타데이터들로 이루어진 dict를 반환하고, 'ContentLength'라는 key에 객체의 size가 값으로 담겨 있다. 단위는 byte다. 아쉽게도 Bucket 객체에는 head_object 메소드가 없다.

쿼리의 결과 row 수를 제한하기 위해 LIMIT 쿼리를 사용한다. 사용자가 리소스를 만들어가는 형태의 서비스(SNS, 게시판 블로그 등)는 데이터들이 수직적으로 늘어나는데, 이런 곳에서는 일반적으로 목록 데이터를 넘겨줄 때 pagination 개념을 적용한다. 페이스북 뉴스피드에서 포스트가 가장 처음에는 10개 남짓 보이다가, 스크롤이 끝나면 포스트가 새롭게 로드되는 것이 그 예다.

사용자에겐 자체적인 rank 알고리즘이나, 특별한 기준(가장 최신 글부터 등)에 따라 먼저 몇 개 정도만 보여주고, 원하면 더 불러오게 만드는 것이다. 수만 개가 넘는 포스트 목록이 있어봤자 사용자는 그들 중 일부만 볼텐데, 그 데이터를 다 넘겨줘 봐야 불필요하게 화면에 렌더링시키는 시간만 늘어나고, 네트워크 비용이 낭비될테니 말이다.

이런저런 이야기들로 LIMIT 쿼리의 의도를 설명했는데, 아무튼 결론적으로는 자원 낭비를 줄이기 위해 LIMIT 쿼리를 사용한다. 목록 데이터를 다 내려주기엔 양이 너무 많아서던, 표본 추출(Sampling) 방식으로 통계를 내기 위해서던 말이다.

SQLAlchemy에서 limit은 그냥 query 객체에 슬라이싱을 씌우면 된다. Query 클래스에는 슬라이싱을 처리할 수 있는 __getitem__ 매직 메소드가 구현되어 있고, 이는 n:m으로 슬라이싱했을 때 쿼리에 LIMIT n OFFSET m을 추가하고 all의 호출 결과를 반환하는 역할을 한다. 주저리주저리 열심히 설명했지만 참 간단하게 된다.

query 객체의 slice 메소드를 사용하는 것도 한가지 방법이다.


'Python 계열 > SQLAlchemy' 카테고리의 다른 글

Column.like, Column.ilike, not_, ~expr  (0) 2019.02.12
특정 컬럼만 SELECT  (0) 2019.02.12
aliasing과 함수  (0) 2019.02.12
query 객체가 실제로 쿼리를 실행하는 시기  (0) 2019.02.12
Multiple Primary Key  (0) 2018.10.18

SQLAlchemy를 쓰면서 가장 혼란스러운 것 중 하나가 'Query 객체는 도대체 언제 SELECT 쿼리를 수행하는가?'였다. 마주치게 되는 상황은 이렇다.

  • query.all()로 SELECT의 결과를 모두 가져올 수 있다고 한다. 이는 전형적인 반복자(iterator)로, for row in query.all()처럼 for문에서 사용할 수 있다.
  • 그런데 그냥 query 자체를 for문에 넣는 예제도 보인다. for row in query를 해도 문제가 없다.
  • LIMIT 쿼리를 어떻게 표현하는지 검색했더니, 그냥 슬라이싱하라고 한다. 그래서 for row in query[:10].all()로 했더니 에러가 발생하고, for row in query[:10]처럼 표현하니 제대로 동작한다.

그냥 SQLAlchemy의 소스 코드를 GitHub에서 살펴보면, 그 비밀을 알 수 있다. sqlalchemy.orm.query.py 모듈을 보면 되고, 아래는 그들 중 일부를 가져온 snippet이다.

__iter__

Query 객체는 __iter__ 메소드에서 실제로 SELECT를 수행한다고 요약할 수 있다. __iter__ 메소드가 호출되는 타이밍 중 몇개를 예로 들면,

  • iter 함수에 전달되었을 때
  • for문에 사용되었을 때(for문은 in 우측에 전달된 객체를 iter 함수에 전달하므로)
  • list 함수에 전달되었을 때

그럼 의문이 조금 풀린다.

  • for row in query : for문에 의해 간접적으로 __iter__가 호출되므로 잘 동작한다.
  • for row in query.all() : 위 snippet을 보면 알 수 있듯, list(self)의 반환을 iteration하므로 잘 동작한다.

__getitem__

__getitem__ 메소드는 인덱싱과 슬라이싱 연산을 오버라이딩한다. Query.__getitem__ 메소드는

  • 슬라이스를 받았다면 현재 Query 객체에 LIMIT 쿼리를 추가하는 self.slice 메소드를 호출한 후, 이를 통해 LIMIT이 반영된 Query 객체의 SELECT 결과를 반환하거나
  • 인덱스를 받았고 그 인덱스가 -1이라면 list(self)[-1]을 반환하고, 아니라면 list(self[item:item + 1])[0]으로 __getitem__을 재귀호출하여 해당 인덱스에 대한 row 하나만을 가져오도록 LIMIT 쿼리를 수행해서 경제적으로 row를 반환한다. 예를 들어 query[15]에 대해서는, LIMIT 15, 1 쿼리가 추가된다.

따라서,

  • for row in query[5:8] : __getitem__에 슬라이스가 전달되고, LIMIT 5, 3 쿼리가 추가된 Query 객체의 iterator가 반환된다.
  • row = query[10] : __getitem__에 인덱스가 전달되고, LIMIT 10, 1 쿼리가 추가된 Query 객체의 결과 중 0번째 인덱스가 반환된다.

'Python 계열 > SQLAlchemy' 카테고리의 다른 글

Column.like, Column.ilike, not_, ~expr  (0) 2019.02.12
특정 컬럼만 SELECT  (0) 2019.02.12
aliasing과 함수  (0) 2019.02.12
limit  (0) 2019.02.12
Multiple Primary Key  (0) 2018.10.18

아래 명령은 설치된 모든 패키지를 upgrade합니다.

$ pip install --upgrade $(pip list | egrep -v 'Pack|----' | awk '{print $1}')

아래 명령은 pip, wheel, setuptools를 제외한 패키지를 모두 제거합니다.

$ pip uninstall $(pip list | egrep -v 'Pack|----|pip|wheel|setuptools')

'Python 계열 > Python 레거시 글' 카테고리의 다른 글

requirements.txt로 협업 상황에서의 의존성 관리하기  (0) 2018.11.07
pip  (0) 2018.11.05
__slots__  (0) 2018.11.03
Context Manager  (0) 2018.11.01
Argument Unpacking  (0) 2018.10.30

requirements.txt는 npm의 packages.json과 유사하게, 의존성의 목록을 관리하는 Python의 관례적인 파일입니다. 다수의 개발자가 Python으로 프로젝트를 진행하는 경우, 개발 시 필요한 의존성 패키지들을 동일하게 맞추고 개발하는 것이 좋습니다. Python은 requirements.txt라는 개념을 통해 이를 해결합니다.

pip freeze

pip freeze라는 명령이 있습니다. pip list처럼 시스템에 설치된 파이썬 패키지의 목록을 출력하는데, freeze의 경우 이 목록을 raw하게 표현한다는 특징이 있습니다.

$ pip3 freeze
pbr==4.0.4
six==1.11.0
stevedore==1.28.0
virtualenv==16.0.0
virtualenv-clone==0.3.0
virtualenvwrapper==4.8.2

이전에 pip install을 하면서, 특정 버전의 패키지를 설치할 때 사용했던 표현과 동일합니다. 따라서 최초 개발자 A는 pip freeze 명령어의 결과를 외부 파일로 export해 의존성 목록을 만들 수 있습니다.

$ pip3 freeze > requirements.txt

requirements.txt 파일 내에는 다음과 같이 개발 시 필요한 패키지들이 저장됩니다.

$ cat requirements.txt
pbr==4.0.4
six==1.11.0
stevedore==1.28.0
virtualenv==16.0.0
virtualenv-clone==0.3.0
virtualenvwrapper==4.8.2

파일 이름은 아무렇게나 해도 상관 없으나, 관례 상 위처럼 requirements.txt를 사용하는 것이 좋습니다.

pip install -r [filename]

pip install 명령에는 전달할 수 있는 많은 인자들이 존재하지만, --upgrade와 이 -r 인자를 가장 많이 사용할 것입니다. pip install 뒤에 -r을 붙이고, 의존성 목록이 적혀 있는 파일의 경로를 입력하면 적혀 있는 그대로 패키지들이 설치됩니다.

$ pip3 install -r requirements.txt
Collecting pbr==4.0.4 (from -r requirements.txt (line 1))
https://files.pythonhosted.org/packages/b3/5d/c196041ffdf3e34ba206db6d61d1f893a75e1f3435699ade9bd65e089a3d/pbr-4.0.4-py2.py3-none-any.whl
Collecting six==1.11.0 (from -r requirements.txt (line 2))
https://files.pythonhosted.org/packages/67/4b/141a581104b1f6397bfa78ac9d43d8ad29a7ca43ea90a2d863fe3056e86a/six-1.11.0-py2.py3-none-any.whl
Collecting stevedore==1.28.0 (from -r requirements.txt (line 3))
https://files.pythonhosted.org/packages/17/6b/3b7d6d08b2ab3e5ef09e01c9f7b3b590ee135f289bb94553419e40922c25/stevedore-1.28.0-py2.py3-none-any.whl

(...)

Installing collected packages: pbr, six, stevedore, virtualenv, virtualenv-clone, virtualenvwrapper
Successfully installed pbr-4.0.4 six-1.11.0 stevedore-1.28.0 virtualenv-16.0.0 virtualenv-clone-0.3.0 virtualenvwrapper-4.8.2


'Python 계열 > Python 레거시 글' 카테고리의 다른 글

귀찮음을 줄여주는 간단한 pip 관련 명령 모음(Draft)  (0) 2018.11.12
pip  (0) 2018.11.05
__slots__  (0) 2018.11.03
Context Manager  (0) 2018.11.01
Argument Unpacking  (0) 2018.10.30

좀 잘 나가는 프로그래밍 언어라면, 패키지(라이브러리) 생태계가 굉장히 활발하게 형성되어 있습니다. 그 대표격으로 JavaScript 진영의 npm이 있고, 이와 유사한 패키지 생태계로 Python에는 PyPI(Python Package Index)가 있습니다. 그리고 이들은 pip를 이용해 관리할 수 있습니다. pip를 이용해 외부 라이브러리를 설치하면, 파이썬이 설치된 경로의 site-packages 디렉토리에 적용되어 즉시 사용할 수 있게 됩니다. 별도의 의존성 있는 모듈이나 패키지도 함께 설치해 주기 때문에 매우 편리합니다.

Linux 기반 운영체제에서 pythonpython3 명령이 별도로 존재하는 것처럼, pippip3 명령도 별도로 존재합니다. 각각 python2에 대한 라이브러리, python3에 대한 라이브러리를 관리합니다. pip --version 또는 pip -V를 통해 설치되어 있는 pip의 버전을 확인할 수 있습니다.

$ pip -V
pip 10.0.1 from /usr/local/lib/python2.7/site-packages/pip (python 2.7)
$ pip3 --version
pip 10.0.1 from /usr/local/lib/python3.6/site-packages/pip (python 3.6)

pip install [packages ...]

PyPI에서 packages에 해당하는 패키지들을 가져와 설치합니다. 아래의 명령은 virtualenvwrapper를 설치합니다.

$ pip3 install virtualenvwrapper
Collecting virtualenvwrapper
  Downloading https://files.pythonhosted.org/packages/2b/8c/3192e10913ad945c0f0fcb17e9b2679434a28ad58ee31ce0104cba3b1154/virtualenvwrapper-4.8.2-py2.py3-none-any.whl

(...)

https://files.pythonhosted.org/packages/b3/5d/c196041ffdf3e34ba206db6d61d1f893a75e1f3435699ade9bd65e089a3d/pbr-4.0.4-py2.py3-none-any.whl (98kB)
    100% |████████████████████████████████| 102kB 2.1MB/s
Installing collected packages: six, pbr, stevedore, virtualenv-clone, virtualenvwrapper
Successfully installed pbr-4.0.4 six-1.11.0 stevedore-1.28.0 virtualenv-clone-0.3.0 virtualenvwrapper-4.8.2

여러 개의 패키지들을 한 번에 설치할 수도 있으며, 이미 존재하는 패키지인 경우 'Requirement already satisfied'라는 결과가 보여집니다. 아래의 명령은 시스템에 이미 설치되어 있는 virtualenv와 virtualenvwrapper를 설치합니다.

$ pip3 install virtualenv virtualenvwrapper
Requirement already satisfied: virtualenv in /usr/local/lib/python3.6/site-packages (16.0.0)
Requirement already satisfied: virtualenvwrapper in /usr/local/lib/python3.6/site-packages (4.8.2)
Requirement already satisfied: stevedore in /usr/local/lib/python3.6/site-packages (from virtualenvwrapper) (1.28.0)
Requirement already satisfied: virtualenv-clone in /usr/local/lib/python3.6/site-packages (from virtualenvwrapper) (0.3.0)
Requirement already satisfied: six>=1.10.0 in /usr/local/lib/python3.6/site-packages (from stevedore->virtualenvwrapper) (1.11.0)
Requirement already satisfied: pbr!=2.1.0,>=2.0.0 in /usr/local/lib/python3.6/site-packages (from stevedore->virtualenvwrapper) (4.0.4)

다음과 같이 특정 버전의 패키지를 설치할 수도 있습니다. 아래의 명령은 virtualenvwrapper의 4.7 버전을 설치합니다.

$ pip3 install virtualenvwrapper==4.7
Collecting virtualenvwrapper==4.7
  Downloading 

(...)

Installing collected packages: virtualenvwrapper
Successfully installed virtualenvwrapper-4.7.0

pip list

pip list는 pip에 의해, 또는 수동으로 설치되어 있는 패키지들의 목록을 출력합니다.

$ pip3 list
Package           Version
----------------- -------
pbr               4.0.4
pip               10.0.1
setuptools        39.0.1
virtualenv        16.0.0

(...)

wheel             0.31.0

pip install --upgrade [packages ...]

packages에 해당하는 패키지들을 시스템에서 찾아, 설치되어 있으면 최신 버전으로 업그레이드하고, 그렇지 않으면 pip install을 진행합니다. 패키지가 이미 최신 버전으로 설치되어 있다면, 'Requirement already up-to-date'라는 결과가 보여집니다. 아래는 최신 버전인 virtualenv최신 버전이 아닌 virtualenvwrapper를 업그레이드하는 명령입니다.

$ pip3 install --upgrade virtualenv virtualenvwrapper
Requirement already up-to-date: virtualenv in /usr/local/lib/python3.6/site-packages (16.0.0)

(...)

Installing collected packages: virtualenvwrapper
  Found existing installation: virtualenvwrapper 4.7.0
    Uninstalling virtualenvwrapper-4.7.0:
      Successfully uninstalled virtualenvwrapper-4.7.0
Successfully installed virtualenvwrapper-4.8.2

pip uninstall [packages ...]

packages에 해당하는 패키지들을 시스템에서 제거합니다. 시스템에 존재하지 않는 패키지일 경우, 'Skipping *** as it is not installed.'라는 결과가 보여집니다. 아래는 설치되어 있는 jinja2와 markupsafe, 설치되어 있지 않은 flask를 제거하는 명령입니다.

$ pip3 uninstall jinja2 markupsafe flask
Uninstalling Jinja2-2.10:
  Would remove:
    /usr/local/lib/python3.6/site-packages/Jinja2-2.10.dist-info/*
    /usr/local/lib/python3.6/site-packages/jinja2/*
Proceed (y/n)? y
  Successfully uninstalled Jinja2-2.10
Uninstalling MarkupSafe-1.0:
  Would remove:
    /usr/local/lib/python3.6/site-packages/MarkupSafe-1.0.dist-info/*
    /usr/local/lib/python3.6/site-packages/markupsafe/*
Proceed (y/n)? y
  Successfully uninstalled MarkupSafe-1.0
Skipping flask as it is not installed.


Python의 모든 클래스는 인스턴스 속성(instance attribute)을 가집니다. 기본적으로, 이러한 객체의 인스턴스 속성을 관리하기 위해 Python에서 내부적으로 딕셔너리를 사용합니다. 객체의 __dict__ 필드에 접근하여 인스턴스 속성들을 딕셔너리 형태로 확인할 수 있고, 이는 런타임에 새로운 속성을 설정하는 데에 매우 유용하게 사용됩니다.

그러나, 알려진(known) 속성들로 구성된 클래스들의 경우 이러한 구조는 딕셔너리가 낭비하는 RAM 때문에 병목이 발생할 수 있습니다. 클래스 레벨에 __slots__라는 변수를 설정해서, 해당 클래스에 의해 만들어진 객체의 인스턴스 속성 관리에 딕셔너리 대신 속성에 대한 고정된(fixed) set을 사용하도록 할 수 있습니다.

__slots__가 정의된 클래스의 인스턴스에는 __dict__ 필드가 없다 정도밖에 차이가 없어 보이지만, IronPython의 도움을 받아 메모리 사용량을 보면, 효율이 확실히 개선된 것을 확인할 수 있습니다. ipython-memory-usage를 사용해, 1024 * 256개의 객체 생성에 대한 메모리 사용량을 확인해 보았습니다.

In [1]: class WithoutSlots:
   ...:     def __init__(self, name, identifier):
   ...:         self.name = name
   ...:         self.identifier = identifier
   ...:

In [2]: class WithSlots:
   ...:     __slots__ = ['name', 'identifier']
   ...:     def __init__(self, name, identifier):
   ...:         self.name = name
   ...:         self.identifier = identifier
   ...:

In [3]: import ipython_memory_usage.ipython_memory_usage as imu

In [4]: size = 1024 * 256

In [5]: imu.start_watching_memory()
In [5] used 0.0000 MiB RAM in 5.31s, peaked 0.00 MiB above current, total RAM usage 15.57 MiB

In [6]: x = [WithoutSlots(1, 1) for _ in range(size)]
In [6] used 22.6680 MiB RAM in 0.80s, peaked 0.00 MiB above current, total RAM usage 38.24 MiB

In [7]: x = [WithSlots(1, 1) for _ in range(size)]
In [7] used 9.3008 MiB RAM in 0.72s, peaked 0.00 MiB above current, total RAM usage 47.54 MiB


'Python 계열 > Python 레거시 글' 카테고리의 다른 글

requirements.txt로 협업 상황에서의 의존성 관리하기  (0) 2018.11.07
pip  (0) 2018.11.05
Context Manager  (0) 2018.11.01
Argument Unpacking  (0) 2018.10.30
[Python] zip  (0) 2018.07.28

Context(문맥)이라는 단어가 참 많은 곳에서 사용되기 때문에 조금 애매하지만, 아무튼 Python의 특정 객체들은 context를 가집니다. 예를 들어, file 객체는 open-close의 2가지 context를 가지는 식입니다.

file 객체의 open과 close처럼, context가 명확한 객체의 경우 Python에서는 원하는 타이밍에 정확하게 리소스를 할당하고 제거할 수 있도록 context manager 프로토콜을 갖도록 하는 것을 권고하고 있습니다. 'context manager 프로토콜을 가지고 있다'의 조건은 생각보다 간단합니다.

  • 매직 메소드 __enter__()를 구현하고 있어야 한다.
  • 매직 메소드 __exit__()을 구현하고 있어야 한다.

이 두 매직 메소드들은 가장 일반적인 context manager인 with-as 문에서 사용됩니다.

with 문에 의해 감싸진 객체는, 블럭이 시작되기 이전에 as 뒤의 객체에 __enter__()의 반환값을 할당하며, 블럭을 탈출하는 시점에 해당 객체의 __exit__()을 호출합니다. file의 경우, __enter__()에서 파일 객체를 열어 반환하고, __exit__()에서 파일 객체를 닫는(close) 역할을 하므로 context manageable하다고 말할 수 있습니다.

Context manageable한 클래스 만들기

__enter__()와 __exit__()이 정의된, context manageable한 클래스를 만들고 with-as 문에 사용해 보겠습니다.

역시, Python은 생각보다 훨씬 깊습니다.

'Python 계열 > Python 레거시 글' 카테고리의 다른 글

pip  (0) 2018.11.05
__slots__  (0) 2018.11.03
Argument Unpacking  (0) 2018.10.30
[Python] zip  (0) 2018.07.28
[Python] Coroutines  (0) 2018.07.27

Python에서 언더스코어처럼, asterisk(*)도 꽤 특별하게 사용할 수 있었습니다. 가장 일반적인 사용 사례는 수 사이의 곱셈과 거듭제곱 연산, Sequence 객체의 반복 확장, 그리고 아래의 가변 인자 표현식이었습니다.

Argument unpacking

Iterable 객체는 unpacking이 가능합니다. 이를 함수 인자에도 적용할 수 있는데, 여기에서 asterisk(*) 기호를 사용합니다.

이를 Argument unpacking이라 부르며, 위치 인자의 argument unpacking에는 List와 Tuple, String과 같은 sequence 객체를 사용할 수 있습니다. 아래는 키워드 인자에 대한 argument unpacking입니다.

여기에는 Dictionary와 같은 mapping 객체를 사용할 수 있습니다.

응용

Argument unpacking은 설정 데이터를 외부에서 다룰 때 유용하게 사용할 수 있는데, 아래와 같습니다. 데이터베이스에 연결하는 스크립트를 작성한다고 가정해 보겠습니다.

위처럼 argument unpacking으로 설정 데이터를 관리하는 경우, 대표적으로 아래 2가지의 강점을 가집니다.

  1. 코드가 간결해집니다. config에서 관리하는 데이터가 많아질수록, 간결함은 더 강해집니다.
  2. 추상화 레벨을 높여 유지 보수에 조금이나마 편의성을 제공합니다. 설정이 추가되거나 제거되는 경우, argument unpacking을 사용하는 코드에서는 원본 해당 객체만 수정함으로써 해결 가능하게 됩니다.


'Python 계열 > Python 레거시 글' 카테고리의 다른 글

__slots__  (0) 2018.11.03
Context Manager  (0) 2018.11.01
[Python] zip  (0) 2018.07.28
[Python] Coroutines  (0) 2018.07.27
[Python] memoize  (0) 2018.07.26

+ Recent posts