Skip to content

Latest commit

 

History

History
184 lines (151 loc) · 8.61 KB

220319.md

File metadata and controls

184 lines (151 loc) · 8.61 KB

Chapter 2

프로그래밍에서 관용구는 특정 작업을 수행하기 위해 코드를 작성하는 특별한 방법이다.
디자인 패턴과 관용구의 가장 큰 차이점

  • 디자인 패턴은 언어와 무관한 고차원의 개념으로 코드로 즉시 변환되지 않는다.
  • 관용구는 실제 코딩으로 변환된다.

관용구에 따른 파이썬 코드를 파이썬스럽다고 표현한다.
파이썬스러운 코드의 장점

  • 일반적으로 더 나은 성능을 냄
  • 코드가 더 작고, 이해하기 쉽다.
  • 팀이 동일한 패턴과 구조에 익숙해지면 실수를 줄이고, 문제의 본질에 집중할 수 있다.

인덱스와 슬라이스

파이썬은 음수 인덱스를 사용하여 끝에서부터 접근이 가능하다.

my_numbers = (4, 5, 3, 9)
print(my_numbers[-1])
print(my_numbers[-3])

9
5

slice를 사용하여 특정 구간의 요소를 구할 수도 있다.

my_numbers = (1, 1, 2, 3, 5, 8, 13, 21)
print(my_numbers[2:5])
print(my_numbers[:3])
print(my_numbers[3:])
print(my_numbers[::])
print(my_numbers[1:7:2]) # 1에서 7 사이를 2 칸씩 점프

(2, 3, 5)
(1, 1, 2)
(3, 5, 8, 13, 21)
(1, 1, 2, 3, 5, 8, 13, 21)
(1, 3, 8)

slice는 파이썬 내장 객체로 직접 빌드하여 전달할 수도 있다.

interval = slice(1, 7, 2)
print(my_numbers[interval])

interval = slice(None, 3)
print(my_numbers[interval] == my_numbers[:3])

(1, 3, 8)
True

slice의 (시작, 중지, 간격) 중 하나를 지정하지 않은 경우 None으로 간주한다.

자체 시퀀스 생성

위의 기능은 __getitem__이라는 매직 메소드 덕분에 동작한다.
이것은 myobject[key]와 같은 형태를 사용할 때 호출되는 메소드로 key에 해당하는 대괄호 안의 값을 파라미터로 전달한다.
특히 시퀀스는 __getitem__과 __len__을 모두 구현하는 객체라서 반복이 가능하다.
리스트, 튜플, 문자열은 시퀀스 객체의 예이다.

클래스가 표준 라이브러리 객체를 감싸는 래퍼인 경우 기본 객체에 가능한 많은 동작을 위임할 수 있다.

class Items:
    def __init__(self, *values):
        self._values = list(values)
    
    def __len__(self):
        return len(self._values)

    def __getitem__(self, item):
        return self._values.__getitem__(item)

위와 같은 캡슐화 방식 외에도 상속을 사용할 수 있다.
이 경우 collections.UserList 부모 클래스를 상속해야 한다.

래퍼도 아니고 내장 객체를 사용하지도 않은 경우는 자신만의 시퀀스를 구현할 수 있다.
유의할 점

  • 범위로 인덱싱하는 결과는 해당 클래스와 같은 타입의 인스턴스여야 한다.
    • 미묘한 오류 배제
  • slice에 의해 제공된 범위는 파이썬이 하는 것처럼 마지막 요소는 제외해야 한다.
    • 일관성 유지

컨텍스트 관리자 (Context manager)

컨텍스트 관리자는 원하는 타이밍에 정확하게 리소스를 할당하고 제공하는 역할을 하고 예로는 with가 있다.
컨텍스트 관리자는 주요 동작의 전후에 작업을 실행하려고 할 때 유용하다.

with open(filename) as fd:
    process_file(fd)

위의 경우 with문은 컨텍스트 관리자로 진입하고, open 함수는 컨텍스트 관리자 프로토콜을 구현한다. 즉, 예외가 발생한 경우에도 블록이 완료되면 파일이 자동으로 닫힌다.

컨텍스트 관리자는 __enter__와 __exit__ 두 개의 매직 메소드로 구성된다.
with문은 __enter__ 메소드를 호출하고 블록의 마지막 문장이 끝나거나 예외 또는 오류가 발생하면 __exit__ 메소드를 호출한다.

블록의 전후에 필요로 하는 특정 논리를 제공하기 위해 자체 컨텍스트 관리자를 구현할 수도 있다.

컨텍스트 관리자는 관심사를 분리하고 독립적으로 유지되어야 하는 코드를 분리하는 좋은 방법이다.
이들을 섞으면 로직을 관리하기가 더 어려워지기 때문이다.

예시
스크립트를 사용해 데이터베이스 백업을 해야하는데 백업은 데이터베이스가 실해되고 있지 않은 동안에만 가능하다.
백업이 끝나면 성공 여부에 관계없이 프로세스를 다시 시작해야 한다.

def stop_database():
    run("systemctl stop postgresql.service")

def start_database():
    run("systemctl start postgresql.service")

class DBHandler:
    def __enter__(self):
        stop_database()
        return self

    def __exit__(self, exctype, ex_value, ex_traceback):
        start_database()

def db_backup():
    run("pg_dump database")

def main():
    with DBHandler():
        db_backup()

컨텍스트 관리자를 디자인할 때 블록이 시작된 후에 무엇이 필요한지 고려해야 한다.
일반적으로 필수는 아니지만 __enter__에서 무언가를 반환하는 것이 좋은 습관이다.

__exit__메소드의 서명을 주목하면 블록에서 발생한 예외를 파라미터로 받고, 블록에 예외가 없으면 모두 None이다.
__exit__의 반환 값을 잘 생각해야 한다.
큭별한 작업을 할 필요가 없다면 아무것도 반환하지 않아도 된다.
__exit__가 True를 반환하면 잠재적으로 발생한 예외를 호출자에게 전파하지 않고 멈춘다는 것을 뜻하고, 이는 가급적 하지 않는 것이 좋다.

컨텍스트 관리자 구현

__enter__와 __exit__ 매직 메소드만 구현하면 해당 객체는 컨텍스트 관리자 프로토콜을 지원할 수 있다.

표준 라이브러리 contextlib 모듈은 컨텍스트 관리자를 구현하거나 더 간결한 코드를 작성하는 데 도움이 된다.

함수에 contextlib.contextmanager 데코레이터를 적용하면 해당 함수의 코드를 컨텍스트 관리자로 변환한다.
이때 함수는 제너레이터라는 특수한 함수의 형태여야 한다.

import contextlib

@contextlib.contextmanager
def db_handler():
    stop_database()
    yield
    start_database()

with db_handler():
    db_backup()

contextmanager 데코레이터를 적용하면 yield 문 앞의 모든 것은 __enter__ 메소드의 일부처럼 취급된다.
__enter__ 메소드의 반환 값과 같은 역할을 하는 것으로 as x:와 같은 형태로 변수에 할당할 수 있다.
yield 문에서 아무것도 반환하지 않으면 None을 반환하는 것과 같다.
yield 다음에 오는 모든 것들을 __exit__ 로직으로 볼 수 있다.

컨텍스트 매니저를 작성하면 기존 함수를 리팩토링하기 쉬운 장점이 있다.
일반적으로 어느 특정 객체에도 속하지 않은 컨텍스트 관리자가 필요한 경우 좋은 방법이다.

contextlib.ContextDecorator 클래스는 컨텍스트 관리자 안에서 실행될 함수에 데코레이터를 적용하기 위한 로직을 제공하는 믹스인 클래스이다.
컨텍스트 관리자 자체의 로직은 매직 메소드를 구현하여 제공해야 한다.

class dbhandler_decorator(contextlib.ContextDecorator):
    def __enter__(self):
        stop_database()

    def __exit__(self, ext_type, ex_value, ex_traceback):
        start_database()

@dbhandler_decorator()
def offline_backup():
    run("pg_dump database")

이 경우 with 문 없이 함수를 호출하기만 하면 offline_backup 함수가 컨텍스트 관리자 안에서 자동으로 실행된다.
이 접근법의 단점은 완전히 독립적인 것이다.
데코레이터는 함수에 대해 아무것도 모르고 그 반대도 마찬가지이다.
이는 좋은 특징이지만 컨텍스트 관리자 내부에서 사용하고자 하는 객체를 얻을 수 없다는 것을 의미한다.
대신 데코레이터로서의 이점으로 로직을 한번만 정의하면 동일한 로직을 필요로 하는 함수에 단지 데코레이터를 적용함으로써 재사용할 수 있다.

contextlib.suppress는 컨텍스트 관리자에서 사용하는 util 패키지로 제공한 예외 중 하나가 발생한 경우에는 실패하지 않도록 한다.
try/except에서는 코드를 실행하고 예외를 전달하거나 로그를 남기는 것은 비슷하지만 suppress 메소드를 호출하면 로직에서 자체적으로 처리하고 있는 예외임을 명시한다.

import contextlib

with contextlib.suppress(DataConversionException):
    parse_data(input_json_or_dict)

DataConversionException는 입력 데이터가 이미 기대한 것과 같은 포맷이어서 변환할 필요가 없으므로 무시해도 안전하다는 것을 뜻한다.