1. 함수 중요성

  • 첫 번째 , 코드의 흐름을 원활히 할 수 있다.
    • 요즘은 코드의 복잡도가 커지면서 코드 양이 매우 많아졌다. 그래서 이 코드를 일괄작성하기가 힘들다. 이에 대한 대책으로 단계별로 생각하여 각 단계마다 함수를 사용하여 개발을 원활하게 풀어갈 수 있다.
  • 두 번째, 함수로 사용하면 코드의 재사용성이 향상된다.
    • 하나의 기능을 각 소스마다 중복하여 집어 넣으면, 그 기능을 수정해야할 경우, 다 수정해야하는 번거로움이 있다. 이런 것들이 비효율적이기 때문에, 함수로 만들면 한 번의 수정으로 다 수정할 수 있다.
  • 세 번째, 코드의 안정성이 좋아진다.
    • 그 이유는 개발자가 자신이 담당하는 함수에만 집중할 수 있기 때문에, 함수 이외의 부분과 나눠서 생각할 수 있다.

 

2. 함수 선언 및 사용

2.1 return의 유무

  • 함수에서 return을 사용하지 않으면

    • print로 출력할 수 없다.
    • unpacking을 사용할 수 없다.
  • 여러 값을 return 으로 반환하는 것을 다중 반환이라 한다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    
    ## return 명령어가 없을 경우
    > def func_mul(x):
    >    y1 = x * 10
    
    > x1 = func_mul(10)
    > print(x1, type(x1))
    None <class 'NoneType'>
    # 출력할 수 없다.
    
    ## return 명령어가 있을 경우
    > def func_mul(x):
    >    y1 = x * 10
    >    return y1
    
    > x1 = func_mul(10)
    > print(x1)
    100
    
    
    ## No return and unpacking
    > def func_mul1(x):
    >    y1 = x * 10
    >    y2 = x * 20
    >    y3 = x * 30
    
    # unpacking
    > x1, x2, x3 = func_mul1(10)
    
    > print(x1, x2, x3)
    TypeError: cannot unpack non-iterable NoneType object
    
    # 다중 반환 확인하기
    > def func_mul1(x):
    >    y1 = x * 10
    >    y2 = x * 20
    >    y3 = x * 30
    >    return y1, y2, y3
    # unpacking
    > x1, x2, x3 = func_mul1(10)
    > print(x1, x2, x3)
    100 200 300
    

 

2.2 원하는 data type으로 return하기

  • 원하는 data type으로 함수값을 출력하려면 어떻게 해야하는지 알아보자.

  • return 할 data의 type이 출력할 data type이 된다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    
    ## tuple return
    > def func_mul1(x):
    >    y1 = x * 10
    >    y2 = x * 20
    >    y3 = x * 30
    >    return (y1, y2, y3)  # tuple 형식
    
    > t1 = func_mul1(10)
    > print(t1, type(t1))
    (100, 200, 300)  <class 'tuple'>
    
    ## list return
    > def func_mul2(x):
    >    y1 = x * 10
    >    y2 = x * 20
    >    y3 = x * 30
    >    return [y1, y2, y3]  # list 형식
    
    > t1 = func_mul2(10)
    > print(t1, type(t1))
    [100, 200, 300] <class 'list'>
    
    ## dictionary return
    > def func_mul3(x):
    >    y1 = x * 10
    >    y2 = x * 20
    >    y3 = x * 30
    >    return {'v1' : y1, 'v2' : y2, 'v3' : y3} # keyword 형식
    
    > t1 = func_mul3(10)
    > print(t1, type(t1))
    > print(t1.values())
    {'v1': 100, 'v2': 200, 'v3': 300} <class 'dict'>
    dict_values([100, 200, 300])
    
    > d = func_mul3(30)
    > print(type(d), d, d.get('v2'), d.items(), d.keys())
    <class 'dict'> {'v1': 300, 'v2': 600, 'v3': 900} 600 dict_items([('v1', 300), ('v2', 600), ('v3', 900)]) dict_keys(['v1', 'v2', 'v3'])
    

 


3. Packing, Unpacking

 

3.1 Positional argument, Keyword argument

  • 함수 인자에는 Positional argument(위치인자)Keyword argument(키워드 인자)가 있다.
    • 인자란 함수 기능에 필요한 값을 말한다.
    • 기본값이란 미리 기본으로 지정된 값을 말한다.
    • Positional argument는 인자값이 위치 에 의해 결정되는 인자다.
      • 순서 가 중요하다.
    • Keyword argument는 key value가 key 에 의해 결정되는 인자다.
      • 순서 상관 없이 keyword 가 중요하다.

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
## Positional argument(위치인자)
# real number (실수)는 앞에, imaginary number(허수)는 뒤에 위치해야 된다.
# 위치 즉, 순서가 중요하다.
> complex(3, 5)
(3 + 5j)

## Keyword argument(키워드 인자)
# key = value
> complex (real = 3, imag = 5)
(3 + 5j)

 

3.2 Packing의 두 종류

print함수처럼 함수가 받을 인자의 갯수를 유연하게 지정 하기 위해 사용하는 것

  • print 함수는 객체의 갯수에 제한 없이 출력한다.

    1
    2
    3
    4
    5
    6
    7
    
    # 1개
    > print('123 456 789')
    123 456 789
    
    # 3개
    > print('123', '456', '789')
    123 456 789
    
  • packing

    • arguments하나의 객체로 합쳐서 받을 수 있도록 한다.
    • positional argument packing 과 keyword argument packing이 있다.
    • 다음 table과 같은 특징을 가진다.
      • args: arguments / kwargs: keyword arguments
      • args와 kwargs는 매개변수 명으로, 자유롭게 명명한다.
packingpositional argument packingkeyword argument packing
expression*args**kwargs
data typetupledictionary

 

3.2.1 Positional arguments packing

  • positional argument에 대해 앞서서 enumerate () 에 대해 알아보겠다.

  • enumerate는 index 와 value 를 tuple 형식으로 하나의 성분으로서 맺어주고, return 해주는 함수다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    ## enumerate()
    > seasons = ['Spring', 'Summer', 'Fall', 'Winter']
    
    # enumerate()를 하면 바로 id 값만 출력된다.
    > print(enumerate(seasons))
    <enumerate object at 0x000002957E6DE640>
    
    > print(list(enumerate(seasons)))
    [(0, 'Spring'), (1, 'Summber'), (2, 'Fall'), (3, 'Winter')]
    
    > print(tuple(enumerate(seasons)))
    ((0, 'Spring'), (1, 'Summber'), (2, 'Fall'), (3, 'Winter'))
    
  • enumerate () 를 사용하여 positional arguments packing을 설명하겠다.

  • enumerate ()for ~ in문에 사용하겠다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    # args == arguments
    > def args_func(*args):
    >   for i, v in enumerate(args):
    >     print('Result : {}'.format(i), v)
    
    # 인자의 수가 다양해도 다 받아지는 걸 알 수 있다.
    > args_func('Lee')
    Result : 0 Lee
    
    # 위치인자로 보낸 모든 객체들('Lee', 'Park')을 *args로 하나의 객체로서 관리해준다.
    > args_func('Lee', 'Park')
    Result : 0 Lee
    Result : 1 Park
    
    
    > args_func('Lee', 'Park', 'Kim')
    Result : 0 Lee
    Result : 1 Park
    Result : 2 Kim
    

 

3.2.2 Keyword arguments packing

  • keyword argument packing을 사용하는 방법

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    > def kwargs_func(**kwargs):
    >     for v in kwargs.keys():
    >       print("{}".format(v), kwargs[v])
    
    # keyword arguments를 packing 하여 dictionary로 관리한다.
    > kwargs_func(name1='Apple')
    name1 Apple
    
    > kwargs_func(name1='Apple', name2='Window')
    name1 Apple
    name2 Window
    
    > kwargs_func(name1='Apple', name2='Window', name3='Choice')
    name1 Apple
    name2 Window
    name3 Choice
    
  • positional argumentkeyword argument를 같이 사용해보자.

    1
    2
    3
    4
    
    > def example(args_1, args_2, *args, **kwargs):
    >    print(args_1, args_2, args, kwargs)
    > example(10, 20, 'Lee', 'Kim', 'Park', 'Cho', age1=20, age2=30, age3=40)
    10 20 ('Lee', 'Kim', 'Park', 'Cho') {'age1': 20, 'age2': 30, 'age3': 40}
    
  • args_1, args_2 로 총 2개이므로, print의 매개변수 앞에서 2개까지가 일반적인 positional argument이다.

  • 그 뒤에, *args 는 positional argument packing이므로 제한 없다. tuple 로 출력된 걸 확인할 수 있다.

  • 맨 마지막 인자는 ** 이므로, keyword argument packing이다. dictionary로 출력된 걸 확인할 수 있다.

 

3.3 Unpacking

주의사항: Unpacking 시 해체되는 인자의 수와 매칭되는 변수의 수가 동일해야 가능하다.

  • Unpackingpacking과는 반대로 여러개의 객체를 포함하고 있는 하나의 객체를 푼다.

  • packing 시에는 function (or method) 정의 시, parameter*을 붙였지만,

  • unpacking 시에는 function (or method) 사용 시, argument 앞에 *를 붙여서 사용한다.

    1
    2
    3
    4
    5
    
    > def sum(a, b, c):
    >  return a + b + c
    > number = (1, 2, 3)
    > print(sum(*number))
    6
    
  • 또는 다음과 같은 방식으로 unpacking 할 수 있다.

    1
    2
    3
    4
    5
    6
    7
    
    > def func_mul1(x):
    >    y1 = x * 10
    >    y2 = x * 20
    >    y3 = x * 30
    >    return y1, y2, y3
    # unpacking
    > x1, x2, x3 = func_mul1(10)
    

 

4. 중첩 함수 (Nested function)

함수 내부에 정의된 또 다른 함수

  • 중첩 함수는 함수형 프로그래밍에서 많이 사용된다.

  • 부모 함수(외부 함수)와 하위 함수(내부 함수)

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    # 중첩 함수
    > def nested_func(num):  # 부모 함수
    >    def func_in_func(num): # 부모 함수의 매개변수를 받아서 사용 가능
    >      print(num)
    >    print("In func")
    >    func_in_func(num + 100)
    
    > nested_func(100)
    200
    
    # 부모 함수의 하위 함수를 호출하여 사용할 수 없다.
    > func_in_func(100)
    NameError: name 'func_in_func' is not defined
    

 


5. 람다(lambda) 함수 (익명함수)

  • 람다식의 장점 from python 공식 사이트

    • 메모리 절약
    • 가독성 향상
    • 코드 간결
  • 람다식의 단점 (많은 실력자 분들이 람다식을 부정적으로 피력한다.)

    • 과한 사용 시, 가독성 감소된다. 왜냐하면 익명 함수이기 때문이다.
      • 일반적인 함수는 함수명을 보고 그 기능을 추측할 수 있으나, 익명 함수라 추측할 수 없다.
  • 일반적인 함수와 람다식 함수의 차이

    • 일반적인 함수함수명 이 있기 때문에, 객체 생성 된다. 그 후, resource(memory)를 할당 한다.
    • 하지만, 람다식 함수
      • 즉시 실행 함수 라서 일회성이고,
      • Heap 영역에 저장되고 (Heap 초기화),
      • 파이썬의 garbage collection에 의해 메모리 초기화가 되기 때문에, 메모리를 효율적으로 사용할 수 있다.
      • 함수명이 존재하지 않아, 익명 함수라 한다. 그래서 별도의 변수에 할당해야 한다.
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    
    ## 첫 번째: 이미 변수에 할당해 놓은 일반적인 함수를 넣는 방법
    > def mul_func(x, y):
    >   return x * y
    
    > print(mul_func(10, 50))
    500
    
    > mul_func_var = mul_func
    > print(mul_func_var(10, 50))
    500
    
    ## 두 번째: 자주 쓰는 람다 함수이기 때문에, 정의를 해서 변수로 넘기는 방식
    # 일시적으로 그 자리에서 함수가 필요할 때 사용한다.
    # def 와 return이 없어도 가능하다.
    # 람다식을 넣은 함수
    > lambda_mul_func = lambda x,y : x * y
    > print(lambda_mul_func(10, 50))
    500
    
    # 함수 안에서 함수를 인자로 받는 함수
    > def func_final(x, y, func):
    >     print(x * y * func(1,1))
    
    ## 첫 번째 방식
    > func_final(10, 50, mul_func_var)
    500
    
    ## 두 번째 방식
    > func_final(10, 50, lambda_mul_func)
    500
    
    ## 세 번째 방식: 바로 그 자리에서 람다식을 써서 넘기는 방법
    > func_final(10, 50, lambda x,y : x * y)
    
  • 위 방식대로 총 함수를 정의하는데 3가지 방식이 있다.

  • 각 방식에 대해서 언제 무엇을 써야할지 생각해보자.


 

6. 함수 Type Hint: Annotation

함수의 매개변수와 함수의 결과값의 각 데이터 타입을 알려주기 위해 python 3.5 부터 나온 기능

  • def <function-name>(parameter1: <data type>) -> <함수 결과값의 data type>

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    # 아래 예시처럼 각 매개변수의 데이터 타입이 무엇인지 알려준다.
    # 그리고, 함수의 결과값의 데이터 타입도 알려준다.
    > def tot_length1(word: str, num: int) -> int:
    >    return len(word) * num
    
    # 아래 함수는 위 함수와 동일하다.
    > def tot_length1(word, num):
    >    return len(word) * num
    
    > print('hint exam1 : ', tot_length1("i love you", 10))
    > print(type(tot_length1("i love you", 10)))
    hint exam1 : 100
    <class 'int'>
    
    > def tot_length2(word: str, num: int) -> None:
    >    print('hint exam2 : ', len(word) * num)
    
    
    > print(tot_length2("niceman", 10))
    > print(type(tot_length2("i love you", 10)))
    hint exam2 : 70
    <class 'Nonetype'>
    

’tot_length2’의 data type이 ‘Nonetype’인 이유는 return 값이 없기 때문이다.


7. Method와 function 과의 차이

Method: 객체에 속한 function

  • 해당 내용은 다음 문서를 참고했다.

  • 기본적인 function의 expression은 다음과 같다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    > def functionName(arg1, arg2, ...):
    >  """
    >  # Function _body
    >  """
    
    > def sum(num1, num2):
    >   return num1 + num2
    
    > sum(5,6)
    11
    
  • Method의 expression은 다음과 같다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    > class ClassName:
    >    def method_name():
    >       …………..
    >       # Method_body
    >       ………………
    
    > class Pet(object):
    >   def my_method(self):
    >     print("I am a Cat")
    
    > cat = Pet()
    > cat.my_method()
    I am a Cat
    
  • 그러면 위 두 가지 코드를 통해서 function과 method의 차이는 무엇일까??

    • function과 달리 method는 class object와 관련하여 호출된다. 위 코드를 보자면 “cat” 인스턴스 객체에 관련된 “my_method” 를 호출했다. 하지만, function “sum” 은 객체 없이 호출된다.
    • 또한, method는 클래스 객체에 관련하여 호출되기 때문에, 객체 안에 있는 data에 접근할 수 있다. 그래서 객체의 상태를 바꿀 수 있지만, function은 할 수 없다.
  • 즉, method는 클래스 객체에 속한 function임을 알 수 있다.

    • function과 method의 호출 방법 차이: 그래서 method는 . dot annotation을 통해 사용된다.
  • 이를 보다 깊이 들어가자면 Docs.python - Functions and methods 이 공식 문서를 참고해보자.

    • 클래스의 namespace에 저장된 functions들은 호출될 때, method로 바뀐다고 한다. 단지 method는 “객체 인스턴스"가 다른 인자들보다 앞에 오는 점에서 일반 function들과 다르다고 한다.

 

Reference