Chapter 3 함수
BETTER WAY 19 함수가 여러 값을 반환하는 경우 절대로 네 값 이상을 언패킹하지 말라
함수의 Return값이 많아질 경우
- 반환값의 순서를 확실하게 이해하고 있지 않다면, 나중에 알아내기 어려운 버그를 생성한다.
- 함수를 호출하는 부분과 반환 값을 언패킹하는 부분이 길어지고, 코드 줄이 바뀔 수 있어 가독성이 나빠진다.
- 경량 클래스(lightweight class)나 nametuple을 사용해 함수의 반환값을 지정하는 게 현명하다.
기억해야 할 내용
- 함수가 여러 값을 반환하기 위해 값들을 튜플에 넣어서 반환하고, 호출하는 쪽에서는 언패킹 구문을 쓸 수 있다.
- 함수가 반환한 여러 값을, 모든 값을 처리하는 별표 식을 사용해 언패킹할 수도 있다.
- 언패킹 구문의 변수가 4개 이상 나오면 실수하기 쉬우므로 4개 이상 사용하지 말아야 한다.
- 언패킹 구문의 변수가 4개 이상일 경우 반환값으로 작은 클래스를 반환하거나 namedtuple 인스턴스를 반환하라.
BETTER WAY 20 None 을 반환하기보다는 예외를 발생시켜라
Return None
- 0, False, None은 모두 동일하게 if문에서 제외되므로 Return 값을 None으로 사용할 경우 문제가 발생할 수 있다.
Return None의 해결방안
- 반환 값을 2-튜플로 분리해 첫 번째 부분은 연산의 성공 여부, 두 번째 부분은 실제 결괏값을 저장한다.
- 결코 None을 반환하지 않고, Exception을 발생 시켜 호출자가 처리하게 한다.
- 독스트링(Docstring)과 타입 애너테이션을 사용해 함수의 반환 타입을 지정해 None이 반환되지 않음을 알리는 것이 좋다.
기억해야 할 내용
- None을 반환하는 함수를 사용하면 None과 다른 값(0 or 빈 문자열)이 조건문에서 False로 평가될 수 있다.
- None을 반환하기보단 예외를 발생시켜라. 문서에 예외 정보를 기록해 예외를 제대로 처리하도록 하라.
- 함수가 어떤 경우에도 절대로 None을 반환하지 않는다는 것을 타입 애너테이션(typing 모듈)으로 명시할 수 있다.
BETTER WAY 21 변수 영역과 클로저의 상호작용 방식을 이해하라
sort 메서드
- key값에 별도의 도우미 함수를 작성해 임의의 정렬 방법을 정의할 수 있다.
Python 변수 영역과 클로저의 특징
- 파이썬이 Closure를 지원
- Closure란 자신이 정의된 영역 밖의 변수를 참조하는 함수
- Closure로 인해 sort 메서드에 사용되는 도우미 함수가 외부 함수 인자에도 접근할 수 있다.
- 즉, Closure란 지연 변수와 코드를 묶어서 처리해야 하거나 데이터를 숨기고 싶을 때 사용한다.
- 파이썬에서 함수가 first-class citizen 객체다.
- first-class citizen 객체란 직접 가리킬 수 있고, 변수에 대입하거나 다른 함수에 인자로 전달할 수 있으며, 식이나 if 문에서 함수를 비교하거나 함수에서 반환가는 것 등이 가능하다는 것
- first-class citizen 객체의 특성으로 인해 sort 메서드는 Closure 함수를 key 인자로 받을 수 있다.
- 파이썬에는 시퀀스(튜플 포함)를 비교하는 구체적인 내부 규칙이 존재한다.
- 파이썬은 시퀀스를 비교할 때 0번 인덱스의 값을 비교한 다음, 이 값이 같으면 다시 1번 인덱스의 값을 비교한다.
- 순서대로 원소를 비교해 두 값이 같으면 그다음 원소로 넘어가는 작업을 모든 원소를 비교하거나, 결과가 정해질 때까지 반복한다.
- 이런 규칙으로 인해 helper Closure가 반환하는 튜플이 서로 다른 두 그룹을 정렬하는 기준 역할을 할 수 있다.
Python 변수를 참조하는 순서 ☆
- 현재 함수의 영역
- 현재 함수를 둘러싼 영역(현재 함수를 둘러싸고 있는 함수 등)
- 현재 코드가 들어 있는 모듈의 영역 (전역 영역(global scope)이라고도 부름)
- 내장 영역(built-in scope)(len, str 등의 함수가 들어 있는 영역)
- 이 4가지의 영역에 해당하는 변수가 없다면 NameError 예외가 발생한다.
Python 변수에 값을 대입하는 방식 ☆☆☆
- 변수가 현재 영역에 이미 정의돼 있다면 그 변수의 값만 새로운 값으로 변경한다.
- 변수가 현재 영역에 정의돼 있지 않다면 변수 대입을 변수 정의로 취급한다
- 즉, 새로 정의된 변수는 함수 내부에 있는 Local 변수를 새롭게 정의한 것으로 취급한다.
nonlocal
- Closure 밖으로 데이터를 끌어내는 구문
- 지정된 변수에 대하여 영역 결정 규칙에 따라 대입될 변수의 영역을 결정할 수 있다.
- 한계점 : (전역 영역을 더럽히지 못하도록) 모듈 수준 영역까지 변수 이름을 찾아 올라가지 않는다.
- 변수 대입 시 직접 모듈 영역(전역 영역)을 사용해야 할 때 지정하는 global 문을 보완시켜주는 구문이다.
- 간단한 함수 외에는 어떤 경우라도 nonlocal을 사용하지 않아야 한다.
- 함수가 길고 nonlocal 문이 지정한 변수와 대입이 이뤄지는 위치의 거리가 멀수록 함수의 동작을 이해하기 힘들어진다.
- 사용 방식이 복잡해지면 도우미 함수로 상태를 감싸, nonlocal을 대체하도록 정의하는 것이 현명하다.
기억해야 할 내용
- Closure 함수는 자신이 정의된 영역 외부에서 정의된 변수도 참조할 수 있다.
- 기본적으로 Closure 내부에 사용한 대입문은 Closure를 감싸는 영역에 영향을 끼칠 수 없다.
- Closure가 자신을 감싸는 영역의 변수를 변경한다는 사실을 표시할 때는 nonlocal 문을 사용하라.
- 간단한 함수가 아닌 경우에는 nonlocal 문을 사용하지 말라.
BETTER WAY 22 변수 위치 인자를 사용해 시각적인 잡음을 줄여라.
위치 인자 (positional argument) (위치 기반 인자)
- 위치 인자를 가변적으로 받을 수 있으면 함수 호출이 더 깔끔해지고 시각적 잡음도 줄어든다.
- 스타 인자(star args) : c언어와 같이 관례적으로 가변 인자의 이름을 *args라고 붙이는 것에서 유래했다.
- 가변인자 함수에 시퀀스를 사용하려면 입력받는 인자 이름 앞에 *를 붙이면 위치 인자로 지정할 수 있다.
- 인자 목록에서 가변적인 부분에 들어가는 인자의 개수가 처리하기 좋을 정도로 작을 때 사용하기 적합하다.
- *args를 받아들이는 함수를 확장할 때는 키워드 기반의 인자만 사용해야 한다.
가변적인 위치 인자의 문제점
- 위치 인자가 함수에 전달되기 전에 항상 튜플로 변환된다.
- 함수를 호출하는 쪽에서 제너레이터 앞에 * 연산자를 사용하면 제너레이터의 모든 원소를 얻기 위해 반복한다.
- 새롭게 생성되는 튜플은 제너레이터가 만들어낸 모든 값을 포함하며, 이로 인해 많은 메모리를 소비하게 된다. ☆
- 함수에 새로운 위치 인자를 추가하면 해당 함수를 호출하는 모든 코드를 변경해야 한다. ☆
- 이미 위치 인자가 존재하는 함수에 인자 목록 앞부분에 추가하려고 시도하면, 코드가 미묘하게 깨질 수 있다.
기억해야 할 내용
- def 문에서 *args를 사용하면 함수가 가변 위치 기반 인자를 받을 수 있다.
- *연산자를 사용하면 가변 인자를 받는 함수에게 시퀀스 내의 원소들을 전달할 수 있다.
- 제너레이터에 *연산자를 사용하면 프로그램이 메모리를 모두 소진하고 중단될 수 있다. ☆
- *args를 받는 함수에 새로운 위치 기반 인자를 넣으면 감지하기 힘든 버그가 발생할 수 있다.
BETTER WAY 23 키워드 인자로 선택적인 기능을 제공하라
키워드 인자
- 파이썬의 함수에서 모든 일반적인 인자를 순서에 상관없이 키워드를 사용해 넘길 수 있다.
- 위치 기반 인자를 지정하려면 키워드 인자보다 앞에 지정해야 한다.
- 각 인자는 단 한 번만 지정해야 한다.
키워드 인자 장점
- 키워드 인자를 사용하면 코드를 처음 보는 사람들에게 함수 호출의 의미를 명확히 알려줄 수 있다.
- 키워드 인자의 경우 함수 정의에서 Default 값을 지정할 수 있다.
- 어떤 함수를 사용하던 기존 호출자에게는 하위 호환성을 제공하면서 함수 파라미터를 확장할 수 있는 방법을 제공한다.
- 기존 코드를 별도로 마이그레이션(Migration)을 하지 않아도 기능을 추가할 수 있다.
- 새로운 버그가 생길 여지가 줄어든다.
** 연산자
- 파이썬이 딕셔너리에 들어 있는 값을 함수에 전달하되 각 값에 대응하는 Key를 키워드로 사용하도록 명령한다.
- ** 연산자를 위치 인자나 키워드 인자와 섞어서 함수를 호출할 수 있다. / 중복되는 인자가 없어야 한다.
- ** 연산자를 여러 번 사용할 수도 있다. 다만 여러 딕셔너리에 겹치는 키가 없어야 한다.
**kwargs 파라미터
- 아무 키워드 인자나 받는 함수를 만들고 싶을 때 사용한다.
- 모든 키워드 인자를 dict에 모아주는 역할을 한다.
- 함수 본문에서 이 dict를 사용해 필요한 처리를 할 수 있다.
기억해야 할 내용
- 함수 인자를 위치에 따라 지정할 수도 있고, 키워드를 사용해 지정할 수도 있다.
- 키워드를 사용하면 위치 인자만 사용할 때는 혼동할 수 있는 여러 인자의 목적을 명확히 할 수 있다.
- 키워드 인자와 Default 값을 함께 사용하면 기본 호출 코드를 마이그레이션하지 않고도 함수에 새로운 기능을 쉽게 추가할 수 있다.
- 선택적 키워드 인자는 항상 위치가 아니라 키워드를 사용해 전달돼야 한다.
BETTER WAY 24 None과 독스트링을 사용해 동적인 디폴트 인자를 지정하라.
Default 인자 ☆☆☆
- 함수가 정의되는 시점에 단 한 번만 호출된다.
- 모듈을 호출하는 Default인자일 경우 처음 함수가 정의될 때 값으로 고정이 된다.
- 즉, Default 값을 None으로 지정하고 실제 동작을 독스트링에 문서화 하는 것이 효율적이다.
typing.Optional
- Optional 타입 애너테이션으로 None이 허용되는 함수의 매개 변수에 타입을 명시할 때 사용한다.
- ex) when : Optional[datetime] = None
- when 매개 변수는 datetime 타입만 사용할 수 있고, None이 허용된다.
기억해야 할 내용
- Default 인자 값은 그 인자가 포함된 함수 정의가 속한 모듈이 로드되는 시점에 단 한 번만 평가된다. ☆
- Default 인자에 동적인 값 ({}, [], datetime.now() 등)의 경우 얕은 복사로 인해 이상한 동작이 일어날 수 있다. ☆☆
- 동적인 값을 가질 수 있는 키워드 인자의 Default 값을 표현할 때는 None을 사용하라.
- 동적인 값을 가질 수 있는 키워드 인자는 함수의 독스트링에 실제 동적인 Default 인자가 어떻게 동작하는지 문서화해두라.
- 타입 애너테이션을 사용할 때도 None을 사용해 키워드 인자의 Default 값을 표현하는 방식을 적용할 수 있다.
BETTER WAY 25 위치로만 인자를 지정하게 하거나 키워드로만 인자를 지정하게 해서 함수 호출을 명확하게 만들라
함수의 * 기호 인자 ☆☆☆
- 위치 인자의 마지막과 키워드만 사용하는 인자의 시작을 구분한다.
함수의 / 기호 인자 ☆☆☆
- 위치로만 지정하는 인자의 끝을 표시한다.
함수의 / 기호 인자와 * 기호 인자의 장점
- / 와 * 기호사이에 있는 모든 파라미터는 위치를 사용하거나 키워드를 사용해 전달할 수 있다. ☆
- 함수 정의에서 각 인자들이 호출하는 쪽과 분리(decouple)시킬 수 있다.
- 함수의 파라미터 이름을 바꿔도 함수가 망가지지 않는다.
- 필요에 따라 두 인자 전달 방식을 모두 사용하면 가독성을 높이고 잡음도 줄일 수 있다.
기억해야 할 내용
- 키워드로만 지정해야 하는 인자를 사용하면 호출하는 쪽에서 특정 인자를 반드시 키워드를 사용해 호출하도록 강제할 수 있다.
- * 기호 인자를 사용해 키워드 인자로만 호출하도록 강제할 수 있고, 사용할 경우 함수 호출의 의도를 명확하게 할 수 있다.
- 위치로만 지정해야 하는 인자를 사용하면 호출하는 쪽에서 키워드를 사용해 인자를 지정하지 못하게 강제할 수 있다.
- / 기호 인자를 사용해 위치 인자로만 호출하도록 강제할 수 있고, 사용할 경우 함수 구현과 함수 호출 지점 사이의 결합을 줄일 수 있다.
- 인자 목록에서 / 와 * 사이에 있는 파라미터는 키워드 또는 위치 인자로 전달해도 된다.
BETTER WAY 26 functools.wrap을 사용해 함수 데코레이터를 정의하라
데코레이터 (Decorator) ☆
- 감싸고 있는 함수가 호출되기 전과 후에 코드를 추가로 실행해주는 구문
- 감싸고 있는 함수의 입력 인자, 반환 값, 함수에서 발생하는 오류에 접근할 수 있다.
- 함수의 의미를 강화하거나 디버깅을 하거나 함수를 등록하는 것에 유용하게 쓸 수 있다.
데코레이터의 단점
- help를 호출하였을 때 원래의 함수가 아닌 wrapper 함수의 정보가 반환된다.
- 데코레이터가 감싸고 있는 원래 함수의 위치를 찾을 수 없기 때문에 객체 직렬화가 깨진다.
functools.wraps
- 데코레이터 작성을 돕는 데코레이터
- 함수의 인터페이스를 처리하는 애트리뷰트를 제대로 복사해서 함수가 제대로 동작할 수 있도록 도와준다.
- wraps를 wrapper 함수에 적용하면 데코레이터 내부에 들어가는 함수에서 중요한 메타데이터를 복사해 wrapper 함수에 적용한
기억해야 할 내용
- 파이썬 데코레이터는 실행 시점에 함수가 다른 함수를 변경할 수 있게 해주는 구문이다.
- 데코레이터를 사용하면 디버거 등 인트로스펙션을 사용하는 도구가 잘못 작동할 수 있다.
- 직접 데코레이터를 구현할 때 인트로스펙션에서 문제가 생기지 않길 바란다면 functools.wraps 데코레이터를 사용하라.
'필기노트' 카테고리의 다른 글
[Effective Typescript] Chapter 1 타입스크립트 알아보기 (0) | 2022.01.17 |
---|---|
[Python 코딩의 기술] Chapter 2 리스트와 딕셔너리 (0) | 2021.10.16 |
[Python 코딩의 기술] Chapter 1 파이썬 답게 생각하기 (0) | 2021.10.12 |
[Javascript 코딩의 기술] Chapter 4 조건문을 깔끔하게 작성하라 (0) | 2021.10.04 |
[Javascript 코딩의 기술] Chapter 3 특수한 컬렉션을 이용해 코드 명료성을 극대화하라 (0) | 2021.10.04 |