List Comprehension: 새로운 리스트를 만들 때 사용할 수 있는 간단한 표현식
사용법: []
를 사용하여 만들고자 하는 원소를 표현하는 표현식으로 시작해서
for
루프가 뒤에 따라오는 형식이며, 필요에 따라 뒤에 if
문을 추가할 수 있음
new_list = [표현식 for 원소 in 반복 가능한 객체]
new_list_if = [표현식 for 원소 in 반복 가능한 객체 if문]
new_list = [x for x in range(1, 11)]
print(new_list) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
odd_numbers = [element for element in range(1, 11) if (element % 2) == 1]
print(odd_numbers) # [1, 3, 5, 7, 9]
for
루프와 리스트 컴프리헨션을 통해 구현한 결과, 일반 for
루프를 통해 구현한 코드가 0.062밀리초, 리스트 컴프리헨션을 이용한 코드는 0.024초로 약 2배 정도의 빠르게 실행되는 것을 볼 수 있음import timeit
def for_loop():
num_list = []
for i in range(1000):
num_list.append(i)
def list_comprehension():
num_list = [ i for i in range(1000) ]
if __name__ == "__main__":
time1 = timeit.Timer("for_loop()", "from __main__ import for_loop")
print("for loop time = ", time1.timeit(number=1000), "milliseconds")
time2 = timeit.Timer("list_comprehension()", "from __main__ import list_comprehension")
print("list_comprehension time = ", time2.timeit(number=1000), "milliseconds")
성능 측정을 위해 함수들을 1,000번 실행한 시간을 측정하는 코드인
timeit
을 활용
Assignment:
# 1
cities = ["Tokyo","Shanghai", "Jakarta", "Seoul", "Guangzhou", "Beijing", "Karachi", "Shenzhen", "Delhi"]
def not_start_with_s(cities):
cities_list = [city for city in cities if not city.startswith("S")]
return cities_list
print(not_start_with_s(cities)) # ['Tokyo', 'Jakarta', 'Guangzhou', 'Beijing', 'Karachi', 'Delhi']
# 2
population_of_city = [('Tokyo', 36923000), ('Shanhai', 34000000), ('Jakarta', 30000000), ('Seoul', 25514000), ('Guangzhou', 25000000), ('Beijing', 24900000), ('Karachi', 24300000), ('Shenzen', 23300000), ('Delhi', 21753486)]
def cities_pop_dict(list):
"""returns information about city and population in Dictionary form"""
pop_dict = {city:pop for (city, pop) in list}
return pop_dict
print(cities_pop_dict(population_of_city)) # {'Tokyo': 36923000, 'Shanhai': 34000000, 'Jakarta': 30000000, 'Seoul': 25514000, 'Guangzhou': 25000000, 'Beijing': 24900000, 'Karachi': 24300000, 'Shenzen': 23300000, 'Delhi': 21753486}
반복가능한 객체
에 대해 iter()
메서드를 적용하여 이터레이터로 만듦L = [1, 2, 3]
for x in L:
print(x ** 2, end = ' ') # 1 4 9
반복 가능한 객체
인지 확인하는 방법: dir
로 호출하여 __iter__
함수가 있는지 확인
dir
: 어떤 객체를 인자로 넣으면, 해당 객체가 어떤 변수와 메서드를 가지고 있는지 나열해주는 내장함수
L=['__add__', '__class__', '__contains__', '__delattr__', '__delitem__',
'__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
'__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__',
'__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__',
'__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__',
'__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__',
'__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend',
'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
위와 같이 반복가능한 객체임(즉, 객체 내에 활용할 수 있는 메서드로 __iter__
가 있음)을 확인했을 때,
object.__iter__()
혹은 iter(object)
를 입력하면 다음과 같은 결과로 이터레이터를 반환함을 확인할 수 있음
L.__iter__()
# <list_iterator object at 0x7fe7b08f1c40>
이터레이터를 변수에 저장 후, 이터레이터에 대해 dir
을 통해 내부의 객체를 확인해보면, 내부에 __next__
메서드가 생기는 것을 볼 수 있다.
__next__
메서드를 호출하면 for
문의 동작과 유사하게 값을 하나씩 꺼내올 수 있다.
# 이터레이터 변수 저장
iterator_L = L.__iter__()
iterator_L = iter(L)
__next__
: 이터레이터 객체에 대해 사용시 for
루프 동작과 유사하게 값을 하나씩 꺼내옴print(iterator_L.__next__()) # 1
print(iterator_L.__next__()) # 2
print(iterator_L.__next__()) # 3
print(iterator_L.__next__()) # 3
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
#StopIteration
next(object)
혹은 object.__next__()
를 통해 값을 하나씩 출력 하며, 이터레이터의 인덱스를 벗어나 더이상 출력할 값이 없을 때는 StopIteration
이 발생함
StopItration
에 대한 예외처리를 통해 다음과 같은 while
문 구현이 가능함
I = iter(L)
while True:
try:
X = next(I)
except StopIteration:
break
print(X**2, end = " ")
yield
)한다.def generator_squares():
for i in range(3):
yield i ** 2
print("gen object=", end=""), print(generator_squares())
# gen object=<generator object generator_squares at 0x7fc146809900>
Generators 함수 형식에 대해
yield
대신return
을 쓰면?
위의generators_squares()
함수에 대해yield
대신return
을 사용하면, 일반 함수로써 첫 값으로0
을 반환한 뒤 함수가 종료된다.def generator_squares(): for i in range(3): return i ** 2 # 0
위에서 yield
는 제너레이터 함수에서 값을 반환할 때 사용되며,
yield
호출후에 다시 next
가 호출될 때까지 현재의 상태에 머물다가 next
함수가 호출되면 이전의 상태에 이어서 다음 연산을 수행
제너레이터를 dir
로 내부의 변수와 메서드를 확인해보면, __iter__
와 __next__
가 포함되어 있다.
print("dir gen=" end=""), print(dir(generator_squares()))
# dir gen =['__class__', '__del__', '__delattr__', '__dir__', '__doc__',
# '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__',
# '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__name__',
# '__ne__', '__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__',
# '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__',
# 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']
이터레이터 함수를 만들 때,
__iter__
함수를 사용하는 과정을 거친 것과 다르게,
제너레이터 함수는yield
를 활용하여 제너레이터 형식으로만 만들어주면 자동으로 제너레이터로써 동작한다.
__next__()
: 이터레이터와 마찬가지로, 다음 차례의 값을 반환하며, 반환할 값이 더이상 없을 경우 StopIteration
이 발생gen = generator_squares()
print(gen.__next__()) # 0
print(gen.__next__()) # 1
print(gen.__next__()) # 4
print(gen.__next__())
# StopIteration Traceback (most recent call last)
# <ipython-input-52-9340d28f24b7> in <module>
# ----> 1 print(gen.__next__())
# StopIteration:
__send__()
: 제너레이터 함수 실행 중 __send__()
함수를 통해 값 전달이 가능함아래는 send
함수의 인수를 통해 받은 값을 yield
로 받아 received_value
에 할당하고, 그 값의 2배 수를 리턴 받는 제너레이터이다.
def generator_send():
received_value = 0
while True:
received_value = yield
print("received_value = ",end=""), print(received_value)
yield received_value * 2
gen = generator_send()
next(gen)
print(gen.send(2))
# received_value = 2
4
next(gen)
print(gen.send(3))
# received_value = 3
6
제너레이터에서는 위의 case 처럼 yield
를 통해 제너레이터 실행 중 값을 전달할 수 있으며, 이를 응용하여 제너레이터 함수를 사용해서 main 실행 루프의 연산 결과에 따라 호출을 제어할 수 있음
()
)를 활용하여 생성Lazy Evaluation(느긋한 계산법): 계산의 결과값이 필요할 때까지 계산을 늦추는 기법
L = [1, 2, 3]
def generate_square_from_list():
result = (x*x for x in L)
print(result)
return result
def print_iter(iter):
for element in iter:
print(element)
print_iter(generate_square_from_list())
import time
def print_iter(iter):
for element in iter:
print(element)
def lazy_return(num):
print("sleep 1s")
time.sleep(1)
return num
print("comprehension_list=")
comprehension_list = [ lazy_return(i) for i in L ]
print_iter(comprehension_list)
print("generator_exp=")
generator_exp = ( lazy_return(i) for i in L )
print_iter(generator_exp)
comprehension_list
[lazy_return(i) for i in L]
의 경우, L
리스트에 담긴 값 1, 2, 3을 차례로 불러 lazy_return()
의 인자로 넣어서
(1) print("sleep 1s")
를 출력하고
(2) 1초 간 쉬고
(3) 반환받은 결과값을 차례로 리스트에 넣는다.
따라서 comprehension_list
에는 [1, 2, 3]
값이 들어가며, 최종적으로 다음 print_iter(comprehension_list)
에서 for
루프를 통해 comprehension_list
의 값 1, 2, 3을 차례로 돌아 출력한다.
generator_exp
(lazy_return(i) for i in L)
은 제너레이터 표현식을 만들며 이를 generator_exp
라는 변수명에 저장한다. 따라서 위 과정을 마친 후의 generator_exp
변수는 제너레이터이며, print_iter(generator_exp)
를 통해 제너레이터 객체를 순회하며
(1) print("sleep 1s")
를 출력
(2) 1초간 쉬고
(3) L
의 값을 순회하며 출력
과정을 반복한다.
Lazy Evaluation 개념 정리
- 개념
- 장점
lambda
: 인라인 함수를 정의할 때 사용하며,
익명함수(Anonymous Functions) 또는 람다 표현식(Lambda Expression)으로 칭함
기존 함수와의 차이점
return
문이 없어도 표현식의 결과가 리턴# 함수
def name(arg1, arg2, ...):
block of statement
# lambda 함수
lambda argument1, argument2, ... argumentN : expression using arguments
lambda
또한 중첩되거나 복잡한 구조를 가질 수 있지만, 일반적으로 한줄로 간단한 형식으로 활용
f = lambda x, y, z : x + y + z
print(f) # <function <lambda> at 0x7f7edf1698b0>
print(f(1, 2, 3)) # 6
콜백함수
- 특정 이벤트가 발생했을 때 호출되는 함수
- 해당 콜백함수가 여러블록으로 구성된 실행문이 아니고 다른 컴포넌트에서 사용되지 않는다면 해당 컴포넌트만을 위한 람다 표현식이 적절함
Lambdas = [
lambda x : x ** 2,
lambda x : x ** 3,
lambda x : x ** 4
]
for lambda_func in Lambdas:
print( lambda_func(2) )
# 4
# 8
# 16
types
모듈을 활용해 LambdaType
확인하기import types
f = lambda x, y, z : x + y + z
print(f) # <function <lambda> at 0x7f7edf169af0>
print(type(f)) # <class 'function'>
print(type(f) == types.LambdaType) # True
types
모듈에 포함된 다른 타입들: <참조>
lambda
표현식을 활용한 형식으로 수정하기# 기본 함수
def check_password(password):
if len(password) < 8:
return 'SHORT_PASSWORD'
if not any(c.isupper() for c in password):
return 'NO_CAPITAL_LETTER_PASSWORD'
return True
# lambda 함수 활용
lambdas = [
lambda password : 'SHORT_PASSWORD' if len(password) < 8 else None,
lambda password : 'NO_CAPITAL_LETTER_PASSWORD' if not any(c.isupper() for c in password) else None
]
def check_password_using_lambda(password):
for f in lambdas:
if f(password) is not None:
return f(password)
return True
print( check_password_using_lambda('123') ) # SHORT_PASSWORD
print( check_password_using_lambda('12356789f') ) # NO_CAPITAL_LETTER_PASSWORD
print( check_password_using_lambda('123456789fF') ) # True
lambda
표현식 안에서if
문 사용하기lambda arguments: '값1' if '판별식1' else '값2' if '판별식2' else
all()
과any()
# all all(True, True, True) # True all(True, False, False) # False all(False, False, False) # False #any any(True, False, False) # True any(True, False, False) # True any(False, False, False) # False