본문 바로가기
파이썬

[Python] 파이썬의 함수

by 책 읽는 개발자_테드 2022. 3. 26.
반응형

파이썬 프로그래밍를 읽고, 정리한 글입니다.

 

목차

· 함수 중심 프로그래밍

· 내장 함수

· 사용자 정의 함수

· 변수의 활동 범위

· 가변 인수 처리

· 클로저

· 람다 함수

· 일급 함수

· 함수 장식자

· 재귀 함수


 

함수 중심 프로그램

· 함수 중심(지향) 프로그래밍은 대부분의 소스 코드를 함수로 작성하여 문제를 해결하는 프로그래밍 기법으로, 소스의 내용을 성격 따라 정리해서 볼 수 있도록 한다.

· 함수를 사용하면 한 번의 코드 작성으로 다양한 상황에서 호출할 수 있어 코드 재사용성이 높아지고, 코드의 수정 및 유지 보수도 쉬워진다.

· 함수의 이름은 객체변수로 기억 장소에 대한 장소를 기억한다.

 

내장 함수

·  내장 함수(built-in function)는 프로그램을 실행할 때 기본적으로 주기억장치에 로딩되는 함수로, 언제든지 사용할 수 있다.

- 즉, 인터프리터에 포함되어 있어 별도의 로딩 과정 없이 바로 사용할 수 있는 기본 함수를 말한다.

 

· 자주 사용되는 내장함수:

함수명 기능
sum(iterable) 숫자들의 합을 반환
input(prompt) 사용자 입력을 문자열로 반환
globals() 전역변수의 리스트를 반환
del(x) 혹은 del x 객체변수를 삭제
eval(expr) 수식 모양의 문자열을 파이썬 수식으로 변환
hex(x) 정수 x의 16진수 값을 구해서 '문자열'로 반환
oct(x) 정수 x의 8진수 값을 구해서 '문자열'로 반환
bin(x) 정수 x의 2진수 값을 구해서 '문자열'로 반환
int(x) 정수로 형 변환하여 결과를 반환
float(x) 실수로 형 변환하여 결과를 반환
all(iterable) iterable의 모든 요소가 참일 경우 True를 반환
any(iterable) iterable의 하나 이상의 요소가 참일 경우 True를 반환
max(iterable) 최댓값을 반환
min(iterable) 최솟값을 반환
round(x) 반올림을 반환
zip(*iterable) 동일한 개수로 이루어진 iterable 객체를 묶어 준다.

 

· 예시1 - 내장 함수 사용

# 리스트 요소의 합을 반환
print(sum([3, 5, 7]))

# 실수를 정수화
print(int(1.7))

# 정수를 실수화
print(float(3))

# 숫자를 문자화
print(str(5) + ': 숫자 오')

a = 10
# 숫자 모양의 문자열을 수식화
print('eval 결과:', eval('a + 5'))
# 반올림한 결과를 반환
print(round(1.2), round(1.6))

# iterable 요소 참, 거짓 확인
b_list2 = [1, 3, 2, 5, 7, 6]
print('모든 숫자가 10 미만인가? ', all(a < 10 for a in b_list2))
print('숫자 중 3 미만이 있는가? ', any(a < 3 for a in b_list2))

# x와 y 두 개의 리스트에 zip()을 사용하여 튜플 작성
x = [10, 20, 30]
y = ['a', 'b']
for i in zip(x, y): # 30은 대응되는 짝이 없어 제외된다.
    print(i)

결과

 

· 예시2 - globals 함수 사용하기

- 현재 파일에서 사용하고 있는 객체의 목록들을 확인하려면 내장 함수인 globals를 사용한다.

print('현재 파일의 객체 목록:', globals())

결과

- 개발자가 직접 만든 함수와 파이썬이 내부적으로 만든 객체들을 볼 수 있다.

 

· 예시3 - 외장 함수 사용

- 파이썬을 설치하면 사용할 수 있으나 내장 함수만큼 사용 빈도가 높지 않은 함수나 별도의 모듈로 지원하는 함수는 import 구문을 이용해 특정 모듈로 로딩하면 사용할 수 있다. 이를 외장 함수라고 부른다. 

# 수학 관련 함수를 담고 있는 모듈 로딩
import math

# 근사치 중 큰 장수 반환
print(math.ceil(1.2), math.ceil(1.6))

# 근사치 중 작은 정수 반환
print(math.floor(1.2), math.floor(1.6))

결과

 

사용자 정의 함수

· 내장 함수만으로 프로그램을 작성하기 어려울 때, 직접 함수를 만들어 사용할 수 있다. 이를 사용자 정의 함수라고 부른다.

· 사용법:

- 사용자 정의 함수의 선언은 키워드 def로 시작하고, 그 뒤에 함수명, 소괄호가 차례로 나온다. 소괄호 안에는 매개변수(parameter)를 적어줄 수 있고, 매개변수가 여러 개면 쉼표를 사용해 구분한다.

- 매개변수는 해당 함수 영역 안에서만 사용할 수 있는 지역변수가 된다.

- 함수 명명 규칙은 변수명 규칙과 같다. https://scshim.tistory.com/555

- def 함수명(): 이라는 함수 정의의 첫 줄을 헤더라고 부르고, 나머지 함수 부분은 바디라고 부른다.

- 헤더는 콜론으로 끝나야 하고, 바디 영역에 적는 수행문은 들여쓰기(Tab or Space Bar)해야 한다. 수행문의 개수는 제한이 없다.

- 함수 내에 'return 반환값' 형태의 수행문이 사용되었다면 해당하는 값을 반환하고, return 문이 없다면 None 값을 반환한다.

 

사용자 정의 함수를 작성하고 호출하기

· 예시1 - 하나의 매개변수를 갖고, return문이 있는 함수

def func1(arg):
    return arg

print(func1(1))

결과

· 예시2 - 두 개의 매개변수를 갖고, return문이 없는 함수

# 반환할 return 문이 없는 함수
def func2(arg1, arg2):
    arg1 + arg2

# 함수가 실행은 되나 아무런 결과가 없다.
print(func2(10, 20))

결과

- 함수에서 reutrn 문을 사용하지 않으면, return None이 생략한 형태라고 보면 된다. 

 

· 예시3 - 함수 블록 탈출과 return 문

def func3(arg):
    if arg % 2 == 0:
        return
    print(arg)

func3(4)
func3(5)

결과

- 함수 수행 중 return 문을 만나면 무조건 함수 진행이 종료되고 함수를 호출한 지점으로 수행 순서가 이동한다.

 

· 예시4 - 함수명과 주소

def func1():
    print('func1 호출')

func1()
# 함수명은 객체 주소를 기억한다.
print(func1)
# 함수 주소를 치환
other = func1
# func1과 동일한 결과를 얻는다.
print(other)

결과

- 파이썬은 모든 값이 객체로 취급되고, 함수도 마찬가지로 객체로 취급된다. 사용자 정의 함수명은 함수로 만들어진 객체의 주소를 갖고 있다.

- '변수 = 함수명'을 한면 변수는 함수명이 가진 객체의 주소를 치환받는다. 함수 객체는 별명을 하나 더 갖는다고 보면 된다.

 

· 예시5 - 인자가 여러 개인 return문

def swapfunc(a, b):
    return b, a

a = 1
b = 2
print(swapfunc(a, b))

결과

- 함수의 반환 값은 언제나 한 개다. 'return 값1, 값2' 형식으로 작성한 것은 'return (값1, 값2)'와 같다. 반환값은 튜플로 만들어져 한 개의 묶음으로 반환된다.

 

 

· 매개변수의 유형 

- 함수를 호출할 때 입력값을 받아 해당 함수의 수행 작업을 하는 매개변수의 유형은 네 가지가 있다.

1. 위치 매개변수(positional arguments): 인수와 순서대로 대응

2. 가본값 매개변수(default arguments): 매개변수와 입력이 없으면 기본값을 사용

3. 키워드 매개변수(keyword arguments): 인수와 파라미터를 동일 이름으로 대응

4. 가변 매개변수(arbitrary arguments): 인수의 개수가 동적인 경우

 

 

함수 호출 방법의 다른 형태

※ if 조건문으로 함수 호출

· 함수를 if 조건문에서 사용하면 편리할 때가 있다.

def isodd(arg):
    return arg % 2 == 1

result = {x: x * 2 for x in range(1, 11) if isodd(x)}
print(result)

결과

- for 반복 처리로 1~10 사이의 숫자가 차례대로 if 조건에 적어 둔 isodd 함수의 인자 x에 대응된 후 홀수면 True, 짝수면 False를 반환한다. 함수 값이 False이면 아무런 작업을 하지 않고, True이면 홀수만 제곱하여 출력한다.

 

※ 내부 함수 호출

· 함수 안에 함수를 만들 수 있다. 이를 내부 함수라고 부른다.

· 내부 함수는 외부 함수의 멤버이므로 외부 함수에서 내부 함수를 실행하도록 해야 한다.

def outerfunc():
    def innerfunc():
        print('내부 함수')
    innerfunc() # 내부 함수를 실행

outerfunc() # 외부 함수 실행

결과

 

변수의 활동 범위

· 변수가 어느 영역(모듈, 함수, 클래스)에서 선언되었는지 또는 치환되었는지에 따라 변수의 활동 범위가 달라진다.

- 함수 안에 변수가 선언되면 해당 함수 안에서만 유효한 지역변수가 된다.

- 변수를 모듈(보통은 파일로 저장) 수준으로 선언하면 해당 파일의 어디에서든 호출이 가능한 전역변수가 된다.

 

job = '기획자'	# 전역변수

def funJob():
    name = 'Ted'	# 함수 내에서만 유효한 지역변수
    job = '개발자'	# 지역변수
    print(name, job)	

funJob()

결과

 

명령어 nonlocal로 변수의 참조 수준 변경

· nonlocal 명령어는 지역 변수가 아님을 선언한다.

· nonlocal 명령어를 사용하면,  nonlocal 이 사용된 함수 바로 한단계 바깥쪽에 위치한 변수와 바인딩을 할 수 있다.

 

def kbs():
    a = 1           # kbs 함수에서 유효한 지역변수
    def mbc():
        nonlocal a  # a는 mbc 함수의 외부 함수인 kbs 함수의 a 변수가 된다.
        print(a)
        a = 2
    mbc()
kbs()

결과

 

명령어 global로 변수의 참조 수준 변경

· global 명령어는 적용된 변수를 전역변수로 만든다.

a = 1 # 전역변수 a

def mbc():
    global a    # a는 전역변수가 된다.
    print(a)    # global이 없으면 UnboundLocalError 에러
    a = 2

mbc()
    

결과

 

· 예시 - global과 nonlocal 명령어 사용하기

a = 10;b = 20;c = 30    # 전역변수
print('함수 수행 전 a:{}, b:{}, c:{}'.format(a, b, c))

def foo():
    # foo 함수 영역에서만 유효한 지역변수
    a = 40
    b = 50

    def bar():
        # b = 60  -> bar 함수 영역에서만 유효한 지역변수
        # c = 70
        nonlocal b  # b는 bar의 외부 함수인 foo의 b가 된다.
        global c    # c는 전역변수를 의미
        print('bar에서 출력1) a:{}, b:{}, c:{}'.format(a, b, c))
        b = 80
        print('bar에서 출력2) a:{}, b:{}, c:{}'.format(a, b, c))
        c = 90
        print('bar에서 출력3) a:{}, b:{}, c:{}'.format(a, b, c))

    bar()
    print('foo에서 출력 a:{}, b:{}, c:{}'.format(a, b, c))

foo()
print('함수 수행 후 a:{}, b:{}, c:{}'.format(a, b, c))

결과

- 전역변수를 함수 안에서 사용하려면 함수 안에서 global 변수명이라고 적어 주면 함수 안에 선언된 변수는 지역변수가 아니라 전역변수로 인정된다.

- 내부 함수의 경우 nonlocal 변수명으로 해당 변수의 수준을 외부 함수로 설정할 수 있다.

 

함수를 호출할 때 named argument로 매핑

· 함수를 선언하면서 매개변수에 디폴트 값을 적어줄 수 있다. 만약 전달되는 인수 없이 함수를 호출하면 지정된 디폴트 매개변수로 대체되어 처리된다.

· 함수를 호출하면서 함수명처럼 인수에 초깃값을 주고 매개변수의 이름과 매핑시켜 전달할 수 있다. 이 방법을 사용하면 함수의 매개변수에 인수를 전달할 때 매핑 순서를 지키지 않아도 된다.

 

· 예시

def showplus(start, end=5):
    print(start + end)

showplus(2, 3)
showplus(3)
showplus(start=2, end=3)    # 동일한 이름의 인수와 매개변수가 매핑
showplus(end=4, start=3)    # 동일한 이름의 인수와 매개변수가 매핑
showplus(2, end=3)

# 두 번째 인자가 상수면 에러 발생
# showplus(start=2, 3)
# showplus(end=4, 3)

결과

 

가변 인수 처리

· 매개변수 이름에 *를 붙여 주면 입력값을 전부 묶어 튜플로 처리하게 된다. *를 사용한 packing은 하나의 매개변수에 여러 개의 값을 넣는 것이 가능하다.

 

· 딕셔너리 자료형을 처리하려면, 매개변수명 앞에 **를 주면 된다.

 

· 예시1 - 입력값을 튜플로 처리하기

def fun1(*ar):  # 매개변수에 *을 사용
    print(ar)   # 튜플로 처리
    for i in ar:    #튜플 자료를 반복문으로 하나씩 출력
        print('음식:' +i)

# 가변적인 인수 개수
fun1('공기밥')
fun1('비빔밥', '김밥')
print()

def fun2(a, *ar):
    print(ar)
    for i in ar:
        print(a + ':'+ i)

fun2('과일','사과', '배')
fun2('과일','사과', '배', '바나나')

# 다음은 오류 발생
# def fun2(*ar, a)

결과

 

· 예시2 - 딕셔너리 자료형 매핑

def func(w, h, **other):    # other은 딕셔너리 형 자료와 매핑
    print('몸무게 {}. 키 {}'.format(w,h))
    print(other)

func(64, 175, name='Ted', age=23)
func(50, 175, name='Robin')
print()

# 함수의 매개변수에 인수를 전달할 때 *, ** 혼합도 가능
def functotal(a, b, *v1, **v2):
    print(a, b)
    print(v1)
    print(v2)

functotal(1, 2)
functotal(1, 2, 3, 4, 5)
functotal(1, 2, 3, 4, 5, m=6, n=7)

결과

 

 

클로저

· 클로저(closure)는 함수 밖에서 함수 안의 지역변수에 접근할 수 있도록 하는 구조를 의미하며, 스코프(scope)에 제약을 받지 않는 변수들을 포함하고 있는 코드 블록이다.

 

· 사용 목적: 함수 수행 후에도 지역 변수를 참조하여 사용할 수 있도록 만든다. 기본적으로 함수 안에 선언된 지역변수를 함수 수행이 끝나면 소멸한다.

 

· 클로저 사용법: 함수 안에 함수를 선언하고 내부 함수의 주소를 반환하도록 구현한다.

def 외부 함수
    지역변수
    ...
    def 내부 함수
        지역변수
    ...
    return 내부 함수명   # 클로저

 

- 내부 함수의 주소를 반환함으로써 함수 내의 변수를 함수 밖에서 계속 유지할 수 있는 구조가 클로저다.

- 전역변수를 사용하지 않도고 함수 내의 지역변숫값을 지속해서 기억하고 변경된 최신 상태를 유지할 수 있기 때문에 내부 데이터를 은닉하기 위해 사용할 수 있다.

 

· 예시1

def outer():
    count = 0
    def inner():    # 내부 함수
        nonlocal count
        count += 1
        return count
    return inner    # 클로저를 통해 내부 함수의 주소를 반환한다.

# 함수 내의 count 값 확인하기
var1 = outer()  # 객체 생성 후 내부 함수의 객체 주소를 var1에게 치환
print(var1())   # 변숫값이 계속 유지
print(var1())   # 변수 count의 값이 계속 유지

var2 = outer()   # 새로운 객체 생성
print(var2())

결과

- var1 = outer()하면 내부 함수객체가 만들어지고, 변수 var1이 내부 함수 객체의 주소를 갖게 된다. 즉, 함수가 호출될 때마다 함수 내에서 선언된 변수가 가진 값이 초기화되는 것이 아니라 값이 계속 유지된다.

 

· 예시2 - 분기별로 상품 총액에 대한 세금을 다르게 적용하기

def outer2(tax):    # 매개변수 tax는 outer2 함수의 지역변수
    def inner2(amount, unitPrice):
        amount = amount * unitPrice * tax     # 수량 * 단가 * 세금
        return amount   # inner2 함수는 amount를 반환
    return inner2   # outer2는 inner2 함수의 주소를 반환 (클로저)

# 1분기에는 수량 * 단가에 tax를 0.1 부과
q1 = outer2(0.1)    # inner2 함수의 객체 주소 기억
print('result1: ', q1(5, 10000))
print('result2: ', q1(10, 20000))

# 2분기에는 수량 * 단가에 tax를 0.05 부과
q2 = outer2(0.05)    # inner2 함수의 객체 주소 기억
print('result3: ', q2(5, 10000))
print('result4: ', q2(10, 20000))

결과

 

람다 함수

· 익명함수 또는 축약함수라고 부르며 이름이 없고, return 문 없이 결과를 반환하는 단순한 구조의 함수다.

 

· 사용 목적: 함수를 쓰고 싶은데 이름을 정하지 않고, 일회용으로 사용하고 싶을 때가 있다. 이렇게 하면 소스의 길이를 줄이는 효과가 있고, 개발자의 의도가 명확히 드러나므로 가독성이 향상된다.

 

· 사용법: 

lambda 인자... :표현식   # return 없이 결과를 반환

 

· 예시 1

 

· 예시 2

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

print(hap(1, 2))

# 위 코드를 람다로 표현
print((lambda x, y: x + y)(1, 2))

lambdaFun1 = lambda x, y: x + y  # 람다 함수를 변수에 치환
print(lambdaFun1(3, 4))

# 람다도 인수에 초깃값 지정 가능
lambdaFun2 = lambda x, y=10: x + y
print(lambdaFun2(5))
print(lambdaFun2(5, 6))

# 람다도 가변인수 지정 가능
lambdaFun3 = lambda x, *y, **z : print(x, y, z)
lambdaFun3(1, 2, 3, m=4, n=5)

# 다른 함수(filter)에서 람다 함수 사용하기
result = list(filter(lambda a: a < 5, range(10)))
print(result)

print(list(filter(lambda a:a%2, range(10))))    # 홀수 출력

결과

- 람다 함수는 자체적으로 처리 결과를 출력할 수 있고, 수식을 변수에 치환해 변수명()으로 처리해도 된다. 치환한 것은 함수객체의 주소이므로 제대로 처리된다.

 

일급 합수

· 어떤 객체가 있을 때 다음 조건을 만족하면, 일급 함수를 지원하는 프로그래밍 언어로 취급한다.

1. 함수를 변수나 데이터  구조 안에 담을 수 있다.

2. 함수 안에 함수를 선언할 수 있다.

3. 함수의 매개변수로 함수를 전달할 수 있다.

4. 함수를 반환값으로 사용할 수 있다.

 

· 파이썬은 함수 중심(지향) 언어이므로 일급 함수를 지원한다.

 

· 예시

def func1(a, b):
    return a + b

func2 = func1  # 함수의 수행 결과가 아닌 주소를 치환
print(func1(3, 4))  # 함수를 호출한 후 반환값 출력
print(func2(3, 4))

def func3(func): # 함수의 매개변수로 함수를 받는다.
    def func4():
        print('내부 함수 실행')

    func4() # 내부 함수 실행
    return func # 함수의 반환값이 함수

savedFun = func3(func1)
print(savedFun(3,4))

결과

 

함수 장식자

· 함수 감싸기(wrapping)을 해 주는 패턴으로, 함수 장식자(decorator)는 다른 함수를 감싼 함수다.

- 첫 번째 함수가 호출되면 반홥값이 장식자에게 건네진다. 그러면 장식자는 포장된 함수로 교체하여 함수를 돌려 준다.

 

· 장식자는 함수를 변형시키는 방법을 기술하는 데 편리하다. 장식자는 본질적으로 장식하는 함수의 기능을 강화하는 메타(어떤 정보를 내포) 프로그래밍 기법이다.

- 즉, 함수 장식자란 이미 정의된 함수로 다른 함수를 장식하는 데 사용하는 것으로, 장식자를 프로그램하는 것은 기본적으로 장식된 함수가 특정 함수로 전달될 수 있도록 해 준다.

 

· 사용 목적:

1. 기존 함수에 기능을 추가하고, 새로운 함수를 만들 수 있으므로 함수의 내부를 수정하지 않고 기능에 변화를 주고 싶을 때 사용한다.

2. 어떤 동작을 함수 앞뒤에 수행하거나, 공통으로 사용하는 코드를 쉽게 관리하는 목적으로 적용하면 효과적이다.

 

· 예시1

def make2(make1fn):
    # make1이 실행된 "안녕 반가워 Ted"를 반환하는 람다 함수의 주소를 반환
    return lambda: "안녕 " + make1fn()

def make1(hellofn):
    # hello가 실행된 "반가워 Robin"을 반환하는 람다 함수의 주소를 반환
    return lambda: "반가워 " + hellofn()

def hello():
    return "Ted"

hi = make2(make1(hello))  # 일반적인 함수 처리 방법
print(hi())

# 함수 장식자 처리 방법
@make2
@make1
def hello2():
    return "Robin"

hi2 = hello2() # 함수 실행 결과를 치환
print(hi2)

결과

 

- 일급 함수는 함수를 호출하면서 인수를 이용하여 매개변수에 함수를 전달할 수 있다. 위 예시의 make2(make1(hello))의 수행 순서를 보면 make1의 매개변수로 hello 함수의 주소가, make2의 매개변수로 make1 함수의 주소가 전달된후 최종적으로 make2 함수가 반환된 람다 함수를 실행한다.

 

- 이것을 함수 장식자로 표현할 수 있다.

@make2
@make1
def hello2():
    return "Robin"

hi2 = hello2() # 함수 실행 결과를 치환
print(hi2)

- hello2 함수의 주소가 @make1에 의해 make1(hellofun)의 매개변수로 전달된다. make1 함수는 "반가워" + hello2()에 의해 수행된 "반가워 Robin"을 반환하는 람다 함수 주소를 리턴한다. 이 주소는 다시 @make2에 의해 make2(makefn)의 매개변수로 전달된다. make2 함수는 "안녕" + make1 함수 결과인 "안녕 반가워 Robin"을 반환하는 람다 함수의 주소를 리턴받아 출력된다.

 

· 예시2

def outer(func):
    def inner(no1, no2):
        print("결과: {0}".format(func(no1, no2)))
    return inner # 클로저

@outer  # func 함수를 감싼 구조가 됨
def func(n1, n2):
    return n1 + n2

func(3, 4)

결과

 

재귀 함수

· 재귀 함수(recursive function)는 수행 중인 함수 내에서 자기 자신을 다시 호출하는 패턴이다.

· 장단점: 코드의 간결함과 코딩이 편리하지만, 메모리 관리가 비효율적이다. 

· 탈출 조건을 지정하지 않으면 무한루프에 빠지지 않으므로, 주의하자.

· 반복문과 재귀 함수의 차이: 재귀 함수는 함수이기 때문에 메모리 스택에 인자를 각각 따로 갖는다. 따라서 특정 상태의 인자를 특별한 코드나 변수 없이도 저장할 수 있다.

 

· 예시

def countdown(n):
    if n == 0:	# 탈출 조건
        print("완료")
    else:
        print(n, end=' ')
        countdown(n - 1)    # 재귀 처리

countdown(5)

결과

반응형

댓글