해당 글은 파이썬 코딩의 기술(개정 2판 -> 3.8버전까지 맞춤형) 서적을 읽고
나중에 다시 보려고 몇몇 내용을 간략하게 정리했다.
'이렇게 짯으면 더 좋은 코드가 되었을텐데' 라는 부분이 많았고
알지만 애매하게 알았던 부분에 대해 도움이 되었던 내용을 정리했다.
BETTER WAY 08 - 여러 이터레이터에 루프를 수행하려면 zip를 사용하라
주어진 names list의 가장 긴 길이를 가지는 원소를 구해보자.
longest_name = None
max_count = 0
names = ['Kain', 'Joe', 'Alice']
counts = [ len(n) for n in names ]
print(counts)
>>>
[4, 3, 5]
enumerate를 생각할 수 있다.
for i, name in enumerate(names):
count = counts[i]
if count > max_count:
longest_name = name
max_count = count
print(longest_name)
>>>
Alice
zip을 사용하면 더 깔끔한 코드를 만들 수 있다.
for name, count in zip(names, counts):
if count > max_count:
longest_name = name
max_count = count
print(longest_name)
>>>
Alice
BETTER WAY 10 - 대입식을 사용해 반복을 피해라
대입식 assignment expression이라하며 왈러스 연산자(walrus operator)라고 한다.
대입식( := )은 파이썬 3.8에서 새롭게 도입된 구문이다.
파이썬 3.8의 새롭게 추가된 기능 정리 참조
https://docs.python.org/ko/3/whatsnew/3.8.html
fresh_fruit = {
'레몬': 5,
'사과': 10
}
count = fresh_fruit.get('레몬', 0)
if count:
print('more than 0')
...
위의 코드를 아래와 같이 count변수 선언 중복을 줄일 수 있다.
if count:= fresh_fruit.get('레몬', 0):
print('more than 0')
...
다른 사용 예시이다.
if count := 0:
print(f"if : {count}")
else:
print(f"else : {count}")
print(f"out of if, {count}")
>>>
else : 0
out of if, 0
BETTER WAY 13 - 슬라이싱보다는 나머지를 모두 잡아내는 언패킹을 사용하라
언패킹시에 별표식(starred expression)을 사용할 수 있다.
car_ages = [0, 9, 4, 8, 7 ,20, 19, 1, 6, 15]
car_ages_decending = sorted(car_ages, reverse=True)
# 가장오래된 자동차와 두번째로 오래된 자동차 가져오기
oldest, second_oldest, *others = car_ages_decending
print(oldest, '/',second_oldest, '/', *others)
>>> 20 / 19 / 15 9 8 7 6 4 1 0
# 별표식의 위치를 조정할 수 있다.
oldest, *others, youngest = car_ages_decending
print(oldest, '/',youngest, '/', *others)
>>> 20 / 0 / 19 15 9 8 7 6 4 1
슬라이싱(ex. value[2:7]) 보다 코드의 가독성이 좋지만 아래와 같은 제한사항도 있다.
# 별표식만으론 언패킹 불가능
*others = car_ages_decending
>>> SyntaxError: starred assignment target must be in a list or tuple
# 언패킹 패턴에 별표식을 두개 이상 사용 불가능
first, *middle, *second_middle, last = [1,2,3,4,5]
>>> SyntaxError: multiple starred expressions in assignmen
BETTER WAY 15 - 딕셔너리 삽입순서에 대해서
Python 3.6부터는 딕셔너리 삽입 순서를 보존하도록 개선되었다.
이제 이터레이션을 통해 변수에 접근해도 순서를 생각하지 않고 편하게 로직을 구현 할 수 있다.
def my_func(**kwargs):
for key, value in kwargs.items():
print('%s = %s' % (key, value))
my_func(goose='gosling', kangaroo='joey', dog='john', cat='ailce')
>>>
goose = gosling
kangaroo = joey
dog = john
cat = ailce
BETTER WAY 22 - 변수 위치 인자를 사용해 시각적인 잡음을 줄여라
아래 예시는 log의 두번째 인자를 배열로 받는다.
def log(message, values):
if not values:
print(message)
else:
values_str = ', '.join(str(x) for x in values)
print(f'{message}: {values_str}')
log('내 숫자는', [1,2])
log('안녕', [])
>>>
내 숫자는: 1, 2
안녕
위치 인자(positional argument)를 가변적으로 받을 수 있으면 함수 호출문이 더 깔끔해 진다.
두번째 인자가 없을 경우도 있으므로 빈 리스트를 넘기는 것 보다는 완전히 생략하는게 좋을 것이다.
def log(message, *values): # 달라진 부분
if not values:
print(message)
else:
values_str = ', '.join(str(x) for x in values)
print(f'{message}: {values_str}')
log('내 숫자는', 1,2)
log('안녕')
단, 가변 인자가 들어가는 부분에 인자의 개수가 많으면,
많은 메모리를 소비할 수 있으므로 인자의 개수가 적을 경우 사용하면 좋다.
BETTER WAY 25 - 위치 또는 키워드 인자를 지정하여 함수 호출을 명확하게 하라
키워드 인자의 유연성을 활용하면 코드를 처음 읽는 사람도 명확하게 이해할 수 있다.
아래 예시는 숫자를 나눌 때 발생하는 예외처리를 처리하는 예시이다.
3, 4번째 인자를 통해 OverflowError, ZeroDivisionError를 어떻게 처리할지 선택 할 수 있다.
def safe_division(number, divisor, ignore_overflow, ignore_zero_division):
try:
return number / divisor
except OverflowError:
if ignore_overflow:
return 0
else:
raise
except ZeroDivisionError:
if ignore_zero_division:
return float('inf')
else:
raise
result = safe_division(1.0, 10**500, True, False)
print(result)
# >> 0
result = safe_division(1.0, 0, False, True)
print(result)
# >>> inf
예시에선 인자가 4개지만 인자가 많아지면 함수 호출시 순서를 혼동하여 의도치 않은 결과를 줄 수있다.
여기서 인자에 대해 위치인자만 사용(/), 키워드인자만 사용(*) 하게 끔 함수 호출문에 정의를 할 수있다.
def safe_division(
number, divisor,
/, *, #변경 부분
ignore_overflow=False, ignore_zero_division=False
):
....
'/' 문자를 통해 인자 number, divisor는 위치인자만 사용하도록 정하고
'*' 문자를 통해 ignore_overflow, ignore_zero_division는 키워드 인자만 사용이 가능하다.
result1 = safe_division(1.0, 10)
print(result1)
>> 0.1
result2 = safe_division(1.0, divisor=5)
print(result2)
# 위치인자에 키워드인자를 선언하여 에러
# >> TypeError: safe_division() got some positional-only arguments passed as keyword arguments: 'divisor'
result3 = safe_division(1.0, 10**500, ignore_overflow=True)
print(result3)
# >> 0
result4 = safe_division(1.0, 0, False, True)
print(result4)
# 키워드인자에 위치인자를 선언하여 에러
# >>> Error : safe_division() takes 2 positional arguments but 4 were give
BETTER WAY 26 - 함수데코레이터 사용
파이썬은 데코레이터(decorator)라는 특별한 구문을 제공한다.
특정 함수가 호출되기 전과 후에 추가로직을 실행 시켜줄 수 있다.
기존 함수를 변경하지 않으면서 추가 함수를 선언하거나 디버깅 하는 듯 유용하게 사용할 수 있다.
코딩 도장이라는 곳에서 예제코드를 참고했다.
콘솔에 찍히는 내용을 보면 어떤 흐름으로 실행되는지 알 수 있다.
def trace(func): # 호출할 함수를 매개변수로 받음
def wrapper():
print(func.__name__, '함수 시작') # __name__으로 함수 이름 출력
func() # 매개변수로 받은 함수를 호출
print(func.__name__, '함수 끝')
return wrapper # wrapper 함수 반환
@trace # @데코레이터
def hello():
print('hello')
@trace # @데코레이터
def world():
print('world')
hello() # 함수를 그대로 호출
world() # 함수를 그대로 호출
>>>
hello 함수 시작
hello
hello 함수 끝
world 함수 시작
world
world 함수 끝
아래와 같이 다중 데코레이션 지정도 가능하다.
def decorator1(func):
def wrapper():
print('decorator1')
func()
return wrapper
def decorator2(func):
def wrapper():
print('decorator2')
func()
return wrapper
# 데코레이터를 여러 개 지정
@decorator1
@decorator2
def hello():
print('hello')
hello()
>>>
decorator1
decorator2
hello
또한 재귀함수에서 디버깅할 때도 유용하게 쓰인다.
아래는 피보나치수열을 재귀로 구현한 예제이다.
def trace1(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
print(f'{func.__name__}({args!r}, {kwargs!r})->{result!r}')
return result
return wrapper
@trace1
def fibonacci(n):
"""n번째 피보나치 수를 반환한다."""
if n in (0, 1):
return n
return (fibonacci(n-2) + fibonacci(n-1))
fibonacci = trace1(fibonacci)
fibonacci(4)
결과값
fibonacci((1,), {})->1
wrapper((1,), {})->1
fibonacci((0,), {})->0
wrapper((0,), {})->0
fibonacci((1,), {})->1
wrapper((1,), {})->1
fibonacci((2,), {})->1
wrapper((2,), {})->1
fibonacci((3,), {})->2
wrapper((3,), {})->2
BETTER WAY 27 - 컴프리헨션 사용
파이썬은 이터러블에서 새 리스트를 만든는 간결한 구문인 컴프리헨션을 제공한다.
가독성이 좋아 자주 쓰이며, 리스트를 만드는 다른 방법과 비교해보자.
아래는 각 배열의 요소를 제곱근로 변환하는 예제이다.
첫번째는 for반복문, 두번째는 리스트컴프리헨션, 세번째는 람다함수를 사용하였고 결과 값을 모두 동일하다.
a = [1,2,3,4,5,6,7,8,9,10]
squares_1 = []
for x in a:
squares_1.append(x**2)
print(squares_1)
squares_2 = [x**2 for x in a]
print(squares_2)
squares_3 = list(map(lambda x: x**2, a))
print(squares_3)
>>>
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
위의 예제에서 짝수요소만 제곱근을 하게 하는 조건을 추가한다.
result_2 = [x**2 for x in a if x%2 == 0]
print(result_2)
result_3 = list(map(lambda x: x**2, filter(lambda x: x%2 ==0, a)))
print(result_3)
>>>
[4, 16, 36, 64, 100]
[4, 16, 36, 64, 100]
비교해본 것과 같이 코드를 짧게 사용하고 가독성도 좋은 컴프리헨션을 사용하는데 의도를 나타내는데 명확하다.
또한 딕셔너리와 집합 자료형도 컴프리헨션으로 생성할 수 있다.
BETTER WAY 26 - 이터레이터나 제너레이터 사용시 itertools를 사용하자
itertools를 사용하면 단순히 반복해서 출력하는 것이 아닌 여러가지 기능을 만들 수 있다.
itertools 함수는 세가지 범주로 나뉜다.
- 이터레이터 연결
- 이터레이터 필터링
- 원소의 조합
이터레이터 연결
import itertools
# chain
it = itertools.chain([1,2,3],['a','b','c'])
print(list(it))
>>>
[1, 2, 3, 'a', 'b', 'c']
# repeat
it = itertools.repeat('hellow', 5)
print(list(it))
>>>
['hellow', 'hellow', 'hellow', 'hellow', 'hellow']
이터레이터 필터링
import itertools
# takewhile
values = [1,2,3,4,5,6,7,8,9,0]
less_than_seven = lambda x : x < 7
it = itertools.takewhile(less_than_seven, values)
print(list(it))
>>>
[1, 2, 3, 4, 5, 6]
# filter & filterFalse
values = [1,2,3,4,5,6,7,8,9,0]
evens = lambda x: x % 2 == 0
filter_result = filter(evens, values)
print(f"Filter: {list(filter_result)}")
filter_false_result = itertools.filterfalse(evens, values)
print(f"Filter false: {list(filter_false_result)}")
>>>
Filter: [2, 4, 6, 8, 0]
Filter false: [1, 3, 5, 7, 9]
원소의 조합
import itertools
# accumulate
# 원본 이터레이터의 각 원소에 대해 누적된 결과를 반환한다.
values = [1,2,3,4,5,6,7,8,9,0]
sum_reduce = itertools.accumulate(values)
print(f"SUM : {list(sum_reduce)}")
>>>
SUM : [1, 3, 6, 10, 15, 21, 28, 36, 45, 45]
# permutations
# 원소들로 만들어낸 길이 N인 순열을 돌려준다.
it = itertools.permutations([1,2,3,4],2)
print(list(it))
>>>
[(1, 2), (1, 3), (1, 4), (2, 1), (2, 3), (2, 4), (3, 1), (3, 2), (3, 4), (4, 1), (4, 2), (4, 3)]
제시된 예제들은 각챕터의 일부분에 해당하며
더 많은 유용한 정보를 위해 해당 서적을 읽어보기를 추천한다.
'Programming > Python' 카테고리의 다른 글
Python - Product vs Permutations vs Combinations (0) | 2024.08.18 |
---|---|
Python - Numpy란 [1] (0) | 2023.11.18 |
Python - 클로저와 __call__ 함수 (0) | 2023.06.18 |
Python - AES 양방향 암호화 (0) | 2023.03.27 |
Python - ModuleNotFoundError : No module named (0) | 2023.03.27 |