소개
이 랩에서는 NumPy 배열 조작의 기본 개념을 탐구하며, 특히 복사본 (copy) 과 뷰 (view) 의 차이점에 중점을 둘 것입니다. 이 차이를 이해하는 것은 Python 에서 효율적이고 버그 없는 수치 코드를 작성하는 데 매우 중요합니다. 이러한 개념을 실제로 확인하기 위해 Python 스크립트를 작성하고 실행할 것입니다.
복사본과 뷰의 차이점
NumPy 에서 **복사본 (copy)**은 자체 데이터를 가진 완전히 새로운 배열인 반면, **뷰 (view)**는 동일한 데이터를 보는 새로운 방식입니다. 뷰를 수정하면 원본 배열에 영향을 미치지만, 복사본을 수정해도 원본 배열에는 영향을 미치지 않습니다.
이를 실제로 살펴보겠습니다. 배열을 생성한 다음, 해당 배열의 뷰와 복사본을 만들 것입니다. 그런 다음 둘 다 수정하고 원본 배열에 미치는 영향을 관찰할 것입니다.
먼저 왼쪽 파일 탐색기에서 main.py 파일을 엽니다. 그런 다음 해당 파일의 내용을 다음 코드로 바꿉니다.
import numpy as np
## --- Part 1: Modifying a View ---
print("--- Modifying a View ---")
## Create an original array
original_array_view = np.array([1, 2, 3, 4, 5])
print(f"Original array: {original_array_view}")
## Create a view of the array
view_array = original_array_view.view()
## Modify the first element of the view
view_array[0] = 99
print(f"View after modification: {view_array}")
print(f"Original array after modifying the view: {original_array_view}\n")
## --- Part 2: Modifying a Copy ---
print("--- Modifying a Copy ---")
## Create another original array
original_array_copy = np.array([10, 20, 30, 40, 50])
print(f"Original array: {original_array_copy}")
## Create a copy of the array
copy_array = original_array_copy.copy()
## Modify the first element of the copy
copy_array[0] = 999
print(f"Copy after modification: {copy_array}")
print(f"Original array after modifying the copy: {original_array_copy}")
이제 파일을 저장하고 터미널에서 실행하여 출력을 확인합니다.
python main.py
다음과 같은 출력을 볼 수 있습니다. 뷰가 수정되었을 때 원본 배열이 변경되었지만, 복사본이 수정되었을 때는 변경되지 않은 것을 확인하세요.
--- Modifying a View ---
Original array: [1 2 3 4 5]
View after modification: [99 2 3 4 5]
Original array after modifying the view: [99 2 3 4 5]
--- Modifying a Copy ---
Original array: [10 20 30 40 50]
Copy after modification: [999 20 30 40 50]
Original array after modifying the copy: [10 20 30 40 50]
이는 핵심적인 차이를 보여줍니다. 뷰는 원본 데이터와 연결되어 있지만, 복사본은 완전히 독립적입니다.
배열 슬라이싱 - 뷰 생성
NumPy 에서 매우 흔한 연산은 슬라이싱 (slicing) 으로, 배열에서 특정 범위의 요소를 선택하는 데 사용됩니다. 기본 슬라이싱은 항상 원본 배열의 **뷰 (view)**를 생성합니다. 이는 메모리 효율성을 위한 핵심 기능이지만, 슬라이스를 수정하면 원본 데이터도 변경된다는 점을 인지해야 합니다.
이를 테스트해 보겠습니다. main.py 파일의 내용을 지우고 다음 코드를 추가합니다.
import numpy as np
## Create an array from 0 to 9
original_array = np.arange(10)
print(f"Original array: {original_array}")
## Create a slice of the array (elements from index 2 to 4)
array_slice = original_array[2:5]
print(f"Slice of the array: {array_slice}")
## Modify the first element of the slice
print("Modifying the first element of the slice to 100...")
array_slice[0] = 100
## Print the original array again to see the change
print(f"Original array after modification: {original_array}")
파일을 저장하고 터미널에서 실행합니다.
python main.py
출력은 다음과 같습니다.
Original array: [0 1 2 3 4 5 6 7 8 9]
Slice of the array: [2 3 4]
Modifying the first element of the slice to 100...
Original array after modification: [ 0 1 100 3 4 5 6 7 8 9]
보시다시피, array_slice[0]을 변경하는 것이 original_array[2]도 변경했습니다. 이는 슬라이스가 복사본이 아닌 뷰였음을 확인시켜 줍니다.
고급 인덱싱 - 복사본 생성
기본 슬라이싱은 뷰를 생성하는 반면, **고급 인덱싱 (advanced indexing)**은 항상 **복사본 (copy)**을 생성합니다. 고급 인덱싱은 인덱스의 리스트, 튜플 또는 다른 배열을 전달하여 요소를 선택하는 것을 포함합니다. 선택된 요소들이 메모리에서 연속적인 블록에 있지 않을 수 있으므로, NumPy 는 이를 담을 새로운 배열 (복사본) 을 생성합니다.
이전 단계와 비교해 보겠습니다. main.py를 지우고 다음 코드를 삽입합니다.
import numpy as np
## Create an array from 0 to 9
original_array = np.arange(10)
print(f"Original array: {original_array}")
## Use advanced indexing to select elements at indices 1, 3, and 5
indexed_array = original_array[[1, 3, 5]]
print(f"Indexed array: {indexed_array}")
## Modify the first element of the new array
print("Modifying the first element of the indexed array to 100...")
indexed_array[0] = 100
## Print the original array again to see if it changed
print(f"Original array after modification: {original_array}")
파일을 저장하고 스크립트를 실행합니다.
python main.py
출력은 다음과 같습니다.
Original array: [0 1 2 3 4 5 6 7 8 9]
Indexed array: [1 3 5]
Modifying the first element of the indexed array to 100...
Original array after modification: [0 1 2 3 4 5 6 7 8 9]
이번에는 원본 배열이 변경되지 않았습니다. indexed_array는 복사본이었으므로, 이를 수정해도 original_array에 영향을 미치지 않았습니다.
.base 를 이용한 복사본 및 뷰 식별
때로는 어떤 연산이 뷰를 반환했는지 복사본을 반환했는지 명확하지 않을 수 있습니다. NumPy 는 이를 확인할 수 있는 신뢰할 수 있는 방법, 즉 배열의 .base 속성을 제공합니다.
- 배열이 **뷰 (view)**인 경우,
.base속성은 해당 배열이 속한 원본 배열 객체를 가리킵니다. - 배열이 **복사본 (copy)**인 경우,
.base속성은None이 됩니다.
이를 사용하여 이전 단계에서 얻은 결과를 확인해 보겠습니다. main.py를 지우고 다음 코드를 추가합니다.
import numpy as np
## Create an original array
original_array = np.arange(10)
print(f"Original array: {original_array}\n")
## Create a view using slicing
view_slice = original_array[2:5]
print(f"Slice (view): {view_slice}")
## Check if the slice is a view of the original array
print(f"Is the slice a view? {view_slice.base is original_array}\n")
## Create a copy using advanced indexing
copy_indexed = original_array[[1, 3, 5]]
print(f"Indexed (copy): {copy_indexed}")
## Check if the indexed array is a copy
print(f"Is the indexed array a copy? {copy_indexed.base is None}")
파일을 저장하고 터미널에서 실행합니다.
python main.py
다음과 같은 출력을 얻게 되며, 이는 슬라이스가 뷰이고 인덱싱된 배열이 복사본임을 프로그래밍 방식으로 확인시켜 줍니다.
Original array: [0 1 2 3 4 5 6 7 8 9]
Slice (view): [2 3 4]
Is the slice a view? True
Indexed (copy): [1 3 5]
Is the indexed array a copy? True
.base 속성은 디버깅하고 코드가 예상대로 작동하는지 확인하는 데 매우 유용한 도구입니다.
요약
이번 실습에서는 NumPy 배열의 복사본 (copy) 과 뷰 (view) 간의 중요한 차이점을 배웠습니다. 둘 다 생성하는 연습을 하고 수정이 원본 데이터에 어떻게 영향을 미치는지 관찰했습니다.
.view()및 기본 슬라이싱과 같은 메서드는 뷰를 생성하며, 이는 메모리 효율적이지만 수정 시 의도하지 않은 부작용을 초래할 수 있음을 배웠습니다..copy()및 고급 인덱싱과 같은 메서드는 원본 배열과 독립적인 복사본을 생성함을 배웠습니다.- 마지막으로
.base속성을 사용하여 배열이 뷰인지 복사본인지 확실하게 확인하는 방법을 배웠습니다.
이러한 개념을 숙달하는 것은 NumPy 를 사용하여 견고하고 효율적인 수치 애플리케이션을 작성하는 데 중요한 단계입니다.



