<jupyter_start><jupyter_text>Writing Reusable Code using Functions in Python

![](https://i.imgur.com/TvNf5Jp.png)

 Part 4 of "Data Analysis with Python: Zero to Pandas"

 다음은 이번 강의에서 다룰 주제입니다:

* 함수를 생성 및 사용하는 법
* 하나 이상의 인수를 사욯하는 함수
* 지역 변수 및 범주
* `return`을 사용하여 값을 반환
* 기능을 유용하게 만들기 위하여 기본 인자 사용
* 함수를 호출할 때. 이름지어진 인자 사용
* 모듈 가져오기 및 라이브러리 사용
* 새로운 활용 사례 처리를 위한 기능 재사용 및 개선
* `try`- `exception`으로 예외 처리
* Docstring 을 이용한 함수 문서 명세  Creating and using functions
함수는 하나 이상의 입력을 사용하고, 작업을 수행하며, 출력을 반환하는 재사용 가능한 명령의 집합입니다.  
Python은 `print`,`len` 등과 같은 많은 내장 기능을 포함하며, 새로운 기능을 정의할 수도 있습니다.<jupyter_code>today = "Saturday"
print("Today is", today)<jupyter_output>Today is Saturday<jupyter_text>`def` 를 사용하여 새로운 함수를 정의할 수 있습니다.<jupyter_code>def say_hello():
    print('Hello there!')
    print('How are you?')<jupyter_output><empty_output><jupyter_text>함수 이름 뒤에 대괄호 또는 `()`과 `:`을 붙입니다.  함수의 본문은 들여쓰기로 구분합니다.  
기능이 정의 될 때는 기능의 본문 내부에 있는 내용이 실행되지 않습니다. 함수가 호출되게 되면, 해당 내용이 실행됩니다.<jupyter_code>say_hello()<jupyter_output>Hello there!
How are you?<jupyter_text>Function arguments
함수는 0개 이상의 값을 입력으로 받을 수 있습니다.(`인자` 혹은 `매개 변수`라고 불립니다.) 인자값은 변수의 값의 따라 다른 연산을 하도록 하는 함수를 작성할수 있게 해줍니다.  
함수는 변수의 값을 저장하거나 또 다른 다양한 방식으로 결과를 반환한다.  

 다음 함수는 list에서 짝수를 걸러내고 `return` 를 통하여 새로운 만들어진 list를 반환한다.<jupyter_code>def filter_even(number_list):
    result_list = []
    for number in number_list:
        if number % 2 == 0:
            result_list.append(number)
    return result_list
even_list = filter_even([1, 2, 3, 4, 5, 6, 7])
even_list<jupyter_output><empty_output><jupyter_text>Writing great functions in Python

Python은 다양한 함수를 강력하고 유연하게 해주는 기능을 제공합니다.  
다음 중 몇 가지 문제를 해결하여 살펴보겠습니다.  


> 라다씨는 `$1,260,000`짜리 집을 살계획이다. 그녀는 구매 자금을 조달하기 위해 두 가지 조건을 고려하고 있습니다.:

> * Option 1: 즉시, 계약금 30만 달러를 내고 나머지 금액에 대해서는 월 10%의 이자율로 8년 만기 대출을 받는다.
> * Option 2: 10년 만기 대출은 전체 금액에 대해 이자율 8%(매월 합산)로 받으세요.
>
> 이 두 대출 모두 동일한 월부금 일 때, 둘 중 월부금의 총합이 낮은 대출은 무엇입니까?


두 가지 대출 옵션에 대한 월부금를 비교할 필요가 있으므로 대출에 대한 월부금를 계산하는 기능을 정의하는 것이 좋은 아이디어일 것이다. 기능에 대한 입력은 주택 비용, 계약금, 대출 기간, 이자율 등이 될 것이다. 차근차근 이 기능을 구축하겠습니다.

먼저, 대출금을 1년 안에 갚아야 학소 이자나 계약금은 없다고 가정하여 주택 전체 비용에 대한 월부금 계산하는 간단한 함수를 작성해봅시다.<jupyter_code>def loan_emi(amount):
    emi = amount / 12
    print('The EMI is ${}'.format(emi))
loan_emi(1260000)<jupyter_output>The EMI is $105000.0<jupyter_text>지역변수 및 볌위

대출기간을 고려하기 위해 2번째 인자값을 추가해 봅시다.<jupyter_code>def loan_emi(amount, duration):
    emi = amount / duration
    print('The EMI is ${}'.format(emi))<jupyter_output><empty_output><jupyter_text>함수 내부에 정의된 변수 'emi'는 외부에서 액세스할 수 없습니다. 다른 `amount` ,`duration` 매개 변수도 마찬가지입니다.  
이러한 모든 `지역변수`는 함수 범위 내에 있습니다.

> **범위**: 범위는 코드 내에서 특정 변수가 표시되는 영역을 나타냅니다. 모든 함수(혹은 클래스 정의)는  Python 내에서 범위를 정의합니다.  
> 이 범위에서 정의된 변수를 `로컬 변수`라고 합니다. 어디서나 사용할 수 있는 변수를 `글로벌 변수` 라고 합니다. 범위 규칙을 사용하면 값을 서로 공유하지 않고 서로 다른 기능에서 동일한 변수의 이름을 사용할 수 있습니다.<jupyter_code>emi
amount
duration<jupyter_output><empty_output><jupyter_text>이제 6년 만기 대출과 10년 만기 대출을 비교할 수 있다.<jupyter_code>loan_emi(1260000, 8*12)
loan_emi(1260000, 10*12)<jupyter_output>The EMI is $10500.0<jupyter_text>반환값

예상할 수 있듯이, 6년 대출의 월부금은 10년 대출에 비해 높습니다. 지금 결과를 출력하고 있습니다.  
비교하기 쉽도록 반환하고 결과를 변수에 저장하는 것이 좋습니다. `return` 구문를 사용하여 이 작업을 수행할 수 있습니다.<jupyter_code>def loan_emi(amount, duration):
    emi = amount / duration
    return emi
emi1 = loan_emi(1260000, 8*12)
emi2 = loan_emi(1260000, 10*12)
emi1
emi2<jupyter_output><empty_output><jupyter_text>선택적 인자값

다음으로, 즉시 계약금을 고려하기 위해 다른 인자값을 추가해봅시다. 해당 인자값의 초기값은 `0`인 `선택적 인자값`로 정의합니다.<jupyter_code>def loan_emi(amount, duration, down_payment=0):
    loan_amount = amount - down_payment
    emi = loan_amount / duration
    return emi
emi1 = loan_emi(1260000, 8*12, 3e5)
emi1
emi2 = loan_emi(1260000, 10*12)
emi2<jupyter_output><empty_output><jupyter_text>다음으로, 함수에 아래 수식을 추가하려고 합니다. 이 수식은 대출에 대한 월부금을 계산하는데 사용됩니다.



* `P` 는 대출의 총합
* `n` 는 개월 수
* `r` 는 월 이율<jupyter_code>def loan_emi(amount, duration, rate, down_payment=0):
    loan_amount = amount - down_payment
    emi = loan_amount * rate * ((1+rate)**duration) / (((1+rate)**duration)-1)
    return emi<jupyter_output><empty_output><jupyter_text>Note that while defining the function, required arguments like `cost`, `duration` and `rate` must appear before optional arguments like `down_payment`.

함수를 정의할 때, `cost`, `duration`, `rate` 와 같은 필수적인 인자들은 `down_payment`와 같은 선택적 인자보다 앞에 나타나야합니다.

Option 1에 대한 월이율을 계산해보겠습니다.<jupyter_code>loan_emi(1260000, 8*12, 0.1/12, 3e5)<jupyter_output><empty_output><jupyter_text>While calculating the EMI for Option 2, we need not include the `down_payment` argument.<jupyter_code>loan_emi(1260000, 10*12, 0.08/12)<jupyter_output><empty_output><jupyter_text>Named arguments

많은 인자값이 필요한 함수는 종종 혼란을 유발합니다. 때문에 Python은 명확한 인자의 구별을 위해 **nameed** 인수를 사용하여 함수를 호춣하는 옵션을 제공합니다.  
함수 호출은 여러 줄로 분할할 수도 있습니다.<jupyter_code>emi1 = loan_emi(
    amount=1260000, 
    duration=8*12, 
    rate=0.1/12, 
    down_payment=3e5
)
emi1
emi2 = loan_emi(amount=1260000, duration=10*12, rate=0.08/12)
emi2<jupyter_output><empty_output><jupyter_text>Modules and library functions

옵션 1의 월부금가 옵션 2의 월부금보다 낮다는 것을 알 수 있습니다. 다만 소수점 이하 자릿수보다는 달러화그로 반올림하면 좋을 것 같다. 이를 위해 숫자를 받아 올림할 수 있는 함수를 작성할 수 있습니다(예: 1.2는 2까지 올림).

Python에서는 올림기능을 함수로 제공합니다. [Python Standard Library](https://docs.python.org/3/library/).  
함수들은 **모듈**안에 구성되어있으며 필요하면 가져와서 사용할 수 있습니다.

> **Modules**: 모듈이란 Python 코드가 있는 파일이다. 모듈은 Python 프로젝트의 코드를 파일과 폴더로 구성하는 방법을 제공한다.  
> Python 스크립트 또는 notebook에서 모듈의 함수를 사용하려면 모듈을 불러와야합니다.  
> 모듈에서 강점은 _namespaces_ 입니다.
> _namespaces_는 캡슐화를 통해 코드와 모듈 또는 모듈 간의 이름 충돌을 방지합니다.

`math` 모듈을 통해 산술 모듈의 `ceil` 함수를 사용하여 올림 기능을 사용할 수 있습니다.<jupyter_code>import math
help(math.ceil)
math.ceil(1.2)<jupyter_output><empty_output><jupyter_text>이제 `math.ceil`함수를 사용하여 `home_loan_emi` 함수의 총 EMI값을 올림하는 기능을 완성해봅시다.

> 함수를 사용하여 다른 기능을 구축하면 코드를 재사용할 수 있고, 복잡한 논리를 관략하게 구현하고 간편하게 관리 할 수 있습니다.  
> 이상적으로 한개의 함수는 한가지 기능만을 수행해야 합니다.<jupyter_code>def loan_emi(amount, duration, rate, down_payment=0):
    loan_amount = amount - down_payment
    emi = loan_amount * rate * ((1+rate)**duration) / (((1+rate)**duration)-1)
    emi = math.ceil(emi)
    return emi
emi1 = loan_emi(
    amount=1260000, 
    duration=8*12, 
    rate=0.1/12, 
    down_payment=3e5
)
emi1
emi2 = loan_emi(amount=1260000, duration=10*12, rate=0.08/12)
emi2<jupyter_output><empty_output><jupyter_text>EMI 값을 비교하여, 낮은 EMI 값을 가지는 옵션을 메세지로 출력해보겠습니다.<jupyter_code>if emi1 < emi2:
    print("Option 1 has the lower EMI: ${}".format(emi1))
else:
    print("Option 2 has the lower EMI: ${}".format(emi2))<jupyter_output>Option 1 has the lower EMI: $14568<jupyter_text>함수의 개선과 재사용성

이제 `Option 1`이 두 옵션 중 낮은 EMI를 가지고 있는 것을 알았습니다. 하지만 더 좋은 것은 이제 우리가 단 몇줄의 코드만으로 비슷한 문제들을 해결할 수 있는 편리한 함수인 `loan_emi`를 가지고 있다는 것 입니다.   

아래에 추가적인 질문에 답해봅시다. > **Q**: 숀은 현재 몇 년 전에 구입한 집을 위해 주택 융자를 갚고 있다. 집값은 80만 달러였고 숀은 25%의 계약금을 냈다. 나머지 금액은 연 7%(매월 합산)의 6년 만기 대출로 조달했다. 숀은 연 12%의 금리로 1년 만기 대출을 받아 6만 달러 상당의 자동차를 구입하고 있다. 두 대출 모두 EMI로 상환된다. 숀이 대출 상환을 위해 매달 지불하는 총 금액은 얼마입니까?
> 
이제 개발한 `loan_emi` 함수를 통해 해당 질문을 해결할 수 있습니다.<jupyter_code>cost_of_house = 800000
home_loan_duration = 6*12 # months
home_loan_rate = 0.07/12 # monthly
home_down_payment = .25 * 800000

emi_house = loan_emi(amount=cost_of_house,
                     duration=home_loan_duration,
                     rate=home_loan_rate, 
                     down_payment=home_down_payment)

emi_house
cost_of_car = 60000
car_loan_duration = 1*12 # months
car_loan_rate = .12/12 # monthly

emi_car = loan_emi(amount=cost_of_car, 
                   duration=car_loan_duration, 
                   rate=car_loan_rate)

emi_car
print("Shaun makes a total monthly payment of ${} towards loan repayments.".format(emi_house+emi_car))<jupyter_output>Shaun makes a total monthly payment of $15561 towards loan repayments.<jupyter_text>예외처리 와 `try`-`except`

> Q: 연리 9%의 10년 만기 대출로 10만 달러를 빌린다면 이자는 얼마가 됩니까?

이 문제를 해결하는 방법은 두 대출에 대한 EMI를 비교해보는 것이다. 하나는 주어진 이자율이고 다른 하나는 0% 이자율이다. 총 이자는 단순히 대출기간 동안의 월차이의 합계이다.<jupyter_code>emi_with_interest = loan_emi(amount=100000, duration=10*12, rate=0.09/12)
emi_with_interest
emi_without_interest = loan_emi(amount=100000, duration=10*12, rate=0./12)
emi_without_interest<jupyter_output><empty_output><jupyter_text>이따금 `wrong!` 에러메세지가 뜨는데, 주의 깊게 보시면 파이썬이 정확히 어떤 문제가 있는지 알려줍니다.
Python은 숫자를 `0`으로 나누려고 하면 `ZeroDiveisionError`메세지를 띄웁니다. 해당 에러는 프로그램의 추가 실행을 중지시키는 `예외` 입니다.

> **Exception**: 문법이나 식이 올바르더라도 Python 인터프리터가 실행하려고 할 때 오류가 발생할 수 있습니다.  
> 예외는 일반적으로 프로그램 내에서 `try`- `except` 구문을 제외하고 처리하지 않는 한 프로그램 실행을 중단합니다.

Python은 기본 연산자, 함수 또는 메서드가 잘못 사용될 때를 위한 기본 예외 처리를 제공합니다.https://docs.python.org/3/library/exceptions.htmlbuilt-in-exceptions.  
또한 사용자가 자체적으로 커스텀 `예외처리`클래스를 정의 할 수도 있습니다.

`try` -  `except`문을 사용하여 예외처리를 할 수 있습니다. 예제는 다음과 같습니다.<jupyter_code>try:
    print("Now computing the result..")
    result = 5 / 0
    print("Computation was completed successfully")
except ZeroDivisionError:
    print("Failed to compute result because you were trying to divide by zero")
    result = None

print(result)<jupyter_output>Now computing the result..
Failed to compute result because you were trying to divide by zero
None<jupyter_text>`try` 문 블럭에서 예외가 발생하면 해당 블럭의 나머지 내용은 건너뜁니다. `except` 블록은 예외 유형이 처리 중인 예외 유형과 일치하는 경우 실행되는 블럭입니다.  
`except`블럭이 실행된 후 프로그렘은 다시 정상적으로 실행됩니다.

복수의 `except`문을 사용하여 복수의 예외 유형을 처리할 수 있습니다.  예외처리는 다음을 참고하시기 바랍니다.  https://www.w3schools.com/python/python_try_except.asp 


금리가 0%인 조건에서 `try` - `except`문을 상용할 수 있도록 `loan_emi` 함수를 개선해봅시다.  
새로운 시나리오와 사용사례가 등장하면 함수를 변경/개선하는 것은 일반적입니다.<jupyter_code>def loan_emi(amount, duration, rate, down_payment=0):
    loan_amount = amount - down_payment
    try:
        emi = loan_amount * rate * ((1+rate)**duration) / (((1+rate)**duration)-1)
    except ZeroDivisionError:
        emi = loan_amount / duration
    emi = math.ceil(emi)
    return emi<jupyter_output><empty_output><jupyter_text>> **Q**: 연리 9%의 10년 만기 대출로 10만 달러를 빌린다면 이자는 얼마가 됩니까?<jupyter_code>emi_with_interest = loan_emi(amount=100000, duration=10*12, rate=0.09/12)
emi_with_interest
emi_without_interest = loan_emi(amount=100000, duration=10*12, rate=0)
emi_without_interest
total_interest = (emi_with_interest - emi_without_interest) * 10*12
print("The total interest paid is ${}.".format(total_interest))<jupyter_output>The total interest paid is $51960.<jupyter_text>Docstring을 사용하여 함수를 문서화 해봅시다..

**docstring**을 사용하여 함수 내에 일부 문서를 추가할 수 있습니다. Docstring은 단순히 기능 본문 내에서 첫 번째 문장으로 나타나는 문자열이며 '도움말' 기능에 사용됩니다. 좋은 문서 문자열은 함수가 수행하는 작업을 설명하고 인수에 대한 설명을 제공합니다.<jupyter_code>def loan_emi(amount, duration, rate, down_payment=0):
    """Calculates the equal montly installment (EMI) for a loan.
    
    Arguments:
        amount - Total amount to be spent (loan + down payment)
        duration - Duration of the loan (in months)
        rate - Rate of interest (monthly)
        down_payment (optional) - Optional intial payment (deducted from amount)
    """
    loan_amount = amount - down_payment
    try:
        emi = loan_amount * rate * ((1+rate)**duration) / (((1+rate)**duration)-1)
    except ZeroDivisionError:
        emi = loan_amount / duration
    emi = math.ceil(emi)
    return emi<jupyter_output><empty_output><jupyter_text>위의 Docstring에서 `duration`과 `rate` 이 몇개월 담위로 측정된다는 것인지 몇 가지 추가적인 정볼흘 제공했습니다. 혼동을 피하기 위해 `duration_months` `rate_monthly`라 이름을 변경하는 것도 고려해볼 수 있습니다.  
함수를 개선할 수 있는 또 다른 방법은 뭐가 있겠습니까?<jupyter_code>help(loan_emi)<jupyter_output>Help on function loan_emi in module __main__:

loan_emi(amount, duration, rate, down_payment=0)
    Calculates the equal montly installment (EMI) for a loan.
    
    Arguments:
        amount - Total amount to be spent (loan + down payment)
        duration - Duration of the loan (in months)
        rate - Rate of interest (monthly)
        down_payment (optional) - Optional intial payment (deducted from amount)<jupyter_text>Exercise - Data Analysis for Vacation Planning

여러분은 휴가를 계획하고 있고, 어느 도시를 방문하고 싶은지 결정해야 해요. 귀하는 4개 도시를 최종 선정했으며 반환 항공편 비용, 일일 호텔 비용 및 주간 렌터카 비용을 확인했습니다. 차를 빌리는 동안, 차를 더 빨리 반납하더라도 일주일 내내 돈을 내야 한다.


| City | Return Flight (`$`) | Hotel per day (`$`) | Weekly Car Rental  (`$`) | 
|------|--------------------------|------------------|------------------------|
| Paris|       200                |       20         |          200           |
| London|      250                |       30         |          120           |
| Dubai|       370                |       15         |          80           |
| Mumbai|      450                |       10         |          70           |         


위의 데이터를 보고 질문에 대답하시기 바랍니다.:

1. 만약 여러분이 1주일의 긴 여행을 계획하고 있다면, 돈을 적게 쓰려면 어느 도시를 방문해야 할까요?
2. 여행 기간을 4일, 10일 또는 2주로 변경하면 이전 질문에 대한 답변이 어떻게 변경됩니까?
3. 여행의 총예산이 1000달러라면 여행 기간을 극대화하기 위해 어느 도시를 방문해야 하는가? 기간을 최소화하려면 어느 도시를 방문해야 하나요?
4. 예산이 '600달러', '2000달러', '1500달러'라면 이전 질문에 대한 답은 어떻게 달라집니까?

*Hint: 이에 대한 해답을 드리자면, 항공료, 호텔 요금, 렌터카 요금, 여행 기간 등 관련 정보를 포함한 `cost of trip` 기능을 정의하는 데 도움이 될 것이다. math.ceil 함수는 렌터카 총비용을 계산하는 데 유용할 수 있다.*<jupyter_code># Use these cells to answer the question - build the function step-by-step<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10095048290663805, 'fraction_numerical': 0.03541315345699832, 'mean_word_length': 3.452901023890785, 'pattern_counts': {'":': 0, '<': 93, '<?xml version=': 0, '>': 111, 'https://': 4, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '25', 'dir': 'jupyter-structured-clean-dedup', 'id': '367990', 'license': 'MIT', 'n_tokens_mistral': 8014, 'n_tokens_neox': 9378, 'n_words': 1702, 'path': '4) python-functions-and-scope/python-functions-and-scope.ipynb', 'repo_name': 'LEE-SEON-WOO/Deep_Learning_Zero_to_Gan', 'size': '13046'}