IT

[코딩온] 신재생에너지 IoT 개발자 입문_ 파이썬10

혈중당부족 2025. 12. 9. 17:49

 

12 function

 

함수는 특정 작업을 수행하는 코드 블록이다.

수학 함수와 유사한 점이 있어 비유한 이미지

 

 

함수를 쓰는 건 코드를 재사용(재활용)하여 동일한 작업 반복의 효율을 올려주고, 미리 작성된 코드를 호출해 쓰는 식이니 코드의 가독성도 올라간다. 기능 단위로 묶어서 코드를 쓸테니 명확하고, 다른 사람도 이게 무슨 코드인지 읽고 이해하기 쉬워지기 때문이다. 거기다 수정이 필요하다면 그 함수만 고쳐도 전체를 수정할 필요 없이 쓸 수 있으니 유지 보수에도 용이해진다. 

이런 구조이니 큰 작업에 여러 개의 역할을 나눠서 작업해 합칠 수 있는 구조화(모듈화)가 되고, 작업 분담으로 협업이 가능하고 계층적 설계에도 쓰이니 설명을 듣고 활용할 수 있도록 배우는 게 맞다. 

 

기본적으로 파이썬에는 기본적으로 제공하는 기능(내장함수)가 있어 별도 지정 없이 이미 몇 번 씩 끌어 쓰고 있다.

예시: print(), len (), type(), sum(), max(), sorted()

그리고 이번에 배울 건 사용자 정의 함수다. 사용자가 def 키워드를 사용하여 직접 만든 함수를 그렇게 부른다.

 

> 사용자 정의 함수 기본 문법
함수의 정의: define의 약자로 def 사용
def 함수이름 (매개변수): #콜론(:), 들여쓰기 중요
    #실행할 코드
    print(매개변수)
    return "반환값"

#함수의 실행 (호출 call)
result = 함수이름("인자") #인자라는 갓이 def의 매개변수로 들어감.
print(result) #"반환값"으로 출력됨 return 결과물을.

> 매개 변수(Parameter): 매개 + 변수
매개 : 둘 사이를 연결해줌.
함수가 실행될 때 인자로부터 입력되는 값을 함수의 코드 블록으로 전달하는 역할.
 
> return: 결과값 반환(선택적 사용, 없으면 None으로 출력)
return을 만나면 함수는 즉시 종료된다.
> ()는 함수에 전달할 값이 없더라도 반드시 필요하며, 인자는 함수 호출 할 때 전달하는 값이다.
> 매개 변수와 인자는 같은 맥락으로 사용되기도 하고 둘의 수가 일치해야 함수가 작동된다. 아니면 TypeError 뜬다. 
> 인자의 순서대로 매개변수에 대응된다 : 가장 일반적이고 기본적인 전달 방식.
 
 
 
 
 
#함수의 필요성 예제
a = 10
b = 20

if a > b:
    print(a - b)
else:
    print(a + b)

c = 30
d = 40

if c > b:
    print(c - d)
else:
    print(c + d)
#... 비슷한 구조로 길어지게 된다.

def my_func(a,b):
    if a > b:
        return a - b
    else:
        return a + b

print(my_func(10,20))
print(my_func(30,40))

 

그러니 def로 연산하는 코드를 작성하고 그 함수 안에 인자(10,20/ 30,40)을 넣으면 출력된다.

 

#실습 1 : 사칙연산 계산기 함수 만들기

조건

1 매개 변수는 a,b,operator 3개고 oper-은 문자열이다.

2 나눗셈을 실수(float)로 반환

3 올바르지 않은 연산자 들어올 시 잘못된 연산자라고 반환한다.

def calculate(a,b,operator):
    if operator == "+":
        return a + b
    elif operator == "-":
        return a - b
    elif operator == "*":
        return a * b
    elif operator == "/":
        return float(a / b)
    else:
        print("지원하지 않는 연산입니다.")

print(calculate(20,5,"?"))

? 들어가서 바로 출력된다.

 

함수 정의 시 인자에 대한 여러 종류들.
 
 
- 키워드 인자
인자의 이름을 명시하여 전달한다. 순서와 무관하게 매개변수에 대응해 순서가 헷갈릴 때 유용한다.
 
예시 1
print("안녕하세요", "반갑습니다.", sep="-", end="/") #공백은 -, 마무리는 /

예시 2
def my_func(a, b, c=None, operator=None):
    if operator == "+":
        return a + b
    else:
        return c

print(my_func(10,20,operator="+"))
#a,b만 설정하면 none 뜨고 operator = "+"라고 새인자 지정하면 변경해 적용됨
 
/ 다음에 10+20 값 30이 출력된 거다.

 

- 기본값 인자
함수 정의 시 매개변수에 기본값을 지정한다. 호출할 때 인자를 생략하면 기본 값으로 나온다.
단, 기본값 매개변수는 뒤쪽에 위치해야 함.
 
def greet(name, message="안녕하세요."):
    print(f"{name}님, {message}")

#호출 시 인자 생략 -> 기본값 사용.
greet("luna"
greet("ff","반갑습니다!")

 

- 위치 가변 인자
여러개의 값을 유동적으로 받을 수 있음
값이 튜플 형태로 받아짐. 함수 정의 시 *args 로 표기한다. 

def add_all(*args):
    return sum(args)

print(add_all(1,2,3,4,5)) #15 출력

 

- 키워드 가변 인자
여러 키워드 인자를 유동적으로 받을 수 있다.
딕셔너리 형태로 값이 입력됨. 함수 정의시 **kwargs로 표기한다.

def print_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key} : {value}")

print_info(name="luna", age = 15 , city="souel")

 

인자 지정해서 줄 때, 순서에 유의해야 한다.

 

매개변수 전달 방식은, 파이썬에서 값 전달( Call by Value)이 아닌 객체 참조에 의한 전달 (Call by Object Reference)이다. 

변경 가능한 객체(mutable)을 함수 내에서 변경하면 외부에도 영향을 주고 [ 리스트(list), 딕셔너리(dict), 집합(set) ]

변경 불가능한 객체 (immutable)을 쓰면 외부에 영향이 없는 형식이다. [ 정수(int), 실수(float), 문자열(str), 튜플(tuple) ]

 

 

 

실습

1. 숫자 여러 개의 평균 구하기.

def avg(*args):
    return sum(args)/len(args) #sum(iterable) = 모든 요소의 합을 계산

print(avg(1,4,5,2,6))

 

2. 가장 긴 문자열 찾기 (max함수 사용)

def max_log(*args):    
    return max(args, key= len)

print(max_log("as", "asdf"))
 
문제보고 이렇게 작성했었는데. 두가지 방법을 알려주셨다. 
 
#모범 답안
def max_log(*args):
    answer = ""
    for s in args:
        if len(s) > len(answer):
            answer = s
    return answer

print(max_log("asg","qwtsah","01asfssg"))
작동 잘 된다.

여러가지 값을 받는 *args만 쓴다면, 그 값의 요소 s 하나씩 answer이 되고 비교해서

s가 크면 그걸로 바꾸는 식으로 바꿔 반환되는 구조다.


#모범 답안 2
def max_log(*args):
    #예외처리
     if len(args) == 0:
         return "입력값이 없습니다"
     return max(args, key=len)

max_log()
 

3. 사용자 정보 출력 함수

사용자의 이름 나이 이메일 등의 정보를 받아 출력하는 함수. 몇 개가 들어오든 모두 출력해야 한다.

def print_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key} : {value}")

print_info(name="luna", age = 15 , email="coodingon@gmail.com")
 
헷갈리는 거: name="luna" 입력 시 key = name 이고 value = "luna"로 알아서 적용되는 거다.
age 정보 넣어도 알아서 반환 되는 걸 보면
키워드 인자는 딕셔너리처럼 받는 형식이니, key = value 인자를 입력한 대로 나오는 거다.
 
 
4. 할인 계산기

def print_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key} 할인가: {value * 0.9} 정가: {value}")

print_info(item = 12000, item1 = 10000, item2 = 15000)

이것도 딕셔너리 구조처럼 입력하니 잘 나온다. 10% 할인은 0.9를 곱하면 된다.

 

 

 

지역변수와 전역변수

 

들어가기 전에 파이썬의 메모리 구조부터 쓰자면

스택 영역: 지역 변수( local variable ), 매개 변수( parameter )가 저장되는 곳

힙 영역: 파이썬에서 생성되는 모든 객체 ( 숫자 (parameter) , 문자열 , 등이 리스트 , 저장되는 공간 딕셔너리 , 클래스 인스턴스 등 ) 가 저장되는 공간

이렇게만 봐서는 잘 와닿지 않아 아쉽다. 

 

 
전역 변수
x = 200

예제
def my_func():
    #지역변수
    x = 10
    print(x)

my_func() # 10
print("함수 밖", x) # 200

 

지역 변수
x = 200

예제
def my_func2():
    #지역변수
    x = 20
    x += 5
    print("지역변수", x)

my_func2() # 25 x가 20으로 바뀌고, 5가 더해졌단 뜻.
print("전역변수", x) #200

 

 

예제2
전역변수를 쓸 때 오류. UnboundLocalError, x(전역 변수)를 지역변수로 인지함.
 
def my_func2():
    x = 20
    x += 5
    print("지역변수", x)

my_func2()
print("전역변수", x)

 

전역 변수 사용 주의사항

 

 

global 키워드 사용 : 함수 내부에서 전역 변수 수정이 필요할 때.
 
global 키워드 쓰면 전역 변수라고 지정할 수 있지만.
어디서 선언 했는지 파악하기 어렵고, 의도 따라 달리 익히기 때문에 권장하지 않는다.

nonlocal :내부 함수가 외부 함수를 쓰려고 할 때 씀.
전역 변수 관련
 
예제3
x = 10 #지역변수

def my_func3():
    global x #전역변수 사용 선언
    x += 5
    print("지역", x) #15 지역이 전역으로 바뀌었다.

my_func3()
print("전역", x) #15
 
 

권장되는 패턴
부수효가(side effect)을 발생시키지 않는 함수를 위주로 프로그래밍
x = 10

def my_func4(n): #변수명칭(n)이 함수 내부에서 일치해 사용하면 된다.
    n += 5
    return n

x = my_func4(x)

print("전역", x )

 

 

 

전역, 지역 변수 헷갈리나 실습은 해야한다. 당시에 받았던 실습 문제.

 

문제: 로그인/로그아웃 전역 상태 관리

이건 문제를 받고 입력했던 코드.

current_user = ""

#로그인, 로그아웃 함수식 만들기
def login(name): #name = 매게 위치 login(A) 쓰면 name = a다.
    global current_user #전역변수로 사용.

    if current_user == "":
        current_user = name
        print(f"{name}님 로그인 성공!")
    else:
        print("이미 로그인 되어 있습니다.")

def logout():
    global current_user

    if not current_user:
        print("로그인 상태 없습니다.")
    else:
        current_user = ""
        print("로그아웃 되었습니다.")


login("ian")
login("coodingOwl")
logout()
login("coodinOwl")
print(current_user)
어찌저찌 작동은 됐기 때문에...
보여주던 시범 코드 보고 신기해 했던 기억 있다.
 
#모범 답안
current_user = ""
login_count = 0

def login(name):
    global current_user
    global login_count

    if current_user == None:
        current_user = name
        print(f"{name}님 로그인 성공!")
    else:
        print("이미 로그인 되어있습니다.")
        login_count += 1
        if login_count > 4:
            print("더이상 로그인 할 수 없습니다.")

def logout():
    global current_user
    global login_count

    if current_user == None:
        print("로그인 상태가 아닙니다.")
    else:
        print("로그아웃 되었습니다.")
        current_user = None
        login_count = 0

 

 

재귀함수
1 자기 자신을 호출하는 함수
2 반드시 기본 조건 (종료 조건)이 있어야 함.
 - 큰 문제를 작은 문제로 나누었을 때 일정한 패턴이 있어야 함.
주의사항까지 있는 이미지.

 

재귀함수의 장단점
비슷한 역할을 하는 반복문 중, 쓰는 것이 좋은지 알기 위한 비교.

 

 
 
import time

def recursive_func(n):
    #기본조건
    if n == 0:
        return
   
    recursive_func(n-1)
    print("재귀 호출" , n)
    time.sleep(1)

recursive_func(5
 
재귀 함수 진행 과정 설명을 주변에 물어서 들었는데, 대략적인 이해로
recursive_func(5) 는 변수를 5넣은 것이고. 매개변수가 0인지 조건을 확인한다. 0이 아니기 때문에  recursive_func(n-1)로 가고, 그건 5-1로 부른다. 그래서  print("재귀 호출" , n)에 n의 값은 4. 
모든 함수는 그 내용이 끝나기 전까지 다음 줄을 호출하지 않으므로 그렇게 작동된다. 
매개변수 = 나는 뭐든 된다.
동작 과정(왼쪽 넘버링은 내부적 실행 순서.
 
 
 

실습4
거듭제곱. a**b = a*(a*b-1) 함수 <사실 여기서 수학적 지식 필요한 사실이 괴로웠다...
def func_c (a,b):
    if (b == 0): # a 의 b(0승) = 1이란 뜻
        return 1
    return a * func_c(a, b-1)
    # a가 b개 만큼 곱해야 한다. a * a * a*... 1 (=> b = 1)
       
print("재귀함수", func_c(2,3))
 
2의 3승 2*2*2*1 b= 3이면 3,2,1,0이 되는 순간까지 포함 총 4개의 곱
그래서 0 되는 걸 1로 조건 거는 것 if (b == 0): 1

 

실습, 팩토리얼
팩토리얼 : 1부터 지정된 수 까지 모든 수의 곱 수학 기호로 "!" 숫자 뒤에 붙여 표시
 3! = 3의 계승 1 2 3  =6
 
문제를 받아 팩토리얼 계산식 찾고, 당시에 짰던 코드.

N = int(input("팩토리얼 할 숫자: "))
Result = 1
for n in range(1, N+1):
    Result *= n

print(Result)
# 1부터 N+1의 값을 반복 생성한다.
# Result은 1에서 시작하고 지정한 값만큼 곱해진다.
# 1 사이클 1*1 / 2사이클 +1*2/ 3사이클 + 1*3 =6

 

그런데 재귀함수를 배웠다는 걸 그걸 쓰라는 것이니까.

그걸 활용해 짠다면 이런 코드여야 한다.

모범답안
def fact_f(n):
    #예외 처리
    if n < 0 :
        return "음수의 팩토리얼은 정의되지 않습니다."
    if n == 0 or n == 1:
        return 1
   
    return n * fact_f(n-1)

def m_func(N):
    if (N == 0):
        return 1
    return N * m_func(N-1)
   
print(m_func(N))
#1*1 + 1*2 + 1*3 = 6
#리턴을 한다. 3이면 3(N)에 2(n-1)호출, 2에서는 1(n-1)호출, 1은 0이되니 1로.
#사이클 도는 것처럼 역순으로 더해져서 3 * 2* 1* 1로 =6이다.

 

실습, 피보나치 수열
피보나치 수열은? N의 수가 N-1와 N-2를 더한 값.
프로그램 작성 시, 규칙을 찾아서 공통된 코드로 만든다.

def fibonacci_f(n):
    if n <= 0:
        return 0
    elif n == 1:
        return 1
    a, b = 0, 1
    for _ in range(n-1):
        a,b = b , a+b
        # 0 1 <= 1 1
        # 1 1 <= 1 2
        # 1 2 <= 2 3
    return b

print("파보나치 수열", fibonacci_f(5))    
 
 
#모범 답안
def fibonacci_R(n):
    if n <= 0:
        return 0
    elif n == 1:
        return 1
    return fibonacci_R(n-1) + fibonacci_R(n-2)

print(fibonacci_R(6))
 
답안을 보니 지정한 값에서 -1, -2를 더하는 것이니, 매개변수가 이 조건일때 이걸 출력할지만 검토하고 아니면 return fibonacci_R(n-1) + fibonacci_R(n-2)로 가서 계산하면 그만이었다. 
6 넣으면 0과 1이 아니니 5와 3이 더해지는 거다.

 

 

 

람다(lambda) 함수 : 익명 함수
간단한 함수를 한줄로 표현 할 때 사용
 
 

 

 

예시 함수 코드

 

일반 함수
def add(x,y):
    return x+y
 
람다 함수
add_func = lambda x, y: x + y
 
print(add_func(3, 5))

 

어디든 똑같이 8로 반환된다. 

람다 함수는 한 줄로 간단히 축약하고자 할 때 쓰는 것 같다.

 

 

람다로 값을 반환하고 사용을 끝내는 경우
 
print("람다 값 반환", (lambda x: x**2)(10))

lambda x(변수)는 x 제곱이고 그 다음에 x변수를 10으로 넣은 거다.

 

람다 함수의 활용
1 map 에서 활용
map(function, iterable): iterable의 각 요소에 함수를 적용한 값을 반환한다.
map(함수 명칭, 변수)구조라는 뜻이다.
 
my_list = [1,2,3,4]

일반 함수
def square_func(x):
    return x ** 2

함수를 인자로 받는 함수
print(list(map(square_func, my_list))) 

람다 함수
print(list(map(lambda x: x ** 2,my_list))) 

 

[1, 4, 9, 16]로 매개변수(my_list)에 있는 1,2,3,4를 순차 적용해 나온 값이다.

 

2 filter 에서 활용
filter(function, iterable): iterable의 요소 중 함수의 조건에 맞는 것만 남긴다.
my_list2 = [1,2,3,4,5,6,7,8,9,10]

일반 함수
def is_even(x):
    return x % 2 == 0

print(list(filter(is_even, my_list2)))

람다 함수
print(list(filter(lambda x: x % 2 == 0, my_list2)))
return x % 2 == 0의 계산식 넣음.
 
[2, 4, 6, 8, 10]로 매개변수(my_list2)에 있는 요소 중 충족되는 것만 나온다.

 

 

3 sorted 에서 활용
sorted 함수의 key 에는 각 요소에 대해 정렬 기준이 될 값을 반환하는 함수를 넣을 수 있음
 
my_list3 = ["apple", "banana", "watermelon", "grape"]
print(sorted(my_list3, key= lambda word: len(word)))
#['apple', 'grape', 'banana', 'watermelon'] 요소의 글자 수를 기준으로, 오름차순으로 출력
 

디버깅이 뭔가요? 컴퓨터 프로그램이나 시스템에서 오류(버그) 찾아내고 수정하는 과정이다.

 

실습
특정 조건 만족하는 튜플만 추출
 
students = [
    ("Alice", [80, 90]),
    ("Bob", [60, 65]),
    ("Charlie", [70, 70])
    ]
평균 점수 70 넘는 학생만 추출.
x = ()
total = 0

def average_70(x):
    global students
    global total
    for name, score in students:
        total = sum(students)/len(students)
        if total >= 70:
            x.append()

    return

일반 함수 코드, 외부에 있는 변수를 내부에서 사용하겠다 선언하고 반복문 실행 조건 맞을 시 리스트 추가해서 출력.

print(list(filter(lambda a: sum(a[1] )/ len(a[1]) >= 70 , students)))
#튜플 키워드 a의 v 값을 더하고(sum), v의 개체 수(len)만큼 나누겠다.
#튜플을 리스트로 변환하고, [1]인덱스 값을 쓰겠다.(튜플의 각 요소 2번째 값)

출력, 튜플이 리스트로 변환되어 나왔다.

 

 


키워드 추출 리스트 만들기: 맨 앞단어만 출력하기
여기서 str.split () 활용한다. (문자열을 일정한 규칙으로 잘라 리스트로 만든다.) 
 
sentences = [
    "Python is fun",
    "Lambda functions are powerful",
    "Coding is creative"
]
 
print(list(map(lambda s: s.split()[0],sentences)))
#공백을 기준으로 나누고 리스트의 첫번째 요소만 나열하겠다.
#마지막 위치는 넣어야 할 리스트,튜플 등의 정보
#리스트 변환, 지정값, 계산식, 넣을 정보 흐름.

튜플 리스트 정렬 (나이를 기준으로 오름차순)
 
people = [("Alice", 30), ("Bob", 25), ("Charlie", 35)]

print(sorted(people, key= lambda age : age[1]))
#key 사용해 기준 정하고 리스트를 정렬함.
#튜플의 1인덱스를 기준으로 나열하겠다.
print(sorted(my_list3, key= lambda word: len(word)))
 
이런 저런 사용자 지정 함수, 가변 인자, 전역/지역 변수, 재귀 함수, 람다 함수 통째로 묶어 함수라는 카테고리 안에서 작성했다. 각자 의미가 다르고, 구분해서 적절하게 쓰고자 배우다보니 포괄적인 만큼 분량이 꽤 길어졌다.
 

이 중에서 가장 어려웠던 건 재귀 함수로 꼽고 싶다. 만든 함수를 다른데서 또 불러다 쓰는 건 알겠지만 실행 원리가 헷갈려서 돌려보고, 읽으면서 파악하느라 더 구체적인 것도 크다. 람다 함수는 간단한 산술식으로 쓰는 것이니 내가 연산자/ 명령어를 잘 안다면 그만큼 써먹기 유용하다고 느꼈다.

 

후기는 이렇게 마무리 됐다. 작성 끝!