Python_Day.3

조현기·4일 전

Python_Day.3

반복문의 다른 한 축인 for와 range, 그리고 함수.
오늘은 "같은 일을 짧게 쓰는 법"(range, 리스트 컴프리헨션, 함수)을 배운 날이다.

학습 세 번째 날이다. 오늘 배운 내용은 이렇다.

  1. for 반복문과 range
  2. for ~ else, reversed
  3. 리스트 컴프리헨션
  4. 함수: 매개변수와 return의 유무, 여러 값 반환
  5. *args와 `kwargs`**, 매개변수 기본값
  6. input()

1. for 반복문

어제 while로 1~10을 출력했는데, 같은 일을 for로 하면 훨씬 간단해진다. 우선 while부터 다시 보자.

n = 1
while n <= 10:
    print(n)
    n += 1
# 1~10까지 출력되는 while문

for의 기본 구조

# for 변수 in 모음(collection):
#     반복할 실행 코드

test_list = ['one', 'two', 'three']
for i in test_list:
    print(i)              # one two three

for i in 'one':
    print(i)              # o n e  (문자열은 글자 하나씩)

for는 리스트, 튜플, 문자열처럼 요소가 줄지어 있는 것에 사용한다. 문자열을 돌리면 글자가 하나씩 나온다. 처음 보면 의외지만, 문자열도 글자들의 나열이다.

위의 첫 번째 for를 while로 바꾸면 이렇다.

i = 0
while i < len(test_list):
    print(test_list[i])
    i += 1

for는 초기식, 조건식, 증감식을 직접 쓰지 않아도 알아서 요소를 하나씩 꺼내 준다. JS에서 forEach나 for ... of가 하던 일이다.

names = ['kim', 'lee', 'park', 'choi']
for i in names:
    print(i)              # 요소가 하나씩

word = '파이썬을 해보자!'
for i in word:
    print(i)              # 글자가 하나씩 (띄어쓰기도 한 글자로)

딕셔너리 돌리기

profile = {
    'name': 'Tom',
    'age': 25
}

for i in profile:
    print(i)              # name, age  (키가 나온다)
    print(profile[i])     # Tom, 25   (키로 값을 꺼낸다)

for i in profile.values():
    print(i)              # Tom, 25   (값만 출력하고 싶다면 이렇게)

딕셔너리를 for로 돌리면 키가 나온다. 값만 필요하면 .values()가 효율적이다. 키와 값을 같이 쓰고 싶으면 Day.2에서 배운 .items()를 쓰면 된다. 집합(set)도 같은 방식으로 돌릴 수 있다.

튜플을 풀어서 받기

a = [(3, 4), (6, 8), (9, 0), (34, 2)]
for (c, b) in a:          # 튜플의 요소가 자동으로 c, b에 들어간다
    print(c + b)          # 7 14 9 36

요소가 튜플이면 Day.2의 언패킹이 반복마다 자동으로 일어난다.

대문자 확인, 변환

fruit = 'piNeApplE'
for i in fruit:
    if i.isupper():       # 대문자인지 확인
        print(i)
    else:
        print(i.upper())  # 대문자로 변환
# 결과는 P I N E A P P L E (한 글자씩)

isupper()는 대문자인지 확인하는 것(True/False)이고, upper()는 대문자로 바꾸는 것이다. 이름이 비슷하니 구분해야 한다.

break와 서식 응용

number = [17, 23, 56, 22, 117, 3, 42]
for i in number:
    if i >= 50:
        print(f'{i}으로 마무리하겠습니다!')
        break
    else:
        print('{:*^10}'.format(i))
# ****17****
# ****23****
# 56으로 마무리하겠습니다!

50 이상이 나오면 break로 끝내고, 그 전에는 *로 채운 가운데 정렬로 출력한다. Day.1의 출력 서식이 반복문 안에서 쓰였다.

type()과 is

li = ['3', 1, 2, True, 4.5]

for i in li:
    if type(i) is str:    # 타입이 문자열이면
        continue          # 아래로 가지 않고 다음 요소로
    print(i, type(i))
# 1 <class 'int'>
# 2 <class 'int'>
# True <class 'bool'>
# 4.5 <class 'float'>
표현의미
==값이 같은지 비교
is같은 객체인지(메모리 주소가 같은지) 비교
type(x)x의 자료형(클래스)을 돌려줌

type(i) is str은 "i의 타입이 문자열 클래스 그 자체인가"를 확인한다. JS의 typeof i === 'string'과 비슷한 용도다. 또 True가 bool로 나오는 것도 확인할 수 있다.

for ~ else

number = [17, 23, 56, 22, 117, 3, 42]
for i in number:
    if i >= 100:
        print(f'{i:^10}으로 \n마무리하겠습니다!')
        break
else:                      # break가 나지 않았을 때만 작동
    print('{:*^10}'.format(i))

어제 while ~ else와 같은 규칙이다. else 블록은 for문이 중간에 끊기지 않고(break 없이) 끝까지 실행되었을 때만 실행된다. 위 코드는 117에서 break가 되므로 else는 실행되지 않는다. 조건을 i >= 200으로 바꾸면 끝까지 돌고 else가 실행된다.

reversed

fruit2 = '므아아앙고'
print(reversed(fruit2))              # <reversed object at 0x...>
print(list(reversed(fruit2)))        # ['고', '앙', '아', '아', '므']
print(tuple(reversed(fruit2)))       # ('고', '앙', '아', '아', '므')
print(set(reversed(fruit2)))         # {'아', '므', '앙', '고'}  (집합이라 순서와 중복이 사라짐)
print(fruit2)                        # 므아아앙고  (원본 그대로)

reversed()는 거꾸로 뒤집은 결과를 곧바로 보여 주지 않고, reversed 객체(하나씩 꺼낼 수 있는 상태)를 돌려준다. 그래서 print(reversed(...))만 하면 주소 같은 것이 나온다. list()나 tuple()로 감싸면 안의 값이 펼쳐진다.

그리고 reversed를 했다고 원본 fruit2가 뒤집히지는 않는다. 리스트와 튜플로 각각 변환한 결과가 똑같이 나오는 것을 보면, 원본이 바뀌었다면 한 번 쓸 때마다 값이 다시 뒤집혔을 텐데 그렇지 않다. Day.1에서 배운 li.reverse()는 원본을 뒤집는 메소드였고, reversed()는 원본을 두고 뒤집은 결과만 만드는 함수다.


2. range

range는 숫자의 범위를 만드는 함수다. for와 함께 쓴다.

# range(끝)              : 0부터 끝-1까지
# range(시작, 끝)        : 시작부터 끝-1까지
# range(시작, 끝, 증감)  : 시작부터 끝 미만까지 증감만큼씩

끝은 항상 "미만"이다. 슬라이싱과 같은 규칙이다.

# 1부터 100까지 합
total = 0
for i in range(101):          # 0~100
    total += i
print(total)                   # 5050

# 1부터 10까지 출력
for i in range(1, 11):
    print(i)

# 같은 일을 while로
n = 1
while n <= 10:
    print(n)
    n += 1
for i3 in range(1, 11, 2):       # 1부터 11 미만까지 2씩 증가
    print(i3, end=' ')            # 1 3 5 7 9
print('개행이 되나?')              # 같은 줄에 이어서 출력됨

print의 end는 출력 끝에 붙일 문자를 정한다. 기본값은 줄바꿈(\n)이라서 print마다 줄이 바뀌는데, end=' '로 바꾸면 줄바꿈 대신 공백이 붙어서 한 줄에 이어서 출력된다. 위 결과처럼 마지막 print('개행이 되나?')도 같은 줄에 이어서 나온다.

필기에서는 합계를 구하는 변수를 sum이라고 썼다(sum = int()). sum은 Python이 이미 가진 함수 이름(합계를 구하는 sum())이라, 이 이름으로 변수를 만들면 이후 sum([1, 2, 3])을 쓸 수 없게 된다. Day.1에서 str을 변수로 쓰면 안 된다고 했던 것과 같은 문제라 이 글에서는 total로 썼다. 그리고 int()를 인자 없이 호출하면 0이다.

중첩 반복문: 구구단

for i in range(2, 10):
    for n in range(1, 10):
        print(f'{i}X{n}={i*n}', end=' ')   # 한 줄에 i단을 쓴다
    print('')                               # 한 단이 끝나면 줄바꿈

바깥 for가 단(2~9)을, 안쪽 for가 곱하는 수(1~9)를 담당한다. 안쪽 반복에서는 end=' '로 한 줄에 이어 쓰고, 한 단이 끝나면 print('')로 줄을 바꾼다. JS Day.3에서 중첩 for로 구구단을 만들었을 때 document.write에 <br>을 붙이던 자리가 print('')다.


3. 응용 문제

대문자만 출력하기

msg = 'It is Time'
for n in range(len(msg)):
    if msg[n].isupper():
        print(msg[n], end=' ')
print('')
# I T

range(len(msg))는 range(10)과 같아서 n에는 글자가 아니라 0~9의 번호가 들어간다. 그래서 n.isupper()가 아니라 msg[n].isupper()로 번호를 이용해 글자를 꺼내야 한다. n이 숫자인데 .isupper()를 붙일 수는 없다.

번호가 필요 없다면 더 간단하게 글자를 바로 꺼낼 수도 있다.

for ch in msg:
    if ch.isupper():
        print(ch, end=' ')

홀수만 출력하기 (range, continue)

# 1~10까지 수 중 홀수만 출력
for i in range(1, 11):
    if i % 2 == 1:
        print(i, end=' ')
    else:
        continue
# 1 3 5 7 9

continue를 쓰는 문제라서 위처럼 풀었다. 그런데 증감값을 이용하면 조건문 없이 바로 된다.

for i in range(1, 11, 2):
    print(i, end=' ')

3의 배수만 출력하기

# 3~32까지 수 중 3의 배수만 출력
for i in range(3, 33):
    if i % 3 == 0:
        print(i, end=' ')
# 3 6 9 12 15 18 21 24 27 30

이것도 range(3, 33, 3)으로 시작값과 증감값을 맞추면 if가 필요 없다.

for i in range(3, 33, 3):
    print(i, end=' ')

방금 배운 range의 세 번째 인자를 활용하면 조건 검사 없이 반복 자체를 원하는 모양으로 만들 수 있다.

필기 코드에 for i in range(3,33): //3,33,3 을 쓰자 같은 주석이 붙어 있었는데, Python의 주석은 //가 아니라 #다. Python에서 //는 몫을 구하는 나눗셈 연산자(예: 7 // 2는 3)라서, 이대로 실행하면 오류가 난다.

한 줄에 9번씩 3줄

문제: 문장을 27번 출력하되, 한 줄에 9번씩 써야 한다(총 3줄).

msg = '안녕.'
cnt = 0
for i in range(27):
    print(msg, end=' ')
    cnt += 1
    if cnt % 9 == 0:
        print('')              # 9번째마다 줄바꿈

횟수를 세는 변수 cnt가 9의 배수가 될 때마다 줄을 바꾼다. 같은 일을 cnt 없이 반복 변수만으로도 할 수 있다. i는 0부터 시작하므로 (i + 1) % 9 == 0일 때 줄을 바꾸면 된다.


4. 리스트 컴프리헨션

for로 리스트를 만드는 흔한 패턴을 한 줄로 줄이는 표현이다.

a = [1, 2, 3, 4]
result = []

for num in a:
    result.append(num * 3)
print(result)                  # [3, 6, 9, 12]

이것을 줄이면 이렇다.

a = [1, 2, 3, 4]
result = [num * 3 for num in a]
print(result)                  # [3, 6, 9, 12]

for문의 결과값이 앞의 식에 대입되면서, 나온 값들이 리스트로 만들어진다. 이 표현 방식을 리스트 컴프리헨션이라고 한다. 구조는 이렇다.

[ 식  for 변수 in 모음 ]
[ 식  for 변수 in 모음  if 조건 ]

조건 붙이기

result = [num * 3 for num in a if num % 2 == 0]
print(result)                  # [6, 12]

a 안의 num을 반복하는데, num % 2 == 0인 것(2와 4)만 뽑아서 앞의 num * 3에 넣고 결과를 리스트로 만든다.

풀어서 쓰기

a = [1, 2, 3, 4]
result = [num for num in a]
print(result)                  # [1, 2, 3, 4]

# 위를 풀어서 쓰면
result = []
for num in a:
    result.append(num)
print(result)                  # [1, 2, 3, 4]

result = [num * 2 for num in a]
print(result)                  # [2, 4, 6, 8]

출력한 값들을 리스트에 저장하고 싶을 때 쓴다.

range와 함께

newlist1 = [i for i in range(3, 31)]     # 3~30
print(newlist1)

# 3~30 중 3의 배수 중에서 6의 배수만 리스트로
b = [th for th in range(3, 31, 3) if th % 6 == 0]
print(b)                       # [6, 12, 18, 24, 30]

c = [i for i in range(1, 11)]
print(c)                       # [1, 2, ..., 10]

필기에서는 range(3, 30, 3)으로 "3~30"을 처리했는데, range의 끝은 미만이라 30이 빠진다. 30까지 포함하려면 range(3, 31, 3)이다. 이 경우 결과는 [6, 12, 18, 24]가 아니라 [6, 12, 18, 24, 30]이 된다.

반복문 두 개 겹치기

result = [f'{x}X{y}={x*y}' for x in range(2, 10)
          for y in range(1, 10)]
print(result)                  # ['2X1=2', '2X2=4', ..., '9X9=81']

컴프리헨션 안에 for를 두 개 쓰면 구구단 전체를 리스트로 만든다. 앞의 for가 바깥 반복, 뒤의 for가 안쪽 반복이다.

컴프리헨션이 어울리지 않을 때

문제: 컴프리헨션을 사용해서, A학급의 평균 점수만큼 별을 출력하되, 별의 개수가 3의 배수인 줄은 나오지 않게 하라.

처음에는 컴프리헨션을 쓸 수 없다고 판단했다. 리스트로 저장할 값이 따로 없기 때문이다. 억지로 쓰면 가능하겠지만 오히려 비효율적이게 되는 느낌이라 제외하고 for로 풀었다.

A = [70, 60, 55, 75, 95, 90, 80, 80, 85, 100]

# 평균 점수 구하기
total = 0
for i in A:
    total += i
average = total / len(A)
print(average)                 # 79.0

# 별을 average만큼 연속해서 출력하기
for n in range(1, int(average) + 1):
    if n % 3 == 0:             # 3으로 나눴을 때 나머지가 0이면 건너뛴다
        continue
    print('*' * n)

range(1, int(average) + 1)로 쓴 이유는, 평균이 79.0이라 int(average)가 79이고, range의 끝은 미만이라 +1을 해야 79번째 줄까지 포함되기 때문이다. 필기에서는 range(1, int(average))였는데, 그러면 78줄에서 끝난다. average = total / len(A)에서 /는 정수끼리 나눠도 실수가 나오므로 int()로 감쌌다.

굳이 컴프리헨션을 쓰고 싶다면 "출력할 문자열"을 리스트로 만들어 두는 방법이 있다.

lines = ['*' * n for n in range(1, int(average) + 1) if n % 3 != 0]
print('\n'.join(lines))

'\n'.join(리스트)는 리스트의 문자열들을 줄바꿈으로 이어 붙인다. 다만 이 경우에는 for로 바로 출력하는 쪽이 더 단순하다.


5. 함수

함수는 개발자가 이름을 붙여 정의해 두고 필요할 때마다 호출하는, 반복되는 코드를 한 번 구현해서 재사용할 수 있게 만든 코드의 집합이다. 함수를 만들어 두고(구현), 필요할 때 부른다(재사용).

함수는 이렇게 나눌 수 있다.

  • 매개변수가 있는 함수 / 없는 함수
  • 결과값을 반환하는(return) 함수 / 반환하지 않는 함수
# def 함수명(매개변수):
#     함수 정의 코드

def func():                    # 매개변수가 없는 함수
    print('함수입니다!')         # return이 없으므로 결과값을 반환하지 않음

func()                         # 함수 호출


def func2(a, b):               # 매개변수가 있는 함수
    print(a, b)                # 결과값을 반환하지 않음

func2(5, 6)                    # 5 6


def func3(a, b):
    return a + b               # 결과값을 반환

print(func3(7, 8))             # 15

JS의 function 대신 def를 쓰고, 중괄호 대신 콜론과 들여쓰기로 함수 몸체를 구분한다. return이 없는 함수가 돌려주는 값은 None이다.

def name(word):
    print(word)

a = '파이써어언'
name(a)                         # 파이써어언


def name2(word):
    n = 'Hi ' + word
    return n

print(name2('길동!'))            # Hi 길동!

name은 출력만 하고, name2는 결과를 돌려준다. 돌려받은 값은 print에 넣거나 변수에 저장해서 쓸 수 있다.

여러 값을 반환하기

def multi(x):
    y1 = x * 5
    y2 = x * 10
    y3 = x * 20
    return y1, y2, y3           # 튜플 선언 양식과 같다

print(type(multi(3)))           # <class 'tuple'>
print(multi(3))                 # (15, 30, 60)

return y1, y2, y3처럼 쉼표로 이으면 튜플 하나로 묶여서 반환된다. Day.2에서 배운 패킹이다. 받을 때는 언패킹으로 풀 수 있다.

y1, y2, y3 = multi(3)           # 언패킹
print(y1, y2, y3)               # 15 30 60

반환하는 자료형에 따라 결과를 쓰는 방식이 달라진다.

# 1. 튜플로 반환
def func1(x):
    y1 = x * 5
    y2 = x * 10
    y3 = x * 20
    return (y1, y2, y3)

a = func1(5)
print(a, type(a))               # (25, 50, 100) <class 'tuple'>
print(list(a))                  # [25, 50, 100]  (리스트로 변환)

# 2. 리스트로 반환
def func2(x):
    y1 = x * 5
    y2 = x * 10
    y3 = x * 20
    return [y1, y2, y3]

a = func2(5)
print(a, type(a), set(a))       # [25, 50, 100] <class 'list'> {25, 50, 100}

# 3. 딕셔너리로 반환
def func3(x):
    y1 = x * 5
    y2 = x * 10
    y3 = x * 20
    return {'y1': y1, 'y2': y2, 'y3': y3}

a = func3(5)
print(a, type(a))               # {'y1': 25, 'y2': 50, 'y3': 100} <class 'dict'>

print(a['y2'])                  # 50  (y2의 대응값)
print(a.items())                # 키와 값 전부
print(a.keys())                 # 키만

딕셔너리로 반환하면 a['y2']처럼 이름으로 꺼낼 수 있어서 어떤 값인지 알아보기 쉽다.

def qq(x, y):
    for i in range(x, y):
        print(i)

qq(1, 10)                       # 1~9까지 출력
qq(4, 6)                        # 4~5까지 출력

함수 안에서 for와 range를 쓰면, 범위를 매개변수로 받는 출력 함수를 만들 수 있다.


6. 매개변수: *args, **kwargs, 기본값

*args: 개수가 정해지지 않은 매개변수

def add_many(*args):
    result = 0
    for i in args:
        result = result + i
    return result

print(add_many(1, 2, 3))                       # 6
print(add_many(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) # 55

매개변수 앞에 *을 붙이면 몇 개가 들어와도 전부 받는다. 받은 값들은 튜플로 묶여서 들어온다.

필기의 def add_many(*args) 뒤에는 콜론이 빠져 있었다. 함수를 정의할 때도 def ...:처럼 콜론이 꼭 필요하다. 위 코드는 고친 것이다.

def func1(*args):
    for i in args:
        print(i)

func1('gildong')
func1('길동', '토옴', '닉')
func1('길동', '토옴', '닉', '주디')    # 넘긴 값이 하나씩 출력된다

**kwargs: 이름 붙은 매개변수

# keyword arguments
def func2(**a):
    for i in a.keys():
        print(i, a[i], type(i), a, type(a))

func2(name1='철수', age=29)
# name1 철수 <class 'str'> {'name1': '철수', 'age': 29} <class 'dict'>
# age 29 <class 'str'> {'name1': '철수', 'age': 29} <class 'dict'>

**를 붙이면 이름=값 형태로 넘긴 인자들이 딕셔너리로 묶여 들어온다. 이름이 키, 넘긴 값이 값이다.

표기받는 형태넘기는 모양
*args튜플f(1, 2, 3)
**kwargs딕셔너리f(a=1, b=2)
def func3(arg1, arg2, *args, **kwargs):
    print(arg1, arg2, args, kwargs)

func3(10, 70, 'kim', 'lee', 'park', age=10, adr='seoul')
# 10 70 ('kim', 'lee', 'park') {'age': 10, 'adr': 'seoul'}

출력을 보면 args는 튜플(( )), kwargs는 딕셔너리({ })인 것이 분명하다.

매개변수 순서 규칙은 일반 인자 → *args → **kwargs다. 별 두 개가 붙은 **kwargs는 반드시 매개변수의 맨 뒤에 있어야 한다.

매개변수 기본값

def say(name, man, old=20):
    print(name)
    if man:
        print('남자', old)
    else:
        print('여자', old)

say('철수', True)          # 철수 / 남자 20
say('영희', False, 10)     # 영희 / 여자 10

old=20처럼 기본값을 정해 두면, 호출할 때 그 자리에 값이 없으면 기본값 20으로 실행된다.

기본값이 정해진 매개변수는 매개변수의 가장 뒤쪽에 와야 한다. 앞에 오면 parameter without a default follows parameter with a default 오류가 난다.

실습: 요금 계산 함수

def calcu(a, b, c, d):
    if b == 15 and c >= 3:
        print('{name}씨는 10% 할인 ={price}원'.format(name=a, price=int(d * 0.9)))
    elif b == 12 and c >= 5:
        print('%s씨는 20%% 할인 =%d원' % (a, d * 0.8))
    else:
        print(f'{a}씨는 정가 = {d}원')

# 15시에 방문하고 3개 이상 구매하면 10% 할인
# 12시에 방문하고 5개 이상 구매하면 20% 할인

# calcu(이름, 시간, 개수, 가격)
calcu('철수', 15, 4, 20000)    # 철수씨는 10% 할인 =18000원
calcu('영희', 12, 5, 50000)    # 영희씨는 20% 할인 =40000원
calcu('민수', 10, 2, 70000)    # 민수씨는 정가 = 70000원

출력을 format(), % 서식, f-string으로 세 가지 방식을 각각 한 번씩 써서 구현했다. 파이썬 문자열의 % 서식에서 % 글자 자체를 출력하고 싶다면 %%라고 써야 한다. 그냥 %를 쓰면 서식 문자로 오해한다. format()과 f-string에서는 %를 그대로 써도 된다.

d * 0.8은 실수(40000.0)지만 %d가 정수로 출력해 준다.


7. input()

a = input('너는 누구니')
print(a)

a = float(input('키 몇 cm?'))
print(a)

input('안내 문구')는 사용자가 입력한 값을 받는다. JS의 prompt에 해당한다. 입력받은 값은 항상 문자열이라서 숫자로 쓰려면 int()나 float()로 변환해야 한다.

a = int(input('첫 번째 정수 입력'))
b = int(input('두 번째 정수 입력'))
print(a * b)

input()을 int()로 감싸는 형태는 JS에서 Number(prompt(...))로 감싸던 것과 같은 패턴이다. 감싸지 않으면 '3' * '4'처럼 문자열끼리 곱하게 되어 오류가 난다.


마무리

  • for는 리스트, 튜플, 문자열, 딕셔너리, 집합의 요소를 하나씩 꺼내고, range(시작, 끝, 증감)으로 숫자 범위를 만든다. 끝은 미만이다.
  • for ~ else의 else는 break 없이 끝났을 때만 실행된다.
  • 증감값을 활용하면 if로 거르지 않아도 되는 문제가 많다.
  • 리스트 컴프리헨션 [식 for 변수 in 모음 if 조건]은 값을 리스트로 만들 때 쓴다.
  • 함수는 def 함수명(매개변수):로 만들고, 여러 값을 return하면 튜플로 묶인다.
  • *args는 튜플, **kwargs는 딕셔너리로 받는다. 순서는 일반 → *args → **kwargs, 기본값은 뒤쪽이다.
  • input()은 항상 문자열을 돌려준다.
  • 변수 이름에 str, sum처럼 Python이 이미 쓰는 이름을 쓰지 않는다.

Tags: Python for문 range 리스트컴프리헨션 함수 args kwargs input 개발자 학습기록

profile
난 뭘까

0개의 댓글