[Python] 함수 & 람다 & 복사 방식

SIK407·2025년 12월 18일

Python & FastAPI

목록 보기
2/6
post-thumbnail

◽ 함수 정의

  1. 정의한다(define)를 의미로 def를 사용
  2. 주로 snake_case를 사용
  3. 매개변수 나열 가능 (없어도 괄호는 필수)
def fun(a, b):
	return a + b



◽ 다양한 함수 사용법

✔ 기본 사용법

def repeat_print(message, count):
    """message를 count번 출력"""
    for _ in range(count):
        print(message)

repeat_print("안녕?", 3)                  # 위치 인자(positional arguments)                
repeat_print(count = 3, message = "안녕") # 키워드 인자(keyword arguments)

기본적으로 정의된 매개변수 순서대로 전달해야 한다.
다만, 키워드 인자(keyword arguments)를 사용하게 되면 순서가 필요없다.

✔ 매개변수의 기본값

# 에러발생 → 기본값이 있는 매개변수가 더 앞에 있음.
def repeat_print_2(message = "test", count):
    for _ in range(count):
        print(message)

# 정상 → 기본값이 없는 매개변수가 왼쪽에 와야 함.
def repeat_print_2(message, count = 1):
    for _ in range(count):
        print(message)

✔ 덕 타이핑 (Duck Typing)

인수(Parameter)의 자료형을 고려하지 않고, 일단 넣어서 작동.
("오리처럼 걷고, 오리처럼 꽥꽥거리면, 그 새를 오리라고 하겠다."는 말에서 유래)

def test_print_1(message):
    for i in range(len(message) + 1):
        print(message)

test_print_1("안녕??")
test_print_1(["딸기", "바나나", "포도"])

test_print_2(123) # 에러 -> int형은 len()을 사용할 수 없음.

✔ 타입 힌트(type hinting)

def repeat_print_3(message: str, count: int) -> None:
    for _ in range(count):
        print(message)

repeat_print_3("3", 3)

# 시퀀스도 타입을 선언 가능
# mypy 같은 타입체커가 검사
arr: list[str] = ["a", "bc", "dcf"]              # List

names: tuple[int, ...] = (1, 2, 3)               # 개수 상관없는 Tuple
names: tuple[int, int, int] = (1, 2, 3)          # 개수를 지정한 Tuple

nums: set[int] = {1, 2, 3}                       # Set
scores: dict[str, int] = {"kim": 90, "lee": 85}  # Dictionary

✔ 인수의 개수가 변할 경우

def print_many(my_tuple):
    print(my_tuple)

print_many(1, 2, 3, 4, 5)   # 에러발생! -> 인수가 여러개...
print_many((1, 2, 3, 4, 5)) # 정상

# 단, 이렇게 하면 무조건 튜플로 받아와짐
def print_many_tuple(*my_tuple):
    print(my_tuple)

print_many_tuple(1, 2, 3, 4, 5) # 출력: (1, 2, 3, 4, 5)

# *을 두 개 붙일 경우, dict로 받아와짐
def print_many_dict(**keywords):
    for k in keywords:
        print(f"{k} : {keywords[k]}")

print_many_dict(a = 1, b = 2, c = 3) # 출력: a b c (가로로)

✔ 언패킹 연산자

여러 값을 담고 있는 객체를 개별 변수로 '풀어주는(Unpacking)' 역할

my_list = [1, 2, 3, 4, 5]
print(*my_list)

my_dict = {"a": 1, "b": 2, "c": 3}
print(*my_dict)  # *가 한개면, key만 풀어버림

# *가 두개면, key와 value 둘 다 풀어버림
def greet(name, age):
    print(f"{name}({age})")

params = {
    "name": "철수", 
    "age": 20
}
greet(**params)   # greet(name = "철수", age = 20)와 같음

✔ 반환 (Return)

def two_random_numbers():
    return random.random(), random.random()     # 기본적으로 튜플로 반환
    # return (random.random(), random.random()) # 튜플로 반환

print(type(two_random_numbers())) # <class 'tuple'>

======================================================================

def two_random_numbers():
    return [random.random(), random.random()] # 리스트로 반환

print(type(two_random_numbers()))# <class 'list'>

✔ Call by Value

매개변수가 불변 객체일 경우, 내부에서 변경을 하면 객체를 새로 만듦. (인수로 넣어줬던 객체 영향 X)

def my_fun(a):
    # 내부적으로 a = x
    a += 100
    return a

x = 1
print(x)         # 1
print(my_fun(x)) # 101

✔ Call by Object Reference

가변 객체일 경우, 넣어줬던 객체도 영향 O

def append_hello(func_list):
    print("In: ", id(func_list))
    func_list.append("hello")

my_list = ['a']
print("Before: ", id(my_list))
append_hello(my_list) # my_list.copy()하면, 얕은 복사가 됨.
print("After: ", id(my_list))
print(my_list)

✔ 함수 객체

함수를 변수로 저장했다가, 사용하는 기능.

def test_fun():
    print("아아 마이크 테스트")

test_fun_2 = test_fun
test_fun_2()

# 예제
from datetime import datetime

def print_date():
    today = datetime.today()
    print(f"오늘은 {today.year}년 ~~~")

def print_time():
    current_time = datetime.now()
    print(f"지금은 {current_time}시")

command_callback = {"날짜" : print_date, "시간" : print_time}
func = command_callback.get("날짜인지, 시간인지 입력")
func()



◽ 람다 (Lambda)

이름 없는 함수(익명 함수)

# 함수 객체처럼 변수를 통해서 사용
my_func = lambda x, y: x + y
my_func(1, 2)

✔ 기본 사용법

map(), filter() 등의 내장함수들과 함께 사용하기에 편리하다.

# map(): 주어진 함수에 이터러블의 각 아이템을 적용함
print(list(map(lambda x: x + 100, [1, 2, 3])))
print(list(map(lambda x, y: x + y, [1, 2, 3], [10, 20, 30])))

# filter(): 주어진 조건에 따라, 요소들을 걸러줌
print(list(filter(lambda x: x % 2 == 0, [1, 2, 3, 4, 5])))

# reduce(): 주어진 함수를 시퀀스에 반복적으로 적용 -> 하나의 값으로 만듦.
from functools import reduce

print(reduce(lambda x, y: x + y, [1, 2, 3, 4])) # (((1 + 2) + 3) + 4) == 10

# sorted(): 정렬 기준을 지정해줄 때도, 람다를 사용할 수 있음.
print(dict(sorted(my_dict.items(), key=lambda item: item[1], reverse=True)))

# 실습 - 자리수 합치기
num = 1357

def add_digits(num: int) -> int:
    print(num)

    if num < 10:
        return num
    else:
        #return add_digits(reduce(lambda x, y: int(x) + int(y), str(num)))
        return add_digits(sum(map(int, str(num))))

# 1+3+5+7 = 16
# 1+6 = 7
print(add_digits(num))



◽ 복사(Copy) 방식

✔ 얕은 복사(Shallow Copy)

old = [1,2,3,4,5]
new = old.copy()

print(id(old) == id(new))          # False (껍데기는 다름)
print(id(old[0]) == id(new[0]))    # True  (0번째 원소는 같은 객체)

얕은 복사 (Shallow Copy)객체들의 주소만 복사하여 새로운 객체로 생성한다.
주소를 복사하기 때문에, 원본과 복사본의 객체가 동일한 데이터를 참조한다.

old[0] = 3
print(id(old[0]) == id(new[0])) # False

int는 파이썬에서 불변 객체다.
불변 객체는 값이 바뀌게 되면, 새로운 객체를 생성하기 때문에 서로 다른 주소가 나오게 된다.

old = [[1], [2]]
new = old.copy()

old[0][0] = 999
print(new)  # [[999], [2]]  <- 내부 리스트를 공유해서 같이 바뀜

print(id(old[0]) == id(new[0])) # True

이처럼 가변 객체를 사용하고 변경할 경우, 주소는 동일하다.
즉, 주소만 그대로 가져온 것을 확인할 수 있다.

얕은 복사 (Shallow Copy) 메모리를 적게 사용하고, 복사 과정이 간단하기 때문에 성능이 좋다.
하지만, 객체의 상태를 완전히 복제할 수 없기 때문에 안전하지 않을 수 있다.

✔ 깊은 복사 (Deep Copy)

import copy

xs: list[int] = [1, 2, 3]
xa = xs                 # 복사가 아닌, 그냥 같은 객체
xc = xs.copy()          # 얕은 복사
xdc = copy.deepcopy(xs) # 깊은 복사

깊은 복사 (Deep Copy)는 모든 필드를 복사하여, 새로운 객체를 생성하는 방식이다.
객체의 모든 데이터를 복사하기 때문에, 원본과 복사본이 독립적으로 존재한다.

즉 불변 객체든 가변 객체든 상관없이, 주소가 다 달라 다른 객체로 인식된다.

당연히 메모리도 많이 사용하고, 복사 과정은 얕은 복사보다 복잡하다.

profile
감자 그 자체

0개의 댓글