Python 함수는 문법보다 이 로직에 이름과 경계를 줘야 하는가를 먼저 보면 판단이 빨라집니다.
같은 동작이 반복되는지, 계산 결과를 다른 코드가 다시 써야 하는지, 출력이나 저장 같은 부작용을 계산과 분리해야 하는지부터 보면 대부분 방향이 정해집니다.
숏컷 코드
def ping() -> None:
print("pong")
def greet(name: str) -> str:
return f"Hello, {name}"
def is_even(value: int) -> bool:
return value % 2 == 0
message = greet("Mina")문법
기본형은 매개변수 없는 함수와 매개변수가 있는 함수를 함께 보면 됩니다.
def function_name():
...
def function_name(param1, param2):
return result매개변수가 없으면 "호출만 하면 되는 동작"에 가깝고, 매개변수가 있으면 호출자가 어떤 값을 넘겨야 하는지 계약이 생깁니다.
매개변수는 입력 계약이고 return은 계산 결과입니다.
화면에 보여 주는지, 값을 돌려주는지, 호출자 상태를 바꾸는지 구분해서 읽으면 함수 역할이 분명해집니다.
def refresh_cache() -> None:
print("cache refreshed")
def add(a: int, b: int) -> int:
return a + b
def log_name(name: str) -> None:
print(name)refresh_cache()는 입력 없이 동작만 실행하는 함수이고, add()는 계산 결과를 넘기는 함수이며, log_name()은 출력이라는 부작용이 목적입니다. 셋은 겉모양이 비슷해도 계약이 다릅니다.
함수 분리
함수를 언제 나누는가
같은 의미의 동작이 반복되면 함수로 빼는 편이 좋습니다.
함수는 코드를 짧게 만드는 도구이기도 하지만, 더 중요한 역할은 동작에 이름을 붙이는 것입니다.
# 함수 없이 쓰면 의도가 묻힌다
user_name = raw_name.strip().title()
# 함수로 감싸면 의도가 드러난다
def normalize_name(raw_name: str) -> str:
return raw_name.strip().title()
user_name = normalize_name(raw_name)호출부가 "무엇을 하는가" 수준으로 읽히면 흐름을 훨씬 빨리 따라갈 수 있습니다.
def parse_age(text: str) -> int:
return int(text.strip())
def is_adult(age: int) -> bool:
return age >= 20
age = parse_age("21")
print(is_adult(age))입력 정리, 판단, 저장, 출력, 네트워크 호출을 한 함수에 몰아넣기 시작하면 호출부에서 "무슨 단계가 지금 일어나는가"를 읽기 어려워집니다.
반환과 부작용
return과 부작용을 어떻게 나누는가
계산 결과가 목적이면 return, 외부 효과가 목적이면 부작용 함수로 읽으면 됩니다.
def build_message(name: str) -> str:
return f"Hello, {name}"
def log_message(message: str) -> None:
print(message)계산 함수는 조합과 테스트가 쉽고, 출력이나 저장은 경계 함수에서 마지막에 붙이는 편이 읽기 쉽습니다.
def tax(amount: int) -> float:
return amount * 1.1
def log_tax(amount: int) -> None:
print(f"tax={amount * 1.1}")호출자 상태를 직접 바꾸는 함수도 있으니 반환값이 없다고 무조건 실수는 아닙니다.
def add_item(items: list[str], name: str) -> None:
items.append(name)이 함수는 리스트를 제자리에서 바꾸는 것이 목적입니다. 그래서 return이 없는 것이 오류가 아니라 계약 자체입니다.
주의할 점
print()와 return은 역할이 다릅니다. 화면에 보여주는 것만 하고 값을 돌려주지 않으면, 다른 코드에서 결과를 재사용할 수 없습니다.
# ❌ 출력만 하고 결과를 반환하지 않음
def add(a, b):
print(a + b)
result = add(1, 2) # None
# ✅ 계산 결과를 반환
def add(a, b):
return a + b
result = add(1, 2) # 3정의만 했다고 실행되는 것은 아닙니다. 실제 동작은 호출에서 시작합니다.
# ❌ 호출을 빼먹으면 함수는 정의만 되고 실행되지 않음
def greet(name):
return f"Hello, {name}"
# ✅ 실제 사용은 호출에서 시작
message = greet("Mina")제자리 변경 메서드는 새 값을 돌려주지 않는 경우가 많습니다.
# ❌ in-place 메서드 반환값을 다시 쓰려 하면 None이 들어갈 수 있다
def sorted_names(names):
return names.sort()
# ✅ 새 결과가 필요하면 sorted(), 원본 변경이면 sort()를 분리
def sorted_names(names):
return sorted(names)# ❌ append()도 새 리스트를 돌려주지 않는다
items = ["a"]
items = items.append("b")
# ✅ 제자리 변경과 새 값 반환을 구분해서 읽는다
items = ["a"]
items.append("b")참고 링크
2 sources