여전히 교육은 진행중이고 여전히 블로그 정리는 계속됩니다.
오늘 강의는 이해는 되는데 어려웠습니다.
오히려 재밌는 것들을 많이 알려주셔서 집중은 더 잘됐습니다.
앞으로 할 수 있는 것들과 추천하는 형태의 코드들과 비추천 하는 형태의 코드들을
상세히 알려주셨습니다. 저도 한번 잘 정리해보려고 합니다.


클로저

함수 내 함수가 외부 변수를 참조하여 보존하는 것을 클로저 라고 합니다.

# 클로징 되어야 하는 변수에 접근하는 것을 클로저
def outer_function(x):
    def inner_function(y):
        return x + y
    return inner_function

inner = outer_function(100)
inner(200) # inner 입장에서 100을 변경할 수 있는 방법이 없습니다.
def make_counter():
    count = 0

    def counter():
        nonlocal count      # 밖에 있는 count를 가져오겠다는 뜻
        count += 1
        return count

    return counter

counter_a = make_counter()
print(counter_a())  # 1
print(counter_a())  # 2
print(counter_a())  # 3

이렇게 함으로 순서는 항상 상승된다고 보장할 수 있습니다. DB에서 게시물 번호 등에 쓰입니다.
security 개념으로 쓰이진 않습니다.

# 어떠한 변수를 보호하기 위한 용도로는 잘 사용하지 않습니다.
# 아래와 같이 사용하면 문제가 발생할 수도 있습니다.
def 은행(원금):
    def 입금(입금금액):
        return 원금 + 입금금액
    return 입금

통장_입금함수 = 은행(1000) # 1000만원을 초기에 입금
통장_입금함수(100)
통장_입금함수(-1100)

요즘 은행에서 이런 시큐어 코딩 문제로 문제가 발생되는 경우는 극히 드뭅니다.
이더리움 <- 음수가 없습니다. 양수만 있습니다.
0원에서 -1원 한다. => 옛날 차 계기판 => 나올 수 있는 최댓값 => 은행 통장 잔고 전체
마이너스 연산을 허락하지 않았는데 어떻게 마이너스 연산을 한 것인까요?
해커가 인자로 인스턴스를 넣어서 마이너스 연산되도록 만들어서 해킹이 가능하다고 합니다.
대단하죠?


데코레이터

기본적으로 함수 또는 메서드를 꾸며주는 함수 데코레이터는 고차함수입니다.
일급함수는 함수를 값으로 취급 하는 것.
고차함수는 함수를 아규먼트로 사용할 수 있고, 함수를 리턴할 수 있는 함수입니다.

# 데코레이터는 함수가 호출되었을 때 실제 실행되는 함수입니다.
# 데코레이터의 return 함수가 실행되는 것입니다.
def simple_decorator(function):
    def wrapper():
        print("전")
        function()
        print("후")
    return wrapper

@simple_decorator
def hello():
    print("Hello, World!")

hello() 

hello() 는 데코레이터가 없는 상태에서는 simple_decorator(hello)() 와 같습니다.

데코레이터 만드는 순서를 정리해주셨습니다.

# 제가 사용하는 데코레이터
# step 0
sum([1, 2, '3', 4, 5, '6']) # error! 그런데 이게 되게 하고 싶음
# step 1 : 골격을 만듭니다.

def data_pre(function):
    def wrapper():
        return None
    return wrapper

@ data_pre
def mean(l):
    return sum(l) / len(l)

mean([1, 2, '3', 4, 5, '6'])    # dara_pre(mean)()
# step 2 : 파라미터를 설정합니다.
# 얻어가야할 포인트(데코레이터와는 관련이 없습니다.) : map은 __len__이 없어서 len() 안됩니다.
# 포인트2: list 형변환은 부담이 있는 연산이닌 주의를 해주세요.
def data_pre(function):
    def wrapper(iter_obj):
        return function(list(map(int, iter_obj)))
    return wrapper

@ data_pre
def mean(l):
    return sum(l) / len(l)

mean([1, 2, '3', 4, 5, '6'])    # dara_pre(mean)(iter_obj)  # iter_obj 에 [1, 2, '3', 4, 5, '6']
# 이 3.5는 실제 mean 반환값인가요? 실제는 wrapper에 반환값입니다.
# 좀 더 정교한 작업이 필요할 때
def data_pre(function):
    def wrapper(iter_obj):
        l = []
        for i in iter_obj:
            if isinstance(i, str):
                s = ''
                for j in i:
                    if j.isdigit():
                        s += j
                l.append(int(s))
            else:
                l.append(i)
        return function(list(map(int, l)))
    return wrapper

@ data_pre
def mean(l):
    return sum(l) / len(l)

mean([1, 2, 'l3l', 4, 5, 'abc6def'])    # dara_pre(mean)(iter_obj)  # iter_obj 에 [1, 2, '3', 4, 5, '6']
# 이 3.5는 실제 mean 반환값인가요? 실제는 wrapper에 반환값입니다.

강사님이 추천하시는 형태입니다.^^ 정규표현식을 배우면 훨씬 더 간단해지지만 정규표현식을 사용하지 않고,
만든 코드입니다. re.sub('[a-zA-Z]+', '') 이렇게 사용할 수 있다고 합니다. 나중에 배우겠지요?


lambda

numbers = [1, 2, 3, 4, 5]
print(list(filter(lambda x: x > 3, numbers)))  # 출력: [4, 5]
def f(x):
    return x > 3
numbers = [1, 2, 3, 4, 5]
print(list(filter(f, numbers)))  # 출력: [4, 5]

재사용 여부에 따라 lambda를 사용할지 def 사용할지 판단하시면 됩니다.


args, kwargs

애스터리스크(*) 1개는 튜플이나 리스트를 패킹, 언패킹하는데 사용하며 2개는 딕셔너리를 패킹, 언패킹 하는데 사용됩니다.
아규먼츠(args)는 튜플 형식으로 아규먼트를 받는 반면 키워드 아규먼츠(kwargs)는 딕셔너리 형태로 아규먼트를 받습니다.

# 패킹
# *r가 패킹에 쓰였습니다.
def func(*args):
    print(args)

func(10, 20, 30)
# 10, 20, 30 => *args => (10, 20, 30)

# 언패킹
# 언패킹 실무에서 자주 사용하진 않음. 패킹을 자주 사용함.
def func(a, b, c):
    print(a, b, c)

args = (10, 20, 30)
func(*args) # * 안해주면 error 가 납니다. 넘겨야할 아규먼트가 3개여야 error가 안남.
# (10, 20, 30) => *args => 10, 20, 30
def f(a, b, c, d, e):
    print(a, b, c, d, e)    

f(1, 2, e=3, d=4, c=5)  # 이렇게 보장해주는 언어가 별로 없어요. 출력 : 1 2 5 4 3

def f(*args, **kargs):  # 가변적으로 parameter를 받고 싶을 때 이 형태를 많이 사용합니다.
    print(args)        # 출력 : (1, 2)
    print(kargs)    # 출력 : {'e': 3, 'd': 4, 'c': 5}

f(1, 2, e=3, d=4, c=5)    
one, two, *three = 1, 2, 3, 4, 5
print(one, two, three)    # 출력 : 1 2 [3, 4, 5]

a, b, *c = 'hello world'
c # 출력: ['l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd']

유용하게 사용할 수 있을 것 같습니다.


이터레이터 와 제너레이터

이터레이터란, 값을 차례대로 꺼낼 수 있는 객체
시퀀스형 자료형이란? index가 있고, indexing, slicing이 가능한 자료형
dict는 이터레이터인가요? Yes
dict는 시퀀스형 자료형인가요? No
{'one': 1, 'two': 2}[2:] # error
제너레이터는 이터레이터를 만드는 함수

for i in {'one': 1, 'two': 2}:
    print(i)

list(map(lambda x: x[0], {'one': 1, 'two': 2}))

결과는

one
two
['o', 't']
class MyIterator:
    def __init__(self, stop):
        self.current_value = 0  # 현재 값
        self.stop = stop  # 순회를 멈출 값

    def __iter__(self):
        return self

    def __next__(self):
        if self.current_value >= self.stop:
            raise StopIteration
        result = self.current_value
        self.current_value += 1
        return result

my_iterator = MyIterator(5)

for i in my_iterator:
    print(i)

for i in my_iterator:
    print(i)

전에도 배웠지만 for문은 __iter__ 함수를 처음 호출하고, __next__ 함수를 호출하게 됩니다.
그래서 첫 for문은 0, 1, 2, 3, 4 를 차례로 출력하지만 아래 for문은 바로 stopIteration 으로 멈춥니다.
다시 순회를 하려면 __iter__ 함수에 current_value = 0 으로 초기화 하는게 필요합니다.
위의 클래스에서는 __init__ 에만 쓰였습니다.

def my_generator():
    x = 10
    yield x
    x = 20
    yield x
    x = 30
    yield x
    return
    x = 40
    yield x

for i in my_generator():
    print(i)

제너레이터는 이터레이터를 생성해주는 함수로, yield 키워드를 사용하여 만듭니다.

def my_generator():
    x = 0
    while True:
        yield x
        x += 2

list(zip('hello', my_generator()))    # 결과 : [('h', 0), ('e', 2), ('l', 4), ('l', 6), ('o', 8)]

def my_generator():
    l = ['짝', '홀']
    while True:
         # := 왈러스 연산자(python 3.8버전)
        yield l[t := False]    l[False]
        yield l[t := True]    l[True]

list(zip([0, 1, 2, 3, 4, 5, 6], my_generator()))

결과는 [(0, '짝'), (1, '홀'), (2, '짝'), (3, '홀'), (4, '짝'), (5, '홀'), (6, '짝')] 입니다.
왈러스 연산자를 배웠었다는데 기억이 안납니다.^^

nonlocal

nonlocal은 파이썬의 키워드 중 하나로, 중첩 함수 내부에서 바깥 함수의 변수를 참조할 수 있게 합니다.
nonlocal은 주로 클로저(closure)에서 변수의 값을 변경하고자 할 때 사용합니다.

# nonlocal
a = 10
def f():
    a = 100
    print(f'f a: {a}')
    def ff():
        a = 1000
        print(f'ff a: {a}')
        def fff():
            nonlocal a # global a로 변경해보세요.
            a = 100
            print(f'fff a: {a}')
        fff()
        print(f'ff a: {a}')
    ff()
f()
print(f'global a: {a}')

nonlocal 은 바로 바깥에서 참조할 변수를 찾습니다. 없으면 다음, 없으면 다음 순서대로 찾고,
마지막에 전역변수 까지 찾습니다.

모듈

모듈이란 클래스나 함수, 변수를 다른 파일(.py)에 작성하여 다른 파이썬 코드에서 재사용할 수 있도록 한 것입니다.

# 제대로 알고 사용해야 합니다.
# 1번 스탭 : info.py를 생성
# name = 'honggildong'
# age = 10
#
# def hello():
#     print('안녕하세요 저는 홍길동 입니다.')

위의 과정이 끝나면

import info as q # info.py 를 가져오겠다! info라는 이름 대신 q

q.name
q.age
q.hello()

이렇게 사용할 수 있습니다.

# 문제가 있는 코드
# 뒤에 로드 된 것이 덮어 씁니다.
# 이런 경우는 아주 극히 드물지만 일어나는 일입니다.

from info import name, age, hello
from infotwo import name

print(name)

이렇게 될 수도 있다고 하네요. 주의해서 사용해야 할 것 같습니다.
뒤에 로드 된 것이 덮어 쓴다고 합니다.
카카오 로그인, 구글 로그인, 엑셀 로드, 문자를 보내거나 하는 것들은 다 모듈로 나와 있다고 합니다.
직접 코드를 짜기 전 라이브러리가 있는지 확인할 필요가 있다고 합니다.
파이썬은 라이브러리, 프레임웤, 서드파티가 정말 잘 되어 있습니다.


라이브러리: 여러분들 코드에 라이브러리가 섞여 들어가는 코드. 예를 들어 크롤링에 request, bs4
프레임웤 : 설계 도면이 정해져 있어서 이 설계 도면대로 코딩을 해야 하는 경우,
레고 설계 도면처럼 완성품에 설계도면이 존재합니다.
서드파티 : 프레임웤에 붙는 코드. 예를 들어 Django 서드파티라고 하면 Django 로그인, DRF, Django-cors
Django 에 사용하는 서드파트만 잘 알아둬도 개발하는데 문제가 없음

collection

import collections  # 알고리즘 문제에서 많이 나옴.
# deque문제: 페이지 교체 알고리즘, 회전 초밥 등 다양한 문제에서 활용됩니다.

d = collections.deque([1, 2, 3, 4])
d.rotate(1) # 1번 오른쪽으로 쉬프트 합니다. 숫자를 2로 바꾸어 비교해보세요.
d # 출력: deque([4, 1, 2, 3])

d = collections.deque([1, 2, 3, 4])
d.rotate(2) # 1번 오른쪽으로 쉬프트 합니다. 숫자를 2로 바꾸어 비교해보세요.
d # 출력: deque([4, 1, 2, 3])
c = collections.Counter('hello world')
c # 출력: Counter({'h': 1, 'e': 1, 'l': 3, 'o': 2, ' ': 1, 'w': 1, 'r': 1, 'd': 1})
c.most_common()

# 출력:
'''
[('l', 3),
 ('o', 2),
 ('h', 1),
 ('e', 1),
 (' ', 1),
 ('w', 1),
 ('r', 1),
 ('d', 1)]
'''

그외 다양한 데이터 처리 하는 방법, Json 처리하는 방법 등을 배웠습니다.
다음에 또 배운다고 하니 그때 다시 정리해보겠습니다.


예외처리

try:
    # s = 1/0
    l = []
    l.appnd(10)
    print(s)
except ZeroDivisionError:
    print('0으로 나누어졌습니다!')
except AttributeError:
    print('메서드 없어요!')
except:
    print('오류가 났습니다!')

오류가 날만한 곳에 사용합니다. 명시적으로 할수도 있지만 무슨 에러가 날지 모르잖아요.
except: 하는게 좋지 않을까 싶습니다.^^

'''
게시물 5개 쓰는 코드
'''
assert 게시물.count() == 'hello', '에러 체크 하기 위해 이렇게 많이 사용함

assert 는 에러 체크하기 위해 많이 사용한다고 합니다.

여기까지 정리하도록 하겠습니다. 많이 배우기도 했고, 어렵기도 했습니다.
과제하는데도 헷갈려서 고생을 좀 했습니다. 찾아보면서 하지 않으면 어렵습니다.
앞으로 좋은 코드를 많이 만나봐야 할 것 같습니다.
힘들고, 어렵지만... 그래도 화이팅 입니다.