ex) 클래스를 통한 구현 (클로저 X)
class Mul:
def __init__(self, m):
self.m = m
def __call__(self, n):
return self.m * n
if __name__ == "__main__":
mul3 = Mul(3)
mul5 = Mul(5)
print(mul3(10)) # 30 출력
print(mul5(10)) # 50 출력
mul3 = Mul(3)에서 생성자를 통해 함수 내부의 m이 3으로 지정.mul3(10)을 통해 __call__ 메소드가 호출됨. ex) 함수 안에 함수를 구현하는 방법 (클로저)
def mul(m):
def wrapper(n):
return m * n
return wrapper
if __name__ == "__main__":
mul3 = mul(3)
mul5 = mul(5)
print(mul3(10)) # 30 출력
print(mul5(10)) # 50 출력
ex)
import time
def elasped(original_func):
def wrapper():
start = time.time()
result = original_func()
end = time.time()
print("함수 수행시간: %f 초" % (end - start))
return result
return wrapper
def myfunc():
print("함수가 실행됩니다.")
decorated_myfunc = elasped(myfunc)
decorated_myfunc()
나의 의문 : 여기서 굳이 wrapper을 써서 클로저로 구현해야 하는 이유는? 사실 wrapper 아래 코드를 바로 함수에서 실행해도 작동은 동일할 텐데?
이유 :
elasped를 데코레이터로 만들기 위해서. 아래의 데코레이터로써의 기능을 사용하기 위해서는 클로저로 구현해야 할 필요가 있음.
@elasped
def myfunc():
print("함수가 실행됩니다.")
myfunc() # 데코레이터가 위에 바로 붙었으므로 별도의 선언이 더 이상 필요하지 않음.
*args와 **kwargs를 사용.def elasped(original_func):
def wrapper(*args, **kwargs):
...
나의 의문 : 꼭
*args랑**kwargs를 둘 다 써야 하나?
해답 : 꼭 그럴 필요는 없는데, 지금처럼 유연하게 어떤 인자가 들어와도 받을 수 있게 처리하는 것이 목적이라면 둘 다 써야 함. 지금의 경우에는
*args가 빠질 경우에는 문제가 생김. 위치 인자가original_func로 들어오고 있고,*args가 그걸 받는 역할을 하는 인자이기 때문.
이터러블 : 리스트와 같이 반복 구문에 적용할 수 잇는 객체이터레이터 : next 함수 호출 시 계속 그다음 값을 리턴하는 객체ex) 리스트를 이터레이터로 변환하고 next를 사용해 값 가져오기
a = [1, 2, 3]
ia = iter(a) # 모든 이터러블은 iter 함수를 통해 이터레이터로 변환 가능.
next(ia)
# 1
next(ia)
# 2
next(ia)
# 3
next(ia)
# StopIteration 예외 : 더 리턴할 값이 없으므로
ex) for을 사용해 이터레이터의 값 가져오기
a = [1, 2, 3]
ia = iter(ia)
for i in ia:
print(i)
# 1 2 3 출력
for i in ia:
print(i)
# 아무것도 출력 X, for이나 next로 값을 한 번 읽으면 다시 읽을 수 없는 이터레이터의 특징.
__iter__과 __next__ 라는 2개의 메서드를 구현.class MyIterator:
def __init__(self, data):
self.data = data
self.position = 0
def __iter(self):
return self
def __next__(self):
if self.position >= len(self.data):
raise StopIteration
result = self.data[self.position]
self.position += 1
return result
if __name__ == "__main__":
i = MyIterator([1, 2, 3])
for item in i:
print(item)
__iter__ : 이 메서드가 있는 클래스로 생성한 객체는 반복 가능한 객체가 됨. 반복 가능한 객체를 리턴해야 함.__next__ : __iter__이 있는 클래스는 반드시 __next__를 구현해야 함. 반복 가능한 객체의 값을 차례대로 반환하는 역할을 함.next 함수 호출 시 그 값을 차례대로 얻을 수 있음.yield 키워드 사용def mygen():
yield 'a'
yield 'b'
yield 'c'
if __name__ == "__main__" :
g = mygen()
next(g)
# a
next(g)
# b
next(g)
# c
next(g)
# StopIteration 예외
yield를 만날 시 해당 값을 리턴하되, 현제 상태를 그대로 기억한 채로 멈춤.def mygen():
for i in range(1, 1000):
result = i * i
yield result
gen = mygen()
print(next(gen))
# 1
print(next(gen))
# 4
print(next(gen))
# 9
다음과 같은 제너레이터를 아래와 같이 튜플 표현식으로 간단하게 만들 수 있음.
gen = (i * i for i in range(1, 1000))
import time
def longtime_job():
print("job start")
time.sleep(1) # 1초 지연
return "done"
다음과 같은 코드에서
list_job = [longtime_job() for i in range(5)]와
list_job = (longtime_job() for i in range(5))를 사용 가능.
위 : longtime_job()을 5번 실행해서 그 결과로 리스트를 만듦. -> 즉시 longtime_job() 5번 실행
아래 : 제너레이터 표현식. 즉 next로 list_job의 결과를 불러오면 그 때마다 실행되고 값을 리스트에 넣음. -> next할 때마다 longtime_job() 실행
동적 언어 : 프로그램 실행 중 변수의 타입을 바꿀 수 있는 언어. ex) 파이썬a = 1
a = "1" # 타입을 실행 중 스트링으로 바꿈 -> 동적 언어
# a와 b의 타입 : int, 리턴값의 타입 : int
def add(a: int, b: int) -> int:
return a+b
pip install mypy를 통해 설치 후 사용C:\doit>mypy typing_sample.py
typing_sample.py:5: error: Argument 2 to "add" has incompatible type "float"; expected "int"
Found 1 error in 1 file (checked 1 source file)