파이썬의 인덱싱, 슬라이싱, 컨텍스트 관리자
인덱스와 슬라이스
인덱스
파이썬의 리스트에 대해서 인덱싱과 슬라이싱이 가능합니다.
인덱싱은 원하는 위치의 요소를 가져오는 방식입니다.
파이썬의 인덱싱의 특이한 점은 음수 인덱스를 통해 배열의 뒤에서부터 접근이 가능하다는 것입니다.
sample_list = [1, 2, 3]
sample_list[0] # 1
sample_list[-1] # 3
슬라이스
파이썬에서는 슬라이싱을 통해 원하는 구간의 요소들을 가져올 수 있습니다.
이 때 알아야 할 점은 슬라이싱의 결과는 기존 객체와 같은 타입의 인스턴스라는 점입니다.
이것이 무슨 뜻이냐면, 리스트에 대해 슬라이싱을 적용하면 슬라이싱의 결과 역시 리스트일 것이고, 튜플에 대해 적용하면 결과가 튜플로 도출되어야한다는 뜻입니다.
슬라이싱은 list[a:b:c]
형태로 할 수 있는데,
- a는 가져올 시작 인덱스 (생략하면 0번부터)
- b는 마지막 인덱스 (생략하면 마지막 요소까지, 마지막 인덱스로 명시된 부분의 바로 앞 요소까지 가져옴)
- c는 interval (생략하면 interval은 1)
을 의미합니다.
sample_list = [1, 2, 3, 4, 5]
sample_list[0:3] # [1, 2, 3]
sample_list[0:5:2] # [1, 3, 5]
sample_list[:-2] # [1, 2, 3]
참고로 위의 경우 sample_list[a:b:c]
형태로 간격을 전달할 때 실제로는 슬라이스 객체를 전달하는 것과 같습니다.
이게 무슨 뜻인지 아래 코드를 확인해보도록 하겠습니다.
sample_list = list(range(1, 11)) # [1, 2, 3, ..., 10]
if sample_list[1:10:2] == sample_list[slice(1, 10, 2)]:
print("Result is the same")
else:
print("Result is not the same")
위의 경우 sample_list[slice(1, 10, 2)]
와 sample_list[1:10:2]
는 같으므로 조건절에서는 True
를 결과로 반환할 것입니다.
자체 시퀀스 생성
앞에서 인덱싱을 이용해 list[key]
형식으로 리스트의 특정 요소에 접근할 수 있음을 알게 되었습니다.
이를 가능하게 하는 것은 파이썬의 __get_item__이라고 하는 매직 메소드 때문입니다.
시퀀스 객체는 이 __get_item__ 매직 메서드와 __len__ 매직 메서드를 모두 구현한 객체입니다.
시퀀스 객체에 대해서는 슬라이싱과 인덱싱이 가능합니다.
__get_item__와 __len__ 를 모두 구현한 객체를 생성하고 동작을 확인해보도록 하겠습니다.
class Classroom:
def __init__(self, student_list: list):
self._student_names = student_list
def __len__(self):
return len(self._student_names)
def __getitem__(self, student_number):
return self._student_names.__getitem__(student_number)
if __name__ == "__main__":
classroom = Classroom(["Kang", "Kim", "Park", "Song", "Lee", "Jeon", "Choi", "Hwang"])
print(len(classroom)) # 8
print(classroom[0]) # Kang
print(classroom[6:]) # ['Choi', 'Hwang']
인덱싱과 슬라이싱이 모두 잘 동작하는 것을 확인할 수 있습니다.
컨텍스트 관리자
컨텍스트 관리자란
컨텍스트 관리자는 특정 로직 앞 뒤로 처리해야 하는 로직이 있을 때 유용합니다.
예를 들면 파일을 읽어 무언가를 처리해야 하는 로직이 있을 때 파일이 제대로 열렸는지, 오류가 나진 않았는 지를 메인 로직에서 처리하는 것은 적합하지 않습니다.
파이썬에서 파일을 여닫을 때는 with open(file_name) as f:
구문을 많이 쓰는데요, 이 구문이 가장 대표적인 컨텍스트 관리자의 예입니다.
컨텍스트 관리자는 메인 로직 이전에 실행되는 __enter__ 매직 메서드와 메인 로직 종료 후에 수행되는 __exit__ 매직 메서드를 갖습니다.
위의 with open(file_name) as f:
를 가지고 설명을 해보자면, __enter__는 as f:
의 f 변수에 들어갈 값을 반환합니다.
__exit__는 로직이 정상종료되든 예외가 발생하든 호출되며 예외 사항을 파라미터로 받습니다.
# __exit__ 매직 메서드의 형태
def __exit__(self, exc_type, exc_value, exc_traceback):
컨텍스트 관리자 생성 예제
예제로 컨텍스트 관리자를 하나 생성해보도록 하겠습니다.
만들 컨텍스트 관리자는 파일을 저장하기 이전에 파일 시스템이 잔여 용량이 충분한지 확인하는 역할을 합니다.
- FileDownloader: 컨텍스트 관리자. 파일 다운로드 이전에 용량이 충분한지 확인
- FileSystem: 파일 시스템. 파일이 다운로드되면 총 용량이 줄어듦
사용 함수
def check_remain_capacity(file_system, file_size):
if file_system.total_size >= file_size:
file_system.decrease_total_size(file_size)
print("##### DOWNLOAD success #####")
print(f"downloaded file size: {file_size}")
print(f"remain file system: {file_system.total_size}")
return True
return False
def print_error_log(file_system, file_size):
print("##### DOWNLOAD failed #####")
print("lack of file system capacity")
print(f"remain file system: {file_system.total_size}, download file size: {file_size}")
check_remain_capacity
: 파일 시스템에 용량이 층분한지 확인하는 함수. 용량이 충분하면 True 반환, 부족하면 False 반환print_error_log
: 파일 시스템에 용량이 부족할 때 에러 로그를 프린트하는 함수
FileSystem 클래스
class FileSystem:
def __init__(self, total_size: int):
self.total_size = total_size
def decrease_total_size(self, size: int):
self.total_size = self.total_size - size
- total_size를 가지고 있으며 새로운 파일이 다운로드 되면 total_size를 파일의 크기만큼 줄입니다.
FileDownloader 클래스
class FileDownloader:
def __init__(self, file_system: FileSystem, file_size: int):
self.file_system = file_system
self.file_size = file_size
def __enter__(self):
return check_remain_capacity(self.file_system, self.file_size)
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type:
print_error_log(self.file_system, self.file_size)
print("##########end#############\n")
- 초기화 시에 사용할 파일 시스템과 다운로드 받을 파일 용량을 받습니다.
- __enter__ 메서드에서 용량이 충분한지 확인하고 충분하면 True, 부족하면 False를 반환합니다.
- __exit__ 메서드에서는 에러가 발생한 경우 로그를 남깁니다
LackOfCapacityException 예외 클래스
class LackOfCapacityException(Exception):
pass
- 파일 시스템 용량이 부족할 때 발생하는 예외입니다.
메인 코드
if __name__ == "__main__":
fs = FileSystem(100)
download_list = [10, 20, 50, 30]
for file in download_list:
with FileDownloader(fs, file) as success:
if success:
print("Do something with downloaded file")
else:
raise LackOfCapacityException
- 다운로드 받을 파일의 용량 리스트를 가지고 있습니다.
- 사용할 파일시스템의 총 용량은 100으로 설정하여 초기화합니다.
- 파일 하나하나마다 컨텍스트 매니저를 이용하여 파일을 다운로드 받습니다.
- 파일 다운로드에 성공했을 때는 파일을 이용해서 할 일을 하고 파일 다운로드에 실패하면 LackOfCapacityException을 발생시킵니다.
위의 코드를 실행하면 download_list = [10, 20, 50, 30]
내의 원소들 중 30에 해당하는 원소를 실행할 때 예외가 발생하는 것을 확인할 수 있습니다.
contextlib 모듈을 이용한 컨텍스트 관리자 생성 예제
컨텍스트 관리자 객체를 굳이 생성할 필요가 없을 때 contextlib에서 제공하는 데코레이터를 이용해 간편하게 컨텍스트 관리자를 생성할 수 있습니다.
위의 예제와 동일하게 작동하는 컨텍스트 관리자를 contextlib를 이용해서 생성한 코드는 다음과 같습니다.
import contextlib
@contextlib.contextmanager
def file_downloader(file_system, file_size):
result = check_remain_capacity(file_system, file_size)
try:
yield result
except LackOfCapacityException:
print_error_log(file_system, file_size)
print("##########end#############\n")
yield 문을 기준으로 yield문 앞 부분이 __enter__ 로직, 뒷 부분이 __exit__로직으로 작동하게 됩니다.
이렇게 생성한 컨텍스트 관리자를 이용하는 코드는 아래와 같습니다.
if __name__ == "__main__":
fs = FileSystem(100)
download_list = [10, 20, 50, 30]
for file in download_list:
with file_downloader(fs, file) as success:
if success:
print("Do something with downloaded file")
else:
raise LackOfCapacityException
코드 수행의 결과는 앞의 예제와 동일합니다.