수달이네 기술 블로그

6. 함수2(Call-by-value,reference, 람다식, 튜플, 모듈 등...) 본문

언어/Python

6. 함수2(Call-by-value,reference, 람다식, 튜플, 모듈 등...)

슬픈 수달이 2025. 8. 25. 00:31

🔍Call-by-value(pass-by-value)

이전 글에서 말했던 call by value와 call by reference를 좀더 자세하게 살펴보려 한다.

def modify(n):
    print("함수 내 연산 전 주소", id(n))
    n=n+1
    print("함수 내 연산 후 주소", id(n))

k = 10
print("k=",k) #10
print("메인 내 연산 전 주소", id(k))
modify(k)
print("메인 내 연산 후 주소", id(k))
print("k=",k) #10

# k= 10
# 메인 내 연산 전 주소 3116073445968
# 함수 내 연산 전 주소 3116073445968
# 함수 내 연산 후 주소 3116073446000
# 메인 내 연산 후 주소 3116073445968
# k= 10

위 코드에서 보면 위와 아래의 print가 동일하게 10이 출력되는 이유를 살펴보면

주소값을 보면 알 수 있다.

그 이유는 함수 내에서 변환이 일어나기 전엔 main내에서 매개변수로 참조된 주소값이 동일했다. 그러나,

값이 변경되려 하자 새로운 주소값으로 이전된 것을 볼 수 있으며,

해당 주소값을 리턴하지 않았기 때문에 해당 주소값은 그대로 찾을 수 없게 되고, 원래 주소의 원래 값만 출력된다.

문자열도 마찬가지이며 함수가 아니더라도 확인할 수 있는데,

msg = "Happy Birthday"
print(id(msg))
msg += "To You"
print(id(msg))

#1341205684656
#1341205984160

위에서 보면 원래 주소값에서 연산을 가하자 새로운 주소값으로 바뀐 것을 볼 수 있다.

 

위의 모든 이유는 정수, 문자열, 튜플 등은 변경할 수 없는 객체(immutable object)이다.

따라서 해당 객체는 객체단위에서 변경하는 것이 불가하므로 새로운 주소값이 전달된다.

이것을 call-by-value라 한다.

🔍Call-by-reference(call by objective)

위와 반대로 변경할 수 있는 객체(mutable object)들이 존재하는데 그중 하나가 list객체이다.

def modify2(li):
    print("함수 내 연산 전 주소", id(li))
    li += [100, 200]
    print("함수 내 연산 후 주소", id(li))

lists = [1,2,3,4,5]

print(lists)
print("메인 내 연산 전 주소", id(lists))
modify2(lists)
print("메인 내 연산 후 주소", id(lists))
print(lists)

# [1, 2, 3, 4, 5]
# 메인 내 연산 전 주소 2364829478976
# 함수 내 연산 전 주소 2364829478976
# 함수 내 연산 후 주소 2364829478976
# 메인 내 연산 후 주소 2364829478976
# [1, 2, 3, 4, 5, 100, 200]

위처럼 리스트의 경우 변경할 때 새로운 객체를 생성하지 않고 기존의 객체에 추가된다.

그 이유는 리스트들이 값의 주소를 가질 뿐, 값 자체를 가진 것이 아니기 때문이다.

 

또 다른 mutable object로는 딕셔너리 타입이 존재한다.

dic = {"name" : "MariBlossom", "age" : 25, "height":174}
print(dic)

print(dic["name"])
print(dic["age"])
print(dic["height"])

# {'name': 'MariBlossom', 'age': 25, 'height': 174}
# MariBlossom
# 25
# 174

파이썬에선 기본 기능으로 딕셔너리를 제공하는데

콜론을 기준으로 왼쪽은 key, 오른쪽은 value가들어가며,

key값을 기준으로 value와 dictionary자체의 리스트들은 변경가능하다.

따라서

def modify2(dic):
    print("함수 내 연산 전 주소", id(dic))
    dic["kg"] = 80
    print("함수 내 연산 후 주소", id(dic))

dic = {"name" : "MariBlossom", "age" : 25, "height":174}

print(dic)
print("메인 내 연산 전 주소", id(dic))
modify2(dic)
print("메인 내 연산 후 주소", id(dic))
print(dic)

# {'name': 'MariBlossom', 'age': 25, 'height': 174}
# 메인 내 연산 전 주소 2581181967360
# 함수 내 연산 전 주소 2581181967360
# 함수 내 연산 후 주소 2581181967360
# 메인 내 연산 후 주소 2581181967360
# {'name': 'MariBlossom', 'age': 25, 'height': 174, 'kg': 80}

위처럼 함수에서 변화해도 주소값이 변경되지 않는다.


🔍지역변수, 전역변수

파이썬에서는 지역변수가 선언된 함수에서 벗어나면 사용할 수 없다.

그러나 전역변수를 사용하고 싶다면 global키워드를 사용하여 전역변수를 함수 내에서 사용할 수 있는데,

def sub():
    print(s)
    s = "바나나 좋아!"
    print(s)
s = "사과가 좋아"
sub()
print(s)

#UnboundLocalError: local variable 's' referenced before assignment

위의 경우는 지역변수에러가 나며,

def sub():
    global s  #전역변수 선언
    print(s)
    s = "바나나 좋아!"
    print(s)
s = "사과가 좋아"
sub()
print(s)

# 사과가 좋아
# 바나나 좋아!
# 바나나 좋아!

위처럼 global로 따로 선언해주어야 전역변수로 사용할 수 있다.

그러나 전역변수를 사용하는 프로그램은 가독성 상 좋지 않은 프로그램이다!

list객체 또한 마찬가지이다!

위에서 말했듯 list는 변경 가능한 객체이다. 그러나,

def sub(mylist):
    #리스트가 함수로 전달
    mylist = [1,2,3,4]
    print("함수 내의 리스트", mylist)

mylist = [5,6,7,8]
sub(mylist)
print("함수 외의 리스트", mylist)

# 함수 내의 리스트 [1, 2, 3, 4]
# 함수 외의 리스트 [5, 6, 7, 8]

위와 같이 함수 내에서 mylist로 새로 할당을 해버리면, 원래 주소가 아닌 새로운 주소에서 덮어씌워진다.

그렇기 때문에 mylist자체의 값은 유지된다.

따라서 

def sub(mylist):
    #리스트가 함수로 전달
    for x in range(len(mylist)):
        mylist[x] -= 4
    print("함수 내의 리스트", mylist)

mylist = [5,6,7,8]
sub(mylist)
print("함수 외의 리스트", mylist)

# 함수 내의 리스트 [1, 2, 3, 4]
# 함수 외의 리스트 [1, 2, 3, 4]

위처럼 할당이 아닌 변경을 해주면 아래에도 함수 밖에서도 적용이 됨을 확인할 수 있다.


🔍튜플과 여러값 반환

다른 프로그래밍 언어에서는 함수는 항상 하나의 값만 반환한다. 

하지만 파이썬에선 여러값을 반환하는 것 처럼 보이게 할 수 있는데, 튜플을 사용한 개념이다.

def sub():
    return 1,2,3
a,b,c = sub()
print(a,b,c)

#1 2 3

위에서 보면 return 1,2,3이라는 말도 안되는 식이 통하는 것을 볼 수 있다. 

이것은 사실 자세히 보면 return (1,2,3)이라고 작성된 것이라 볼 수 있으며,

이것은 튜플이라는 객체로 반환되어, a,b,c변수에 하나하나 풀어서 저장되는 방식이다.

그렇기 때문에 아래의 식도 적용이 된다.

x = 10
y = 11
print(x,y)
x,y = y,x
print(x,y)
#10 11
#11 10

리스트와 튜플의 다른점은

1. 리스트는 [대괄호]로 둘러싸지만, 튜플은 (소괄호)로 둘러싼다

2. 리스트는 mutable object지만 튜플은 immutable object이기 때문에 값을 변환할 수 없다.


🔍무명함수(람다식)

무명함수(람다식)은 이름은 없고 몸체만 있는 함수를 말한다.

sum = lambda x, y: x + y
print(sum(10, 20))

위처럼 lambda라는 키워드를 통해 선언하고 사용할 수 있으며, 

구조는 lambda 인수:수식으로 구성된다.

특징으로는

1. 키워드 안에선 print()같은 함수를 호출할 수 없으며 오직 계산만이 가능하다.

2. 람다 함수도 함수처럼 전역변수를 참조할 수 없고 자신만의 이름공간을 가지고 있다.(인수로 들어온 이름 사용)

3. 람다 함수에서는 변수에 값을 할당할 수 없다.

4. 람다 함수는 항상 반환되는 수식만 쓸수 있기 때문에 return 키워드가 필요없다.

5. 함수처럼 메모리를 갖는다.(함수 객체)

6. 람다함수는 리스트로도 생성할 수 있는데,

L = [lambda x: x**2,
     lambda x: x**3,
     lambda x: x**4,
     lambda x: x**5]
for f in L:
    print(f(5))
    
# 25
# 125
# 625
# 3125

위와 같이 사용할 수 있다.

람다함수는 코드안에서 함수를 포함하는 곳 어디든지 사용할 수 있다.


🔍모듈과 함수를 사용한 프로그램 설계

모듈이란 함수나 변수들을 모아둔 파일을 칭한다.(.py파일)

모듈안의 함수들은 import로 다른 모듈에 포함될 수 있다.

모듈 중에서 __main__모듈은 최상위 수준에서 실행되는 스크립트를 의미한다.

__name__란?

인터프리터가 실행 전에 만들어두는 내장 전역 변수이다.

이 변수는 직접 실행된 모듈에 __main__이라는 값을 가지게 되며, 직접 실행되지 않고, import된 모듈은 파일 명을 가지게 된다.

한마디로 if __ name__ == “__main__”라는 조건문을 넣어두면 직접 실행시켰을때만 실행되는 코드를 작성하면 된다.

위의 기능은 함수들을 디버깅 해볼 때 사용할 수 있을 것이다.

def readList():
    nlist = []
    flag = True
    while flag:
        number = int(input("Enter a number: "))
        if number < 0:
            flag = False
        else:
            nlist.append(number)
    return nlist


def processList(nlist):
    nlist.sort()
    return nlist


def printList(nlist):
    for i in nlist:
        print(i)


def main():
    nlist = readList()
    nlist = processList(nlist)
    printList(nlist)


if __name__ == '__main__':
    main()

# Enter a number: 30
# Enter a number: 50
# Enter a number: 10
# Enter a number: 20
# Enter a number: 90
# Enter a number: -1
# 10
# 20
# 30
# 50
# 90

위와 같은 식으로 메인을 실행시킬 수 있다.

 

대부분 대형 프로그램의 코드들은 매우 길다. 이것을 함수없이 작성한다면 가독성이 떨어지며, 디버깅이 힘들다.

이걸 해결하기 위해 코드를 최대한 작은 조각으로 분리해서 여러개의 함수로 나누어 작성해야한다

 

'언어 > Python' 카테고리의 다른 글

8. 리스트2(리스트 관련 함수들, 리스트 함축...)  (6) 2025.08.26
7. 리스트(list 자료형의 기능, 함수 등..)  (5) 2025.08.25
5. 함수  (0) 2025.08.23
4. 자주 쓰이던 함수  (0) 2025.08.22
3. 반복문  (0) 2025.08.21