[6일차] 제어문(반복문), 사용자 정의 함수(함수 생성, 전역변수, 지역변수)

2026. 5. 27. 17:19KDT/1. Python

# for문 (앞 포스터 이어서)

dic1 = {'no':1, 'userid':'apple', 'name':'김사과', 'hp':'010-1111-1111'}

for i in dic1:
    print(i, end=' ') 		# no userid name hp (키만 복사)
print()

for i in dic1.keys():       	# 키만 모여있는 객체가 존재
    print(i, end=' ') 		# no userid name hp (키만 복사)
print()

for i in dic1.values():
    print(i, end=' ') 		# 1 apple 김사과 010-1111-1111 (값만 복사)
print()

for i in dic1:
    print(dic1[i], end=' ')		# 1 apple 김사과 010-1111-1111 
print()

for i in dic1:
    print(dic1.get(i), end=' ')
    # 1 apple 김사과 010-1111-1111 
    # key를 통해 value를 가져온다 (없으면 None. None이 싫으면 대체 문자 설정 가능 -> 5일차 dict 함수와 메서드 참고)
print()

for i in dic1.items():
    print(i)
	# ('no', 1)
	# ('userid', 'apple')
	# ('name', '김사과')
	# ('hp', '010-1111-1111')

for key, value in dic1.items():
    print(key, value)
	# no 1
	# userid apple
	# name 김사과
	# hp 010-1111-1111

# range() 함수

  • 순차적인 정수의 시퀀스(순서가 있는 시퀀스 객체)를 생성하는 데 사용되는 내장 함수
  • 주로 for문과 함께 사용
  • 일정한 범위 내에서 반복 작업을 수행할 때 유용하게 활용
  • range() 함수의 반환값은 range 객체로, 실제 정수 시퀀스를 메모리에 저장하지 않고 범위 정보만 가지고 있음
    따라서 필요한 정수 시퀀스는 실제 사용 시에 생성
range([start], stop, [step])

start (선택 사항): 순차적인 범위의 시작 값을 지정합니다. 기본값은 0입니다.
stop: 순차적인 범위의 끝 값을 지정합니다. 생성된 시퀀스는 stop 값 직전까지의 정수를 포함합니다.
step (선택 사항): 순차적인 값을 증가시키는 간격을 지정합니다. 기본값은 1입니다.

# enumerate() 함수

  • 반복문을 사용할 때 (인덱스, 값)의 튜플을 반환하는 iterator 생성
    (인덱스와 값을 함께 가져오는 내장 함수)
  • 주로 for문과 사용
  • 반복문 내에서 순서를 추적하면서 값과 해당 값의 인덱스를 함께 사용할 때 유용
  • iterable(반복 가능 객체) : 리스트, 튜플, 딕셔너리, 문자열 같은 컬렉션 자료형은 반복 가능한 객체로 이터레이터 생성 가능
enumerate(iterable, [start=0])
for e in enumerate('hello', 0):
	print(e)
    
# (0, 'h')
# (1, 'e')
# (2, 'l')
# (3, 'l')
# (4, 'o')
for e in enumerate('hello', 2):
    print(e)

# (2, 'h')
# (3, 'e')
# (4, 'l')
# (5, 'l')
# (6, 'o')
list1 = [10, 20, 30, 40]

for e in enumerate(list1):
	print(e)
    
# (0, 10)
# (1, 20)
# (2, 30)
# (3, 40)
list1 = [10, 20, 30, 40]

# (0, 10)
# i, v = (0, 10)
for i, v in enumerate(list1):
    print(f'인덱스:{i}, 값:{v}')

# 인덱스:0, 값:10
# 인덱스:1, 값:20
# 인덱스:2, 값:30
# 인덱스:3, 값:40

# 이터러블과 이터레이터⭐

1. Iterable (이터러블)

  • for문처럼 반복문에서 사용할 수 있는 모든 객체
  • 이터러블한 객체는 for item in 객체 : 구조에서 사용 가능
  • 모든 순서 있는 컬렉션(리스트, 튜플, 딕셔너리, 문자열)은  이터러블
  • 하지만 모든 이터러블이 순서 있는 것은 아님
    (set, dict.keys()는 반복되지만 인덱스가 없음)

2. Iterator (이터레이터)

  • 이터러블 객체는 iter() 함수를 사용해 이터레이터로 변경 가능
  • 또한 next() 사용하여 값을 하나씩 꺼낼 수 있음
  • iterable 생성 -> iter() 사용하여 iterator 로 변경 (안의 값을 순서대로 뽑을 수 있는 상태로 만듦) -> next() 사용하여 딱 한 가지 값을 꺼내 사용 -> next() 반복 사용하면 다음 값을 꺼내 사용 가능 
    이 과정을 for문이 해주는 것임!
numbers = [10, 20, 30]
# iterable -> iterator로 변경
iterator = iter(numbers)

# next()로 값을 하나씩 꺼냄
print(next(iterator))		# 10
print(next(iterator))		# 20
print(next(iterator))		# 30
# print(next(iterator)) -> 여기서 더 꺼내면 StopIteration 예외 발생. for문에는 오류처리기능이 있음

# zip() 함수

  • 여러 개의 iterable 객체를 병렬적으로 묶어주는 내장 함수
  • 각 객체로부터 순서대로 하나의 요소씩 가져와 튜플로 묶어주는 iterator 반환
  • 주로 여러 개의 리스트나 튜플을 병렬적으로 처리하고자 할 때 사용
zip(iterable1, iterable2, ...)
# range() 안의 숫자가 좋지 않은 코드. 특정 숫자가 코드에 박혀있는 것은 안좋은 코드

li1 = [10, 20, 30]
li2 = ['apple', 'banana', 'orange']

for i in range(0, 3):
    print(li1[i], li2[i])
    
# 10 apple
# 20 banana
# 30 orange
# len() 을 사용하는 것도 좋다
# 하지만 li1, li2의 길이가 달라지게 되면 IndexError 발생가능
# li1의 len에 의존하고 있으므로 안좋은 코드

li1 = [10, 20, 30]
li2 = ['apple', 'banana', 'orange']

for i in range(len(li1)):
    print((li1[i], li2[i]))
    
# (10, 'apple')
# (20, 'banana')
# (30, 'orange')
# li라는 변수 1개만 쓰면 패킹돼서 튜플로 반환

li1 = [10, 20, 30]
li2 = ['apple', 'banana', 'orange']

for li in zip(li1, li2):
	print(li)

# (10, 'apple')
# (20, 'banana')
# (30, 'orange')

# continue

  • 반복문 안에서 사용
  • 현재 반복을 즉시 건너뛰고 다음 반복으로 넘어가도록 하는 제어문
  • 예를 들어, 리스트를 순회하면서 특정 값만 건너뛰고 싶을 때 if문과 함께 사용
  • 불필요한 코드 실행 피하기 가능

# break

  • 반복문 안에서 반복을 즉시 종료시키는 제어문
  • break 실행 시 반복문의 조건과 상관없이 해당 반복문을 완전히 빠져나옴
  • 주로 특정 조건을 만족하면 더 이상 반복을 진행할 필요가 없을 때 사용
  • 불필요한 반복을 줄일 수 있음

# 중첩 반복문

1. 중첩 for문

# 2단부터 9단까지 구구단을 출력하는 프로그램을 작성

# for 문
for i in range(2, 10):
    print(f"{i}단")
    for j in range(1, 10):
        print(f"{i} * {j} = {i * j}")
    print()

 

2. 중첩 while문

# while 문
i = 2
j = 1

while i <= 9:
    print(f'{i}단')
    while j <= 9:
        print(f'{i} * {j} = {i*j}')
        j += 1
    i += 1
    j = 1
    print()

# Comprehension (컴프리헨션)

  • 리스트, 세트, 딕셔너리 등의 컬렉션을 반복문과 조건문을 사용하여 간단하게 생성하거나 변형하는 방법 중 하나

1. list comprehension (리스트 컴프리헨션)

n = 10

result = [0 for i in range(n)]
print(result)		# [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

result = [i for i in range(n)]
print(result)		# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

=======================================================

li = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]

result = [n for n in li]
print(result)		# [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]

result = [n*2 for n in li]
print(result)		# [20, 40, 60, 80, 100, 120, 140, 160, 180, 200]

=======================================================

li = ['apple', 'banana', 'orange', 'melon']

result = [len(str1) for str1 in li]
print(result)		# [5, 6, 6, 5]
# if문이 for문의 오른쪽에 있는 경우
result = [n for n in range(10) if n % 2 == 0]
print(result)		# [0, 2, 4, 6, 8]

# for문에서 0을 꺼내고, if문으로 확인 후, True이면 return
# if문이 for문의 왼쪽에 있는 경우
# 양수는 리스트에 그대로 저장하고, 음수는 0으로 변환해서 저장하기
li = [-1, 0, -4, 24, 5, -10, 2, 20]

result = [n if n>0 else 0 for n in li]
print(result)		# [0, 0, 0, 24, 5, 0, 2, 20]
	
# for문에서 -1을 꺼내고, if문에서 조건이 맞으면 -1 return, 틀리면 else 실행
li = []

# 다중 for문
for i in range(1, 4):
    for j in range(1, 3):
        li.append(i * j)
print(li)		# [1, 2, 2, 4, 3, 6]

# list comprehension
li = [i*j for i in range(1, 4) for j in range(1, 3)]
print(li)		# [1, 2, 2, 4, 3, 6]

# 다중 for문과 list comprehension 중 다중 for문이 가독성 측면에서 나음
# 이런 comprehension은 잘 안씀

 

2. set comprehension (세트 컴프리헨션)

li = [1, 2, 3, 4, 5, 2, 3, 4]
unique_numbers = set(li)
print(unique_numbers)		# {1, 2, 3, 4, 5}

li = [1, 2, 3, 4, 5, 2, 3, 4]
unique_numbers = {x for x in li}
print(unique_numbers)		# {1, 2, 3, 4, 5}

 

3. dictionary comprehension (딕셔너리 컴프리헨션)

names = ['apple', 'banana', 'orange']
name_lengths = {name:len(name) for name in names}
print(name_lengths)		# {'apple': 5, 'banana': 6, 'orange': 6}

# 사용자 정의 함수

  • 사용자가 특정 작업을 수행하기 위해 직접 작성한 함수
  • 파이썬에는 많은 내장 함수가 있지만 요구사항에 맞게 동작하는 함수를 만들어야할 때가 있음

함수

1. 함수의 동작

  1. 함수 객체를 메모리에 생성
  2. 그 함수 객체에 함수명 이름을 붙임
def 함수명(매개변수1, 매개변수2, ...):
    # 함수 내용
    return 결과값
def hello():
    print("안녕하세요")

a = hello		# 함수를 복사한 것이 아니라, 같은 함수 객체를 참조하는 이름을 하나 더 생성
a()
hello()

 

2. 함수 객체

function object
├── 함수 이름
├── 매개변수 정보
├── 실행할 코드 정보
├── 기본값 정보
├── 전역 네임스페이스 정보
└── 클로저 정보 : 함수 안에 함수가 있는 경우

 

3. 함수 호출 시 메모리에서 일어나는 일⭐

def add(a, b):
    result = a + b
    return result

x = add(3, 5)
  1. add 이름이 가리키는 함수 객체 찾음
  2. 함수 실행을 위한 '새 공간' 생성
  3. 매개변수 a, b에 값 연결
  4. 함수 본문 실행
  5. return 값 반환
  6. 함수 실행 공간 제거
  • 스택 프레임(stack frame) 또는 함수 호출 프레임
    (= 이때 맨들어지는 '새 공간')
    • 함수 호출 시 생성되는 독립적 실행 공간
    • 해당 함수의 매개변수, 지연변수, 반환 주소, 실행 상태 (어디까지 실행했는지 등) 저장하는 구조
    • 이 프레임을 stack 형태로 관리 -> 함수가 호출될 때 쌓이고 끝나면 제거
    • 이를 통해 변수 충돌 없이 안전하게 실행

# 함수 만들기

1. 매개변수x 반환값x

def func1():
    print('처음으로 만드는 함수!')
    
func1()
func1()
print()

for i in range(5):
    func1()

 

2. 매개변수o 반환값x

def func3(start, end):
    sum = 0
    for i in range(start, end+1):
        sum += i
    print(f'{start}부터 {end}까지의 합: {sum}')

func3(1, 10)
func3(1, 100)

 

3. 매개변수x 반환값o

# ⭐
# 함수 스택을 느낄 수 있는 코드 (LIFO)
def funcA():
    print("A 시작")
    result = funcB()
    print("A 끝")
    return result

def funcB():
    print("B 시작")
    result = funcC()
    print("B 끝")
    return result

def funcC():
    print("C 실행")
    return 100

# 실행
value = funcA()
print("최종 결과:", value)

 

4. 매개변수o 반환값o

def func5(num1, num2):
    sum = num1 + num2
    return sum

print(func5(10, 5))
temp = func5(4, 3)
print(temp)

# print(func5(10)) # TypeError: func5() missing 1 required positional argument: 'num2'

 

4-1. 기본값이 설정된 매개변수

def func6(num1=0, num2=0):
    sum = num1 + num2
    return sum
    
print(func6())
print(func6(10))
print(func6(10, 3))
# print(func6(, 3))   # SyntaxError: invalid syntax
# print(func6(None, 3))   # TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
print(func6(num2=3))    # num2만 보내고 싶을 경우 지정해서 매개변수 넣어주면 됨
def add_item(item, items=[]):
    items.append(item)
    return items

print(add_item("사과"))
print(add_item("바나나"))
print(add_item("오렌지"))

# ['사과']
# ['사과', '바나나']
# ['사과', '바나나', '오렌지']
  • items=[] 처럼 컬렉션 초기화를 매개변수에 하지 마라
  • 함수가 호출될 때마다 새로 만들어지는 것이 아니라 함수 객체가 만들어질 때 한 번 생성
  • 모든 호출이 같은 리스트 공유 (items는 stack이 아닌 heap에 올라가게됨)
  • 함수는 무조건 실행할 때마다 처음부터 실행되는 것처럼 만들기 (과거에 얽히면 독립적 실행이 어려움)
  • print 결과에 사과, 바나나가 누적되는 이유는 items가 살아있기 때문
# 안전하게 사용하는 방법
def add_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

print(add_item("사과"))
print(add_item("바나나"))
print(add_item("오렌지"))

# ['사과']
# ['바나나']
# ['오렌지']

 

# None

  • 값이 존재하지 않음을 나타내는 특별한 객체
  • 모든 None 비교 시 == 대신 is 사용 권장
  • 함수에서 return 명시하지 않으면 자동으로 None
  • False로 평가되지만 (), "", [] 등과는 의미적으로 구분되며,
    연산 대상이 아니기 때문에 산술 연산에 사용 불가

5. 가변 매개변수

def func7(*args):
    return args
    
print(func7())			# ()
print(func7(10))		# (10,)
print(func7(10, 30, 50))	#(10, 30, 50)
def func8(a, b, c):
    return a + b + c

print(func8(10, 20, 30))      # 60

numbers = [1, 2, 3]
print(func8(*numbers))      # 6
# print(func8(numbers))   # TypeError: func8() missing 2 required positional arguments: 'b' and 'c'
# [1, 2, 3] 이 a로 들어가니까 b, c가 없으므로 에러

 

6. 키워드 매개변수

def func9(id, name, age):
    print(f'아이디: {id}')
    print(f'이름: {name}')
    print(f'나이: {age}')

func9(age=30, id='orange', name='오렌지')

# ⭐
# 매개변수명과 딕셔너리의 키가 같아야함
# 딕셔너리의 키는 반드시 '문자열' 형태
dic1 = {'age':25, 'id':'banana', 'name':'반하나'}
func9(**dic1)

# *의 데이터를 보낼 경우 키가 저장
func9(*dic1)

 

7. 여러 개의 반환 값

def func10(num1=0, num2=0):
    return num1+num2, num1-num2, num1*num2, num1/num2
    
result = func10(10, 3)
print(result)       # (13, 7, 30, 3.3333333333333335) 패킹돼서 tuple로 출력

result1, result2, result3, result4 = func10(10, 3)      # 언패킹도 가능
print(f'덧셈:{result1}')
print(f'뺄셈:{result2}')
print(f'곱셈:{result3}')
print(f'나눗셈:{result4}')

_, _, result3, _ = func10(10, 3)        # 원하는 것만 가능
print(f'곱셈: {result3}')   
print(f'언더스코어: {_}')       # 누적되기 때문에 마지막 것을 반환. 언더스코어는 메모리에 항상 존재하고 있음

_, _, result3, result4 = func10(10, 3)
print(f'곱셈: {result3}')
print(f'나눗셈: {result4}')
li1 = [10, 20, 30, 40, 50]

for _, v in enumerate(li1):     
    print(f'값: {v}')

# 변수의 범위

1. 지역변수

  • 가장 내부의 범위
  • 함수 내에서 정의된 변수들이 해당됨
  • 함수 내에서만 접근 가능

2. 전역변수

  • 스크립트 전체에서 사용되는 범위
  • 함수 외부에 정의된 변수가 해당됨
  • 모든 함수에서 접근 가능하지만 함수 내부에서 수정하려면 global 키워드 사용
global_var = 10

def modify_global():
    global global_var		# global 키워드
    global_var = 20

modify_global()
print(global_var)  # 20

* range()는 사용되고 가비지컬렉터가 지워버리므로 메모리 아낄 수 있음

* iterable과 iterator의 완벽한 이해를 하려면 클래스, 객체지향 등 다른 내용이 필요

    - iterable은 여러 자료를 담을 수 있고 (리스트, 튜플, 딕셔너리, 문자열) 반복 가능

    - 후에 더 깊게 학습할 예정. 지금은 이정도만 알고 있자

* 예제를 외우지 말고 '로직'을 그려보자 -> 그 후 소스코드를 보지 않고 작성해보기

* 함수를 사용하는 이유는 반복된 과정을 재사용하기 위함


[수업후기]

- enumerate() 함수는 처음 보았다. 반복문에서 잘 활용하면 편리할 것 같다.

 

- iterable 객체에 리스트, 튜플, 딕셔너리, 문자열이 포함되는 것은 알고 있었고

iterator의 존재도 알고 있었으나 저런 용도로 쓰이는 지는 처음 알았다. 

 

- zip() 함수 길이가 다르다고 에러나거나 한 쪽만 돌아가지 않는다는 것과

여러 개의 반환 값에서 C, Java보다 Python이 발전한 언어라는 것을 느낄 수 있었다.

 

- 대학 수업에서 처음 반복문을 수강할 때 별 찍는 문제를 풀었었는데, 그 때 조금 헷갈려했던 기억이 있다. 앞으로 어려운 내용들이 나오더라도 지금의 내가 중첩반복문을 보는 것처럼 쉽게 느껴지는 때가 오겠지.

 

- 리스트 컴프리헨션 이름은 처음 들었지만, 코딩테스트 준비할 때 봤던 내용이고 문제들에 굉장히 자주 쓰였던 아이였다..!

세트, 딕셔너리 컴프리헨션도 있구나

 

- 키워드 매개변수에서 매개변수명과 딕셔너리의 키가 같으면 출력해주는 것? 정말 멋지다

 

- 아스테리스크(Asterisk) * 기호의 개수 신경써야겠다. 한 끝 차이로 출력이 달라지니까

 

- 파이썬에만 있는 함수들을 알려주시면서 수업하셔서 그 부분에서는 좀 더 귀 기울이게 된다. 신기하고 재밌으니까. 그리고 다른 구닥다리 언어들과 차이도 느낄 수 있어서 좋다.

 

- 5일차 포스팅까지는 그냥 코드들 한 두개 빼고는 다 넣었었는데, 너무 간단하거나 전에도 많이 봤던 예제들까지 적어둘 필요는 없어보여서 오늘부터 걸러서 정리해보았다.