foo = long_function_name(
var_one, var_two, var_three,
var_four):
print(var_one)
이 코드에서처럼 첫 번째 줄에 파라미터가 없다면, 공백 4칸 인덴트를 한 번 더 추가하여 다른 행과 구분되게 한다.
foo = long_function_name(
var_one, var_two,
var_three, var_four)
이 코드에서처러 여러 줄로 나눠쓸 경우 다음 행과 구분되도록 인덴트를 추가한다.
파이썬으로 코딩 시에는 스네이크 표기법 코딩을 기본으로 하되 혹시 면접관이 이런 질문을 하게 된다면 파이썬의 PEP8 및 철학에 따라 스네이크 코딩을 지향한다고 얘기할 수 있어야 한다.
# 카멜 케이스
camelCase: int = 1
# 스네이크 케이스
snake_case: int = 1
a: str = "1"
b: int = 1
예를 들어 기존에 타입 힌트를 사용하지 않는 파이썬 함수는 다음과 같이 함수를 정의해 사용해왔다.
def fn(a):
빠르게 정의해서 사용할 수 있다는 장점이 있지만 fn() 함수의 파라미터 a에는 숫자를 넘겨야 하는지, 문자를 넘겨야 하는지 전혀 알 수 없으며 이 함수의 리턴값이 무엇인지도 알 수 없다. a에 숫자를 넘겨야 하는데 잘못해서 문자를 넘기게 되면 버그가 발생하게 될 것이기 때문이다.
def fn(a: int) -> bool:
이처럼 타입 힌트를 사용하게 되면 이제 fn()함수의 파라미터 a가 정수형임을 분명하게 알 수 있으며 리턴 값으로 True 또는 Falase를 리턴할 것이라는 점도 확실하게 알 수 있다.
이와 같이 명시적으로 선언하게 되면 가독성이 좋아지며 버그 발생 확률을 줄일 수 있다.
>>> list(map(lambda x: x + 10, [1, 2, 3]))
[11, 12, 13]
리스트 컴프리헨션이란 기존 리스트를 기반으로 새로운 리스트를 만들어내는 구문이다.
다음은 홀수인 경우 2를 곱해 출력하라는 리스트 컴프리헨션이다.
>>> [n + 2 for n in range(1, 10 + 1) if n % 2 == 1]
[2, 6, 10, 14, 18]
리스트 컴프리헨션이라고 반딋 리스트만 가능한 것은 아니다. 버전 2.7 이후에는 다음과 같이 리스트 외에도 딕셔너리 등이 가능하도록 추가됐다.
a = {key: value for key, value in original.items()}
이처럼 한 줄로 간결하게 작성할 수 있는 리스트 컴프리헨션은 가독성이 좋은 편이지만 이 또한 무리하고 복잡하게 작성할 경우 가독성을 떨어뜨릴 수 있으므로 적절히 사용하는 것이 중요하다. 대체로 표현식은 2개를 넘지 않아야 한다.
예를 들어 임의의 조건으로 숫자 1억 개를 만들어내 계산하는 프로그램을 작성한다고 가정해보자. 이 경우 제너레이터가 없다면 메모리 어딘가에 만들어낸 숫자 1억 개를 보관하고 있어야 한다. 그러나 제너레이터를 이용하면, 단순히 제너레이터만 생성해두고 필요할 때 언제든 숫자를 만들어낼 수 있다. 만약에 1억 개 중 100개 정도만 쓰인다면 차이는 더욱 클 것이다.
이때 yield 구문을 사용하면 제너레이터를 리턴할 수 있다.
기존의 함수는 return 구문을 맞닥뜨리면 값을 리턴하고 모든 함수의 동작을 종료한다. 그러나 yield는 제너레이터가 여기까지 실행 중이던 값을 내보낸다는 의미로, 중간값을 리턴한 다음 함수는 종료되지 않고 계속해서 맨 끝에 도달할 때까지 실행된다.
다음 코드의 경우처럼 while True 구문은 종료 조건이 없으므로 계속해서 값을 내보낼 수 있다.
def get_natural_number():
n = 0
while True:
n += 1
yield n
이 경우 함수의 리턴 값은 다음과 같이 제너레이터가 된다.
만약 다음 값을 생성하려면 next()로 추출하면 된다. 예를 들어 100개의 값을 생성하ㅗㄱ 싶다면 다음과 같이 100번 동안 next()를 수행하면 된다.
g = get_natural_number()
for _ in range(0, 100):
print(next(g))
아울러 제너레이터는 다음과 같이 여러 타입의 값을 하나의 함수로 생성하는 것도 가능하다.
def generator():
yield 1
yield 'string'
yield True
g = generator()
>>> next(g)
1
>>> next(g)
'string'
>>> next(g)
True
>>> list(range(5))
[0, 1, 2, 3, 4]
>>> range(5)
range(0, 5)
>>> type(range(5))
<class 'range'>
>>> for i in range(5):
print(i, end=' ')
0 1 2 3 4
이 코드에서 range()는 range 클래스를 리턴하며, for 문에서 사용할 경우 내부적으로는 제너레이터의 next()를 호출하듯 매번 다음 숫자를 생성해내게 된다.
만약 생성할 숫자가 100만 개쯤 된다면 어떻게 될까? 메모리에서 적지 않은 공간을 차지할 것이고 생성 시간도 오래 걸릴 것이다. 그러나 제너레이터를 리턴하듯 range 클래스만 리턴하면 그렇지 않다. 생성 조건만 정해두고 나중에 필요할 때 생성해서 꺼내 쓸 수 있다.
a = [n for n in range(1000000)]
b = range(1000000)
실제로 len()으로 길이 비교를 해보면 둘 다 동일한 100만 개가 출력되며, 비교 연산자에서도 True를 리턴한다.
그러나 a에는 이미 생성된 값이 담겨 있고, b는 생성해야 한다는 조건만 존재한다.
이제 둘 사이의 메모리 점유율을 비교해보면 range클래스를 리턴하는 방식의 장점이 쉽게 와닿을 것이다.
>>> sys.getsizeof(a)
8697464
>>> sys.getsizeof(b)
48
range클래스는 인덱스로 접근 시에는 바로 생성사도록 구현되어 있기 때문에 다음과 같이 리스트와 거의 동일한 느낌으로 불편 없이 사용할 수 있다.
>>> b[999]
999
>>> a = [1, 2, 3, 2, 45, 2, 5]
>>> a
[1, 2, 3, 2, 45, 2, 5]
>>> enumerate(a)
<enumerate object at 0x1010f83f0>
>>> list(enumerate(a))
[(0, 1), (1, 2), (2, 3), (3, 2), (4, 45), (5, 2), (6, 5)]
이처럼 list()로 결과를 추출할 수 있는데, 인덱스를 자동으로 부여해주기 때문에 매우 편리하게 활용할 수 있다. 그렇다면 a = ['a1', 'b2', 'c3']가 있을 때 이 리스트의 인덱스와 값을 함께 출력하려면 어떻게 해야할까?
for l, v in enumerate(a):
print(l, v)
>>> 5 / 3
1.66666666667
>>> type(5 / 3)
<class 'float'>
# // 나눗셈 연산자는 int(a / b)와 동일하다
>>> 5 // 3
1
>>> type(5 // 3)
<class 'int'>
>>> int (5 / 3)
1
>>> type(int(5 / 3))
<class 'int'>
나머지를 구하는 모듈로 연산자는 %이며 다음과 같이 사용할 수 있다.
# 나머지
>>> 5 % 3
2
몫과 나머지를 동시에 구하려면 divmod() 함수를 사용하면 된다. 앞으로 이 책에서 문제풀이 시 이 함수도 자주 사용할 예정이다.
# 몫과 나머지를 한 번에 구할 때
>>> divmod(5, 3)
(1, 2)
>>> print('A1', 'B2')
A1 B2
다음과 같이 sep 파라미터로 구분자를 콤마(,)로 지정해줄 수도 있다.
>>> print('A1', 'B2', sep=',')
A1,B2
print() 함수는 항상 줄바꿈을 하기 때문에 긴 루프의 값을 반복적으로 출력하면 디버깅하기가 어려운데 이 경우 다음과 같이 end 파라미터를 공백으로 처리하여 줄바꿈을 하지 않도록 제한할 수 있다.
print('aa', end=' ')
print('bb')
aa bb
리스트를 출력할 때는 join()으로 묶어서 처리한다.
>>> 'a' = ['A', 'B']
>>> print(' '.join(a))
A B
다음과 같이 idx와 fruit이 정의되어 있다고 할 때,
>>> idx = 1
>>> fruit = "Apple"
idx 값에 1을 더해서 fruit와 함께 출력하는 방법은 어떤 방법이 있을까?
>>> print(f'{idx + 1}: {fruit}')
2: Apple
※ 아쉬운 점은 f-string은 파이썬 3.6+에서만 지원한다는 점이다. 그 이하 버전에서는 동작하지 않으니 이 점에 유의해야 한다.
import pprint
pprint.pprint(locals())
pprint로 출력하게 되면 보기 좋게 줄바꿈 처리를 해주기 때문에 가독성이 높다.
클래스 메소드 내부의 모든 로컬 변수를 출력해주기 때문에 디버깅에 많은 도움이 된다.