본문 바로가기

TIl

함수, 재귀 함수, Packing

함수

특정 작업을 수행하기 위한 재사용 가능한 코드 묶음, ⇒ 모듈화를 시킨다.

함수가 많아지면, 느려진다. 그럼에도 불구하고 유지보수 등을 고려했을 때 사용함

왜? 사용하는 가

코드의 중복을 방지

재사용성이 높아지고, 코드의 가독성 유지보수성 향상

함수 call하고 return 하면 복사되면서 사라짐

내장 함수 Built-in

파이썬이 기본적으로 제공 = 별도의 import 필요 없음

ex) print(), abs()

외장 함수

공식적으로는 존재하지 않음, 단지 내장함수의 반댓말로 말하기 위함

사용자정의함수

개발자가 만든 함수, 내장함수 반댓말 중 하나

함수 호출 (function call)

function_name(arguments)

함수를 실행하기 위해 함수의 이름을 사용하여 해당 함수의 코드 블록을 실행하는 것

구조

def make_sum(pram1, pram2):
  # prameter : 매개변수 
  #  ':'하단부터 'return' 까지를 body라고 함
  return pram1 + pram2 # 

return 값은 무조건 하나 <= return 은 한번만 나오기 때문

여러 줄 주석 할 때도 “”” 대신에 ‘#’ 을 사용하자

""" """ Docstring : Document String
함수를 사용할 가이드를 제공해서 함수에 넣어줌
특수한 역할을 함, 하지만 필수적인 것은 아님
=> 이런 이유로 여러 줄 주석할 때 # 으로 다 처리하는걸 권장 함

정의

#함수 정의
def greet(name): # def 키워드로 시작
  """여러줄    # 클론(:) 다음에 들여쓰기 된 코드 블록
  주석"""    # Docstring은 함수 body 앞에 선택적으로 작성
  message = 'Hello, ' + name
  return message # 함수는 필요한 경우 결과를 반환 가능       # return 문은 함수의 실행을 종료      # => return 뒤로 나오는 문자들 무시함
  • 값을 반드시 받아야 한다? ⇒ 매개변수(name)를 입력해둔다
  • return 문은 함수의 실행을 종료 ⇒ return 뒤로 나오는 문자를 무시함

호출

  • 함수를 호출하기 위해서는 함수의 이름과 필요한 인자(argument)를 전달해야 함
  • 호출 부분에서 전달된 인자는 함수 정의 시 작성한 매개변수에 대입됨

매개변수와 인자

책마다 동일 시 하기도 함 but 다르다

기본적으로 매개변수의 수와 인자의 수는 같아야만 한다.

  • 매개변수 parameter

    함수를 정의할 때, 함수가 받을 값을 나타내는 변수, 지역변수로 취급 됨

  • 인자 argument

    함수를 호출할 때, 실제로 전달되는 값

def add_numbers(x,y): #x와 y는 매개변수(parameter)
  result = x + y
  return result

a = 2
b = 3
sum_result = add_numbers(a,b) # a와 b는 인자(argument)
print(sum_result)

위치 인자 Positional Arguments

  • 함수 호출 시 인자의 위치에 따라 전달되는 인자
  • 위치 인자는 함수 호출 시 반드시 값을 전달해야 한다.
  • 파이썬은 다이나믹 타이핑(변수의 타입을 선언해두지 않음)이기 때문에 위치 인자가 더 중요하다.
def greet(name, age):     print(f'안녕하세요, {name}님! {age}살이시군요.')

greet('Alice', 25) # 안녕하세요, Alice님! 25살이시군요
greet('Bella') # age인자가 없어서 실행 안됨
greet(30) # age인자가 없어서 실행 안됨, 30이 name의 인자로 들어갔음
greet(30,'bella','aaa') # 3개의 인자가 들어가서 에러

return은 필수 X, 하지만 위치 인자 age 필수

기본 인자 값 Default Argument Values

  • 함수 정의에서 매개변수에 기본 값을 할당하는 것
  • 함수 호출 시 인자를 전달하지 않으면, 기본값이 매개변수에 할당됨
  • 반드시 위치 인자 다음에 나와야 한다. def greet(age = 30, name): 은 안됨
  • 인수를 전달할 때 매개변수에 할당된 기본 값의 타입을 맞춰줘야 한다.
def greet(name, age=30): 
  print(f'안녕하세요, {name}님! {age}살이시군요.')

greet('Bob') # 안녕하세요, Bob님! 30살이시군요.
greet('Charlie', 40) # 안녕하세요, Charlie님! 40살이시군요.

키워드 인자 Keyword Arguments

  • 함수 호출 시 인자의 이름과 함께 값을 전달하는 인자
  • 매개변수와 인자를 일치 시키지 않고, 특정 매개변수에 값을 할당할 수 있음
  • 인자의 순서는 중요하지 않으며, 인자의 이름을 명시하여 전달

단, 호출 시 키워드 인자는 위치 인자 뒤에 위치함

greet(age=35, name='Dave') #정상
grret(age=35,'Dave') # 에러

임의의(가변) 인자 목록 Arbitrary Argument Lists

  • 정해지지 않은 개수의 인자를 처리하는 인자

    ⇒ 유연하게 처리할 필요성이 생기기 때문

    print() 함수의 인자가 몇 개 나올지 모르지만 여러개를 넣어둘 수 있는 이유

  • 함수 정의 시 매개변수 앞에 *를 붙여 사용하며, 여러 개의 인자를 tuple 로 처리
def calculate_sum(*args): # 인자가 몇개가 나오던지 묶어버려라   print(args)   total = sum(args)   print(f'합계: {total}')   """   (1, 2, 3)   합계: 6   """
calculate_sum(1, 2, 3)

임의의 키워드(가변 키워드) 인자 목록 Arbitrary Keyword Argument Lists

  • 정해지지 않은 개수의 키워드 인자를 처리하는 인자

    ⇒ 0개이상의 키워드 목록을 처리해야 한다

  • 함수 정의시 매개변수 앞에 **를 붙여 사용하며, 여러 개의 인자를 dictionary로 묶어 처리
def print_info(**kwargs):
  print(kwargs)

print_info(name='Eve', age = 30) #{'name':'Eve','age':30}

함수 인자 권장 작성순서

  • 위치 인자는 반드시 높은 우선순위 (가장 앞에 와야함)
  • 위치 → 기본 → 가변 → 키워드인수 → 가변 키워드

    ⇒ 키와 값을 가진 인수(키워드 인수)들은 위치와 무관하게 잘 찾아갈 수 있다.

    ⇒ 하지만, 위치인수, 기본인수는 정확한 위치를 요구받기때문에 우선순위가 높다.

  • 호출 시 인자를 전달하는 과정에서 혼란을 줄일 수 있도록 함
  • 단, 절대적인 규칙 아님, 유연하게 조정해야함

def func(pos1, pos2, age='default', *args, **kwargs):
  print(pos1, pos2, age, args, kwargs)

func(1,2,3,4,5) # 1 2 3 (4,5) {}
func(1,2,3,1=100,b=200) # 1 2 3 () {'a':100,'b':200}

범위 Scope

함수는 코드 내부에 local scope를 생성하며, 그 외의 공간인 global scope로 구분

scope

  • global scope : 코드 어디에서든 참조할 수 있는 공간
  • local scope : 함수가 만든 scope(함수 내부에서만 참조 가능)

‘함수’ 를 기준으로 나뉜다

variable

  • global variable : global scope 에 정의된 변수
  • local variable : local scope에 정의된 변수

Scope 예시

local scope에 존재하는 변수 global 에서 사용할 수 없음

변수의 수명주기와 연관이 있음

변수 수명주기(lifecycle)

  • 변수의 수명주기는 변수가 선언되는 위치와 스코프에 따라 결정됨
  1. built_in scope

    파이썬이 실행된 이후부터 영원히 유지

  2. global scope

    모듈이 호출된 시점 이후 혹은 인터프리터가 끝날 때까지 유지

  3. local scope

    함수가 호출될 때 생성되고, 함수가 종료될 때까지 유지

이름 검색 규칙(Name Resolution, LEGB로 흔히 말함)

enclosed Scope : 함수 중첩될때

함수 내에서 바깥 Scope의 변수에 접근 가능하나 수정은 할 수 없음 (L → E → G → B)

파이썬에서 사용되는 이름(식별자)들은 특정한 이름공간(namespace)에 저장되어 있음

  • 아래와 같은 순서로 이름을 찾아 나가며, LEGB Rule이라고 부름
    1. Local scope : 지역 범위(현재 작업 중인 범위)
    2. Enclosed scope : 지역 범위 한 단계 위 범위
    3. Global scope : 최상단에 위치한 범위
    4. Built-in scope : 모든 것을 담고 있는 범위(정의하지 않고 사용 할 수 있는 모든 것)

REGB Rule 예시

  • sum이라는 이름을 global scope에서 사용하게 되면서
    기존에 built-in scope에 있던 내장함수 sum을 사용하지 못하게 됨
  • sum을 참조 시 LEGB Rule에 따라 global에서 먼저 찾기 때문
print(sum) # 
print(sum(range(3))) # 3

sum = 5

print(sum) # 5
print(sum(range(3))) # TypeError: 'int' object is not callable
a = 1
b = 2

def enclosed():   a = 10   c = 3   def local(c):     print(a, b, c) # 10 2 500   local(500)   print(a, b, c) # 10 2 3
enclosed()
print(a, b) # 1 2

‘global’ 키워드

  • 변수의 스코프를 전역 범위로 지정하기 위해 사용
  • 일반적으로 함수 내에서 전역 변수를 수정하려는 경우에 사용
  • 일반적으로 권장되지 않음

    ⇒ 함수로 값을 바꾸고자 한다면 항상 인자로 넘기고 함수의 반환 값을 사용하는 것을 권장

num = 0 # 전역 변수   def increment():     global num # num를 전역 변수로 선언     num += 1   print(num) # 0   increment()   print(num) # 1

global 주의사항

global 선언 후 사용가능함

def increment():     # SyntaxError: name 'num' is used prior to global declaration     print(num)     global num     num += 1

매개변수에 global 사용 불가

num = 0
def increment(num):     # "num" is assigned before global declaration     global num     num += 1

재귀 함수

  • 함수 내부에서 자기 자신을 호출하는 함수
  • 어느 시점에서 무조건 종료가 된다

특징

  • 특정 알고리즘 식을 표현할 때 변수의 사용이 줄어들며, 코드의 가독성이 높아짐
  • 1개 이상의 base case(종료되는 상황)가 존재하고, 수렴하도록 작성

    ⇒ 큰 문제를 작은 문제로 쪼개서 해결하기 위해 사용됨

    ⇒ 큰 문제를 잘못 짜면 터지기 때문

  • 반복과 관련 있음, 반복을 재귀로 변경할 수 있지만, 재귀를 반복으로 못 바꾸는 경우가 있다. = 재귀를 써야만 하는 상황이 발생한다.

예시 - 팩토리얼

$$n * (n-1) * ...* 1$$
def factorial(n):
  #종료 조건: n이 0이면 1을 반환 / basis 기저조건
  if n ==0:
    return 1
  #재귀 호출 : n과 n-1의 팩토리얼을 곱한 결과를 반환 / Inductive 유도파트
  return n * factorial(n-1)

#팩토리얼 계산 예시
result = factorial(5)
print(result) #120

재귀 함수는 반드시

  1. 종료 조건을 명확히
  2. 반복되는 호출이 종료 조건을 향하도록

유용한 내장 함수

1. map (function, iterable)

  • 순회 가능한 데이터구조(iterable)의 모든 요소에 함수를 적용하고, 그 결과를 map object로 반환

    iterable : 반복 가능한 친구(시퀀스) str, list, dict, set, range()

    ⇒ 해당 iterable 에 하나하나 적용 하는 것

    map object로 반환하기 때문에, list(), set(), tuple() 등으로 다시 처리해야 함(주로 list())

numbers = input().split() # 1 2 3 4 5
print(numbers) # ['1','2','3','4','5']

# int() 하고싶다
result = map(int, numbers)
print(result) # 

#map 뭐? 가 나온다. 바꿔야지
print(list(result)) #[1,2,3,4,5]

#=> result = list(map(int,input(),split())) 로 한줄로도 씀
  • 사용자 함수도 ( )을 떼고 사용함

2. zip(*iterables)

  • *가포함된다 ⇒ 가변인자를 받는다
  • 임의의 iterable을 모아 튜플을 원소로 하는 zip object를 반환
  • 매개변수의 수를 다르게 해서 테스트 하는 연습하기
girls = ['jane','ashley','jasmine']
boys = ['peter', 'jay']
pair = zip(girls, boys)

print(pair) # 
print(list(pair)) # [('jane', 'peter'), ('ashley', 'jay')] 
# jasmine과 매칭되는 boy는 없기 때문에 zip되지 않았음
arr = [
  [1,2,3],
  [4,5,6],
  [7,8,9]
]
list(zip(*arr)) # list(zip([1,2,3],[4,5,6],[7,8,9]) 
list(zip(arr)) # [([1, 2, 3],), ([4, 5, 6],), ([7, 8, 9],)] # 튜플이니까 (인수,) 형태
print(list(zip(*arr))) # [(1,4,7),(2,5,8),(3,6,9)] #전치행렬 된것(colums 끼리 묶은것)

3. lambda 매개변수: 표현식

이름 없이 정의되고 사용되는 익명 함수

def addition(x, y):
  return x + y

addition = lambda x,y: x + y

일회성으로 함수를 사용할 때 많이 사용됨, map이랑 같이 많이 쓰임

#map과 연결해서 사용할 때
def func(x,y):
  return x ** 2
result1 = list(map(func, numbers))
result2 = list(map(lambda x: x**2, numbers))
# 결과는 같다

매개변수가 두개인 map 람다식 : Collection을 두개 입력해야 함

l = list(map(lambda x, y: x * y, [0, 1, 2, 3], [4, 5, 6, 7]))
print(l) #[0, 5, 12, 21]
# 2차원 정렬 하기
arr = [
  [1,2,3],
  [4,5,6],
  [7,8,9]
]
arr = sorted(arr) #변화없음
arr = [
  [9,7,3],
  [9,5,6],
  [7,8,9]
]
arr = sorted(arr) #[[7,8,9],[9,5,6],[9,7,3]]

arr = sorted(arr, key=lambda x: x[1]) # 몇번쨰 열을 기존으로 정렬을 할 건지? 람다가 필수같다

Packing & Unpacking

변수에 담긴 값들은 튜플(tuple) 형태로 묶임

여러 개의 인자를 튜플로 묶는것

packed_values = 1, 2, 3, 4, 5
print(packed_values)  # (1, 2, 3, 4, 5)
  • * 을 활용한 패킹
    • *b는 남은 요소들을 리스트로 패킹하여 할당
    numbers = [1,2,3,4,5]
    a, *b , c = numbers
    
    print(b) # [2,3,4]
    • print함수에서 임의의 가변인자를 작성할 수 있었던 이유

      ⇒ 인자 개수와 상관 없이 튜플 하나로 패킹 되어서 내부에서 처리

      print(*objects,      sep=' ',end='\n',file=sys.stdout,flush=False)
          '가변인자'       '기본 인자' 들로 이루어짐
      기본적으로 sep, end, file 에 기본적으로 이 값들이 들어가고 있었다.
      print(1,2,3, end=" ") # end 을 커스텀
      print(4,5,6)
      # 1 2 3 4 5 6 출력

언패킹

  • 패킹된 변수의 값을 개별적인 변수로 분리하여 할당하는 것
  • tuple, 리스트 등의 객체의 요소들을 개별 변수에 할당
packed_values = 1,2,3,4,5 # 오른쪽 값이 왼쪽에 적용, 패킹된 값
a,b,c,d,e = packed_values # 오른쪽 값이, 각 요소에 적용, 언패킹된 값

print(a,b,c,d,e) #1 2 3 4 5
패킹된 값의 갯수, 언패킹된 값의 갯수가 동일해야 에러가 나오지 않음
패킹된 값의 갯수 > 언패킹된 값의 갯수 = too many value to unpack
패킹된 값의 갯수 < 언패킹된 값의 갯수 = not enough value to unpack
  • * 는 리스트의 요소를 언패킹
    names = ['alice', 'jane', 'peter']
    print(*names) #alice jane peter
  • ** 는 딕셔너리의 키-값 쌍을 함수의 키워드 인자로 언패킹
    def my_function(x,y,z):
      print(x,y,z)
    
    my_dict = {'x':1,'y':2,'z':3}
    my_function(**my_dict) # 1 2 3

    매개변수 이름과 인자의 이름이 동일해야지 언패킹 가능하다

*, ** 패킹/ 언패킹 연산자 정리

  • *
    • 패킹 : 여러 개의 인자를 하나의 튜플로 묶음
    • 언 패킹 : 시퀀스나 반복 가능한 객체를 각각의 요소로 언패킹하여 함수의 인자로 전달
  • **
    • 패킹 : 사용안함
    • 언 패킹 : 딕셔너리의 키-값 쌍을 키워드 인자로 언패킹하여 함수의 인자로 전달
      def my_function(x, y, z):
        print(x,y,z)
      
      my_dict = {'x':1,'y':2,'z':3}
      my_function(**my_dict) # 1 2 3 

      이 때 dictionary키의 이름개수함수의 매개변수이름개수가 동일해야 한다

      그래야 이 입력 된다.

    추가로 배운 것

    1. 상수 전달하는 방식

    기본 자료형(str 등) : call by value, 값을 복사한다 = 원본값은 변하지 않음

    리스트,객체 : call by refrence 값을 참조한다 = 원본이 변한다

    자료형이 큰 애들은 크기 때문에 직접 찾아가라(참조시키는것이) 메모리 관리가 좋지만, 기본 자료형 들은 작기 때문에 복사하는 것으로 충분하다.

    얕은 복사 (임시방편, 중첩문에서는 작동하지 않음, 1차원용)
    리스트, 딕셔너리 등 '참조'하는 데이터들은 복사하기 위해서는
    1. .copy 메소드를 사용해야된다
    2. list_B = list_A[:]
    a = [    ['시', '반', '경계'],    ['연', '고백', '그림자'],    ['칼날', '간신', '영웅'],    ['열쇠', '변화', '달성'],
    ]
    b = a (x) # 각 각의 리스트를 가지고 있는것 같지만, 하나의 리스트를 두개의 변수가 쓰는 꼴      # => 주소값이 같다는 말
    print(a is b) # True
    c = a.copy() #.copy를 하면 같은 리스트를 공유하는 것이 아님
    print(a is c) # False 

    해야 하는것

    1. 재귀함수

    callback 이란?

    다른 코드의 인자로 넘겨주는 함수

    콜백 함수를 넘겨받은 코드는 이 콜백 함수를 필요에 따라 적절한 시점에 실행됨

    ⇒ 다른 코드에 인자로 넘겨줌 + 제어권 위임

    map(int,list()))에서 int()가 아니라 int로 쓰는 이유

    사용자 함수도 ( )을 떼고 사용함

    callback 함수란?

    함수내에서 함수를 인자로써 사용되는 함수

    ex) map() zip()

    map 함수를 사용할 때 map(int,시퀀스) 처럼 int()를 쓰는 대신에 int만 쓰는 이유

    ⇒ map함수는 함수 int참조 하기 때문이다

    ⇒ int() 라고 적는다면 함수 int()호출 하려고 하기 때문에 에러가 발생한다. ⇒ 함수 이름만 전달하여 참조한다.

    얕은복사 깊은 복사 : 객체를 복사하는 두 가지 다른 방법

    https://black-hair.tistory.com/49

    https://dojang.io/mod/page/view.php?id=2477

    1. 얕은 복사 Shallow Copy

      새로운 객체 생성 but 원본 객체 안에 있는 객체들은 동일한 객체를 참조

      copy 모듈의 copy() 함수나 객체의 메서드 중 하나인 copy() 를 사용함

      참조 : 원본과 복사본이 같은 내부 리스트를 참조하기 때문에 수정 할 시 원본도 같이 변경됨

    2. 깊은 복사 Deep Copy

      새로운 객체 생성 and 모든 객체들에 대해 새로운 복사본을 생성 ⇒ 완전히 새로움

      copy 모듈의 deepcopy() 함수 사용

'TIl' 카테고리의 다른 글

관통 PJT 1  (1) 2024.01.20
모듈, 제어문  (0) 2024.01.18
01.16 Collection, 형변환, 연산자  (0) 2024.01.16
0115 파이썬 1  (1) 2024.01.15
1월 14(토)  (0) 2024.01.13