티스토리 뷰

 

원문: Top 5 Mistakes You Make When Declaring Functions in Python

(약간의 의역을 얹었습니다.)

 

어떤 프로그래밍 프로젝트에서든 함수는 핵심요소입니다. 잘 만들어진 함수는 코드의 가독성을 높이고 유지보수를 쉽게 만듭니다. 반대로 함수를 엉성하게 만들면 가독성이 떨어지고 유지보수 비용이 올라갑니다. 아무리 실력좋은 프로그래머라도 시간이 지나면 자신의 코드를 잊어버리기 마련이므로, 잘 작성된 함수는 프로젝트의 가치를 올려준다고 할 수 있습니다. 물론 파이썬도 예외가 아닙니다.

 

함수 작성 시 흔히 범하는 실수를 소개하겠습니다. 이 실수들을 이해하고 나면 가독성과 유지보수 면에서 향상된 코드를 짤 수 있을 것입니다.

 

"가독성은 중요하다." - 파이썬 철학 (역자 주: 번역)

 

1. 이름을 제대로 짓지 않은 함수

 

아기나 고양이 이름 지을 때만 어려운게 아닙니다. 프로그래머라면 커리어 내내 작명의 고통 속에 살아야 합니다. (역자 주: 프로그래머가 가장 힘들어하는 일은?) 이름 짓기가 어려운 이유는 작명의 세 가지 규칙 - 유일성, 명시성, 일관성을 지켜야 하기 때문입니다.

 

유일성 (Uniqueness)

 

매우 당연한 요구조건입니다. 파이썬의 모든 객체가 그러하듯 함수도 이름을 통해 구별합니다. 같은 이름으로 만들어진 함수가 있다면 IDE가 알려주거나, 혹은 먼저 정의된 함수가 무시됩니다. 아래 예시에서는 두 함수를 say_hello라는 하나의 이름으로 만들었습니다. 결과에서 보듯 두 번째 함수가 호출되었습니다.

 

함수 이름은 유일해야 합니다.

 

명시성 (Informativeness)

 

함수의 역할은 특정 작업의 수행입니다. 따라서 함수 이름에 수행할 작업이 드러나야 합니다. 함수 이름이 이를 보여주지 못하면 남이 짠 코드를 이해하기 어려울 뿐더러 내가 지난 달에 짠 코드도 이해하기 어려워집니다. 함수 이름이 명시적이려면 함수의 동작을 정확하고 분명하게 보여줄 수 있어야 합니다. 아래는 그 예시입니다.

 

함수 이름은 명시적이어야 합니다.

 

일관성 (Consistency)

 

파이썬 프로그래밍은 모듈화를 권장합니다. 즉, 관련된 클래스와 함수들은 하나의 모듈에 담는 것이 좋습니다. 모듈 내에서의 이름들은 일관성을 가져야 합니다. 일관성이라 함은 객체들과 함수들의 이름이 동일한 규칙을 따르는 것을 말합니다. 아래의 예제를 보겠습니다.

함수 이름은 일관성을 가져야 합니다.

 

앞의 세 함수들은 두 수를 받아서 비슷한 동작을 하므로, 이름을 '동사+밑줄+numbers'의 형태로 지었습니다. Mask 클래스에는 promotion_pricesales_price라는 두 함수가 있는데 비슷한 이름 구조를 갖도록 만들었습니다. 첫 단어는 가격의 종류를, 그 다음 단어는 반환값이 가격임을 표현하고 있습니다.

 

2. 한번에 다양한 작업을 하는 함수

 

두 번째 실수는 한 함수 내에서 너무 다양한 작업이 이루어지는 경우입니다. 이는 시니어 프로그래머도 본인 프로그램의 리팩토링을 소홀히 하면서 종종 저지르는 실수입니다. 잘 만들어진 함수는 잘 정의된 하나의 동작만을 수행하는 함수입니다.

 

다양한 작업을 한꺼번에 수행하는 함수는 코드가 길어지는 문제점도 있습니다. 코드가 길어지면 이해하기 어렵고 디버깅도 어렵습니다. 아래의 예제를 살펴보겠습니다. 판다스를 이용하여 생리학 실험 데이터를 처리하는 코드입니다.

 

다양한 작업을 한번에 처리하는 긴 함수

 

각 환자에 대해서 4개의 세션 데이터가 CSV 포맷으로 있습니다. 그런데 process_physio_data라는 하나의 함수에서 3개의 데이터 처리를 모두 수행하고 있습니다. 데이터가 복잡하니 함수는 100줄이 넘는 코드가 될 것입니다.

 

이 경우 각 처리를 맡은 함수를 따로 작성하면 데이터 처리과정을 더 잘 보이게 만들 수 있습니다. 아래 코드에서는 각 데이터 처리를 담당하는 3개의 헬퍼 함수를 만들었습니다.

 

한 가지 작업만 수행하는 짧은 함수들

 

각 함수는 정확히 한 가지 동작만을 수행합니다. 이제 process_physio_data 함수는 훨씬 명확하고 간결해졌습니다. 이 함수는 실험 데이터를 처리하는 파이프라인을 제공하는 역할만을 수행합니다. 이런 리팩토링을 통해 코드 전체의 가독성을 훨씬 높일 수 있습니다.

 

3. 문서가 없는 함수

 

문서화는 프로그래머가 긴 경험을 통해 배워야 하는 작업입니다. 처음에는 코드가 동작만 잘 하면 문서가 없어도 아무 문제가 없는 것처럼 보일 수 있습니다. 예를 들어 딱 하나의 프로젝트만 몇 주째 진행하고 있다면 함수 하나하나의 기능이 모두 머리 속에 잘 저장되어 있을 겁니다. 하지만 예전에 만든 코드를 수정하기 위해 다시 열어본다면 어떨까요? 본인이 짠 코드를 다시 이해하기 위해 시간이 얼마나 소요될까요? 저도 힘든 과정을 통해 교훈을 얻었고, 여러분도 비슷한 경험이 있으실 겁니다.

 

API를 공유하는 팀 업무 환경이나 오픈소스 라이브러리를 만드는 경우라면 소홀한 문서화는 더 심각한 문제를 일으킵니다. 다른 사람이 만든 간단하지 않은 함수를 사용할 때 우리는 함수 내부의 동작을 정확히 알 수 없습니다. 하지만 문서화가 되어있다면 함수를 어떻게 호출하며 반환값은 무엇일지 읽어볼 수 있습니다. 여러분이 사용하는 라이브러리나 프레임워크에 문서화가 전혀 되어있지 않다면 어떨지 상상해보세요.

 

여러분이 만든 함수에 장황한 독스트링을 꼭 달아야 한다는 얘기를 하려는 것은 아닙니다. 앞서 말한 함수 이름의 세 가지 조건(유일성, 명시성, 일관성)을 만족시켰고 하나의 동작만 하는 적절한 길이의 함수를 만들었다면, 문서는 간단히 작성해도 됩니다. 하지만 회사에서 큰 팀의 팀원으로 일하거나 오픈소스를 개발 중이라면 여러분 자신과 다른 사람들을 위해서라도 문서화 표준 규정을 따라야 합니다.

 

문서화는 이쯤에서 정리하겠습니다. 독스트링 작성법에 대해서는 관련 글을 참고해주세요.

 

4. 기본값을 잘못 사용한 함수

 

파이썬에서는 함수의 인자에 기본값을 설정할 수 있습니다. 이미 많은 내장함수들이 이 기능을 사용하고 있습니다. 아래의 예시를 먼저 보겠습니다.

 

인자 기본값을 활용한 range 함수

 

range() 함수를 이용하여 리스트 객체를 만들 수 있으며, 기본 문법은 range(start, stop, step)입니다. step은 생략하면 1이 자동으로 들어가지만 위 코드와 같이 명시할 수도 있습니다.

 

그런데 함수 인자의 기본값에 뮤터블 자료형을 넣으면 상황이 조금 복잡해집니다. 뮤터블 자료형이란 리스트, 딕셔너리, 집합과 같이 생성 후에 값을 바꿀 수 있는 자료형을 말합니다. 뮤터블에 대한 자세한 내용은 저의 다른 글로 갈음하겠습니다. 아래는 함수 인자 기본값에 뮤터블 자료형인 리스트를 넣은 예시입니다.

 

인자 기본값으로 뮤터블을 설정한 함수

 

첫 번째와 같이 score 98을 append 하면 결과는 예상한 대로 나옵니다. 함수를 호출할 때 scores를 생략했으므로 scores에는 빈 리스트가 들어갔기 때문입니다. 두 번째에는 score 92를 리스트 [100, 95]에 append 했고, 역시 예상한 결과가 나왔습니다. 그런데 세 번째가 이상합니다. score 94를 빈 리스트에 append 했으므로 결과는 [94]가 나와야 할텐데 이상한 결과가 나왔습니다. 왜 그럴까요?

 

이는 파이썬에서 함수는 일급 객체(first-class citizen)이자 일반 객체이기 때문입니다. (파이썬에서 함수도 객체임을 이전 글에서 쓴 바 있습니다.) 즉, 함수객체는 함수가 정의될 때 생성되는데 함수의 기본인자값도 이때 같이 생성됩니다. 아래 코드에서 이 점을 확인할 수 있습니다. 리스트 scores의 메모리 주소를 출력하도록 이전 코드를 수정했습니다.

 

기본값이 뮤터블인 함수 인자를 추적한 결과

 

우선 함수를 호출하지 않아도 함수의 기본인자값과 이 값의 주소를 __default__ 속성을 통해 볼 수 있습니다. 그 후 함수를 두 번 호출했는데 같은 메모리 주소를 갖는 같은 리스트 scores가 사용되는 것도 볼 수 있습니다. (역자 주: 함수 호출 시 scores를 명시적으로 선언하면, 이때의 scores는 인자 기본값으로 설정된 scores와는 다른 메모리 주소의 리스트가 됩니다. 자세한 내용은 종욱 님의 블로그에서 보실 수 있습니다.)

 

이런 실수를 피하려면 어떻게 하면 될까요? 뮤터블 자료형의 기본값으로 None을 쓰면 됩니다. 기본값이 None인 인자에 대해서는 미리 객체를 만들어두지 않기 때문입니다. 대신 함수 호출 시 scores를 생략했다면 함수 내부에서 만들어주어야 합니다. 아래는 이 사항들을 적용한 결과입니다. 예상한 대로 잘 동작하지요?

 

인자 기본값으로 뮤터블 대신 None을 사용

 

5. *args와 **kwargs를 남용한 함수

 

파이썬에서는 함수의 입력인자 개수를 가변적으로 선언할 수 있습니다. 사용하는 라이브러리의 문서를 한번이라도 봤다면 *args**kwargs라는 표현을 본 적이 있을겁니다. 요컨대 *args는 위치 인자(positional argument)의 개수가 정해지지 않았음을, **kwargs는 키워드 인자(keyward argument)의 개수가 정해지지 않았음을 뜻합니다. 위치 인자는 인자의 위치(순서)를 통해 인식되는 인자이며, 키워드 인자는 키워드를 통해 인식되는 인자입니다. 아래는 간단한 예제입니다.

 

위치 인자와 키워드 인자

 

함수 add_numbers에서 num0num1은 위치 인자이고 num2num3은 키워드 인자입니다. 키워드 인자 간에는 순서를 바꿔도 되지만 키워드 인자와 위치 인자 간에는 순서를 바꿀 수 없습니다. 이제 *args**kwargs가 어떻게 동작하는지 예제를 통해 조금 더 자세히 들어가보겠습니다. 우선 아래 두 가지를 알고 있어야 합니다.

 

  1. 가변 개수의 위치 인자는 튜플로 변환되어 전달됩니다. 따라서 *로 언패킹 할 수 있습니다. (튜플 언패킹에 대해서는 이 글을 참고해주세요.)
  2. 가변 개수의 키워드 인자는 딕셔너리로 변환되어 전달됩니다. 따라서 **로 언패킹 할 수 있습니다. (딕셔너리 언패킹에 대해서는 이 글을 참고해주세요.)

 

파이썬 함수에서 *args와 **kwargs

 

*args**kwargs가 함수 작성을 유연하게 만들어주는 것은 맞지만, 이를 너무 남용하면 오히려 함수 사용이 어려워집니다. 판다스의 read_csv 함수로 CSV 파일을 읽는 예제를 앞서 보여드린 바 있습니다. 이 함수가 총 몇 개의 인자를 받을 수 있는지 알고 계시나요? 공식문서를 보면 아래와 같이 나옵니다.

 

 

안 세어보셔도 됩니다. 제가 세어봤습니다. 장장 49개의 인자를 받는 함수입니다. 첫 번째가 위치 인자이고, 그 뒤 48개는 키워드 인자입니다. 물론 이 함수는 아래와 같이 만들 수도 있습니다.

 

 

그런데 이렇게 만든 함수는 내부에서 **kwargs를 언패킹해야 할 뿐더러, 호출할 때 어떤 키워드 인자를 써야 하는지 미리 알기도 어렵습니다. 소위 전문가라는 사람들이 왜 이런 무시무시한 길이의 함수를 만든 걸까요? 전문가는 아래의 철학을 알기 때문입니다.

 

"명시적인(explicit) 것이 암묵적인(implicit) 것보다 낫다." - 파이썬 철학 (역자 주: 번역)

 

**kwargs를 쓰면 함수 선언은 간결해질지 모르나, 대신 코드의 명시성 저하를 감수해야 합니다. *args도 마찬가지입니다. 이미 언급한 바와 같이 팀으로 진행하는 업무라면 코드는 명시적이어야 이해하기 쉬워집니다. 이런 이유로 *args**kwargs의 사용은 가급적 지양해야 합니다.

 

마치며

 

지금까지 파이썬 함수 작성 시 자주 저지르는 실수 다섯 가지를 살펴보았습니다. 여러분이 이런 실수를 무시하는 코딩 스타일에 익숙해져있다면 여러분의 코드는 이해하기 어렵고 유지보수가 어려운 코드가 됩니다. 이런 습관을 고친하면 가독성이 높고 공유하기도 좋은 코드를 작성할 수 있을 것입니다.

 

 

같이 읽기:

- 30 Simple Tricks to Level Up Your Python Coding

- Dictionary Merging and Updating in Python 3.9

- 10 Things You Should Know About Tuples in Python

 

 

번역: 게으른 파이썬

 

※ 좋은 글을 알려주신 페가님, 번역에 도움을 주신 홀로님, 준원님께 감사말씀 드립니다.

 

댓글