함수 또는 변수할당 시 가변길이를 허용하여, flexibility를 높여주기 위한 의미에서 파이썬에서는 packing, unpacking 기능을 지원하고 있다. 연산자 *
(asterisk) 의 사용 방식에 따라 packing, unpacking 수행이 나뉘어지므로, 어떤 때에 packing 또는 unpacking이 수행되는지를 정확히 알아야 에러없이 수월하게 사용할 수 있다
*
연산자 (asterisk) 는 non-keyworded, 즉 인자(argument)들에게 key-value로 맵핑되지 않은 값들에 대해 packing 및 unpacking 이라는 기능을 수행하는데 packing 이란, 여러개의 값을 하나의 tuple 로 묶어주는 기능이며, unpacking이란, 하나의 자료구조에 들어있던 원소의 값들을 개수에 맞게 자동으로 원소에 할당하는 것을 뜻한다.
정의된 함수를 호출(call) 할 때, 인자로 sequence type 앞에 해당 연산자를 붙여준다면 unpacking이 실행된다. 즉, 여러개 원소를 포함한 하나의 데이터가 인자로 주어졌지만, 원소들을 풀어서 (unpacking) 여러개로 함수 인자로 전달한다. 따라서 아래 예시에서 divmod(*(100, 9))
는 곧 divmod(100, 9))
와 동일하게 된다고 이해할 수 있다.
print(*(divmod(100, 9)))
의 경우에는 divmod
함수가 계산된 뒤 print(*(11, 1))
가 되고, print문 안에서 unpacking을 통해 11과 1이 따로 전달되어 하나의 tuple아닌, 두개의 값을 공백으로 이은 string이 출력된 것을 알 수 있다
print(divmod(100, 9)) # (11, 1)
print(divmod(*(100, 9))) # (11, 1)
print(*(divmod(100, 9))) # 11 1
그렇다면, 이번에는 packing이 실행되는 예를 보자. 변수 할당 시에도 변수 앞에 *
연산자를 활용하게 되면, 해당 변수에게 할당될 수 있는 값들을 packing하여 전달하게 된다. 아래 예시와 같이 여러개의 변수에 여러 개의 값을 할당할 때, 길이가 정해진 부분을 미리 할당하고, 나머지 값들에 대해 packing 으로써 변수에 할당되게 할 수 있다
# unpacking + packing
x, y, *rest = range(10)
print(x, y, rest) # 0 1 [2, 3, 4, 5, 6, 7, 8, 9]
x, y, *rest = range(2)
print(x, y, rest) # 0 1 []
x, y, *rest = 1, 2, 3, 4, 5
print(x, y, rest) # 1 2 [3, 4, 5]
추가내용
강의에는 없었지만, packing, unpacking과 관련된 개념들을 좀 더 조사해서 추가적으로 함께 정리하고자 한다. 함수를 호출할 때는 인자로 넣은 값 앞에 * 를 붙인다면 해당 값이 인자들로 unpacking 되는 것을 확인하였다. 그러나, 함수를 정의할 때 매개변수(parameter) 앞에 *
를 사용한다면 packing 기능을 수행한다. 아래 예제를 보면, func
함수에서 매개변수 args
가 여러개의 값을 받았을 때 packing을 수행하여 print 하도록 정의하였다. 그 결과 함수의 return 값을 출력하면 tuple 객체가 출력되는 것을 확인할 수 있다. 매개변수명이 꼭 args
가 아니여도 에러가 나지 않지만, 일종의 convention이니 args를 쓰고 있으니 맞추어 쓰도록 하자
# packing - 함수의 매개변수들에 대해 packing 수행
def func(*args):
return args
print(func(2,3,4)) # (2, 3, 4)
추가적으로 **
는 keyworded argument, 즉 인자와 값이 key-value로 맵핑된 Dict type 과 관련하여 packing 또는 unpacking을 수행한다. 아래 코드의 함수를 정의한 부분을 보면, job 매개변수 이외에 더 많은 매개변수와 값들이 함께 들어올 때는 이들을 Dict type으로 packing
하여 kwargs
변수에 전달하도록 되어있다. 따라서 함수를 실행할 때 job외에 다른 인자와 값을 명시해도, Dict type으로 kwargs
로 전달되었고 return 된 것을 확인할 수 있다
def func(job, **kwargs):
return job, kwargs
print(func(job = "MLEngineer", domain = 'NLP', lang = "python")) # ('MLEngineer', {'domain': 'NLP', 'lang': 'python'})
**
의 unpacking 예를 보자. 함수를 실행할 때, 인자와 값을 Dict type으로 생성하여 함수 인자로 전달하고 해당 연산자를 사용한다. 그러면, 각 unpacking 이 수행되어 인자들에 값을 풀어서 전달한 것과 같게 된다
params = {'job' : "MLEngineer", 'domain' : 'NLP', 'lang' : "python"}
print(func(**params)) # ('MLEngineer', {'domain': 'NLP', 'lang': 'python'})
Transformers 라이브러리를 사용자들은 이러한 **
의 unpacking 이 너무나 익숙하다.
전처리 단계에서, Tokenizer 가 문장들을 encoding 하게 되면 “input_ids”, “attention_mask”, “token_type_ids” 를 key로 가지고 해당 tensor들을 value로 가지는 Dict 객체를 반환하게 된다. (아래에 batch
와 유사) 이것을 model의 forward
method 내에 unpacking 해주기 위해 **batch
를 model의 인자로 넣어주는 것이 일반적인 코드이다
# Huggingface Transformers 사용 시
model = ElectraForSequenceClassification(ckpt, num_labels = 2) # Binary Classification 으로 Fine-tuning
....
....
for epoch in range(num_epochs):
for step, batch in enumerate(train_loader):
"""
batch = {
"input_ids": torch.tensor([ .... ]),
"attention_mask": torch.tensor([ .... ]),
"token_type_ids": torch.tensor([ .... ])
}
"""
...
outputs = model(**batch)
...
lambda, map, filter, reduce 함수 (0) | 2022.05.17 |
---|---|
변수 범위 variable scope (python) (0) | 2022.05.17 |
Deep, Shallow Copy (3) - sorted vs sort (0) | 2022.04.30 |
Deep, Shallow Copy (2) - Mutable vs Immutable (0) | 2022.04.29 |
Deep, Shallow Copy (1) - 반복을 통한 List 생성 2가지 비교 (0) | 2022.04.28 |
댓글 영역