etc/introducing python: 처음 시작하는 파이썬

4장: 파이 크러스트- 코드 구조

seul chan 2017. 1. 13. 00:42

4.1 코멘트 달기:

# 문자를 사용하여 코멘트를 표시할 수 있다. # 문자가 시작된 곳부터 라인의 마지막까지가 코멘트이다. #가 스트링 안에 있다면 평범한 문자로 쓰인다.

4.2 라인 유지하기: \ (backslash)

한 라인에서 권장하는 최대 문자수는 80. 이를 넘어가지 않게 코드를 작성해야 가독성이 좋아진다.

4.3 비교하기: if, elif, else (조건문)

다른 프로그래밍 언어와 달리 파이썬의 if 조건문에는 괄호가 필요 없다. 대신 문장 끝에 콜론(:)을 사용해야 한다.
조건문의 하위 부분에는 스페이스를 4칸씩 사용한다. 탭과 스페이스를 혼합해서 들여쓰기 하지 않는 것이 좋다! pep-8에는 4칸의 스페이스를 권장한다.

pep-8 확인하기

4.3.1 True & False

확인할 요소가 boolean 형태 (true/false)가 아닐 때 true/false는 어떻게 구분할까? 다음은 모두 false로 간주되는 것들이다

  • null: None
  • 정수 0
  • 부동소수점수 0.0
  • 빈 문자열 ''
  • 빈 리스트 []
  • 빈 튜플 ()
  • 빈 딕셔너리 {}
  • 빈 셋 set()

4.4 반복하기: While

파이썬에서 가장 간단한 looping 메커니즘이다.

4.4.1 중단하기: break

언제 어떤 일이 일어날지 확실하지 않으면 무한 루프 속에 break문을 사용한다.

In [1]:
# 4.4.1 예시 : q를 입력하면 break하는 while문을 만들었다.
while True:
    stuff = input("String to capitalize [type q to quit]:")
    if stuff == 'q':
        break
    else:
        print(stuff.upper())
String to capitalize [type q to quit]:test
TEST
String to capitalize [type q to quit]:wow
WOW
String to capitalize [type q to quit]:interesting
INTERESTING
String to capitalize [type q to quit]:q

4.4.2 건너뛰기: continue

반복문을 중단하고 싶지는 않지만 다음 루프로 건너뛰고 싶을 때 continue를 사용한다.
위의 예제에서 짝수일 경우 건너뛰는 continue문을 추가해보겠다

In [3]:
# 4.4.2 예시 : q를 입력하면 break하는 while문을 만들었다.
while True:
    stuff = input("Integer, please [type q to quit]:")
    if stuff == 'q':
        break
    number = int(stuff)
    if number % 2 == 0:
        continue
    else:
        print(stuff, "squared is", number*number)
Integer, please [type q to quit]:1
1 squared is 1
Integer, please [type q to quit]:2
Integer, please [type q to quit]:3
3 squared is 9
Integer, please [type q to quit]:4
Integer, please [type q to quit]:5
5 squared is 25
Integer, please [type q to quit]:6
Integer, please [type q to quit]:q

4.4.3 break 확인하기: else

break 체커라고 생각하면 된다

4.5 순회하기: for

파이썬에서 iterator는 매우 유용하게 쓰인다. while 문보다 훨씬 파이써닉하게, 우아하게 표현할 수 있다.
다음 예제에서 while문을 파이써닉하게 나타내보겠다

  • 문자열은 한번에 한 문자를 순회한다
  • 딕셔너리는 key를 반환한다.
  • value를 순회하려면 dict.values() 함수를 사용하면 된다
  • 키, value를 모두 순회하려면 items() 함수를 사용한다
In [4]:
rabbits = ['Flopsy', 'Mopsy', 'Cottontail','Peter']
current = 0
while current < len(rabbits):
    print(rabbits[current])
    current += 1
Flopsy
Mopsy
Cottontail
Peter
In [5]:
# for 문을 사용해서 이를 더 쉽게 해보겠다
for rabbit in rabbits:
    print(rabbit)
Flopsy
Mopsy
Cottontail
Peter
In [9]:
accusation = {
    'room':'ballroom',
    'weapon' : 'lead pipe',
    'person':'Col.mustard',
}
for card in accusation:
    print(card)
for value in accusation.values():
    print(value)
for item in accusation.items():
    print(item)
weapon
person
room
lead pipe
Col.mustard
ballroom
('weapon', 'lead pipe')
('person', 'Col.mustard')
('room', 'ballroom')
In [11]:
# .items() 함수를 key, value로 하나씩 할당 가능하다
for card, value in accusation.items():
    print('card', card, 'has the contents', value)
card weapon has the contents lead pipe
card person has the contents Col.mustard
card room has the contents ballroom

4.5.1 break

while문의 break와 똑같이 동작한다

4.5.2 continue

while문의 continue와 똑같이 동작한다

4.5.3 else

모든 항목을 순회했는지 확인하는 부가적인 옵션의 else문이 있다. for에서 break문이호출되지 않으면 else문이 실행된다. 즉, for문에서 뭔가를 찾지 못했을 때 else문이 실행된다고 생각하면 된다.

4.5.4 여러 시퀀스 순환하기: zip()

zip()은 여러 시퀀스를 병렬로 순회한다

In [12]:
#4.5.4 예제
days = ['Monday', 'Tuesday', 'Wednesday']
fruits = ['bannan', 'orange', 'peach']
drinks = ['coffee', 'juice', 'beer']
desserts = ['tiramisu', 'ice cream', 'pie', 'pudding']
for day, fruit, drink, dessert in zip(days, fruits, drinks, desserts):
    print(day, ": drink", drink, "- eat", fruit, "- enjoy", dessert)
Monday : drink coffee - eat bannan - enjoy tiramisu
Tuesday : drink juice - eat orange - enjoy ice cream
Wednesday : drink beer - eat peach - enjoy pie
In [14]:
# 4.5.4 zip 예제 2
english = ['Monday', 'Tuesday', 'Wednesday']
french = ['Lundi', 'Mardi', 'Mercredi']
print(list(zip(english, french)))
dict(zip(english, french))
[('Monday', 'Lundi'), ('Tuesday', 'Mardi'), ('Wednesday', 'Mercredi')]
Out[14]:
{'Monday': 'Lundi', 'Tuesday': 'Mardi', 'Wednesday': 'Mercredi'}

4.5.5 숫자 시퀀스 생성하기: range()

range() 함수는 리스트, 튜플과 같은 자료구조를 생성하여 저장하지 않고 특정 범위의 스트림을 반환한다. 이는 컴퓨터 메모리를 전부 사용하지 않고 효과적으로 큰 범위를 생성할 수 있게 한다.
range(start, stop, step) 형식을 사용한다.

  • start를 입력하지 않으면 0부터 시작한다.
  • stop은 꼭 입력하지 않으면 안된다. 범위의 끝은 stop -1 까지
  • step의 기본값은 1. -1로 지정해서 끝부터 할 수 있다.

4.6 컴프리헨션(comprehension)

컴프리헨션은 하나 이상의 이터레이터로 파이썬의 자료 구조를 만드는 컴팩트한 방법이다. 이는 반복문과 조건 테스트를 결합할 수 있게 해준다.
매우 파이써닉한 방법이다.

4.6.1 리스트 컴프리헨션

1부터 5까지의 정수 리스를 다양한 방법으로 만들 수 있다.

[표현식 for 항목 in 순회 가능한 객체]

  1. number_list = [] 한 후에 append(1)...로 5까지 넣어주기
  2. 이터레이터와 range 함수를 사용
  3. 리스트에 직접 range()를 넣기: number_list = list(range(1, 6))
  4. 리스트 컴프리헨션: number_list = [number for number in range(1,6)]

다음과 같이 조건 표현식을 포함할 수 있다. (예제- number_list)

[표현식 for 항목 in 순회 가능한 객체 if 조건]

또한 하나 이상의 중첩 루프 (for)를 사용할 수 있다 (예제- row, col)

In [16]:
# list comprehension 예제
number_list = [number for number in range(1, 6)]
number_list
Out[16]:
[1, 2, 3, 4, 5]
In [19]:
number_list = [number for number in range(1, 6) if number % 2 ] 
# number % 2 는 1일 때 true, 0일때 false 
number_list
Out[19]:
[1, 3, 5]
In [21]:
rows = range(1, 4)
cols = range(1, 3)
for row in rows:
    for col in cols:
        print(row, col)
1 1
1 2
2 1
2 2
3 1
3 2
In [23]:
#list comprehension으로 위의 for 문을 표현하면
cells = [(row, col) for row in rows for col in cols]
print(cells)
for row, col in cells:
    print(row, col)
[(1, 1), (1, 2), (2, 1), (2, 2), (3, 1), (3, 2)]
1 1
1 2
2 1
2 2
3 1
3 2

4.6.2 딕셔너리 컴프리헨션

{키 표현식: 값_표현식 for 표현식 in 순회 가능한 객체}

In [24]:
# 딕셔너리 컴퓨리헨션 예제
word = 'letters'
letters_counts = {letter: word.count(letter) 
                for letter in word 
                }
letters_counts
# 여기서는 l, e, t, t, e, r 6번이 반복되어 count됨
Out[24]:
{'e': 2, 'l': 1, 'r': 1, 's': 1, 't': 2}
In [26]:
letters_counts = {
    letter : word.count(letter) for letter in set(word)
}
letters_counts
# set(word)를 순회하는 것은 문자열 word를 순회하는 것과 다르게 문자를 반환.
# set(word) 에서 나온 l, e, t, r만 반복
Out[26]:
{'e': 2, 'l': 1, 'r': 1, 's': 1, 't': 2}

4.6.3 셋 컴프리헨션

딕셔너리 컴프리헨션과 비슷한 모양을 하고 있고, if테스트, for 다중문 또한 사용 가능하다.

4.6.4 제너레이터 컴프리헨션

튜플은 컴프리헨션이 없다. 리스트의 [] 대신 ()로 바꿔서 사용하면 튜플 컴퓨리헨션이 생성 될 것이라고 생각 할 수 있고, 실제로 예외가나타나지 않기 때문에 잘 동작하는 것으로 보인다.
하지만 이는 '제너레이터 컴프리헨션'이고 제너레이터 객체를 반환한다.

In [34]:
number_thing = (number for number in range(1, 6))
type(number_thing)
# 제너레이터 타입임을 확인할 수 있다
Out[34]:
generator
In [36]:
for number in number_thing:
    print(number)
# 바로 순회할 수 있는 것을 확인 할 수 있다.
# 하지만 제너레이터는 한 번만 실행할 수 있다. 다시 순회하면 아무것도 나오지 않는다. 

4.7 함수

코드 조각을 다루기 쉽게 관리하는 다양한 방법들이 있다.
첫 번째 단계로 코드의 재사용을 위한 함수가 있다.

  • 정의하기(define)
  • 호출하기(call) 함수로 전달한 값을 인자(argument)라고 부른다. 인자의 값은 함수 내에서 해당하는 parameter (매개 변수)에 복사된다.

만약 return 값을 명시적으로 호출하지 않으면 None을 얻는다

None은 아무것도 없다는 것을 뜻하는 파이썬의 특별한 값이다. Non은 false와는 다르다. 0, '', [], {} 등은 모두 false지만 None 은 아니다

4.7.1 위치 인자

파이썬은 다른 언어에 비해 함수의 인자를 독특하고 유연하게 처리한다. 가장 익숙한 타입은 순서대로 상응하는 매개 벼누에 복하사는 '위치 인자'(positional arguments)다. 위치 인자의 단저은 각 위치의 의미를 알아야 한다는 것이다

4.7.1 키워드 인자

위치 인자의 혼동을 피하기 위해서 매개변수에 상응하는 이름을 인자에 지정할 수 있다. 위치 인자, 키워드 인자를 섞어서 쓸 수 있다. (대신 위치 인자가 먼저 와야한다)

4.7.2 기본 매개변수값 지정하기

매개 변수에 기본 값을 지정할 수 있다. 호출자가 대응하는 인자를ㄹ 제공하지 않으면 기본 값을 사용하는 것이다.

In [39]:
# 4.7.1 위치 인자 예시
def menu(wine, entree, dessert):
    return {'wine': wine, 'entree':entree, 'dessert':dessert}
menu('chardonnay', 'chicken', 'cake')
Out[39]:
{'dessert': 'cake', 'entree': 'chicken', 'wine': 'chardonnay'}
In [40]:
# 4.7.2 키워드 인자
menu(wine='bordeaux', entree='beef', dessert='bagel')
Out[40]:
{'dessert': 'bagel', 'entree': 'beef', 'wine': 'bordeaux'}
In [41]:
# 4.7.3 기본 매개 변수- 함수를 정의할 때 지정해야한다. 
def menu(wine, entree, dessert = 'pudding'):
    return {'wine': wine, 'entree':entree, 'dessert':dessert}
menu('cs', 'pork')
Out[41]:
{'dessert': 'pudding', 'entree': 'pork', 'wine': 'cs'}
In [42]:
# 기본 인자값은 실행될 때가 아닌, 정의할 때 계산한다. 
# 아래 예제는 정의할 때 계산된 result(empty list)가 두 번째 'b'를 추가할 때
# 이미 'a'를 가지고 있는 것을 볼 수 있다. 
def buggy(arg, result=[]):
    result.append(arg)
    print(result)
In [43]:
buggy('a')
['a']
In [44]:
buggy('b')
['a', 'b']
In [45]:
def works(arg):
    result = []
    result.append(arg)
    return result
works('a')
Out[45]:
['a']
In [47]:
works('b')
Out[47]:
['b']

4.7.4 위치 인자 모으기: * (argments)

arg는 가변 인자, 맨 끝에 args를 써서 나머지 인자를 모두 취하게 할 수 있다.

  • *를 사용할 때 가변 인자의 이름으로 args를 쓸 필요는 없지만 관용적으로 args를 사용한다
In [50]:
def print_args(*args):
    print('Positional argument tuple:', args)
In [51]:
print_args()
Positional argument tuple: ()
In [52]:
print_args(3, 2, 1, 'wait!', 'uh...')
Positional argument tuple: (3, 2, 1, 'wait!', 'uh...')
In [53]:
def print_more(required1, required2, *args):
    print("need this one:", required1)
    print("need this two:", required2)
    print("all the rest:", args)
In [55]:
print_more('cap', 'belt', 'watch', 'laptop')
need this one: cap
need this two: belt
all the rest: ('watch', 'laptop')

4.7.5 키워드 인자 모으기: ** (keyword argments)

키워드 인자를 딕셔너리로 묶기 위해 **를 사용할 수 있다.

In [56]:
def print_kwargs(**kwargs):
    print('Keyword arguments:', kwargs)
In [ ]:
print_kwargs(wine='merlot', entree='mutton', dessert='macaroon')

4.7.6 docstring (document string)

redability counts (가독성은 중요하다)라는 구절이 zen of python에 들어있다. 함수에 커멘트를 달고 싶으면 docstring을 추가해주면 된다.

  • docstring은 반드시 함수 본문 내에서 처음으로 정의되는 객체여야 함
  • 코드 상으로는 함수 선언 바로 다음 줄에 써줘야 한다
  • docstring은 단순한 주석문이 아니라, 함수의 attribute 중 하나이다.
  • help() 함수, print(echo.doc) 를 통해 docstring을 출력할 수 있다.
  • 여러 줄에 걸쳐서 사용하고 싶으면 '''(따옴표 세 개)를 사용하면 된다.
In [59]:
# docstring 예제
def echo(anything):
    'echo returns its input argument twice'
    return anything + anything
In [63]:
help(echo)
Help on function echo in module __main__:

echo(anything)
    echo returns its input argument twice

In [64]:
print(echo.__doc__)
echo returns its input argument twice

4.7.7 일등 시민: 함수

모든 것이 객체(object)다 는 파이썬의 mantra이기도 하다. 파이썬에서 함수는 first-class citizen이다. 이말은 함수를 변수에 할당할 수 있고, 다른 함수에서 이를 인자로 쓸 수 있다는 말이다.

In [65]:
# 함수를 변수에 할당하는 예제
def answer():
    print(42)
def run_something(func):
    func()
run_something(answer)
42
In [66]:
def add_args(arg1, arg2):
    print(arg1+arg2)
def run_something_with_args(func, arg1, arg2):
    func(arg1, arg2)
run_something_with_args(add_args, 5, 9)
14
In [67]:
# 이것들을 *args, **kwargs 인자와 결합해서 쓸 수 있다
def sum_args(*args):
    return sum(args)
def run_with_positional_args(func, *args):
    return func(*args)
run_with_positional_args(sum_args, 1, 2, 3, 4)
Out[67]:
10

함수를 리스트, 튜플, 셋, 딕셔너리의 요소로 사용할 수 있다.

4.7.8 내부 함수

함수 안에 또 다른 함수를 정의할 수 있다. inner function은 루프, 코드 중복을 피하기 위해 또 다른 함수 내에 어떤 복잡한 작업을 한 번 이상 수행할 때 유용하게 사용 된다.

In [69]:
# 내부함수 예제
def outer(a, b):
    def inner(c, d):
        return c + d
    return inner(a, b)
outer(4, 7)
Out[69]:
11
In [70]:
# 인자에 텍스트를 붙여주는 inner func
def knights(saying):
    def inner(quote):
        return "We are the knights who say: '%s'" % quote
    return inner(saying)
knights('Ni!')
Out[70]:
"We are the knights who say: 'Ni!'"

4.7.9 클로져(closure)

내부 함수는 클로져처럼 행동할 수 있다. 클로저는 다른 함수에 의해 동적으로 생성되고 바깥 함수로부터 생성된 변수값을 변경, 저장 가능한 함수이다. 아래 예제는 inner2()를 활용해 인자를 취하지 않고 outer func의 변수를 직접 사용한다.

In [75]:
def knight2(saying):
    def inner2():
        return "We are the knights who say: '%s'" % saying
    return inner2
In [76]:
a = knight2('Duck')
b = knight2('Hasenpfeffer')
type(a)
Out[76]:
function
In [77]:
a
# a는 함수이자 클로져이기도 하다
Out[77]:
<function __main__.knight2.<locals>.inner2>
In [78]:
a()
Out[78]:
"We are the knights who say: 'Duck'"
In [80]:
b()
# a, b를 호출하면 knight2() 함수에 전달되어 사용된 saying을 기억하고 있따
Out[80]:
"We are the knights who say: 'Hasenpfeffer'"

4.7.10 익명 함수: lambda()

파이썬의 람다 함수는 단일문으로 표현되는 anoymous function이다.
edit_story() 함수를 정의해 예시를 들어좌

  • words: words 리스트
  • func: words의 문자열에 적용되는 함수
  • 위 함수가 시행되려면 words 리스트와 이에 적용될 함수가 필요하다. 위에서 사용한 edit_story()를 람다로 간단하게 바꿀 수 있다.
In [81]:
def edit_story(words, func):
    for word in words:
        print(func(word))
# 위 함수가 시행되려면 words 리스트와 이에 적용될 함수가 필요
In [82]:
stairs = ['thud', 'meow', 'thud', 'hiss']
def enlive(word):
    return word.capitalize() + '!'
# enlive 함수는 첫글자를 대문자로 만들고 !를 붙여준다 
In [83]:
edit_story(stairs, enlive)
Thud!
Meow!
Thud!
Hiss!
In [85]:
# 위의 함수를 람다로 바꿔보았다.
edit_story(stairs, lambda word: word.capitalize()+'!')
# 람다에서 하나의 word 인자를 취했다. 
# 대부분의 경우에서 enliven()과 같이 실제 함수를 사용하는 것이 명확하다
# 하지만 작은 함수를 정의하고, 콜백 함수를 정의하는 경우에 람다를 사용할 수 있다
Thud!
Meow!
Thud!
Hiss!

4.8 제너레이터 (Generator)

제너레이터는 파이썬의 시퀀스를 생성하는 객체이다. 제너레이터로 전체 시퀀스를 메모리에 생성할 필요 없이 잠재적으로 시퀀스를 순회할 수 있다. 제너레이터는 이터레이터에 대한 데이터의 소스로 자주 사용된다.

  • 제너레이터를 순회 할 때마다 마지막으로 호출 된 항목을 기억하고 다음 값을 반환한다.
  • 일반 함수는 이전 호출에 대한 메모리가 없고 항상 똑같은 상태로 첫 라인부터 수행
  • 대표적으로 range() 함수가 제너레이터이다.
  • 제너레이터 함수에서는 return 대신에 yield를 사용한다.
In [86]:
sum(range(1, 101))
Out[86]:
5050
In [87]:
# range 함수를 제너레이터 함수로 정의해보았다. 
def my_range(first = 0, last=10, step=1):
    number = first
    while number < last:
        yield number
        number += step
In [88]:
# 다음과 같이 함수 시행
my_range
Out[88]:
<function __main__.my_range>
In [89]:
# 다음과 같이 제너레이터 객체를 반환한다
ranger = my_range(1, 5)
ranger
Out[89]:
<generator object my_range at 0x7faf285ddfc0>
In [90]:
for i in ranger:
    print(i)
1
2
3
4

4.9 데코레이터 (decorator)

데코레이터는 하나의 함수를 취해서 또 다른 함수를 반환하는 함수이다. 이를 사용하기 위해서 다음과 같은 무기를 사용한다

  • args, **kwargs
  • 내부 함수
  • 함수 인자

아래서 정의할 document_it() 함수는 다음과 같이 데코레이터를 정의한다

  • 함수 이름, 인자값을 출력한다
  • 인자로 함수를 실행한다
  • 결과를 출력한다
  • 수정된 함수를 사용할 수 있게 반환한다.
In [91]:
def document_it(func):
    def new_function(*args, **kwargs):
        print('Running function:', func.__name__)
        print('Positional arguments:', args)
        print('Keyword arguments:', kwargs)
        result = func(*args, **kwargs)
        print('result:', result)
        return result
    return new_function

위의 document_it() 함수에 어떤 func를 전달하던지간에 document_it() 함수에 추가 선언문이 포함된 새로운 함수를 얻는다. decorator는 실제로 func 함수로부터 코드를 실행하지는 않지만 func를 호출하여 새로운 함수를 얻는다.
수동으로 데코레이터를 적용해보자

In [93]:
def add_ints(a, b):
    return a + b
add_ints(3, 5)
Out[93]:
8
In [95]:
cooler_add_ints = document_it(add_ints) # 데코레이터를 수동으로 할당
cooler_add_ints(3, 5)
Running function: add_ints
Positional arguments: (3, 5)
Keyword arguments: {}
result: 8
Out[95]:
8

위와 같이 수동으로 데코레이터를 할당하는 대신에, 데코레이터를 사용하고 싶은 함수에 그냥 @데코레이터_이름 을 추가할 수 있다

In [96]:
@document_it
def add_ints(a, b):
    return a+b
In [97]:
add_ints(3, 5)
Running function: add_ints
Positional arguments: (3, 5)
Keyword arguments: {}
result: 8
Out[97]:
8

함수는 여러개의 데코레이터를 가질 수 있다. result를 제곱하는 sqare_it() 데코레이터를 추가해 보겠다.

함수에서 가장 가까운 (def 바로 위) 데코레이터를 먼저 실행한 후 그 위의 데코레이터가 실행된다.

In [98]:
def square_it(func):
    def new_function(*args, **kwargs):
        result = func(*args, **kwargs)
        return result * result
    return new_function
In [99]:
# square_it 데코레이터가 적용되어 result가 64가 되고, 이를 document_it에서 반환
@document_it
@square_it
def add_ints(a, b):
    return a+b
add_ints(3, 5)
Running function: new_function
Positional arguments: (3, 5)
Keyword arguments: {}
result: 64
Out[99]:
64
In [100]:
# 순서를 바꾸면 결과는 같지만 과정이 다르다
# 위와 달리 아래 함수는 document_it이 먼저 실행되고 result * result를 반환
@square_it
@document_it
def add_ints(a, b):
    return a+b
add_ints(3, 5)
Running function: add_ints
Positional arguments: (3, 5)
Keyword arguments: {}
result: 8
Out[100]:
64
In [120]:
# 데코레이터 이해를 돕기 위한 예제
def outer_func(msg):
    def inner_func():
        print(msg)
    return inner_func
hi_func = outer_func('Hi')
bye_func = outer_func('Bye')

hi_func()
bye_func()
Hi
Bye
In [121]:
def decorator_func(original_func):
    def wrapper_func():
        return original_func()
    return wrapper_func
def display():
    print('display 함수가 시행되었습니다.')

decorated_display = decorator_func(display)
decorated_display()
display 함수가 시행되었습니다.
In [132]:
def decorator_func(original_func):
    def wrapper_func():
        print('{original_func}함수가 호출되기 전입니다.'.format(original_func = original_func.__name__))
        return original_func()
    return wrapper_func

def display_1():
    print('display_1 함수가 실행되었습니다.')
    
def display_2():
    print('display_2 함수가 실행되었습니다.')
In [133]:
@decorator_func
def display_1():
    print('display_1 함수가 실행되었습니다.')
@decorator_func
def display_2():
    print('display_2 함수가 실행되었습니다.')
In [135]:
display_1()
display_1함수가 호출되기 전입니다.
display_1 함수가 실행되었습니다.
In [143]:
def decorator_func(original_func):
    def wrapper_func(*args, **kwargs):
        print('{original_func}함수가 호출되기 전입니다.'.format(original_func = original_func.__name__))
        return original_func(*args, **kwargs)
    return wrapper_func

def display_info(name, age):
    print('display_2({},{}) 함수가 실행되었습니다.'.format(name, age))
In [144]:
display_info('john',25)
display_2(john,25) 함수가 실행되었습니다.

4.10 네임스페이스와 스코프

네임스페이스는 특정 이름이 유일하고, 다른 네임스페이스에서의 같은 이름과 관계가 없는 것을 말한다. 즉, 메인 프로그램에서 x와 함수에서 x라는 변수는 서로 다른 것을 참조한다.

  • 메인 프로그램은 전역 네임스페이스를 정의 (global variable)
In [146]:
animal = 'fruitbat'
def print_global():
    print('inside print_global:', animal)
print('at the top level:', animal)
print_global()
at the top level: fruitbat
inside print_global: fruitbat
In [147]:
# 함수에서 global variable의 값을 얻어서 바꾸려 하면 에러가 난다
def change_and_print_global():
    print('inside chgange_and_print_global:', animal)
    animal = 'wombat'
    print('after the change:', animal)
change_and_print_global()
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-147-12ee9f741ba6> in <module>()
      4     animal = 'wombat'
      5     print('after the change:', animal)
----> 6 change_and_print_global()

<ipython-input-147-12ee9f741ba6> in change_and_print_global()
      1 # 함수에서 global variable의 값을 얻어서 바꾸려 하면 에러가 난다
      2 def change_and_print_global():
----> 3     print('inside chgange_and_print_global:', animal)
      4     animal = 'wombat'
      5     print('after the change:', animal)

UnboundLocalError: local variable 'animal' referenced before assignment
In [148]:
def change_local():
    animal = 'wombat'
    print('inside change_local:', animal, id(animal))
change_local()
inside change_local: wombat 140390273074768
In [150]:
id(animal)
Out[150]:
140390272682992

함수 내의 지역 변수(local variable)가 아닌 global variable을 접근하기 위해 global 키워드를 사용해서 전역 변수의 접근을 명시해야 한다

In [151]:
animal = 'fruitbat'
def change_and_print_global():
    global animal
    animal = 'wombat'
    print('inside chnage_and_print_global:', animal)
print(animal)
change_and_print_global()
print(animal)
fruitbat
inside chnage_and_print_global: wombat
wombat

함수 안에 global를 사용하지 않으면 파이썬은 로컬 namespace를 사용하고 local variable은 함수를 수행한 뒤 사라진다.

파이썬은 네임스페이스의 내용에 접근할 수 있게 두 함수를 제공한다

  • locals(): 로컬 네임스페이스 내용이 담긴 딕셔너리
  • globals(): 글로벌 네임스페이스 내용이 담긴 딕셔너리 반환
In [152]:
animal = 'fruitbat'
def change_local():
    animal = 'wombat' # local var
    print('locals:', locals())
In [153]:
animal
Out[153]:
'fruitbat'
In [154]:
change_local()
locals: {'animal': 'wombat'}

4.10.1 이름에 _와 __ 사용

파이썬 변수를 선언할 때 두 언더스코어 (__)를 사용하면 안된다.

  • 예를 들어 함수의 이름은 시스템 변수 function.name 에 있다.
  • docstring은 function.doc 안에 있다.
In [156]:
def amazing():
    '''
    this is the amazing func.
    Want to see it again?
    '''
    print('This func is named:', amazing.__name__)
    print('And its docstring is:', amazing.__doc__)
In [157]:
amazing()
This func is named: amazing
And its docstring is: 
    this is the amazing func.
    Want to see it again?
    
In [159]:
amazing.__doc__
Out[159]:
'\n    this is the amazing func.\n    Want to see it again?\n    '

4.11 에러 처리하기: try, except

파이썬에서는 관련 에러가 발생할 때 실행되는 코드인 exception을 사용한다. 어떤 상황에서 실패할 수 있는 코드를 실행할 때는 모든 잠재적인 에러를 방지하기 위해 적절한 예외 처리가 필요하다.
문제를 해결하지 못하면 사용자에게 상황을 알리고 정상적으로 프로그램을 종료하는게 좋다. 만약 이에 대한 핸들러를 제공하지 않으면 에러 메세지, 오류 위치를 출력하고 프로그램을 종료한다.
에러가 발생하도록 코드를 내버려두는 것보다, 에러가 예상되는 코드에 try문을 사용하고, 에러를 처리하기 위해 except 문들 사용한다.

In [2]:
short_list = [1,2,3]
position = 5
short_list[position]
# 위 예제에서는 IndexError가 발생했다. 
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-2-39cecf346f17> in <module>()
      1 short_list = [1,2,3]
      2 position = 5
----> 3 short_list[position]

IndexError: list index out of range
In [3]:
# 에러 처리를 한 예시
short_list = [1,2,3]
position = 5
try:
    short_list[position]
except:
    print('Need a position between 0 and', len(short_list)-1, 'but got',\
         position)
# try 블록 안의 코드를 실행할 때 에러가 있다면 예외가 발생하고, except 코드가 실행
Need a position between 0 and 2 but got 5

만약 인자 없이 except를 지정하면 모든 예외 타입을 잡겠다는 것이다. 두개 이상의 예외 타입이 발생하면 각각 별도의 예외 핸들러를 사용하는 것이 좋다.

각 에러에 대한 세부 정보를 얻고 싶다면 변수 이름에서 예외 객체 전체를 얻을 수 있다.

except 예외 타입 as 이름

다음 예제는 IndexError를 먼저 찾아 출력한다

In [5]:
short_list = [1, 2, 3]
while True:
    value = input('Position [q to quit]?')
    if value == 'q':
        break
    try:
        position = int(value)
        print(short_list[position])
    except IndexError as err:
        print('Bad index:', position)
    except Exception as other:
        print('Something wrong:', other)
Position [q to quit]?0
1
Position [q to quit]?5
Bad index: 5
Position [q to quit]?two
Something wrong: invalid literal for int() with base 10: 'two'
Position [q to quit]?q

4.12 예외 만들기

모든 예외는 파이썬 표준 라이브러리에 미리 정의되어 있다. 즉, 필요한 예외 처리를 선택해서 사용할 수 있는 것이다. 또한 만든 프로그램에서 특별한 상황의 예외를 처리하기 위해 예외 타입을 정의할 수 있다.

예외 타입을 정의하려면 클래스 객체 타입을 정의하여야 한다

예외는 클래스이고, Exception 클래서의 자식이다. 다음 예제에서 words 문자열에 대문자가 있을 때 예외를 발생시키는 UppercaseException 예외를 만들어보자

In [6]:
class UppercaseException(Exception):
    pass
words = ['eeenie', 'meenie', 'miny', 'MO']
for word in words:
    if word.isupper():
        raise UppercaseException(word)
---------------------------------------------------------------------------
UppercaseException                        Traceback (most recent call last)
<ipython-input-6-8c22de013301> in <module>()
      4 for word in words:
      5     if word.isupper():
----> 6         raise UppercaseException(word)

UppercaseException: MO

심지어 UppercaseException 에 대한 행동도 정의하지 않고 pass문만 사용했는데도 부모 클래스인 Exception이 예외가 발생했을 때 무엇을 출력하는지 알아내도록 처리하고 있다 .
다음과 같이 예외 객체에 접근해서 내용을 출력할 수 있다.

In [8]:
class OopsException(Exception):
    pass

try:
    raise OopsException('panic')
except OopsException as exc:
    print(exc)
panic

4.13 연습문제

4.1

guess_me 변수에 7을 할당하고 값이 7보다 작으면 too low를, 7보다 크면 too high를, 7과 같으면 just right을 출력하는 조건테스트를 작성하여라

In [10]:
guess_me = 7
if guess_me == 7:
    print('just right')
elif guess_me > 7:
    print('too high')
else:
    print('too low')
just right

4.2

guess_me 변수에 7을 할당하고, start에 1을 할당한다. start, guess_me를 비교하는 while 문들 작성하고 start가 작은 경우에는 'too low'를, 같은 경우에는 found it!을 출력하고 루프를 종료한다. start가 큰 경우에는 oops를 출력하고 루프를 종료한다. 그리고 루프의 마지막에 start를 1씩 증가시킨다.

In [13]:
guess_me = 7
start = 1
while True:
    if start < guess_me:
        print('too low')
    elif start == guess_me:
        print('found it!')
        break
    else:
        print('opps')
        break
    start += 1
    
too low
too low
too low
too low
too low
too low
found it!

4.3

for 문으로 [3, 2, 1, 0]을 출력하여라

In [29]:
for i in [3, 2, 1, 0]:
    print(i)
3
2
1
0

4.4

리스트 컴프리헨션을 사용하여 range(10)에서 짝수 리스트를 만들어라

In [30]:
even_li = [number for number in range(10) if number % 2 ==0]
even_li
Out[30]:
[0, 2, 4, 6, 8]

4.5

딕셔너리 컴프리헨션을 이용하여 squares 딕셔너리를 생성하여라. range(10)을 키로, 각 키의 제곱을 value로 한다

In [32]:
squared_dict = {number:number*number for number in range(10)}
squared_dict
Out[32]:
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}

4.6

셋 컴프리헨션을 이용하여 range(10)의 홀수 셋을 만들어라

In [37]:
odd_set = {number for number in range(10) if number % 2}
odd_set
Out[37]:
{1, 3, 5, 7, 9}

4.7

제너레이터 컴프리헨션을 이용하여 'Got'과 range(10)의 각 숫자를 반환하라.

Got 1
Go2 2
...

이런 식으로

In [46]:
temp_generator = ("Got{num}".format(num=num)
                  for num in range(10)
                 )
for i in temp_generator:
    print(i)
Got0
Got1
Got2
Got3
Got4
Got5
Got6
Got7
Got8
Got9

4.8

'Harry', 'Ron', 'Hermione' 리스트를 반환하는 good 함수를 정의하라

In [48]:
def potter_friend():
    print(['Harry','Ron', 'Hermione'])
potter_friend()
['Harry', 'Ron', 'Hermione']

4.9 <- 이해 안됨

range(10)의 홀수를 반환하는 get_odds 제너레이터 함수를 정의하라. for문으로 반환된 세번째 함수를 찾아서 출력하라

In [53]:
def get_odds():
    generator = (num for num in range(10) if num % 2)
    for i in generator:
        yield i
for i in get_odds():
    print(i)
1
3
5
7
9
In [54]:
def get_odds():
    for number in range(1, 10, 2):
        yield number
for count, number in enumerate(get_odds(), 1):
    if count == 3:
        print("The third odd number is:", number)
        break
The third odd number is: 5

4.10

어떤 함수가 호출되면 start, 끝나면 end를 출력하는 test decorator를 정의하라

In [93]:
def test_decorator(func):
    def new_func(*args, **kwargs):
        print('start')
        result = func(*args, **kwargs)
        print('end')
        return result
    return new_func
In [96]:
@test_decorator
def add_ints(a, b):
    print(a + b)
In [97]:
add_ints(1,2)
start
3
end

4.11

OopsException이라는 예외를 정의하라. 이 예ㄹ외를 잡아서 Caught an oops를 출력하는 코드를 작성하라

In [98]:
class OopsException(Exception):
    pass
In [100]:
try:
    for i in range(5):
        if i == 3:
            raise OopsException
except OopsException as caught:
    print("Caught an oops")
Caught an oops

4.12

zip() 함수를 사용하여 다음 두 리스트를 짝으로 하는 movies 딕셔너리를 만들어라

In [101]:
titles = ['Creature of Habit', 'Crewel Fate']
plots = ['A nun turns into a mon ster', 'A haunted yarn shop']
In [107]:
for title, plot in zip(titles, plots):
    movies[title] = plot
movies
Out[107]:
{'Creature of Habit': 'A nun turns into a mon ster',
 'Crewel Fate': 'A haunted yarn shop'}