개발 · 자동화기초 · 21분 읽기

Python 함수·인수·반복과 모듈

함수는 입력과 결과, 책임의 경계를 만드는 도구입니다. 기본 인수와 반환값, 범위와 반복 가능 객체를 정확히 이해합니다.

개념부터 찾아보기

정의·역할·동작 원리를 독립 페이지에서 확인하세요. 본문의 밑줄 용어에도 설명이 있습니다.

이 노트에서 설명할 수 있게 되는 것

  1. 정의 시점과 호출 시점의 기본 인수 평가 차이를 설명한다.
  2. 호출 프레임·매개변수·객체 참조·반환값을 추적한다.
  3. 함수의 입력 변경과 출력·반환 계약을 검증한다.
먼저 알면 좋은 개념Python 객체·자료형과 복사

01def의 실행과 호출 프레임의 생성

  • def 문을 실행하면 함수 객체를 만들고 함수 이름에 연결한다.
  • 그때 함수 본문의 일반 문장 전체를 실행하는 것은 아니며 호출할 때 인자를 매개변수 이름에 묶고 새 호출 프레임에서 본문을 실행한다. 함수가 다른 함수를 부르면 호출 관계가 쌓이고 return은 현재 함수의 결과를 호출자에게 전달한다.
  • 명시적 return 값이 없으면 None을 반환한다.
  • 함수의 return 0은 값 0을 반환할 뿐 자동으로 운영체제에 성공 종료를 알리는 동작은 아니다.
  • 의 종료 상태는 sys.exit(0) 같은 프로그램 종료 경로와 구분한다.
  • print는 출력 부작용이고 return은 호출자가 다시 계산에 사용할 값이다.
    • 화면에 원하는 값이 찍혔다고 변수가 그 값을 받은 것은 아니다.
def add(a, b):
    return a + b

result = add(2, 3)
print(result)          # 5
print(print("hello"))  # hello를 출력한 뒤 None을 출력

코드 동작을 설명하는 교육 예시. 주석의 결과는 언어 규칙으로 설명한 예상값이며 이 페이지 작성 중 실행하지 않았다.

LEARN THROUGH PRACTICE

반복 종료를 함수의 입력·반환값으로 재구성하기

range의 종료값, 루프의 break, 함수의 return이 바꾸는 실행 위치를 구분한다.

실습 구성과 전제

  • 사진은 range 표현과 카운터 반복의 독립 사례다. 아래 함수는 이 반복을 입력과 반환값이 있는 형태로 재구성한 코드다.
  • 에서 실행하는 예시다. 화면에 없는 함수 실행을 실제 촬영 결과로 주장하지 않고 예상 출력을 따로 표시한다.
  1. range의 표현과 실제 반복 값을 구별한다

    • range(3)은 0부터 3 미만의 정수를 표현하는 객체다. range 객체를 출력한 문자열과 로 변환한 결과는 다른 표현이다.
    • for는 해당 객체를 순회하면서 값을 차례로 받는다. 종료 경계 3이 포함되지 않으므로 이 예시에서 본문을 최대 세 번 수행한다.
    4/10 · 코드와 실행 결과 5원본 크기로 보기 ↗
    • range(3)을 직접 출력하면 range(0, 3)이 표시된다.
    • list(range(3))은 [0, 1, 2]로 출력되어 종료값 3이 제외됨을 보여 준다.
    확인 기준

    종료값을 포함하는 횟수로 계산하지 않는다. 시작·끝·간격과 break 같은 별도 종료 조건을 나누어 읽는다.

  2. 카운터 증가와 break의 위치를 추적한다

    • 두 번째 사진은 반복에서 카운터를 증가·출력한 뒤 10이면 break한다. range(100)의 모든 값을 쓰지 않고 조건이 만족된 시점에 가장 가까운 루프를 끝낸다.
    • 루프 밖의 출력이 이어지므로 break가 프로그램 전체나 함수 전체를 끝내는 명령이 아님을 확인할 수 있다.
    4/10 · 코드와 실행 결과 4원본 크기로 보기 ↗
    • for range(100) 내부에서 cnt를 1씩 늘리고 cnt==10에서 break한다.
    • 화면에는 열 번의 반복 출력 뒤 루프 밖의 탈출 메시지가 보인다.
    확인 기준

    증가·출력·조건 검사 순서가 달라지면 마지막으로 출력되는 값과 수행 횟수가 달라진다.

  3. 함수 입력과 반환값으로 결과를 명확히 전달한다

    • 재구성 함수는 limit을 받아 그만큼 반복한 뒤 실제 처리 횟수를 반환한다. break는 루프를 끝내고, 뒤의 return은 현재 함수 호출을 끝내며 호출자에게 count를 전달한다.
    • 반환값 없이 print만 하는 함수는 호출자가 결과를 계산에 쓰기 어렵다. 출력 효과와 반환값을 분리하면 반복 코드를 자동화 작업에 재사용할 수 있다.
    def count_until(limit):
        if not 1 <= limit <= 100:
            raise ValueError("limit must be 1..100")
        count = 0
        for _ in range(100):
            count += 1
            if count == limit:
                break
        return count
    
    result = count_until(10)
    print(result)  # 10

    정수 limit을 입력하는 학습용 재구성 함수다. 사진의 반복을 함수 경계와 반환값으로 확장했다.

    확인 기준

    예상 결과는 10이다. limit이 허용 범위를 벗어나면 반복을 시작하기 전에 ValueError로 조건 위반을 분리한다.

실습에서 확인한 동작 원리

  • range의 끝은 제외되며 반복 중 break가 있으면 전체 수열을 끝까지 소비하지 않을 수 있다.
  • break와 return은 이동하는 실행 경계가 다르므로 함수의 입력·상태·출력을 분리해서 추적한다.

설정·출력 추가로 확인하기 12장

아래 화면을 펼쳐 위 실습의 설정값, 명령과 확인 기준을 비교해 보세요. 각 묶음은 별도 실습 기록입니다.

실습 화면 묶음 01 3장
4/10 · 실습·개념 참고 화면 1원본 크기로 보기 ↗
  • 문서 문맥에 삽입된 원본 실습·개념 참고 화면이다. Python 함수·인수·반복과 모듈 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
4/10 · 실습·개념 참고 화면 3원본 크기로 보기 ↗
  • 문서 문맥에 삽입된 원본 실습·개념 참고 화면이다. Python 함수·인수·반복과 모듈 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
4/10 · 코드와 실행 결과 2원본 크기로 보기 ↗
  • 문서 문맥에 삽입된 원본 코드와 실행 결과이다. Python 함수·인수·반복과 모듈 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
실습 화면 묶음 02 9장
python · 구성·동작 참고 화면 26원본 크기로 보기 ↗
  • 「문자열 함수」 문맥에 삽입된 원본 구성·동작 참고 화면이다. Python 함수·인수·반복과 모듈 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 실습·개념 참고 화면 27원본 크기로 보기 ↗
  • 「문자열 함수」 문맥에 삽입된 원본 실습·개념 참고 화면이다. Python 함수·인수·반복과 모듈 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 실습·개념 참고 화면 30원본 크기로 보기 ↗
  • 「문자열 함수」 문맥에 삽입된 원본 실습·개념 참고 화면이다. Python 함수·인수·반복과 모듈 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 실습·개념 참고 화면 33원본 크기로 보기 ↗
  • 「문자열 함수」 문맥에 삽입된 원본 실습·개념 참고 화면이다. Python 함수·인수·반복과 모듈 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 실습·개념 참고 화면 35원본 크기로 보기 ↗
  • 「문자열 함수」 문맥에 삽입된 원본 실습·개념 참고 화면이다. Python 함수·인수·반복과 모듈 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 25원본 크기로 보기 ↗
  • 「문자열 함수」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 함수·인수·반복과 모듈 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 31원본 크기로 보기 ↗
  • 「문자열 함수」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 함수·인수·반복과 모듈 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 32원본 크기로 보기 ↗
  • 「문자열 함수」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 함수·인수·반복과 모듈 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
python · 코드와 실행 결과 34원본 크기로 보기 ↗
  • 「문자열 함수」 문맥에 삽입된 원본 코드와 실행 결과이다. Python 함수·인수·반복과 모듈 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
동작·명령 참고 문서

02객체 공유 전달: 재대입은 바깥 이름을 바꾸지 않는다

  • 호출 시 매개변수는 전달된 객체에 연결되는 새 지역 이름이 된다.
  • 가변 객체 내부를 수정하면 호출자에게도 보이지만 매개변수 이름을 새 객체로 재대입하면 호출자의 이름 연결까지 바뀌지는 않는다.
  • 단순한 call by value 또는 C++ 참조 변수와 동일하다고 표현하기보다 객체를 공유하는 으로 이해하는 편이 정확하다.
  • 아래 함수의 append는 전달받은 원본 에 영향을 준다.
  • 이후 items = [9]는 지역 이름만 새 로 연결한다.
    • 함수 밖 original은 여전히 [1,2]를 가리킨다.
  • 정수 += 연산은 보통 새 정수 객체로 이름을 다시 연결하는 결과가 되므로 같은 문법이라도 가변 객체의 제자리 연산과 관찰 결과가 다를 수 있다.
def change(items):
    items.append(2)
    items = [9]
    return items

original = [1]
returned = change(original)
print(original, returned)  # [1, 2] [9]

코드 동작을 설명하는 교육 예시. 주석의 결과는 언어 규칙으로 설명한 예상값이며 이 페이지 작성 중 실행하지 않았다.

03인자 바인딩과 가변 기본값의 함정

  • 위치 인자는 순서로, 키워드 인자는 이름으로 매개변수를 지정한다.
  • *args는 남은 위치 인자를 로, **kwargs는 남은 키워드 인자를 로 모은다.
  • 함수 정의의 / 앞은 위치 전용, * 뒤는 키워드 전용 매개변수로 만들 수 있다.
  • 중복 값 지정이나 필수 인자 누락은 함수의 업무 로직 이전에 TypeError를 낼 수 있다.
  • 기본값 표현식은 일반적으로 def가 실행될 때 평가되어 함수 객체에 보관된다.
  • def add(x, items=[])처럼 가변 객체를 기본값으로 만들면 인자를 생략한 여러 호출이 같은 를 공유한다.
  • 호출마다 새 목록을 원하면 None을 sentinel로 받고 본문에서 생성한다.
  • type hint는 의도를 기록하며 이 모든 인자를 자동으로 런타임 검증한다는 뜻은 아니다.
def collect(value, items=None):
    if items is None:
        items = []
    items.append(value)
    return items

print(collect(1), collect(2))  # [1] [2]

def describe(a, *args, **kwargs):
    return a, args, kwargs
print(describe(1, 2, 3, mode="read"))
# (1, (2, 3), {"mode": "read"})

코드 동작을 설명하는 교육 예시. 주석의 결과는 언어 규칙으로 설명한 예상값이며 이 페이지 작성 중 실행하지 않았다.

04LEGB·global·nonlocal은 이름을 찾고 다시 연결하는 규칙

  • 일반적인 함수의 이름 조회는 Local, Enclosing, Global, Built-in 순서로 설명한다.
  • 모듈의 global 이름은 프로그램 전체 모든 모듈의 단일 공간이 아니라 그 함수가 정의된 모듈의 공간이다.
  • 함수 안에서 이름에 대입하면 특별한 선언이 없는 한 지역 이름으로 취급되어 먼저 읽는 코드에서 UnboundLocalError가 생길 수 있다.
  • if나 for는 일반 블록마다 별도 지역 범위를 만들지 않지만 comprehension 등에는 별도 규칙이 있다.
  • global은 모듈 수준 이름의 재대입, nonlocal은 둘러싼 함수 범위의 기존 이름 재대입을 가리킨다.
  • 바깥 에 append하는 것은 이름 재대입이 아니라 객체 변경이라 같은 선언을 반드시 요구하지 않는다.
  • 나 sum을 변수 이름으로 쓰면 built-in을 가리는 shadowing이 되므로 명명으로 예방하는 것이 보통 낫다.
  • global은 shadowing 문제를 자동 고치는 도구가 아니다.

05클로저·함수 객체·데코레이터의 연결

  • 함수는 변수에 저장하고 다른 함수로 전달하거나 반환할 수 있는 객체다.
  • 는 내부 함수가 둘러싼 함수의 필요한 을 참조하여 바깥 호출이 끝난 뒤에도 그 환경을 사용하는 구조다.
  • 반복문에서 만든 여러 함수가 같은 변수의 나중 값을 볼 수 있는 late binding은 값을 함수 생성 순간마다 복제한다고 생각하면 이해하기 어렵다. @decorator는 정의된 함수를 decorator에 전달한 결과로 이름을 다시 연결하는 문법이다.
  • functools.wraps는 감싼 함수의 이름·문서 같은 메타데이터를 보존하는 데 도움을 준다. 순수 함수는 같은 입력으로 같은 결과를 내고 외부 상태를 바꾸지 않도록 구성한다.
  • functools.lru_cache는 반복 계산을 저장할 수 있지만 인자는 hash 가능해야 하고 시간·외부 상태에 따라 달라지는 결과를 무조건 캐시하면 오래된 값을 반환할 수 있다.
def make_counter():
    count = 0
    def next_value():
        nonlocal count
        count += 1
        return count
    return next_value

counter = make_counter()
print(counter(), counter())  # 1 2

코드 동작을 설명하는 교육 예시. 주석의 결과는 언어 규칙으로 설명한 예상값이며 이 페이지 작성 중 실행하지 않았다.

원리 심화

06클로저는 외부 이름을 읽는 시점을 보존한다

  • 중첩 함수가 바깥 함수의 지역 이름을 사용하면 함수 객체는 필요한 외부 바인딩을 유지할 수 있다. 바깥 함수가 반환했다고 그 이름에 연결된 상태가 즉시 사라지는 것은 아니다.
  • lambda: i처럼 이름을 본문에서 읽으면 보통 함수가 호출될 때의 바인딩을 조회한다. 함수 생성 순간의 값을 자동으로 복사해 고정한 표현식이 아니다.
  • lambda i=i: i는 생성할 때 오른쪽 i를 평가하여 기본값으로 보관한다. 호출 시 인자를 생략하면 그 기본값이 사용되어 반복별 값을 고정하는 효과가 난다.
  • nonlocal은 둘러싼 함수의 기존 바인딩을 재대입하게 한다. 외부 가변 객체의 append와 외부 이름 자체를 새 객체에 연결하는 작업을 구분한다.
  • 이 늦은 조회는 lambda만의 특징이 아니다. def로 만든 중첩 함수도 같은 외부 이름을 참조하면 같은 종류의 결과를 낼 수 있다.
예제로 추적

07반복에서 만든 세 함수가 모두 2를 반환하는 이유

  1. make에서 i가 0·1·2로 변할 때마다 함수 객체를 하나씩 만든다. 함수 객체 세 개가 모두 둘러싼 함수의 같은 i 바인딩을 읽는다.
  2. 반복이 끝나면 i의 마지막 값은 2다. make가 함수 목록을 반환해도 그 함수들이 필요한 외부 바인딩은 유지된다.
  3. 반환된 함수를 그 뒤 호출하면 각각 i를 조회하므로 결과가 [2, 2, 2]가 된다. 생성 순서가 0·1·2였다는 사실은 호출 시 값과 다르다.
  4. fixed에서는 각 함수 생성 시 i를 기본값 n에 저장한다. 세 함수가 인자 없이 호출되면 각각 0·1·2를 반환한다.
  5. 기본값이 리스트라면 그 리스트의 복사본이 자동으로 생기는 것은 아니다. 정수 값을 고정하는 이 예제와 가변 객체 공유 문제를 혼동하지 않는다.
def make():
    result = []
    for i in range(3):
        result.append(lambda: i)
    return result

late = make()
fixed = [lambda n=i: n for i in range(3)]
print([f() for f in late])   # [2, 2, 2]
print([f() for f in fixed])  # [0, 1, 2]

실행하지 않은 교육 예시. 주석의 출력은 언어 규칙으로 추적한 예상값이다.

검증·예외

08지역 바인딩·인수 오류·지연 실행을 구별하기

  • 함수 안에서 x를 대입하는 문장이 있으면 선언이 없는 보통 경우 x는 지역 이름으로 분류된다. 대입 전에 print(x)를 하면 전역 x가 있어도 아직 지역 값이 없어 실패할 수 있다.
  • f(1, a=2)처럼 같은 매개변수 a에 위치와 키워드 값을 동시에 주면 함수 본문 전에 TypeError가 난다. 함수 안의 계산 오류와 호출 바인딩 오류를 분리한다.
  • def f(value, *, limit)에서 limit은 이름으로 전달해야 한다. /와 *는 매개변수 전달 방법을 제한하며 타입 힌트가 이런 호출 규칙을 대신하지 않는다.
  • yield가 있는 함수를 호출하면 generator 객체를 얻고 본문은 순회할 때 진행된다. 객체 생성이 성공했어도 next에서 파일·변환 예외가 생길 수 있다.
  • global은 현재 모듈의 바인딩을 가리킨다. 다른 모듈에서 from module import value로 가져온 이름이 원 모듈의 재대입을 자동으로 따라가는 별칭이라고 가정하지 않는다.

09yield는 함수의 진행 위치를 보존한다

  • 본문에 yield가 있는 generator 함수를 호출하면 즉시 모든 값을 계산하지 않고 generator 객체를 얻는다.
  • next로 진행하면 첫 yield까지 실행하고 값을 내보낸 뒤 지역 변수와 진행 위치를 보존한다.
  • 다음 next는 그다음 지점부터 이어지고 함수가 끝나면 StopIteration으로 반복 종료를 알린다.
    • for 문은 이 규약을 사용한다.
  • 리스트 반환은 결과를 한꺼번에 보관하기 좋고 generator는 큰 로그를 한 줄씩 변환하는 작업에서 메모리 사용을 줄일 수 있다.
    • 다만 한 번 소진된 generator를 다시 처음부터 사용할 수 없고 안에서 파일 같은 자원을 열었다면 그 수명도 반복 과정과 연결된다.
  • 함수의 계산·I/O·반복 상태를 나누면 자동화 프로그램을 작은 단위로 이해할 수 있다.
def squares(limit):
    for number in range(limit):
        yield number * number

g = squares(3)
print(next(g))   # 0
print(list(g))   # [1, 4]

코드 동작을 설명하는 교육 예시. 주석의 결과는 언어 규칙으로 설명한 예상값이며 이 페이지 작성 중 실행하지 않았다.

10모듈로 책임 나누기

  • 모듈은 관련 함수·클래스 등을 묶어 다른 코드에서 가져올 수 있게 한다.
  • import 시 실행되는 최상위 코드에 환경 변경을 넣으면 단순히 불러오는 것만으로 부작용이 발생할 수 있다.
    • 실행 진입점과 재사용할 정의를 분리한다.
def normalize_names(names=None):
    if names is None:
        names = []
    return [name.strip().lower() for name in names]

print(normalize_names([" Alpha ", "BETA"]))

입력 리스트를 직접 변경하지 않고 정규화한 새 리스트를 반환한다.

11가변 기본 인수가 호출 사이에 남는 과정

  • def add_item(value, items=[])처럼 정의된 함수는 그 정의가 실행되는 시점에 기본 리스트를 만든다.
  • 첫 호출에서 items를 생략하면 그 기본 객체를 사용하고 append로 값을 추가한다.
  • 다음 호출에서도 인수를 생략하면 새 리스트가 아니라 같은 기본 객체를 다시 받는다.
  • 호출 프레임이 새로 만들어진다는 사실과 매개변수가 가리키는 객체가 새로 만들어진다는 사실은 다르다.
  • 두 번째 호출에 이전 값이 남아 있는 현상을 이 구분으로 설명할 수 있다.
  • 호출마다 독립된 목록이 필요하면 기본값을 None으로 두고 함수 안에서 새 리스트를 만든다.
    • 반대로 호출자가 전달한 목록을 의도적으로 바꾸는 함수라면 그 동작을 계약으로 명시해야 한다.
  • 이름 재대입과 객체 변경도 분리한다.
  • 함수 안에서 items = []로 새 객체를 가리키게 하는 것과 items.clear()로 전달받은 객체의 내용을 지우는 것은 호출자에게 미치는 영향이 다르다.
  • 반환값으로 새 결과를 돌려주는 설계는 이러한 부작용을 줄이는 한 방법이다.
  1. 가변 기본값 items=[]를 가진 def가 실행될 때 빈 리스트 L이 한 번 만들어진다. 각 함수 호출 때마다 기본 리스트를 만드는 것이 아니다.
  2. items 인수를 생략한 첫 호출의 매개변수는 L을 참조한다. 함수 안에서 append하면 L 자체가 변경된다.
  3. 인수를 다시 생략하면 새 호출 프레임에서도 같은 L을 기본값으로 사용한다. 첫 호출이 넣은 요소가 남아 있으므로 두 번째 변경이 누적된다.
  4. 함수가 L을 반환했다면 첫 호출 때 보관한 결과도 L의 참조다. 나중 호출이 L을 변경하면 그 이전 결과를 조회해도 바뀐 내용이 나타난다.

설명과 함께 보는 보조 도식

처리 과정

기본 리스트가 한 번 만들어지는 함수

add_item(value, items=[])가 items.append(value)를 실행하고 items를 반환하는 사례다.

전체 단계를 한 번에 읽기
  1. 01

    정의 실행

    빈 리스트 L을 한 번 만든다.

    L
    []
  2. 02

    첫 호출

    새 호출 프레임의 items가 L을 참조한다.

    L
    ["alpha"]
  3. 03

    둘째 호출

    프레임은 달라도 생략된 기본 인수는 같은 L이다.

    L
    ["alpha", "beta"]
  4. 04

    반환 참조 확인

    첫 호출에서 보관한 결과도 같은 L이면 나중 변화가 보인다.

    원인
    기본 객체 공유
학습 모식도. 표시된 사례의 객체·상태를 단계별로 표현하며 세부 구현과 다른 분기는 생략한다.

12함수 경계에서 관찰할 값

  • 호출 전 입력, 호출 후 입력, 반환값을 별도로 비교하면 함수가 복사본을 만드는지 원본을 바꾸는지 알 수 있다.
  • print로 화면에 보인 내용과 return으로 전달된 값도 따로 본다.
  • 반복자나 제너레이터를 인수로 받는 함수는 입력을 한 번 소비하면 다시 읽을 수 없는 경우가 있으므로 len이나 여러 번의 순회를 당연하게 기대하지 않는다.
  • 호출 계약에는 허용 자료형, 빈 입력과 실패 시 행동, 원본 변경 여부를 포함한다.
  • 지역 변수의 값을 읽기 전에 대입하는지, 바깥 이름과 같은 이름을 재정의했는지도 확인한다.
  • 은 이름이 연결되는 범위를 바꾸지만 객체 복사를 수행하지 않는다.
  • 모듈을 import할 때 최상위 코드가 실행될 수 있으므로 재사용할 정의와 실제 실행 진입점을 구분한다.
  • 자동화 함수가 import만으로 네트워크에 접속하거나 설정을 바꾸면 예상하지 못한 부작용이 생긴다. 입력 변환·검증과 실제 장비 작업을 분리하면 작은 자료로 함수 계약을 확인하기 쉽다.
구성·관찰 대상확인 정보판단할 차이
함수 정의기본 인수 객체호출마다 생성되는지
호출 프레임지역 이름·매개변수프레임과 객체 수명의 차이
입력·반환값원본 변경·새 결과print와 return
모듈 진입점import 시 실행 내용정의 불러오기와 작업 수행
학습 확인

이해했는지 확인하기

  • print 출력과 return 결과를 구분한다.
  • 가변 기본 인수가 호출 사이에 공유되는지 확인한다.
  • 함수의 외부 상태 의존성과 입력 변경 여부를 확인한다.

함께 확인할 공식 자료