x = [1, 2, 3]
y = [4, 5, 6]
iterator_items = zip(x, y)
print(iterator_items)
list(iterator_items)
<zip object at 0x7fcbee42c840>
[(1, 4), (2, 5), (3, 6)]
x = [1, 2, 3]
y = [4, 5, 6, 7]
z = [8, 9]
from itertools import zip_longest
iterator_items = zip_longest(x,y,z, fillvalue=0)
print(iterator_items)
list(iterator_items)
<itertools.zip_longest object at 0x7fcbee3da9f0>
[(1, 4, 8), (2, 5, 9), (3, 6, 0), (0, 7, 0)]
x = [1, 4, 3, 5, 2]
y = [1, 4, 3, 5, 2]
x.sort()
x
[1, 2, 3, 4, 5]
sorted(y, reverse=True)
[5, 4, 3, 2, 1]
x = (1, 4, 3, 5, 2)
filterd_x = filter(lambda i: i > 3, x)
print(filterd_x)
list(filterd_x)
<filter object at 0x7fcbec35d760>
[4, 5]
# 딕셔너리 값을 사용한 정렬
counts = [
{'word': 'python', 'count': 3},
{'word': 'practice', 'count': 3},
{'word': 'book', 'count': 2},
]
# itemgetter의 동작을 확인
from operator import itemgetter
sorted(counts, key=itemgetter('count'))
[{'word': 'book', 'count': 2}, {'word': 'python', 'count': 3}, {'word': 'practice', 'count': 3}]
def gen_function(n):
print('start')
while n:
print(f'yield: {n}')
yield n
n -= 1
# 제너레이터 이터레이터를 출력한다
gen = gen_function(2)
print(gen)
<generator object gen_function at 0x7fcbec3795f0>
# 내장함수 next() 에 전달하면 __next__() 메서드를 실행한다
# StopIteration 예외를 출력 할 때까지 반복한다
next(gen)
start yield: 2
2
x = [1, 2, 3, 4, 5]
list_comprehension = [i**2 for i in x]
list_comprehension
[1, 4, 9, 16, 25]
# 제너레이터 식을 활용하여 동일한 기능 구현하기
generator_items = (i**2 for i in x)
generator_items # 결과값이 필요 할 때까지 연산을 하지 않는다
<generator object <genexpr> at 0x7fcbec379740>
list(generator_items)
[1, 4, 9, 16, 25]
서브 제너레이터로 처리를 이첩하기
def chain(iterables):
for iterable in iterables:
yield from (v for v in iterable)
# for v in iterable:
# yield v
iterables = ('python', 'book', 'quant', 'anaysis')
print(chain(iterables))
" , ".join(list(chain(iterables)))
<generator object chain at 0x7fcbec379a50>
'p , y , t , h , o , n , b , o , o , k , q , u , a , n , t , a , n , a , y , s , i , s'
def gen(n):
while n:
yield n
n -= 1
# zip() 함수에 `리스트` 와 `제너레이터` 동시에 전달
x = [1,2,3,4,5]
print([i for i in zip(x, gen(5))])
odd = filter(lambda x :x % 2 == 1, gen(5))
print(odd)
[_ for _ in odd]
[(1, 5), (2, 4), (3, 3), (4, 2), (5, 1)] <filter object at 0x7fcbec37d2e0>
[5, 3, 1]
try:
len(gen(5))
except Exception as e:
print(e) # ... PRINT THE ERROR MESSAGE
# 이럴땐, 제너레이터를 list() 객체로 변환 한 뒤에 적용 하면 됩니다
# 단 리스트 크기에 따라 메모리에 부담이 될 수 있습니다
len(list(gen(5)))
object of type 'generator' has no len()
5
list_nums = list(gen(4))
list_nums
[4, 3, 2, 1]
읽기 -> 변환 -> 쓰기의 과정을 단계적 실행하여, 메모리에 부담을 주지않습니다
# 파일을 한 행씩 읽는다
def reader(src):
with open(src) as f:
for line in f:
yield line
## 행 단위로 실행
def convert(line):
return line.upper()
# 읽기 -> 변환 -> 쓰기를 한 행씩 실행
def write(dest, reader):
with open(dest, 'w') as f:
for line in reader:
f.write(convert(line))
# write('dest.text', reader('src.txt'))
from functools import lru_cache
from time import sleep
# 최근 호출한 인수와 결과를 최대 32회까지 캐쉬
@lru_cache(maxsize=32)
def heavy_funcion(n):
sleep(3) # 무거운 처리 시뮬레이션
return n + 1
%time print(heavy_funcion(2))
3 CPU times: user 1.72 ms, sys: 30 µs, total: 1.75 ms Wall time: 3.02 s
# cache 를 실행하는 경우, 바로 결과를 출력한다
%time heavy_funcion(2)
CPU times: user 2 µs, sys: 0 ns, total: 2 µs Wall time: 3.1 µs
3
from dataclasses import dataclass
@dataclass(frozen=True)
class Fruit:
name: str # 타입 힌트를 붙여 속성을 정의
price: int = 0 # 초기값도 지정
# __init__()나 __repr__()가 추가되어 있음
apple = Fruit(name='apple', price=128)
print(apple)
# frozen=True 읽기전용 오류를 출력
try:
apple.price = 256
except Exception as e:
import termcolor # termcolor.COLORS
print(termcolor.colored(e, 'red'))
Fruit(name='apple', price=128)
cannot assign to field 'price'
# 데커레이트 함수를 받음
def deco1(f):
print('deco1 called')
def wrapper():
print('before exec')
v = f() # 원래 함수를 실행
print('after exec')
return v
return wrapper
# 데커레이터 함수 정의 시 실행됨
@deco1
def func():
print('exec')
return 1
# deco1(func)의 결과로 대체되어 있음
print(func.__name__, "\n" + "*"*20)
func()
deco1 called wrapper ******************** before exec exec after exec
1
def deco2(f):
# 새로운 함수가 인수를 받음
def wrapper(*args, **kwargs):
print('before exec')
# 인수를 전달해 원래 함수를 실행
v = f(*args, **kwargs)
print('after exec')
return v
return wrapper
@deco2
def func(x, y):
print('exec')
return x, y
func(1, 2)
before exec exec after exec
(1, 2)
# 인수 z를 받음
def deco3(z):
def _deco3(f): # deco2()와 동일한 구조
def wrapper(*args, **kwargs):
# 여기 에서도 z 참조 가능하다
print('before exec', z)
v = f(*args, **kwargs)
print('after exec', z)
return v
return wrapper
return _deco3 # 데커레이터 반환
# deco3(z=3)의 반환값이 데커레이터의 실체에게 전달,
# func = deco3(z=3)(func) 와 동일하다
@deco3(z=3)
def func(x, y):
print('exec')
return x, y
# z에 전달한 값은 유지되고 있음
func(1, 2)
before exec 3 exec after exec 3
(1, 2)
안쪽 데코레이터를 바깥쪽 데코레이터가 감싼다
@deco3(z=4)
@deco3(z=3)
def func(x, y):
print('exec')
return x, y
func(1, 2)
before exec 4 before exec 3 exec after exec 3 after exec 4
(1, 2)
wraps()
함수를 사용합니다from functools import wraps
def deco4(f):
@wraps(f) # 원래 함수를 인수로 받는 데커레이터
def wrapper(*args, **kwargs):
print('before exec')
v = f(*args, **kwargs)
print('after exec')
return v
return wrapper
@deco4
def func():
"""func입니다"""
print('exec')
func.__name__, func.__doc__
('func', 'func입니다')
# decorator 의 인자를 받는 함수로 1번 더 감싸야 한다
from functools import wraps
def check_with_arg(test):
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
print(f"Args from Decorator is : {test}")
result = f(*args, **kwargs)
return result
return wrapper
return decorator
@check_with_arg(test=1)
def testing(test):
print(test)
testing("go Python")
Args from Decorator is : 1 go Python
wraps()
함수를 사용합니다from functools import wraps
import time
def elapsed_time(f):
@wraps(f)
def wrapper(*args, **kwargs):
start = time.time()
v = f(*args, **kwargs)
print(f"{f.__name__}: {time.time() - start}")
return v
return wrapper
# 0부터 n-1까지의 총합을 계산하는 함수
@elapsed_time
def func(n):
return sum(i for i in range(n))
# func() 실행 결과 표시
# f-string에서 콤마(,)로 구분해 여러값을 지정
f'{func(1000000)=:,}'
func: 0.031409263610839844
'func(1000000)=499,999,500,000'
class ContextManager:
def __enter__(self):
return 1
def __exit__(self, exc_type, exc_value, traceback):
pass
print("Process 1")
with ContextManager() as f:
print(f)
# as 키워드 생략
print("Process 2")
with ContextManager():
pass
Process 1 1 Process 2
contextlib.contextmanager
를 활용한 구현
from contextlib import contextmanager
@contextmanager
def point(**kwargs):
print('__enter__ was called')
value = kwargs # dict 파라미터 객체
try:
# 전처리 : value 를 as 키워드에 전달
yield value # 아래부터는 후처리
except Exception as e:
print(e)
raise # 에러를 호출됨
finally:
print('__exit__ was called')
print(value)
with point(x=1, y=2) as p:
print(p)
p['z'] = 3
__enter__ was called {'x': 1, 'y': 2} __exit__ was called {'x': 1, 'y': 2, 'z': 3}
contextlib.contextmanager
를 활용한 구현
import logging
from contextlib import contextmanager
logger = logging.getLogger(__name__)
logger.addHandler(logging.StreamHandler())
# 기본값이 INFO 레벨이므로, DEBUG 레벨 로그는 무시됨
logger.setLevel(logging.INFO)
@contextmanager
def debug_context():
level = logger.level
try:
# 로깅 레벨을 변경함
logger.setLevel(logging.DEBUG)
yield
finally:
# 원래 로깅 레벨로 되돌림
logger.setLevel(level)
def main():
logger.info('before: info log')
logger.debug('before: debug log')
# DEBUG 로그를 볼 때의 처리를 with 문 블록 안에서 실행함
with debug_context():
logger.info('inside the block: info log')
logger.debug('inside the block: debug log')
logger.info('after: info log')
logger.debug('after: debug log')
if __name__ == '__main__':
main()
before: info log inside the block: info log inside the block: debug log after: info log
# 디스크립터가 가진 메서드가 정의되어 있음
" , ".join(dir(property()))
'__class__ , __delattr__ , __delete__ , __dir__ , __doc__ , __eq__ , __format__ , __ge__ , __get__ , __getattribute__ , __gt__ , __hash__ , __init__ , __init_subclass__ , __isabstractmethod__ , __le__ , __lt__ , __ne__ , __new__ , __reduce__ , __reduce_ex__ , __repr__ , __set__ , __setattr__ , __sizeof__ , __str__ , __subclasshook__ , deleter , fdel , fget , fset , getter , setter'
__set__()
디스크립터 구현¶Data Descriptor
__set_name__()
: 디스크립터를 사용하는 클래스 객체 와 속성 딕셔너리 를 전달 합니다__set__()
: Memory <==
대입 시 호출__get__()
: Memory ==>
취득 시 호출class TextField:
def __set_name__(self, owner, name):
print(f'__set_name__ was called')
print(f'{owner=}, {name=}')
self.name = name
def __set__(self, instance, value):
print('__set__ was called')
if not isinstance(value, str):
raise AttributeError('must be str')
# .표기법이 아닌 속성 딕셔너리를 사용해 저장
instance.__dict__[self.name] = value
def __get__(self, instance, owner):
print('__get__ was called')
return instance.__dict__[self.name]
class Book:
title = TextField()
__set_name__ was called owner=<class '__main__.Book'>, name='title'
book = Book()
# `Memory <==` 대입 시 호출 __set__()
book.title = 'Python Practice Book'
__set__ was called
# `Memory ==>` 취득 시에는 __get__()
book.title
__get__ was called
'Python Practice Book'
# 다른 인스턴스를 작성해서 대입
notebook = Book()
notebook.title = 'Notebook'
__set__ was called
# 각 데이터를 유지하고 있음
book.title, notebook.title
__get__ was called __get__ was called
('Python Practice Book', 'Notebook')
# 문자열 이외는 대입할 수 없음
try:
book.title = 123
except Exception as e:
print(e)
__set__ was called must be str
__get__()
디스크립터 구현¶Non Data Descriptor 또는 Non override descriptor 라고 한다
__get__()
포함한 인스턴스는 호출하지 않는다# __get__()만 있다면 비데이터 디스크립터
class TextField:
def __init__(self, value):
if not isinstance(value, str):
raise AttributeError('must be str')
self.value = value
def __set_name__(self, owner, name):
print(f'__set_name__ was called')
print(f'{owner=}, {name=}')
self.name = name
def __get__(self, instance, owner):
print('__get__ was called')
return self.value
class Book:
title = TextField('Python Practice Book')
book = Book()
book.title # 대입 전에는 __get__() 호출
__set_name__ was called owner=<class '__main__.Book'>, name='title' __get__ was called
'Python Practice Book'
book.title = 'Book' # 대입하면 인스턴스 변수가 된다
book.title # 인스턴스 변수가 있으면 __get__()은 호출되지 않음
'Book'
@LazyProperty
는 비데이터 디스크립터로 __get__()
안에서 원래 함수를 실행한다__get__()
을 호출하지 않는다class LazyProperty:
def __init__(self, func):
self.func = func
self.name = func.__name__
def __get__(self, instance, owner):
if not instance: # 클래스 변수로 엑세스될 때
return self
v = self.func(instance) # self.func는 함수로 명시적으로 인스턴스를 전달
instance.__dict__[self.name] = v
return v
TAX_RATE = 1.10
class Book:
def __init__(self, raw_price):
self.raw_price = raw_price
@LazyProperty
def price(self):
print('calculate the price')
return int(self.raw_price * TAX_RATE)
book = Book(1980)
book.price
calculate the price
2178