소연의_개발일지

*args and **kwargs in Python

두개 모두 가변인자를 위한 변수이다.

 

 

 

*args(키워드가 없는 인자, arguemnts)

  • 함수에 가변 값을 넣어줄 때 사용한다. 즉 인자의 값의 길이에 제한 없이 사용할 수 있다.
  • 이전에 정의한 값보다 더 많은 인자들을 넣을 때 사용한다.
  • 인자의 값이 튜플 형태로 저장된다.

예시

def foo(*args):
    for a in args:
        print(a)
foo(1)
# 1

foo(1,2,3)
# 1
# 2
# 3

 

 

 

**kwargs(키워드가 있는 인자, keyword arguments)

  • 인자의 값이 딕셔너리 형태로 저장된다.
  • 더블 스타(**)를 사용하는 이유는 키워드 인자를 통해 인자를 전달할 수 있기 때문이다. 이를 통해 어떤 수의 키워드 인자라도 전달할 수 있다.

예시

def bar(**kwargs):
    for a in kwargs:
        print(a, kwargs[a])

bar(name='one', age=27)
# name one
# age 27

다른 예시

def table_things(**kwargs):
    for name, value in kwargs.items():
        print(f'{name} = {value}')


table_things(apple='fruit', cabbage='vegetable')

사전에 딕셔너리를 만들지 않아도 함수에서 넣어줄 때 담아주면 키값도 같이 가져온다. 


 

위치 인자와 키워드 인자

첫번째로 무엇이 위치 인자와 키워드 인자를 이해해 보자.

아래는 위치 인자와 함수 정의 예시이다.

def test(a, b, c):
    print(a)
    print(b)
    print(c)
    
test(1,2,3)

#output:
1
2
3

1, 2, 3으로 받은 값을 하나씩 출력하는 함수이다.

이 함수는 위치 인자를 정의하고 있다. 키워드와/이름이 지어진 인자들 또한 호출할 수 있다.

def test(a, b, c):
    print(a)
    print(b)
    print(c)

test(a=1, b=2, c=3)

#output:
1
2
3

위처럼 지정하여 인자를 지정해 주어도 결과는 동일하다.

 

 

이제 키워드 인자의 함수 정의 예시를 공부해 보자.

def test(a=0, b=0, c=0):
    print(a)
    print(b)
    print(c)
    print('-------------------------')

test(a=1,b=2,c=3)

#output:
1
2
3
-------------------------

또한 위치인자의 함수들 또한 호출할 수 있다. 실행결과는 위와 같다.

이제 우리는 위치 인자와 키워드 인자를 사용하는 함수 정의에 대해 이해할 수 있다.

여기까지는 함수를 호출할 때 들어올 인자 수를 3개로 지정해 주었다.

 

 

* 연산자와 ** 연산자

이제 '*' 연산자와 '**' 연산자를 공부해 보자.

이러한 연산자는 두 가지 영역에서 사용할 수 있다.

a) 함수 호출

b) 함수 정의

함수 호출에서 '*' 연산자와 '**' 연산자의 사용예제를 살펴보고 그 후에 이에 대해 논의해 보자.

def sum(a, b):  # 인자들을 다음과 같이 받는다. -> sum(1,2) or sum(a=1,b=2)
    print(a + b)

my_tuple = (1, 2)  # 튜플
my_list = [1, 2]  # 리스트
my_dict = {'a': 1, 'b': 2}  # 딕셔너리

# Let us unpack data structure of list or tuple or dict into arguments with help of '*' operator
# '*'오퍼레이터의 인자를 활용하여 리스트 또는 튜플 또는 딕셔너리의 데이터 구조를 알아보자.
sum(*my_tuple)  # '*'를 사용한 튜플실행결과는 sum(1,2)와 같다.
sum(*my_list)  # '*'를 사용한 리스트 실행 결과는 sum(1,2)의 결과와 같다.
sum(**my_dict)  # '**'를 사용한 딕셔너리 실행 결과는 sum(a=1, b=2)와 같다.

# output is 3 in all three calls to sum function.
# 3개의 함수에서 모두 3의 결과가 나온다.

 

위에서 인자 2개를 받아 더한 값을 반환하는 함수 sum(a, b)를 만든다.

두개의 값이 각각 담긴 튜플, 리스트, 딕셔너리를 만들었다. (my_tuple, my_list, my_dict)

같은 함수에 *을 붙여 튜플과 리스트를 각각 넣고, **을 붙여 딕셔너리를 넣는다.

위에서 설명했던 것처럼 *는 리스트는 튜플 형태로 값을 전달하고, **를 붙이면 딕셔너리 형태로 값을 전달한다.

 

→ 그래서 기억해야 할 것은

‘*’연산자와 ‘**’연산자는 함수를 호출하는데 사용된다는 것이다.

'*' 연산자리스트나 튜플과 같은 데이터 구조체를 함수 정의에서 필요한 인자들로 분해한다.

'**' 연산자딕셔너리를 함수 정의에서 필요한 인자들로 분해한다.

 

이제 ‘*’연산자를 함수 정의에서 사용하는 방법을 보자.

예시

def sum(*args): #pack the received positional args into data structure of tuple. after applying '*' - def sum((1,2,3,4))
    sum = 0
    for a in args:
        sum+=a
    print(sum)

sum(1,2,3,4)  #positional args sent to function sum

#output
10

위의 sum(*args) 함수에서는 *args로 인자에 제한 없이 값들을 튜플로 받는다. 

그래서 1, 2, 3, 4는 for문을 돌면서 값들을 더하고, 더한 값 10을 출력한다.

 

요약: ‘*’연산자의 정의 기능은 튜플 형식으로 정보를 받는다.

 

또다른 예시1

def myFun(arg1, *argv):
    print("첫번째 인자:", arg1)
    for arg in argv:
        print("그 다음으로 받은 인자 :", arg)


myFun('안녕하세요', '오늘은', '2023년  6월', '막바지입니다', '굉장히', '습하네요.')

'''
[출력]
첫번째 인자: 안녕하세요
그 다음으로 받은 인자 : 오늘은
그 다음으로 받은 인자 : 2023년  6월
그 다음으로 받은 인자 : 막바지입니다
그 다음으로 받은 인자 : 굉장히
그 다음으로 받은 인자 : 습하네요.

'''

두번째 인자에 *을 붙여서 받으면, 첫 번째 인자만 그로 들어가고 그 뒤로는 *argv로 받는다.

 

또다른 예시2

def print_everything(*args):
    for count, thing in enumerate(args):
        print( f'{count}. {thing}')

print_everything('apple', 'banana', 'cabbage')

enumerate를 활용하여 같이 쓸 수도 있다.

 


 

이제, 함수 정의에서 ‘**’이 사용되는 예시를 보자.

def sum(**args): 
    # '**'뒤에 키워드 연산을 입력하면 딕셔너리 자료구조로 저장된다 = def sum(a:1,b:2,c:3,d:4}) 와 같다.
    sum=0
    for k,v in args.items():
        sum+=v
    print(sum)

sum(a=1,b=2,c=3,d=4) # sum() 함수로 보내진다.

#output
10

위의 sum(**args) 함수에서는 sum() 함수 안에 args라는 딕셔너리를 만들고, 그 안에 1, 2, 3, 4값을 넣는다.

*와 다르게 파라미터 명을 같이 보낼 수 있다.

‘**’ 연산은 딕셔너리 형태로 인자들을 받는다.

 

함수 호출에서 '*'는 튜플이나 리스트 구조체를 함수 정의에서 받을 위치나 키워드 인자로 언팩(unpack)한다.

함수 호출에서 '**'는 딕셔너리 구조체를 함수 정의에서 받을 위치나 키워드 인자로 언팩한다.

함수 정의에서 '*'는 위치 인자를 튜플로 패킹(pack)한다.

함수 정의에서 '**'는 키워드 인자를 딕셔너리로 패킹한다. 

(여기서 패킹은 튜플로 값을 묶는다는 뜻이고, 언패킹은 튜플로 묶인 값을 푼다는 뜻이다.)

더보기

더 설명하자면,

 

함수 정의에서 *는 위치 인자를 튜플로 패킹한다.

이는 함수를 정의할 때 인자 앞에 *를 붙이면 해당 인자들이 튜플로 묶여서 함수 내에서 사용된다.

이렇게 하면 함수를 호출할 때 인자의 개수가 가변적일 때 유용하다.

 

함수 호출에서 **는 딕셔너리 구조체를 함수 정의에서 받을 위치나 키워드 인자로 언팩한다.

이는 호출할 함수에서 받은 딕셔너리를 개별 인자로 쪼개서 함수에 전달하는 것을 의미한다.

이를 통해 함수 호출 시 개별 인자를 지정하는 것보다 더 많은 인자를 쉽게 전달할 수 있다.

 

함수 정의에서 **는 키워드 인자를 딕셔너리로 패킹합니다.

이는 함수를 정의할 때 인자 앞에 **를 붙이면 해당 인자들이 딕셔너리로 묶여서 함수 내에서 사용된다.

이렇게 하면 함수를 호출할 때 인자의 이름과 값을 함께 전달할 수 있어서 가독성이 높아진다.

 


*args and **kwargs의 사용

 

def time_cal(func):
    def wrapper(*args, **kwargs):
        start=time.time()
        time.sleep(1)
        result = func(*args, **kwargs)
        end= time.time()
        print(func.__name__+ ' took ' + str((end-start)*1000) + ' milli seconds')
        return result
    return wrapper

*args와 **kwargs는 데코레이터를 만들 때 유용하게 사용된다. 

위의 코드는 함수를 실행할 때 몇 초 걸리는지 알려주는 데코레이터이다.

사용 방법은 함수를 만들 때 @를 붙인 후 데코레이터 함수 이름을 적어주면 된다.

 

사용 예시

	 arr = [5, 17, 31, 34, 40, 50, 61, 71, 79, 87, 97, 100]

@time_cal
def sample_func(arr, target):
    """일일히 비교"""
    for idx, i in enumerate(arr):
        if i == target:
            print(idx)
            break

sample_func(arr, 50)

# 5
sample_func took 0.0 milli seconds

참고 사이트: https://stackoverflow.com/questions/3394835/use-of-args-and-kwargs

profile

소연의_개발일지

@ssoyxon

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!