#Programming

Composing Programs - 5

2026.04.28

Control

현재 우리가 정의할 수 있는 함수들의 표현력은 매우 제한적이다. 왜냐하면 아직 비교를 수행하거나 그 비교 결과에 따라 다른 연산을 실행할 방법을 도입하지 않았기 때문이다.

제어문은 우리에게 이러한 능력을 부여해준다. 제어문은 논리적 비교 결과에 따라 프로그램의 실행 흐름을 제어하는 문장이다.

statements는 우리가 지금까지 학습한 표현식과 근본적으로 다르다. statments는 값을 가지지 않는다. 무언가를 계산하는 대신, 제어문을 실행하는 것은 인터프리터가 다음에 무엇을 해야 할 지 결정한다.

Statements

지금까지 우리는 주로 표현식을 평가하는 방법을 살펴보았다. 하지만 우리는 이미 3가지 종류의 statements를 접했다. 바로 할당, def, return 문들이다.

파이썬 코드의 이러한 줄들은 그 자체로 표현식은 아니지만, 모두 표현식을 구성 요소로 포함하고 있다.

statements는 평가되는 것이 아니라 실행된다. 각 문은 인터프리터 상태에 일어날 어떤 변화를 기술하며, statements를 실행한다는 것은 그 변화를 적용한다는 뜻이다. return문과 할당문에서 보았듯이, statements를 실행하는 과정에서 그 안에 포함된 하위 표현식들을 평가하는 과정이 수반될 수 있다.

표현식 역시 statements로 실행될 수 있는데, 이 경우 표현식은 평가되지만 그 결과값은 버려진다. 순수 함수를 이렇게 실행하면 아무런 효과가 없지만, 비순수 함수를 실행하면 함수 적용의 결과로 부수 효과가 발생할 수 있다.

예를 들어 아래 코드를 고려해보자.

이 예제는 문법적으로 올바른 코드이지만, 아마 의도한 결과는 아닐 것이다. 함수의 본문이 하나의 표현식으로 구성되어 있다. 표현식 그 자체는 유효한 statements지만, 이 statements의 결과로 mul 함수가 호출되고 그 결과값은 그대로 버려진다. 만약 표현식의 결과로 무언가를 하고 싶다면 그것을 명시해야 한다.

아래처럼 할당문으로 값을 저장하거나, return 문으로 값을 반환해야 한다.

물론 print와 같은 비순수 함수를 호출할 때는 함수의 본문이 표현식인 것이 의미가 있다.

가장 고차원 수준에서 보면, 파이썬 인터프리터의 역할은 statements로 구성된 프로그램을 실행하는 것이다. 하지만 계산 과정에서 흥미로운 작업의 상당 부분은 표현식을 평가하는 데서 나온다.

statements는 프로그램 내의 서로 다른 표현식들 사이의 관계를 관리하고, 그 평가 결과에 어떤 일이 일어날지 결정한다.

Compound Statements

일반적으로 파이썬 코드는 일련의 statements들의 집합이다. 단순문은 콜론 (:)으로 끝나지 않는 단일행의 문장이다. 반면 복합문은 다른 문들로 구성되기 때문에 그렇게 불린다. 복합문은 대게 여러줄에 걸쳐 작성되며, 문의 종류를 식별하는 콜론으로 끝나는 한줄짜리 헤더로 시작한다. 헤더와 그 아래 들영쓰기 된 스위트를 합쳐서 절 이라고 부른다.

하나의 복합문은 하나 이상의 절로 구성된다.

우리는 이미 배운 문장들을 이 용어들로 이해할 수 있다.

  • 표현식, return문, 할당문은 단순문이다.
  • def 문은 복합문이다. def 헤더 다음에 오는 스위트가 함수의 본문을 정의한다.

각 헤더 종류에 따른 특수한 규칙은 해당 스위트 안의 문장들이 언제, 그리고 실행될지 여부를 결정한다. 이룰 두고 헤더가 해당 스위트를 제어한다라고 표현한다.

예를 들어 def 문의 경우, return 표현식은 즉시 평가되지 않고 나중에 정의된 함수가 실제로 호출될 때 사용하기 위해 저장된다는 것을 확인했다.

이제 여러 줄로 이루어진 프로그램도 이해할 수 있다. 일련의 문장들을 실행하려면, 먼저 첫 번째 문장을 실행한다. 만약 그 문장이 제어 흐름을 다른 곳으로 돌리지 않는다면, 나머지 문장들이 있을 경우 순차적으로 실행을 이어간다.

이 정의는 재귀적으로 정의된 시퀀스의 본질적인 구조를 드러낸다. 즉 하나의 시퀀스는 첫 번째 요소와 나머지 요소들로 분해될 수 있다는 것이다. 문장 시퀀스에서 나머지 또한 그 자체로 하나의 문장 시퀀스이다. 따라서 우리는 이 실행 규칙을 재귀적으로 적용할 수 있다.

이 규칙의 중요한 결과는 문장들이 순서대로 실행되지만, 제어 흐름이 바뀌면 뒤에 오는 문장들은 결코 실행되지 않을 수도 있다는 점이다.

실무 지침 : 스위트를 들여쓰기 할 때는 모든 줄의 들여쓰기 정도와 방식이 동일해야 하며 조금이라도 차이가 있다면 오류가 발생한다.

Defining Functions II : Local Assignment

처음에 우리는 사용자 정의 함수의 본문이 단일 반환 표현식을 가진 return문으로만 구성된다고 설명했다. 하지만 사실 함수는 단일 표현식을 넘어선 일련의 연산 시퀀스를 정의할 수 있다.

사용자 정의 함수가 적용될 때마다, 함수 정의에 포함된 스위트 내의 절 시퀀스는 로컬 환경에서 실행된다. 이 환경은 해당 함수를 호출함으로써 생성된 로컬 프레임으로 시작된다.

return문은 제어 흐름을 바꾼다. 즉, 첫번째 return문이 실행되는 즉시 함수 적용 과정이 종료되며 해당 return 표현식의 값이 함수가 반환하는 최종 값이 된다.

함수 본문 내에는 할당문이 나타날 수 있다. 예를 들어 아래 함수는 두 단계의 계산을 거쳐 두 수치 사이의 철대 차이를 첫번째 수치에 대한 백분율로 반환한다.

할당문의 효과는 현재 환경의 첫번째 프레임(로컬 프레임)에 이름을 값에 바인딩 하는 것이다. 그 결과, 함수 본문 내에서의 할당문은 전역 프레임에 영향을 줄 수 없다. 함수가 오직 자신의 로컬 환경만을 조작할 수 있다는 사실은 모듈화된 프로그램을 만드는 데 매우 중요하다. 이를 통해서 순수 함수들은 오직 주고받는 값을 통해서만 상호작용 하게 된다.

물론 아래와 같이 percent_difference 함수를 단일 표현식으로도 작성할 수도 있지만, 이 경우 반환 표현식이 더 복잡해진다.

지금까지는 로컬 할당이 함수 정의의 표현력을 크게 높여주지는 않았다. 하지만 로컬 할당은 다른 제어문들과 결합될 때 그 진가를 발휘하게 된다. 또한, 중간 계산값에 이름을 부여함으로써 복잡한 표현식의 의미를 명확하게 만드는 데 결정적인 역할을 한다.

Conditional Statements

파이썬에는 절대값을 계산하는 내장 함수가 있다.

우리는 이와 같은 함수를 직접 구현하고 싶지만, 지금까지는 비교를 수행하고 그 결과에 따라 선택을 내리는 함수를 정의할 명확한 방법이 없었다.

우리가 표현하고 싶은 로직은 이렇다. "만약 x가 양수라면 abs(x)는 x를 반환한다. 또한 x가 0이라면 0을 반환한다. 그렇지 않으면 -x를 반환한다." 파이썬에서는 이러한 선택을 조건문으로 표현할 수 있다.

이 absolute_value 구현은 몇가지 중요한 쟁점을 제기한다.

조건문 파이썬의 조건문은 일련의 헤더와 스위트로 구성된다. 필수적인 if 절과 선택 사항인 elif 절들, 그리고 마지막으로 선택 사항인 else 절로 이루어진다.

조건문을 실행할 때는 각 절을 순선대로 검토하며, 그 과정은 다음과 같다.

  1. 헤더의 표현식을 평가한다.
  2. 만약 결과가 참이라면 해당 스위트를 실행한다. 그 후 조건문 내의 이어지는 모든 절을 건너뛴다.
  3. 만약 else 절에 도달하면 해당 스위트를 실행한다.

Boolean contexts 위의 실행 절차에서 거짓인 값과 참인 값이라는 표현이 등장했다. 조건문 블록의 헤더 안에 있는 표현식들은 불리언 맥락에 있닥 말한다. 즉, 그 값의 참/거짓 여부가 제어 흐름에는 중요하지만, 그 외에 값이 할당되거나 반환되지는 않는다.

파이썬에는 0, None, False를 포함하여 몇 가지 거짓인 값이 존재한다. 그 외의 모든 숫자는 참인 값이다.

Boolean values 파이썬에는 True와 False라는 2가지 불리언 값이 있다. 이들은 논리 표현식에서 진리값을 나타낸다. 내장 비교 연산자인 >, <, >=, <=, ==, !=는 이 값들을 반환한다.

마지막 예시는 True를 반환하며, 이는 operator 모듈의 eq 함수와 대응된다. 파이썬은 할당과 등호 비교를 엄격히 구분하는데, 이는 많은 프로그래밍 언어가 공유하는 관습이다.

Boolean operators 파이썬에는 세 가지 기본 논리 연산자가 내장되어 있다.

논리 표현식에는 그에 맞는 평가 절차가 있다. 이 절차는 논리 표현식의 진리값을 결정할 때 모든 하위 표현식을 평가하지 않아도 되는 경우가 있다는 점을 이용하는데 이를 단락 평가라고 한다.

  • left and rignt 평가 :
  1. 하위 표현식인 left를 평가
  2. 결과가 거짓인 값 v라면 전체가 v로 평가
  3. 그렇지 않으면 표현식은 하위 표현식인 right의 값으로 평가
  • left or right 평가
  1. 하위 표현식인 left를 평가
  2. 결과가 참인 값 v라면 표현식 전체가 v로 평가된다.
  3. 그렇지 않으면 표현식은 하위 표현식 right의 값으로 평가
  • not exp 평가
  1. exp를 평가한다. 결과가 거짓인 값이면 True, 그렇지 않으면 False가 된다.

이러한 값과 규칙, 연산자들은 비교 결과를 결합하는 방법을 제공한다. 비교를 수행하고 불리언 값을 반환하는 함수들은 관례적으로 is로 시작하며 그 뒤에 밑줄을 붙이지 않는다.

Iteration

실행할 문장을 선택하는 것 외에도, 제어문은 반복을 표현하는데 사용된다. 만약 우리가 작성한 코드의 각 줄이 단 한번만 실행된다면, 프로그래밍은 매우 비효율적인 작업이 될 것이다. 오직 문장을 반복해서 실행함으로써 우리는 컴퓨터의 잠재력을 완전히 끌어낼 수 있다. 우리는 이미 한 가지 형태의 반복을 보았다. 함수는 단 한번 정의되지만 여러 번 호출될 수 있다는 점이다. 반복 제어 구조는 동일한 문장들을 여러번 실행하기 위한 또 다른 매커니즘이다.

앞에 두 수를 더해 다음 수를 만드는 피보나치 수열을 생각해보자. 0, 1, 1, 2, 3, 5, 8, 13, 21, ...

각 값은 이전 두 수를 더한다는 규칙을 반복적으로 적용하여 만들어진다. 첫 번째와 두 번째 값은 0과 1로 고정되어 있다. 예를 들어, 여덟 번째 피보나치 수는 12이다.

우리는 while 문을 사용하여 n번째 피보나치 수를 계산할 수 있다. 우리가 지금까지 만든 값의 개수(k) 그리고 k번째 값(curr)과 그 이전 값(pred)를 추적해야 한다. 이 함수가 실행되는 과정을 따라가며, 피보나치 수들이 어떻게 하나씩 curr에 바인딩 되며 진화하는지 관찰해보자.

할당문에서 쉼표는 여러 이름과 값을 구분한다.

pred, curr = curr, pred + curr에서 pred라는 이름을 현재 curr 값에 다시 바인딩하고, 동시에 curr을 pred + curr 값에 다시 바인딩하는 효과가 있다. =의 오른쪽에 있는 모든 표현식은 바인딩이 일어나기 전에 먼저 평가된다.

이러한 이벤트 순서는 이 함수의 정확성을 위해 필수적이다.

while 문은 헤더 표현식과 그 뒤에 오는 스위트로 구성된다.

while 문을 실행하는 방법은 다음과 같다.

  1. 헤더의 표현식을 평가한다.
  2. 만약 그것이 참이라면, 스위트를 실행한 뒤 다시 1단계로 돌악나다.

2단계에서 헤더 표현식이 다시 평가되기 전에 while 절의 스위트 전체가 먼저 실행된다.

while의 스위트가 무한히 실행되는 것을 방지하기 위해 스위트는 매 반복마다 항상 어떤 바인딩을 변경해야 한다. 종료되지 않는 whilㄷ 문을 무한 루프라고 한다. 파이썬이 루프를 강제로 중단하게 하려면 contorl + c를 누르면 된다.

Testing

함수를 테스트 한다는 것은 함수의 동작이 예상과 일치하는지 확인하는 행위이다. 우리가 사용하는 프로그래밍 언어는 이제 충분히 복잡해졌으므로, 구현한 내용을 테스트하기 시작해야 한다.

테스트는 이러한 확인 작업을 체계적으로 수행하기 위한 메커니즘이다. 일반적으로 테스트는 테스트 대상 함수를 한번 이상 호출하는 코드를 포함한 별도의 함수 형태를 띤다. 호출 결과로 반환된 값은 예상 결과와 대조하여 검증된다.

일반적인 함수가 범용성을 지향하는 것과 달리, 테스트는 특정 인자 값을 선택하고 그 호출 결과를 검증하는 과정을 포함한다. 또한 테스트는 문서 역할도 한다. 함수를 어떻게 호출해야 하는지, 어떤 인자 값이 적절한지를 보여주기 때문이다.

Assertion 프로그래머는 테스트 중인 함수의 출력과 같은 예상치를 검증하기 위해 assert 문을 사용한다. assert문은 불리언 맥락의 표현식과, 그 표현식이 거짓일 경우 표시될 따옴표로 감싼 문자열로 구성된다.

assert의 표현식이 참으로 평가되면 아무일도 일어나지 않는다. 하지만 거짓으로 평가되면 assert는 오류를 발생시켜 실행을 중단한다. fib를 위한 테스트 함수 n의 극단적인 값을 포함하여 여러 인자를 테스트 해야 한다.

파이썬 코드를 인터프리터에 직접 입력하지 않고 파일로 작성할 때, 테스트는 보통 같은 파일에 작성하거나 접미사 _test.py가 붙은 인접한 파일에 작성한다.

Doctests 파이썬은 함수의 독스트링 안에 직접 간단한 테스트를 넣을 수 있는 편리한 방법을 제공한다. 독스트링의 첫 줄에는 함수에 대한 한 줄 설명을 적고, 그 뒤에 한줄을 비운다. 그 다음 인자와 동작에 대한 자세한 설명을 덧붙일 수 있다. 여기에 더해, 독스트링에는 함수를 호출하는 대화형 세션 예시를 포함할 수 있다.

이러한 상호작용 예시는 doctest 모듈을 통해 검증할 수 있다. 아래의 globals() 함수는 인터프리터가 표현식을 평가하는 데 필요한 전역 환경의 표현을 반환한다.

단 하나의 함수에 대해서만 doctest를 확인하려면, run_docstring_examples라는 함수를 사용한다. 이 함수는 호출법이 조금 복잡하다.

첫 번째 인자는 테스트 함수이고, 두 번째 인자는 항상 전역 환경을 반환하는 내장 함수인 globals()의 결과여야 한다. 세번째 인자인 True는 실행된 모든 테스트 목록을 보여주는 상세 출력 모드를 의미한다.

함수의 반환 값이 예상 결과와 일치하지 않으면 run_docstring_examples 함수는 이 문제를 테스트 실패로 보고한다.

파이썬을 파일로 작성할 때 파일 내의 모든 독테스트는 파이썬 실행 시 독테스트 명령줄 옵션을 사용하여 실행 할 수 있다.

효과적인 테스트이 핵심은 새로운 함수를 구현한 직후에 테스트를 작성하는 것이다. 구현하기 전에 몇가지 예시 입력과 출력을 머릿속에 두기 위해 테스트를 먼저 작성하는 것도 좋은 관습이다. 하나의 함수만을 적용하는 테스트를 unit test라 한다. 철저한 unit test는 좋은 프로그램 설계의 특징이다.