Computer >> 컴퓨터 >  >> 프로그래밍 >> Python

파이썬 인수 패킹(Packing)과 언패킹(Unpacking) 완벽 이해하기: *args와 **kwargs 활용법

*args와 **kwargs란 무엇인가?

파이썬으로 조금이라도 프로그래밍을 해봤다면 함수 정의에서 *args**kwargs라는 표현을 한 번쯤은 접했을 것입니다. 그렇다면 이들은 정확히 무슨 역할을 할까요?

*** 연산자는 사용되는 위치에 따라 서로 상보적인 관계에 있는 서로 다른 연산을 수행합니다.

예를 들어 메서드를 정의할 때는 다음과 같이 사용합니다.

def __init__(self, *args, **kwargs):
    pass

위와 같은 동작을 패킹(Packing)이라고 부릅니다. 메서드 호출 시 전달받은 모든 인수를 하나의 변수로 묶어 args라는 튜플에 담기 때문입니다. 이름은 args가 아니어도 상관없지만, args가 가장 널리 쓰이는 파이썬다운 관례입니다. 왜 모든 인수를 하나의 변수로 묶어야 하는지 이해하려면 아래 예제를 살펴보세요.

세 개의 인수를 받는 함수가 있고, 함수에 전달할 인수들을 담은 크기 3의 리스트가 있다고 가정해 보겠습니다. 이때 리스트를 그대로 함수에 전달하면 호출이 정상적으로 작동하지 않고 오류가 발생합니다.

예제 1

# 세 개의 인수를 받는 함수
def func(a, b, c):
    print("{} {} {}".format(a, b, c))

# 인수 리스트
lst = ['python', 'java', 'csharp']

# 리스트를 그대로 전달
func(lst)

실행 결과

TypeError: func() missing 2 required positional arguments: 'b' and 'c'

리스트 전체가 첫 번째 인수 a에만 전달되고 나머지 인수가 부족하기 때문에 TypeError가 발생합니다.

패킹된 인수 다루기

일단 변수가 '패킹'되면, 일반적인 방식으로는 불가능한 다양한 작업을 수행할 수 있습니다. args[0], args[1], args[2]를 통해 각각 첫 번째, 두 번째, 세 번째 인수에 접근할 수 있습니다. 또한 args 튜플을 리스트로 변환하면 항목을 수정, 삭제, 변경하는 작업도 가능해집니다.

패킹된 인수를 다른 메서드로 전달하려면 언패킹(Unpacking)이 필요합니다.

def __init__(self, *args, **kwargs):
    # 일부 코드
    super(VehicleClass, self).__init__(self, *args, **kwargs)
    # 이후 코드

여기서도 * 연산자가 사용되었지만, 이번에는 메서드 호출의 맥락에서 사용되었습니다. 이 경우 args 배열을 풀어서 각 변수가 독립적인 것처럼 메서드를 호출합니다. 더 명확한 이해를 위해 아래 예제를 살펴보겠습니다.

예제 2

def func1(x, y, z):
    print(x)
    print(y)
    print(z)

def func2(*args):
    # 수정 작업을 위해 args 튜플을 리스트로 변환
    args = list(args)
    args[0] = 'HTML'
    args[1] = 'CSS'
    args[2] = 'JavaScript'
    func1(*args)

func2('Python', 'Java', 'CSharp')

실행 결과

HTML
CSS
JavaScript

위 출력 결과에서 볼 수 있듯이, func1에 전달하기 전에 세 개의 인수를 모두 변경할 수 있었습니다.

같은 방식으로 예제 1에서 발생한 TypeError도 해결할 수 있습니다.

예제 1-1

# 세 개의 인수를 받는 함수
def func(a, b, c):
    print("{} {} {}".format(a, b, c))

# 인수 리스트
lst = ['python', 'java', 'csharp']

# 언패킹하여 리스트 전달
func(*lst)

실행 결과

python java csharp

이처럼 함수에 전달해야 할 인수의 개수를 알 수 없을 때, 패킹을 사용해 모든 인수를 하나의 튜플로 묶을 수 있습니다.

# 패킹을 활용해 임의 개수의 인수를 합산하는 함수
def Sum(*args):
    total = 0
    for i in range(0, len(args)):
        total = total + args[i]
    return total

# 실행 코드
print("2개 인수 함수의 합: \n", Sum(9, 12))
print("5개 인수 함수의 합: \n", Sum(2, 3, 4, 5, 6))
print("6개 인수 함수의 합: \n", Sum(20, 30, 40, 12, 40, 54))

실행 결과

2개 인수 함수의 합:
21
5개 인수 함수의 합:
20
6개 인수 함수의 합:
196

패킹과 언패킹 동시에 활용하기

아래 프로그램은 패킹과 언패킹을 모두 활용하는 방법을 보여줍니다.

# 세 개의 인수를 받는 함수
def func1(x, y, z):
    print("첫 번째 인수: ", x)
    print("\n두 번째 인수: ", y)
    print("\n세 번째 인수: ", z)

# 패킹 - func2에 전달된 모든 인수가 *args 튜플로 묶임
def func2(*args):
    # 수정 작업을 위해 args 튜플을 리스트로 변환
    args = list(args)

    # args[0]과 args[1] 수정
    args[0] = 'Hello'
    args[1] = 'TutorialsPoint'

    # args를 언패킹하여 func1() 호출
    func1(*args)

# 실행 코드
func2("I", "Love", "Coding")

실행 결과

첫 번째 인수: Hello

두 번째 인수: TutorialsPoint

세 번째 인수: Coding

딕셔너리에 ** 사용하기

** 연산자는 딕셔너리를 다룰 때도 활용됩니다. 키-값 형태의 딕셔너리를 함수에 언패킹하여 키워드 인수처럼 전달할 수 있습니다.

# **를 사용한 딕셔너리 항목 언패킹 데모
def func(x, y, z):
    print("딕셔너리 첫 번째 항목: ", x)
    print("\n딕셔너리 두 번째 항목: ", y)
    print("\n딕셔너리 세 번째 항목: ", z)

d = {'x': 27, 'y': 54, 'z': 81}
func(**d)

실행 결과

딕셔너리 첫 번째 항목: 27

딕셔너리 두 번째 항목: 54

딕셔너리 세 번째 항목: 81

활용 사례

  • 서버로 알 수 없는(무한한) 개수의 요청을 보내야 하는 소켓 프로그래밍에서 활용됩니다.

  • 장고(Django) 같은 웹 프레임워크에서 뷰 함수에 가변 인수를 전달할 때 사용됩니다.

  • 가변 인수를 전달해야 하는 래퍼(Wrapper) 함수에서 활용됩니다.