1097 : [기초-2차원배열] 바둑알 십자 뒤집기(설명)

처음 바둑판 상태를 입력받고, 뒤집기할 횟수를 입력받고, 좌표를 입력받는다.  반복문으로 반복하며 좌표의 가로줄과 세로줄의 흑, 백을 변환한다. 처음 바둑판 상태는 판 리스트를 하나 만들어 두고, 입력값 각 줄을 리스트로 만들어 기존에 만들어둔 리스트에 넣어 2차원 리스트로 만들었다. 십자 뒤집기는 입력받은 좌표의 세로줄과 가로줄 각각 반복문으로 19번 반복하며 흑과 백을 변환하였다.

pan = []
# 바둑판 초기 상태 입력
for i in range(19):
    a = list(map(int, input().split()))
    pan.append(a)

n = int(input())

for i in range(n):
    # 십자 뒤집기 할 좌표
    x, y = map(int, input().split())
    # 리스트 인덱스는 0번부터 시작하므로 1씩 빼줌
    x -= 1
    y -= 1
    # 가로 줄 흑, 백 변환
    for j in range(19):
        if pan[x][j] == 0:
            pan[x][j] = 1
        else:
            pan[x][j] = 0
    # 세로줄 흑, 백 변환
    for j in range(19):
        if pan[j][y] == 0:
            pan[j][y] = 1
        else:
            pan[j][y] = 0

# 최종 값 출력
for i in range(19):
    for j in range(19):
        print(pan[i][j], end = ' ')
    print()

 

1098 : [기초-2차원배열] 설탕 과자 뽑기

입력받은 가로, 세로 크기 만큼 0을 채운 격자판을 2차원 리스트로 만들고, 길이, 방향, 좌표를 입력받아 입력받은 좌표부터 가로 또는 세로 방향으로 입력받은 길이까지 1로 채우는 행위를 입력받은 좌표 수만큼 반복하고, 막대가 채워진 최종 격자판을 출력한다.

h, w = map(int, input().split())
pan = []

# 격자판 초기상태(모든 칸 0)
for i in range(h):
    pan.append([0] * w)

n = int(input())
for i in range(n):
    l, d, x, y = map(int, input().split())
    x -= 1
    y -= 1
    # 막대가 가로로 놓여있을 때
    if d == 0:
    	# 시작 좌표부터 가로 길이만큼 1로 채워짐
        for j in range(l):
            pan[x][y+j] = 1
    # 세로로 놓여있을 때
    else:
        # 시작 좌표부터 세로 길이만큼 1로 채워짐
        for j in range(l):
            pan[x+j][y] = 1

# 격자판 채운 모양 출력
for i in range(h):
    for j in range(w):
        print(pan[i][j], end = ' ')
    print()

 

1099 : [기초-2차원배열] 설탕 과자 뽑기

리스트의 인덱스는 0부터 시작하기 때문에 시작 인덱스를 1,1 로 지정하였고, 입력값은 한 줄씩 리스트로 만들어 입력받아 2차원 리스트로ㅓ 만들었다. 무한 루프 반복문을 만들어 개미가 먹이를 만나거나, 오른쪽 또는 아래로 이동할 수 없을 때 반복문을 탈출하도록 하였고, 벽이 없으면 오른쪽으로 벽이 있으면 아래로 이동하면서 현재 위치에 9를 저장하였다.

miro = []
x, y = 1, 1
for i in range(10):
    a = list(map(int, input().split()))
    miro.append(a)

while True:
    if miro[x][y] == 2:
        miro[x][y] = 9
        break
    miro[x][y] = 9
    if miro[x][y+1] == 0:
        y += 1
    elif miro[x][y+1] == 1:
        if miro[x+1][y] == 1:
            break
        elif miro[x+1][y] == 2:
            miro[x+1][y] = 9
            break
        else:
            x += 1
    else:
        miro[x][y+1] = 9
        break



for i in range(10):
    for j in range(10):
        print(miro[i][j], end = ' ')
    print()

1078 : [기초-종합] 짝수 합 구하기(설명)

for 반복문으로 1부터 입력받은 수까지 반복하고, 반복문 안에서 짝수일 경우 그 숫자들의 합을 누적하여 저장한다.

n = int(input())
s = 0
for i in range(1,n+1):
    if i % 2 == 0:
        s += i
print(s)

 

1079 : [기초-종합] 원하는 문자가 입력될 때 까지 반복 출력하기(설명)

입력값들을 변수 하나에 공백으로 분리하여 입력받고, 입력 받은 문자 수만큼 반복하여 각 문자를 각 줄에 출력하고, q를 확인하면 반복문을 종료한다.

chars = input().split()
for i in chars:
    print(i)
    if i == 'q':
        break

 

1080 : [기초-종합] 언제까지 더해야 할까?

정수를 입력받아, 1부터 그 정수 만큼 반복하며 합을 누적한다. 누적된 합이 입력받은 정수보다 같거나 커지면 마지막으로 더한 정수를 출력하고 반복문을 종료시킨다.

n = int(input())
s = 0
for i in range(1, n):
    s += i
    if s >= n:
        print(i)
        break

 

1081 : [기초-종합] 주사위를 2개 던지면?(설명)

첫 번째 주사위 면이 1부터 n일 때 각 면의 주사위 숫자에 두번째 주사위 1부터 m이 조합될 수 있다. 1부터 n 까지 반복하면서 그 안에서 1부터 m까지 반복하여 첫번째 주사위 면이 1일때 두번째 주사위 면이 1, 2, 3, ... , m인 경우를 모두 출력할 수 있다.

n, m = map(int, input().split())

for i in range(1, n+1):
    for j in range(1, m+1):
        print(i,j)

 

1082 : [기초-종합] 16진수 구구단?

입력 받은 값을 1부터 F까지 반복하여 계산하여 출력하면 된다. 출력 예시를 보니 16진수 인데, 0x가 안붙어 있고, 다 대문자로 출력되어 있어서, 출력값을 문자열로 바꿔서 인덱스 슬라이싱과 .upper()을 통해 출력 형태를 예시와 동일하게 맞췄다.

dan = input()
dan = int(dan, 16)
for i in range(1, 16):
    print('{}*{}={}'.format(str(hex(dan))[2:].upper(), 
    str(hex(i))[2:].upper(), str(hex(dan*i))[2:].upper()))

 

1083 : [기초-종합] 3 6 9 게임의 왕이 되자!(설명)

1부터 입력값까지 반복하는 코드를 작성하고, 반복문 안에서 현재 값이 3 또는 6또는 9면 or연산을 이용하여 X를 출력하고 continue를 이용하여 반복 처음을 돌아가고, 3, 6, 9가 아니라면 그 숫자를 출력한다. 출력은 한 줄로 한 칸 씩 띄워서 하므로 end = ' '를 사용하였다.

n = int(input())
for i in range(1,n+1):
    if i == 3 or i == 6 or i == 9:
        print('X', end = ' ')
        continue
    print(i, end=' ')

 

1084 : [기초-종합] 빛 섞어 색 만들기(설명)

r, g, b는 각각 하나라도 다른 값을 가지면 다른 색을 만들수 있으므로 0부터 각 색에 대한 입력값까지 3번 중첩하여 반복하면 모든 경우의 수를 출력할 수 있고, 각 경우의 조합을 출력할때 가지수를 세는 변수를 만들어 1씩 누적한후 마지막에 누적한 값을 출력하면 된다.

r, g, b = map(int, input().split())
cnt = 0

for i in range(0, r):
    for j in range(0, g):
        for k in range(0, b):
            print(i,j,k)
            cnt += 1
print(cnt)

 

1085 : [기초-종합] 소리 파일 저장용량 계산하기(설명)

입력받은 h,b,c,s를 위에서 제시한 계산식에 넣어 계산하고, MB단위로 맞추면 된다. 위 제시한 계산식에 넣어 계산했을 때의 단위는 bit이기 때문에 MB 단위로 표현하려면 8로 한번 나누고, 1024로 두번 나눈 후 소수점 첫째자리까지 출력하면 된다.

h, b, c, s = map(int, input().split())
pcm = h * b * c * s
mb = pcm / 8 / 1024 / 1024
print(format(mb, '.1f'), 'MB')

 

1086 : [기초-종합] 그림 파일 저장용량 계산하기(설명)

입력 값들을 문제에서 제시한 계산식에 넣어 계산 한 뒤 MB단위로 바꾸면 된다. 계산식에 넣어 계산하면 단위가 비트이기 때문에 8로 나눈 후 1024로 두번 나누면 된다.

w, h, b = map(int, input().split())
bit = w * h * b
mb = bit / 8 / 1024 /1024
print(format(mb,'.2f'), 'MB')

 

1087 : [기초-종합] 여기까지! 이제 그만~(설명)

1 부터 입력값 만큼 반복하며 각 숫자의 합을 누적한다. 누적한 값이 입력한 값과 같아지거나 더 커지면 break로 반복문을 종료하고 누적했던 값을 출력한다.

n = int(input())
s = 0

for i in range(1, n+1):
    s += i
    if s >= n:
        break
print(s)

 

1088 : [기초-종합] 3의 배수는 통과?(설명)

for 반복문으로 1부터 입력 받은 숫자까지 출력하는데, 3의 배수이면 continue로 숫자를 출력하지 않고, 반복문 처음으로 돌아간다.

n = int(input())

for i in range(1, n+1):
    if i % 3 == 0:
        continue
    print(i, end = ' ')

 

1089 : [기초-종합] 수 나열하기1

수학에서 등차수열 n번째 항을 구하는 공식을 이용하여 풀었다.

a, d, n = map(int, input().split())

an = a + (n-1)*d
print(an)

 

1090 : [기초-종합] 수 나열하기2

수학에서 등비수열의 n번째 항을 구하는 공식을 이용하여 풀었다.

a, r, n = map(int, input().split())
an = a * r ** (n-1)
print(an)

 

1091 : [기초-종합] 수 나열하기3

시작 값에 어떤 값을 곱하고, 더하는 행위를 계속 반복하므로 n번째 반복했을때의 수를 구하는 것이므로 반복문으로 n번째 까지 반복한 값을 출력했다.

a, m, d, n = map(int, input().split())

for i in range(n-1):
    a = m * a + d

print(a)

 

1092 : [기초-종합] 함께 문제 푸는 날(설명)

가입한 다음 모두 같이 함께 방문하는 날은 각 방문 주기의 최소 공배수 이다. for문으로 하루씩 증가시키면서 and연산으로 방문 주기의 최소공배수를 출력하면 된다.

a, b, c = map(int,input().split())
day = 1
while True:
    if day % a == 0 and day % b == 0 and day % c == 0:
        print(day)
        break
    day += 1

 

1093 : [기초-1차원 배열] 이상한 출석번호 부르기1(설명)

무작위로 부른 출석 번호를 입력 받아 리스트로 만들고, 23개의 요소가 0으로 채워진 리스트를 만들어 23번 반복하며 리스트의 각 요소를 무작위로 부른 리스트에서 각 번호가 몇번 불러졌는지 .count로 세서 넣었다.

n = int(input())
num = map(int, input().split())
num = list(num)
num_li = [0]*23

for i in range(len(num_li)):
    num_li[i] = num.count(i+1)

for i in num_li:
    print(i, end = ' ')

 

1094 : [기초-1차원 배열] 이상한 출석번호 부르기2(설명)

리스트는 list.reverse()와 같은 형식으로 거꾸로 뒤집을 수 있다. 무작위로 부른 번호를 리스트로 만들고 거꾸로 뒤집은 후 출력하면 된다.

n = int(input())
num = map(int, input().split())
num_li = list(num)
num_li.reverse()

for i in num_li:
    print(i, end=' ')

 

1095 : [기초-1차원 배열] 이상한 출석번호 부르기3(설명)

list.sort()와 같이 사용하면 리스트가 오름차순으로 정렬된다. 무작위로 부른 번호들을 리스트로 만들어 오름차순으로 정렬한 후 첫번째 요소를 출력하면 된다.

n = int(input())
num = map(int, input().split())
num_li = list(num)
num_li.sort()
print(num_li[0])

 

1096 : [기초-2차원 배열] 바둑판에 흰 돌 놓기(설명)

19 * 19 크기의 2차원 리스트의 모든 요소를 다 0으로 초기화 하고, 흰돌의 개수를 입력 받고, 개수 만큼 반복하여 해당 위치의 요소를 1로 바꿨다. for 문 2중 중첩으로 2차원 리스트의 모든 요소를 바둑판 모양으로 출력하였다.

n = int(input())
pan = [[0]*19 for i in range(19)]

for i in range(n):
    x, y = map(int, input().split())
    pan[x-1][y-1] = 1
    
for i in range(19):
    for j in range(19):
        print(pan[i][j], end = ' ')
    print()

1035 : [기초-출력변환] 16진 정수 1개 입력받아 8진수로 출력하기(설명)

파이썬에서는 16진수는 0x가 붙기 때문에 입력값에 0x를 붙여줘야 한다. 입력값에 0x를 붙이고 int(변수, 현재진법)의 형식으로 10진수로 변환한 다음 다시 oct()로 8진수로 변환한다. 변환한 값을 다시 문자열로 변환한 후 인덱스 슬라이싱을 이용하여 0o를 빼고 출력한다.

n = input()
n = '0x' + n
n = int(n, 16)
n = oct(n)
n = str(n)
print(n[2:])

 

1036 : [기초-출력변환] 영문자 1개 입력받아 10진수로 출력하기(설명)

문자를 아스키 코드표의 10진수로 변환하려면 파이썬에서는 ord()함수를 이용한다. 입력받은 값을 ord로 10진수로 변환한 후 출력한다.

a = input()
n = ord(a)
print(n)

 

1037 : [기초-출력변환] 정수 입력받아 아스키 문자로 출력하기(설명)

10진수를 아스키 문자로 변환하려면 파이썬에서는 chr()함수를 사용한다. 입력 받은 값을 chr로 변환한 후 출력한다.

n = int(input())
a = chr(n)
print(a)

 

1038 : [기초-산술연산] 정수 2개 입력받아 합 출력하기1(설명)

map으로 두 입력값을 정수로 입력받고, 입력받은 값들을 더하여 출력한다.

n1, n2 = map(int, input().split())
print(n1+n2)

 

1039 : [기초-산술연산] 정수 2개 입력받아 합 출력하기2(설명)

파이썬에서는 정수 범위의 제한이 없기 때문에 위 코드를 동일하게 사용하면 된다.

n1, n2 = map(int, input().split())
print(n1+n2)

 

1040 : [기초-산술연산] 정수 1개 입력받아 부호 바꿔 출력하기(설명)

정수를 저장한 변수에 -(마이너스)기호를 붙이면 부호가 바뀐다.

n = int(input())
print(-n)

 

1041 : [기초-산술연산] 문자 1개 입력받아 다음 문자 출력하기(설명)

아스키 문자는 알파벳 순서대로 배열되어 있어 아스키 코드에 1을 더하면 다음 알파벳이 나온다. 입력받은 문자를 아스키 코드로 변환한 후 1을 더한 후에 다시 문자로 변환하여 출력한다.

a = input()
a = chr(ord(a) +1)
print(a)

 

1042 : [기초-산술연산] 정수 2개 입력받아 나눈 몫 출력하기(설명)

파이썬에서는 //연산자로 몫연산을 진행한다. 나머지는 버리고 몫만 반환한다.

a, b = map(int, input().split())
print(a//b)

 

1043 : [기초-산술연산] 정수 2개 입력받아 나눈 나머지 출력하기(설명)

나머지를 구하는 연산자는 %이다. 입력값을 정수로 입력받아 나머지 연산하여 출력한다.

a, b = map(int, input().split())
print(a%b)

 

1044 : [기초-산술연산] 정수 1개 입력받아 1 더해 출력하기(설명)

입력받은 값에서 1 더한 값을 출력하면된다.

n = int(input())
print(n+1)

 

1045 : [기초-산술연산] 정수 2개 입력받아 자동 계산하기(설명)

두 입력값을 정수로 입력받아 각 줄에 각각 연산한 값을 출력한다. 나눈 값은 실수로 출력되기 때문에 format으로 소숫점 둘째자리 까지 출력할 수 있도록 고정해준다.

n1, n2 = map(int, input().split())
print(n1+n2)
print(n1-n2)
print(n1*n2)
print(n1//n2)
print(n1%n2)
print(format(n1/n2,".2f"))

 

1046 : [기초-산술연산] 정수 3개 입력받아 합과 평균 출력하기(설명)

정수 3개를 입력받고 첫번째 줄에는 합을 출력하고, 두번째 줄에는 평균을 출력한다. 평균은 합에서 3을 나누면 되고, format으로 소숫점 첫째자리 까지 고정하여 출력한다.

n1, n2, n3 = map(int, input().split())
hap = n1 + n2 + n3
print(hap)
print(format(hap/3, ".1f"))

 

1047 : [기초-비트시프트연산] 정수 1개 입력받아 2배 곱해 출력하기(설명)

*2 로 출력해도 되지만, 비트 시프트를 이용할 수 있다. 2진수 형태의 값을 <<(왼쪽)으로 밀면 값이 두 배 증가하고, >>(오른쪽)으로 밀면 값이 반으로 줄어든다. 입력받은 값을 비트시프트를 이용하여 1칸 밀면 2배 곱한 값을 출력할 수 있다.

n = int(input())
print(n<<1)

 

1048 : [기초-비트시프트연산] 한 번에 2의 거듭제곱 배로 출력하기(설명)

왼쪽(<<)으로 밀 때 마다 값이 두번씩 증가하기 때문에 a를 b칸만큼 밀면 된다.

a, b = map(int, input().split())
print(a<<b)

 

1049 : [기초-비교연산] 두 정수 입력받아 비교하기1(설명)

비교 연산자는 왼쪽이 오른쪽보다 크면 > , 작으면 <, 크거나 같으면 >=, 작거나 같으면 <=, 같으면 ==, 다르면 != 을 사용한다. if 조건문을 사용하여 a > b일 경우 1을 출력하고 아니면 0을 출력하면 된다.

a, b = map(int, input().split())

if a > b:
    print(1)
else:
    print(0)

 

1050 : [기초-비교연산] 두 정수 입력받아 비교하기2(설명)

두 정수가 같으면 == 연산자를 이용하여 비교했을 때 참이 나오고, 다르면 != 연산자를 이용하여 비교했을 때 참이 나온다. 이를 활용하여 if 조건문으로 해결할 수 있다.

a, b = map(int, input().split())

if a == b:
    print(1)
else:
    print(0)

 

1051 : [기초-비교연산] 두 정수 입력받아 비교하기3(설명)

크거나 같은지 확인하는 연산자는 >= 이다. if 조건문으로 크거나 같은지 확인하고, 아니라면 else에서 처리하면 된다.

a, b = map(int, input().split())

if b >= a:
    print(1)
else:
    print(0)

 

1052 : [기초-비교연산] 두 정수 입력받아 비교하기4(설명)

두 정수가 다른지 확인하는 연산자는 != 이다. if 조건문으로 다른지 확인하고 아니면 else에서 처리하면 된다.

a, b = map(int, input().split())

if b != a:
    print(1)
else:
    print(0)


1053 : [기초-논리연산] 참 거짓 바꾸기 (설명)

입력값은 둘 중 하나이기 때문에 입력값을 받아서 1이 입력되면 0을 출력하고, 아니면 1을 출력하도록 하면 된다.

a = int(input())

if a == 0:
    print(1)
else:
    print(0)

 

1054 : [기초-논리연산] 둘다 참일 경우만 참 출력하기 (설명)

if 조건문에 조건식을 넣을 때 and 연산자를 사용하면 조건식을 여러 개 넣을 수 있고, and연산자는 and의 왼쪽 식과 오른쪽 식이 모두 참 이여야 참을 반환한다. if 1 and 1: 과 같이 둘 다 참이여야 조건식을 수행한다. 따라서 둘 다 1을 입력 받을 경우 1을 출력하도록 하면 된다.

a, b = map(int,input().split())

if a == 1 and b == 1:
    print(1)
else:
    print(0)

 

1055 : [기초-논리연산] 하나라도 참이면 참 출력하기 (설명)

or 연산자는 둘 중 하나만 참이면 참을 출력한다. 위 코드에서 and 부분만 or로 바꾸면 된다.

a, b = map(int,input().split())

if a == 1 or b == 1:
    print(1)
else:
    print(0)

 

1056 : [기초-논리연산] 참/거짓이 서로 다를 때에만 참 출력하기 (설명)

입력 될 수 있는 값은 0 과 1 밖에 없으므로 둘이 다르면 1을 출력하고, 같으면 0을 출력한다.

a, b = map(int,input().split())

if a != b:
    print(1)
else:
    print(0)

 

1057 : [기초-논리연산] 참/거짓이 서로 같을 때에만 참 출력하기 (설명)

두 입력값이 서로 같으면 1을 출력하고, 아니면 0을 출력한다.

a, b = map(int,input().split())

if a == b:
    print(1)
else:
    print(0)

 

1058 : [기초-논리연산] 둘 다 거짓일 경우만 참 출력하기 (설명)

and 연산을 이용하여 입력받은 두 값이 모두 0일때 1을 출력한다.

a, b = map(int,input().split())

if a == 0 and b == 0:
    print(1)
else:
    print(0)

 

1059 : [기초-비트단위논리연산] 비트단위로 NOT 하여 출력하기 (설명)

10진수를 2진수로 바꾸면 0과 1의 배열로 표현한다. 이 배열에서 0은 1로, 1은 0으로 바꾸는것을 비트 단위로 NOT한다고 한다. 파이썬에서도 ~연산자를 통해 비트단위 NOT연산을 할 수 있다.

n = int(input())
print(~n)

 

1060 : [기초-비트단위논리연산] 비트단위로 AND 하여 출력하기 (설명)

비트 단위로 AND연산 하는 것은 두 수를 2진수 배열로 바꿨을때 각 자리가 둘다 1인 부분만 1로 하고 나머지는 0으로 만드는 것이다. &연산자를 통해 비트단위 AND연산을 할 수 있다.

a, b = map(int,input().split())
print(a & b)

 

1061 : [기초-비트단위논리연산] 비트단위로 OR 하여 출력하기 (설명)

비트 단위로 or 연산 하는 것은 두 수를 2진 배열로 바꿨을 때 각 자리중 하나만 1이면 1로하고, 둘다 0이면 0으로 만드는 것이다. | 연산자를 통해 비트단위 OR연산을 할 수 있다.

a, b = map(int,input().split())
print(a | b)

 

1062 : [기초-비트단위논리연산] 비트단위로 XOR 하여 출력하기 (설명)

비트 단위로 XOR연산 하는 것은 두 수를 2진 배열로 바꿨을 때 각 자리의 수가 서로 다르면 1로 하고, 같으면 0으로 하는 것이다. ^연산자를 이용하여 비트단위 XOR연산을 할 수 있다.

a, b = map(int,input().split())
print(a ^ b)

 

1063 : [기초-삼항연산] 두 정수 입력받아 큰 수 출력하기 (설명)

파이썬에서는 삼항연산을 다음과 같이 수행한다.

참일때 반환값 if 조건 else 거짓일때 반환값

따라서 정답은 다음과 같다.

a,b = map(int, input().split())
print(a if a > b else b)

 

1064 : [기초-삼항연산] 정수 3개 입력받아 가장 작은 수 출력하기 (설명)

a와 b중 작은 값을 삼항 연산으로 구하는 식을 괄호에 넣어서 괄호를 c와 비교하는 방식으로 삼항 연산을 구성할 수 있다.

a,b,c = map(int, input().split())
print((a if a < b else b) if (a if a < b else b) < c else c)

 

1065 : [기초-조건/선택실행구조] 정수 3개 입력받아 짝수만 출력하기 (설명)

if 조건문을 활용해서 입력받은 각 수가 짝수일 때만 출력하도록 한다. 짝수는 2로 나눴을 때 나머지가 0이면 짝수이다.

a,b,c = map(int, input().split())
if a % 2 == 0:
    print(a)
if b % 2 == 0:
    print(b)
if c % 2 == 0:
    print(c)

 

1066 : [기초-조건/선택실행구조] 정수 3개 입력받아 짝/홀 출력하기 (설명)

각 변수마다 if~else문으로 짝이면 even을 출력하고, 아니면 odd를 출력한다.

a,b,c = map(int, input().split())
if a % 2 == 0:
    print('even')
else:
    print('odd')
if b % 2 == 0:
    print('even')
else:
    print('odd')
if c % 2 == 0:
    print('even')
else:
    print('odd')

 

1067 : [기초-조건/선택실행구조] 정수 1개 입력받아 분석하기 (설명)

if 조건문으로 먼저 입력받은 정수가 양수인지 음수인지 확인하여 출력하고, 그 안에서 다시 if문으로 짝수인지 홀수인지 확인하여 출력한다.

a = int(input())
if a > 0 :
    print('plus')
    if a % 2 == 0:
        print('even')
    else:
        print('odd')
else:
    print('minus')
    if a % 2 == 0:
        print('even')
    else:
        print('odd')

 

1068 : [기초-조건/선택실행구조] 정수 1개 입력받아 평가 출력하기(설명)

if 문으로 입력받은 점수를 비교하여 평가를 출력해야 한다. 문제와 같이 검사해야 할 조건이 많을 때는 if 다음에 elif를 사용하면 if 조건이 거짓일 경우 순차적으로 아래로 내려오며 elif의 조건식을 검사한다.

score = int(input())
if score < 40:
    print('D')
elif score < 70:
    print('C')
elif score < 90:
    print('B')
else:
    print('A')

 

1069 : [기초-조건/선택실행구조] 평가 입력받아 다르게 출력하기(설명)

이 문제도 위와 같이 if~elif~else 문을 사용하여 출력할 수 있다. 입력받은 평가를 비교하면서 그에 맞는 값을 출력하고 나머지 문자들은 else에서 처리한다.

grade = input()

if grade == 'A':
    print('best!!!')
elif grade == 'B':
    print('good!!')
elif grade == 'C':
    print('run!')
elif grade == 'D':
    print('slowly~')
else:
    print('what?')

 

1070 : [기초-조건/선택실행구조] 월 입력받아 계절 출력하기(설명)

월이 입력되면 if 조건식에 or 연산을 이용하여 해당하는 계절을 출력할 수 있다.

month = int(input())

if month == 12 or month == 1 or month == 2:
    print('winter')
elif month == 3 or month == 4 or month == 5:
    print('spring')
elif month == 6 or month == 7 or month == 8:
    print('summer')
else:
    print('fall')

 

1071 : [기초-반복실행구조] 0입력될 때까지 무한 출력하기1(설명)

파이썬에서는 goto문이 없다. 입력 예시를 보니 정수가 한번에 입력되기 때문에 한 변수 x에 입력 받아서 for 반복문으로 반복하며 x의 각 요소들이 0인지 비교하고, 0이 아니면 그 요소를 출력하고, 0이면 반복문을 종료한다.

x = map(int, input().split())

for i in x:
    if i == 0:
        break
    else:
        print(i)

 

1072 : [기초-반복실행구조] 정수 입력받아 계속 출력하기(설명)

입력 받을 정수의 개수를 입력받고, 정수들을 하나의 변수에 입력받는다. 정수들을 입력받은 변수를 for 반복문으로 반복하며 각 요소를 출력한다.

n = int(input())
num = map(int, input().split())
for i in num:
    print(i)

 

1073 : [기초-반복실행구조] 0입력될 때까지 무한 출력하기2(설명)

1071번 문제와 동일하다. 1071번 문제는 goto문을 사용하는것이 문제의 의도인듯 했으나 파이썬에서는 goto는 사용할 수 없어 for 문으로 풀었다. 이 문제도 1071번과 동일한 코드를 사용하겠다.

x = map(int, input().split())

for i in x:
    if i == 0:
        break
    else:
        print(i)

 

1074 : [기초-반복실행구조] 정수 1 개 입력받아 카운트다운 출력하기1(설명)

while문을 이용하여 해결할 수 있다. while 조건식에 입력받은 정수가 0이 될 때 까지 반복 하도록 하고, 입력받은 정수를 출력하고 거기서 1을 빼는 것을 반복한다.

n = int(input())
while n != 0:
    print(n)
    n -= 1

 

1075 : [기초-반복실행구조] 정수 1 개 입력받아 카운트다운 출력하기2(설명)

이번에는 for 반복문을 이용하여 range(입력값, 끝날값, 증가시킬 크기)와 같은 형식으로 반복 실행할 수 있는데 1씩 줄여야 하므로 증가시킬 크기에 -1을 넣으면 된다. 그리고 끝날 값은 실제 출력값보다 1 작게 넣어줘야 한다. 첫번째 출력값도 입력값보다 1 작으므로 입력값 - 1 을 넣어줘야 한다.

n = int(input())

for i in range(n-1, -1, -1):
    print(i)

 

1076 : [기초-반복실행구조] 문자 1 개 입력받아 알파벳 출력하기(설명)

영소문자는 97 부터 시작한다. 입력한 값을 아스키 코드 10진값으로 바꾸고 97 부터 입력값을 바꾼 아스키 코드 값까지 반복하여 chr()로 문자로 출력하면 된다. 공백을 두고 1열로 출력해야하므로  print에 sep = ' '을 설정해줘야 한다.

a = input()
a = ord(a)
for i in range(97, a+1):
    print(chr(i), sep = ' ')

 

1077 : [기초-반복실행구조] 정수 1 개 입력받아 그 수까지 출력하기(설명)

for문으로 0부터 입력받은 수까지 출력할 수 있다. range로 범위 설정을 할때는 출력하려고 하는 수보다 1 크게 설정해야 한다.

n = int(input())
for i in range(0, n+1):
    print(i)

1001 : [기초-출력] 출력하기01(설명)

파이썬을 사용하기 때문에 print()함수를 사용하였다.

print('hello')

 

 1002 : [기초-출력] 출력하기02(설명)

print()함수로 주어진 문장을 그대로 출력하면 된다.

print('Hello World')

 

 1003 : [기초-출력] 출력하기03(설명)

줄을 바꿀 때는 개행문자 \n을 사용하면 된다.

print('Hello\nWorld')

 

1004 : [기초-출력] 출력하기04(설명)

작은 따옴표를 출력하려면 문자열을 큰 따옴표로 감싸면 된다.

print("'Hello'")

 

1005 : [기초-출력] 출력하기05(설명)

큰 따옴표를 출력하려면 문자열을 작은따옴표로 감싸면 된다.

print('"Hello World"')

 

1006 : [기초-출력] 출력하기06(설명)

큰 따옴표도 출력해야 하므로 작은따옴표로 문자열을 감싸서 출력한다.

print('"!@#$%^&*()"')

 

1007 : [기초-출력] 출력하기07(설명)

print함수에서 역슬래쉬 하나를 출력하려면 두개를 써야 한다.

print('"C:\\Download\\hello.cpp"')

 

1008 : [기초-출력] 출력하기08(설명)

문제에서 제시한 특수문자는 유니코드를 이용하여 출력할 수 있다고 한다. 

문제에서 준 활용할 수 있는 유니코드 표는 다음과 같다.

유니코드를 출력하려면 \u코드번호 와 같은 형식으로 출력할 수 있다. 위 코드표에서 유니코드를 찾아 코드를 작성하면 다음과 같다.

print('\u250C\u252C\u2510')
print('\u251C\u253C\u2524')
print('\u2514\u2534\u2518')

 

1010 : [기초-입출력] 정수 1개 입력받아 그대로 출력하기(설명)

파이썬에선 입력받은 값은 문자형이기 때문에 정수형으로 형변환을 해준 후 출력해야 한다.

n = int(input())
print(n)

 

1011 : [기초-입출력] 문자 1개 입력받아 그대로 출력하기(설명)

입력받은 값은 정수형이기 때문에 그대로 출력하면 된다.

c = input()
print(c)

 

1012 : [기초-입출력] 실수 1개 입력받아 그대로 출력하기(설명)

입력받은 값을 실수형으로 형변환 하고 출력하면 된다. 답을 제출하니 오답이라 나와서 테스트 케이스들을 확인하니 소숫점이 6자리로 고정인것 같다. 실수를 출력할때 format(실수, ".자리수f")와 같은 형식으로 출력하면 원하는 소숫점 자리수 만큼 출력할 수 있다. 정답은 다음 코드와 같다.

f = float(input())
print(format(f, ".6f"))

 

1013 : [기초-입출력] 정수 2개 입력받아 그대로 출력하기(설명)

map을 활용하여 한번에 두 개의 정수를 입력받아 정수로 형변환 할 수 있다. split()은 공백을 기준으로 입력값을 구분한다.

n1, n2 = map(int, input().split())
print(n1,n2)

 

1014 : [기초-입출력] 문자 2개 입력받아 순서 바꿔 출력하기(설명)

문자 두개를 입력 받고, 출력할 때 변수의 순서를 지정하면 된다.

a, b = input().split()
print(b,a)

 

1015 : [기초-입출력] 실수를 입력받아 둘째 자리까지 출력하기(설명)

format(실수, ".자리수f")와 같은 형식으로 출력하면 지정한 자리수 만큼 반올림되어 출력된다.

f = float(input)
print(format(f, ".2f")

 

1017 : [기초-입출력] 정수 1개 입력받아 3번 출력하기(설명)

입력받은 정수를 저장한 변수를 3번 출력하면 된다.

 

1018 : [기초-입출력] 시간 입력받아 그대로 출력하기(설명)

split()괄호 안에 ':'을 넣으면 :을 기준으로 구분된다. print()에 .format()을 사용하면 문자열의 중괄호 안에 format의 인자로 넣은 변수가 순서대로 들어간다.

h, m = map(int, input().split(':'))
print('{}:{}'.format(h,m))

 

1019 : [기초-입출력] 연월일 입력받아 그대로 출력하기(설명)

split('.')로 입력값을 .을 기준으로 구분한다. 한 자리 수면 십의 자리에 0을 채워야 하는데, 중괄호에 format으로 값을 대입할 때 {:02d}와 같은 방식으로 자리수를 맞추면 된다. 두자리수면 그대로 출력되고, 한 자리 수면 2자리로 맞춰준다. 처음에 실패해서 테스트 케이스를 살펴보니 년도 4자리로 맞춰줘야 하는 것 같다.

y, m, d = map(int, input().split('.'))
print('{:04d}.{:02d}.{:02d}'.format(y,m,d))

 

1020 : [기초-입출력] 주민번호 입력받아 형태 바꿔 출력하기(설명)

 

입력값을 '-'을 기준으로 구분하여 각 변수에 저장하고, 출력할 때 붙여서 출력하면 된다. print함수의 sep는 두 개 이상의 변수를 출력할 때 구분 문자를 지정하는데, 기본값은 공백이였지만, sep=''으로 공백을 없애면 된다.

j1, j2 = input().split('-')
print(j1,j2,sep='')

 

1021 : [기초-입출력] 단어 1개 입력받아 그대로 출력하기(설명)

변수 하나에 입력값을 저장하고 print로 저장한 변수를 출력하면 된다.

s = input()
print(s)

 

1022 : [기초-입출력] 문장 1개 입력받아 그대로 출력하기(설명)

위 문제와 같이 변수 하나에 입력받아 그대로 출력하면 된다.

s = input()
print(s)

 

1023 : [기초-입출력] 실수 1개 입력받아 부분별로 출력하기(설명)

입력값을 '.'으로 구분하여 각 변수에 저장하여 print로 각 줄에 출력하면 된다.

n, f = input().split('.')
print(n)
print(f)

 

1024 : [기초-입출력] 단어 1개 입력받아 나누어 출력하기(설명)

입력받은 단어를 변수에 저장하고, for 반복문을 이용하여 한줄에 한 문자씩 출력할 수 있다. 테스트 케이스를 확인해보니 출력한 문자를 작은 따옴표로 감싸고 있었기에 format을 이용하여 작은따옴표 안에 넣어줬다

word = input()
for i in word:
    print("'{}'".format(i))

위와 같은 방식으로 반복문을 사용하면 word변수에 들어있는 각 문자가 반복을 하며 순서대로 i에 들어간다.

 

1025 : [기초-입출력] 정수 1개 입력받아 나누어 출력하기(설명)

숫자를 입력받고, 반복문으로 한자리씩 숫자의 자리에 맞게 곱하여 [] 안에 넣어 출력하였다. cnt는 자리수에 맞게 곱할 숫자인데, 입력받은 숫자가 5자리 이기 때문에 10000에서 시작한다. 반복 횟수를 거듭할수록 10씩 나눠주고 정수를 나누면 실수형이 되기 때문에 출력할때 다시 한번 정수형으로 형변환을 해줬다.

n = input()
cnt = 10000
for i in n:
    pr_num = int(i) * cnt
    print('[{}]'.format(int(pr_num)))
    cnt /= 10

 

1026 : [기초-입출력] 시분초 입력받아 분만 출력하기(설명)

split()으로 시, 분, 초를 구분하여 입력받고, 분을 저장한 변수만 출력한다.

h, m, s = map(int, input().split(':'))
print(m)

 

1027 : [기초-입출력] 년월일 입력받아 형식 바꿔 출력하기(설명)

입력값은 split()으로 구분 받아서 각 변수에 저장한다. 저장한 변수를 .format()으로 순서를 바꾸고 {:02d}와 같은 형식으로 자리수를 바꾸어 출력하면 된다.

y, m, d = map(int, input().split('.'))
print('{:02d}-{:02d}-{:04d}'.format(d,m,y))

 

1028 : [기초-데이터형] 정수 1개 입력받아 그대로 출력하기2(설명)

파이썬에서는 int자료형의 범위가 더 넓기 때문에 입력 받은 값을 int로 저장하여 출력하면 된다.

n = int(input())
print(n)

 

1029 : [기초-데이터형] 실수 1개 입력받아 그대로 출력하기2(설명)

float로 실수를 입력받아 format(실수, ".11f")로 11자리까지 출력하면 된다.

f = float(input())
print(format(f, ".11f"))

 

1030 : [기초-데이터형] 정수 1개 입력받아 그대로 출력하기3(설명)

파이썬에서는 int범위를 벗어나도 type를 확인해보면 int로 나오기 때문에 입력값을 int로 저장하여 그대로 출력하면 된다.

n = int(input())
print(n)

 

1031 : [기초-출력변환] 10진정수 1개 입력받아 8진수로 출력하기(설명)

파이썬에서는 8진수를 출력하면 0o가 앞에 붙는데, 출력 예시를 보면 0o가 없다. 정수로 입력받은 입력값을 oct()로 8진수로 형변환 한 후 str로 다시 형변환을 하여 출력할 때 [2:]인덱스를 활용하여 0o를 제외한 2번 인덱스 부터 출력하였다.

n = int(input())
n = str(oct(n))
print(n[2:])

 

1032 : [기초-출력변환] 10진정수 1개 입력받아 16진수로 출력하기1(설명)

16진수를 출력할 때도 0x가 앞에 붙는데, 출력 예시를 보면 0x가 없기 때문에 hex()로 16진수로 형변환 한 후, str로 형변환 하여 2번 인덱스부터 출력하면 된다.

n = int(input())
n = str(hex(n))
print(n[2:])

 

1033 : [기초-출력변환] 10진정수 1개 입력받아 16진수로 출력하기2(설명)

대문자로 출력해야 하므로 위와 같이 출력하고, 출력 자료형이 str인 상태이기 때문에 .upper()를 사용하여 대문자로 출력할 수 있다.

n = int(input())
n = str(hex(n))
print(n[2:].upper())

 

1034 : [기초-출력변환] 8진정수 1개 입력받아 10진수로 출력하기(설명)

파이썬에서는 0o가 앞에 붙어있어야 8진수로 인식하기 때문에 인식 받은 값에 0o를 붙여줘야 한다. int로 형변환 할때 int(변수, 현재진수)의 형식으로 형변환을 하면 변수의 값을 지정한 진법에서 int형으로 형변환 된다. 형변환 한 값을 출력하면 된다.

n = input()
n = '0o' + n
n = int(n, 8)
print(n)

 

Unit 35. 클래스 속성과 정적, 클래스 메서드 사용하기

35.1 클래스 속성과 인스턴스 속성 알아보기

속성에는 클래스 속성과 인스턴스 속성 두 가지 종류가 있다. __init__메서드에서 만들었던 속성은 인스턴스 속성이다.

 

35.1.1 클래스 속성 사용하기

클래스 속성은 다음과 같이 클래스에 바로 속성을 만든다.

class 클래스이름:
    속성 = 값

다음은 사람 클래스에 클래스 속성으로 가방 속성을 만들고, 가방에 물건을 넣는 put_bag메서드를 사용하는 코드이다.

class Person:
    bag = []

    def put_bag(self, stuff):
        self.bag.append(stuff)

james = Person()
james.put_bag('book')

maria = Person()
maria.put_bag('key')

print(james.bag)
print(maria.bag)

결과를 보면 두 인스턴스에 넣었던 물건이 합쳐저서 출력되는것을 확인할 수 있다.

클래스 속성은 클래스에 속해있으면서 모든 인스턴스에서 공유된다.

클래스 속성에 접근할 때는 다음과 같이 클래스 이름으로 접근하면 코드가 좀 더 명확해진다.

class Person:
    bag = []
 
    def put_bag(self, stuff):
        Person.bag.append(stuff)

클래스 밖에서도 다음과 같이 클래스 이름으로 클래스 속성에 접근하면 된다.

print(Person.bag)

 

속성, 메서드 이름을 찾는 순서

파이썬에서는 속성, 메서드 이름을 찾을 때 인스턴스, 클래스 순서로 찾는다. 그래서 인스턴스 속성이 있는지 먼저 확인하고, 클래스 속성을 찾는다. 인스턴스와 클래스에서 __dict__ 속성을 출력해보면 현재 인스턴스와 클래스의 속성을 딕셔너리로 확인할 수 있다.

print(james.__dict__)
print(Person.__dict__)

 

35.1.2 인스턴스 속성 사용하기

bag속성을 여러 객체가 공유하지 않으려면 bag를 인스턴스 속성으로 만들면 된다.

class Person:
    def __init__(self):
        self.bag = []

    def put_bag(self, stuff):
        self.bag.append(stuff)

james = Person()
james.put_bag('book')

maria = Person()
maria.put_bag('key')

print(james.bag)
print(maria.bag)

이제 james.bag과 maria.bag을 출력해보면 각자 넣은 물건만 출력된다.

인스턴스 속성은 인스턴스 별로 독립되어 있으며 서로 영향을 주지 않는다.

 

35.1.3 비공개 클래스 속성 사용하기

클래스 속성도 비공개 속성을 만들수 있다. 클래스 속성을 만들 때 __속성처럼 앞에 밑줄 두 개로 시작하면 비공개 속성이된다. 비공개 클래스 속성은 클래스 안에서만 접근할 수 있고, 밖에서는 접근할 수 없다.

class 클래스이름:
    __속성 = 값

클래스에서 공개하고 싶지 않은 속성이 있다면 비공개 클래스를 사용해야 한다. 다음은 기사 게임 캐릭터의 아이템 최대 개수를 비공개 클래스 속성으로 작성한 것이다.

class Knight:
    __item_limit = 10

    def print_limit(self):
        print(Knight.__item_limit)

x = Knight()
x.print_limit()

print(x.__item_limit)

위와 같이 비공개 클래스 속성은 클래스 밖에서는 접근할 수 없고, 클래스 내부의 메서드에서만 접근이 가능하다.

 

클래스와 메서드의 독스트링 사용하기

함수와 마찬가지로 클래스와 메서드 에서도 클론 바로 다음 줄에 """ """ 또는 ''' '''로 독스트링을 사용할 수 있고, 클래스.__doc__, 메서드.__doc__와 같이 출력할 수 있다.

print(Person.__doc__)
print(james.put_bag.__doc__)

 

35.2 정적 메서드 사용하기

인스턴스를 통하지 않고, 클래스에서 바로 호출할 수 있는 정적 메서드와 클래스 메서드가 있다.

정적 메서드는 다음과 같이 메서드 위에 @staticmethod를 붙인다. 이때 정적 메서드는 매개변수에 self를 지정하지 않는다.

class 클래스이름:
    @staticmethod
    def 메서드(매개변수1, 매개변수2)
        코드

@staticmethod 처럼 앞에 @가 붙은 것을 데코레이터라고 하며 메서드(함수)에 추가 기능을 구현할 때 사용한다.

다음은 덧셈과 곱셈을 하는 클래스이다.

class Calc:
    @staticmethod
    def add(a,b):
        print(a + b)

    @staticmethod
    def mul(a,b):
        print(a*b)


Calc.add(5,3)
Calc.mul(5,3)

정적 메서드는 위와 같이 호출할 때 클래스에서 바로 메서드를 호출할 수 있다.

정적 메서드는 self를 받지 않기 때문에 인스턴스 속성에는 접근할 수 없다. 그래서 보통 정적 메서드는 인스턴스 속성, 인스턴스 메서드가 필요 없을 때 사용한다.

정적 메서드는 메서드의 실행이 외부 상태에 영향을 끼치지 않는 순수 함수를 만들 때 사용한다. 순수 함수는 부수 효과가 없고, 입력값이 같으면 언제나 같은 값을 반환한다. 정적 메서드는 인스턴스의 상태를 변화시키지 않는 메서드를 만들 때 사용한다.

파이썬의 자료형도 인스턴스 메서드와 정적 메서드로 나누어져 있다. 인스턴스의 내용을 변경시켜야 할 땐 인스턴스 메서드를 사용하고, 인스턴스 내용과 상관 없이 결과만 구하면 될 때는 정적 메서드를 이용한다.

 

35.3 클래스 메서드 사용하기

클래스 메서드는 메서드 위에 @classmethod를 붙인다. 첫번째 매개변수로는 cls를 지정해야 한다.(cls는 class에서 따온 것이다.)

class 클래스이름:
    @classmethod
    def 메서드(cls, 매개변수1, 매개변수2)
        코드

다음은 사람 클래스에 인스턴스가 몇 개 만들어졌는지 출력하는 클래스 메서드를 만들었다.

class Person:
    count = 0

    def __init__(self):
        Person.count += 1

    @classmethod
    def print_count(cls):
        print('{} people'.format(cls.count))

james = Person()
maria = Person()

Person.print_count()

인스턴스가 만들어 질때마다 __init__메서드로 클래스 속성 count에 1을 더해준다.

클래스 메서드의 매개변수 cls는 현재 클래스가 들어오기 때문에 cls.count와 같은 방식으로 cls로 클래스 속성 count 에 접근할 수 있다.

print_count 메서드는 클래스 메서드이기 때문에 Person.print_count처럼 클래스로 호출해야 한다.

Person으로 인스턴스를 두 개 만들었기 때문에 '2 people'이 출력된다.

클래스 메서드는 메서드 안에서 클래스 속성, 클래스 메서드에 접근해야 할 때 사용한다.

다음과 같이 cls를 사용하여 현재 클래스의 인스턴스를 만들 수도 있다. cls()는 Person()와 같다.

    @classmethod
    def create(cls):
        p = cls()
        return p


35.4 퀴즈

정답은 a 이다.

정답은 c이다.

정답은 c,e이다. 클래스 메서드의 첫번째 매개변수는 cls이다. 클래스 메서드는 위에 @classmethod를 붙여야 한다.

 

35.5 연습문제: 날짜 클래스 만들기

정답은 다음 코드와 같다.

    @staticmethod
    def is_date_valid(date):
        year, month, day = map(int, date.split('-'))
        return 0 < month <= 12 and 0 < day <= 31 

클래스.메서드의 형식으로 호출했기 때문에 정적 메서드를 사용하여 날짜 형식에 따라 True와 False를 반환하도록 하였다.

 

35.6 심사문제: 시간 클래스 만들기

정답은 다음 코드와 같다.

    @staticmethod
    def is_time_valid(time):
        h, m, s = map(int, time.split(':'))
        return 0 < h <=24 and 0 <= m <= 59 and 0 <= s <= 60
    
    @classmethod
    def from_string(cls, time):
        h, m, s = map(int, time.split(':'))
        t = cls(h,m,s)
        return t

is_time_valid는 조건문에 들어가 True와 False만 반환하면 되기에 정적 메서드로 입력값을 시, 분, 초로 분리하여 각 값이 올바른 형식인지 검사하였다. from_string는 인스턴스 속성이 필요하기 때문에 클래스 메서드로 메서드 내에서 입력값을 시, 분, 초로 분리하여 새로운 인스턴스를 만들어 반환하였다.

 

Unit 36. 클래스 상속 사용하기

클래스 상속은 물려받은 기능을 그대로 유지한 채로 다른 기능을 추가할 때 사용하는 기능이다. 기능을 물려주는 클래스를 기반 클래스, 상속을 받아 새롭게 만드는 클래스를 파생 클래스라고 한다. 보통 기반클래스를 부모클래스 또는 슈퍼클래스 라고 부르고, 파생클래스를 자식클래스 또는 서브클래스 라고 부른다.

새로운 기능이 필요할 때 마다 계속 클래스를 만들면 중복되는 부분도 계속해서 만들어야 한다. 이때 상속을 사용하면 중복되는 기능을 만들지 않아도 되기 때문에 상속클래스는 기존 기능을 재사용 할 수 있어서 효율적이다.

 

36.1 사람 클래스로 학생 클래스 만들기

클래스 상속은 다음과 같이 파생클래스를 만들 때 괄호를 붙이고 괄호 안에 기반클래스 이름을 넣는다.

class 기반클래스이름:
    코드
    
class 파생클래스이름(기반클래스이름):
    코드

 

다음은 사람클래스를 만들고 사람클래스를 상속받아 학생클래스를 만든 것이다.

class Person():
    def greeting(self):
        print('hello')
    
class Student(Person):
    def study(self):
        print('do study')

james = Student()
james.greeting()
james.study()

Student 클래스를 만들 때 괄호 안에 기반클래스의 이름인 Person을 넣었다.

Student 클래스에는 greeting메서드가 없지만, Person클래스를 상속받아 Person클래스의 메서드를 호출할 수 있다.

이후 Student클래스만 갖고 있는 study 메서드를 호출하였다.

클래스 상속은 기반 클래스의 기능을 유지하며 새로운 기능을 추가할 수 있다. 클래스 상속은 연관되면서 동등한 개념일 때 사용한다.

클래스의 상속 관계를 확인하려면 issubclass를 사용한다. 기반클래스의 파생클래스가 맞으면 True, 아니면 False를 반환한다.

함수 사용법은 다음과 같다.

issubclass(파생클래스, 기반클래스)

 

36.2 상속 관계와 포함관계 알아보기

36.2.1 상속관계

위에서 만든 Student 클래스는 Person 클래스를 상속받아 만들어졌다. 

학생 Student는 사람 Person과 같은 종류이며 동등한 관계이다.

상속은 명확하게 같은관계이며 동등한 관계일 때 사용한다. 영어로는 is-a 관계라고 부른다.

 

36.2.2 포함관계

class Person:
    def greeting(self):
        print('안녕하세요.')
 
class PersonList:
    def __init__(self):
        self.person_list = []	# 리스트 속성에 Person인스턴스를 넣음
 
    def append_person(self, person):
        self.person_list.append(person)

위 코드의 경우 상속을 사용하지 않고, 속성에 인스턴스를 넣어서 관리하므로 PersonList가 Person을 포함하고 있다. 두 클래스는 동등한 관계가 아니라 포함관계이다. 포함 관계는 영어로 has-a 관계라고 부른다.

같은 종류의 동등한 관계라면 상속을 사용하고, 그 이외에는 속성에 인스턴스를 넣는 포함방식을 사용하면 된다.

 

36.3 기반 클래스의 속성 사용하기

class Person:
    def __init__(self):
        print('Person __init__')
        self.hello = 'Hello!'
    
class Student(Person):
    def __init__(self):
        print('Student __init__')
        self.school = 'python dojang'

james = Student()
print(james.school)
print(james.hello)

위 코드를 실행해보면 기반클래스의 hello속성에 접근하려고 하면 에러가 발생한다. 기반 클래스 Person의 __init__메서드가 호출되지 않았기 때문이다. Person클래스의 __init__가 실행되지 않으면 self.hello도 실행되지 않아서 속성이 만들어지지 않는다.

 

36.3.1 super()로 기반 클래스 초기화하기

super()를 사용하여 기반 클래스의 __init__ 메서드를 호출할 수 있다. 다음과 같이 super() 뒤에 점을 붙여서 메서드를 호출하는 방식이다.

super().메서드
class Person:
    def __init__(self):
        print('Person __init__')
        self.hello = 'Hello!'
    
class Student(Person):
    def __init__(self):
        print('Student __init__')
        super().__init__()
        self.school = 'python dojang'

james = Student()
print(james.school)
print(james.hello)

super().__init__() 처럼 기반 메서드의 __init__ 메서드를 호출해주면 기반 클래스가 초기화되어 속성이 만들어진다. 실행 결과를 보면 Person __init__ 과 Student __init__ 이 둘다 출력되었다.

 

36.3.2 기반 클래스를 초기화하지 않아도 되는 경우

파생클래스에서 __init__메서드를 생략하면 기반클래스의 __init__이 자동 호출되므로 super()은 사용하지 않아도 된다.

class Person:
    def __init__(self):
        print('Person __init__')
        self.hello = 'Hello!'
    
class Student(Person):
    pass

james = Student()
print(james.hello)

super()은 괄호 안에 파생클래스와 self를 넣어서 현재 클래스가 어떤 클래스인지 명확하게 표시하는 방법도 있다. super()와 기능은 같다.

super(파생클래스, self).메서드

 

36.4 메서드 오버라이딩 사용하기

메서드 오버라이딩은 파생클래스에서 기반 클래스의 메서드를 새로 정의하는 것이다.

class Person:
    def greeting(self):
        print('hello')
    
class Student(Person):
    def greeting(self):
        print('hello i am student')

james = Student()
james.greeting()

Person 클래스에 greeting메서드가 있는 상태에서 파생클래스인 Student에도 greeting 메서드를 만들었다.

Student의 greeting메서드를 호출하니 hello i am student가 출력되었다. 

오버라이딩은 무시하다, 우선하다 라는 뜻을 갖고 있어 기반클래스의 메서드를 무시하고, 파생클래스의 메서드를 출력하였다.

메서드 오버라이딩은 프로그램에서 어떤 기능이 같은 메서드 이름으로 계속 사용되어야 할 때 사용된다. 

Person클래스와 Student클래스의 greeting메서드를 보면 hello라는 문구가 중복된다. 이와 같은 경우 오버라이딩 된 메서드에서 super()로 기반클래스의 메서드를 호출하여 재활용하면 중복을 줄일 수 있다.

class Person:
    def greeting(self):
        print('hello')
    
class Student(Person):
    def greeting(self):
        super().greeting()
        print('i am student')

james = Student()
james.greeting()

중복되는 기능은 파생 클래스에서 다시 만들지 않고, 기반 클래스의 기능을 사용하면 된다. 메서드 오버라이딩은 원래 기능을 유지하며 새로운 기능을 덧붙일 때 사용한다.

 

36.5 다중 상속 사용하기

다중 상속은 여러 클래스로부터 상속을 받아서 파생 클래스를 만드는 방법이다. 다음과 같이 클래스를 만들때 괄호 안에 상속받는 클래스 이름을 콤마로 구분하여 넣는다.

class 기반클래스이름1:
    코드
 
class 기반클래스이름2:
    코드
 
class 파생클래스이름(기반클래스이름1, 기반클래스이름2):
    코드

다음은 사람클래스와 대학교 클래스를 만든 뒤 다중 상속으로 상속받는 학생 클래스를 만든 것이다.

class Person:
    def greeting(self):
        print('hello')
    
class University:
    def manage_credit(self):
        print('Manage Credit')

class Undergraduate(Person, University):
    def study(self):
        print('do study')

james = Undergraduate()
james.greeting()
james.manage_credit()
james.study()

Undergraduate 클래스는 기반 클래스로 Person클래스와 University 클래스를 상속받아서 위와 같이 Undergraduate 클래스의 인스턴스로 Person의 greeting과 University의 manage_credit 메서드를 호출할 수 있다.

 

36.5.1 다이아몬드 상속

class A:
    def greeting(self):
        print('hello A')

class B(A):
    def greeting(self):
        print('hello B')

class C(A):
    def greeting(self):
        print('hello C')

class D(B, C):
    pass

x = D()
x.greeting()

기반 클래스 A가 있고, B, C는 A를 상속 받고, D는 다시 B, C를 상속받는다. 그림으로 이 관계를 확인하면 다음과 같다.

클래스 간의 관계가 다이아몬드 처럼 생겨서 다이아몬드 상속 이라고 부른다. A,B,C 모두 greeting라는 메서드를 갖고 있고, A를 상속받은, B,C를 상속받은 D는 어떤 클래스의 메서드를 호출해야할지 매우 애매한 상황에 놓인다. 프로그램에서 이렇게 명확하지 않은 상황은 큰 문제를 일으킬 수 있기 때문에 죽음의 다이아몬드 라고도 불린다.

 

36.5.2 메서드 탐색 순서 확인하기

파이썬에서 이러한 다이아몬드 상속의 해결책을 제시하기 위해 메서드 탐색 순서(Method Resolution Order, MRO)를 따른다. 클래스 D에서 mro를 사용하면 메서드 탐색 순서가 나온다.

print(D.mro())

MRO에 따르면 D메서드의 호출 순서는 자기자신 D, 그 다음이 B이다. 따라서 D로 greeting를 호출하면 B의 메서드가 호출된다.

파이썬에서 상속 관계가 복잡하게 얽혀있다면 MRO를 살펴보는 것이 좋다.

 

파이썬에서 object는 모든 클래스의 조상이다. 모든 클래스는 object클래스를 상속받기 때문에 기본적으로 object를 생략했다.

class X(object):	# object는 모든 클래스에 기본적으로 상속되어 있어 보통은 생략한다.
    pass

 

36.6 추상 클래스 사용하기

추상클래스는 메서드의 목록만 가진 클래스이며 상속받는 클래스에서 메서드 구현을 강제하기 위해 사용한다.

추상 클래스를 만드려면 import로 abc모듈을 가져와야 한다. 그리고 클래스의 괄호 안에 metaclass=ABCMeta를 지정하고, 메서드를 만들 때 위에 @abstractmethod를 붙여서 추상 메서드로 지정해야 한다.

from abc import *

class 추상클래스이름(metaclass=ABCMeta):
    @abstractmethod
    def 메서드이름(self):
        코드
from abc import *
 
class StudentBase(metaclass=ABCMeta):
    @abstractmethod
    def study(self):
        pass
 
    @abstractmethod
    def go_to_school(self):
        pass
 
class Student(StudentBase):
    def study(self):
        print('do study')
 
james = Student()
james.study()

위 코드를 실행하면 다음과 같은 에러가 발생한다. 추상클래스에서 지정한 go_to_school메서드가 상속받은 클래스인 Student에서 구현하지 않았기 때문이다.
추상클래스를 상속받았다면 @abstractmethod가 붙은 추상 메서드를 모두 구현해야 한다. 다음과 같이 코드를 수정하면 에러 없이 실행 가능하다.

from abc import *
 
class StudentBase(metaclass=ABCMeta):
    @abstractmethod
    def study(self):
        pass
 
    @abstractmethod
    def go_to_school(self):
        pass
 
class Student(StudentBase):
    def study(self):
        print('do study')
    
    def go_to_school(self):
        print('go school')
 
james = Student()
james.study()
james.go_to_school()

추상클래스는 파생 클래스가 반드시 구현해야 하는 메서드를 정해줄 수 있다.

추상클래스의 추상메서드를 다 구현했는지 확인하는 시점은 파생클래스의 인스턴스를 만들 때 이다. 위 코드에서는 james = Student() 코드 부분이다.

 

36.6.1 추상메서드를 빈 메서드로 만드는 이유

추상 클래스는 인스턴스로 만들 수가 없다. 추상클래스를 인스턴스로 만드려고 하면 에러를 발생한다. 그래서 추상클래스의 추상 메서드는 빈 메서드로 만든다. 인스턴스를 만들 수 없기 때문에 추상메서드를 호출할 일도 없기 때문이다.

추상클래스는 오로지 상속에만, 파생클래스에서 반드시 구현해야 할 메서드를 정해줄 때만 사용한다.

 

36.7 퀴즈

정답은 c이다.

정답은 d이다.

정답은 a,d이다. 함수 이름을 같게 만드는것이 메서드 오버라이딩이다. 오버라이딩 된 메서드에서도 super()로 기반 클래스의 메서드를 호출할 수 있다.

정답은 b,e이다.

 

36.8 연습문제: 리스트에 기능 추가하기

정답은 다음 코드와 같다.

class AdvancedList(list):
    def replace(self, target, change):
        while target in self:
            self[self.index(target)] = change

list클래스를 상속받은 클래스에 replace 메서드를 만들어서 리스트에서 바꿀 요소를 지정한 요소로 바꾸는 메서드를 만들었다.

 

36.9 실습문제: 다중 상속 사용하기

정답은 다음 코드와 같다.

class Bird(Animal, Wing):
    def fly(self):
        print('날다')

Bird클래스는 Animal 과 Wing 클래스를 상속받으며 fly라는 메서드를 갖는다.

 

Unit 37. 두 점 사이의 거리 구하기

37.1.1 클래스로 점 구현하기

2차원 평면에서 위치를 표현하려면 x와 y값이 필요하다. 다음과 같이 Point2D로 점을 만드는 클래스를 구현하고 점 두 개를 만들었다.

class Point2D:
    def __init__(self, x, y):
        self.x = x
        self.y = y
 
p1 = Point2D(x=30, y=20)
p2 = Point2D(x=60, y=50)
 
print('p1: {} {}'.format(p1.x, p1.y))
print('p2: {} {}'.format(p2.x, p2.y))

2차원 평면에서 두 점을 표시해보면 다음 모양과 같다.

 

37.1.2 피타고라스의 정리로 두 점의 거리 구하기

두 점의 거리를 구하려면 피타고라스 정리를 이용해야 한다. 

피타고라스 정리는 a^2 * b^2 = c^2 이고, p1과 p2로 직각 삼각형을 그리면 다음과 같다.

피타고라스 정리를 이용하려면 먼저 변 a,b의 길이를 알아야 한다. a,b의 길이는 다음과 같다.

a = p2.x - p1.x
b = p2.y - p1.y

피타고라스 정리에서 c의 길이를 구하려면 제곱근을 구해야한다. 파이썬에서 제곱근은 math 모듈의 sqrt 함수를 이용하여 구할 수 있다.

p1과 p2의 거리를 구하는 코드는 다음과 같다.

import math

class Point2D:
    def __init__(self, x, y):
        self.x = x
        self.y = y
 
p1 = Point2D(x=30, y=20)
p2 = Point2D(x=60, y=50)
 
a = p2.x - p1.x
b = p2.y - p1.y

c = math.sqrt((a*a) + (b*b))
print(c)

거듭제곱을 구할 때는 math모듈의 pow 함수를 사용해도 된다.

math.pow(값,지수)
c = math.sqrt(math.pow(a, 2) + math.pow(b, 2))

또한 파이썬의 거듭제곱 연산자 **를 사용해도 된다.

c = math.sqrt((a ** 2) + (b ** 2))

 

절댓값 함수

내장함수 abs 또는 math 모듈의 fabs함수를 사용하면 양수 또는 음수를 절댓값으로 만들 수 있다.

abs(값)
math.fabs(값)

 

namedtuple 사용하기

collections모듈의 namedtuple함수를 사용하여 각 요소에 이름을 지정해 줄 수 있다. namedtuple은 자료형 이름과 요소의 이름을 지정하면 클래스를 생성해준다. 자료형 이름은 문자열, 요소의 이름은 문자열 리스트로 넣어줘야 한다.

클래스 = collections.namedtuple('자료형이름', ['요소이름1', '요소이름2'])

namedtuple로 생성한 클래스는 값을 넣어서 인스턴스를 만들 수 있다. 인스턴스.요소이름 또는 인스턴스[인덱스]형식으로 요소에 접근할 수 있다.

다음은 namedtuple를 이용하여 두 점 사이의 거리를 구한 것이다.

import math
import collections
 
Point2D = collections.namedtuple('Point2D', ['x', 'y']) 
 
p1 = Point2D(x=30, y=20) 
p2 = Point2D(x=60, y=50)   
 
a = p1.x - p2.x  
b = p1.y - p2.y   
 
c = math.sqrt((a * a) + (b * b))
print(c)    

 

37.2 연습문제: 사각형의 넓이 구하기

정답은 다음 코드와 같다.

wid = abs(rect.x2 - rect.x1)
hei = abs(rect.y2 - rect.y1)
area = wid * hei

높이와 밑변의 길이는 경우에 따라 음수가 될수 있기 때문에 abs함수를 이용하여 절댓값으로 만들었다.

 

37.3 두 점 사이의 거리 구하기

정답은 다음 코드와 같다.

length = 0
for i in range(len(p)-1):
    a = p[i+1].x - p[i].x
    b = p[i+1].y - p[i].y
    length += math.sqrt((a*a) + (b*b))

반복문을 활용해서 각 점사이의 거리를 구한 후 값들을 더해줬다.

Unit 32. 람다 표현식 사용하기

람다 표현식은 식 형태로 되어 있다고 해서 람다 표현식 이라고 부른다. 함수를 간편하게 작성할 수 있어서 다른 함수의 인수로 넣을 때 주로 사용한다. 

 

32.1 람다 표현식으로 함수 만들기

다음은 숫자를 받은 뒤 10을 더해서 반환하는 함수 이다.

람다 표현식은 lambda에 매개변수를 저장하고 :(클론) 뒤에 반환 값으로 사용할 식을 지정한다. 이 함수를 람다 표현식으로 작성하면 다음과 같다. 

실행을 해보면 함수 객체가 나오고, 이 상태로는 함수를 호출할 수 없다. 람다 표현식은 이름이 없는 익명 함수이기 때문이다. lamda로 만든 익명 함수를 호출하려면 다음과 같이 람다 표현식을 변수에 할당해야 한다.

위 람다 표현식은 매개변수로 x를 받고, x에 10을 더해 반환한다는 뜻이다.

 

32.1.1 람다 표현식 자체를 호출하기

람다표현식은 변수에 할당하지 않고, 람다표현식 자체를 괄호로 묶고, 다시 괄호를 붙여 괄호 안에 인수를 넣어 호출할 수 있다.

 

32.1.2 람다 표현식 안에서는 변수를 만들 수 없다

람다 표현식은 변수 표현식 안에서 새 변수를 만들 수 없다. 반환값 부분은 변수의 생성 없이 한 줄로 표현할 수 있어야 한다. 새 변수를 만드는 코드를 넣으면 에러를 발생한다.

표현식 밖에서 선언된 변수는 사용할 수 있다.

 

31.1.3 람다 표현식을 인수로 사용하기

def로 만든 함수에서 map을 사용하여 리스트를 plus_ten() 함수에 넣으면 매개변수에 10을 더해서 반환하므로 리스트의 각 요소들은 10씩 더해진다. 

map의 함수 자리에 람다 표현식을 넣어도 된다.

람다 표현식을 사용하면 세줄이였던 위의 코드를 1줄로 줄일 수 있다.

람다 표현식으로 매개변수가 없는 함수를 만들때는 lamba뒤에 아무것도 지정하지 않고 클론을 붙인 후 반환값을 지정한다.

 

32.2 람다 표현식과 map, filter, reduce 함수 사용하기

32.2.1 람다 표현식에 조건부 표현식 사용하기

람다 표현식에 조건부 표현식을 사용하는 방법은 다음과 같다.

lambda 매개변수들: 식1 if 조건식 else 식2

다음은 map을 사용하영 3의 배수를 문자열로 변환하는 것이다.

리스트의 요소를 각각 처리했기 때문에 반환값도 요소로 나온다. 요소가 3의 배수일 때는 문자열로 변환하였다.

람다 표현식 안에서 조건부 표현식(if, else)을 사용할 때는 클론을 붙이지 않는다. 조건부 표현식은 '식1 if 조건식 else 식2' 형식으로 사용하며 조건식이 참이면 식1을, 거짓이면 식2 를 사용한다. 람다 표현식에서 if를 사용하면 반드시 else를 사용해야 한다. else를 사용하지 않으면 에러가 발생한다.

람다 표현식에서는 elif를 사용할 수 없다. 따라서 람다 표현식에서는 다음과 같이 if를 연달아서 사용해야 한다. 

lambda 매개변수들: 식1 if 조건식1 else 식2 if 조건식2 else 식3

다음은 리스트에서 1은 문자열로 변환하고, 2는 실수로 변환하고, 3이상의 수는 10을 더하는 코드이다.

식이 알아보기 복잡하기 때문에 이와 같은 경우에는 코드의 길이가 길어지더라도 알아보기 쉽게 작성하는것이 좋다.

 

32.2.2 map에 객체를 여러 개 넣기

map은 리스트 등 반복 가능한 객체를 여러개 넣을 수 있다. 다음은 두 리스트의 요소들을 곱해서 새로운 리스트를 만든다.

리스트 두개를 처리할 때는 람다 표현식에서 매개변수를 두 개 지정하고, 리스트 두 개를 콤마로 구분해서 넣어준다. 람다 표현식의 매개변수의 개수에 맞게 반복 가능한 객체도 콤마로 구분하여 넣어주면 된다.

 

32.2.3 filter 사용하기

filter는 반복 가능한 객체에서 특정 조건에 맞는 요소만 가져온다. filter에 지정한 함수가 True를 반환할 때만 해당 요소를 가져온다.

다음은 def로 함수를 만들어 리스트에서 5보다 크고 10보다 작은 숫자만 가져오는 함수이다.

함수 f를 람다표현식으로 만들면 다음과 같다. 

람다 표현식에 조건식을 넣어 True인 요소만 가져오도록 하였다.

 

32.2.4 reduce 사용하기

reduce는 반복 가능한 객체의 각 요소를 지정된 함수로 처리한 뒤 이전 결과와 누적하여 반환하는 함수이다.

reduce는 내장함수가 아니라 functools모듈에 있다.

다음은 리스트에 저장된 요소를 순서대로 더한 뒤 누적한 결과를 반환하는 코드이다.

함수 f를 람다 표현식으로 만들어 reduce에 넣으면 다음과 같다.

매개변수 x,y를 지정하고 x와 y를 더한 값을 반환하도록 하였다.

 

32.3 퀴즈 

정답은 d 이다.

정답은 b이다.

정답은 c이다.

 

32.4 연습문제: 이미지 파일만 가져오기

정답은 다음 코드와 같다.

list(filter(lambda x: x.find('jpg') != -1 or x.find('png') != -1, files))

find()메서드는 찾는 문자열이 있으면 그 문자열의 인덱스를 반환하고, 없으면 -1을 반환한다. 둘중 하나만 참이 되면 되므로 or연산자를 사용하여 람다 표현식으로 조건식이 True 가 나오는 요소들을 가져왔다.

 

32.5 심사문제: 파일 이름을 한꺼번에 바꾸기 

정답은 다음 코드와 같다.

list(map(lambda x : '{0:03d}.{1}'.format(int(x.split('.')[0]), x.split('.')[1]), files))

files의 각 요소들을 .을 기준으로 나누어 숫자는 0을 채운 3자리로 만들어 다시 확장자를 더해서 리스트에 담았다.

 

Unit 33. 클로저 사용하기

33.1 변수의 사용 범위 알아보기 

파이썬 스크립트에서 변수를 만들면 다음과 같이 함수 안에서도 사용할 수 있다.

x = 10

def foo():
    print(x)

foo()
print(x)

위 변수 x처럼 함수를 포함하여 스크립트 전체에서 접근할 수 있는 변수를 전역변수 라고 하며, 전역 변수에 접근할 수 있는 범위를 전역 범위라 한다.

x를 함수 안에 지정하면 함수 밖에서는 x를 출력할 수 없다.

def foo():
    x = 10
    print(x)

foo()
print(x)

x가 정의되지 않았다는 에러가 발생한다. 변수 x는 함수 안에서 만들어 졌기 때문에 함수 안에서만 접근할 수 있는 지역변수이다. 지역변수를 접근할 수 있는 범위를 지역 범위라고 한다.

 

33.1.1 함수 안에서 전역 변수 변경하기

x = 10

def foo():
    x = 20
    print(x)

foo()
print(x)

위 코드는 함수 안에서 x의 값을 20으로 변경했지만, 함수 밖에서 출력하면 10이 나왔다. 함수 안의 x는 그 함수의 지역 변수이다. 함수 밖의 x와 함수 안의 x는 이름만 같을 뿐 서로 다른 변수이다. 

함수 안에서 전역변수의 값을 변경하려면 global이라는 키워드를 사용해야 한다. 

x = 10

def foo():
    global x
    x = 20
    print(x)

foo()
print(x)

함수 안에서 변수를 global로 지정하면 전역 변수로 사용할 수 있고, 해당 전역 변수가 없으면 해당 변수는 전역변수가 된다.

def foo():
    global x
    x = 20
    print(x)

foo()
print(x)

네임 스페이스 

파이썬에서 변수는 네임 스페이스에 저장된다. locals함수를 사용하면 현제 네임스페이스를 딕셔너리 형태로 출력할 수 있다.

다음과 같이 함수 내에서 locals함수를 사용하면 함수의 지역 범위 내의 네임스페이스를 출력할 수 있다.

 

33.2 함수 안에서 함수 만들기 

다음과 같이 def로 함수를 만들고, 그 안에서 다시 def로 함수를 만들면 된다.

def 함수이름1():
    코드
    def 함수이름2():
        코드

다음은 함수 안의 함수로 문자열을 출력하는 함수이다. 

def print_hello():
    hello = 'hello world'
    def print_message():
        print(hello)
    print_message()

print_hello()

print_hello 함수 안에 print_message 함수를 만들었다. hello변수는 print_hello 안에 있으며, print_message함수에서 hello변수를 출력한다. print_hello 함수 안에서는 print_message 함수를 실행한다. 두 함수가 실제로 동작하려면 print_hello를 호출하면 print_hello > print_message 순서로 실행된다.

 

33.2.1 지역변수의 범위 

위의 코드에서 안쪽 함수 print_message에서는 바깥쪽 함수 print_hello의 지역변수 hello를 사용할 수 있다. 바깥쪽 함수의 지역 변수는 그 안에 속한 모든 함수에서 접근할 수 있다.

 

33.2.2 지역변수 변경하기

def A():
    x = 10
    def B():
        x = 20
    B()
    print(x)

A()

A함수에서 변수 x에 10을 할당하고, A안의 B함수에서 변수 x에 20을 할당하고 B를 호출하고 x를 출력하는 A함수를 실행했다. 결과값은 10이 나왔다. B함수에서 바깥쪽 함수의 지역변수 x를 변경하는 것이 아니라 안쪽 함수 B에서 이름이 같은 지역변수 x를 새로 만드는 것이다. 파이썬에서는 함수에서 변수를 만들면 항상 현재 함수의 지역변수가 된다.

현재 함수 바깥쪽에 있는 지역변수의 값을 변경하려면 nonlocal 키워드를 사용해야 한다.

def A():
    x = 10

    def B():
        nonlocal x
        x = 20
    B()
    print(x)

A()

nonlocal키워드로 바깥쪽 함수의 지역변수의 값을 변경한 것을 확인할 수 있다.

nonlocal은 현재 함수의 지역변수가 아니라는 뜻이기 때문에 바깥쪽 함수의 지역변수를 사용한다.

 

33.2.3 nonlocal이 변수를 찾는 순서

def A():
    x = 10
    y = 100
    def B():
        x = 20
        def C():
            nonlocal x
            nonlocal y
            x += 30
            y += 300
            print(x)
            print(y)
        C()
    B()

A()

함수 C에서 nonlocal 키워드를 사용하게 되면 변수 x는 바깥쪽의 b 함수의 지역변수 x = 20을 사용하게 된다. 변수 y는 B 함수에는 없기 때문에 한단계 더 바깥으로 나가서 A 함수의 y 변수를 사용하게 된다. nonlocal은 가까운 함수부터 지역변수를 찾고, 지역변수가 없으면 계속 밖으로 나가서 찾는다.

nonlocal 키워드를 사용했는데, 바깥쪽 함수에 nonlocal로 지정한 변수가 없다면 위와 같은 에러를 발생시킨다.

 

33.2.4 global로 전역변수 사용하기

x = 1

def A():
    x = 10
    def B():
        x = 20
        def C():
            global x
            x += 30
            print(x)
        C()
    B()

A()

함수가 몇단계든 상관없이 global키워드를 사용하면 무조건 전역변수를 사용하게 된다. 함수 C에서만 전역변수를 사용했기 때문에 x+=30 은 31이된다.

 

33.3 클로저 사용하기

다음은 함수 바깥 쪽에 있는 지역변수 a,b를 사용하여 a * x + b를 계산한 뒤 반환한다.

def calc():
    a = 3
    b = 5
    def mul_add(x):
        return a * x + b
    return mul_add

c = calc()
print(c(1), c(2), c(3), c(4), c(5))

calc함수에 변수 a,b를 만들고 3과 5를 저장하였다. mul_add 함수에서는 a 와 b를 사용하여 a * x + b를 반환한다. 이후 calc 함수에서 mul_add 함수 자체를 반환한다. 함수를 반환할 때는 함수 이름만 반환하며 괄호를 붙이지 않는다. 

함수 calc를 호출하고 반환값을 c에 저장하면 calc에서 mul_add를 반환했으므로 c에는 함수 mul_add가 들어간다.

print(c)를 통해 c를 출력해보면 mul_add함수 자체만 들어간 것을 알 수 있다.

그리고 c에 숫자를 넣어 호출하면 계산식에 따라 연산한 값이 출력된다. 

함수 calc가 끝났는데도, calc의 지역변수 a,b를 사용하여 계산을 하고 있다. 이렇게 함수를 둘러싼 환경을 유지하다가 함수를 호출할 때 다시 꺼내서 사용하는것을 클로저 라고 한다.

c에 저장된 함수가 클로저 이기 때문에 위 그림처럼 calc함수 내의 지역변수를 가져와서 사용할 수 있다. 

클로저를 사용하면 프로그램의 흐름을 변수에 저장할 수 있고, 지역변수와 코드를 묶어서 사용하고 싶을 때 활용한다. 

또한 클로저에 속한 지역변수는 밖에서 직접 접근할 수 없기 때문에 데이터를 숨기고 싶을때 사용한다. 함수 외부에서 데이터의 변경이 일어나면 안될 때 사용해도 좋을것 같다.

 

33.3.1 lambda로 클로저 만들기

클로저는 다음과 같이 람다로도 만들 수 있다.

def calc():
    a = 3
    b = 5
    return lambda x: a * x + b

c = calc()
print(c(1),c(2),c(3))

람다는 이름이 없는 익명함수 이고, 클로저는 함수를 둘러싼 환경을 유지했다가 나중에 다시 사용하는 함수를 뜻한다.  

 

33.3.2 클로저의 지역변수 변경하기

클로저의 지역변수를 변경하고 싶으면 다음과 같이 nonlocal을 사용한다.

def calc():
    a = 3
    b = 5
    total = 0
    def mul_add(x):
        nonlocal total
        total = total +  a * x + b
        print(total)
    return mul_add

c = calc()
c(1)
c(2)
c(3)

a * x + b의 결과를 calc함수의 지역변수 total에 누적한다.

 

33.4 퀴즈

정답은 b,d이다. 함수 안에서 전역변수의 값을 바꿀 때는 global을 사용하고, 함수 안에 함수가 여러 단계 있어도 전역변수를 사용할 수 있다.

정답은 c,d이다. 지역변수는 함수 바깥쪽에서 사용할 수 없고, nonlocal을 이용하면 두단계 이상 바깥쪽에 있는 지역변수도 사용할 수 있다.

정답은 c,d이다. 람다는 익명함수를 의미하므로 다른 의미이다. 클로저는 람다 표현식으로 만들 수 있다.

 

33.5 연습문제 : 호출 횟수를 세는 함수 만들기

정답은 다음코드와 같다.

        nonlocal i
        i += 1
    	return i
    return count

conunt 함수에서 counter 함수의 지역변수 i를 사용하여 counte 함수가 실행될 때마다 1씩 누적하고 누적한 값을 반환하며 counter 함수에서 count함수를 반환하게 하였다.

 

33.6 심사문제: 카운트 다운 함수 만들기

정답은 다음 코드와 같다.

    i = n+1
    def down():
        nonlocal i
        i -= 1
        return i
    return down

입력한 값을 포함하여 숫자를 세야 하므로 countdown 함수의 지역변수 i를 입력값보다 1 크게 하고, down함수를 만들어 i를 1씩 감소시킨 후 반환하였다. countdown 함수는 down 함수를 반환하였다.

 

Unit 34. 클래스 사용하기

클래스는 객체를 표현하기 위한 문법이다. 클래스는 게임속의 다양한 직업들, 웹 브라우저 에서 사용하는 스크롤바, 버튼, 체크박스 등 같은 개념을 사용하는 객체를 묶은 것이다. 객체는 스크롤바, 버튼, 체크박스처럼 특정한 개념이나 모양으로 존재하는 것이다. 프로그래밍에서 객체를 만들 때 클래스를 사용한다.

게임의 캐릭터를 클래스로 표현하면 캐릭터는 체력, 마나, 공격력 등이 필요하며 칼로 찌르기, 베기 등의 스킬이 있어야한다. 

체력, 마나, 공격력 등을 속성(attribute)라 하고, 찌르기, 베기 등의 스킬을 메서드(method)라 한다.

이런 프로그래밍 방법을 객체 지향 프로그래밍이라고 한다. 객체 지향 프로그래밍은 복잡한 문제를 잘게 나누어 객체로 만들고, 객체를 조합하여 문제를 해결한다. 기능을 개선하고 발전시킬때도 해당 기능을하는 클래스만 수정하면 되서 유지보수도 효율적이다.

지금까지 사용했던 숫자, 문자, 리스트, 딕셔너리 등도 다 각각의 객체이다.

 

34.1 클래스와 메서드 만들기

클래스는 class에 이름을 지정하고 클론을 붙인 뒤 다음줄 부터 def로 메서드를 작성하면 된다. 메서드는 클래스 안에 들어있는 함수들이다.

파이썬에서는 보통 클래스 이름은 대문자로 시작한다. 메서드 작성 방법은 함수와 같고, 코드는 반드시 들여쓰기를 해야 한다. 메서드의 첫번째 매개변수는 반드시 self를 지정해야 한다.

class 클래스이름:
    def 메서드(self):
        코드

다음은 간단한 사람 클래스 이다.

다음과 같이 클래스에 괄호를 붙인뒤 변수에 할당하면 사용할 수 있다.

변수 james를 클래스의 인스턴스라고 한다. 클래스는 특정 개념만 표현할 뿐 사용하려면 인스턴스를 생성해야 한다.

 

34.1.1 메서드 호출하기

메서드는 인스턴스를 통해 호출된다. 다음과 같이 인스턴스 뒤에 .(점)을 붙이고 메서드를 호출하면 된다.

위와 같이 인스턴스를 통해 호출하는 메서드를 인스턴스 메서드라고 인스턴스 메서드 라고 부른다.

 

34.1.2 파이썬에서 흔히 볼 수 있는 클래스

지금까지 사용한 int, list, dict등도 클래스 이다. 이 클래스로 인스턴스를 만들고, 메서드를 사용한 것이다.

위와 같은 코드들은 int 클래스로 인스턴스 a를 만들고, list클래스로 인스턴스 b를 만들고, dict클래스로 인스턴스 c를 만든 것이다. 정수를 만들 땐 int를 생략하고, 리스트, 딕셔너리는 [ ], { }을 이용하여 만들지만 이것들도 다 클래스 이다.

위 코드는 인스턴스 b에서 append라는 메서드를 호출하여 사용한 것이다.

파이썬에서는 자료형도 각각의 클래스이다. type을 사용하면 객체(인스턴스)가 어떤 클래스 인지 확인할 수 있다.

 

34.1.3 인스턴스와 객체의 차이점

객체와 인스턴스는 같은 것이다. 보통 객체만 지정할 때는 객체라고 부르고, 클래스와 연관지어서 말할 때는 인스턴스라고 부른다. 

 

빈 클래스 만들기

빈 클래스는 다음과 같이 코드 부분에 pass를 넣어주면 된다. 

class Person:
    pass

메서드 안에서 메서드 호출하기

메서드 안에서 메서드를 호출할 때는 self.메서드() 형식으로 호출해야 한다. self없이 메서드 이름만 사용하면 클래스 바깥쪽에 있는 함수를 호출한다는 뜻이 된다.

class Person:
    def greeting(self):
        print('hello')
    
    def hello(self):
        self.greeting()

특정 클래스의 인스턴스인지 확인하기

현재 인스턴스가 특정 클래스의 인스턴스인지 확인할 때는 isinstance함수를 사용한다. 특정 클래스의 인스턴스가 맞으면 True, 아니면 False를 반환한다.

isinstance는 주로 객체의 자료형을 판단할 때도 사용한다.

 

34.2 속성 사용하기

속성을 만들 때는 __init__메서드 안에서 self.속성 에 값을 할당한다.

class 클래스이름: 
    def __init__(self):
        self.속성 = 값
class Person:
    def __init__(self):
        self.hello = 'hello! '

    def greeting(self):
        print(self.hello)

james = Person()
james.greeting()

__init__ 메서드는 james = Person() 처럼 클래스에 괄호를 붙여서 인스턴스를 만들 때 호출되는 특별한 메서드이다. __init__메서드는 인스턴스(객체)를 초기화 한다.

__init__처럼 앞뒤로 __(밑줄두개)가 붙은 메서드는 파이썬이 자동으로 호출해주는 메서드로 스페셜 메서드 또는 매직메서드 라고 부른다.

greeting메서드 에서는 print로 self.hello를 출력하였다.

Person으로 객체를 만들고, greeting를 호출하면 self.hello에 저장된 hello! 가 출력된다.

속성은 __init__메서드 안에서 만들고, self에 .(점)을 붙여서 할당한다. 클래스 안에서 속성을 사용할 때도 self에 .(점)을 붙여서 사용한다.

 

34.2.1 self의 의미

self는 인스턴스 자기 자신을 의미한다. 인스턴스가 생성될 때 self.hello처럼 자기 자신에 속성을 추가하였다.

위 클래스에서 __init__의 매개변수 self에 들어가는 값은 Person이다.  self가 완성된 뒤에는 james가 할당된다. 이후 메서드를 호출하면 현재 인스턴스가 자동으로 매개변수 self에 들어온다. 그래서 greeting에서 self.hello의 속성을 출력할 수 있었던 것이다.

위 설명을 그림으로 나타내면 다음과 같다.

 

34.2.2 인스턴스를 만들 때 값 받기

다음과 같이 __init__메서드에서 self 다음에 값을 받을 매개변수를 지정하고, 매개변수를 self.속성에 넣어준다.

class 클래스이름:
    def __init__(self, 매개변수1, 매개변수2):
        self.속성1 = 매개변수1
        self.속성2 = 매개변수2

다음은 Person 클래스로 인스턴스를 받을 때 매개변수로 이름, 나이, 주소를 받은 것이다.

class Person:
    def __init__(self, name, age, address):
        self.hello = 'hello!'
        self.name = name
        self.age = age
        self.address = address

    def greeting(self):
        print('{} I am {}.'.format(self.hello, self.name))

maria = Person('maria', 20, 'seoul')
maria.greeting()

print(maria.name)
print(maria.age)
print(maria.address)

__init__메서드 에서는 매개변수로 name, age, address를 받고 각각 그대로 self에 넣어서 속성으로 만들었다.

greeting메서드는 인사를 하고, self.name으로 name 속성에 접근하여 이름을 출력하도록 하였다.

Person() 괄호 안에 이름, 나이, 주소 정보를 넣어 maria인스턴스를 만들었다. 괄호 안에 넣은 값은 __init__ 메서드의 self다음 매개변수로 차례대로 들어간다.

클래스 바깥에서 속성에 접근할 때는 인스턴스.속성 의 형식으로 접근이 가능하다. maria.name, maria.age등과 같은 방식으로 속성에 접근한다. 이렇게 인스턴스를 통해 접근하는 속성을 인스턴스 속성이라고 한다.

 

클래스의 위치인수, 키워드 인수

클래스로 인스턴스를 만들 때 위치 인수와 키워드 인수를 사용할 수 있다. 규칙은 함수와 동일하다. 위치인수와 리스트 언패킹을 사용하려면 다음과 같이 *args를 사용하면 된다. 매개변수에서 값을 가져오려면 args[0]과 같은 방식으로 사용해야 한다.

class Person:
    def __init__(self, *args):
        self.name = args[0]
        self.age = args[1]
        self.addr = args[2]
 
maria = Person(*['maria', 20, 'seoul'])

키워드 인수와 딕셔너리 언패킹을 사용하려면 **kwargs를 사용하면 된다. 매개변수에서 값을 가져오려면 kwargs['name']처럼 사용해야 한다.

class Person:
    def __init__(self, **kwargs):
        self.name = kwargs['name']
        self.age = kwargs['age']
        self.address = kwargs['addr']
 
maria1 = Person(name='maria', age=20, addr='seoul')
maria2 = Person(**{'name': 'maria', 'age': 20, 'addr': 'seoul'})

 

인스턴스를 생성한 뒤에 속성 추가하기, 특정 속성만 허용하기

다음과 같이 클래스로 인스턴스를 만든 뒤에 인스턴스.속성 = 값 의 형식으로 속성을 계속 추가할 수 있다.

이렇게 추가한 속성은 해당 인스턴스에서만 사용할 수 있다. 클래스로 다른 인스턴스를 만들면 추가한 속성은 없다.

__init__메서드가 아닌 다른 메서드에서도 속성을 추가할 수 있지만 이 경우 메서드를 호출해야 속성이 생성된다.

greeting메서드를 호출하기 전까지는 hello 속성이 없다.

특정 속성만 허용하고, 다른 속성들은 제한하고 싶을때 __slots__에 허용할 속성을 리스트로 넣어주면 된다. 속성 이름은 반드시 문자열 이여야 한다.

리스트에 들어있는 이름 외에 다른 이름을 가진 속성은 생성할 수 없다.

 

34.3 비공개 속성 사용하기

class Person:
    def __init__(self, name, age, address):
        self.hello = 'hello!'
        self.name = name
        self.age = age
        self.address = address

위의 클래스에는 hello, name, age, address의 속성이 있고, 이 속성들은 메서드에서 self로 접근할 수 도 있고, 인스턴스.속성의 형식으로 클래스 밖에서도 접근할 수 있다.

클래스 안에서만 접근 가능하고, 밖에서는 접근할 수 없는 비공개 속성이 있다. 

비공개 속성은 이름이 __(밑줄 두 개)로 시작해야 한다. 단, __속성__ 처럼 밑줄 두 개가 양 옆에 왔을 때는 비공개 속성이 아니다.

class 클래스이름:
    def __init__(self, 매개변수):
        self.__속성 = 값

다음은 Person클래스에 비공개 속성 __wallet을 넣은것이다.

class Person:
    def __init__(self, name, age, address, wallet):
        self.name = name
        self.age = age
        self.address = address
        self.__wallet = wallet
 
maria = Person('maria', 20, 'seoul', 10000)
maria.__wallet -= 10000   

위 코드를 실행하면 다음과 같은 에러가 발생한다.

__wallet는 비공개 속성이기 때문에 클래스 밖에서는 접근할 수 없다. __wallet에 접근하려면 다음과 같이 클래스 안에서 접근해야 한다.

class Person:
    def __init__(self, name, age, address, wallet):
        self.name = name
        self.age = age
        self.address = address
        self.__wallet = wallet
    
    def pay(self, amount):
        self.__wallet -= amount
        print('이제 {}원 남았네요'.format(self.__wallet))
 
maria = Person('maria', 20, 'seoul', 10000)
maria.pay(3000)

위 코드와 같이 비공개 속성은 클래스 안의 메서드에서는 접근할 수 있다.

보통의 프로그램에서는 지갑에 든 돈은 굳이 밝힐 필요가 없기 때문에 다음과 같이 돈이 얼마인지 확인하고, 모자라면 쓰지 못하는 식으로 만든다.

    def pay(self,amount):
        if amount > self.__wallet:
            print('돈이 모자랍니다')
            return
        self.__wallet -= amount

 

비공개 메서드 사용하기

메서드도 이름이 __(밑줄 두 개)로 시작하면 클래스 안에서만 호출할 수 있는 비공개 메서드가 된다.

 

34.4 퀴즈

정답은 d이다.

정답은 __init__ 이다.

정답은 e이다.

정답은 c이다.

 

34.5 연습문제: 게임 캐릭터 클래스 만들기

정답은 다음 코드와 같다.

class Knight:
    def __init__(self, health, mana, armor):
        self.health = health
        self.mana = mana
        self.armor = armor
        
    def slash(self):
        print('베기')

 

34.6 심사문제: 게임 캐릭터 클래스 만들기

정답은 다음 코드와 같다.

class Annie:
    def __init__(self, health, mana, ability_power):
        self.health = health
        self.mana = mana
        self.ability_power = ability_power
    def tibbers(self):
        print('티버: 피해량 {}'.format(self.ability_power * 0.65+400))

Unit 27. 파일 사용하기

27.1 파일에 문자열 쓰기, 읽기

27.1.1 파일에 문자열 쓰기

파일에 문자열을 쓸때는 open()함수로 파일을 열어 파일 객체를 얻은 뒤에 write메서드를 사용한다. 파일 쓰기가 끝나면 close()로 파일 객체를 닫아야 한다.

파일객체 = open(파일이름, 파일모드)
파일객체.write('문자열')
파일객체.close()

 

다음은 파일을 열어 'hello world' 라는 문자열을 작성하는 소스코드이다. 

file = open('hello.txt', 'w')
file.write('hello world')
file.close()

이 소스코드를 실행하면 소스코드 파일이 있는 폴더에 hello.txt파일이 생성되고, 파일을 열어보면 작성한 문자열이 있는 것을 확인할 수 있다.

파일을 사용하려면 먼저 open함수로 파일을 열어 파일 객체를 얻어야 한다. 열 파일의 이름을 지정하고 파일 모드를 지정하는데 위 코드에서 지정했던 'w'는 파일에 내용을 쓸때 사용하는 것이고, write의 w이다. 

파일 객체를 얻었기 때문에 write() 메서드로 파일에 문자열을 쓸 수 있다. 파일 쓰기가 끝나면 close()로 파일 객체를 닫아줘야 한다.

 

27.1.2 파일에서 문자열 읽기

파일을 읽을 때도 open()함수로 파일을 열어 파일 객체를 얻은 뒤 read()메서드로 파일 내용을 읽는다. 파일을 열때 파일 모드는 'r'로 읽기모드를 의미한다.

file = open('hello.txt', 'r')
s = file.read()
print(s)
file.close()

open을 사용하여 파일을 읽기모드로 열고, read의 반환값을 변수에 저장하여 print로 그 변수를 출력한 후 close로 파일 객체를 닫았다. 

 

27.1.3 자동으로 파일 객체 닫기

with as를 사용하면 파일을 사용한 뒤 자동으로 파일 객체를 닫아준다. 다음과 같은 형식으로 사용한다.

with open(파일이름, 파일모드) as 파일객체:
    코드

다음 코드는 위의 파일 읽는 코드를 with as를 이용하여 작성한 것이다.

with open('hello.txt', 'r') as file:
    s = file.read()
    print(s)

 

27.2 문자열 여러줄을 파일에 쓰기, 읽기

27.2.1 반복문으로 문자열 여러줄을 파일에 쓰기

문자열 여러줄을 파일에 쓰려면 다음과 같이 반복문을 이용하면 된다.

with open('hello.txt', 'w') as file:
    for i in range(3):
        file.write('hello world {}\n'.format(i))    

파일에 문자열을 여러 줄로 저장할 때는 문자열 끝에 개행문자(\n)을 꼭 지정해줘야 한다. 개행문자를 지정해주지 않으면 파일을 쓸 때 문자열이 모두 한 줄로 붙어서 저장된다. 

 

27.2.2 리스트에 들어있는 문자열을 파일에 쓰기 

리스트에 들어있는 문자열을 파일에 쓸 때는 writelines()를 사용한다. 

lines = ['hello world\n', 'python is fun\n', 'project h4c is good\n']

with open('hello.txt', 'w') as file:
    file.writelines(lines)

writelines()는 리스트에 들어있는 문자열을 파일에 쓴다. 위와 같이 여러줄로 쓰고 싶으면 리스트 각 요소의 끝에 개행문자(\n)을 붙여줘야 한다. 개행문자를 붙이지 않으면 모든 요소가 한 줄로 붙어서 저장된다.

 

27.2.3 파일의 내용을 한 줄씩 리스트로 가져오기

readlines()는 파일의 내용을 한 줄씩 리스트 형태로 가져온다.

with open('hello.txt', 'r') as file:
    lines = file.readlines()
    print(lines)

 

27.2.4 파일의 내용을 한 줄씩 읽기

파일 내용을 한 줄씩 순차적으로 읽을때는 readline()을 사용한다.

with open('hello.txt', 'r') as file:
    line = None
    while line != '':
        line = file.readline()
        print(line.strip('\n'))

readline()으로 파일을 읽을 때는 파일에 문자열이 몇줄이나 있는지 모르기 때문에 while 반복문을 사용해야 한다. while 반복문은 빈문자열이 아닐 때 계속 반복한다. 빈 문자열이 이 아닐때 반복해야 하기 때문에 line은 처음에 빈 문자열이 아니라 None로 초기화 한다. 빈 문자열로 초기화 하면 반복문을 진행할 수 없기 때문이다. 문자열을 출력할 때는 strip로 개행문자를 삭제한다. 개행문자를 삭제하지 않으면 문자열 안에 들어있는 개행문자와 print()함수 때문에 문자열 한 줄을 출력할 때 마다 빈 줄이 계속 출력된다.

 

27.2.5 for 반복문으로 파일의 내용을 줄 단위로 읽기

for 반복문으로 while반복문 보다 간단하게 파일의 내용을 읽을 수 있다.

with open('hello.txt', 'r') as file:
    for line in file:
        print(line.strip('\n')) 

for 반복문에 파일 객체를 지정하면 반복을 할 때 마다 파일의 내용을 한 줄씩 읽어서 변수에 저장할 수 있다.

파일 객체는 이터레이터이다. 따라서 변수 여러 개에 저장하는 언패킹도 가능하다. 할당할 변수의 개수와 파일에 저장된 문자열의 줄 수가 같아야 한다.

 

27.3 파이썬 객체를 파일에 저장하기, 가져오기

파이썬은 객체를 파일에 저장하는 pickle 모듈을 제공한다. 파이썬 객체를 파일에 저장하는 과정을 피클링 이라고 하고, 파일에서 객체를 읽어오는 과정을 언피클링 이라고 한다.

 

27.3.1 파이썬 객체를 파일에 저장하기

피클링은 pickle 모듈의 dump메서드를 사용한다.

import pickle

name = 'phulasso'
age = 20
address = 'Seoul'
scores = {'korean': 90, 'english': 95, 'mathematics': 85, 'science': 82}

with open('james.p', 'wb') as file:
    pickle.dump(name, file)
    pickle.dump(age, file)
    pickle.dump(address, file)
    pickle.dump(scores,file)

pickle.dump로 객체를 저장할 때는 파일 모드를 'wb'로 지정해야 한다. b는 바이너리(binary)의 b이다. 바이너리 파일은 컴퓨터가 처리하는 파일 형식이기 때문에 사람이 알아보기 힘들다.

 

27.3.2 파일에서 파이썬 객체 읽기

언피클링은 pickle모듈의 load를 사용한다. 언피클링 할때는 파일 모드를 바이너리 읽기 모드인 'rb'로 설정해야 한다.

import pickle

with open('james.p', 'rb') as file:
    name = pickle.load(file)
    age = pickle.load(file)
    address = pickle.load(file)
    scores = pickle.load(file)
    print(name)
    print(age)
    print(address)
    print(scores)

파일에서 객체를 가져올때도 pickle.load를 4번 사용하여 저장한 순서대로 가져온다. 

파일 모드는 r, w 이외에도 추가 'a', 배타적생성 'x'도 있다. 추가 모드는 이미 있는 파일에서 파일 끝에 내용을 추가할 때 사용하고, 배타적 생성 모드는 파일이 이미 있으면 에러를 발생하고, 파일이 없으면 파일을 만든다. 파일 형식도 함께 지정할 수 있는데 t는 텍스트 모드로 r, w와 같은 방식으로 생략이 가능하며, 사람이 읽을 수 있는 텍스트 파일이다. b는 바이너리 모드로 피클링을 사용하거나, 바이너리 데이터를 직접 지정할 때 사용한다. +는 파일을 읽기/쓰기 모드로 연다. 아래 그림과 같이 다양한 방식으로 조합하여 사용할 수 있다.

 

27.4 퀴즈

정답은 d이다.

정답은 c이다.

정답은 b이다.

 

27.5 연습문제: 파일에서 10자 이하인 단어 개수 세기

정답은 다음 코드와 같다.

with open('words.txt', 'r') as file:
    count = 0
    words = file.readlines()
    for i in words:
        if len(i.strip('\n')) <= 10:
            count += 1

주어진 파일을 일기 모드로 읽어서 리스트로 가져온 후 반복문으로 리스트의 각 요소들이 개행문자를 제외하고 10자 이하인지 확인하여 10자 이하면 count에 1을 더해주었다.

27.6 심사문제: 특정 문자가 들어있는 단어 찾기

정답은 다음 코드와 같다.

with open('words.txt', 'r') as f:
    s = list(f.readline().split())
    w_list = []
    for i in s:
        if 'c' in list(i.strip(',.')):
            w_list.append(i.strip(',.'))
    for i in w_list:
        print(i)

파일에서 문자열을 읽어와 공백을 기준으로 나눈 리스트로 만들고, 리스트의 각 요소에 c가 들어있는지 확인해서 들어 있으면 다른 리스트에 , 와 . 을 없애고 추가한다. 추가한 다른 리스트를 출력하면 문제를 해결할 수 있다.

 

Unit 28. 회문 판별과 N-gram만들기

회문은 유전자 염기서열 분석에 많이 쓰고, N-gram은 빅데이터 분석, 검색 엔진에서 많이 사용된다. 

 

28.1 회문 판별하기

회문은 순서를 거꾸로 읽어도 제대로 읽은 것과 같은 단어와 문장을 말한다. 예를 들면 level, sos, rotator, nurses run 등이 있다.

문자열이 회문인지 판별하려면 첫번째 글자와 마지막 글자가 같고, 안쪽으로 한 글자씩 좁혔을 때 글자가 서로 같으면 회문이다.

 

28.1.1 반복문으로 문자 검사하기

다음은 반복문으로 문자열의 각 문자들을 검사해서 회문인지 판단하는 코드이다.

word = input('단어를 입력하세요 : ')

is_palindrome = True
for i in range(len(word) // 2):
    if word[i] != word[-1 - i]:
        is_palindrome = False
        break

print(is_palindrome)

회문이면 True를 출력하고, 회문이 아니면 False를 출력한다.

회문 판별은 문자열의 길이를 기준으로 하며 문자열을 절반으로 나누어 왼쪽 문자와 오른쪽 문자가 같은지 구분한다.

다음과 같이 반복할 때 문자열 길이의 절반만큼 반복한다. 

for i in range(len(word) // 2):

문자열 길이가 홀수 일수도 있기 때문에 버림 나눗셈을 한다. 문자열이 홀수여도 가운데 글자 바로 앞까지 검사하게 된다.

반복문 안에서 왼쪽 문자와 오른쪽 문자가 다르면 회문이 아니므로 is_palindrome에 False를 넣고 반복문을 종료한다. 

왼쪽 문자와 오른쪽 문자가 같으면 반복문이 진행하며 word[i]와 word[-1 - i]로 문자열 왼쪽, 오른쪽에서 한 칸씩 안으로 들어가며 다시 비교한다.

 

 28.1.2 시퀀스 뒤집기로 문자열 검사하기

시퀀스 객체의 슬라이스를 이용하면 더 간편하게 회문을 판별할 수 있다.

word = input('단어를 입력하세요 : ')

print(word == word[::-1])

word[::-1]은 문자열 전체에서 인덱스를 1씩 감소시키며 요소를 가져 오기 때문에 문자열을 반대로 뒤집는것과 똑같다. 원래 문자열과 반대로 뒤집은 문자열이 같으면 회문이다.

 

28.1.3 리스트와 reversed 사용하기

다음과 같이 반복 가능한 객체를 반대로 뒤집는 reversed를 사용해도 회문을 판별할 수 있다.

리스트에 문자열을 넣으면 문자 하나 하나가 리스트의 요소로 들어가고 reversed로 뒤집은 문자열을 리스트에 넣어 두 리스트를 비교한다.

 

28.1.4 문자열의 join메서드와 reversed 사용하기

다음과 같이 join메서드를 사용하여 회문을 판별할 수 있다.

join은 구분자 문자열과 문자열 리스트를 연결하는 메서드이다. 위 코드에서는 빈문자열과 word를 뒤집은 객체의 요소를 연결했으므로  문자순서가 반대로 된 문자열을 얻을 수 있다. 두 문자열을 비교하여 회문을 판단할 수 있다.

 

28.2 N-gram 만들기

N-gram은 문자열에서 N개의 연속된 요소를 추출하는 방법이다. 만약 'Hello'라는 문자열을 2-gram으로 추출하면 다음과 같다. 

He
el
ll
lo

문자열 처음부터 끝까지 한 글자씩 이동하며 두글자씩 추출한다. 3-gram은 3글자 4-gram은 4글자를 추출한다.

 

28.2.1 반복문으로 N-gram 출력하기

text = 'hello'

for i in range(len(text) - 1):
    print(text[i],text[i+1], sep='')

문자열 끝에서 한글자 앞까지만 반복하며 현재 문자와 그다음 문자 두 글자씩 출력한다.

만약 3-gram이라면 반복 횟수는 range(len(text) - 2))이고, 문자열을 출력할때는 text[i], text[i+1], text[i+2]를 출력한다.

단어 단위 N-gram은 문자열을 공백으로 구분하여 출력한다. 다음은 단어단위 2-gram의 예시다.

text = 'this is python script'
words = text.split()

for i in range(len(words) - 1):
    print(words[i],words[i+1])

문자열을 공백을 기준으로 분리하여 리스트를 만들고 2-gram이기 때문에 리스트의 마지막 요소 한개 앞까지 반복하면서 현재 문자열과 그 다음 문자열을 출력한다.

 

28.2.2 zip로 2-gram 만들기

zip함수를 이용하여 2-gram을 만드는 코드는 다음과 같다.

text = 'hello'

two_gram = zip(text, text[1:])
for i in two_gram:
    print(i[0], i[1], sep='')

 zip 함수는 반복 가능한 개체의 각 요소를 튜플로 묶어준다. text[1:]은 두번째 문자부터 마지막 문자 까지 가져 와서 text와 text[1:]을 zip으로 묶으면 문자 하나가 밀린 상태로 각 문자를 묶는다.

for로 반복하며 i[0],i[1]을 순서대로 출력하면 된다.

단어 단위의 2-gram도 같은 방식으로 만든다.

3-gram을 만드려면 zip(words, words[1:], words[2:]) 처럼 3개의 리스트를 넣으면 된다.

 

28.2.3 zip과 리스트 표현식으로 N-gram만들기

리스트 표현식을 사용하면 코드로 편하게 N-gram을 만들 수 있다.

for 로 3번 반복하면서 text[0:]부터 text[2:]까지 만들어졌다. 이 반환값을 zip에 넣으면 다음과 같다.

결과를 보면 3-gram이 아니다. zip은 반복 가능한 객체 여러개를 콤마로 구분하여 넣어줘야 한다. 하지만 ['hello', 'ello', 'llo']는 요소가 3개 들어있는 리스트 1개이다. 리스트의 각 요소를 콤마로 구분하려면 리스트 앞에 *을 붙여줘야 한다.

리스트에 *을 붙이는 방법은 리스트 언패킹 이라고 한다.

 

28.3 연습문제: 단어 단위 N-gram만들기

정답은 다음 코드와 같다.

1. text.split()
2. n > len(words)
3. zip(*[words[i:] for i in range(n)])

input로 입력받은 문자열은 공백을 기준으로 분리하여 리스트로 만들어 줘야 하며, 입력받은 숫자보다 리스트 요소 개수가 작으면 wrong를 출력하고, 리스트 표현식을 이용하여 N-gram을 만들었다.

 

28.4 심사문제: 파일에서 회문인 단어 출력하기

정답은 다음 코드와 같다.

 with open('words.txt', 'r') as f:
    words = f.readlines()
    for i in words:
        if i.strip('\n') == i.strip('\n')[::-1]:
            print(i,end='')

파일을 열어 리스트로 내용을 읽어와서 요소들을 개행문자를 제거하고 시퀀스 슬라이스를 이용해 뒤집은것과 비교해서 참인것들만 출력하였다.

 

Unit 29. 함수 사용하기

함수는 특정 용도의 코드를 한 곳에 모아 놓은 것이다. 함수를 처음에 한 번만 작성해 놓으면 나중에 필요할 때 계속 불러쓸 수 있다. print(), input()등도 파이썬에서 미리 만들어진 함수이다. 함수를 사용하면 좋은 점들은 다음과 같다.

  • 코드의 용도를 구분할 수 있다
  • 코드를 재사용 할 수 있다.
  • 실수를 줄일 수 있다.

 

29.1 Hello, world! 출력함수 만들기 

함수는 def에 함수 이름을 지정하고, ( )(괄호)와 :(클론)을 붙인 뒤 다음줄에 들여쓰기를 한 후 원하는 코드를 작성한다.

def 함수이름():
    코드

def는 define(정의하다)에서 따온 것이다.

 

29.1.1 함수 만들기

Hello, world!를 출력하는 함수는 다음과 같다.

함수 이름은 hello이고, 코드는 print()로 출력하는 코드이다.

 

29.1.2 함수 호출하기

함수는 hello()와 같이 함수 이름과 괄호를 붙여주면 사용할 수 있다. 이렇게 함수를 사용하는 것을 함수를 호출한다고 한다.

 

29.1.3 소스파일에서 함수를 만들고 호출하기

다음과 같이 코드를 작성하면 hello함수가 만들어진 후 호출되어 Hello, world!가 출력된다.

def hello():
    print('Hello, world!')

hello()

 

29.1.4 함수의 실행 순서

hello 함수의 실행 순서는 다음과 같다.

  1. 파이썬 스크립트 최초 실행
  2. hello 함수 호출
  3. hello 함수 실행
  4. print 함수 실행 및 'Hello, world!' 출력
  5. hello 함수 종료
  6. 파이썬 스크립트 종료

 

29.1.5 함수 작성과 함수 호출 순서 

함수를 만들기 전에 함수를 먼저 호출하면 에러가 발생한다.

파이썬 코드는 위에서 부터 아래로 순차적으로 실행하기 때문에 호출을 먼저하면 함수가 정의되지 않았다는 오류가 발생한다.

 

빈 함수 만들기

내용이 없는 빈함수를 만들때는 코드 부분에 pass를 넣는다.

def hello():
   pass

pass는 아무 일을 하지 않아도 함수의 틀을 유지해야 할 때 사용한다.

 

29.2 덧셈 함수 만들기

함수에 값을 받으려면 ( )(괄호) 안에 변수 이름을 지정해주면 된다. 이 변수를 매개변수 라고 한다.

def 함수이름(매개변수1, 매개변수2):
    코드

두 수를 더하는 함수는 다음과 같다. 괄호 안에 매개변수 a와 b를 지정하고, 그 다음줄에서 print로 a와b의 합을 출력한다.

add함수에 10과 20을 넣어서 호출하면 두 값을 더한 30이 나온다. 함수를 호출할 때 넣는 값을 인수 라고 부른다. add 함수의 호출 과정은 다음과 같다.

함수의 클론 바로 다음줄에 """ """(큰따옴표 3개)안에 문자열을 입력하면 함수에 대한 설명을 넣을 수 있다. 이런 방식의 문자열을 독스트링 이라고 하며, 독스트링 위에는 다른 코드가 오면 안된다. 독스트링은 ' ', " " , ''' ''' 등으로 만들어도 되지만 파이썬 코딩 스타일 가이드에서 """ """ 을 권장한다. 위의 add함수에 독스트링을 추가하면 다음과 같다.

독스트링은 함수의 설명을 기록한 것이기 때문에 함수를 사용해도 출력되지 않는다. 독스트링을 출력하려면 함수의 __doc__를 출력하면 된다.

다음과 같이 help()에 함수를 넣으면 함수의 이름, 매개변수, 독스트링을 도움말 형태로 출력한다.

 

29.3 함수의 결과를 반환하기

함수 안에서 return을 사용하면 값을 함수 바깥으로 반환한다.

def 함수이름(매개변수):
    return 반환값

두 수를 더한 값을 반환하는 add함수는 다음과 같다. 매개 변수 두개를 더한 값을 return으로 반환한다.

함수에 3과 5를 넣은 결과를 x에 저장하고 x를 출력하면 두 값을 더한 8이 나온다. return을 사용하면 값을 함수 바깥으로 반환할 수 있고, 함수에서 나온 값을 변수에 저장할 수 있다. return으로 반환하는 값은 반환값 이라고 하고, 함수를 호출해준 바깥에 결과를 알려주기 위해 사용한다.

반환값은 변수에 저장하지 않고, 바로 다른 함수에 넣을 수 있다.

다음과 같이 함수를 만들 때 매개변수는 없고 값만 반환하는 함수를 만들 수 있다.

return은 값을 반환하는 기능 뿐 아니라 함수 중간에서 바로 빠져나오는 기능도 있다. 다음은 매개변수 a 가 10이면 함수를 그냥 빠져나오는 함수이다.

인수로 10을 넣으면 return으로 함수 중간에서 빠져나와 그 아래 코드는 실행하지 않는다.

 

 29.4 함수에서 값을 여러 개 반환하기

함수에서 값을 여러 개 반환할 때는 다음과 같이 return에 값이나 변수를 콤마로 구분한다.

def 함수이름(매개변수):
    return 반환값1, 반환값2

다음은 두 수를 더한 값과 뺀 값을 반환하는 함수이다.

x의 값을 출력하면 두 값을 더한 30이 나오고, y의 값을 출력하면 두 값을 뺀 10이 나온다. return은 값을 여러 개 반환할 수 있다.

함수를 실행해 보면 다음과 같이 값은 튜플로 반환된다.

튜플을 언패킹 하여 두 변수에 할당한 것이다.

 

29.5 함수의 호출 과정 알아보기

스택은 아래에서 부터 차곡차곡 쌓고, 꺼낼때는 위쪽부터 차례대로 꺼내는 접시 쌓기와 비슷하다. 파이썬에서는 반대로 함수가 아래쪽 방향으로 추가되고, 함수가 끝나면 위쪽 방향으로 사라진다.

다음은 덧셈 함수 add와 곱셈 함수 mul이 있고, add함수 안에서 mul함수를 호출하는 소스코드이다.

def mul(a,b):
    c = a * b
    return c

def add(a,b):
    c = a + b
    print(c)
    d = mul(a, b)
    print(d)

x = 10
y = 20
add(x,y)

파이썬 스크립트가 실행되면 첫번째 줄부터 실행된다. 변수 y를 선언하는 부분까지 실행하면 전역 프레임에는 함수 mul, add와 변수 x,y가 들어간다.

프레임은 메모리에서 함수와 함수가 속한 변수가 저장되는 독립적인 공간이다. 전역 프레임은 파이썬 스크립트 전체에 접근할 수 있어서 전역 프레임 이라고 부른다.

함수 add를 호출한뒤 print(c)까지 보면 함수 add의 스택프레임이 만들어지고, 매개변수 a, b 와 변수 c 가 들어간다.

다음 줄에서는 함수 mul을 호출하여 함수 안으로 들어가 return c까지 실행하면 함수 mul의 스택프레임이 만들어지고, 매개변수 a,b와 변수 c가 들어가게 된다.

이제 add함수의 print(d)까지 실행하면 mul함수는 끝나며 반환값은 변수 d에 저장되며 add의 스택프레임에 저장된다. mul함수는 종료되었기 때문에 mul 스택프레임은 사라진다.

이후 add(x,y) 까지 실행하고 나면 add 함수도 끝나기 때문에 add의 스택 프레임도 사라지게 된다. 

함수는 스택 방향으로 호출되어 함수를 호출하면 스택의 아래쪽 방향으로 함수가 추가되고, 함수가 끝나면 위쪽 방향으로 사라진다. 전역프레임은 스크립트 파일의 실행이 끝나면 사라진다.

 

29.6 퀴즈

정답은 c이다.

정답은 d이다.

정답은 a,c,d이다.

 

29.3 연습문제: 몫과 나머지를 구하는 함수 만들기

정답은 다음 코드와 같다.

def get_quotient_remainder(a,b):
    return a // b , a % b

 

29.4 심사문제: 사칙연산 함수 만들기

정답은 다음 코드와 같다.

def calc(a,b):
    return a+b, a-b, a*b, a/b

 

Unit 30. 함수에서 위치 인수와 키워드 인수 사용하기

30.1 위치 인수와 리스트 언패킹 사용하기

함수에 인수를 순서대로 넣는 방식을 위치 인수라 한다. 다음은 print()함수에 인수들을 순서대로 넣은 것이다.

인수를 10,20,30 순서로 넣었으므로 출력할 때도 10,20,30의 순서로 출력된다.

 

30.1.1 위치 인수를 사용하는 함수를 만들고 호출하기

다음은 숫자 세개를 각 줄에 출력하는 함수이다.

print_numbers()에 세개의 숫자를 넣으면 각 줄에 숫자가 출력된다.

 

30.1.2 언패킹 사용하기

인수를 순서대로 넣을 때는 리스트나 튜플을 이용할 수 도 있다. 리스트나 튜플 앞에 *(애스터리스크)를 붙이면 언패킹이 된다. 

리스트(튜플)앞에 *을 붙이면 언패킹이 되어 print_numbers(10,20,30)과 똑같은 동작을 한다.

리스트 변수 대신 리스트 앞에 바로 *을 붙여도 된다.

언패킹을 사용하여 함수를 호출할때 매개변수의 개수와 리스트 요소의 개수는 반드시 같아야 한다. 만약 다르면 에러가 발생한다. 

 

30.1.3 가변인수 함수 만들기

가변 인수는 인수의 개수가 정해지지 않은 것이다. 같은 함수에 인수를 한 개를 넣을 수도 있고, 여러개를 넣을 수 도 있고, 넣지 않을 수도 있다. 

가변인수 함수는 다음과 같이 매개변수 앞에 *을 붙여서 만든다.

def 함수이름(*매개변수):
    코드

다음은 숫자 여러개를 입력 받고, 입력받은 숫자를 각 줄에 출력하는 함수이다.

매개 변수 이름은 원하는 대로 지어도 되지만 관례적으로 argumnets를 줄여서 args로 많이 사용한다. args는 튜플이라서 for 로 반복할 수 있다. 위와 같이 인수 여러개를 넣어도 되지만 리스트(튜플)을 언패킹 해도 된다.

고정인수와 가변인수를 함께 사용할 때는 다음과 같이 고정 매개변수를 먼저 지정하고, 그 다음 매개변수에 *을 지정해야 한다.

매개변수 순서에서 *args는 항상 가장 뒤쪽에 와야 한다.

 

30.2 키워드 인수 사용하기

함수에 인수를 넣을 때 값이나 변수를 그대로 넣게 되면 각각의 인수가 무슨 용도인지 알기 어려울 수 있다.

다음 함수의 경우 인수의 순서가 달라지면 잘못된 결과가 출력될 수 도 있다.

이러한 불편함을 해결하기 위해 인수의 순서와 용도를 매번 기억하지 않도록 키워드 인수 라는 기능을 제공한다. 키워드 인수는 키워드 = 값의 형식으로 인수에 키워드를 붙이는 기능이다. 

위 함수를 키워드 인수 방식으로 호출하면 다음과 같다.

키워드 인수를 사용하면 함수할 때 호출할 인수의 용도를 명확히 알 수 있고, 인수의 순서를 맞추지 않아도 키워드에 해당하는 값이 들어간다. print()함수의 sep, end도 키워드 함수이다.

 

30.3 키워드 인수와 딕셔너리 언패킹 사용하기

딕셔너리 언패킹은 딕셔너리 앞에 **(애스터리스크 두 개)를 사용한다.

personal_info 함수는 다음과 같다.

딕셔너리를 키워드:값 의 형식으로 만들고, **을 붙여서 함수에 출력하면 다음과 같이 딕셔너리의 값들이 함수의 인수로 들어간다. 딕셔너리의 키워드는 반드시 문자열 형태여야 한다.

딕셔너리 변수 대신 딕셔너리 바로 앞에 **을 붙여도 된다.

딕셔너리를 언패킹 할 때는 함수의 매개변수 이름과 딕셔너리의 키 이름이 같아야 하며 매개변수 개수와 딕셔너리의 키 개수가 같아야 한다. 만약 이름이나 개수가 다르면 에러가 발생한다.

 

30.3.1 **를 두 번 사용하는 이유 

딕셔너리는 키-값 쌍 형태로 값이 저장되어 있기 때문이다. *을 한번 만 사용하면 다음과 같이 딕셔너리의 키가 출력된다.

딕셔너리를 한 번 언패킹 하면 키를 사용하겠다는 뜻이 되고, 두번 언패킹하면 값을 사용하겠다는 뜻이 된다.

 

30.3.2 키워드 인수를 사용하는 가변 인수 함수 만들기 

키워드 인수를 사용하는 가변 인수 함수는 매개변수 앞에 **를 붙여서 만든다.

def 함수이름(**매개변수):
    코드

다음은 값을 여러개 받아서 매개변수 이름과 값을 각 줄에 출력하는 함수이다.

매개변수 이름은 아무거나 써도 되지만, 관례적으로 keyword arguments를 줄인 kwargs를 사용한다. 이 kwargs는 딕셔너리 이기 때문에 for문으로 반복할 수 있다.

위와 같이 원하는 만큼 값을 넣을 수 있다.

딕셔너리 언패킹을 사용하여 딕셔너리를 인수로 넣을 수도 있다.

함수를 만들때 매개변수에 **를 붙여주면 키워드 인수를 사용하는 가변인수 함수를 만들 수 있다. 함수를 호출 할 때는 키워드와 인수를 각각 넣거나, 딕셔너리 언패킹을 이용할 수 있다.

보통 **kwargs를 사용한 가변 인수 함수는 다음과 같이 함수 안에 특정 키가 있는지 확인하고 해당 기능을 만든다.

def personal_info(**kwargs):
    if 'name' in kwargs:    
        print('이름: ', kwargs['name'])
    if 'age' in kwargs:
        print('나이: ', kwargs['age'])
    if 'address' in kwargs:
        print('주소: ', kwargs['address'])

고정인수와 가변 키워드 인수를 같이 사용하려면 고정 인수를 먼저 지정하고 그 다음 매개변수에 **를 붙여야 한다.

위치인수를 받는 *args와 키워드 인수를 받는 **kwargs를 동시에 사용할 수 도 있다. 대표적인것이 print()함수 인데, 출력할 값을 위치 인수로 넣고, sep, end등을 키워드 인수로 넣는다.

 

30.4 매개변수에 초기값 지정하기

함수를 호출할 때 매개변수에 초기값을 지정하면 인수를 생략할 수 있다. 초기값은 다음과 같은 형식으로 지정한다.

def 함수이름(매개변수=값):
    코드

 

매개변수의 초기값은 주로 사용하는 값이 있으면서, 가끔 다른 값을 사용해야 할때 활용한다. print()의 경우 sep의 초기값은 공백으로 설정되어 있고, 가끔 sep에 다른 값들을 넣어 사용한다.

다음은 personal_info 함수의 address의 초기값을 비공개로 지정한 것이다.

address는 초기값이 있으므로 personal_info는 다음과 같이 address를 비워두고 호출할 수도 있고, 값을 넣으면 값이 전달된다.

 

30.4.1 초기값이 저장된 매개변수의 위치

초기값이 지정된 매개변수는 초기값이 없는 매개변수 앞에 올 수없다. 초기값이 지정된 매개변수가 중간에 나오면 인수가 어디로 들어가야 할 지 알 수 없기 때문에 에러를 발생한다. 초기값이 지정된 매개 변수는 항상 뒤쪽에 몰아 줘야 한다.

다음과 같이 모든 매개 변수에 초기값이 지정되면 인수를 넣지 않고도 호출할 수 있다.

def personal_info(name='비공개', age=0, address='비공개'):

 

30.5 퀴즈

정답은 d이다. 인수로 전달된것이 리스트이기 때문에 언패킹을 한 번만 해야 한다.

정답은 b,c이다.

정답은 a,c이다.

 

30.6 연습문제: 가장 높은 점수를 구하는 함수 만들기

정답은 다음 코드와 같다.

def get_max_score(*args):
    return max(args)

함수를 호출할 때 인수의 개수가 정해져 있지 않으므로 *args를 사용하고, 가장 높은 점수를 변수에 넣어 반환하므로 return을 사용해야 한다.

 

30.7 가장 낮은점수, 높은점수와 평균을 구하는 함수 만들기

정답은 다음 코드와 같다.

def get_min_max_score(*args):
    return min(args), max(args)

def get_average(**kwargs):
    return sum(kwargs.values()) / len(kwargs)

낮은 점수와 높은 점수는 min()함수와 max()함수를 이용하여 구했고, 평균은 키워드 인수를 사용했기 때문에 키워드의 값들만 더한후, 인수의 길이로 키의 개수로 나눠서 구했다.

 

Unit 31. 함수에서 재귀호출 사용하기

재귀호출은 함수 안에서 함수 자기 자신을 호출하는 방법이다. 알고리즘에 따라서 반복문으로 구현한 코드보다 재귀호출로 구현한 코드가 더 직관적이고 이해하기 쉬운 경우가 많다.

 

31.1 재귀호출 사용하기

def hello():
    print('Hello, world!')
    hello()
 
hello()

위 소스코드는 hello()라는 함수 안에서 다시 hello() 함수를 호출하고 있다. 소스코드를 실행하면 Hello, world! 가 계속 출력되다가 다음과 같은 에러가 발생한다.

파이썬에서는 최대 재귀 깊이가 1000으로 정해져있기 때문이다. hello함수가 자기 자신을 계속 호출하다가 최대 재귀 깊이를 넘어가면 RecursionError가 발생한다.

 

31.1.1 재귀 호출에 종료 조건 만들기

재귀 호출을 사용하려면 반드시 다음과 같이 종료 조건을 만들어야 한다.

def hello(count):
    if count == 0:
        return

    print('Hello, world!', count)
    count -= 1
    hello(count)
 
hello(3)

hello 함수의 반복 횟수를 계산하기 위해 매개변수 count를 사용해서 count 가 0이 되면 함수를 종료하고 0이 아니면 count를 1씩 감소시키면서 재귀 호출을 하는 코드이다. 

 

31.2 재귀 호출로 팩토리얼 구하기

팩토리얼은 1부터 n까지 양의 정수를 차례대로 곱한 값이며 ! 기호로 표시한다.

다음은 팩토리얼을 구하는 스크립트 코드이다. 

def factorial(n):
    if n == 1:
        return 1
    return n * factorial(n-1)

print(factorial(5))

1 팩토리얼은 1이기 때문에 n이 1이면 1을 반환하고 함수를 종료한다. 1이 아니면 n과 n에서 1을 뺀것을 factorial 함수에 넣어서 곱하는것을 재귀 호출로 반복한다. 함수에 5를 넣어 재귀 호출하는 것은 다음과 같다.

그리고 이것을 반환하며 연산하면 다음과 같다.

1부터 반환하여 1씩 증가하며 계속해서 곱한 값을 반환한다.

 

31.3 퀴즈 

정답은 a,c이다. 재귀 호출은 함수에서 자기 자신 함수를 호출하는 방식이며, 재귀호출은 반환값을 사용할 수 있다.

정답은 a이다.

정답은 24이다.

 

31.4 연습문제: 재귀 호출로 회문 판별하기

정답은 다음 코드와 같다.

    if len(word) < 2:
        return True
    if word[0] != word[-1]:
        return False
    return is_palindrome(word[1:-1])

첫번째 문자와 마지막 문자가 다르면 False를 반환하고 함수를 종료한다. 첫 번째 문자와 마지막 문자가 같으면 word[1:-1]로 첫번째 문자와 나머지 문자를 제거한 값을 재귀 호출한다. 인수로 받은 문자열이 2자 미만이 될때까지 재귀 호출이 완료되면 회문이기 때문에 True를 반환하고 함수를 종료한다.

 

31.5 심사문제: 재귀호출로 피보나치 수 구하기

정답은 다음 코드와 같다.

def fib(n):
    if n == 0:
        return 0
    elif n < 3:
        return 1
    else:
        return fib(n-1) + fib(n-2)

피보나치수는 n이 0이면 0이고, n이 1, 2일경우 1이다. 이외의 더 큰 숫자들은 n-1번째 수와 n-2번째 수의 합과 같다.

25.1.4 딕셔너리에서 키-값 쌍 삭제하기

pop(키)는 딕셔너리에서 해당 키-값 쌍을 삭제하고 삭제한 값을 반환한다.

pop(키, 기본값)처럼 기본값을 지정하면 딕셔너리에 키가 있다면 그 키에 해당하는 값을 반환하지만, 키가 없다면 기본값만 반환한다. 

del dic[key]의 형식으로도 키-값쌍을 삭제할 수 있다.

 

25.1.5 딕셔너리에서 임의의 키-값 쌍 삭제하기

popitem()은 딕셔너리에서 임의의 키-값쌍을 삭제하고 삭제한 키-값쌍을 튜플로 반환한다. 3.6이상 버전에서는 마지막 키-값쌍을 삭제하고 3.5 이하 버전에서는 무작위로 삭제하여 매번 삭제하는 키-값쌍이 달라진다.

 

25.1.6 딕셔너리의 모든 키-값 쌍 삭제하기

clear()로 딕셔너리의 모든 키-값쌍을 삭제하여 빈 딕셔너리로 만들 수 있다.

 

25.1.7 딕셔너리에서 키의 값을 가져오기

get(키)는 딕셔너리에서 지정한 키에 해당하는 값을 가져온다.

get(키,기본값)의 형태로 사용하면 키가 있을 경우 키의 값을 반환하지만, 키가 없을 경우 기본값을 반환한다.

 

25.1.8 딕셔너리에서 키-값 쌍을 모두 가져오기

딕셔너리에서 키와 값을 가져오는 메서드는 다음과 같다.

  • items : 키-값쌍을 모두 가져옴
  • keys : 키를 모두 가져옴
  • values : 값을 모두 가져옴

 

25.1.9 리스트와 튜플로 딕셔너리 만들기

먼저 키로 사용할 값들이 들어있는 리스트나 튜플을 준비해야 한다. 그리고 dict.fromkeys()에 키가 들어있는 리스트를 넣으면 딕셔너리가 생성된다. 

dict.fromkeys(키리스트)의 형식으로 딕셔너리를 생성하면 값들은 모두 None가 되고, dict.fromkeys(키리스트, 값)의 형식으로 딕셔너리를 생성하면 값들은 모두 지정한 값이 된다.

 

defaultdict 사용하기

딕셔너리는 없는 키에 접근하면 에러를 발생한다. collections 모듈의 defaultdict를 사용하면 없는 키에 접근하더라도 에러를 발생하지 않고 기본값을 반환한다. defaultdicr()괄호 안에는 기본값 생성 함수가 들어간다.

int()에 아무 값도 넣지 않고 호출하면 0이 나오기 때문에 int 의 기본값으로 0이 나온다.

다른 값을 기본값으로 넣고 싶으면 다음과 같이 사용하면 된다. 

 

25.2 반복문으로 딕셔너리의 키-값 쌍을 모두 출력하기

다음과 같이 for 반복문에 딕셔너리를 지정한 후에 i를 출력하면 키값만 출력된다.

값까지 같이 출력하려면 다음과 같이 for in 뒤에 딕셔너리를 지정하고 items를 사용해야 한다.

키와 값 순서대로 키는 k에 지정하고, 값은 v에 지정하여 출력할 수 있다. 

for in 뒤에 딕셔너리 변수가 아니라 딕셔너리를 직접 지정해도 된다.

 

25.2.1 딕셔너리 키만 출력하기

keys()를 사용하면 된다.

 

25.2.2 딕셔너리의 값만 출력하기

values()를 사용하면 된다.

 

25.3 딕셔너리 표현식 사용하기

딕셔너리도 for 반복문과 if조건문을 이용하여 딕셔너리를 생성할 수 있다. 사용 방법은 다음과 같다.

{키:값 for 키, 값 in 딕셔너리}
dict({키:값 for 키, 값 in 딕셔너리})

for in 다음에 딕셔너리를 지정하여 items()로 키,값 모두를 가져와서 딕셔너리를 생성한다.

다음과 같이 keys()로 키만 가져와 특정 값을 넣거나, values()로 값만 가져와 키로 사용하거나, 키와 값의 자리를 바꿀 수도 있다.

 

25.3.1 딕셔너리 표현식에서 if 조건문 사용하기

딕셔너리는 특정 키를 삭제하는 메서드만 제공하고, 특정 값을 찾아서 삭제하는 메서드는 제공하지 않는다. 반복문을 통해 삭제하려고 하는 값을 찾으면 키를 통해 삭제하려는 방식으로 코드를 구성하면 삭제하고 나서도 반복을 진행해야 하기 때문에 딕셔너리 크기가 중간에 바뀌어서 에러가 발생하게 된다.

이와 같은 경우 딕셔너리 표현식에서 if 조건문을 사용하여 삭제할 값을 제외할 수 있다.

{키: 값 for 키, 값 in 딕셔너리 if 조건식}
dict({키: 값 for 키, 값 in 딕셔너리 if 조건식})

위와 같은 방식은 키-값 쌍을 삭제하는 것이 아니라 삭제할 키-값 쌍을 제외하고 새로운 딕셔너리를 만드는 것이다.

 

25.4 딕셔너리 안에서 딕셔너리 사용하기

딕셔너리는 다음과 같이 값 부분에 다시 딕셔너리가 계속 들어갈 수 있다.

딕셔너리 = {키1: {키A: 값:A}, 키2: {키B: 값B}}

다음은 지구형 행성의 반지름, 질량, 공전주기를 딕셔너리로 표현한 예시이다.

terrestrial_planet = {
    'Mercury': {
        'mean_radius': 2439.7,
        'mass': 3.3022E+23,
        'orbital_period': 87.969
    },
    'Venus': {
        'mean_radius': 6051.8,
        'mass': 4.8676E+24,
        'orbital_period': 224.70069,
    },
    'Earth': {
        'mean_radius': 6371.0,
        'mass': 5.97219E+24,
        'orbital_period': 365.25641,
    },
    'Mars': {
        'mean_radius': 3389.5,
        'mass': 6.4185E+23,
        'orbital_period': 686.9600,
    }
}
 
print(terrestrial_planet['Venus']['mean_radius'])  

딕셔너리 안에 있는 딕셔너리에 접근하려면 딕셔너리 뒤에 [ ](대괄호)를 단계만큼 붙이고 키를 지정해주면 된다.

딕셔너리[키][키]

 

25.5 딕셔너리의 할당과 복사

딕셔너리를 다른 변수에 할당하면 두 딕셔너리는 이름만 다를 뿐 같은 객체이다.

같은 객체이기 때문에 한 변수의 키-값을 변경하면 둘 다 반영된다. 

x와 y를 다른 두 딕셔너리로 만드려면 copy()메서드를 사용하여 모든 키-값 쌍을 복사하면 된다.

x와 y는 다른 객체이기 때문에 is로 비교하면 false가 나오며 둘 중 한 딕셔너리의 값을 변경해도 다른 딕셔너리에 반영되지 않는다.

 

25.5.1 중첩 딕셔너리의 할당과 복사 알아보기

중첩 딕셔너리는 copy메서드로 복사해도 한 딕셔너리의 값 변경시 다른 딕셔너리에도 적용된다. 
중첩 딕셔너리를 완전히 복사하려면 copy모듈의 deepcopy함수를 사용해야 한다.

 

25.6 퀴즈

정답은 c,e이다.

정답은 b,c이다. setdefault는 키만 지정하면 값은 None가 되고, keys는 키만 가져온다.

정답은 b,c,e 이다.

정답은 d 이다.

정답은 d이다.

정답은 c이다.

 

25.7 연습문제: 평균 점수 구하기

정답은 다음 코드와 같다.

average = sum(maria.values()) / len(maria)

 

25.8 심사문제: 딕셔너리에서 특정 값 삭제하기

정답은 다음 코드와 같다.

x = {key: value for key, value in x.items() if value != 30 and key != 'delta'}

삭제해야 하는 요소 중에 해당하는 값을 사용하는 경우도 있기 때문에 표현식을 사용해야 한다.

 

Unit 26. 세트 사용하기

파이썬에서는 집합을 표현하는 세트 자료형을 제공한다. 세트는 합집합, 교집합, 차집합 등의 연산이 가능하다.

 

26.1 세트 만들기

세트는 { }(중괄호) 안에 값을 저장하고, 콤마로 구분한다.

세트는 요소의 순서가 정해져 있지 않고, 요소가 중복되지 않는다. 같은 값을 두 개 이상 넣는다면, 하나만 저장된다. 요소의 순서가 정해져 있지 않기 때문에 인덱스로 특정 요소를 출력할 수 없다.

 

26.1.1 세트에 특정 값이 있는지 확인하기

in 연산자를 사용하면 된다. 값이 있으면 True, 없으면 False가 나온다. 

 

in 앞에 not을 붙이면 값이 없는지 확인한다.

 

26.1.2 set를 사용하여 세트 만들기

set(반복 가능한 객체)와 같은 형식으로 세트를 만든다. 반복 가능한 객체에서 중복된 요소는 제거된다.

빈 세트는 set에 아무것도 지정하지 않으면 된다.

세트는 세트 안에 세트를 넣을 수 없다.

프로즌세트 라는 내용을 변경할 수 없는 세트도 있다. 

프로즌세트는 요소를 추가, 삭제 하는 연산 메서드를 사용할 수 없고, 요소를 변경할 수 없다.

프로즌세트는 다음과 같이 프로즌세트를 중첩해서 넣을 수 있다.

 

26.2 집합 연산 사용하기

| 연산자와 set.union()메서드는 합집합을 구한다. 

& 연산자와 set.intersection()메서드는 교집합을 구한다.

-연산자와 set.difference()메서드는 차집합을 구한다.

^연산자와 set.symmetric_difference()메서드는 대칭 차집합을 구한다. 대칭 차집합은 xor연산자의 특성을 그대로 따라 두 집합중 겹치지 않는 요소만 포함한다.

 

26.2.1 집합 연산 후 할당 연산자 사용하기

세트 자료형에 |,&,-,^연산자와 할당연산자 =을 함께 사용하면 집합 연산의 결과를 다시 변수에 할당한다.

|= 은 현재 세트에 다른 세트를 더하며 update()메서드와 같은 동작을 한다.

&= 와 intersection_update() 메서드는 현재 세트와 다른 세트 중 겹치는 요소만 저장한다.

-= 와 difference_update 메서드는 현재 세트에서 다른 세트를 뺀다.

^= 와 symmetric_difference_update()메서드는 현재 세트와 다른 세트중 겹치지 않는 요소만 현재 세트에 저장한다.

 

26.2.2 부분 집합과 상위 집합

세트는 부분집합, 진부분집합, 상위집합, 진상위집합과 같이 속하는 관계를 표현할 수 있다. 

<=와 issubset() 메서드는 현재 세트가 다른 세트의 부분집합인지 확인한다.

<은 현재 세트가 다른 세트와 진부분집합인지 확인한다. 같은 기능을 하는 메서드는 없다. 진부분집합은 부분집합이지만 두 집합이 같지 않을때 참이다.

>=와 issuperset()메서드는 현재 세트가 다른 세트의 상위 집합인지 확인한다. 두 집합이 같을때도 참이다.

> 은 현재 세트가 다른 세트의 진상위집합인지 확인한다. 같은 기능을 하는 메서드는 없다. 진상위집합은 상위집합이지만 두 집합이 같지 않을 때 참이다.

 

26.2.3 세트가 같은지 다른지 확인하기

== 연산자를 이용하여 세트가 서로 같은지 확인할 수 있다. 세트의 요소는 순서가 없으므로 요소만 같으면 참이다.

!= 연산자는 세트가 다른지 확인한다.

 

26.2.4 세트가 겹치지 않는지 확인하기

disjoint()는 현재 세트가 다른 세트와 겹치지 않는지 확인한다. 겹치는 요소가 없으면 True, 있으면 False를 반환한다.

 

26.3 세트 조작하기

26.3.1 세트에 요소 추가하기

add(요소)는 세트에 요소를 추가한다.

 

26.3.2 세트에서 특정 요소를 삭제하기

remoce(요소)는 세트에서 특정 요소를 삭제하고, 요소가 없으면 에러를 발생한다.

discard(요소)는 세트에서 특정 요소를 삭제하고, 요소가 없으면 그냥 넘어간다.

 

26.3.3 세트에서 임의의 요소 삭제하기

pop()는 세트에서 임의의 요소를 삭제하고 해당 요소를 반환한다. 만약 요소가 없으면 에러를 발생한다.

 

26.3.4 세트의 모든 요소 삭제하기

clear()로 세트의 모든 요소를 삭제할 수  있다.

 

26.3.5 세트의 요소 개수 구하기

len(세트)로 세트의 요소 개수를 구할 수 있다.

 

26.4 세트의 할당과 복사

세트를 만든뒤 다른 변수에 할당하면 이 두 변수는 같은 세트를 가리키기 때문에 is 연산자로 비교해보면 True가 나오고, 요소를 추가하면 두 세트 모두 반영된다.

세트 a와 b를 완전히 두 개로 만드려면 copy()메서드로 모든 요소를 복사해야 한다.

복사를 하면 완전히 다른 두 객체이기 때문에 is 연산자로 비교하면 False가 나오며 값을 추가해도 다른 세트에 반영되지 않는다.

 

26.5 반복문으로 세트의 요소를 모두 출력하기

간단하게 for in 뒤에 세트만 지정하면 된다.

세트의 요소는 순서가 없기 때문에 숫자가 아닌 세트는 출력할 때 마다 순서가 달라질 수 있다.

다음과 같이 in 다음에 세트를 직접 지정해도 된다.

 

26.6 세트 표현식 사용하기

다음과 같이 세트 안에 for 반복문을 지정하면 세트를 생성할 수 있다.

{식 for 변수 in 반복가능한객체}
set(식 for 변수 in 반복가능한객체)

문자열 apple에서 중복되지 않은 문자인 a,p,l,e를 꺼내서 세트를 만든다.

 

26.6.1 세트 표현식에 if 조건문 사용하기

다음과 같이 if 조건문은 for 반복문 뒤에 지정한다.

{식 for 변수 in 세트 if 조건식}
set(식 for 변수 in 세트 if 조건식)

if 조건문을 지정하면 if조건문에서 특정 요소를 제외한 뒤 세트를 생성한다.

 

26.7 퀴즈

정답은 b이다. b는 빈 딕셔너리를 생성하는 방식이다.

정답은 b,e이다. a ^ b는 {1,2,4,5} 이고,  e는 {4,5}이다.

정답은 b,d이다. 진부분집합을 구하는 메서드는 없고, 진상위집합은 두 세트가 같으면 거짓이다.

정답은 a,b,c 이다.

정답은 c,d이다. symmetric_difference_update는  ^=와 같고, issupperset는 >=와 같다.

정답은 a,c이다.

 

26.8 연습문제: 공배수 구하기

정답은 다음 코드와 같다.

{i for i in range(1,101) if i % 3 == 0}
{i for i in range(1,101) if i % 5 == 0}

1부터 100까지 range()로 만들고 그 중 3의배수인 것과 5의 배수인 것을 각 세트에 넣는다.

 

26.9 심사문제: 공약수 구하기

정답은 다음 코드와 같다.

a, b = map(int, input().split())
a = {i for i in range(1, a+1) if a % i == 0}
b = {i for i in range(1, b+1) if b % i == 0}

a, b를 정수로 입력 받고, 1 부터 입력 받은 수까지 입력받은 수와 나눴을때 나머지가 0이 되는 숫자들을 각 세트에 저장했다.

 

Unit 24. 문자열 응용하기

문자열도 문자 여러개가 연속적으로 이어져 있는 시퀀스 자료형이기 때문에 리스트와 비슷하게 사용할 수 있다.

 

24.1 문자열 조작하기

24.1.1 문자열 바꾸기

replace('바꿀 문자열', '새 문자열')의 형식으로 문자열 안의 문자열을 다른 문자열로 바꾼다. 문자열 자체는 바뀌지 않으며 바뀐 결과를 반환한다.

바뀐 결과를 유지하고 싶다면 문자열이 저장된 변수에 replace를 사용한 뒤 다시 변수에 할당해 준다.

 

24.1.2 문자 바꾸기

translate는 문자열 안의 문자를 다른 문자로 만든다. 이것을 사용하기 위해서는 먼저 str.maketrans('바꿀 문자', '새 문자')로 변환 테이블을 만든다. 그 후 translate(테이블)을 사용하면 문자를 바꾼 뒤 결과를 반환한다. 

다음과 같은 경우는 a,e,i,o,u를 각각 1,2,3,4,5로 바꾼다. 

 

24.1.3 문자열 분리하기

split()은 공백을 기준으로 문자열을 분리한다. 또한 괄호 안에 기준 문자열을 지정하면 그 문자열을 기준으로 문자열을 분리한다.

위의 경우 콤마와 쉼표를 기준 문자열로 지정해서 ', '을 분리하여 단어만 리스트로 만든다.

 

24.1.4 구분자 문자열과 문자열 리스트 연결하기

리스트로 분리된 문자열을 다시 연결하여 문자열로 만드는것도 가능하다. join(리스트)로 구분자 문자열과 문자열 리스트의 요소를 연결하여 하나의 문자열로 만든다. 다음은 공백 ' '에 join을 사용하여 각 문자열 사이에 공백이 들어가도록 한 것이다.

 

24.1.5 소문자를 대문자로 바꾸기

문자열의 문자를 모두 대문자로 바꾸려면 upper()를 사용한다. 문자열 안에 대문자가 있으면 그대로 유지된다.

 

24.1.6 대문자를 소문자로 바꾸기

문자열의 문자를 모두 소문자로 바꾸려면 lower()를 사용한다. 문자열 안에 소문자가 있다면 그대로 유지된다.

 

24.1.7 왼쪽 공백 삭제하기

lstrip()은 문자열에서 왼쪽에 있는 모든 연속된 공백을 삭제한다.

 

24.1.8 오른쪽 공백 삭제하기

rstrip()은 문자열에서 오른쪽에 있는 모든 연속된 공백을 삭제한다.

 

24.1.9 양쪽 공백 삭제하기

strip()은 문자열 양쪽에 있는 연속된 모든 공백을 삭제한다.

 

24.1.10 왼쪽의 특정 문자 삭제하기

lstrip('삭제할 문자들')의 형식으로 사용하면 문자열 왼쪽에 있는 해당 문자를 모두 삭제한다.

여기서는 공백을 지정하지 않았기 때문에 공백은 남아있다.

 

24.1.11 오른쪽의 특정 문자 삭제하기

rstrip('삭제할 문자들')의 형식으로 사용하면 문자열 오른쪽에 있는 해당 문자를 모두 삭제한다.

 

24.1.12 양쪽의 특정 문자 삭제하기

strip('삭제할 문자들')의 형식으로 사용하면 문자열 양쪽에 있는 해당 문자들을 삭제한다.

string 모듈의 punctuation에는 모든 구두점이 있다.

다음과 같이 strip()메서드에 string.punctuation을 넣어주면 문자열 양쪽의 모든 구두점을 삭제할 수 있다.

 

24.1.13 문자열을 왼쪽 정렬하기

ljust(길이)는 문자열을 지정한 길이로 만들고, 왼쪽으로 정렬한 후, 남는 공간을 공백으로 채운다.

문자열보다 길이를 작게 지정하면 다음과 같이 아무 변화 없다.

 

24.1.14 문자열을 오른쪽 정렬하기

rjust(길이)는 문자열을 지정한 길이로 만들고, 오른쪽으로 정렬한 후 남은 공간을 공백으로 채운다.

 

24.1.15 문자열을 가운데 정렬하기

center(길이)는 문자열을 지정한 길이로 만들고, 가운데 정렬하며 남는 공간은 공백으로 채운다. 다음의 경우 문자열의 길이를 10으로 만들어 가운데로 정렬한 뒤 양 옆의 남은 공간은 2칸씩 채운다.

남는 공간이 홀수가 된다면 왼쪽에 공백이 한 칸 더 들어간다.

 

24.1.16 메서드 체이닝

문자열 메서드는 처리한 결과를 반환하도록 만들어졌기 때문에 메서드를 계속 연결해서 호출하는 메서드 체이닝이 가능하다. input().split()도 메서드 체이닝이고, 다음 코드는 문자열을 오른쪽 정렬 한 뒤 대문자로 만드는 메서드 체이닝이다.

 

24.1.17 문자열 왼쪽에 0 채우기

zfill(길이)는 지정된 길이에 맞춰서 문자열의 왼쪽에 0을 채운다. 문자열의 길이보다 지정된 길이가 작다면 아무것도 채우지 않는다. 보통 숫자를 일정 자리수로 맞추고 앞자리를 0으로 채울 때 사용한다.

 

24.1.18 문자열 위치 찾기 

find('찾을 문자열')은 문자열에서 해당 문자열을 찾으면 위치 인덱스를 반환하고, 없으면 -1을 반환한다. 왼쪽에서 부터 문자열을 찾는데, 같은 문자열이 여러 개일 경우 처음 찾은 문자열의 인덱스를 반환한다.

 

24.1.19 오른쪽에서부터 문자열 위치 찾기

rfind('찾을 문자열')은 문자열에서 해당 문자열을 오른쪽에서부터 찾아서 찾으면 위치 인덱스를 반환하고, 없으면 -1을 반환한다. 같은 문자열이 여러개인 경우 먼저 찾은 인덱스를 반환한다.

 

24.1.20 문자열 위치 찾기

index('찾을 문자열')은 왼쪽에서 부터 해당 문자열을 찾아서 위치 인덱스를 반환하고, 없으면 에러를 발생시킨다. 해당 문자열이 여러개일 경우 처음 찾은 문자열의 인덱스만 반환한다.

 

24.1.21 오른쪽에서부터 문자열 위치 찾기

rindex('찾을 문자열')은 오른쪽에서부터 특정 문자열을 찾아서 인덱스를 반환한다. 문자열이 없으면 에러를 발생시키며, 해당  문자열이 여러개일 경우 처음 찾은 문자열의 인덱스만 반환한다.

 

24.1.22 문자열 개수 세기

count('문자열')은 현재 문자열에서 특정 문자열이 몇 번 나오는지 확인할 수 있다.

 

24.2 문자열 서식 지정자와 포매팅 사용하기

문자열 안에서 특정 부분을 원하는 값으로 바꿀 때 문자열 서식 지정자 또는 포매팅을 사용한다.

 

24.2.1 서식 지정자로 문자열 넣기

서식 지정자는 %로 시작하고, 자료형을 뜻하는 문자가 붙는다. %s는 문자열 이라는 의미이다. 문자열 안에 서식지정자(%s)를 넣고 그 뒤에 %를 넣고 뒤에 서식 지정자에 넣고 싶은 문자열을 지정한다. 

다음과 같이 문자열을 직접 지정하지 않고, 변수에 넣어 지정하는 것도 가능하다.

 

24.2.2 서식 지정자로 숫자 넣기

십진 정수를 의미하는 서식 지정자는 %d이다.

 

24.2.3 서식 지정자로 소수점 표현하기

실수를 넣을 때는 %f를 사용하며 고정 소수점의 f이다.(Fixed point) %f는 기본적으로 소수점 이하 6자리 까지 표현하며, 소수점 이하 자리수를 지정하고 싶으면 f 앞에 .(점)자리수 를 지정해주면 된다.

 

24.2.4 서식 지정자로 문자열 정렬하기

서식 지정자 % 뒤에 숫자를 넣으면 문자열을 지정된 길이로 만든 뒤 오른쪽으로 정렬하고 남는 공간을 공백으로 채운다.

%d 와 %f도 숫자와 조합하여 오른쪽으로 정렬시킬 수 있다.

%f의 경우 .(점) 앞에 정렬할 길이를 지정하고, 점 뒤에 소숫점 이하 자리수를 지정한다.

왼쪽 정렬은 문자열 길이에 -기호를 붙이면 된다. 

 

24.2.5 서식 지정자로 문자열 안에 값 여러 개 넣기

문자열 앞에 값을 두 개 이상 넣으려면 %를 붙이고, 괄호안에 값(변수)을 콤마로 구분해서 넣으면 된다. 괄호로 값을 묶지 않으면 에러가 발생한다. 

위 경우는 서식 지정자가 두개라 값도 두 개가 들어갔다. 값은 항상 서식 지정자와 개수를 맞춰줘야 한다. 만약 서식 지정자 둘을 붙이면 결과도 붙어서 나오므로 주의해야 한다.

 

24.2.6 format 메서드 사용하기

문자열 포매팅은 { }(중괄호) 안에 포매팅을 지정하고, format()메서드로 값을 넣는다.

{ } 안에는 인덱스를 지정하고, format()안에는 { }에 넣을 값을 지정하면 된다.

 

24.2.7 format메서드로 값을 여러개 넣기

{ } 안에 인덱스를 지정하고 format에는 인덱스가 증가하는 순서대로 값을 넣으면 된다. 문자열에는 인덱스 숫자에 해당하는 값이 들어간다.

format()안에 지정한 값은 인덱스를 0부터 순서대로 지정되며 { }안에 해당하는 인덱스 값에 들어간다.

 

24.2.8 format 메서드로 같은 값을 여러 개 넣기

다음과 같이 같은 인덱스가 지정된 { }가 여러개 있으면 같은 값이 여러 개 출력된다.

 

24.2.9 format 메서드에서 인덱스 생략하기

{ }에서 인덱스를 생략하면 format()에 지정한 순서대로 값이 들어간다.

 

24.2.10 format 메서드에서 인덱스 대신 이름 지정하기

{ } 안에 인덱스 대신 이름을 지정하고, format()에는 이름에 해당하는 값을 지정해주면 된다.

 

24.2.11 문자열 포매팅 변수를 그대로 사용하기

변수에 값을 넣고 { } 에 변수 이름을 지정하고 문자열 앞에 f를 붙이면 변수의 값을 { }에 불러올 수 있다.

{ } 중괄호 자체를 출력하려면 {{, }} 처럼 중괄호를 두번 사용하면 된다.

 

24.2.12 format 메서드로 문자열 정렬하기

다음과 같이 인덱스 뒤에 :(클론)을 붙이고 정렬할 방향과 길이를 지정해주면 된다.

'{인덱스:<길이}'.format(값)

 

부등호 방향이 왼쪽을 가리키고 있기 때문에 왼쪽으로 정렬하고 나머지 공간은 공백으로 채운다.

부등호 방향이 오른쪽을 가리킨다면 오른쪽으로 정렬하고 남는 공간은 공백으로 채운다.

인덱스를 지정하지 않고 클론 뒤에 방향과 길이만 지정해도 된다.

 

24.2.13 숫자 개수 맞추기

정수와 실수 앞에 0을 넣어서 숫자 개수를 맞출 수 있다. %d는 %와 d사이에 0과 숫자 자리수를 넣으면 자리수에 맞춰서 앞에 0이 들어간다. { }를 사용할 때는 인덱스나 이름 뒤에 클론을 붙이고, 03d처럼 0과 숫자 개수를 지정해주면 된다.

실수는 다음과 같이 .(점) 뒤에 소수점 자리수를 지정할 수 있다. 실수는 숫자 개수 부분에 정수 부분, 소수점, 실수부분이 모두 포함된다.

 

24.2.14 채우기와 정렬을 조합하여 사용하기

문자열 포매팅은 채우기와 정렬을 조합하여 사용할 수 있다. 다음과 같이 { }에 인덱스, 채우기, 정렬, 길이, 자릿수, 자료형 순서로 채운다.

'{인덱스:채우기 정렬(부등호) 길이.자릿수 자료형}'

길이를 10으로 만들고 왼쪽, 오른쪽으로 정렬한 후 남은 공간을 0으로 채운 것이다.

실수로 만들 것이면 다음과 같이 소수점 자리수와 실수 자료형 f를 사용하면 된다.

채우기 부분에는 0이 아닌 다른 문자가 들어가도 된다. 공백을 넣어도 되고, 아무 값도 지정되지 않으면 공백으로 처리하며, 다른 아무 문자나 넣어도 된다.

금액을 표현하기 위해 천 단위로 구분할때 format()함수에 숫자와 콤마를 넣어 간편하게 구분할 수 있다.

다음과 같이 서식 지정자와 함께 사용할수 있다. 

포매팅에서 콤마를 넣으려면 다음과 같이 클론 뒤에 콤마를 넣는다.

정렬을 사용하려면 정렬 방향과 길이 뒤에 콤마를 넣는다.

 

24.3 퀴즈

정답은 a, e 이다. count 는 문자열에서 지정한 문자열이 몇번 나오는지 세는 메서드이고, index는 왼쪽에서부터 찾는다.

정답은 d이다.

정답은 b,d이다.

정답은 c,d이다.

 

24.4 연습문제: 파일 경로에서 파일 명만 가져오기

정답은 다음 코드와 같다.

f_list = path.split('\\')
filename = f_list[-1]

경로를 \\를 기준으로 나누면 리스트가 생성된다 출력하고자 하는 내용은 리스트의 마지막 요소에 들어있기 때문에 filename에 -1 인덱스를 저장하면 된다.

 

24.5 심사문제 : 특정 단어 개수 세기

정답은 다음 코드와 같다.

s = input()

s_list = list(s.split())
cnt = 0

for i in s_list:
    if i.strip(',.') == 'the':
        cnt += 1

print(cnt)

입력된 문자열을 공백 기준으로 끊어서 리스트로 만들고, the에 .이나 ,가 붙어있을 수도 있기 때문에 ,.을 제거하고, the랑 비교해서 일치하면 숫자를 하나씩 누적하여 마지막에 누적된 숫자를 출력한다.

 

24.6 심사문제: 높은 가격 순으로 출력하기

정답은 다음 코드와 같다.

price = list(map(int, input().split(';')))

price.sort(reverse=True)

for i in price:
    print('{:>9,}'.format(i))


입력값을 정수로 된 리스트로 만들고, 내림차순으로 정렬한다. 이후 반복문으로 모든 요소들을 9자리로 만들고, 오른쪽으로 정렬하고, 천 단위로 콤마를 붙인다.

 

Unit 25. 딕셔너리 응용하기

25.1 딕셔너리 조작하기

25.1.1 딕셔너리에 키-값 쌍 추가하기

딕셔너리에 키-값 쌍을 추가하는 메서드는 다음 두 가지가 있다.

  • setdefault : 키-값 쌍 추가
  • update : 키의 값 수정, 키가 없으면 키-값 쌍 추가

25.1.2 딕셔너리에 키와 기본 값 저장하기

setdefault(키)는 딕셔너리에 키-값 쌍을 추가한다. setdefault에 키만 지정하면 값에 None을 저장한다. 

setdefault(키, 기본값)으로 지정하면 기본값을 저장한 뒤 해당 값을 반환한다.

 

25.1.3 딕셔너리에서 키의 값 수정하기

update 메서드를 사용하면 값을 수정할 수 있다. update(키=값)으로 값을 수정한다. 키는 큰따옴표나 작은따옴표는 빼고 쓴다.

만약 딕셔너리에 키-값 쌍이 없으면 추가한다.

콤마로 구분하여 키-값 쌍 여러 값을 한번에 수정할 수 있다.

update(키=값)은 키가 문자열일 때만 사용할 수 있다. 키가 숫자일 경우 update()괄호 안에 딕셔너리를 넣어서 수정할 수 있다.

[[키,값], [키,값]]처럼 키와 값을 리스트나 튜플로 수정할 수 있다.

키 리스트와 값 리스트를 묶은 zip객체로도 값을 수정할 수 있다.

Unit 23. 2차원 리스트 사용하기

2차원 리스트는 평면 구조 이다. 다음과 같이 가로x세로 형태로 이루어져 있으며, 행(row)과 열(column) 모두 0부터 시작한다.

 

23.1 2차원 리스트를 만들고 요소에 접근하기 

2차원 리스트는 리스트 안에 리스트를 넣어서 만들 수 있다. 안쪽의 각 리스트는 콤마로 구분한다. 

  • 리스트 = [[값, 값], [값, 값], [값, 값]]

리스트는 가로와 세로를 쉽게 알아내기 위해 다음과 같이 여러 줄로 입력해도 된다.

 

23.1.1 2차원 리스트의 요소에 접근하기 

2차원 리스트의 요소에 접근하거나 값을 할당할 때는 리스트 뒤에 대괄호[ ]를 두 번 사용하며 각각 세로 인덱스와 가로 인덱스를 지정한다.

  • 리스트[세로인덱스][가로인덱스]

2차원 리스트도 0부터 시작하기 때문에 가로 첫번째, 세로 첫번째 요소의 인덱스는 [0][0]이다.

 

톱니형 리스트

다음 리스트 처럼 가로 크기가 불규칙한 리스트를 톱니형 리스트 라고 한다.

톱니형 리스트는 다음과 같이 append메서드를 이용해서도 생성할 수 있다.

 

2차원 튜플 

다음과 같이 튜플안에 튜플을 넣는 방식, 튜플안에 리스트를 넣는 방식, 리스트안에 튜플을 넣는 방식이 있다. 

a = ((1,2), (3,4), (5,6))
b = ([1,2], [3,4], [5,6])
c = [(1,2), (3,4), (5,6)]

튜플은 값을 변경할 수 없다는 특징이 있다. 따라서 a는 안쪽과 바깥쪽 요소 모두 변경할 수 없고, b는 안쪽 리스트만 요소를 변경할 수 있고, c는 바깥쪽 리스트만 요소를 변경할 수 있다.

 

사람이 알아보기 쉽게 출력하기

pprint 모듈의 pprint 함수를 함수를 사용하면 사각형 구조를 유지하도록 출력할 수 있다.

 

23.2 반복문으로 2차원 리스트의 요소 모두 출력하기

23.2.1 for 반복문을 한 번만 사용하기 

for 반복문을 한 번만 사용하는 방법은 다음과 같다. 

in 앞의 변수 개수는 2차원 리스트의 가로 크기와 일치해야 한다. for문에서 2차원 리스트가 가로 한 줄 씩 반복되어 안 쪽 리스트에서 요소들을 통째로 꺼내와서 반복문의 변수에 순서대로 대입한다.

 

23.2.2 for 반복문을 두 번 사용하기 

2중 for문으로 2차원 리스트를 출력하는 방법은 다음과 같다.

a = [[1,2], [3,4], [5,6]]

for i in a:
    for j in i:
        print(j, end=' ')
    print()

for i in a: 는 전체 리스트에서 가로를 한 줄씩 꺼내온다. 이후, for j in i에서 안쪽 리스트의 요소를 하나씩 꺼내온다.

 

23.2.3 for와 range 사용하기

다음과 같이 range에 세로 크기와 가로 크기를 지정해서 리스트의 요소를 인덱스로 접근할 수 있다.

a = [[1,2], [3,4], [5,6]]

for i in range(len(a)):
    for j in range(len(a[i])):
        print(a[i][j], end=' ')
    print()

len(a)로 2차원 리스트의 크기를 구하면 세로 크기(안쪽 리스트의 개수)를 구할 수 있고, len(a[i])로 안쪽 리스트 각각의 안에 있는 요소의 개수를 구할 수 있다. 따라서 a[i][j]로 리스트의 모든 요소들을 출력할 수 있다.

 

23.2.4 while 반복문을 한 번 사용하기 

다음은 while문을 사용하여 2차원 리스트의 요소를 출력한 예시이다.

a = [[1,2], [3,4], [5,6]]
i = 0

while i < len(a):
    x, y = a[i]
    print(x,y)
    i += 1

len(a)로 2차원 리스트의 크기를 구하면 이는 안쪽 리스트의 개수(세로 크기)와 같다. 인덱스를 지정하여 값을 꺼내 올 때는 안쪽 리스트의 요소 개수 만큼 변수를 지정하면 안쪽 리스트(가로)의 요소들을 한 번에 가져올 수 있다.

 

23.2.5 while 반복문을 두 번 사용하기

다음과 같이 while 반복문을 두 번 사용할 수 도 있다.

a = [[1,2], [3,4], [5,6]]
i = 0

while i < len(a):
    j = 0
    while j < len(a[i]):
        print(a[i][j], end=' ')
        j += 1
    print()
    i += 1

2차원 리스트를 접근할 때는 리스트[세로인덱스][가로인덱스]의 형식으로 접근하기 때문에 while i < len(a): 로 세로 크기만큼 반복하고, while j in len(a[i]): 로 가로 크기 만큼 반복한다. while문으로 요소들을 가져왔기 때문에 각 인덱스는 +1 증가 시켜야 한다.

 

23.3 반복문으로 리스트 만들기

23.3.1 for 반복문으로 1차원 만들기

다음과 같이 for 반복문으로 반복하면서 append()로 요소를 추가하면 1차원 리스트를 만들 수 있다.

a = []

for i in range(10):
    a.append(0)

print(a)

 

23.3.2 for 반복문으로 2차원 리스트 만들기

for 반복문을 사용하여 2차원 리스트를 만드는 방법은 다음과 같다.

a = []

for i in range(3):
    line = []
    for j in range(2):
        line.append(0)
    a.append(line)    

print(a)

세로의 크기만큼 반복하며 안쪽 리스트로 사용할 빈 리스트를 만들어야한다. 그리고 가로의 크기만큼 반복하면서 안쪽 리스트에 요소를 추가하면 된다. 리스트에 요소를 추가하는 것은 append()로 추가할 수 있다.

 

23.3.3 리스트 표현식으로 2차원 리스트 만들기

다음과 같이 리스트 표현식을 사용해서도 2차원 배열을 만들 수 있다.

[0 for j in range(2)] 로 [0,0] 을 만들고 이것을 3번 반복하여 위와 같은 배열을 만든다.

다음과 같이 식 부분에서 [0]리스트 자체를 곱해주면 for 반복문을 한 번만 사용할 수 있다.

 

23.3.4 톱니형 리스트 만들기

다음과 같이 가로의 크기를 알고 있다고 가정한 상태에서 톱니형 리스트를 만들 수 있다.

a = [3,1,5,2]
b = []

for i in a:
    line = []
    for j in range(i):
        line.append(0)
    b.append(line)

print(b)

가로 크기를 저장한 리스트 a를 for문으로 반복하여 가로 크기를 꺼내서 각 가로 크기만큼  0을 빈 리스트에 넣고, 이 리스트를 다시 b에 넣는다. 이것도 다음과 같이 리스트 표현식을 이용해 간단하게 만들 수 있다.

가로 크기가 들어있는 리스트 [3,1,2,3,5]에서 꺼낸 숫자만큼 [0]을 곱해서 톱니형 리스트를 만들 수 있다.

 

sorted로 2차원 리스트 정렬하기

sorted(반복가능한객체, key=정렬함수, reverse=True 또는 False)의 형식으로 정렬 할 수 있다.

key에 정렬 함수를 지정하여 안쪽 리스트의 요소를 기준으로 정렬했다. student[1]은 안쪽 리스트의 1번 인덱스를 의미하며, student[2]는 안쪽 리스트의 2번 인덱스를 의미한다. 정렬 함수는 람다 표현식을 이용했다.

 

23.4 2차원 리스트의 할당과 복사 

2차원 리스트를 리스트를 다른 변수에 저장해도 같은 객체이기 때문에 요소를 변경하면 두 리스트에 모두 반영된다.

2차원 리스트는 copy를 사용해 복사해도 한 리스트의 요소를 변경하면 두 리스트에 모두 적용된다.

2차원 리스트 이상의 다차원 리스트는 완전히 복사하려면 copy메서드 대신 copy모듈의 deepcopy함수를 사용해야 한다.

copy.deepcopy()는 중첩된 리스트(튜플)에 들어있는 모든 리스트(튜플)을 복사하는 깊은 복사를 해준다.

 

23.5 퀴즈

정답은 b이다. 

정답은 d이다.

정답은 d이다.

정답은 b이다.

정답은 a,c이다.

 

23.6 연습문제: 3차원 리스트 만들기

정답은 다음 코드와 같다.

[[[for col in range(3)] for row in range(4)] for depth in range (2)]

3차원 리스트는 다음과 같이 높이x가로x세로 의 형태로 되어 있다.

리스트[높이인덱스][세로인덱스][가로인덱스]

 

23.7 심사문제: 지뢰찾기

정답은 다음 코드와 같다.

col, row = map(int, input().split())

matrix = []
cnt = 0

for i in range(row):
    matrix.append(list(input()))

for i in range(len(matrix)):
    for j in range(len(matrix[i])):
        cnt = 0
        if(matrix[i][j] == '.'):
            if i == 0 and j == 0:
                if matrix[i+1][j] == '*':
                    cnt += 1
                if matrix[i][j+1] == '*':
                    cnt += 1
                if matrix[i+1][j+1] == '*':
                    cnt += 1
            elif i == 0 and j == col-1:
                if matrix[i][j-1] == '*':
                    cnt += 1
                if matrix[i+1][j] == '*':
                    cnt += 1
                if matrix[i+1][j-1] == '*':
                    cnt += 1
            elif i == row-1 and j == 0:
                if matrix[i][j+1] == '*':
                    cnt += 1
                if matrix[i-1][j] == '*':
                    cnt += 1
                if matrix[i-1][j+1] == '*':
                    cnt += 1
            elif i == row-1 and j == col-1:
                if matrix[i-1][j] == '*':
                    cnt += 1
                if matrix[i][j-1] == '*':
                    cnt += 1
                if matrix[i-1][j-1] == '*':
                    cnt += 1
            elif i == 0 and 0 < j < col-1:
                if matrix[i+1][j] == '*':
                    cnt += 1
                if matrix[i][j+1] == '*':
                    cnt += 1
                if matrix[i][j-1] == '*':
                    cnt += 1
                if matrix[i+1][j-1] == '*':
                    cnt += 1
                if matrix[i+1][j+1] == '*':
                    cnt += 1
            elif 0 < i < row-1 and j == 0:
                if matrix[i+1][j] == '*':
                    cnt += 1
                if matrix[i-1][j] == '*':
                    cnt += 1
                if matrix[i][j+1] == '*':
                    cnt += 1
                if matrix[i-1][j+1] == '*':
                    cnt += 1
                if matrix[i+1][j+1] == '*':
                    cnt += 1
            elif 0 < i < row-1  and 0 < j < col-1:  ### 여기 조건까지
                if matrix[i+1][j] == '*':
                    cnt += 1
                if matrix[i][j-1] == '*':
                    cnt += 1
                if matrix[i-1][j] == '*':
                    cnt += 1
                if matrix[i][j+1] == '*':
                    cnt += 1
                if matrix[i-1][j-1] == '*':
                    cnt += 1
                if matrix[i-1][j+1] == '*':
                    cnt += 1
                if matrix[i+1][j+1] == '*':
                    cnt += 1
                if matrix[i+1][j-1] == '*':
                    cnt += 1
            elif 0 < i < row-1 and j == col-1:
                if matrix[i+1][j] == '*':
                    cnt += 1
                if matrix[i][j-1] == '*':
                    cnt += 1
                if matrix[i-1][j] == '*':
                    cnt += 1
                if matrix[i+1][j-1] == '*':
                    cnt += 1
                if matrix[i-1][j-1] == '*':
                    cnt += 1
            elif i == row-1 and 0 < j < col-1:
                if matrix[i][j-1] == '*':
                    cnt += 1
                if matrix[i-1][j] == '*':
                    cnt += 1
                if matrix[i][j+1] == '*':
                    cnt += 1
                if matrix[i-1][j+1] == '*':
                    cnt += 1
                if matrix[i-1][j-1] == '*':
                    cnt += 1

            print(cnt, end='')
        else:
            print(matrix[i][j], end='')
    print()

지뢰찾기 구간을 9개로 나눠서 각 경우의 수에 해당하는 코드들을 다 작성했다. 더 간단하고 효율적으로 작성할 수 있는 방법을 생각해봐야 겠다.

 

 

+ Recent posts