Postgresql에서는 order by 부분에 null값이 가장 먼저 나올지, 나중에 나올지 설정할 수 있습니다. 

order by의 예는 아래와 같습니다.

SELECT name
FROM test
ORDER BY name DESC NULLS FIRST;


해당 쿼리는 name 컬럼에 null값인 행을 가장 앞으로 정렬하게되는 쿼리입니다. 


아래는 위 쿼리를 Django ORM으로 똑같이 표현한 예입니다.

from django.db.models import F


Test.objects.order_by(F('name').desc(nulls_first=True))

null값을 가장 뒤로 보내고 싶으면 nulls_first 부분을 nulls_last로 변경해주면 됩니다.

쿼리에서 aggregation함수(count(), max(), first() 등등) 와 필요한 컬럼을 출력하는 방법은 아래와 같습니다.

SELECT name AS changed_name, count(count)
FROM test
GROUP BY name;


위와 같은 쿼리는 아래와 같이 장고 ORM으로 표현할 수 있습니다.

queryset = Test.objects.values('name').annotate(
    max_count=Count('count'),
    changed_name=F('name')
).values('max_count', 'changed_name')


aggregation 함수를 사용하기 전, group by를 진행할 필드를 지정해 줍니다.

queryset = Test.objects.values('name')


group by를 진행할 필드를 작성한 다음, annotate()를 사용해서 aggregation과 변경할 이름을 작성합니다.

queryset = Test.objects.values('name').annotate(
    max_count=Count('count'),
    changed_name=F('name')
)


마지막으로 호출하고자 하는 필드를 지정해 줍니다. 이때는 annotate()에서 변경한 이름을 지정합니다.

queryset = Test.objects.values('name').annotate(
    max_count=Count('count'),
    changed_name=F('name')
).values('max_count', 'changed_name')


lambda, map, filter 같은 경우는 사실 가독성 부분에서 좋지 않다는 생각이 들어 지양하고 있었는데 장점이 있어서 정리해 봅니다.

lambda(람다) 함수

lambda (이후 '람다'라고 호칭)함수는 익명함수로 메모리를 절약하는 이점이 있습니다. 또한 가독성을 향상시킨다 라고도 나오지만 함수와 비교했을 때 가독성이 좋은건지는 잘 모르겠습니다. (개인적인 의견)

일반적인 함수는 객체를 만들고, 재사용을 위해 함수 이름(메모리)를 할당을 합니다.

# lambda 인수1, 인수2, ... : 인수를 이용한 표현식
sum = lambda a, b: a+b
result = sum(3,4)
print(result)
# 7

왜 사용할까?

익명함수이기 때문에 한번 쓰이고 다음줄로 넘어가면 힙(heap) 메모리 영역에서 증발하게 됩니다. (단발성으로 사용하게 될 때 유용)

자세하게 설명하자면 파이썬에서 모든 것이 객체로 관리되고 각 객체는 레퍼런스 카운터를 갖게 되는데 해당 카운터가 0 - 어떤 것도 참조를 하지 않게 되면 메모리를 환원하게 됩니다. - 이러한 역할을 하는 것이 가비지 컬렉터입니다.

map 함수

내장함수이며 입력받은 자료형의 각 요소가 함수에 의해 수행된 결과를 묶어서 map iterator 객체로 반환합니다. map함수는 게으른 연산을 진행해서 메모리를 크게 절약할 수 있습니다. 게으른 연산을 진행하기 때문에 값을 한번 사용하면 사용했던 값은 다시 호출할 수가 없습니다.

# map(function, iterable)
# 예시
li = [1, 2, 3]
result = list(map(lambda i: i ** 2 , li))
print(result)
print(result)


# [1, 4, 9]
# result

게으른 연산이란(lazy evaluation)?

필요할 때만 가져다 사용합니다. 보통 iterator 객체들이 게으른 함수입니다. (아닐 수도 있음 )

iterator 객체

  • next() 메소드로 데이터를 순차적으로 호출 가능
  • 마지막 데이터 이후 next()를 호출하면 StopIteration 에러 발생
  • for 문을 사용할 때, 파이썬 내부에서는 임시로 list를 iterator로 변환해서 사용

iterator 예시

map()

li = [1, 2, 3]
result = map(lambda i: i * i, li)

print(type(result))


print(next(result))
print(next(result))
print(next(result))

# next(result) # StopIteration 발생


# 결과
# <class 'map'>
# 1
# 4
# 9

iter()

li = [1, 2, 3]
result = iter(li)

print(type(result))

print(next(result))
print(next(result))
print(next(result))
# next(result) # StopIteration 발생



# 결과
# <class 'list_iterator'>
# 1
# 2
# 3

generator expression

li = [1, 2, 3]
result = (val*val for val in li)

print(type(result))

print(next(result))
print(next(result))
print(next(result))
# next(result) # StopIteration 발생


# 결과
# <class 'generator'>
# 1
# 4
# 9


Value()

Value(value, output_field=None)

value() 객체는 나타낼 수 있는 가장 작은 값을 표현할 수 있습니다. 예를 들어 쿼리에서 integer나 boolean 또는 string 을 표현하고자 할 때 Value()로 감싸서 나타낼 수 있습니다.

사실상 Value()를 직접 사용할 필요는 드뭅니다. F('foo') + 1 이라는 표현식이 있을 때, 장고는 암시적으로 1을 Value()로 감싸 복잡한 표현식에서 단순한 값을 사용하게 합니다. 만약 string 값을 표현식에 나타내고자 할 때 Value()를 사용하여 나타낼 수 있습니다. 대부분의 표현식은 Lower('name')과 같이 해석하고 있습니다.


output_field는 IntegerField() 나 BooleanField()와 같은 장고 모델에서 제공하는 인스턴스를 작성해야하고 장고는 데이터베이스에서 값을 가져온 후 값을 로드합니다. 일반적으로 데이터 유효성 검사와 관련된 argument(max_length, max_digits 등)가 표현식의 출력 값에 적용되지 않으므로 모델 필드를 인스턴스화 할 때는 arguments가 필요하지 않습니다.

F()

F(name)

F() 객체는 모델 필드의 값을 나타냅니다. 데이터베이스에서 파이썬 메모리로 데이터를 가져 오지 않고 모델 필드 값을 참조하고 사용하여 데이터베이스 작업을 수행 할 수 있습니다. 대신 장고는 F() 객체를 사용하여 데이터베이스 수준에서 필요한 작업을 설명하는 SQL 표현식을 생성합니다.

아래 예제에서 F() 객체의 사용법을 알 수 있습니다.


reporter = Reporters.objects.get(name='Tintin')
reporter.stories_filed += 1
reporter.save()


위 파이썬 구문은 reporter.stories_filed의 값을 데이터베이스에서 메모리로 가져와 파이썬 연산자를 사용하여 조작 한 다음 데이터베이스에 다시 저장했습니다. 아래는 F() 객체를 사용하여 위와 동일한 결과를 생성하는 구문입니다.

from django.db.models import F
reporter = Reporters.objects.get(name='Tintin')
reporter.stories_filed = F('stories_filed') + 1
reporter.save()


reporter.stories_filed = F ( 'stories_filed') + 1은 값을 인스턴스 속성에 할당 한 것처럼 보이지만 데이터베이스에 대한 연산을 설명하는 SQL 구문입니다.  장고는 F()의 인스턴스를 만나면 파이썬 연산자를 오버라이드하여 캡슐화된 SQL 표현식을 생성합니다. 이 경우 reporter.stories_filed가 나타내는 데이터베이스 필드를 증가 시키도록 데이터베이스에 지시합니다. reporter.stories_filed나 다른 어떠한 값이 있어도 데이터베이스에 의해 처리되기 때문에 파이썬은 알지 못합니다. 

위 방법으로 저장한 새 값을 얻기 위해선 다시 호출해야 합니다.

reporter = Reporters.objects.get(pk=reporter.pk)



위 구문을 .update()와 F() 객체를 사용하여 줄일 수 있습니다.

reporter = Reporters.objects.filter(name='Tintin')
reporter.update(stories_filed=F('stories_filed') + 1)


F()를 사용하면 race condition을 피할수 있다

F()의 또 다른 유용한 이점은 파이썬이 아닌 데이터베이스가 필드 값을 업데이트하면 race condition을 피할 수 있다는 것입니다. 

예를 들어, 첫 번째 예시(F()를 사용하지 않은 예시)를 두개의 파이썬 thread를 사용해 실행하면 A라는 thread가 값을 받아 파이썬 메모리에 저장할 때 B라는 thread는 값을 추출, 증가, 저장할 수 있습니다. A라는 thread는 값을 증가, 저장하게 되면 B 라는 thread가 작업했던 내용이 손실됩니다. (A는 B의 작업 이전에 값을 가져왔기 때문..)

F()를 사용하면 위 문제를 해결할 수 있습니다. 메모리에 가져온 값을 기반으로 작업하는 것이 아닌 save() 나 update()가 실행될 때, 데이터베이스의 필드 값을 기준으로 작업하기 때문입니다.

F()는 여러가지로 사용할 수 있다

filter()에서의 사용

SELECT a, b, c, d
FROM test
WHERE a = c


위와 같은 쿼리가 있다고 가정하면 F()를 사용하여 아래와 같이 표현할 수 있습니다.

from django.db.models import F

Test.objects.filter(a=F('c'))

annotate()에서의 사용 - 필드명 변경

SELECT a, b, c, d as dddd
FROM test


위처럼 d라는 컬럼의 이름을 dddd로 변경하고자 할 때 F()를 사용하여 아래와 같이 표현할 수 있습니다.


from django.db.models import F

Test.objects.annotate(dddd=F('d'))

요약

  • F()와 Value()는 연관이 없다.
  • Value()는 사실상 안써도 무관하다.
  • F()는 좋다.



참조문서:

https://docs.djangoproject.com/en/1.7/ref/models/queries/#f-expressions

https://docs.djangoproject.com/en/2.0/ref/models/expressions/#value-expressions

https://docs.djangoproject.com/en/1.7/topics/db/queries/#using-f-expressions-in-filters


import psycopg2.extras
self.conn = psycopg2.connect(host='127.0.0.1', dbname='postgres', user='postgres', password='postgres', port=5432)
self.cur = self.conn.cursor(cursor_factory=psycopg2.extras.DictCursor)


python 3.6 이하 버전에서는 datetime을 생성하면 timezone 정보가 없어 astimezone 같은 메소드를 실행하면 에러가 발생합니다. 지금부터 삽질해서 찾은 방법을 설명합니다. 


python 3.6은 datetime을 생성할 때 timezone을 지정하지 않아도 기본으로 내장으로 설정이 되어 있어 astimezone()를 실행해도 에러가 발생하지 않습니다. 하지만 3.6이하 버전은 datetime을 생성할 때 timezone을 지정하지 않으면 timezone이 None이므로 astimezone()을 실행하면 에러가 발생합니다. 따라서 결론은 datetime을 생성할 때 timezone을 지정하는 것입니다.


하지만 이미 생성된 datetime이라면 어떨까..

여기서 방법은 2가지로 나뉩니다.

datetime → time → datetime 으로 변환

먼저 datetime을 time타입으로 변환 시킨 다음, 이 값으로 timezone을 지정하여 datetime객체를 새로 생성하는 방법입니다.

이 방법은 아래에서 설명할 replace()함수가 지원되지 않는 python 버전에서 사용할 수 있습니다.

import datetime
import time

now_dt = datetime.datetime.now()
print(now_dt)

now_t = time.mktime(now_dt.timetuple()) + now_dt.microsecond / 1E6

print(now_t)

c_now_dt = datetime.datetime.fromtimestamp(now_t, datetime.timezone.utc)
print(c_now_dt)

# 결과
# 2017-10-15 18:45:32.357988
# 1508060732.357988
# 2017-10-15 09:45:32.357988+00:00

replace() 사용 (pyhton 3.3 이상)

위 방법은 상당히 귀찮으므로 python 버전이 3.3 이상이면 replace()함수를 사용합니다.

import datetime


now_dt = datetime.datetime.now()
print(now_dt)

c_now_dt = now_dt.replace(tzinfo=datetime.timezone.utc)
print(c_now_dt)

# 결과
# 2017-10-15 18:48:32.402106
# 2017-10-15 18:48:32.402106+00:00

주의점

위 두 결과를 보고 확인이 되듯이 replace()함수를 사용해 timezone을 입력하면 timezone은 변경이 되지만 utc 시간대로 변경되지 않습니다(!!)

아래는 python 3.6대에서 astimezone()도 추가하여 비교한 코드입니다.

import datetime
import time

now_dt = datetime.datetime.now()
print('현재 시간: ', now_dt)

now_t = time.mktime(now_dt.timetuple()) + now_dt.microsecond / 1E6
c_now_dt = datetime.datetime.fromtimestamp(now_t, datetime.timezone.utc)

print('datetime-> time -> datetime 변환시간: ', c_now_dt)

print('replace() 변환시간: ',now_dt.replace(tzinfo=datetime.timezone.utc))
print('astimezone() 변환시간: ',now_dt.astimezone(datetime.timezone.utc))


# 현재 시간:  2017-10-16 19:09:44.162545
# datetime-> time -> datetime 변환시간:  2017-10-16 10:09:44.162545+00:00
# replace() 변환시간:  2017-10-16 19:09:44.162545+00:00
# astimezone() 변환시간:  2017-10-16 10:09:44.162545+00:00


이것처럼 replace()를 사용할 때, utc 시간대로 변경되지 않는 다는 점을 숙지하고 사용해야 합니다.

결론

datetime 생성할떄 timezone과 timedeltal를 이용해서 필요한 타임존 설정을 해주면 모든게 해결됨. (근데 귀찮다)

파이썬 프로그래밍의 대부분은 데이터를 담은 클래스들을 정의하고 이 객체들이 연계되는 방법을 명시하는 일입니다. 모든 파이썬 클래스는 일종의 컨테이너로, 속성과 기능을 함께 캡슐화합니다. 파이썬은 데이터 관리용 내장 컨테이너 타입(리스트, 튜플, 세트, 딕셔너리)도 제공합니다.

시퀀스처럼 쓰임새가 간단한 클래스를 설계할 때는 파이썬의 내장 list 타입에서 상속받으려고 하는 게 당연합니다. 예를 들어 멤버의 빈도를 세는 메서드를 추가로 갖춘 커스텀 리스트 타입을 생성한다고 가정합니다.

class FrequencyList(list):
    def __init__(self, members):
        super().__init__(members)

    def frequency(self):
        counts = {}
        for item in self:
            counts.setdefault(item, 0)
            counts[item] += 1
        return counts


# list에서 상속받아 서브클래스를 만들어서 list의 표준 기능을 모두 갖춰 파이썬 프로그래머에게 시맨틱을 유지
# 또한 추가한 메서드로 필요한 커스텀 동작을 더할 수 있음


foo = FrequencyList(['a', 'b', 'a', 'c', 'b', 'a', 'd'])
print('Length is ', len(foo))
foo.pop()
print('After pop: ', repr(foo))
print('Frequency: ', foo.frequency())

# 결과
# Length is  7
# After pop:  ['a', 'b', 'a', 'c', 'b', 'a']
# Frequency:  {'a': 3, 'b': 2, 'c': 1}


이제 list의 서브클래스는 아니지만 인덱스로 접근할 수 있게 해서  list처럼 보이는 객체를 제공한다고 합니다. 예를 들어 바이너리 트리 클래스에 (list나 tuple같은) 시퀀스 시맨틱을 제공한다고 가정합니다.

class BinaryNode:
    def __init__(self, value, left=None, right=None):
        self.value = value
        self.left = left
        self.right = right


# 이 클래스가 시퀀스 타입처럼 동작하기 위해 어떻게 해야 할까?
#  파이썬은 특별한 이름을 붙인 인스턴스 메서드로 컨테이너 동작을 구현

bar = [1, 2, 3]
bar[0]

# 위와 같이 시퀀스의 아이템을 인덱스로 접근하면 다음과 같이 해석
bar.__getitem__(0)


# BinaryNode클래스가 시퀀스처럼 동작하게 하려면 객체의 트리를 깊이 우선으로 탐색하는 __getitem__을 구현

class IndexableNode(BinaryNode):
    def _search(self, count, index):
        # ...
        # (found, count) 반환
        pass

    def __getitem__(self, index):
        found, _ = self._search(0, index)

        if not found:
            raise IndexError('Index out of range')

        return found.value

tree = IndexableNode(10,
                     left=IndexableNode(5,
                                        left=IndexableNode(2),
                                        right=IndexableNode(6,
                                                            right=IndexableNode(7)
                                                            )
                                        ),
                     right=IndexableNode(15, left=IndexableNode(11)))

# 트리탐색은 물론 list처럼 접근 가능

print(tree.left.right.right.value)
print(tree[0])


# 결과
7
2


문제는 __getitem__을 구현한 것만드로 기대하는 시퀀스 시맨틱을 모두 제공하지 못합니다.

len(tree)# 에러

이러한 문제처럼 count, index 메서드 등 프로그래머들이 사용하는 기본 메서드를 전부 정의하기는 생각보다 어렵습니다. 


이런 어려움을 피하기 위해 내장 collections.abc 모듈은 각 컨테이너 타입에 필요한 일반적인 메서드를 모두 제공하는 추상 기반 클래스들을 정의합니다. 이 추상 기반 클래스들에서 상속받아 서브클래스를 만들다가 깜빡 잊고 필수 메서드를 구현하지 않으면, 모듈이 에러를 뱉어냅니다.

from collections.abc import Sequence

class BadType(Sequence):
    pass

foo = BadType()

# 결과
# TypeError: Can't instantiate abstract class BadType with abstract methods __getitem__, __len__


앞에서 다룬 SequenceNode처럼 추상 기반 클래스가 요구하는 메서드를 모두 구혀ㅑㄴ하면 별도로 작업하지 않아도 클래스가 index와 count 같은 부가적인 메서드를 모두 제공합니다.

요약

쓰임새가 간단할 때는 list나 dict같은 파이썬의 컨테이너 타입에서 직접 상속

커스텀 컨테이너 타입을 올바르게 구현하는 데 필요한 많은 메서드에 주의

커스텀 컨테이너 타입이 collections.abc에 정의된 인터페이스에서 상속받게 만들어서 클래스가 필요한 인터페이스, 동작과 일치도록


사전작업

virtualenv로 django를 설치한 경우

Pycharm 내의 터미널을 사용하실 경우 선택된 interpreter에 따라 자동으로 변경되므로 아래와 같은 작업을 할 필요가 없습니다.

예시) djangoDjango라는 virtualenv를 생성한 경우

$ cd virtualenv
$ source bin/activate
(virtualDjango) $         # virtualDjango라는 virtualenv 터미널에 접속한 상태
# virtualenv 접속해제는 deactivate
$ 프로젝트로 이동

터미널

settings 파일이 쪼개져 있지 않은 경우

$ python3 manage.py inspectdb > models.py (아무이름이나 가능)

settings 파일이 쪼개져 있는 경우

$ python3 manage.py inspectdb --settings=djangotest(프로젝트이름).settings.dev > models.py (아무이름이나 가능)

결과

# This is an auto-generated Django model module.
# You'll have to do the following manually to clean this up:
#   * Rearrange models' order
#   * Make sure each model has one field with primary_key=True
#   * Make sure each ForeignKey has `on_delete` set to the desired behavior.
#   * Remove `managed = False` lines if you wish to allow Django to create, modify, and delete the table
# Feel free to rename the models, but don't rename db_table values or field names.
from __future__ import unicode_literals

from django.db import models



class TCart(models.Model):
    cart_no = models.BigAutoField(primary_key=True)
    user_no = models.BigIntegerField()
    guest_session_key = models.CharField(max_length=-1)
    save_mileage_amount = models.BigIntegerField()
    delivery_amount = models.DecimalField(max_digits=16, decimal_places=2)
    coupon_sale_amount = models.DecimalField(max_digits=16, decimal_places=2)
    coupon_save_amount = models.DecimalField(max_digits=16, decimal_places=2)
    pay_mileage = models.BigIntegerField()
    pay_deposit = models.BigIntegerField()
    device_type = models.IntegerField()
    insert_timestamp = models.DateTimeField()
    updated_timestamp = models.DateTimeField()
    is_deleted = models.CharField(max_length=1)

    class Meta:
        managed = False
        db_table = 't_cart'
        unique_together = (('user_no', 'guest_session_key'),)

....

주의사항

Django로 가져온 테이블의 컬럼 필드와 postgreSQL 내 테이블의 컬럼 필드가 다를 경우가 존재합니다. (예를 들어, varchar(20)인데 text필드로 migrate가 되었다던지..) 따라서 생성한 후 확인 하는 것을 권장합니다.


len()
은 컨테이너에 포함된 항목의 수를 계산합니다. 다시 말해 문자열일 경우 문자의 길이를 반환하고 컨테이너타입인 튜플, 딕셔너리, 리스트의 경우는 속해있는 값의 개수를 반환합니다.

반면 sys.getsizeof()는 객체의 메모리 사이즈를 바이트 단위로 반환합니다. 객체는 모든 유형이 될 수 있습니다.

파이썬 문자열 객체는 문자 당 1바이트의 간단한 문자 시퀀스가 아닙니다. 특히, sys.getsizeof()함수에는 가비지 컬렉터의 오버헤드(아마 1바이트)가 포함되어 출력됩니다. 

getsizeof()는 object의 __sizeof__ 메서드를 호출하고 object가 가비지 컬렉터에서 관리되는 경우에는 가비지 컬렉터의 오버 헤드를 추가합니다.

예시

# python 3.4
import sys

en = 'a'
ko = 'ㅁ'

print('len()')
print(len(en))
print(len(ko))

# 결과
# 1
# 1

print('encode - utf8')
print(en.encode('utf-8'))
print(ko.encode('utf-8'))


# 결과
# b'a'
# b'\xe3\x85\x81'

print('sys.getsizeof()')
print(sys.getsizeof(''))
print(sys.getsizeof(en))
print(sys.getsizeof(ko))
 
# 결과
# 49
# 50
# 76


len()함수로 알파벳과 한글을 비교하면 길이가 1이 나옵니다. 파이썬3.x는 기본 유니코드를 utf-8로 사용합니다. 따라서 한글이나 다른 문자가 있으면 utf8로 해석을 합니다. 

이 문자열들을 utf-8로 encode하면 바이트 타입으로 변환이되고 알파벳은 1바이트, 한글은 3바이트로 표현이 됩니다.

마지막으로 sys.getsizeof()는 메모리에 실제로 올라가는 크기입니다. ''와 같이 빈 문자열도 49바이트가 차지하게 됩니다.


유니 코드 문자열의 경우 문자 별 크기는 최대 2 또는 4가됩니다 (컴파일 옵션에 따라 다름). Python 3.3 및 이후 버전에서 유니 코드 문자열은 문자열의 내용에 따라 문자 당 1에서 4 바이트를 차지합니다.

pyximport는 Cython의 한 부분입니다. import를 해서 사용할 수 있습니다. 만약 특별한 C 라이브러리를 요구하지 않거나 특별한 setup을 빌드하기를 원하지 않으면 pyximport 모듈을 사용하면 됩니다. pyximport을 import하면 setup.py을 작성하지 않고 .pyx파일을 직접 로드할 수 있습니다.

import pyximport; pyximport.install()
>>> import helloworld
Hello World


+ Recent posts