읽기일기

파이썬 클린 코드 – 2

파이썬스러운(pythonic) 코드

인덱스와 슬라이스

자체 시퀀스 생성

  • 대괄호 기능은 사실 __getitem__ 메소드를 호출한다.
  • 특히 시퀀스는 __getitem__, __len_를 구현해 놓은 객체로 반복이 가능하다.
  • 만약 어떤 클래스가 표준 라이브러리 객체를 감싸는 래퍼라면 표준 라이브러리의 동일한 메소드를 호출하도록 위임할 수 있다.

  • 하지만 래퍼가 아닌 자신만의 시퀀스를 구현할 때에는 다음에 유의하자.
    • 범위로 인덱싱하는 결과는 해당 클래스와 같은 타입의 인스턴스여야 한다.
    • 리스트의 일부를 가져오면 리스트이다.
    • 튜플에서 range를 요청하면 결과는 역시 튜플이다.
    • substring의 결과는 문자열이다.
    • slice에 의해 제공된 범위는 파이썬이 하는 것처럼 마지막 요소는 제외해야 한다.
    • 범위를 처리할 때 일관성을 유지하여야 한다.
    • 특수한 동작을 한다면 사람이 기억하기 어렵기 때문에 버그가 생길 수 있다.

컨텍스트 관리자 (context manager)

  • 리소스 관련하여 컨텍스트 관리자를 볼 수 있다.
  • 보통 리소스를 할당받아 처리할 때 예외가 발생하면 리소스를 해제하여야 한다.
  • finally 블록을 사용하면 다음과 같이 된다.

  • 하지만 똑같은 기능을 파이썬스러운 방법으로 구현하였다.

  • open 함수는 컨텍스트 관리자 프로토콜을 구현한다.
  • 컨텍스트 관리자는 __enter____exit__ 두개의 메소드로 구성된다.
    • with문 처음에서 __enter__를 호출하고 그 반환 값을 as 이후 변수에 할당한다.
    • 블록이 끝나면 __exit__가 호출된다.
    • 예외가 있더라도 __exit__가 호출되고, 파라미터로 예외를 받기 때문에 적절한 처리를 하도록 구현할 수도 있다.
  • 리소스 관리가 아니더라도, 블록 전후로 필요한 어떤 로직을 구현하는데 사용하는 경우도 있다.
    • 전후로 필요한 로직과 실제 동작을 분리하기에 편리하다.
  • 예를 들어 DB 백업을 하는 기능을 구현한다고 하였을 때, DB 서비스를 중단하고 백업을 수행한 후 다시 서비스를 시작하여야 한다고 하자.

  • __exit__를 보면 예외를 파라미터로 받는 것을 볼 수 있다. 예외가 없으면 모두 None이다

  • __exit__True를 반환하면 발생한 예외를 호출자에게 전파하지 않는다는 것을 의미한다.

컨텍스트 관리자 구현

  • contextlib 모듈은 컨텍스트 관리자를 구현하ㅎ고, 더 간결한 코드를 작성하는데 도움이 되는 많은 헬퍼 함수와 객체를 제공한다.
  • 함수에 contextlib.contextmanager 데코레이터를 적용하면 해당 함수의 코드를 컨텍스트 관리자로 변환한다. 함수는 제너레이터라는 특수한 함수여야 한다.
  • 다음은 이전과 동일한 코드를 contextmanager 데코레이터를 사용하여 다시 작성한 것이다.

  • yield를 사용해서 이 함수는 제너레이터가 되었다. 이 yield앞의 것은 __enter__메소드처럼 취급된다. yield문의 반환 값은 __enter__메서드의 반환 값의 역할을 한다.
  • yield를 지나면 잠시 코드가 중간되고 db_backup()코드가 실행된다. 작업이 완료되면 다시 yield 문 다음의 코드가 실행되는데 이 부분이 __exit__코드에 해당하는 부분이 된다.

  • contextmanager를 사용하면 기존 함수를 그대로 재활용할 수 있다는 장점이 있다. 매직 메서드를 모두 추가하다보면 구현이 복잡해 지게 마련이다.

  • 또 다른 헬퍼는 contextlib.ContextDecorator이다. 이 클래스는 컨텍스트 관리자로 실행할 함수를 데코레이터로 적용할 수 있도록하는 믹스인 클래스이다.

  • 이렇게 하면 with문 없이도 함수를 호출만 하면 컨텍스트 관리자 내에서 동작하게 된다.
  • 이 방법은 서로 독립적이라는 면에서 장점이지만, 컨텍스트 내부의 객체에 접근할 수 없다는 단점이 있다.
  • contextlib.suppress는 지정한 예외 중 하나가 발생한 경우에는 예외를 발생시키지 않도록 한다.

프로퍼티, 속성과 객체 메서드의 다른 타입들

  • 다른 언어와는 다르게 파이썬 객체의 모든 프로퍼티나 함수는 public 이다. 따라서 다른 객체가 호출하지 못하도록 할 방법이 없다.
  • 강제는 아니지만 밑줄로 시작하는 속성은 private를 의미하는 관습이 있다.

파이썬에서의 밑줄

  • 이렇게 밑줄에 상관없이 모두 접근이 가능하지만, _timeout은 내부에서만 사용되고 외부에서는 호출하지 말아야 한다.
  • 밑줄로 시작하는 속성은 내부에서만 사용되고 바깥에서는 접근하지 않으므로 언제든 리팩토링이 가능하여야 한다.
  • 이 규칙을 준수하면 객체의 인터페이스를 유지할 수 있어 파급 효과에 대해 걱정하지 않아도 되어 유지보수가 쉽고 견고한 코드를 작성할 수 있다.

  • 밑줄을 두개를 사용하면 private과 같은 동작을 한다고 알려져 있으나 이것은 사실이 아니다.
  • 밑줄 두개를 사용하면 이름을 맹글링한ㄷ. 즉 _<class-name>__<attribute-name>의 형식으로 이름을 바꿔 버린다.

  • 위와 같이 접근이 가능하다. 이는 여러 번 상속받는 경우 클래스의 메서드를 이름 충돌 없이 오버라이드하기 위해 만들어졌다.
  • 따라서 의도한 경우가 아니라면 private의 의미를 위해서는 하나의 밑줄을 사용하자.

프로퍼티

  • 객체에 값을 저장해야 할 경우 보통 attribute를 사용한다.
  • 속성에 대한 적븐을 제어하려는 경우 프로퍼티를 사용한다. 이는 자바에서의 getter, setter에 해당하는 것이다.

  • 이렇게 하면 private으로 표시한 변수에 접근하게 되며, get_, set_을 사용하지 않아 더 간단한다.

  • 프로퍼티는 명령-쿼리 분리 원칙(command and query separation - CC08)을 따르기 위한 좋은 방법이다.

    • 명령-쿼리 분리 원칙은 객체의 메서드가 상태를 변경하는 커맨드이거나 무언가의 값을 반환하는 쿼리이거나 둘 중에 하나만을 수행해야한다는 것이다.

이터러블 객체

  • 내장된 반복형 객체 외에도 사용자 객체에도 이터러블을 만들 수 있다.
    • 이를 위해서는 __iter__ 메서드를 구현하고 이터레이터는 __next__ 메서드를 구현하면 된다.
    • for e in myobject:를 실행하기 위해서 파이썬은 다음 두가지를 검사한다.
    • 객체가 __next____iter__ 메서드는 하나를 포함하는 지 여부
    • 객체가 시퀀스이고 __len____getitem__을 모두 가졌는지 여부

이터러블 객체 만들기

  • 객체를 반복하려고 하면 파이썬은 객체의 __iter__가 있는지 확인하고 있으면 이 메서드를 호출한다.

  • for 루프는 StopIteration이 발생할 때 까지 next()를 호출하게 된다.

  • 이 클래스는 문제가 있는데, 끝에 한 번 도착하면 계속 StopIteration이 발생한다.
  • 이를 회피하기 위해 매번 인스턴스를 만들 수도 있지만, 제너레이터를 사용할 수도 있다.

  • 이와 같은 형태를 컨테이너 이터러블이라고 한다.

시퀀스 만들기

  • 시퀀스는 __len____getitem__을 구현하고 인덱스 0부터 시작하여 요소를 한 번에 하나씩 차례로 가져올 수 있어야 한다.
  • 이터러블을 사용하면 메모리를 적게 사용하지만 n번째 요소를 한번에 얻을 수 없다. 이는 결국 메모리-CPU 트레이드오프이다.

  • __get_item__은 다시 리스트에게 위임하기 때문에 음수 인덱스도 동작한다.

컨테이너 객체

  • 컨테이너는 __contains__ 메서드를 구현하면 되며, in 키워드가 발견될 때 호출된다. 일반적으로 Boolean 값을 반환한다.

  • 이 코드는 아래 코드처럼 해석된다.

  • 2차원 게임 지도에서 특정 위치에 표시를 해야한다고 생각해보자.

  • 코드의 의도가 무엇인지 이해하기 어렵고, 직관적이지 않다.

  • 지도에서 자체적으로 영역을 판단해주고, 이를 더 작은 객체에 위임해보자.

객체의 동적인 속성

<myobject>.<myattribute>를 호출하면 파이썬은 객체의 사전에서 <myattribute>를 찾아서 __getattribute__를 호출한다. 속성이 없으면 __getattr__ 메서드에 이름을 파라미터로 전달하여 호출한다.

호출형(callable) 객체

  • __call__을 사용하면 객체를 일반 함수처럼 호출할 수 있다.
  • 객체를 이렇게 사용하는 이유는 객체에는 상태가 있기 때문에 함수 호출 사이에 ㅈ어보를 저장할 수 있어 편리하기 때문이다.

  • 다음은 입력된 파라미터가 동일한 값으로 몇 번이나 호출되었는지를 반환하는 객체를 만든 것이다.

파이썬에서 유의할 점

변경 가능한(mutable) 파라미터의 기본 값

  • 변경 가능한 객체를 함수의 기본 인자로 사용하면 안된다.

  • 이 코드에는 두가지 문제가 있는데, 하나는 변경 가능한 인자를 사용한 것, 그리고 함수 내에서 가변 객체를 수정했다는 것이다.

  • 기본 값을 사용하여 함수를 호출하면 사전을 한 번만 생성하게 된다. 그리고 함수 내에서 pop을 이용하였기 때문이 두번째 호출할 떄에는 KeyError가 발생하게 된다.

  • 따라서 기본 초기값으로 None을 사용하고 함수 본문에서 기본 값을 할당하자.

내장(built-in) 타입 확장

  • 리스트, 문자열, 사전과 같은 타입을 상속 받아 확장할 때에는 collections 모듈을 사용해야 한다.
  • 타입을 직접 확장하는 경우, CPython에서는 Python에서 확장된 메서드를 호출할 수가 없기 때문에 문제가 발생한다.

  • 새로 정의한 __getitem__이 호출되지 않았음을 알 수 있다.
  • list 대신 UserList를 사용하면 된다.


Add a Comment Trackback