[파이썬 코딩의 기술] 함수

litien·2020년 9월 26일
0

본 문서는 파이썬 코딩의 기술: Effective Python의 정리 내용입니다. 인용구문은 필자의 견해 또는 개인적인 궁금함을 담은 내용입니다.

Better Way 14 - None을 반환하기보다는 예외를 일으키자

  • 특벼한 의미를 나타내려고 None을 반환하는 함수가 오류를 일으키기 쉬운 이유는 None이나 다른 값(0이나 빈 문자열)이 조건식에서 False로 평가되기 때문이다.

때에 따라서 None을 리턴하는 것이 유용한 상황이 있다. 이럴 때는 해당 함수가 None을 반환 할 수 있음을 명시하자. ex) type hintingOptional

Better Way 15 - 클로저가 변수 스코프와 상호 작용하는 방법을 알자

  • 클로저(closure)란 자신이 정의된 스코프에 있는 변수를 참조하는 함수
def sort_priority(values, group):
    def helper(x): 
        if x in group: # helper가 선언된 sort_priority 스코프에는 group 변수가 존재하므로 클로저에서 참조 가능하다.
            return (0, x)
        return (1,x)
    values.sort(key=helper)
  • 함수는 파이썬에서 일급 객체, 일급 객체는 함수를 직접 참조하고, 변수 할당, 인수 전달 등이 가능하다.
  • 파이썬에서 튜플 비교는 특정한 규칙이 있다. 먼저 인덱스 0으로 아이템을 비교하고 그 다음으로 인덱스 1, 다음은 인덱스 2와 같이 진행한다.
  • 표현식에서 스코프 탐색 순서, 아래에서도 찾지 못하면 NameError 예외가 발생
    1. 현재 함수의 스코프
    2. 함수가 정의 된 스코프, 즉 현재 스코프 바로 밖의 스코프
    3. 코드를 포함하고 있는 모듈의 스코프(전역 스코프)
    4. 내장 스코프
  • 할당식에서는 다른 방식으로 동작
    • 변수가 이미 현재 스코프에 정의되어 있다면 새로운 값으로 갱신
    • 현재 스코프에 존재하지 않으면 변수 정의로 취급, 새로 정의 되는 변수의 스코프는 그 할당을 포함하고 있는 함수가 된다.
def sort_priority2(numbers, group):
    found = False # 스코프: sort_priority2
    def helper(x):
        if x in group:
            found = True # 스코프: helper
            return (0, x)
        return (1, x)
    
    numbers.sort(key=helper)
    return found
  • nonlocal문은 특정 변수 이름에 할당 할 때 스코프 탐색이 일어나야함을 나타낸다.
    • 복잡해지기 시작하면 nonlocal 대신 클래스로 상태를 감싸는 방법이 좋다.

    • 파이썬 2에서는 nonlocal이 없기때문에, 상위 스코프 탐색을 위해서 아이템이 한개만 있는 리스트와 같은 mutable 수정 가능한 값으로 문제를 우회한다.

Better Way 16 - 리스트를 반환하는 대신 제너레이터를 고려하자

  • 제너레이터를 정의할 때 주의할 점은 반한되는 이터레이터에 상태가 있고, 재사용할 수 없다는 사실을 호출하는 쪽에서 알아야 한다는 점이다.
  • 제네레이터를 사용하는 방법이 누적된 결과의 리스트를 반환하는 방법보다 이해하기에 명확하다.

개인적인 견해로는 이 부분에 동의하지 않는다. 명시적으로 리스트를 담아서 주는 것이 명확해보이며, 제네레이터 함수는 상태를 가지기 때문에 재사용이 불가능하고 사용하는 쪽에서 상태 관리가 되어야한다.

  • 제너레이터는 모든 입출력을 메모리에 저장하지 않으므로 입력값의 양을 알기 어려울 때도 연속된 출력을 만들 수 있다.

Better Way 17 - 인수를 순회할 때는 방어적으로 하자

def normalize(numbers):
    total = sum(numbers)
    return [100 * value / total for value in numbers]    
    
it = read_visits('/my_numbers.txt') # return iterator
percentages = nomalize(it)
print(percentages)
>>> []

위 결과가 나오는 이유는 sum 함수에서 이터레이터를 이미 소진하였기 때문에, complehension에서는 빈 이터레이터를 순회하게 된다. 이 문제를 해결하려면 이터레이터를 명시적으로 소진하기 전에 전체 콘텐츠 복사본을 리스트에 저장해야한다.

 def normalize(numbers):
 	 numbers = list(numbers)
     total = sum(numbers)
     return [100 * value / total for value in numbers]    

문제는 결국 모든 리스트를 메모리에 올리게 됨으로 이터레이터 콘텐츠 복사본이 클 경우 문제가 될 수 있다.
가장 좋은 방법은 이터레이터 프로토콜을 구현하는 것이다. 파이썬의 for x in foo 구문은 실제로 iter(foo)를 호출하고 내장함수 iter는 매직메서드 foo.__iter__를 호출한다. __iter__ 메서드는 이터레이터 객체를 반환해야한다. for 루프는 이터레이터를 모두 소진 할 때까지 (StopIteration 예외가 발생 할 때까지) 이터레이터 객체에 __next__를 계속 호출한다.

이터레이터 객체는 __next__를 구현해야하는데, __iter__ 메서드를 제너레이터로 구현하면 간단하다.

class ReadVisits(object):
    def __init__(self, data_path):
        self.data_path = data_path
        
    def __iter__(self):
        with open(self.data_path) as f:
           for line in f:
               yield int(line) # __iter__를 제네레이터 함수로 구현하였으므로 별도의 __next__ 구현은 필요없다.
  • 내장함수 iter에 이터레이터를 넘기면 이터레이터 자체가 반환되는 반면 컨테이너 타입을 넘기면 매번 새 이터레이터 객체가 반환된다. 따라서 이 동작으로 이터레이터 타입구분이 가능하다.
if iter(numbers) is iter(numbers): # numbers는 이터레이터 타입 
    # ... something

이터레이터, 제네레이터 참고하면 좋은 글 : https://mingrammer.com/translation-iterators-vs-generators/

Better Way 18 - 가변 위치 인수로 깔끔하게 보이게 하자

  • def문에서 *args 를 사용하면 함수에서 가변 개수의 위치 인수를 받을 수 있다.
  • * 연산자를 쓰면 시퀀스에 들어 있는 아이템을 함수으 위치 인수로 사용 할 수 있다.
    • 제너레이터를 * 연산자와 함께 사용하면, 제너레이터는 evaluataion 된다.

args는 디버깅이 어렵게 하는 원인 중 하나가 될 수 있다. 꼭 필요한 경우에만 사용하고 이외에는 명시적으로 인수를 설정하는 것이 좋다.

Better Way 19 - 키워드 인수로 선택적인 동작을 제공하자

  • 키워드 인수는 호출자로 하여금 함수 호출을 더명확하게 이해시킬 수 있으며 함수 정의시 기본값을 설정 할 수 있다. 또 위치 인자에 비해 호출하는 쪽 코드의 수정없이 유연하게 변경이 가능하다.

Better Way 20 - 동적 기본 인수를 지정하려면 None과 docstring을 이용하자.

  • 키워드 인자의 기본값은 모듈이 로드될 때 한번만 평가된다.
  • 빈 리스트나 딕셔너리와 같이 mutable 한 값을 디폴트로 선언 할 경우, 해당 오브젝트는 모든 곳에서 공유됨으로 문제가 발생 할 수 있다. 따라서 mutable 또는 동적인 값들은 None으로 선언하고 내부에서 처리는 것이 좋다.

Better Way 21 - 키워드 전용 인수로 명료성을 강요하자

  • 키워드 전용 인수는 함수 호출의 의도를 더 명확하게 한다.
  • 파이썬 3의 키워드 전용 인수는 키워드로만 넘길 뿐, 위치로는 절대 넘길 수 없다.
  • 인수 리스트에 있는 * 기호는 위치 인수의 끝과 키워드 전용 인수의 시작을 가리킨다.
def safe_division(number, divisor, *, ignore_overflow=False, ignore_zeroe_division=False)
  • 파이썬 2에서는 **kwargs를 사용하여 함수 키워드 전용 인수를 흉내 낼 수 있다.
profile
어려운 문제를 함께 풀어가는 것을 좋아합니다.

0개의 댓글