함수의 입력과 반환
- 학습 방식
- 개념 해설 · 코드 시연 · 단계별 실습
- 출발점
- 1교시의 여섯 도형 함수
- 결과물
- 입력·반환 함수 · PNG · 기준안 A
- 다음 학습
- 같은 생성기의 설정 비교
학습 목표
- 매개변수·인자·본문의 사용 위치를 연결한다.
- 두 입력을 구분하여 전달하고 이미지 변화를 설명한다.
- 화면 표시와 반환을 구분하고 받은 이미지를 저장한다.
- 오류를 수정하고 생성 함수와 기준안 A를 완성한다.
학습 내용
1교시에 저장한 같은 노트북에서 2교시 구역으로 이동합니다. 1교시 마지막 정상 복구 셀까지 실행한 상태에서 시작합니다. 런타임이 끊겼다면 공통 준비부터 앞 교시의 완성한 셀을 순서대로 실행합니다. 새로운 파일에 함수를 다시 작성하지 않습니다.
이번 시간은 해설 → 코드 시연 → 결과 확인의 흐름으로 입력과 반환을 이해합니다. 짧은 예제의 실행 순서를 따라간 뒤 같은 구조를 이미지에 적용합니다. 각 실습은 수정할 이유와 완성 코드를 함께 보며 진행하고, A·B·C의 차이를 코드와 연결해 확인합니다. 오류 진단 셀은 메시지를 확인한 뒤 같은 셀을 고치며, 최종 전체 실행은 모든 실습을 완성한 후에 수행합니다.
A·B·C 시각 변형
이미지 실습의 A·B·C는 같은 제작 원리로 만든 세 가지 결과입니다. 예를 들어 1-4B는 1교시 실습 4의 B 버전입니다. A를 기준으로 B·C의 변경 지점을 각각 비교합니다. 정의가 공통인 실습은 먼저 정의를 실행한 뒤 세 호출을 이어서 사용합니다. 결과 이미지는 실행 후 펼쳐 확인하며, 그림을 누르면 원본 크기로 볼 수 있습니다.
1. 입력의 필요성
하나의 함수로 큰 경계 60과 63의 이미지를 각각 만들려면 무엇이 필요할까요?
기준 함수의 확인
1교시에서는 이미지 한 장을 만드는 준비·반복·표시를 show_shape_row라는 작업으로 묶었습니다. 두 번 호출하면 같은 규칙의 두 이미지가 나왔고, 팔레트 한 곳을 수정하면 이후 호출에 공통으로 적용되었습니다. 마지막에는 산호색과 큰 경계 60·중간 경계 40을 복구했습니다.
2교시 첫 셀은 그 함수를 한 번 호출합니다. 새 정의를 붙여 넣기 전에 기존 함수가 준비되었는지, 산호색 사각형 두 개가 있는지 확인합니다. 여섯 도형의 크기 목록은 30, 46, 64, 46, 60, 38입니다. 이 결과를 비교의 출발점으로 사용합니다.
show_shape_row()기준 이미지와 복구 코드

from PIL import Image, ImageDraw
from IPython.display import display
def show_shape_row():
sizes = [30, 46, 64, 46, 60, 38]
palette = [(37, 62, 78), (111, 143, 126), (201, 111, 93)]
image = Image.new("RGB", (660, 220), (245, 240, 228))
draw = ImageDraw.Draw(image)
for i in range(6):
x = 50 + i * 100
y = 80
size = sizes[i]
if size >= 60:
draw.rectangle((x, y, x + size, y + size), fill=palette[2])
elif size >= 40:
draw.ellipse((x, y, x + size, y + size), fill=palette[1])
else:
draw.ellipse((x, y, x + size, y + size), outline=palette[0], width=2)
display(image)
show_shape_row()공통 규칙과 개별 설정
1교시의 황토색 변경은 앞으로 만드는 모든 이미지에 같은 규칙을 적용하는 요청이었습니다. 반면 이번 요청은 한 이미지에는 큰 경계 60을, 다른 이미지에는 큰 경계 63을 적용하는 것입니다. 두 설정을 모두 사용해야 하므로 공통 본문을 한 숫자로 고치는 것만으로는 충분하지 않습니다. 작업이 같은 부분과 실행마다 달라지는 부분을 나누어야 합니다.
함수를 두 개 복사하면 지금은 두 설정을 만들 수 있습니다. 하지만 윤곽선 두께를 바꾸는 다음 요청에서는 두 본문을 다시 고쳐야 합니다. 1교시에서 해결한 중복 문제가 다른 이름으로 돌아오는 셈입니다. 하나의 함수가 필요한 값을 입력받고 그 값을 조건식에 사용하면, 작업의 공통 구조를 유지하면서 실행별 설정을 달리할 수 있습니다.
| 요청 | 달라지는 부분 | 필요한 구조 |
|---|---|---|
| 모든 결과의 강조색 통일 | 공통 규칙 자체 | 본문 한 곳 수정 후 호출 |
| 경계 60·63의 두 결과 제작 | 이번 실행에 사용할 경계 | 같은 본문에 다른 값 전달 |
| 두 결과의 PNG 보관 | 완성 이미지의 사용 방법 | 이미지를 받아 각각 저장 |
입력의 선정 이유
제작 요청은 같은 여섯 도형을 두 기준으로 분류한 이미지를 나란히 만드는 것입니다. 도형 자체의 크기나 위치를 바꾸려는 요청이 아니므로, 크기 목록과 좌표 계산은 본문에 그대로 둡니다. 두 결과에서 달라져야 하는 것은 큰 도형을 판정하는 경계값입니다.
예를 들어 다섯 번째 도형의 크기는 60입니다. 큰 경계가 60이면 큰 구간에 들어가 사각형이 되고, 경계가 63이면 중간 구간에 들어가 채운 원이 됩니다. 이때 크기 60을 63으로 고치는 것이 아니라, 같은 크기를 판단하는 기준을 바꿉니다.
이 차이를 호출마다 지정하기 위해 large_threshold를 입력으로 둡니다. 캔버스·좌표·색은 같은 조건으로 유지하고 경계만 전달하면, 두 결과의 차이를 한 설정과 연결해서 설명할 수 있습니다. 이후 중간 구간도 조절할 필요가 생기면 medium_threshold를 두 번째 입력으로 추가합니다.
| 항목 | 첫 결과 | 두 번째 결과 |
|---|---|---|
| 크기 목록 | 30·46·64·46·60·38 | 유지 |
| 캔버스·좌표·팔레트 | 1교시 기준 | 유지 |
| 큰 경계 | 60 | 63 |
| 중간 경계 | 40 | 유지 |
- 1교시 마지막 기준 함수를 호출합니다.
- 공통 수정과 실행별 설정의 차이를 비교합니다.
- 이번에 바꿀 값과 유지할 값을 구분합니다.
기록: 바꿀 값: ___ / 유지할 값: ___ / 두 설정이 필요한 이유: ___
이 요청에서 입력으로 정한 값은 큰 경계이고, 본문에서 그 값을 읽을 곳은 if의 비교식입니다. 다음 절에서 작은 출력 함수로 값 전달을 확인한 뒤 정의의 괄호·조건식·호출의 세 위치를 연결합니다. 제작 요청에서 고른 설정이 실제 코드의 매개변수가 되는 순서입니다.
2. 매개변수와 인자
정의의 입력 이름에 호출의 숫자가 어떻게 연결될까요?
입력 자리와 전달값
매개변수는 입력 자리이고 인자는 실제 값입니다. def의 괄호에 large_threshold라는 이름을 쓰면 호출할 때 값을 받을 자리가 생깁니다. announce_threshold(60)은 그 자리에 60을 전달합니다. 입력 없는 함수의 빈 괄호가 값 전달의 통로로 확장되는 것입니다.
아래 함수의 작업은 받은 경계를 문장으로 표시하는 것입니다. 지금은 그림을 그리지 않습니다. 같은 본문이 두 호출에서 어떤 값을 읽는지에 집중합니다. 호출의 괄호에 숫자를 적는 경우와 값을 보관한 변수 이름을 적는 경우를 함께 비교합니다.

첫 호출과 두 번째 호출에서 large_threshold가 가리킬 값을 적으세요. 두 번째 호출이 끝난 뒤 chosen_threshold에는 어떤 값이 남을까요?
def announce_threshold(large_threshold):
print("이번 큰 경계:", large_threshold)
announce_threshold(60)
chosen_threshold = 63
announce_threshold(chosen_threshold)값 전달 해설
| 호출 | 읽어 온 값 | 함수 안의 이름 | 화면 출력 |
|---|---|---|---|
| announce_threshold(60) | 60 | large_threshold | 이번 큰 경계: 60 |
| announce_threshold(chosen_threshold) | 63 | large_threshold | 이번 큰 경계: 63 |
chosen_threshold는 63을 유지합니다. 함수의 매개변수는 이번 호출에서 전달받은 값을 사용합니다. 다음 호출은 그때 전달하는 값으로 다시 시작합니다. 같은 본문을 다시 정의하지 않고 두 설정을 실행했습니다.
매개변수와 인자의 대응
정의에 적힌 large_threshold는 값이 들어올 자리의 이름입니다. 호출 announce_threshold(63)에서 63이 그 자리에 연결되고, 이번 호출의 본문에서는 large_threshold가 63을 가리킵니다. 다음에 announce_threshold(60)을 호출하면 그 실행에서는 60을 사용합니다. 함수 본문의 철자를 바꾸지 않고 다른 실행을 요청할 수 있는 이유입니다.
매개변수와 인자는 서로 완전히 다른 종류의 숫자를 뜻하는 말이 아닙니다. 함수를 정의하는 쪽의 입력 자리를 매개변수라고 하고, 호출하는 쪽에서 전달하는 실제 값을 인자라고 합니다. 같은 줄만 바라보며 구분하기보다 정의와 호출을 함께 놓고 어느 쪽을 설명하는 말인지 확인합니다.
변숫값의 전달
호출의 괄호에는 숫자를 직접 적을 수도 있고 이미 값이 들어 있는 변수 이름을 적을 수도 있습니다. chosen_threshold = 63 다음에 announce_threshold(chosen_threshold)를 호출하면 숫자 63을 전달합니다. chosen_threshold라는 글자를 전달하는 것이 아닙니다. 따옴표를 붙인 문자열 "chosen_threshold"와 구분합니다.
함수 밖의 변수 이름과 함수 안의 매개변수 이름이 같을 필요는 없습니다. 바깥의 chosen_threshold는 이 실험에서 선택한 값을 보관하고, 안쪽의 large_threshold는 함수에서 그 값이 맡는 역할을 나타냅니다. 이름이 달라도 호출에서 값이 올바른 자리에 연결되면 본문은 같은 값으로 동작합니다.
| 위치 | 코드 | 값의 역할 |
|---|---|---|
| 정의 | def announce_threshold(large_threshold): | 입력 자리의 이름 선언 |
| 본문 | print("이번 큰 경계:", large_threshold) | 받은 값 사용 |
| 호출 | announce_threshold(63) | 실제 숫자 전달 |
호출별 값의 사용
첫 호출에서 받은 60과 다음 호출에서 받은 63을 한 번에 섞어 쓰는 것이 아닙니다. 첫 작업을 끝내고 호출 다음 줄로 돌아온 뒤 두 번째 작업을 시작합니다. 1교시의 호출과 복귀 경로는 그대로이고, 각 작업을 시작할 때 사용할 값이 추가되었습니다.
정의에 입력 자리가 하나 생기면 호출에도 값을 전달해야 합니다. announce_threshold()처럼 빈 괄호로 호출하면 이 정의에 필요한 값이 없습니다. 예전 호출문을 그대로 두지 말고 정의를 바꾼 부분과 호출의 개수를 함께 확인합니다. 구체적인 오류 메시지는 오류 진단 절에서 다룹니다.
실습 1 · 매개변수 연결
입력으로 받은 횟수만큼 도형 번호를 출력하려고 합니다. 아래 함수는 2를 전달해도 4를 전달해도 세 번 출력합니다. 함수 이름과 호출은 그대로 두고 본문의 숫자 한 곳을 고치세요.
def announce_shapes(shape_count):
for i in range(3):
print("도형", i)
announce_shapes(2)
announce_shapes(4)해설과 완성 코드
def announce_shapes(shape_count):
for i in range(shape_count):
print("도형", i)
announce_shapes(2)
announce_shapes(4)range(3)을 range(shape_count)로 고칩니다. 첫 호출은 도형 0·1, 두 번째 호출은 도형 0·1·2·3을 출력합니다. 입력의 값이 반복 횟수에 실제로 연결되었으므로 두 호출의 출력 수가 달라집니다.
매개변수 이름을 count로 바꾸어도 정의와 본문의 사용 이름을 함께 바꾸면 같은 동작을 만들 수 있습니다. 중요한 것은 이름의 길이가 아니라 받은 값을 필요한 계산에 사용하는 연결입니다. 마지막 포스터 생성기의 shape_count도 바로 이 방식으로 range에 연결합니다.
입력의 사용 위치
실습 1에서 입력은 range의 반복 횟수에 사용됩니다. 같은 입력을 print에 넣으면 문장이 바뀌고, range에 넣으면 처리 횟수가 바뀝니다. 값이 어디에 쓰이는지에 따라 그 설정의 의미가 결정됩니다. 함수 이름이나 매개변수 이름만 적는 것으로 효과가 생기지는 않습니다.
shape_count가 2일 때 도형 번호는 0과 1이고, 4일 때 0부터 3까지입니다. range는 마지막 수를 포함하지 않지만 반복 횟수는 전달한 정수만큼입니다. 출력의 마지막 번호와 전체 출력 수를 구분합니다. 실습을 설명할 때 “2가 나왔다”보다 “번호 두 개가 나왔다”라고 기록합니다.
- 짧은 출력 함수에서 매개변수와 인자를 표시합니다.
- 숫자와 변숫값의 전달을 비교합니다.
- 실습 1의 고정 반복 수를 입력 이름으로 바꿉니다.
기록: 매개변수: ___ / 인자: ___ / 본문의 사용 줄: ___ / 호출별 출력: ___
다음 이미지 실습에서는 반복 수를 그대로 두고 조건식에 입력을 연결합니다. 받은 값을 사용하는 위치만 달라집니다. 따라서 여섯 도형 중 표현이 바뀌는 것은 있어도 도형 개수가 줄어드는 것은 이번 수정의 목표가 아닙니다.
3. 이미지 함수의 입력
큰 경계를 60에서 63으로 바꾸면 여섯 도형 중 어느 것이 달라질까요?
정의·본문·호출의 연결
괄호에 매개변수를 추가했다고 본문의 숫자가 자동으로 바뀌지는 않습니다. def show_shape_row(large_threshold)라고 정의해도 if size >= 60이라고 적혀 있으면 여전히 숫자 60을 사용합니다. 이름을 받아 놓고 그 이름을 읽는 곳이 없는 것입니다. 이런 함수는 서로 다른 값으로 호출해도 같은 결과를 만들 수 있습니다.
정의를 수정할 때는 세 곳을 연결합니다. 첫째 정의의 괄호에 입력 자리를 만듭니다. 둘째 본문에서 바뀌어야 할 고정 숫자를 그 입력 이름으로 바꿉니다. 셋째 호출에서 실제 값을 전달합니다. 이 중 하나라도 빠지면 값을 전달하지 못하거나 전달한 값이 제작에 반영되지 않습니다. 사용자가 설정을 바꿨는데 결과가 그대로일 때 이 연결을 먼저 점검합니다.
| 위치 | 입력 없는 함수 | 입력 있는 함수 |
|---|---|---|
| 정의 | def show_shape_row(): | def show_shape_row(large_threshold): |
| 큰 조건 | if size >= 60: | if size >= large_threshold: |
| 호출 | show_shape_row() | show_shape_row(60) 또는 show_shape_row(63) |
데이터와 판단 기준
큰 경계를 바꾸는 것은 크기 목록을 바꾸는 일이 아닙니다. 크기 60인 도형의 가로·세로 값은 계속 60입니다. 같은 크기를 사각형 구간에 둘지 중간 구간에 둘지 판단하는 조건이 달라집니다. 크기 숫자와 경계 숫자가 우연히 같아도 맡은 역할은 다릅니다.
비교 전에는 원본의 여섯 크기를 위에서부터 읽고 새 경계와 대조합니다. size가 large_threshold 이상이면 사각형이고, 그렇지 않으면 중간 경계 40을 검사합니다. 큰 경계만 바꾸므로 두 번째 조건의 40은 그대로 둡니다. 팔레트와 좌표도 유지하여 표현 변화의 원인을 하나로 제한합니다.
실습 2 · 이미지 입력
- 공통 원리
- 같은 함수에 큰 경계 하나를 달리 전달하여 분류와 표현을 바꿉니다.
- 유지 요소
- 함수 본문 · 크기 목록 · 좌표 · 팔레트 · 중간 경계 40
공통 정의의 if에 남은 60을 large_threshold로 바꿉니다. 정의 셀만 실행했을 때는 이미지가 나오지 않습니다. A·B·C 호출을 각각 실행해 60 → 63 → 65의 큰 경계가 크기 60과 64의 표현에 미치는 영향을 확인합니다. 본문을 고치지 않으면 세 입력이 같은 결과를 만듭니다.
| 크기 | A의 표현 | B의 표현 | C의 표현 |
|---|---|---|---|
| 30 · 1번째 | ___ | ___ | ___ |
| 46 · 2번째 | ___ | ___ | ___ |
| 64 · 3번째 | ___ | ___ | ___ |
| 46 · 4번째 | ___ | ___ | ___ |
| 60 · 5번째 | ___ | ___ | ___ |
| 38 · 6번째 | ___ | ___ | ___ |
공통 · 입력 연결과 정의
from PIL import Image, ImageDraw
from IPython.display import display
def show_shape_row(large_threshold):
sizes = [30, 46, 64, 46, 60, 38]
palette = [(37, 62, 78), (111, 143, 126), (201, 111, 93)]
image = Image.new("RGB", (660, 220), (245, 240, 228))
draw = ImageDraw.Draw(image)
for i in range(6):
x = 50 + i * 100
y = 80
size = sizes[i]
if size >= 60: # TODO: 전달받은 입력을 사용하세요.
draw.rectangle((x, y, x + size, y + size), fill=palette[2])
elif size >= 40:
draw.ellipse((x, y, x + size, y + size), fill=palette[1])
else:
draw.ellipse((x, y, x + size, y + size), outline=palette[0], width=2)
display(image)완성 코드
from PIL import Image, ImageDraw
from IPython.display import display
def show_shape_row(large_threshold):
sizes = [30, 46, 64, 46, 60, 38]
palette = [(37, 62, 78), (111, 143, 126), (201, 111, 93)]
image = Image.new("RGB", (660, 220), (245, 240, 228))
draw = ImageDraw.Draw(image)
for i in range(6):
x = 50 + i * 100
y = 80
size = sizes[i]
if size >= large_threshold:
draw.rectangle((x, y, x + size, y + size), fill=palette[2])
elif size >= 40:
draw.ellipse((x, y, x + size, y + size), fill=palette[1])
else:
draw.ellipse((x, y, x + size, y + size), outline=palette[0], width=2)
display(image)A 사각형 두 개
large_threshold에 60을 전달합니다.
실행 전 예상 형태·색·배치의 변화 ___
2-2A · 실습 코드
show_shape_row(60)B 사각형 한 개
large_threshold에 63을 전달합니다.
실행 전 예상 형태·색·배치의 변화 ___
2-2B · 실습 코드
show_shape_row(63)C 원 구성
large_threshold에 65을 전달합니다.
실행 전 예상 형태·색·배치의 변화 ___
2-2C · 실습 코드
show_shape_row(65)A·B·C 결과 비교
비교 기록
- 공통 원리
- ___
- 바꾼 줄
- ___
- A와 B의 차이
- ___
- A와 C의 차이
- ___
관찰의 근거
“입력값을 바꾸니 그림이 달라졌다”에서 멈추지 않고 입력 → 비교식 → 선택된 분기 → 도형의 순서로 설명합니다. 다섯 번째 크기 60이 첫 비교에서 탈락하여 elif로 이동했고, 그 분기의 채움색을 사용했다는 식으로 결과의 원인을 추적합니다.
결과가 예상과 다르면 세 위치를 차례로 확인합니다. 정의의 이름을 받았는지, 본문이 그 이름을 읽는지, 호출이 새 값을 전달하는지를 봅니다. 조건이 맞는데도 예전 결과가 보이면 수정한 정의를 실행한 뒤 새 호출을 했는지 확인합니다. 기존 출력은 새 입력에 맞추어 자동으로 변하지 않습니다.
동일 결과의 원인
입력이 제대로 연결되어도 선택한 값에 따라 결과가 같을 수 있습니다. 기준 목록에 61이나 62는 없으므로 큰 경계 61과 62는 모두 64만 사각형으로 분류합니다. 따라서 두 이미지가 같다는 사실만으로 매개변수가 사용되지 않았다고 결론 내리면 안 됩니다. 두 설정 사이에서 실제로 분류가 바뀔 데이터가 있는지 먼저 확인합니다.
연결을 검증할 때 60과 63을 사용하는 이유는 목록에 정확히 60이 있어서 차이를 예상할 수 있기 때문입니다. 아무 숫자 두 개를 고르는 것보다 바뀔 대상이 확실한 입력을 고르면 결과를 해석하기 쉽습니다. 결과가 같다면 연결 오류인지, 실제로 같은 분류인지 코드와 데이터로 구분합니다.
큰 경계를 61과 62로 각각 전달하면 두 결과가 다를까요? 두 기준 사이에서 분류가 바뀔 크기가 목록에 있는지 먼저 찾으세요.
동일 결과 해설
두 경우 모두 사각형은 64 하나입니다. 60은 이미 두 경계보다 작고 61이나 62인 도형은 없습니다. 이 경우 같은 결과는 정상입니다. 반면 60과 63의 비교에서 같은 결과가 나오면 60이 바뀌어야 하므로 입력 연결과 실행 상태를 다시 확인합니다.
4. 다중 입력과 키워드
두 입력을 뒤집지 않고 각각의 역할에 맞게 전달하려면 어떻게 해야 할까요?
두 번째 입력
큰 경계를 63으로 유지하면서 중간 크기의 채운 원을 줄이는 요청을 생각합니다. 지금 함수의 중간 경계 40은 본문에 고정되어 있으므로 호출에서는 바꿀 수 없습니다. medium_threshold라는 두 번째 자리를 만들고 elif의 숫자를 그 이름으로 바꿉니다.
이제 호출에는 큰 경계와 중간 경계가 모두 필요합니다. 두 숫자가 전달되었다는 사실만으로 올바른 설정인 것은 아닙니다. 각각 어떤 역할에 연결되었는지 확인해야 합니다. 긴 이미지 코드로 돌아가기 전에 짧은 문장으로 두 입력의 대응을 확인합니다.
위치 인자
정의가 show_shape_row(large_threshold, medium_threshold)이면 첫 값은 큰 경계, 두 번째 값은 중간 경계입니다. show_shape_row(63, 50)은 의도에 맞지만 show_shape_row(50, 63)은 다른 설정입니다. 파이썬은 두 숫자의 크기를 보고 큰 값을 자동으로 큰 경계에 넣지 않습니다. 정의에 적힌 순서대로 연결할 뿐입니다.
이번 분류는 큰 경계가 중간 경계보다 커야 세 구간을 자연스럽게 나눌 수 있습니다. 둘을 뒤집으면 큰 경계 50을 먼저 검사하므로 50 이상은 모두 첫 분기에 들어갑니다. 그 아래에서 중간 경계 63을 검사해도 이미 50 미만인 값만 남아 있어 중간 분기가 실행되지 않습니다. 입력 순서의 실수는 문법 오류 없이도 전혀 다른 시각 결과를 만들 수 있습니다.
키워드 인자
show_shape_row(large_threshold=63, medium_threshold=50)처럼 이름을 적으면 각 숫자의 역할을 호출에서 바로 읽을 수 있습니다. 이 경우 키워드 순서를 바꾸어 medium_threshold=50을 먼저 적어도 같은 연결입니다. 단, 왼쪽 이름은 함수 정의에 있는 매개변수 이름과 일치해야 합니다. 설명을 위해 임의로 large=63이라고 줄이면 해당 이름을 받도록 정의하지 않은 함수는 호출을 받아들이지 못합니다.
호출의 등호는 그 호출에서 어느 입력에 어떤 값을 전달하는지 지정합니다. 예를 들어 large_threshold=chosen_large에서 왼쪽은 함수가 받는 자리이고 오른쪽은 함수 밖에서 값을 읽어 오는 표현입니다. 두 이름을 같게 만들 필요는 없습니다. 나중의 shape_count=count_c도 같은 방식으로 읽으면 됩니다.
아래 세 호출의 큰 경계와 중간 경계를 적으세요. 순서가 달라도 같은 결과인 호출과 두 값의 역할이 뒤집힌 호출을 구분하세요.
def announce_pair(large_threshold, medium_threshold):
print("큰 경계", large_threshold, "중간 경계", medium_threshold)
announce_pair(63, 50)
announce_pair(medium_threshold=50, large_threshold=63)
announce_pair(50, 63)세 호출의 해설
| 호출 | 큰 경계 | 중간 경계 |
|---|---|---|
| (63, 50) | 63 | 50 |
| (medium_threshold=50, large_threshold=63) | 63 | 50 |
| (50, 63) | 50 | 63 |
첫째와 둘째는 같습니다. 셋째는 큰 경계 50, 중간 경계 63으로 역할이 뒤집혔습니다. 숫자를 전달하는 문법은 맞으므로 오류 메시지가 없어도 제작 의도와 다를 수 있습니다.
실습 3 · 키워드 인자
바깥에 저장한 chosen_large와 chosen_medium을 아래 함수의 알맞은 매개변수에 키워드로 전달하세요. 값을 정의의 순서와 반대로 적더라도 역할이 명확히 연결되도록 작성합니다.
def announce_pair(large_threshold, medium_threshold):
print("큰 경계", large_threshold, "중간 경계", medium_threshold)
chosen_large = 63
chosen_medium = 50
announce_pair(medium_threshold=____, large_threshold=____)해설과 완성 코드
def announce_pair(large_threshold, medium_threshold):
print("큰 경계", large_threshold, "중간 경계", medium_threshold)
chosen_large = 63
chosen_medium = 50
announce_pair(medium_threshold=chosen_medium, large_threshold=chosen_large)출력은 “큰 경계 63 중간 경계 50”입니다. 키워드 이름으로 연결했으므로 호출에서 먼저 적은 chosen_medium은 두 번째 매개변수인 medium_threshold에 들어갑니다. 순서를 기준으로 전달하는 위치 인자와 차이가 드러나는 예입니다.
등호 오른쪽을 숫자 50과 63으로 바꿔도 같은 값이 전달됩니다. 변수로 분리해 두면 실험 설정을 한곳에서 읽기 쉽습니다. 다만 변수 값을 바꾼 뒤에는 호출도 다시 실행해야 새 설정이 반영된 결과를 얻습니다.
실습 4 · 다중 입력
- 공통 원리
- 키워드로 두 입력을 구분하고 중간 경계만 바꾸어 표현 비율을 조절합니다.
- 유지 요소
- 함수 본문 · 큰 경계 63 · 크기 목록·좌표·팔레트
공통 정의의 elif에 남은 40을 medium_threshold로 바꿉니다. 큰 경계는 63으로 유지하고 세 호출의 중간 경계만 달리합니다. 각 크기를 큰 조건부터 검사하여 사각형·채운 원·윤곽 원의 수를 예상합니다.
| 크기 | A의 표현 | B의 표현 | C의 표현 |
|---|---|---|---|
| 30 · 1번째 | ___ | ___ | ___ |
| 46 · 2번째 | ___ | ___ | ___ |
| 64 · 3번째 | ___ | ___ | ___ |
| 46 · 4번째 | ___ | ___ | ___ |
| 60 · 5번째 | ___ | ___ | ___ |
| 38 · 6번째 | ___ | ___ | ___ |
공통 · 두 입력 연결과 정의
from PIL import Image, ImageDraw
from IPython.display import display
def show_shape_row(large_threshold, medium_threshold):
sizes = [30, 46, 64, 46, 60, 38]
palette = [(37, 62, 78), (111, 143, 126), (201, 111, 93)]
image = Image.new("RGB", (660, 220), (245, 240, 228))
draw = ImageDraw.Draw(image)
for i in range(6):
x = 50 + i * 100
y = 80
size = sizes[i]
if size >= large_threshold:
draw.rectangle((x, y, x + size, y + size), fill=palette[2])
elif size >= 40: # TODO: 두 번째 입력을 연결하세요.
draw.ellipse((x, y, x + size, y + size), fill=palette[1])
else:
draw.ellipse((x, y, x + size, y + size), outline=palette[0], width=2)
display(image)완성 코드
from PIL import Image, ImageDraw
from IPython.display import display
def show_shape_row(large_threshold, medium_threshold):
sizes = [30, 46, 64, 46, 60, 38]
palette = [(37, 62, 78), (111, 143, 126), (201, 111, 93)]
image = Image.new("RGB", (660, 220), (245, 240, 228))
draw = ImageDraw.Draw(image)
for i in range(6):
x = 50 + i * 100
y = 80
size = sizes[i]
if size >= large_threshold:
draw.rectangle((x, y, x + size, y + size), fill=palette[2])
elif size >= medium_threshold:
draw.ellipse((x, y, x + size, y + size), fill=palette[1])
else:
draw.ellipse((x, y, x + size, y + size), outline=palette[0], width=2)
display(image)A 혼합 구성
medium_threshold에 40을 전달합니다.
실행 전 예상 형태·색·배치의 변화 ___
2-4A · 실습 코드
show_shape_row(large_threshold=63, medium_threshold=40)B 윤곽 중심
medium_threshold에 50을 전달합니다.
실행 전 예상 형태·색·배치의 변화 ___
2-4B · 실습 코드
show_shape_row(large_threshold=63, medium_threshold=50)C 채움 중심
medium_threshold에 35을 전달합니다.
실행 전 예상 형태·색·배치의 변화 ___
2-4C · 실습 코드
show_shape_row(large_threshold=63, medium_threshold=35)A·B·C 결과 비교
비교 기록
- 공통 원리
- ___
- 바꾼 줄
- ___
- A와 B의 차이
- ___
- A와 C의 차이
- ___
경계값 검증
비교 연산자가 >=이므로 경계와 같은 값은 그 분기에 포함됩니다. 큰 경계가 63이면 63은 사각형입니다. 중간 경계가 50이면 50은 채운 원입니다. 단, if를 먼저 검사하기 때문에 63 이상은 중간 비교식도 참일 수 있더라도 첫 분기의 표현을 사용합니다. if·elif는 서로 독립적으로 모두 그리는 구조가 아닙니다.
경계 검증에는 49·50·51, 62·63·64처럼 경계 전후의 값을 사용하는 것이 좋습니다. 큰 숫자 하나와 작은 숫자 하나만 확인하면 >=을 >로 잘못 쓴 오류를 놓칠 수 있습니다. 실제 데이터에 없는 값도 짧은 분류 예제에 넣어 규칙 자체가 의도대로인지 확인할 수 있습니다.
| 크기 | 큰 경계 63 검사 | 중간 경계 50 검사 | 예상 표현 |
|---|---|---|---|
| 49 | 거짓 | 거짓 | 윤곽 원 |
| 50 | 거짓 | 참 | 채운 원 |
| 51 | 거짓 | 참 | 채운 원 |
| 62 | 거짓 | 참 | 채운 원 |
| 63 | 참 | 검사하지 않음 | 사각형 |
| 64 | 참 | 검사하지 않음 | 사각형 |
표의 49·50·51·62·63은 현재 크기 목록에 새로 넣으라는 뜻이 아닙니다. 경계 규칙을 손으로 확인하는 시험값입니다. 결과 이미지의 비교와 조건 자체의 검증을 구분합니다. 기본 크기 목록은 계속 유지해야 앞 실습과의 변화 원인을 비교할 수 있습니다.
- 위치·키워드 호출의 값을 대응시킵니다.
- 실습 3의 바깥 변수를 알맞은 키워드에 연결합니다.
- 실습 4에서 중간 경계를 연결하고 63/40·63/50·63/35의 결과를 비교합니다.
기록: 위치와 이름의 대응: ___ / A·B·C의 도형 분류: ___ / 변한 도형: ___
5. 반환값의 필요성
화면에 나온 숫자를 호출한 코드의 다음 계산에도 사용할 수 있을까요?
다음 작업에 필요한 값
지금까지 만든 함수는 호출하면 화면에 결과를 보여 줍니다. 하지만 그 결과로 계산을 더 하거나 파일을 저장하려면 호출한 코드가 사용할 값을 받아야 합니다. 화면에 보이는 결과와 프로그램이 다음 작업에 사용할 결과를 구분하는 것이 반환값의 출발점입니다.
먼저 숫자 6을 두 배로 만드는 짧은 함수 두 개를 비교합니다. 계산식은 같지만 하나는 print를 사용하고 다른 하나는 return을 사용합니다. 이미지를 다루기 전에 숫자가 어디에 남는지 확인하면 그림이 보인다는 사실에 가려진 값 전달의 차이를 읽기 쉽습니다.

실행 전에 화면에 나올 줄을 적고 shown과 received가 각각 어떤 값을 받을지 예상하세요. 마지막 + 3의 계산이 가능한 것은 어느 쪽일까요?
def show_total(count):
print(count * 2)
def make_total(count):
return count * 2
shown = show_total(6)
received = make_total(6)
print("shown:", shown)
print("received:", received)
print("다음 계산:", received + 3)표시와 반환의 해설
| 함수 | 함수 안의 화면 출력 | 호출에서 받은 값 | 다음 계산 |
|---|---|---|---|
| show_total(6) | 12 | None | shown + 3은 실패 |
| make_total(6) | 없음 | 12 | received + 3은 15 |
전체 화면에는 12, shown: None, received: 12, 다음 계산: 15가 순서대로 나옵니다. make_total 안에서는 화면 표시를 하지 않았지만 값을 받은 바깥 코드가 이후에 표시하고 계산했습니다. 반환값이 있다는 것과 함수 안에서 화면에 출력한다는 것은 같은 뜻이 아닙니다.
반환문의 역할
return 뒤의 값은 현재 호출을 요청한 곳으로 전달됩니다. 호출이 received = make_total(6)의 오른쪽에 있다면 먼저 함수를 실행하고, 반환한 12를 받은 뒤 received라는 이름으로 사용합니다. 값을 돌려준 return은 동시에 그 함수 호출을 끝냅니다.
함수 본문 끝까지 return 없이 진행하면 호출 결과는 None입니다. None은 숫자 0이나 빈 문자열이 아니며, 이번에는 다음 계산에 쓸 숫자를 받지 못한 상태입니다. 화면에 12가 출력되었더라도 print가 그 숫자를 호출 결과로 돌려주는 것은 아닙니다.
| 순서 | 실행 위치 | 수행한 작업 |
|---|---|---|
| 1 | 바깥 호출 | make_total에 6 전달 |
| 2 | 함수 본문 | 6 × 2 계산 |
| 3 | return | 12 전달 후 함수 종료 |
| 4 | 바깥 할당 | received가 12를 가리킴 |
| 5 | 다음 문장 | received + 3 계산 |
실습 5 · 반환값 활용
한 줄에 놓을 도형 수와 행 수를 곱하여 전체 개수를 구하고, 여기에 강조 도형 두 개를 더하려고 합니다. 아래 함수는 값을 출력하기만 합니다. 전체 코드를 독립적으로 실행할 수 있도록 함수 본문의 마지막 줄을 고치세요.
def count_motifs(per_row, row_count):
total = per_row * row_count
print(total)
base_count = count_motifs(6, 3)
# base_count를 계산에 사용할 수 있게 함수를 고친 뒤 아래 줄을 실행합니다.
print(base_count + 2)해설과 완성 코드
def count_motifs(per_row, row_count):
total = per_row * row_count
return total
base_count = count_motifs(6, 3)
print(base_count + 2)print(total)을 return total로 바꾸면 18이 호출한 곳으로 전달되고 마지막 출력은 20이 됩니다. 함수 안의 계산은 같고, 계산 결과를 어디에서 사용할 수 있는지가 달라졌습니다. 화면에 확인하고 싶으면 바깥에서 print(base_count)를 추가할 수 있습니다.
단순히 base_count에 18을 직접 적으면 이번 숫자에서는 계산되지만 함수의 결과를 이어 사용하는 실습이 사라집니다. 호출에서 받은 값을 다음 계산에 전달하는 구조를 유지해야 per_row나 row_count를 바꾸어도 전체 과정이 이어집니다. 이미지 반환도 이 숫자 예제와 같은 흐름입니다.
실습 5의 호출을 4와 2로 바꾸면 반환값과 마지막 출력은 각각 얼마일까요? base_count에 18을 직접 적은 코드도 이 변경을 따라갈 수 있을까요?
반환값의 재사용
함수는 4 × 2인 8을 돌려주고 바깥의 + 2는 10이 됩니다. base_count에 18을 직접 넣은 코드는 입력 변경을 따라가지 못합니다. 호출 결과를 받아 쓰는 구조가 유지되어야 바뀐 설정에도 전체 작업이 이어집니다.
print를 return으로 바꾼 함수에서 내부 계산을 확인하고 싶다면 어디에 print를 추가할 수 있을까요? 반환값을 유지하면서 설명하세요.
표시와 반환의 병행
본문에서 return 전에 print(total)을 실행하거나, 바깥에서 print(base_count)를 실행할 수 있습니다. 확인 문장을 출력하면서 값을 반환하는 것도 가능합니다. return 뒤에 print를 넣으면 그 호출은 이미 끝났으므로 실행되지 않습니다.
- 같은 숫자를 print하는 함수와 return하는 함수를 비교합니다.
- 변수에 받은 값과 화면 출력을 따로 기록합니다.
- 실습 5에서 반환값을 다음 계산에 사용합니다.
기록: 화면 출력: ___ / 호출에서 받은 값: ___ / 다음 계산의 결과: ___
이제 숫자 대신 이미지 객체를 같은 경로로 전달합니다. 내부에서 만든 값을 return으로 넘기고, 바깥 변수에 받은 뒤 다른 명령에 사용하는 구조는 같습니다. 이미지에서는 다음 작업이 덧셈 대신 화면 표시와 파일 저장이 됩니다.
6. 이미지 반환과 저장
완성한 이미지 한 장을 함수 밖에서 표시하고 PNG로 저장하려면 어떻게 받아야 할까요?
이미지 표시와 받은 값
두 경계가 있는 show_shape_row는 아직 마지막에 display(image)를 실행합니다. 이번에는 그 호출 결과를 saved에 받아 봅니다. 화면에 여섯 도형이 보인다는 사실과 saved가 사용할 수 있는 이미지인지를 따로 관찰합니다.
반환이 필요한 이유는 저장 명령이 언제나 함수 밖에 있어야 해서가 아닙니다. 이미지를 만든 후 화면에 보여 줄지, 다른 파일명으로 저장할지, 비교판에 넣을지를 호출한 쪽에서 결정하려는 목적 때문입니다. 하나의 완성 이미지를 여러 작업에 사용하려면 그 결과를 받을 수 있어야 합니다.
saved = show_shape_row(large_threshold=63, medium_threshold=50)
print("호출에서 받은 값:", saved)현재 함수의 반환값
이미지는 화면에 표시되지만 호출에서 받은 값은 None입니다. show_shape_row에는 이미지를 반환하는 줄이 없습니다. saved라는 이름을 이미지처럼 지었다고 이미지가 들어가는 것은 아니므로, 실제 받은 값을 확인해야 합니다.
이미지 생성과 반환
새 함수 이름은 create_shape_row로 정합니다. “도형 행을 보여 준다”에서 “도형 행 이미지를 만들어 돌려준다”로 역할이 바뀌었음을 이름에 반영한 것입니다. 두 경계의 매개변수, 여섯 크기와 그리기 규칙은 유지하고 마지막 display(image)를 return image로 바꿉니다.
함수 밖에서는 row = create_shape_row(...)로 결과를 받습니다. 그다음 display(row)로 표시하고 row.save(...)로 저장합니다. 반환된 결과를 받는 이름은 row가 아니어도 되지만, 표시와 저장에서 같은 이름을 사용하여 어떤 이미지를 대상으로 하는지 분명하게 만듭니다.

return image만으로 PNG 파일이 생기지는 않습니다. 저장한 뒤에도 row의 이미지는 그대로 사용할 수 있습니다.| 역할 | show_shape_row | create_shape_row |
|---|---|---|
| 입력 | 큰 경계·중간 경계 | 동일 |
| 제작 | 여섯 도형 이미지 | 동일 |
| 마지막 본문 | display(image) | return image |
| 호출에서 받은 값 | None | 완성 이미지 |
| 표시·저장 위치 | 본문에서 표시 | 호출한 쪽에서 결정 |
실습 6 · 이미지 반환
- 공통 원리
- 같은 생성 함수의 반환값을 서로 다른 변수에 받아 표시하고 PNG로 저장합니다.
- 유지 요소
- 함수 본문 · 캔버스·크기·좌표·팔레트
공통 정의의 마지막 display(image)를 return image로 바꿉니다. 반환은 for 밖·함수 안에 둡니다. 정의 실행 후 각 호출의 결과를 row, row_b, row_c에 받아 표시하고 서로 다른 PNG로 저장합니다. 반환을 고치지 않으면 그림이 보이더라도 저장에서 오류가 납니다.
공통 · 이미지 반환 정의
from PIL import Image, ImageDraw
from IPython.display import display
def create_shape_row(large_threshold, medium_threshold):
sizes = [30, 46, 64, 46, 60, 38]
palette = [(37, 62, 78), (111, 143, 126), (201, 111, 93)]
image = Image.new("RGB", (660, 220), (245, 240, 228))
draw = ImageDraw.Draw(image)
for i in range(6):
x = 50 + i * 100
y = 80
size = sizes[i]
if size >= large_threshold:
draw.rectangle((x, y, x + size, y + size), fill=palette[2])
elif size >= medium_threshold:
draw.ellipse((x, y, x + size, y + size), fill=palette[1])
else:
draw.ellipse((x, y, x + size, y + size), outline=palette[0], width=2)
display(image) # TODO: 표시 대신 결과를 돌려주세요.완성 코드
from PIL import Image, ImageDraw
from IPython.display import display
def create_shape_row(large_threshold, medium_threshold):
sizes = [30, 46, 64, 46, 60, 38]
palette = [(37, 62, 78), (111, 143, 126), (201, 111, 93)]
image = Image.new("RGB", (660, 220), (245, 240, 228))
draw = ImageDraw.Draw(image)
for i in range(6):
x = 50 + i * 100
y = 80
size = sizes[i]
if size >= large_threshold:
draw.rectangle((x, y, x + size, y + size), fill=palette[2])
elif size >= medium_threshold:
draw.ellipse((x, y, x + size, y + size), fill=palette[1])
else:
draw.ellipse((x, y, x + size, y + size), outline=palette[0], width=2)
return imageA 윤곽 중심
큰 경계 63·중간 경계 50으로 생성합니다.
실행 전 예상 형태·색·배치의 변화 ___
2-6A · 실습 코드
row = create_shape_row(large_threshold=63, medium_threshold=50)
display(row)
row.save("week06_return_practice.png")B 혼합 구성
A의 중간 경계만 40으로 바꿉니다.
실행 전 예상 형태·색·배치의 변화 ___
2-6B · 실습 코드
row_b = create_shape_row(large_threshold=63, medium_threshold=40)
display(row_b)
row_b.save("week06_return_b.png")C 원 구성
A의 큰 경계만 65로 바꿉니다.
실행 전 예상 형태·색·배치의 변화 ___
2-6C · 실습 코드
row_c = create_shape_row(large_threshold=65, medium_threshold=50)
display(row_c)
row_c.save("week06_return_c.png")A·B·C 결과 비교
비교 기록
- 공통 원리
- ___
- 바꾼 줄
- ___
- A와 B의 차이
- ___
- A와 C의 차이
- ___
반환과 저장
호출 결과의 할당
row = create_shape_row(...)를 읽을 때는 row라는 이름부터 이미지가 있다고 생각하지 않습니다. 먼저 오른쪽 함수 호출을 실행합니다. 함수 안에서 캔버스를 만들고 모든 도형을 그린 뒤 return image를 만나면 그 결과가 호출한 곳에 전달됩니다. 그다음 바깥 이름 row가 그 이미지를 가리키게 됩니다. 오른쪽 호출이 실패하면 이번 할당도 완료되지 않습니다.
함수 안의 image와 밖의 row는 이름이 다르지만 return으로 전달한 같은 이미지 객체를 가리킬 수 있습니다. 바깥에서 이름을 result_image로 정해도 결과를 사용할 수 있습니다. 반환은 내부 변수의 이름을 밖에 복사하는 규칙이 아니라 그 이름이 가리키는 값을 전달하는 규칙입니다.
| 위치 | 사용하는 이름 | 실행한 일 |
|---|---|---|
| 함수 안 | image | 새 캔버스에 도형 누적 |
| 반환문 | return image | 완성한 객체 전달 |
| 함수 밖 | row | 이번 호출에서 받은 결과 지칭 |
| 표시 | display(row) | 같은 객체의 모습 확인 |
| 저장 | row.save(...) | 같은 객체를 PNG로 기록 |
변수의 범위
1교시 기준 코드에서 만든 바깥 image가 노트북에 남아 있을 수 있습니다. 새 함수의 호출 뒤에 display(image)를 적으면 오류 없이 이전 그림이 보일 수도 있습니다. 실행에 성공했다는 사실만으로 이번 호출의 결과를 사용했다고 판단하지 않습니다. 현재 호출을 받은 row를 표시하는지 확인합니다.
함수 안에서 사용하는 image는 그 본문의 지역 이름입니다. 이름을 밖에서도 같게 맞추려고 전역 변수를 추가할 필요는 없습니다. 현재 호출 → 받은 변수 → 표시·저장의 경로를 따라 읽습니다. 새 세션에서 순서대로 실행하면 이전 셀의 우연한 변수에 의존하는 문제를 확인하기 쉽습니다.
설정 변경과 재호출
반환된 이미지는 호출을 실행한 결과입니다. 아래 시연은 실습 6에서 완성한 create_shape_row를 그대로 사용합니다. 큰 경계만 60에서 63으로 바꾸고 중간 경계는 50으로 유지합니다. 세 코드를 위에서부터 이어서 읽으면 설정 변경과 이미지 생성의 시점을 구분할 수 있습니다.
오른쪽의 create_shape_row가 실행될 때 demo_large의 현재 값 60을 읽어 전달합니다. 함수는 새 캔버스에 여섯 도형을 그려 반환하고, demo_row_before는 그 결과를 가리킵니다. 큰 경계 60에 해당하는 세 번째·다섯 번째 도형이 사각형입니다.
demo_large = 60
demo_row_before = create_shape_row(
large_threshold=demo_large, medium_threshold=50,
)
display(demo_row_before)
바뀐 것은 demo_large가 가리키는 숫자뿐입니다. 이 두 줄에는 create_shape_row 호출이 없으므로 도형을 다시 그리지 않습니다. display는 이미 완성된 demo_row_before를 보여 주며, 다섯 번째 도형도 여전히 사각형입니다.
demo_large = 63
display(demo_row_before)
다시 호출하는 순간 현재 값 63이 전달됩니다. 함수 안의 Image.new부터 실행되어 새 이미지가 만들어지고 demo_row_after로 반환됩니다. 크기 60인 다섯 번째 도형이 채운 원으로 바뀝니다. demo_row_before는 이전 이미지를 계속 가리키므로 두 결과를 함께 사용할 수 있습니다.
demo_row_after = create_shape_row(
large_threshold=demo_large, medium_threshold=50,
)
display(demo_row_after)
| 단계 | demo_large | 생성 함수 실행 | 표시한 결과 |
|---|---|---|---|
| 기존 결과 | 60 | 첫 호출 | 60 기준 이미지 |
| 설정 변경 | 63 | 없음 | 이전에 만든 60 기준 이미지 |
| 재호출 | 63 | 두 번째 호출 | 새로 만든 63 기준 이미지 |
설정 변수와 결과 변수는 역할이 다릅니다. demo_large는 다음 호출에 전달할 숫자이고, demo_row_before와 demo_row_after는 각 호출이 만든 이미지입니다. 새 이미지를 받아도 앞서 만든 이미지가 자동으로 교체되지는 않습니다. 단, 같은 결과 변수에 다시 대입하면 그 이름은 새 결과를 가리키므로, 비교할 두 결과에는 서로 다른 이름을 사용합니다.
설정만 바꾼 뒤 demo_row_before.save(...)를 실행하면 이전 이미지를 저장합니다. 새 결과를 저장할 때는 재호출로 받은 demo_row_after를 사용합니다. 3교시에서도 기준안 poster_a를 남겨 두고, 새로운 입력으로 만든 결과를 별도 변수에 받아 비교합니다.
표시·저장·다운로드
display(row)는 노트북 화면에서 이미지를 확인하는 작업입니다. row.save("week06_return_practice.png")는 현재 실행 환경의 파일로 저장하는 작업입니다. Colab 파일 목록에서 저장 파일을 찾고 내려받아야 자신의 컴퓨터에도 파일이 생깁니다. 화면에 그림이 있다고 로컬 컴퓨터에 PNG가 저장된 것은 아닙니다.
반환받은 row를 표시하고 저장하면 두 작업에 같은 결과를 사용합니다. 표시할 때와 저장할 때 생성기를 별도로 호출하면 설정이나 난수 상태에 따라 서로 다른 결과를 만들 수도 있습니다. 완성된 한 결과를 변수로 받아 두는 이유는 다음 작업들이 어떤 이미지를 대상으로 하는지 분명히 하기 위해서입니다.
with Image.open("week06_return_practice.png") as saved_row:
print("저장 파일 형식:", saved_row.format)
print("저장 이미지 크기:", saved_row.size)
display(saved_row.copy())저장 결과의 확인
노트북의 “저장 파일 확인” 셀은 방금 저장한 파일을 다시 엽니다. 이미지 생성기를 다시 호출하지 않으므로 지금 확인하는 것은 화면을 새로 만든 결과가 아니라 디스크에 기록된 PNG입니다. 파일 형식 PNG, 크기 (660, 220), 여섯 도형의 분류를 화면 결과와 대조합니다.
Image.open은 이미지를 파일에서 읽는 도구입니다. with 안에서 파일을 열어 사용하고, 표시에는 copy로 만든 이미지를 전달합니다. 이 셀은 제공된 대조 도구로 사용합니다. 입력과 반환의 학습에서는 저장에 사용한 row와 다시 연 파일의 내용이 일치하는지 설명하는 데 집중합니다.
파일 대조 기준
| 검사 | 정상 결과 | 다를 때 확인할 순서 |
|---|---|---|
| 파일 형식 | PNG | 파일명과 저장 명령 |
| 가로·세로 | 660 × 220 | 현재 row와 저장한 파일 |
| 분류와 개수 | 사각형 1·채움 1·윤곽 4 | 63/50 호출과 새 반환값 |
| 시각 결과 | 화면의 row와 동일 | 같은 변수를 표시·저장했는가 |
이 파일 대조는 실습 6에서 row로 받아 저장한 기준 결과를 확인합니다. 앞의 재호출 시연은 demo_row_before와 demo_row_after라는 별도 이름을 사용했으므로 row와 저장 파일은 같은 기준 결과를 유지합니다.
7. 오류 진단
호출 실패, 저장 실패, 미완성 이미지의 원인은 어떻게 구분할까요?
증상과 원인의 구분
함수 문제는 모두 같은 위치에서 발생하지 않습니다. 필요한 입력이 없으면 본문을 시작하기 전에 호출이 실패합니다. 반환값이 None이면 함수는 끝났더라도 받은 값을 사용하는 저장 단계에서 실패합니다. 너무 일찍 반환하면 오류 메시지 없이 미완성 이미지가 전달될 수 있습니다.
오류 메시지는 마지막 줄의 종류와 설명을 먼저 읽고 표시된 코드로 돌아갑니다. 저장 줄이 지목되었다고 언제나 저장 명령을 고치는 것은 아닙니다. 그 명령이 받은 값이 어디서 왔는지도 확인해야 합니다. 이 절에서는 증상 → 원인 → 한 곳 수정 → 같은 조건 재실행의 순서로 기록합니다.
실습 7 · 오류 진단
아래 세 오류 코드는 의도적으로 실패하도록 제공했습니다. 실행해 메시지를 확인한 뒤 같은 셀에서 고치고 재실행합니다. 오류가 있는 원본을 최종 실행 상태로 남기지 않습니다. 반환 위치 예제는 실행은 되지만 결과가 요구와 다른 경우이므로 두 종류를 구분합니다.
입력 누락
show_count는 shape_count 하나를 받아 모티프 수를 출력합니다. 그런데 호출 괄호가 비어 있습니다. 함수 이름이 맞는지만 확인하지 말고, 정의에 필요한 자리 수와 호출에서 제공한 값 수를 대응시킵니다. 본문의 print가 실행될지도 먼저 예상합니다.
def show_count(shape_count):
print("모티프 수:", shape_count)
show_count()입력 누락 메시지와 수정
TypeError: show_count() missing 1 required positional argument: 'shape_count'필수 입력 shape_count가 빠졌다는 TypeError입니다. 본문이 시작되기 전 호출에서 문제가 생겼으므로 print를 고칠 필요가 없습니다. show_count(3) 또는 show_count(shape_count=3)으로 수정하면 모티프 수: 3이 출력됩니다.
def show_count(shape_count):
print("모티프 수:", shape_count)
show_count(shape_count=3)키워드 이름
다음 호출에는 숫자 3이 있지만 count라는 키워드를 사용했습니다. 정의에 적힌 이름은 shape_count입니다. 값을 전달했다는 사실과 올바른 입력 자리에 전달했다는 사실을 구분합니다. 함수가 임의의 비슷한 이름을 자동으로 같은 뜻으로 해석하지는 않습니다.
def show_count(shape_count):
print("모티프 수:", shape_count)
show_count(count=3)키워드 메시지와 수정
TypeError: show_count() got an unexpected keyword argument 'count'정의가 받지 않는 키워드 count라는 TypeError입니다. 왼쪽 이름을 shape_count로 고치고 숫자 3은 유지합니다. 바깥 변수 chosen_count가 있다면 show_count(shape_count=chosen_count)로 연결합니다. 오른쪽 변수 이름과 왼쪽 매개변수 이름은 같을 필요가 없습니다.
반환값과 저장 오류
아래 함수는 작은 이미지를 실제로 만들지만 return None으로 끝납니다. 저장 줄에서 문제가 나더라도 이미지 크기나 파일 이름이 원인인지, 저장하려는 값 자체가 이미지가 아닌지 나누어 확인합니다. 반환해야 할 결과가 함수 안에 준비되어 있는지 찾아봅니다.
from PIL import Image
def create_small_image():
small_image = Image.new("RGB", (80, 80), (37, 62, 78))
return None
saved_image = create_small_image()
saved_image.save("week06_small_image.png")저장 오류와 수정
AttributeError: 'NoneType' object has no attribute 'save'NoneType에는 save가 없다는 AttributeError입니다. None은 빈 이미지가 아니므로 이미지 저장 기능을 사용할 수 없습니다. 반환을 return small_image로 바꾸면 저장 줄은 그대로 사용할 수 있습니다. 오류가 드러난 줄과 원인을 만든 줄이 다른 사례입니다.
from PIL import Image
def create_small_image():
small_image = Image.new("RGB", (80, 80), (37, 62, 78))
return small_image
saved_image = create_small_image()
saved_image.save("week06_small_image.png")반환 시점
return을 만나면 값을 전달한 뒤 현재 함수 호출을 끝냅니다. 그 아래에 같은 본문의 코드가 남아 있어도 이번 호출에서는 실행하지 않습니다. 따라서 반복문 안에 return이 있으면 반복만 잠깐 쉬는 것이 아니라 함수 전체가 끝납니다. 여섯 번 반복할 계획이 있어도 첫 반복에서 return을 만나면 두 번째 반복으로 넘어가지 않습니다.
1교시에서 display를 반복 안에 두면 중간 그림을 여러 번 표시하면서 반복은 계속되었습니다. return은 다릅니다. 처음 만나는 순간 함수 자체가 끝나므로 뒤의 도형이 그려지지 않습니다. 두 명령 모두 결과와 관련되어 있어도 실행 흐름에 주는 영향은 구분해야 합니다.

반환 위치 수정
세 크기의 합 140을 반환하려고 합니다. 현재 코드는 첫 값 30만 돌려줍니다. 계산식을 바꾸지 않고 return의 위치만 고쳐 전체 목록을 처리하게 하세요.
def sum_sizes():
total = 0
for size in [30, 46, 64]:
total = total + size
return total
print(sum_sizes())해설과 완성 코드
def sum_sizes():
total = 0
for size in [30, 46, 64]:
total = total + size
return total
print(sum_sizes())return을 for 밖인 네 칸 위치로 옮깁니다. total은 0 → 30 → 76 → 140 순서로 누적되고, 세 반복이 끝난 뒤 140을 전달합니다. 원래 코드에서는 total이 30이 된 순간 함수가 끝나 나머지 두 값을 더하지 않았습니다.
이 예제의 total은 이미지 생성의 image와 비슷한 누적 역할을 합니다. 숫자는 더하면서 결과를 만들고 캔버스는 도형을 그리면서 결과를 만듭니다. 전달할 내용이 완성되는 시점에 return을 두어야 한다는 원리는 같습니다.
이미지의 조기 반환
짧은 합계 실습에서 첫 값 30만 반환된 구조를 이미지에 적용해 봅니다. 다음 코드는 정상 함수와 같은 캔버스·입력·그리기 명령을 사용하지만 return의 들여쓰기가 한 단계 더 깊습니다. 실행 전에 도형 수, 이미지 크기, 표시 횟수를 따로 예상합니다.
return이 for 안에서 첫 도형 뒤에 실행되면 row.size는 정상일까요? 화면에 몇 개의 도형이 남고 어떤 도형이 처음 그려지는지 크기 목록으로 설명하세요.
from PIL import Image, ImageDraw
from IPython.display import display
def create_shape_row(large_threshold, medium_threshold):
sizes = [30, 46, 64, 46, 60, 38]
palette = [(37, 62, 78), (111, 143, 126), (201, 111, 93)]
image = Image.new("RGB", (660, 220), (245, 240, 228))
draw = ImageDraw.Draw(image)
for i in range(6):
x = 50 + i * 100
y = 80
size = sizes[i]
if size >= large_threshold:
draw.rectangle((x, y, x + size, y + size), fill=palette[2])
elif size >= medium_threshold:
draw.ellipse((x, y, x + size, y + size), fill=palette[1])
else:
draw.ellipse((x, y, x + size, y + size), outline=palette[0], width=2)
return image
row = create_shape_row(large_threshold=63, medium_threshold=50)
display(row)
print("반환 이미지 크기:", row.size)조기 반환 결과와 수정

크기 30인 첫 윤곽 원을 그린 뒤 함수가 끝납니다. 이미 캔버스는 만들어졌으므로 크기는 정상이고 반환값도 이미지입니다. 그릴 내용이 모두 들어 있는지는 별도로 확인해야 합니다. return을 for 밖·함수 안의 네 칸 위치로 옮깁니다.
from PIL import Image, ImageDraw
from IPython.display import display
def create_shape_row(large_threshold, medium_threshold):
sizes = [30, 46, 64, 46, 60, 38]
palette = [(37, 62, 78), (111, 143, 126), (201, 111, 93)]
image = Image.new("RGB", (660, 220), (245, 240, 228))
draw = ImageDraw.Draw(image)
for i in range(6):
x = 50 + i * 100
y = 80
size = sizes[i]
if size >= large_threshold:
draw.rectangle((x, y, x + size, y + size), fill=palette[2])
elif size >= medium_threshold:
draw.ellipse((x, y, x + size, y + size), fill=palette[1])
else:
draw.ellipse((x, y, x + size, y + size), outline=palette[0], width=2)
return image
row = create_shape_row(large_threshold=63, medium_threshold=50)
display(row)
print("반환 이미지 크기:", row.size)
이미지 크기와 완성도
잘못된 예제와 정상 예제 모두 처음에 660 × 220 캔버스를 만듭니다. 그래서 반환된 이미지의 size를 확인하면 두 경우 모두 같은 값이 나옵니다. 그러나 하나는 첫 윤곽 원만 있고 다른 하나는 여섯 도형이 있습니다. 크기 검사는 결과의 한 조건을 확인할 뿐, 그릴 내용이 모두 있는지까지 증명하지 않습니다.
반환 오류를 확인할 때는 값의 종류, 이미지 크기, 도형 수, 조건에 따른 표현을 순서대로 봅니다. None이면 값 전달부터 고치고, 이미지지만 한 도형뿐이면 반복과 반환의 위치를 봅니다. 여섯 도형이 있지만 분류가 틀리면 입력과 조건식을 봅니다. 관찰한 증상에 따라 확인할 코드의 범위를 좁힙니다.
| 명령과 위치 | 반복의 진행 | 관찰할 결과 |
|---|---|---|
| display · for 안 | 다음 반복으로 계속 | 중간 상태 여러 장 |
| return · for 안의 첫 도형 뒤 | 함수 종료 | 첫 도형만 있는 이미지 |
| return · for 밖·함수 안 | 반복 완료 후 종료 | 완성 이미지 |
| return 없이 본문 끝 | 반복은 완료할 수 있음 | 호출 결과 None |
모든 경우에 반복문 안의 return이 잘못된 것은 아닙니다. 첫 번째로 찾은 결과만 필요할 때는 일찍 반환하는 구조를 사용할 수 있습니다. 하지만 이번 함수의 약속은 여섯 도형이 완성된 이미지이므로 첫 도형 뒤 반환은 그 약속을 충족하지 못합니다. 문법 위치를 무조건 외우기보다 어떤 시점의 결과를 전달해야 하는지 판단해야 합니다.
최소 수정과 복구
입력 누락은 호출값을 추가하고, 키워드 오류는 이름을 맞추고, 반환값 오류는 돌려줄 값을 고칩니다. 조기 반환은 계산이나 색을 바꾸지 않고 반환 위치를 옮깁니다. 증상에 맞는 한 곳을 먼저 고쳐야 무엇이 원인이었는지 설명할 수 있습니다.
이미지의 조기 반환 셀도 직접 고친 뒤 정상 반환 복구 셀과 대조합니다. 복구 셀은 정상 create_shape_row와 63/50의 결과를 period2_row로 보존합니다. 마지막 상태가 첫 도형 하나인 오류 버전이면 이후 호출도 같은 오류를 반복하므로 다음 생성기 실습 전에 정상 상태를 남깁니다.
- 실습 7의 오류 셀을 실행하고 마지막 메시지를 읽은 뒤 같은 셀을 고칩니다.
- 짧은 합계 함수에서 반환 위치를 수정합니다.
- 이미지의 조기 반환을 고치고 정상 반환 결과를 보존합니다.
기록: 증상: ___ / 원인: ___ / 최소 수정: ___ / 재검증 결과: ___
8. 생성기 실습과 정리
다섯 입력으로 만든 포스터를 반환하여 다음 비교의 기준으로 남길 수 있을까요?
생성기의 입력 구조
마지막 실습은 5주차의 동심원 리듬에 입력과 반환을 연결하는 것입니다. 결과는 600 × 600 포스터 한 장입니다. 여섯 도형 행과 비교하면서 유지되는 구조와 확장되는 그리기 과정을 나누어 읽습니다.
| 구성 | 여섯 도형 행 | 동심원 포스터 |
|---|---|---|
| 크기와 위치 | 고정 목록·일정한 간격 | 난수로 선택 |
| 반복 수 | 6회 | shape_count |
| 큰·중간 경계 | 두 입력으로 분류 | 같은 방식으로 분류 |
| 표현 | 사각형·채움·윤곽 | 강조 동심원·고리·윤곽 |
| 반환 | 완성 행 이미지 | 완성 포스터 이미지 |
생성기의 실행 순서
여섯 도형 함수에서 포스터 생성기로 넘어가도 입력을 받고, 이미지 한 장을 만들고, 완성한 이미지를 반환하는 구조는 같습니다. 길어진 부분은 모티프의 크기·좌표·색을 고르고 동심원을 그리는 본문입니다.
한 번의 호출에서 캔버스 준비와 시드 설정은 각각 한 번, 모티프 제작은 shape_count번 실행됩니다. 반환은 모든 반복 뒤에 한 번 실행됩니다. shape_count가 56이면 포스터를 56장 반환하는 것이 아니라, 한 장에 모티프를 56개 그립니다.
시드·개수·팔레트는 각각 난수 설정·반복·색 선택에 쓰이고, 두 경계는 반복 안의 if·elif에 쓰입니다. 입력이 다섯 개라는 사실은 조절할 항목이 다섯 개라는 뜻이며, 이 함수가 반환하는 결과는 이미지 한 장입니다.
| 단계 | 코드 위치 | 실행 내용 |
|---|---|---|
| 입력 | create_poster(...) | 시드·개수·두 경계·팔레트를 이번 호출에 전달합니다. |
| 준비 | Image.new(...) / ImageDraw.Draw(image) | 새 캔버스 한 장과 그리기 도구를 만듭니다. |
| 난수 설정 | random.seed(seed_number) | 반복을 시작하기 전에 난수의 출발점을 한 번 정합니다. |
| 반복·분류 | for shape_number in range(shape_count): | 모티프 하나의 크기·좌표·색을 고르고, 두 경계로 표현을 분류하여 같은 캔버스에 그립니다. |
| 반환 | return image | 반복이 모두 끝난 뒤 완성된 이미지 한 장을 전달합니다. |
| 결과 사용 | poster_a = create_poster(...) | 호출이 끝나면 반환된 이미지를 poster_a로 받아 표시·저장에 사용합니다. |
본문의 확장
행 이미지에서는 크기 목록의 다음 값을 읽고 정해진 간격에 그렸습니다. 포스터에서는 그 자리에 난수로 크기·좌표·기본색을 고르는 과정이 들어갑니다. 이어지는 if·elif·else는 같은 분류 역할을 맡되, 사각형·채운 원·윤곽 원 대신 동심원·고리·윤곽 원을 그립니다.
그리기 명령이 길어져도 캔버스는 반복 밖에서 준비하고, 반환도 반복 뒤에 둡니다. 반복 안의 본문은 지금 처리하는 모티프 하나를 같은 이미지에 추가하는 부분입니다. 아래 코드에서는 제공된 그리기 과정을 유지하고, 시드 입력·반복 수·완성 이미지의 반환이 연결되는 위치를 확인합니다.
다섯 입력의 대응
기준안 A는 시드 73, 모티프 56개, 중간 경계 42, 큰 경계 64와 제공된 팔레트를 사용합니다. 호출의 오른쪽에는 이 값을 보관한 seed_a, shape_count_a, medium_a, large_a, palette_a가 들어갑니다. 왼쪽은 정의의 입력 이름이고 오른쪽은 설정값 변수라는 구분을 다시 적용합니다.
| 매개변수 | A의 값 | 사용 위치 | 확인할 역할 |
|---|---|---|---|
| seed_number | 73 | random.seed(seed_number) | 반복 전 난수 초기화 |
| shape_count | 56 | range(shape_count) | 모티프의 반복 수 |
| medium_threshold | 42 | elif의 비교식 | 중간 표현의 시작 |
| large_threshold | 64 | if의 비교식 | 강조 표현의 시작 |
| palette | RGB 다섯 색 | 배경·기본색·강조색 | 색의 역할 |
시드 설정과 재현성
random.seed(seed_number)는 이후 난수 선택을 재현할 수 있는 출발점을 정합니다. 같은 실행 환경에서 같은 함수, 같은 입력, 같은 순서의 난수 호출을 사용하면 같은 결과를 확인할 수 있습니다. 시드만 같으면 어떤 코드에서도 항상 같은 이미지가 생긴다는 뜻은 아닙니다. 난수를 뽑는 순서나 횟수가 달라지면 같은 숫자도 다른 역할에 쓰일 수 있습니다.
이번 생성기의 순서는 크기 → x → y → 기본색입니다. 시드를 반복 안에서 매번 다시 설정하면 매번 같은 출발점으로 돌아가 모티프가 같은 위치에 겹칠 수 있습니다. 반복 전에 한 번 설정하면 하나의 난수 흐름에서 서로 다른 모티프를 차례로 선택합니다. 5주차의 난수 실험이 함수 본문에서도 그대로 적용됩니다.
모티프 수와 도형 수
shape_count는 반복하는 모티프의 수입니다. 큰 구간의 모티프 하나는 바깥 원, 배경색으로 덮는 안쪽 원, 중심 원을 포함할 수 있습니다. 따라서 shape_count가 56이라고 해서 눈에 보이는 원 테두리도 정확히 56개인 것은 아닙니다. 서로 겹친 모티프 때문에 일부가 가려질 수도 있습니다. 입력의 의미를 실제 구현의 단위와 맞추어 읽습니다.
개수를 늘리면 더 많은 모티프가 그려지므로 밀도가 달라질 수 있지만, 모든 영역이 균일하게 채워진다고 보장되지는 않습니다. 무작위 위치와 겹침이 함께 작용하기 때문입니다. “개수를 늘렸으니 조밀해졌다”로 단정하기보다 어느 영역의 겹침과 여백이 어떻게 달라졌는지 결과에서 확인합니다.
경계값과 시각 표현
생성기는 먼저 20부터 80 사이의 크기를 고릅니다. 큰 경계 64 이상은 강조 동심원, 중간 경계 42 이상 64 미만은 두꺼운 고리, 42 미만은 얇은 윤곽 원으로 표현합니다. 경계가 크기를 새로 뽑는 것은 아닙니다. 이미 선택된 크기에 어느 표현을 적용할지 결정합니다. 행 이미지에서 사각형·채운 원·윤곽 원을 나누었던 규칙의 역할과 같습니다.
크기 60은 중간 구간의 두꺼운 고리, 64는 큰 구간의 강조 동심원입니다. 경계에 도달한 순간 표현을 바꾸는 규칙은 행 이미지와 같습니다. 한 모티프를 구성하는 그리기 명령이 늘어나더라도 먼저 고른 크기에 한 종류의 표현을 적용한다는 점을 확인합니다.
크기와 좌표 범위
600 × 600 캔버스의 여백이 40이고 모티프 크기가 size라면 왼쪽 x의 최대값은 600 - 40 - size입니다. 모티프의 오른쪽 끝 x + size가 560을 넘지 않도록 하는 관계입니다. y도 같은 방식입니다. 크기를 먼저 고른 다음 좌표 범위를 정해야 큰 모티프가 가장자리 밖으로 잘리는 일을 줄일 수 있습니다.
크기가 80이면 x는 40부터 480까지, 크기가 20이면 40부터 540까지 선택할 수 있습니다. 같은 여백을 유지해도 모티프 크기에 따라 시작할 수 있는 위치의 범위가 달라집니다. 함수로 옮겼다고 이 관계를 없애지 않습니다. 입력과 반환을 추가하면서 이전에 만든 안전한 배치 규칙도 보존합니다.
팔레트의 색상 역할
이 예제의 palette[0]은 배경과 안쪽을 비워 보이게 하는 색입니다. palette[1]부터 palette[3]까지는 기본색 후보이며 palette[3]은 큰 모티프의 강조색으로도 쓰입니다. 팔레트가 단순히 색 다섯 개의 목록인 것에서 한 단계 더 나아가, 각 위치가 어떤 시각적 역할을 맡는지 읽습니다.
palette[4]는 공통 팔레트 형식을 유지하기 위해 보관하지만 현재 동심원 코드의 픽셀 생성에는 사용하지 않습니다. 따라서 다섯 번째 색만 바꾸어도 결과는 같을 수 있습니다. “목록에 값이 있다”와 “그 값이 본문에서 사용된다”를 구분하는 또 하나의 예입니다. 이번 비교에서는 전체 팔레트를 유지하여 색 변경이 실험에 섞이지 않게 합니다.
실습 8 · 생성기 완성
- 공통 원리
- 다섯 입력을 받는 같은 생성 함수에서 팔레트만 바꾸어 동심원 포스터를 만듭니다.
- 유지 요소
- 함수 본문 · 시드 73 · 모티프 56개 · 경계 42/64 · 크기와 위치
공통 정의의 random.seed(0), range(1), return None을 각각 seed_number, shape_count, image에 연결합니다. 반환은 for 밖·함수 안에 둡니다. 다음 셀에서 기준 입력을 준비한 뒤 A·B·C 호출을 실행합니다. B·C는 팔레트만 바꾸고 새 변수에 받아 기준 poster_a를 보관합니다.
공통 1 · 생성 함수 정의
from PIL import Image, ImageDraw
from IPython.display import display
import random
def create_poster(seed_number, shape_count, medium_threshold, large_threshold, palette):
canvas_width = 600
canvas_height = 600
min_size = 20
max_size = 80
margin = 40
background_color = palette[0]
base_palette = [palette[1], palette[2], palette[3]]
accent_color = palette[3]
image = Image.new("RGB", (canvas_width, canvas_height), background_color)
draw = ImageDraw.Draw(image)
random.seed(0) # TODO: 시드 입력 연결
for shape_number in range(1):
size = random.randint(min_size, max_size)
x = random.randint(margin, canvas_width - margin - size)
y = random.randint(margin, canvas_height - margin - size)
base_color = random.choice(base_palette)
if size >= large_threshold:
draw.ellipse((x, y, x + size, y + size), fill=accent_color)
inset = size // 5
draw.ellipse((x + inset, y + inset, x + size - inset, y + size - inset), fill=background_color)
inset = size // 3
draw.ellipse((x + inset, y + inset, x + size - inset, y + size - inset), fill=base_color)
elif size >= medium_threshold:
draw.ellipse((x, y, x + size, y + size), fill=base_color)
draw.ellipse((x + 6, y + 6, x + size - 6, y + size - 6), fill=background_color)
else:
draw.ellipse((x, y, x + size, y + size), outline=base_color, width=2)
return None # TODO: 완성 이미지 반환완성 코드
from PIL import Image, ImageDraw
from IPython.display import display
import random
def create_poster(seed_number, shape_count, medium_threshold, large_threshold, palette):
canvas_width = 600
canvas_height = 600
min_size = 20
max_size = 80
margin = 40
background_color = palette[0]
base_palette = [palette[1], palette[2], palette[3]]
accent_color = palette[3]
image = Image.new("RGB", (canvas_width, canvas_height), background_color)
draw = ImageDraw.Draw(image)
random.seed(seed_number)
for shape_number in range(shape_count):
size = random.randint(min_size, max_size)
x = random.randint(margin, canvas_width - margin - size)
y = random.randint(margin, canvas_height - margin - size)
base_color = random.choice(base_palette)
if size >= large_threshold:
draw.ellipse((x, y, x + size, y + size), fill=accent_color)
inset = size // 5
draw.ellipse((x + inset, y + inset, x + size - inset, y + size - inset), fill=background_color)
inset = size // 3
draw.ellipse((x + inset, y + inset, x + size - inset, y + size - inset), fill=base_color)
elif size >= medium_threshold:
draw.ellipse((x, y, x + size, y + size), fill=base_color)
draw.ellipse((x + 6, y + 6, x + size - 6, y + size - 6), fill=background_color)
else:
draw.ellipse((x, y, x + size, y + size), outline=base_color, width=2)
return image공통 2 · 기준 입력
seed_a = 73
shape_count_a = 56
medium_a = 42
large_a = 64
palette_a = [
(24, 29, 58), (230, 232, 213), (119, 136, 208),
(215, 242, 93), (174, 179, 217),
]
variant_a = (seed_a, shape_count_a, medium_a, large_a, palette_a)A 밤의 색상
기준 팔레트를 그대로 전달합니다.
실행 전 예상 형태·색·배치의 변화 ___
2-8A · 실습 코드
poster_a = create_poster(
seed_number=seed_a, shape_count=shape_count_a,
medium_threshold=medium_a, large_threshold=large_a, palette=palette_a,
)
display(poster_a)
assert isinstance(poster_a, Image.Image) and poster_a.size == (600, 600), "완성 이미지를 반환하세요."
print("2교시 완료: 이 함수와 기준안 A를 3교시에서 이어서 사용합니다.")B 종이 색상
팔레트만 밝은 배경·남색·세이지·산호색 계열로 바꿉니다.
실행 전 예상 형태·색·배치의 변화 ___
2-8B · 실습 코드
study_palette_b = [(245, 240, 228), (37, 62, 78), (111, 143, 126), (201, 111, 93), (174, 179, 217)]
study_poster_b = create_poster(
seed_number=seed_a, shape_count=shape_count_a,
medium_threshold=medium_a, large_threshold=large_a, palette=study_palette_b,
)
display(study_poster_b)C 무채색
팔레트만 회색과 검정 계열로 바꿉니다.
실행 전 예상 형태·색·배치의 변화 ___
2-8C · 실습 코드
study_palette_c = [(245, 245, 245), (90, 90, 90), (160, 160, 160), (30, 30, 30), (200, 200, 200)]
study_poster_c = create_poster(
seed_number=seed_a, shape_count=shape_count_a,
medium_threshold=medium_a, large_threshold=large_a, palette=study_palette_c,
)
display(study_poster_c)A·B·C 결과 비교
비교 기록
- 공통 원리
- ___
- 바꾼 줄
- ___
- A와 B의 차이
- ___
- A와 C의 차이
- ___
생성기와 기준안 A
from PIL import Image, ImageDraw
from IPython.display import display
import random
def create_poster(seed_number, shape_count, medium_threshold, large_threshold, palette):
canvas_width = 600
canvas_height = 600
min_size = 20
max_size = 80
margin = 40
background_color = palette[0]
base_palette = [palette[1], palette[2], palette[3]]
accent_color = palette[3]
image = Image.new("RGB", (canvas_width, canvas_height), background_color)
draw = ImageDraw.Draw(image)
random.seed(seed_number)
for shape_number in range(shape_count):
size = random.randint(min_size, max_size)
x = random.randint(margin, canvas_width - margin - size)
y = random.randint(margin, canvas_height - margin - size)
base_color = random.choice(base_palette)
if size >= large_threshold:
draw.ellipse((x, y, x + size, y + size), fill=accent_color)
inset = size // 5
draw.ellipse((x + inset, y + inset, x + size - inset, y + size - inset), fill=background_color)
inset = size // 3
draw.ellipse((x + inset, y + inset, x + size - inset, y + size - inset), fill=base_color)
elif size >= medium_threshold:
draw.ellipse((x, y, x + size, y + size), fill=base_color)
draw.ellipse((x + 6, y + 6, x + size - 6, y + size - 6), fill=background_color)
else:
draw.ellipse((x, y, x + size, y + size), outline=base_color, width=2)
return image
seed_a = 73
shape_count_a = 56
medium_a = 42
large_a = 64
palette_a = [
(24, 29, 58), (230, 232, 213), (119, 136, 208),
(215, 242, 93), (174, 179, 217),
]
variant_a = (seed_a, shape_count_a, medium_a, large_a, palette_a)
poster_a = create_poster(
seed_number=seed_a, shape_count=shape_count_a,
medium_threshold=medium_a, large_threshold=large_a, palette=palette_a,
)
display(poster_a)
assert isinstance(poster_a, Image.Image) and poster_a.size == (600, 600), "완성 이미지를 반환하세요."
print("2교시 완료: 이 함수와 기준안 A를 3교시에서 이어서 사용합니다.")기준안 A의 검증
정상 반환은 이미지 객체를 받는 것부터 확인합니다. 다음으로 크기 600 × 600과 여러 모티프의 구성을 확인합니다. 마지막으로 다섯 입력의 사용 위치를 읽습니다. 모티프가 겹쳐 있으므로 눈으로 원을 56개 세는 것만으로 반복 횟수를 검증할 수는 없습니다. 코드의 range와 결과를 함께 봅니다.
팔레트는 목록 전체가 입력으로 전달되지만 각 항목의 사용 여부는 본문의 색 선택에 따라 결정됩니다. 이름이 붙어 있다는 사실만으로 모든 색이 화면에 나타나야 한다고 판단하지 않습니다. 기준안에서는 제공된 색 목록과 역할을 그대로 유지하여 다음 비교의 기준을 분명히 합니다.
| 확인 항목 | 정상 상태 | 근거 |
|---|---|---|
| 함수 입력 | 다섯 매개변수 | 정의와 키워드 호출 대응 |
| 시드 | 반복 전 seed_number 사용 | 초기화 위치 |
| 개수 | shape_count만큼 처리 | range의 입력 연결 |
| 반환 | 반복 뒤 완성 image | return의 값과 들여쓰기 |
| 기준안 | poster_a에 600 × 600 이미지 | 반환받은 결과와 화면 |
| 앞 실습 | 정상 create_shape_row와 period2_row | 여섯 도형의 63/50 결과 |
학습 내용 점검
이번 시간의 흐름은 입력 → 처리 → 반환 → 사용입니다. 정의의 괄호는 사용할 설정을 받고 본문은 그 설정으로 결과를 만듭니다. return은 완성한 결과를 전달하고 현재 호출을 끝냅니다. 호출한 코드는 받은 결과를 변수로 가리키며 표시·저장에 사용합니다.
“큰 경계 63과 중간 경계 50을 전달하여 같은 여섯 크기를 분류하고, 완성 이미지를 row로 받아 PNG로 저장했다”처럼 자신의 코드와 결과로 설명합니다. 포스터도 다섯 값을 전달하여 모티프를 만들고 poster_a로 받아 사용하는 같은 문장 구조로 설명할 수 있어야 합니다.
매개변수가 본문에서 쓰이지 않는 경우, 반환값이 None인 경우, 반환이 너무 이른 경우는 각각 어떤 결과를 만들까요? 이번 실습에서 한 사례씩 찾아 원인 줄과 수정 내용을 설명하세요.
3교시 연결
이번 실습 8의 B·C는 색상 비교용이며 study_poster_b와 study_poster_c에 보관했습니다. 시드·개수·경계를 바꾸는 B·C의 포스터 비교는 3교시에서 진행합니다. 완성한 create_poster와 기준안 poster_a를 같은 노트북에 남깁니다. A의 설정은 시드 73·개수 56·중간 경계 42·큰 경계 64이며 palette_a도 유지합니다.
B는 시드만 바꾸고, C는 개수 또는 큰 경계 중 하나를 바꿉니다. 바깥 설정값을 올바른 키워드에 연결하고 반환받은 B·C를 A와 비교합니다. 1교시에서 작업의 범위를 정하고 2교시에서 입력과 반환을 연결했으므로, 3교시는 설정 변화의 원인을 해석하고 결과를 저장하는 실습으로 이어집니다.
노트북에는 앞 실습의 작성 내용과 결과·기록을 보존합니다. 오류 진단 셀까지 수정된 상태인지 확인하고 저장합니다. 마지막에 남긴 정상 함수와 기준안이 다음 교시의 출발점이며, 새 세션에서는 공통 준비부터 완성한 순서대로 실행하여 같은 상태를 복구합니다.
참고 자료
- Python 공식 튜토리얼: 함수 정의정의, 호출, 인자, 지역 변수와 반환값
- Python 공식 튜토리얼: 키워드 인자이름으로 입력값 연결
- Python 언어 참조: return호출을 종료하고 결과 전달
- PEP 8: 함수 이름소문자와 밑줄로 이름 작성
- Pillow 공식 문서: Image이미지 객체와 PNG 저장











