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번째 수의 합과 같다.

+ Recent posts