2019.03.15 TIL

(TIL은 스스로 이해한 것을 바탕으로 정리한 것으로 오류가 있을 수 있습니다)

# 질문에 답하기

  1. 데코레이터에서 대해 이야기 해보기

데코레이터

데코레이터는 쉽게 기능을 추가할 수 있다.
누가 만들어 놓은 함수를 내가 만들고 있는 함수에 갖다 붙이면
그 기능이 추가되어 버린다.

ex)

from fancy import decofunc
@decofunc
def func(a,b): return a + b
  • 이렇게 하면 기능이 그냥 붙어 버림 decofunc의 기능이 실행되고 이후에 def func의 기능이 실행

데코레이터 기본 형태

def outer(org_func):       #org_func는 내가 만들고 있는 함수
    def inner(*args, **kwargs):  # 여기서는 가변인자이고 *과 **을 통해 패킹시킴
        print("added functionalty")
        return org_func(*args, **kwargs)  #실행에서는 *, ** 은 튜플과 딕셔너리로 들어오는 것을 unpacking한다는 것이다.
    return inner    

def func(a,b):
        return a+b

func=outer(func) -----> outer(func)는 return inner가 되고
org_func인자가 func로 됨에 따라서 inner내에서 func가 한번 더 리턴된다.

따라서 여기서 부터 func.name == inner가 된다.

func(10,2)을 실행하면
added functionalty가 되고
리턴값으로 12를 받는다. (inner함수 내부에서 실행)

이것을 나타내기 위해서는 print(func(10,2))가 되어야 한다.
매번 func= outer(func)를 적어줄 수는 없으므로

@outer
def func(a,b):
    return a+b    # 이것 같이 적어주어도 된다.

overloading , overriding

def f(a,b):
    return a+b
def f(a,b):
    return a*b
f(2,7) ===> 14

python에서는 덮어씌어지지만 java같은 것은 인자만 다르면 인정해준다. 이를 overloading이라고 한다. 하지만 파이썬에서는 overloading을 인정하지 않는다.

decorator

여기서 out 9가 나온것은 실제 출력값이 아니라 함수 내부적으로의 return 값이다.

미션 함수 실행 시간 계산하기

import time
def henchmarker(org_func):
    def inner(*args, **kwargs):
        start = time.time()
            result = org_func(*args, **kwargs)
            elapsed = time.time() - start
            print(f"elapsed time : {elapsed: .2f}")  #{elapsed: .2f}는 무엇일까?
            return result
        return inner



@henchmarker
def something(a,b):
    time.sleep(5) ----> 5초 동안 있다가 실행
    return a+b
something(1,2)

미션 callcounter

어떤 함수를 호출한 횟수를 보여준다.

g_call_num = 0
def callcounter(org_func):
    def inner(*args, **kwargs):
        result = org_func(*args, **kwargs)
        global g_call_num
        g_call_num += 1
        print(f"{g_call_num}번 호출되었습니다.")
        return result
        return inner


@callcounter
def func(a,b):
    return (a+b)


for _ in range(10):
    print(func(1,3))

주의사항 만약에 함수를 2개 연속으로 사용할 때

decorator2

from functools import wraps와 @wraps를 꼭 적어주어야지만

함수가 중복적으로 계산되지 않는다.

하나의 공식으로 알아두자.!!

'Python' 카테고리의 다른 글

파이썬 13. openpyxl  (0) 2019.12.28
파이썬 12. 객체와 클래스  (0) 2019.12.28
파이썬 10. filter, map, reduce  (0) 2019.12.28
파이썬 09. lambda  (0) 2019.12.28
파이썬 08. 클로저  (0) 2019.12.28

2019.03.14 TIL

(TIL은 스스로 이해한 것을 바탕으로 정리한 것으로 오류가 있을 수 있습니다)

# 질문에 답하기

  1. filter, map, reduce에 대해 이야기 해보기

filter

기본 문법

filter(f, iterable) 
  • filter는 여러개의 값들이 있을 때 특정한 기준에 대해 블리언을 적용하여 뽑아내는 것이다.

ex)

li = [-3, 5, 1, 2, -5, -4, 14] # 이 리스트 중에서 음수를 제거하고 싶다라고 하면
f = filter(lambda a: a>0, li) # filter을 활용하여 간단하게 끝낼 수 있다. but 하나씩 꺼낼 수 있다
  • 여기서 lazy evaluation이 나와야 한다.

lazy evaluation

lazy evaluation은 내가 원할 때 마다 결과값을 가지고 오는 것이다.
즉 함수의 실행시기를 내가 결정하게 된다.
이것이 filter, map, reduce, generator의 큰 특징이다.

filter, map, reduce는 모두 generator의 일종이다.

다시 돌아와서 위에

f = filter(lambda a: a>0, li) # next(f)라는 것으로 f에 해당하는 요소를 하나씩 뽑아낸다.

# 이것을 다 뽑아 보기 위해서는

for e in f:
    print(e) 

# 이것을 리스트로 만들기 위해서는

li = []

for e in f:
    li.append(e) 

# 혹은 한번에 적어줄 수도 있다.

result = []

for elem in li:
    if elem>0:
        result.append(elem)

# 같이 한번에 적어줄 수도 있게 되었다.
# 또 한줄로 적어줄 수도 있다. 

# 홀수 리스트로 한번에 뽑아내기

li2 = list(filter(lambda x: x%2, li)) # 한번에 리스트로 뽑아낸다.

# 짝수 리스트 한번에 뽑아내기

li2 = list(filter(lambda x:x%2==0, li))
  • filter는 특정한 f(x)에 대해 bloolean값을 필터하여 뽑아낸다.
  • 내가 다른 작업을 하다가 next(f)를 통해 하나를 뽑아내고 또 이후에 next(f)를 함으로서 그 다음 값들을 뽑아 올 수 있다. 메모리를 따로 쓰지 않는다는 장점이 있다.
  • 내가 함수의 실행시간을 결정할 수 있다는 것과, 메모리를 따로 저장하지 않고 바로 가지고 올 수 있다.
  • 내가 원하는 시점에 그때 작용(lazy evaluation)
  • 컴퓨터 프로그래밍에서 느긋한 계산법(Lazy evaluation)은 계산의 결과값이 필요할 때까지 계산을 늦추는 기법이다. 두 가지 관련된 항목들이 있는데 지연 계산법과 최소 계산법이다.
  • 느긋하게 계산하면 필요없는 계산을 하지 않으므로 실행을 더 빠르게 할 수 있고, 복합 수식을 계산할 때 오류 상태를 피할 수 있고, 무한 자료 구조를 쓸 수 있고, 미리 정의된 것을 이용하지 않고 보통 함수로 제어 구조를 정의할 수 있다.
  • 느긋한 계산법을 사용하는 언어들은 "이름으로 호출"하거나 "필요할 때 호출"하는 계산 전략을 사용하는 것으로 나눌 수 있다. 하스켈과 같은 대부분의 실제 느긋한 언어들은 성능상의 이유로 "필요할 때 호출" 전략을 사용한다. 그러나 느긋한 계산법을 이론적으로 표현한 것들은 간편하게 "이름으로 호출"하기도 한다.

map

map 기본 문법

map(f, iterable) #mapobject를 반환한다. map은 함수와 iterable을 받는다.
  • 특정한 f(x)를 적용하여 새롭게 값을 뽑아내는 것. 이것을 선형대수에서는 사상이라고 한다.

ex)

li = [2, 3, 4, 5] # 여기에 있는 값을 제곱을 하여 하나씩 뽑아내보자.
m = map(lambda x: x**2, li)
next(m)
  • 맵에서 나온것 맵객체 필터에서 나온건 필터객체 이건 generator 객체에 포함되고 이건 또 iterator에 포함된다. 따라서 generator로 순회할 수 있다. iterator는 포문을 쓸 수 있으므로 map과 filter 역시 포문을 쓸 수 있다.

filter와 map을 함꼐 사용하여 풀어야 하는 문제

li = [ 2, 3, -5, 6, -2, 1, -10] # 양수를 골라내어 제곱한 값을 리스트로 만드시오

a = list(map(lambda x: x**2 , filter(lambda y: y>0, li)))

reduce

자료구조 (list,tuple)를 연산을 통해서 단 하나의 값으로 만드는 함수이다.
reduce는 내장함수가 아니므로 import를 해서 불러줘야한다.

from functools import reduce
help(reduce)     #함수는 기본적으로 이름, 인자 , 반환값을 꼭 적어줘야한다.
    reduce(function, sequence[, initial]) -> value
    """
    여기서 function 에는 람다가
    sequence에는 자료구조(iterable)이 들어갈 것이고 initial은 초기값이 들어갈 것이다.
    그리고 return 값으로는 하나의 value가 나와야 한다.
    """

# reduce는 기본적으로 2개의 인자를 받는다.

li = [2, 3, -5, 6, -2, 1, -10]

result = reduce(lambda x,y : x+y, li)
result = reduce(lambda x,y: x+y, li, 100) # 100이 초기 x에 들어가도록 초기값 설정이 가능하다.

list의 최대 요소 구하기

li = [3,6,8,-10,2,1, 100, 50, 46, -47]
from functools import reduce
result = reduce(rambda x,y : x if x>y else y, li) (삼차다항식이용)

문자수 세기

li =["a", "b", "a", "b", "b", "a", "c", "a"]가 주어졌을 때
dic ={'a':4, 'b':3, 'c':1} ---->크롤링해서 좋아요 혹은 싫어요 핸들링 할 때 사용

위와 같이 dictionary를 뽑아내자.

  • 첫번쨰 참고 : 파이썬의 함수는 식이므로 무조건 값을 반환(None도 반환)

  • 두번째 참고 : 논리 연산을 할 때 마지막으로 참조한 값을 반환한다.[1,2] or [] ---> [1,2]라는 값을 반환한다.

  • dic.get("a",0) + 1 ---> value로 활용하고

  • dic.update({"a":3, "b" : 4})

  • 초기값 딕셔너리

import reduce
li =["a", "b", "a", "b", "b", "a", "c", "a"]
result  = reduce(lambda dic,cha : dic.update({cha : dic.get(cha,0)+1}) or dic, li, {}) 

dic.update는 None를 반환하므로 false이다 or dic을 통해 dic이 다시 위로 올라가도록 한다.

2019.03.13 TIL

(TIL은 스스로 이해한 것을 바탕으로 정리한 것으로 오류가 있을 수 있습니다)

# 질문에 답하기

  1. 람다함수에 대해 이야기해보기

lambda

lambda : 익명함수

즉 이름이 없다.
함수를 재사용하지 않고 몇 번 정도만 쓸 때 사용한다.(바로 문장 내에서 함수를 바로 실행할 때)

파이썬에서 함수는 "식"이다.
그리고 식은 (반환)값이 있다. 따라서 파이썬에서 모든 함수는 반환값을 가지고 있다.

10 역시 단항식이다.
'i am your father' 역시 식이다.

def func(a,b):
    return a+b         # 함수는 리턴을 하니깐 식이다 
def func(a,b):
    a+b              # 이것 역시 식이다. None을 반환한다.

print(print(func(10,20)) # None, None 


func 
<function __main__.func(a, b)>

lambda a,b: a+b         #같은 펑션이 메모리에는 생겼으나, 변수에 따로 할당해놓지 않으면 접근할 수 없다.  
<function __main__.<lambda>(a, b)>

lambda 기본 문법

lambda 한칸 띄우고 바로 쓴다. lambda a, b: return a + b #람다는 무조건 값을 반환하기 때문에 return을 적어주지 않는다. ---> lambda a,b: a+b --->이게 맞는 것이다.

f2 = lambda a,b:a+b #return이 명시되지 않았지만 암묵적으로 무조건 리턴이 있다.

lambda 사용법

  • 정렬에 사용하기
li = [5, 2, 3, 1, 7, 10]  # 짝수는 앞에 홀수는 위로 배열하고 싶다면

li.sort(key =  ,reverse=True) # sort에는 key가 들어갈 수 있고 정렬에 쓰이는 키를 pred key라고 한다.

# 구현
def pred(x):
    return x % 2 -----> 0과 1을 반환하는데 오름차순으로 하면 0이 먼저 오므로 짝수가 먼저 반환
li.sort(key=pred)
print(li)
[2, 10, 5, 3, 1, 7]

# 홀수 먼저 오고 싶다고 하면

li.sort(key=pred, reverse=True)
li
[5, 3, 1, 7, 2, 10]

#이제 이것을 람다를 활용해서 한번에 적어보자.

li.sort(key=rambda x: x%2, reverse =True)
li
[5, 3, 1, 7, 2, 10]

이 람다를 바탕으로 3가지를 추가적으로 배운다.

  • filter
  • map
  • reduce

###2019.03.12 TIL

클로저

클로저(closure)은 닫혀있다는 의미다.
우리가 일반적으로 배운 함수는 output이 input에 의존하여 나와야 하나, 이제는 함수에 초기 값을 넣어주면
그게 상태정보가 되고 그 상태정보에 따라 내부에서 함수를 호출했을 때 새로운 input과 내부의 상태정보를 함께 연산하여 새로운 출력값을 나타낼 수 있게 된다.
즉 함수 내부에 상태 정보를 클로징하고 있다하여 클로저라고 부른다.
원래 함수는 실행 이후에 스택프레임이 사라지면서 사라져야 하지만 클로저는 주도권만 주고 그대로 살아 있다.

전제조건

파이썬은 first class function을 지원한다.

first class function

  1. 매개변수로 함수를 줄 수 있다.
  2. return 값으로 함수를 줄 수 있고 함수 내부에 함수 정의 가능
  3. 함수를 변수에 할당할 수 있다.

따라서 함수 내부에 새로운 함수를 정의할 수 있다.
이것을 기본 전제조건을 가지고 클로저를 만들기 위한 조건이 있다.

클로저 생성 조건

그럼 이제 파이썬에서 클로저를 만들기 위한 조건을 정리해보자.

  • 중첩 함수(Nested Function)를 갖는다.
  • 중첩 함수는 자신을 감싸고 있는 함수 영역(부모함수)의 변수를 참조하고 있다.
    • 만약 부모 함수의 변수를 변경하고 싶으면 nonlocal로 참조한다고 설정해주어야 한다.
  • 부모함수는 중첩 함수(자식 함수)를 반환한다.
  • 새로운 변수에 부모함수를 할당해주어야 한다.
    • 새로운 변수에 새로운 값을 넣게 되면 내부 상태정보와 함께 새로운 출력값을 나타낸다.

클로저 예시)

def account(clnt_name, balance):  
    def change_money(money):   #매개변수도 지역변수 이다.
        nonlocal balance            #부모 함수의 변수를 변경하기 위해 nonlocal을 사용한다.
        balance += money      #balance 는 account의 지역변수이다. global도 아니다 
        return (clnt_name, balance)
    return change_money
my_acnt=account("greg", 5000)    #원래라면 함수 호출 이후에 스택프레임이 사라져야 하지만
my_acnt(1000)   # ('greg', 6000)   #내부의 상태정보를 가지고 있다가 새로운 출력값을 나타낸다.

클로저는 class를 사용할 수 없을 때 쓴다????

파이썬에서는 class를 지원하기 때문에 클로저의 효용가치가 많이 떨어진다.
왜냐하면 굳이 클로저로 구현하지 않고 클래스를 만들어서 클래스 내부에 함수를 정의해 놓으면 언제든지 객체를 만들어서 해당 인스턴스를 불러올 수 있기 떄문이다.
따라서 클로저 같은 경우 class를 지원하지 않는 자바스크립트와 같은 언어에서 class를 재현하기 위해 사용한다. 즉 프로토타입 기반의 언어인 자바스크립트는 클로저를 통해서 클래스 기반 언어처럼 캡슐화, 모듈화 작업을 수행할 수 있다.

참조 : 자바스크립트 클로저(Closure)

2019.03.08 TIL

(TIL은 스스로 이해한 것을 바탕으로 정리한 것으로 오류가 있을 수 있습니다)

오늘의 한마디:

지금 내 상황을 객관적으로 바라보고, 반성하자.

지금 온전히 집중할 수 있는 지금 이 순간이 내 인생의 최고이다.

if you think you can or if you think you can't you are right.

First class function

first class function란 프래그래밍 언어가 함수를 first class citizen으로 취급하는 것을 뜻한다. 그렇게 취급을 해주면 뭐가 좋을까?

일단 기본적으로 first class function은 3가지 기능을 충족해야 한다.

  1. 함수를 인자로 쓸 수 있는가?

    • 매개변수는 parameter 할당인자는 argument이다. 이 둘을 혼동해서 쓰기도 하는데 명확한 것은 argument은 함수 안에 인자로 전달되는 명확한 값이고 매개변수는 초기 함수에 지정되는 인자이다.
  2. 함수가 변수에 할당될 수 있는가?

    • a = func() 처음 함수가 변수에 직접 할당될 수 있는가에 대한 문제
  3. 함수를 리턴할 수 있는가?

    • 함수 안에서 return func() 처럼 특정함수를 리턴할 수 있는가에 대한 문제

위의 3가지 조건을 모두 만족하게 되면 first class function을 특정언어에서 지원한다고 할 수 있다.

파이썬은 first class function을 지원한다.

그럼 이제 각각의 예를 들어서 설명해보자.

1. 함수를 매개변수(인자)에 할당할 수 있는가?

def f(a,b):
    return (a+b)

def g(func, c, d):
    return func(c,d)

a = 10
b = 20
g(f , a, b) # 해주면 과연 값이 나올까?

30

값은 30을 반환한다. ---> 이를 통해서 우리는 func가 parameter로 쓰인다는 것을 알 수 있다.

2. 함수를 변수에 직접 할당 할 수 있는가??

def f(a,b):
    return (a+b)

g_var = f()
g_var(20, 30) 

값은 50을 반환한다. ----> 이를 통해서 우리는 runc가 변수에 직접 할당될 수 있는 것을 확인할 수 있다.

3. 함수를 리턴할 수 있는가?

firstclassfunction

2019.03.07 TIL

(TIL은 스스로 이해한 것을 바탕으로 정리한 것으로 오류가 있을 수 있습니다)

# 질문에 답하기.

  1. 잘못된 정보를 입력했을 때 지속적으로 새롭게 실행하도록 만드는 방법

재귀함수

  1. 자기가 자기 자신을 호출하는 함수 ---> 함수 내부에서 자기 자신을 또 호출

  2. 기저 조건(base case) 필요 ---> 탈출 조건

  3. 해결방안 - 점화식을 먼저 세우고 기저조건을 세운다.

항상 다시 시작될 자신을 호출 할 때는 return 값으로 호출 해줘야 한다.

예)

def func(num):
    func(num -1)

func(5) ---> 스텍프레임이 계속 쌓여 가므로 스텍이 터지게 된다. stack over flow

위의 예를 기저조건이 없다. 해결하기 위해 기저조건을 설정

def func(num):
    if num <= 0:
        return
    print(num)
    func(num -1)

func(5)
5
4
3
2
1

재귀함수 응용

factorial
5! = 1 x 2 x 3 x 4 x 5

5! = 4! x 5
= 3! x 4 x 5
= 2! x 3 x 4 x 5
= 1! x 2 x 3 x 4 x 5

factorial

전제조건 :

  1. 점화식 -- > fac(num) = fac(num -1 ) x num

  2. 기저조건 ---> if num == 1 then return 1

def factorial(num):
    if num == 1:
        return 1

    return factorial(num-1) * num     #여기에서 return이 안들어가면 실행이 안됨

factorial(5) ===> 120

for num in range(1,10):
    print(factorial(num), end=" ")   ===> 뒤에 " "을 붙여서 뛰어쓰게 해줌

1 2 6 24 120 720 5040 40320 362880

fibonacci

fibonacci

  1. 점화식 ---> fibo(num) = fibo(num-2) + fibo(num-1)

  2. 기저조건 num = 1 or num = 2 then return 1

예 1)

def fibo(num):
    if num == 1 or num == 2:
        return 1
    return fibo(num-2) + fibo(num-1)

예2)

def fibo_iteration(n):
    # 기저 조건 li에 설정
    li = [0, 1]
    a = 0
    while len(li) < n:  # 반복문으로 구현
        result = li[a]+li[a+1]
        li.append(result)
        a = a + 1
    return li[n-1]

예3)

def fibo_iteration_2(n):
   first = 0
   second = 1
   if n==1:
       return first
   elif n==2 :
       return second
   else :
       for i in range(2,n):
           first, second = second, first+second
   return second

위와 같이 하면 fibonacci 수열이 만들어 지게 된다.

for i in range(1, 11):
    print(fibo(i), end = "   ")

와 같이 하면 피보나치 수열의 나열을 볼 수 있다.

하지만 피보나치 수열은 재귀적으로 구현하게 되면 지속적으로 계산해서 나왔던 것을 또 구하고 또 구하는 비효율이 발생한다. 이것을 위해 스택을 활용할 수 있다.

스택을 활용한 피보나치 구현

  • 피보나치 함수를 재귀함수를 통해 구현하되 내부에 캐시로 사용할 자료구조를 두고
    한번 호출되었던 값은 캐시에 저장해두었다가 다음번에 다시 같은 매개변수가 전달되면
    연산하지 않고 캐시에 저장된 값을 바로 반환하도록 구현하십시오.
def make_fibo():
    cache = [0, 1]

    def fibo_recursion(n):
        if fibo_recursion(n) in cache:
            return fibo_recursion(n)
        elif n == 1:
            return 0
        elif n == 2:
            return 1
        return fibo_recursion(n-2) + fibo_recursion(n-1)
        cache.append(fibo_recursion(n))
    return fibo_recursion


if __name__ == "__main__":
    fibo = make_fibo()
    for i in range(1, 11):
        print(fibo(i), end="  ")

하노이 타워

하노이 타워는 개인적으로 풀지 못해 굉장히 아쉬운 문제이다.

내가 그만큼 직접 해보면서 이것을 느꼈었는데 이런 걸 해결하지 못한게 아쉬울 따름이다.

하노이 타워에서 중점적으로 봐야하는 것은

  1. num == 1 일 때 어떻게 해야 하는지

    1. num == 1 일 때는 시작지점에서 도착지점으로 가야 한다.
  2. 마지막 n이 나오기 전에 타워는 어디로 이동해야 하는지

    1. 마지막 n은 항상 도착지점으로 가야 하므로 n-1의 타워는 중간 지점으로 가야 한다.
  3. 마지막 n을 어디에 이동시켜야 하는지

    1. 마지막 n은 항상 바로 시작지점에서 도착 지점으로 가야 한다.
  4. 마지막 n을 옴긴 이후에 n-1은 어떻게 해야 하는지

    1. n-1의 중간지점을 다시 시작점으로 하여 by를 거쳐 end로 가야 한다.

위를 바탕으로 하노이타워를 만들어보자

def hanoi(num , start, by, end):
    if num == 1:
        print(f"{num}번째 도형을 {start}에서 {end}로 옴겨주세요")  #1번조건
        return ---->굉장히 중요한 조건이다.
    hanoi(num -1, start, end, by)   #2번 조건
    print(f"{num}번째 도형을 {start}에서 {end}로 옴겨주세요") #3번 조건
    hanoi(num -1, by, start, end)  #4번 조건 

hanoi(5, "a", "b", "c")

1번째 도형을 a에서 c로 옴겨주세요
2번째 도형을 a에서 b로 옴겨주세요
1번째 도형을 c에서 b로 옴겨주세요
3번째 도형을 a에서 c로 옴겨주세요
1번째 도형을 b에서 a로 옴겨주세요
2번째 도형을 b에서 c로 옴겨주세요
1번째 도형을 a에서 c로 옴겨주세요
4번째 도형을 a에서 b로 옴겨주세요
1번째 도형을 c에서 b로 옴겨주세요
2번째 도형을 c에서 a로 옴겨주세요
1번째 도형을 b에서 a로 옴겨주세요
3번째 도형을 c에서 b로 옴겨주세요
1번째 도형을 a에서 c로 옴겨주세요
2번째 도형을 a에서 b로 옴겨주세요
1번째 도형을 c에서 b로 옴겨주세요
5번째 도형을 a에서 c로 옴겨주세요
1번째 도형을 b에서 a로 옴겨주세요
2번째 도형을 b에서 c로 옴겨주세요
1번째 도형을 a에서 c로 옴겨주세요
3번째 도형을 b에서 a로 옴겨주세요
1번째 도형을 c에서 b로 옴겨주세요
2번째 도형을 c에서 a로 옴겨주세요
1번째 도형을 b에서 a로 옴겨주세요
4번째 도형을 b에서 c로 옴겨주세요
1번째 도형을 a에서 c로 옴겨주세요
2번째 도형을 a에서 b로 옴겨주세요
1번째 도형을 c에서 b로 옴겨주세요
3번째 도형을 a에서 c로 옴겨주세요
1번째 도형을 b에서 a로 옴겨주세요
2번째 도형을 b에서 c로 옴겨주세요
1번째 도형을 a에서 c로 옴겨주세요

와 같이 하면 하노이 문제를 풀 수 있다.

참고 사이트

###2019.03.07 TIL

(TIL은 스스로 이해한 것을 바탕으로 정리한 것으로 오류가 있을 수 있습니다)

대답해보기

========================

  • 전역변수, 지역변수
  • 함수에서 mmutable, immutable에 대해서

개발자가 하는 일

컴퓨터 사이언스 부트캠프 with 파이썬이라는 책을 쓰신 양태환 강사님께서 요즘 강의를 해주시고 계신다. 그리고 나는 TIL에 양태환 강사님께 배운 내용들을 바탕으로 글을 적고 있다.

양태환 강사님이 말씀하신 프로그래머가 하는 단 하나의 일은 추상화(abstraction)된 것(예를 들면 기획, 생각 등)을

인터페이스로 어떻게 설계할 것인지 그리고 그것을 어떻게 implementation(구현) 할 것인지이다.

인터페이스(interface)는 서로 다른 두 개의 시스템, 장치 사이에서 정보나 신호를 주고받는 경우의 접점이나 경계면이다. 즉, 사용자가 기기를 쉽게 동작시키는데 도움을 주는 시스템을 의미한다.

아직 명확히 글의 뜻은 이해 못하겠지만 구글링으로 관련 자료를 가지고 와 보았다.

인터페이스와 구현

객체 지향 디자인의 목표 중에 하나는 소프트웨어를 더 유지보수 가능하게 만드는 것이다. 다시 말해서 시스템의 다른 부분이 변경됐을 때나 프로그램을 새로운 요구사항에 맞춰 수정했을 때도 프로그램이 동작해야 한다는 뜻이다.

이러한 목표를 이루기 위한 디자인 원칙은 인터페이스와 구현을 분리하여 유지하는 것이다. 객체라면 클래스에서 제공하는 메서드가 속성의 표현 방식에 의존하지 않아야 한다는 뜻이다.

예를 들어 이 장에서 우리는 시간을 나타내는 클래스를 개발했다. 이 클래스가 제공하는 메서드에는 time_to_int, is_after, add_time이 있다.

이들 메서드는 몇 가지 방법으로 구현할 수 있다. 세부 구현은 시간을 어떻게 표현하는가에 따라 다르다. 이 장에서 Time 객체의 속성은 hour, minute, second가 있다.

대안으로 이들 속성을 자정부터의 경과 시간을 초로 표현한 정수 하나로 대체할 수 있다. 이러한 구현은 is_after 같은 메서드를 더 쉽게 작성할 수 있지만, 어떤 메서드는 구현하기가 더 어려워지기도 한다.

새 클래스를 배포한 후에 더 나은 구현을 발견할 수도 있다. 프로그램의 다른 부분에서 이 클래스를 사용하고 있다면 이 인터페이스를 변경하는 데 시간이 오래 걸리고 오류가 발생할 수 있다.

인터페이스를 주의 깊게 설계한다면 인터페이스를 변경하지 않아도 구현을 변경할 수 있다. 즉, 프로그램의 다른 부분은 변경하지 않아도 된다.

출처 https://thebook.io/006878/ch17/10/ [바로가기] (https://thebook.io/006878/ch17/10/)

아무튼 인터페이스 설계에서 한 가지의 예를 들면 함수 시그니쳐를 들 수 있다.

함수 시그니쳐

  1. 함수의 이름 - 함수의 기능을 명확하게 표현하는 함수 이름을 설정해야 한다.

  2. 파라미터 - 매개 변수를 어떻게 설정하고 받을 것인지

  3. 결과값 - 어떤 결과값을 반환해줄 것인지

위와 같은 정보가 함수를 설계할 때 꼭 들어가야 한다는 것이다.

예)

함수 시그니처

뒤늦게 정리하다 보니 선생님이 말씀해주신 좋은 설계와 나쁜 설계가 떠오른다.

open-closed

###확장에 대해서는 open 되어 있고 기존 코드의 수정에 대해서는 closed되어 있다.

클래스 간의 계층 구조를 만들고 설계하는 것

좋은 설계와 나쁜 설계

  • 다 크게 만들어 놨는데 클라이언트가 특정 기능을 추가해달라고 했는데 미친듯이 클래스가 변경해야되면 나쁜 설계이다.
  • 좋은 설계는 (궁극적인 목표) 기능이 추가되는 그 클래스 한개만 변경이 되고 나머지는 완벽히 닫혀져 있는 상태(Open-closed)
  • 이것을 위해 design pattern이 필요하고 이게 GOF(gang of Four) - 20개 이렇게 하면 open-closed에 근접해 갈 수 있다.
  • 이것을 가기 위해 가장 밑바닥의 지식이 상속이다.

packing와 unpacking

container = 1, 2, 3, 4, 5 라고 예를 들어보자.

type(container) == tuple 로 나온다.

container라는 이름에 여기에 할당된 값 객차가 5개가 있어 자연스럽게 튜플로 묶어 준다. 이를

packing이라고 한다.

이것을 a와 b라는 이름에 나누어 다시 할당하기 위해서는 특별한 기호가 필요하다

a, *b = container 라고 해주면

a = 1 ===> type(a) == int

b = [2,3,4,5]가 들어가게 된다. === > type(b) == list (한개를 제외한 나머지 전부, 뒤에 c가 있으면 [2,3,4]만 받게 된다.

2가의 타입은 다르지만(왜 다르게 들어가는지는 알 수 없지만) 나누어 할당이 가능하다.

그럼 이것은 내부적으로 어떻게 되는 것인지? 새로운 값 객체가 할당되는 것인가?

이부분에 대해서는 선생님도 새로운 값 객체가 형성되는 것인지 아니면 그냥 값 객체를 나누게 되는지는 잘 알 수 없다고 하신다.

단 한가지 알게 된 것은

container = 1,2,3

a = container 이라고 하면 파이썬에서 이중 할당이 되지 않기 때문에

a도 1,2,3이라는 값 객체를 가르키게 되는 것이다.

a, b, *c = container 역시 가능하다. ===> a=1, b=2, c=[3,4,5]가 된다.

이를 unpacking (다시 풀어준다) 이라고 한다.

unpacking의 한가지 예를 더 들어보면

>>>    dic = {"a" : 1, "b" : 2}
>>>    for k, v in dic.items():
>>>        print(k,v)
>>>    # a 1
>>>    # b 2
>>> 

이것 역시 unpacking의 한 종류로 볼 수 있다.

함수에서 가변인자를 받도록 인터페이스 설계하기

  • 만약에 a,b 2개가 아닌 정해져 있지 않은 숫자의 인자를 받아서 모두 더하게 하려면 어떻게 해야 할까?
>>>    def sum_int(a,b):
>>>        s = a+b
>>>        return s        
>>>    

즉 위와 같이 단순한 형태가 아닌 여러개의 parameter를 받을 때는 어떻게 해야할까?
ex) sum_int(1,2,3) / sum_int(1,2,3,4) 등.

이것을 가능하게 하기 위해서는 가변인자를 사용해야 한다.

>>>    def sum_int(*args):
>>>        print(args)                
>>>
>>>    sum_int(1,2,3,4,5)         
>>>    #(1,2,3,4,5) 즉 튜플로 된 (1,2,3,4,5)를 얻게 된다. 따라서 우리가 원하는 위의 문제를 해결하기 위해서는
>>>    # 튜플로 된 것을 unpacking 작업을 거쳐야 한다.
>>>
  • 해결방안
>>>    def sum_int(*args):
>>>        s = 0
>>>        for i in args:
>>>            s += i
>>>        return s
>>>
>>>    sum_int(1,2,3,4)
>>>    # 10이라는 원하는 값을 얻을 수 있다.
>>>

만약에 li = [1,2,3,4] 를 만들어서 sum_int(li)를 하게 되면 어떻게 될까?

>>>    li = [1,2,3,4]
>>>    def sum_int(*args):
>>>        s = 0
>>>        for i in args:
>>>            s += i
>>>        return s
>>>    sum_int(li)
>>>
>>>    #TypeError: unsupported operand type(s) for +=: 'int' and 'list'

위와 같이 테입에러가 발생하게 된다. 왜냐하면 위의 식을 다시 가져와

>>>    def sum_int(*args):
>>>        print(args)                
>>>
>>>    print(li)   #를 해보면 args가 ([1,2,3,4])로 packing 된 것을 볼 수 있다.
>>>

위의 문제를 해결하기 위해서는 li를 다시 unpacking하도록 해줘야하는데 굉장히 간단하다.

>>>    li = [1,2,3,4]
>>>    def sum_int(*args):
>>>        s = 0
>>>        for i in args:
>>>            s += i
>>>        return s
>>>    sum_int(*li)                            *li를 unpacking해주면 된다.
>>>    #10

위와 같이 li에 * 하나만을 추가하여 해결 할 수 있다.

그럼 여기서 sum_int(1, 2, 3, age = 100, weight = 100) 와 같이 특수 한 경우를 처리하기 위해서는 어떻게 해야 할까?

실행 해보면 TypeError: sum_int() got an unexpected keyword argument 'age'

타입에러가 나오게 되면서 기대하지 않았던 keyword argument인 'age'가 나왔다는 메시지를 접하게 된다.

이 방법을 해결하기 위해 keyword argument를 받아 줄수 있는 가변인자를 한개 더 설정해줘야 한다. **kwargs로 가변인자를 설정해준다.

>>>    def sum_int(*args, **kwargs):
>>>        print(args)
>>>        print(kwargs)
>>>
>>>    sum_int(1,2,3, age = 100, weight = 100)
>>>    #(1,2,3)
>>>    #{"age" = 100, "weight" = 100} 로 프린트 해준다.
>>>

다시 위의 식을 가지고 와서

>>>    def sum_int(*args, **kwargs):
>>>        s = 0
>>>        for i in args:
>>>            s += i
>>>        return s
>>>    sum_int(1,2,3, age=100, weight=100)
>>>    #6 을 잘 반환해준다.
>>>        
packing

위에서 보듯이 6을 return 하지만 age와 weight는 사라진 것이 아니라 출력되지 못한 것이다. 함수안에서 print로 kwargs를 해주면 볼 수 있다.

단 예외가 있는데

sum_int(1, 2, 3, age = 100, weight = 100, 4)를 해보면

SyntaxError: positional argument follows keyword argument

센텍스 에러가 뜨면서 positional argument가 keyword argument를 따라왔다는 오류 메시지가 나온다.

  • 추가적으로 고민해보면 좋은 것
>>>    li = [3,4,5,6]
>>>    dic = {"a" : 1, "b" : 2}
>>>    
>>>    def sum_int(*args, **dic):
>>>        print(args)
>>>        print(dic)
>>>
>>>    sum_int(li, dic)
>>>    #([3,4,5,6] ,{"a":1, "b":2}), {}
>>>
>>>    sum_int(*li,*dic)
>>>    # (3,4,5,6) , {"a":1, "b":2}
>>>

왜 위와 같이 되는지 고민해 보면 좋을 것 같다.

주의할 점

sum_int(* li, ** dic) ---> 함수를 호출할 때 * 와 ** 은 언패킹을 의미한다.

삼항 연산자

  • 사용방법

    참인경우 값 if 조건 else 거짓인경우 값

  1. 연산 대상의 개수에 따라 연산자를 분리하면 단항 연산자, 이항 연산자, 삼항 연산자로 분리 합니다.

  2. 단항 연산자는 부호(+, -), not 등이 있으며 +, -, *, / .... 등 대부분의 연산자가 이항 연산자 입니다.

  3. 삼항 연산자는 1개가 존재한다.

[참고] (https://wikidocs.net/20701) (https://wikidocs.net/20701)

  • 말은 어렵지만 예를 보면 간단해진다.
>>>    a = 10
>>>    if a > 10:
>>>        print("good!")
>>>    else:
>>>        print("bad!")
>>>
>>>    # 삼항 연산자
>>>    print("good!") if a>10 else print("bad!")
>>>

###2019.03.06 TIL

(TIL은 스스로 이해한 것을 바탕으로 정리한 것으로 오류가 있을 수 있습니다)

대답해보기

========================

  • 전역변수, 지역변수
  • 함수에서 mmutable, immutable에 대해서

function

  • 예를 바탕으로 설명을 진행한다.
>>>    a = 10
>>>    
>>>    def func():
>>>        a = 20
>>>        def inner():
>>>            global a
>>>            a = 30
>>>
>>>    print(a)           # a = 10 출력 (func()가 실행되지 않음)
>>>
>>>    def func():
>>>        a = 20
>>>        def inner():
>>>            global a
>>>            a = 30
>>>    
>>>    func()                    # func()의 a는 global a를 참조하고 있지 않다.
>>>    print(a)            # a = 10 출력 (func()는 실행되었으나 inner()가 실행되지 않음)
>>>
>>>    def func():
>>>        global a
>>>        a = 20
>>>        def inner():
>>>            a = 30
>>>    
>>>    func()
>>>    print(a)                # a = 20 출력    (func()는 실행되었으나 inner()가 실행되지 않음)
>>>
>>>    def func():
>>>        global a
>>>        a = 20
>>>        def inner():
>>>            a = 30
>>>        inner()
>>>    
>>>    func()                    # inner의 a는 global a를 참조하고 있지 않다.
>>>    print(a)                # a = 20 출력 (inner()역시 실행되나 , global a를 참조하고 있지 않다)
>>>
>>>    def func():
>>>        a = 20
>>>        def inner():
>>>            global a
>>>            a = 30
>>>        inner()
>>>
>>>    func()                    # inner의 a는 global a를 참조하고 있다.
>>>    print(a)                 # a = 30 출력 (func()를 실행하며 inner() 역시 같이 실행 되었다.)
>>>    
>>>    
  • 참조를 위해서는 계속 namespace를 위로 이동가능하다.
  • 하지만 수정을 위해서는 단계에 따라 꼭 nonlocal 혹은 global 이라는 호출이 필요하다.
  • 네임 스페이스는 local namespace ---> global namespace ---> Built - in namespace로 올라간다.

네임스페이스에 관한 예를 더 들어보자

>>>    a = 10
>>>    def outer():
>>>        b = 20
>>>        def inner1():
>>>            b = 30
>>>            def inner2():
>>>                d = 40
>>>                b = 50
>>>                print(b)
>>>            ineer2()
>>>            print(b)
>>>        inner1()
>>>    outer()                        # 50 와 30이 출력된다.
>>>    
>>>
>>>    a = 10
>>>    def outer():
>>>        b = 20
>>>        def inner1():
>>>            b = 30
>>>            def inner2():
>>>                d = 40
>>>                nonlocal b       # local namesapce의 b를 참조한다. 
>>>                b = 50
>>>                print(b)
>>>            inner2()
>>>            print(b)
>>>        inner1()
>>>    outer()                        # 50 와 50이 출력된다.
>>>        
>>>

위의 이야기를 명확하게 이해할 수 있으면 namespace에 대한 개념이 좀 더 명확해졌다고 할 수 있다.

함수의 스텍프레임

함수를 실행하게 되면 메모리에 스텍프레임이라는 공간이 형성되고 스텍프레임 안에 변수와 값들이 저장되게 된다.
이 스텍프레임은 함수 호출이 끝나면 사라진다. 스텍프레임은 메모리를 공부한 이후에 좀 더 깊게 살펴보기로 한다.

>>>    g_var = 20
>>>    def func(val):
>>>        val += 100
>>>        return val
>>>
>>>    func(g_var)                        # 120이라는 return 값을 낸다.
>>>    print(g_var)                        # 20이 출력된다.
>>>
>>>

위의 함수를 실행함으로 g_var는 120으로 바뀌지 않는다. 왜 그럴까?

처음 g_var = 20 이라고 할당되므로서 20이라는 값 객체가 메모리 저장된다. g_var는 그 20이라는 값 객체를 가르키는 변수이다. func(g_var)를 실행하게 되면 func라는 스텍프레임이 생기게 되고 그 스텍프레임 안에서 val라는 새로운 변수가 20을 가르키게 된다. 숫자는 immutable하기 때문에 그 val에 100을 더해 120이라는 새로운 값 객체를 만들어서 가르키게 된다. 그리고 함수 실행이 끝나게 되면 func의 스텍프레임이 사라지게 되므로 val = 120이라는 값도 사라지게 된다.
따라서 g_var는 그대로 20이 출력되게 된다.

stackframe

위의 그림이 100% 정확하진 않지만 이해하는데 도움은 줄 수 있다.(파이썬에서는 값이 저렇게 들어가는 것이 아니라 가리키는 형태로 나와야한다.)

어떤 사람은 g_var가 120으로 바뀌었으면 할 것이고, 또 누군가는 g_var는 그대로 두되 저 함수를 통해서 실행된 값들을 받고 싶을 것이다.

2가지의 해결책을 보자

  1. g_var의 값을 120으로 바꾸기
>>>    g_var = 20
>>>    def func():
>>>        global g_var
>>>        g_var += 100
>>>        return val
>>>
>>>    func()                    
>>>    print(g_var)                    # 120이 출력된다.
>>>

전역 변수를 불러옴으로서 해결할 수 있다.

  1. g_var는 그대로 두되 저 함수를 통해서 실행된 값들을 받자.
>>>    g_var = 20
>>>    def func(val):
>>>        val += 100
>>>        return val
>>>
>>>    a = func(g_var)                        # 120이라는 return 값을 낸다.
>>>    print(a)                            # 120이 출력된다.
>>>    print(g_var)                        # 20이 출력된다.
>>>
>>>

해결책은 간단하다. 새로운 변수 a를 통해 func(g_var)의 값을 받아오면 된다.

그럼 여기서 또 하나의 의문이 생긴다. 과연 mutable한 list나 dictionary가 오게 되면 바뀔까?

한번 해보면 된다 ㅎㅎ.

>>>
>>>    li1 = [1,2,3]
>>>    def func(li, i):
>>>        li.append(i)
>>>        return li
>>>
>>>    func(li1, 4)
>>>    print(li1)             # [1,2,3,4]
>>>
>>>
list stackframe

너무나도 쉽게 변경되는 것을 알 수 있다.

결론을 내리자면 함수에서 immutable한 객체는 새로운 값 객체를 생성하여 할당하며, mutable한 객체는 기존의 객체를 변경하여 할당한다.

###2019.03.06 TIL

(TIL은 스스로 이해한 것을 바탕으로 정리한 것으로 오류가 있을 수 있습니다)

컴프리헨션 & 연산자

========================

대답해보기

  • 컴프리헨션이란?
  • 연산자의 종류 및 각각 간단하게 이야기해보라

어제 복습을 덜 한 슬픔을 뒤로 하고 공부했던 것들을 조금 더 정리해보고자 한다.

오늘의 보너스

  • 프로그램에서 a = 1 은 할당이고 a == 1 이 같다는 표현이다.

  • 파이썬에서의 False

    -False / None / "" / [] / {} / set()

컴프리헨션

컴프리헨션은 하나 이상의 이터레이터(iterater 반복적으로 하나씩 나올 수 있는 객체[list, string, dic. set, tuple 등]로 부터 파이썬의 자료구조를 만드는 컴팩트한 방법이다.

리스트컴프리헨션

li = [i for i in range(1, 10)] ---> 1부터 10전까지의 i로 리스트 만들기

딕셔너리 컴프리헨션

원래 딕셔너리 만들기 위해서는 아래와 같이 만들 수 있다.

>>>    tu = (("a",1), ("b",2))
>>>    dic = {}
>>>    for k,v in tu:
>>>        dic[k] = v
>>>    #위와 같이 만들 수 있다. 하지만 컴프리헨션을 이용하면 한 문장으로 만들 수 있다. 
>>> 
>>>    dic = {key:value for key, value in tu} 

하지만 컴프리헨션을 통해 한 문장으로 딕셔너리를 만들 수 있다.

오퍼레이터(연산자)

bit 연산자

연산자

위의 진리표에 따라 비트 단위로 2진법 비교

  1. bin(0b1010 & 0b1100) --- > 0b1000 그리고

  2. bin(0b1010 | 0b1100) ---> 0b1110 또는

  3. bin(0b1010 ^ 0b1100) ---> 0b110 이거는 공부 더 필요할 듯

    EXCLUSIVE OR 단독이다. 하늘아래 나만 존재 할 수 있다.

bit 연산자는 게임 인벤토리등을 구성할 때 쓸 수 있다.

shift 연산자

프로그래밍 할 때 2의 배수로 나오면 성능이 더 좋다? 이게 맞는 말인가?
실제로 맞는 말이다. shift operator로 인해 성능이 더 좋음

알고리즘에서 2의 배수로 가는 경우 shift 연산자를 직접 써주면 성능이 더 좋아진다.

1<<1 ---> 1은 0001 이고 <<1은 1을 왼쪽으로 한칸 민다는 것이다.

답은 2

1<<2 ---> 1을 왼쪽으로 2칸 밀게 되므로 0100 답은 4

1<<3 ---> 1을 왼쪽으로 3칸 밀게 되므로 1000 답은 8

10>>1 ---> 10은 2진법으로 하면 1010 이므로 이걸 오른쪽으로 1칸 밀면 101이 된다 답은 5

논리 연산자 (logical operation)

논리 연산자는 진리표를 보고 참고하면 되고, 몇가지 예시를 들어보려고 한다.

  1. [1,2] or [] ----> [1,2]

  2. [1,2] and [] ---> []

  3. [1,2] or [3,4] ---> [1,2]

  4. [1,2] and [3,4] ---> [3,4]

  5. [] and [1,2] ---> []

  6. [] or [1,2] ---> [1,2]

여기서 규칙은 제일 마지막에 참조한 객체를 반환이다.

  1. [1,2] or [] ----> [1,2] ㅡㅡ [1,2] 가 참이므로 더 이상 보지 않고 반환

  2. [1,2] and [] ---> [] ㅡㅡ and 이므로 2번까지 봐야하고 []가 마지막으로 참조한 객체이므로 [] 반환

  3. [1,2] or [3,4] ---> [1,2] ㅡㅡ or이라 [1,2]가 참이므로 더 이상 보지 않고 반환

  4. [1,2] and [3,4] ---> [3,4] ㅡㅡ and 이므로 [3,4]까지 봐야하고 [3,4] 반환

  5. [] and [1,2] ---> [] ㅡㅡ and인데 처음부터 false 이므로 더 이상 보지 않고 [] 반환

  6. [] or [1,2] ---> [1,2] ㅡㅡ or이므로 두 번째까지 확인하고 [1,2] 반환

산술연산자(모듈러)

산술연산자는

  • : 더하기

  • : 뺴기

/ : 나누기 (실수형 나누기)

/ : 몫의 정수 부분만 표현 (정수형 나누기)

  • : 곱하기

** : 2 ** 50 ---> 2에 50승 (보통 math.pow(2, 50) 으로 쓴다.)

% : 8 % 3 ---> 8을 3으로 나눈 나머지 표현

+= / -= / = / *= 도 쓸 수 있음

###2019.03.04 TIL

(TIL은 스스로 이해한 것을 바탕으로 정리한 것으로 오류가 있을 수 있습니다)

파이썬 기본 문법

========================
1.기본 문법(Dictionary)


python 기본 문법 2일차이다.

배운거 복습하기

  • dictionary 삽입(insert) 방법 - 3가지 말하기
  • dictionary 검색(search) 방법
  • dictionary 삭제(delete) 방법 - 3가지 말하기

Dictionary

javascript에서 배우던 object와 비슷한 것 같다.

TIL을 진행하기에 앞서서 오늘 배운 꼭 알아야 하는 것을 좀 적어보려고 한다.

  • 변수란 데이터를 저장할 수 있는 메모리 공간이다.

  • 단 파이썬에서는 변수가 이름과 값 객체로 나누어진다. 우리가 파이썬에서 변수라고 부르는 것은 이름이다. 값 객체는 다른 메모리 공간에 있다.

  • a = 10으로 변수를 지정하고 a = 20 으로 새로운 변수를 지정해주면 10 이 20으로 바뀐 것이 아니라, 20이라는 새로운 값 객체가 생성된 것이고 a 는 20이라는 값 객체를 가르키게 된다. 따라서 처음 생성되었던 값 객체 10은 파이썬에서 자동적으로 없애줌(부르는 이름이 할당되지 않으면 삭제된다) 추가적으로 a = 10 이라고 파이썬에서 적어주면 10이라는 값 객체가 먼저 메모리에 생성되고 a라는 변수가 할당된다.

  • list1 = [1,2,3] 에서 list는 다양한 변수들의 모임이므로 1,2,3은 한곳에 저장되어 있지 않고 다 따로 떨어져있다. 이것에 대한 표현을 [ * , * , * ] 라고 생각하면 좀 더 쉽다. 각각의 *가 1과 2와 3을 가르키고 있다.

  • iterable 객체 - 하나씩 돌면서 갈 수 있는 객체 (list, tuple, string, dictionary)

dictionary

그럼 이제 본격적으로 dictionary에 대해 다뤄보고자 한다.

딕셔너리의 정의

딕셔너리는 collection of pairs로 우리가 저장하려는 데이터(item)을 key라는 곳에 분류하여 넣는 것을 말한다. 이제 key와 item(value)는 pair를 이루어 들어가게 된다.

딕셔너리 요소 추가 (3가지)

dic = {} ---> 항상 빈 딕셔너리를 만든 이후에 모든 것이 진행되어야 한다.

  1. dic["a"] = 1 ---> "a"라는 key값이 있으면 value를 1로 바꾸고 없으면 추가 한다.

  2. dic.setdefault()

    • dic.setdefault("e") --- > "e"라는 key값이 있으면 value 추출 / 없으면 "e"에 None 삽입

    • dic.setdefault("e", 1) ---> "e"라는 key값이 있으면 value 추출 / 없으면 "e"에 1 삽입

  3. dic.update({}) #setdefault는 key값이 있으면 value 추출/ update는 value를 바꿈

    • dic.update({"e":40}) ---> "e"라는 key 값이 있으면 바꾸고/ 없으면 "e"에 40 추가

    • dic.update((("e", 1),)) --- > 위와 같으나 튜플로 넣을 시 튜플안에 튜플이 있는 형태로 삽입

    • update안에 들어있는 sequence element #0 has length 1 ---> 2 is required

딕셔너리 요소 삭제 (3가지)

  1. del a[key] ---> 해당 key 값의 value도 함께 삭제

  2. dic.pop(key) ---> 해당 key의 페어를 꺼내서 반환하고 삭제

  3. dic.clear() ---> 모든 key 및 value 삭제

##딕셔너리 서칭

  1. key 추출 -- 3가지

    1. dic.keys() ---> view로 반환// list(dic.keys()) ---> list로 반환

    2. for k in dic.keys() == for k in dic

    3. key in dic ---> 해당 key가 dic에 있는지 확인 후 True or False 반환

  2. value 추출 -- 3가지

    1. dic.values() ---> view로 반환 // list(dic.values()) ---> list로 반환

    2. dic[key] ---> 해당 key의 value 추출 // 없을시 에러 송출

    3. dic.get(key) ---> 해당 key의 value 추출 // 없을시 None이 나옴

      1. dic.get(key, "None") ---> 해당 key가 없을시 "None" 추출
  3. key & value 추출 -- 2가지

    1. dic.items() ---> key와 value를 view로 반환 // list(dic.items()) ----> list로 반환

    2. for k in dic.items() /--->tuple형태로 반환 / for key, value in dic.items() ---> 그냥 반환

딕셔너리 주의 점

  1. key는 immutable만 쓸 수 있음 (tuple, string, num)

  2. value는 모두 사용 가능

  3. key는 중복이 불가능하다

  4. view 객체와 list의 차이점

###2019.03.04 TIL

(TIL은 스스로 이해한 것을 바탕으로 정리한 것으로 오류가 있을 수 있습니다)

파이썬 기본 문법

========================
1.기본 문법(string,list)


python 기본 문법의 시작이다.

오늘은 string/list으로 나누어 기본 문법을 정리해보려고 한다.

String

String은 기본적으로 immutable로서 변경할 수 없다.(tuple와 마찬가지).

string

String은 index를 사용할 수 있으며 "abcdef"[0],

String은immutable하므로 보이는 형태라도 변경하기 위한 방법

  1. slicing

    • b = a[0:2] + "x" + a[3:]
  2. replace ---> 특정 문자 찾아 바꾸기

    • b = a.replace("a", 'x')
  3. a.split()

    • ---> 공백을 뛰어서 list로 바꿈

위와 같은 방법으로 변경할 수 있다.

string에서 빈칸을 없애는 방법

  1. a.lstrip() ---> 왼쪽 공백 제거
  2. a.rstrip() ---> 오른쪽 공백 제거
  3. a.strip() ---> 양쪽 공백 제거
  4. a.replace(" ",'') ---> 띄어쓰기 모두 제거

위의 4가지 방법을 쓸 수 있다.

string의 길이, 요소 파악

  • len(a)를 사용한다

  • a.count("a") ---> 특정 요소의 갯수 파악

string 내에서 특정한 값을 대입 혹은 바꿔야 할 때

  1. format

    • "{},{}".format(a, b)
  2. 포매팅

    • "I love %s" %you

string내에서의 join

  1. "str".join(list)

    • list의 요소마다 str을 넣어서 string으로 변경 . ---> 단 list의 요소는 다 문자로 구성되어야 함
  2. "str".join(str)

    • str의 사이마다 str을 넣어서 변경

list

정의 : python에서 list는 다양한 타입의 변수 모임이라고 본다

list

li = []
li = [ 1, "b", (1,2,3) ]

Number, string, tuple 등 다양한 타입이 모두 올 수 있다.

요소 삭제

  1. li[1:3] = [] ---> index 활용 삭제

  2. del li[0] ---> 특정 인덱스 요소 삭제

  3. li.remove() ---> 특정 요소 직접 삭제

요소 추가

  1. li.append() ---> 제일 뒤에 1개의 특정 요소 추가

  2. li.insert(index, ele) ---> 특정 인덱스에 원하는 요소 추가

  3. li.extend([ ]) ---> list 요소들을 한번에 추가

요소 추출

  1. li[0] ---> 인덱싱

  2. li[0:2] ---> 슬라이싱

  3. li.index("ele") ---> 특정 요소의 인덱스 파악

  4. li.count("ele") ---> 특정 요소의 갯수 파악

  5. li.pop() ---> 특정 인덱스의 요소 추출 및 제거(없을시 가장 뒷 요소)

요소 수정

  1. li[0] = "1" ---> 하나의 값 수정

  2. li[0:2] = [1, 2, 3] ---> 해당 슬라이싱 만큼 특정 값 추가

요소 정렬

  1. li.sort() ---> 원본이 바뀌는 정렬

  2. li.reverse() ---> 원본을 거꾸로

  3. li.sort(reverse = True)

  4. len(a)

  5. sorted(li) ---> 원본이 바뀌지 않는 정렬

2019.09.24 django select_related, prefetch_related

문제의 제기

  • 기본적으로 장고의 ORM은 불필요한 쿼리문을 많이 요청한다는 이야기가 많았다. 실제로 확인을 해보니 불필요한 중복 쿼리문이 너무 많이 사용되고 있었다.
  • 디버그 툴바를 활용하면 불필요한 쿼리문의 요청들을 확인할 수 있다.

기본적으로 ORM에서는 하나의 모델을 바탕으로 Foreignkey가 묶인 다른 자료들도 불러서 사용할 수 있다.

  • 문제는 여기서 발생하게 된다. 하나의 모델을 불러오고 또 그 관련된 Foreignkey가 연관된 자료들을 불러오려고 하면 DB에 접근을 2번이나 해야 한다.
  • 그렇다면 하나의 모델을 불러올 때 미리 foreignkey로 연결된 자료까지 불러 올 수 있으면 어떨까? 그렇게 되면 DB에 접근을 한번만 해도 된다!

select_related와 prefetch_related란?

  • select_related와 prefetch_related는 하나의 Queryset을 가져올 때, 미리 related objects들까지 다 불러와주는 함수이다.
  • 비록 query를 복잡하게 만들지만, 불러온 데이터는 모두 cache에 남아있게 되므로 DB에 다시 접근해야 하는 이슈가 줄어들 수 있다.
  • 2가지는 모두 DB에 접근하는 수를 줄여주어, performance를 줄여주지만 그 방식에는 차이가 있다.

select_related와 prefetch_related의 차이

  • 와 위의 글을 읽고 머리를 딱 쳤다.
  • select_related와 prefetch_related 모두 필요한 데이터들을 모두 한번에 가지고 올 수 있게 도와준다.
  • select_related는 foreign-key 혹은 one-to-one과 같은 1:1 관계에서 사용이 가능하다.
  • 그에 반해 prefetch_related의 경우 many to many, foreign-key 등 가리지 않고 사용이 가능하다.
  • 하지만 실제로 해당 내용을 한번에 모두 가지고 오기 위해 호출해야 하는 횟수가 다르다.
  • 결론부터 말하자면 prefetch_related가 더 많은 쿼리문(순차적으로 불러옴)을 실행해야 되는 반면에 select_related는 한번의 쿼리문으로 모든 것을 가지고 온다.

결론

  • select_related와 prefetch_related를 적절하게 사용하면 제일 좋다 :)

확인 방법

  • debug toolbar의 SQL문을 참고해보면 된다!

참고

'Django' 카테고리의 다른 글

django celery 적용하기  (0) 2020.03.17
Django migration 되돌리기, 재실행 방법  (3) 2020.01.29
Django queryset filter와 exists()  (0) 2019.12.28
Django Lock에 관해서  (0) 2019.12.28
Django model(default, blank, null)  (0) 2019.12.28

2019.08.27 django queryset filter와 exists()의 차이

django의 쿼리는 마지막까지 지연(lazy)된다.

  • 예를 들어 filter를 건다고 하면, 이것은 DB에 바로 전달되지 않는다.
    person = Person.objects.filter(first_name='kim')
  • 해당 쿼리셋은 바로 실행되지 않는다.
  • DB에 쿼리를 전달하는 일은 웹 애플리케이션이 느려지는 주범 중 하나이기 때문이다.
  • 실제로 실행 되려면 다음과 같은 과정이 실행될 때 일어난다.

for one_person in person:
print(one_person)

  • 해당 과정이 일어나게 되면 실제로 DB에 전달되면서 발생하게 된다.

DB 쿼리셋은 대신 캐시된다.

  • DB쿼리는 단 한번만 발생한다. 똑같은 순회를 할 경우 DB쿼리는 한번만 시행된다.

for one_person in person:
print(one_person.firstname)

for one_person in person:
print(one_person.lastname)

  • 해당 쿼리셋은 한번만 DB에 접근하여 캐시로 해당 값을 가지고 있고 아래의 for문을 해결한다.

if문에서는 쿼리셋 평가가 발생한다.

restaurant_set = Restaurant.objects.filter(cuisine="Indian")

if 문은 쿼리셋을 '평가'한다

if restaurant_set:
# 순회할 때는 캐시된 쿼리셋이 사용된다
for restaurant in restaurant_set:
print(restaurant.name)

  • 오홋 신기하네.
  • 하지만 해당 경우 만약에 있는지 없는지만을 평가할 떄는 캐시가 문제가 된다.
  • 따라서 했는지 없는지만을 평가하기 위해서는 exists()라는 것을 활용한다.

존재만을 확인하기 위한 exists()

if restaurant_set.exists():
실행한 내용

  • 이렇게 되면 exists를 통해 존재만을 확인한다.

하지만 쿼리셋 캐시도 엄청 크다고 하면, 문제가 발생된다.

  • 해당 경우 exists()와 iterator()를 함께 사용하여 쿼리셋 캐시를 생성하는 일을 방지할 수 있다.

person = Person.objects.all()

if person.exists():
for one_person in person.iterator():
print(one_person)

  • 해당 과정을 통해서 첫 번째 exists()로 쿼리셋 레코드가 존재하는지 확인하고
  • 존재가 한다고 하면 iterator()를 통해서 하나씩 돌면서 캐시를 쌓지 않고, 프린트를 하게 된다.

'Django' 카테고리의 다른 글

Django migration 되돌리기, 재실행 방법  (3) 2020.01.29
Django select_related, prefetch_related에 대해서  (0) 2019.12.28
Django Lock에 관해서  (0) 2019.12.28
Django model(default, blank, null)  (0) 2019.12.28
Django admin message  (1) 2019.12.28

2019.08.23 장고에서 Lock을 사용하여 멀티 스레드의 경합 막기

Lock

  • 멀티 스레드 방식에서 경합을 막는 가장 일반적인 방법
    • 하나의 프로세스에서 2개 이상의 스레드가 동시에 수행되는 경우, 스레드간에 프로세스 공간의 메모리를 공유한다.
    • 여러 스레드가 잠금없이 같은 객체를 수정하면 프로그램 자료 구조가 오염될 수 있다.(race condition)
  • 그것을 방지하기 위해 lock을 이용한다.

Lock 객체

  • Lock 객체는 locked와 unlocked 2가지의 상태를 가지며, acquire()와 release()의 2가지 함수만을 제공한다.
  • unlocked 상태에서 acquire가 호출되면 locked 상태로 바뀌고, locked 상태에서 release가 호출되면 unlocked 상태로 바뀌게 된다.
  • 락은 자물쇠처럼 이를 선점한 스레드가 락을 획득하면, 자물쇠가 잠긴다. 이후에 접근하는 스레드들은 락이 열릴 때까지 그 앞에서 멈춰기다렸다가, 락을 선점한 스레드가 락을 풀어주면 차례로 획득 해제를 반복하면서 순차적으로 처리하는 모양을 만들게 된다. 가장 명료한 비유는 칸이 하나 밖에 없는 화장실을 생각하면 된다.

파이썬 스레드

기본 사용 방향

conn = redis.StrictRedis(REDIS_SERVER_IP)
lock_id = '{}'.format(id)
lock = redis_lock.Lock(conn, lock_id, expire=5)

if lock.acquire(blocking=False):
    try:    
        원하는 코드

    finally:
        lock.release()
else:
    실패 했을 때 실행할 코드
  • 해당 lock_id를 통해서 그 특정 것에 대해 lock을 걸게 되므로 lock_id는 하나의 개개인으로 존재하는 것이 좋다.
  • 실패했을 때 어떤 것을 실행하면 좋을까?
    • 실행하다가 실패하게 되면 그 전 것들을 다 되돌려주는 작업을 진행하거나 해야한다.

'Django' 카테고리의 다른 글

Django select_related, prefetch_related에 대해서  (0) 2019.12.28
Django queryset filter와 exists()  (0) 2019.12.28
Django model(default, blank, null)  (0) 2019.12.28
Django admin message  (1) 2019.12.28
Django super & after save  (0) 2019.12.28

2019.08.23 장고 모델의 default, blank, null 에 관하여

django 모델을 설정할 때, default와 blank, null에 관해서

  • 먼저 장고 model을 설정 할 떄 필드의 옵션에 들어가는 default와 blank, null이 각각 무엇을 의미하는지 궁금해졌다.
  • 해당 내용에 대해서 찾아보았다.
The blank option is used in the form validation, and the null is used when writing to database.
So you might add null=True to that field.
EDIT: continue the comment
Considering the two steps when saving object:
1. Validator(controlled by blank)
2. Database limitation(controlled by null)

For default option, take IntegerField for example,


default=5, blank=True, null=False, pass (1) even if you didn't assign a value(having blank=True), pass (2) because it has a default value(5) and writes 5 instead of None to DB.


blank=True, null=False, which pass (1) but not (2), because it attempts to write None to DB.

Thus, if you want to make a field optional, use either default=SOMETHING, blank=True, null=False or blank=True, null=True.

장고 모델에서 저장이 될 때

  • 장고에서 해당 모델 object를 저장 할 때 2가지 과정을 거친다.
    • 첫 번째는 validator(controlled by blank) 즉 검증에서 가능한지 파악하고
    • 두 번째는 Database limitation(controlled by null) 데이터베이스에 넣을 때이다.

예를 들어서 설명해보자

  • 만약에 default=5, blank=True, null=False라고 하면
  • 값(blank=True)을 주지 않아도 통과하게 될 것이다. 왜냐하면 blank=True 때문에 validation을 넘어가게 되고 , default value가 5이기 때문에 db에는 None 대신에 5가 저장될 것이기 때문이다.
  • 하지만 만약에 default value가 없이, blank=True 그리고 null=False만 있다고 하면 통과 할 수 없다. validation은 통과하지만 null을 DB에 저장하려고 시도할 때 null=False이므로 저장할 수가 없다.

따라서 기본적인 옵션을 만들때는

  1. default= something , blank=True, null=False
  2. blank=True, null=True
  3. 위의 2가지 조건 중에 한개를 선택해주어야지만 저장이 될 수 있다.

'Django' 카테고리의 다른 글

Django queryset filter와 exists()  (0) 2019.12.28
Django Lock에 관해서  (0) 2019.12.28
Django admin message  (1) 2019.12.28
Django super & after save  (0) 2019.12.28
Django 트랜젝션  (0) 2019.12.28

+ Recent posts