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

lambda(람다) 함수

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

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

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

왜 사용할까?

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

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

모든 람다 함수가 사용 후 메모리에서 증발할까?

위의 예시와 같은 경우는 람다함수(객체)를 sum이라는 변수에 저장합니다. 위의 설명대로라면 sum()를 사용 후 다음 줄에서 삭제가 되어야 하지만 실제로는 삭제되지 않습니다. sum이라는 변수의 메모리에 람다함수의 메모리가 저장되어 있어있기 때문입니다. 람다함수가 사용 후 메모리에서 증발하기 위해선 함수의 인자값에 전달 되었을 때 증발하게 됩니다. 또 함수 내부에서 사용하는 람다일 경우 해당 함수가 종료되는 순간 메모리에서 증발합니다.

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


  1. 현승우 2019.01.16 21:01

    안녕하세요 lambda 설명하실 때
    ' 익명함수이기 때문에 한번 쓰이고 다음줄로 넘어가면 힙(heap) 메모리 영역에서 증발하게 된다. '
    라고 하셨는데 sum 이라는 객체에 선언했기 때문에 예시에서는 증발하지 않는 것이 맞는지 질문 드립니다.
    다른 함수들의 func인자에 lambda를 쓰는 경우에만 사용하고 나면 증발하게 되는 것 아닌가요?

    • 불곰1 2019.01.16 23:15 신고

      안녕하세요.

      말씀하신 부분이 정확하게 맞는 것 같습니다. sum 이라는 변수에 익명함수의 주소 값을 넣게 되므로 사용 후 다음 줄로 넘어가도 메모리에서 사라지지 않는 것을 확인했습니다.
      예시를 주신 것처럼 함수의 인자값에 lambda를 사용해야 메모리에서 삭제가 됩니다.

      좋은 의견 감사합니다.

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가 되었다던지..) 따라서 생성한 후 확인 하는 것을 권장합니다.

+ Random Posts