공부방/Upstage AI Lab 4기

7/30 학습일지 | 파이썬 함수 쓸 때 꼭 살펴볼 것

Eddie_D 2024. 7. 31. 00:20

파이썬에서 함수를 쓸 때 꼭 알아두어야 할 것들!!

 

1. 함수에 파라미터와 리턴값을 확인하자. (리턴값이 없을 수도 있다.)

파라미터에 디폴드값은 뒤에 몰아준다. 앞에 디폴트를 주고 뒤에 디폴트가 없으면 신텍스 에러가 뜬다.

def foo(a = 3, b):
    c = a + b
    return c

 

컴퓨터 입장에서 foo(5) 를 넣었다고 치면, 5가 a=5 에 들어가는지, b=5에 들어가는지 알 수 없기 때문.

def foo(a, b = 3):
    c = a + b
    return c

위 함수에 foo(5)를 넣어주면 a에 5가 들어가고,  b는 값이 없어서 디폴드값인 3이 들어가서 8이 리턴된다. 디폴드값은 아무런 값이 들어오지 않았을 때에만 들어가는 값이기 때문에, 디폴트가 있다고 해도 값을 넣어주면 입력한 값으로 계산된다. 

함수의 리턴값 ⭐⭐⭐

L = [3,2,1]
L.sort() #이것도 함수. 그런데 리턴값이 없다. 리턴이 없는 함수들도 있다.(파라미터 역시 없을수도 있음)
print(L) #L 자체가 정렬되어 [1,2,3]이 나온다. 
print(L.sort) #none을 리턴하므로, 아무것도 나오지 않는다. 출력하면 None이 뜬다.

L2 = [3,2,1]
L2_sort = sorted(L2) #L2는 변경하지 않고, L2를 정렬시킨 새로운 리스트를 만들어 반환한다.
print(L2) #L2는 정렬되지 않은 모습 그대로 [3, 2, 1]이 나온다. 
print(L2_sort) #[1,2,3]

 

파이썬에는 특정 기능을 하는 여러 가지 함수가 있을 수 있는데, 기능은 같아도 결과값은 다를 수 있다.

대표적인 예로 정렬을 하는 함수가 있다. .sort() 와 sorted()는 리스트를 똑같이 정렬시킬 수 있지만, L.sort의 경우는 none 을 반환한다. L.sort를 한 뒤에 print(L)을 해도 그대로 정렬되지 않은 L이 나온다. 반면 sorted(L)은 새로운 정렬된 리스트를 반환한다. 각각을 사용할 때에는 함수의 리턴값을 살펴보자. (참고로 .sort()는 리스트 객체의 메서드이고(특정 객체에 속한 함수이므로 리스트 객체에서만 사용이 가능하다. 다른 타입에서는 사용 불가) sorted()는 독립적으로 호출될 수 있는 코드블록이다.) 

 

2. 함수가 실행될 때 에러가 발행할 요인을 고려해서 구문을 짠다. 

#에러가 발생할 때 실행할 코드는 try - except 코드로 만들 수도 있다.

def div(a, b)
    try:
        c=a/b
        return c
    except ZeroDivisionError:
        print("0으로 나누는 연산은 지원하지 않습니다.") 
        #except의 모든 에러를 핸들링 하면 안돼. 익셉션은 남발하면 안좋아

 

3. 함수 안에 변수와 함수 바깥의 변수는 scope와 lifetime이 다르다.

함수 안에 들어있는 변수(local variable) vs 함수 밖에 변수(global variable)를 잘 구분해야한다. 로벌 변수는 함수 안에서만 작동하기 때문에 함수 외부에는 영향을 주지 않는다.
강사쌤 설명) 운영체제에서 쓸 때 로컬 변수는 스택에, 글로벌 변수는 데이터 영역에 올라간다. 파이썬은 변수들을 일단 다 메모리에 올리는데, 최대한 메모리를 한번에 올려놓고 이걸 중복해서 쓰는 것이 메모리 효율에 좋다. 그래서 글로벌 변수를 굳이 로컬 변수로 똑같이 복사해서 쓰는 것은 비효율적이다. 만약 글로벌 변수를 함수 안에서 땡겨와서 쓴다면 global 로 할 수도 있다. 

# global variable 
# ipynb 파일에서는 global variable이 한번 실행되면 해당 프로그램이 종료될때까지 수명이 계속된다. 
# global variale의 scope는 해당 변수를 정의한 ipynb 파일 전체입니다.
name = "Kim"
print(f"1. {name}") 
def change_name(name): 
    # local variable
    # 로컬 변수의 라이프타임은 함수가 종료될 때까지(리턴을 실행할 때까지)
    # 로컬 변수의 scope는 해당 함수 내부입니다. 
    print(f"2. {name}") 
    name = 'Lee' # 함수 외부에는 영향을 주지 않는다. 함수 내부에서만 작동함. return된 값을 외부에서 따로 저장해서 사용하지 않았기 때문에!
    print(f"3. {name}") 
    a = 10
    print(a) #여기 넣은 거는 로컬 변수를 인식해서 실행돼
    return name 
    print(f"4. {name}") #return 이후에 있는 건 영원히 실행되지 않음. 

print(a) #이러면 로컬에서 정의된 거라서 실행되고 없어짐. 여기서는 실행이 안됨!
# global variable
print(f"5. {name}") 
change_name(name)  #여기서 name = change_name(name)을 해줘야 name = lee 로 바뀜! 
print(f"6. {name}") 
change_name(name) 
print(f"7. {name}")

결과

1. Kim
5. Kim
2. Kim
3. Lee
6. Kim
2. Kim
3. Lee
7. Kim

하지만! 만약 파라미터로 리스트나 딕셔너리, 세트와 같은 뮤터블 데이터 타입이 들어가면, 함수 내부에서 변수가 바뀌고 외부에서도 바뀐다. (파이썬의 화나는 점(?))

def change_list(L):
    # local variable
    print(f"2. {L}")
    L[0] = 4 # [4, 2, 3]
    L.pop() # [4, 2] 
    print(f"3. {L}") 
    #여기 리턴이 없기 때문에 상식적으로 생각하면 로컬에서만 가지고 놀고 외부에서 L은 변함이 없는데!
    #리스트 같은 mutable object가 들어가면 변경됨. 리스트의 원본 주소를 보내주는 셈이라서. L.copy()
    #안바뀌게 하고 싶다면 tuple로 써주기. (튜플은 값이 넘어가고, 리스트는 주소가 넘어간다.)

# global variable
L = [1, 2, 3]
print(f"1. {L}")
change_list(L)
print(f"4. {L}")

결과 

1. [1, 2, 3]
2. [1, 2, 3]
3. [4, 2]
4. [4, 2]

 

4. Annotations을 꼭 쓰자! 

파이썬은 오만가지 타입을 변수로 받고 또 바로바로 수정이 가능하므로 함수를 만들 때에도 제대로 쓰기 위해서는 그 함수에 맞는 타입을 넣어줘야 한다. 이걸 쉽게 볼 수 있도록 타입 힌팅을 해주는 게 바로 어노테이션. 

기본 사용법은 변수 뒤에 : 콜론을 붙이고, 반환 타입은 -> 화살표 뒤에 써준다. 

def greet(name: str) -> str:
    return f"Hello, {name}"

def add(x: int, y: int) -> int:
    return x + y

def process_list(items: list[int]) -> list[int]:
    return [item * 2 for item in items]

def display_info(name: str, age: int, active: bool = True) -> None:
    print(f"Name: {name}, Age: {age}, Active: {active}")

어노테이션은 list[int] 이런 형식도 가능하다. 복잡한 어노테이션 형식의 예시.

from typing import List, Tuple, Dict, Union, Optional

def process_elements(elements: List[Union[int, str]]) -> Tuple[int, int]:
    ints = sum(1 for e in elements if isinstance(e, int))
    strs = sum(1 for e in elements if isinstance(e, str))
    return (ints, strs)

def get_user_info(user_id: int) -> Optional[Dict[str, str]]:
    if user_id == 1:
        return {"name": "John", "email": "john@example.com"}
    return None

그리고 함수 정의하고 밑에 ''' 설명 ''' 을 잘 달자. (다른 사람과 코드 공유하고 같이 쓸 때 꼭 필요)

def foo(a : int, b : int) -> int:
	''' 설명 설명 '''
    함수 내용
    return