티스토리 뷰

Books

파이썬 클린 코드 (2/10)

최성훈 2022. 3. 4. 05:31
반응형

2장. 파이썬스러운(Pythonic) 코드

인덱스와 슬라이스

.. 만약 래퍼도 아니고 내장(builtin) 객체를 사용하지도 않은 경우는 자신만의 시퀀스를 구현할 수 있다. 이때는 다음 사항에 유의해야 한다.

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

 

여기서 말하는 시퀀스(sequence)란 기본적으로 list, tuple, range 객체를 의미한다. (이외에 다른 시퀀스 타입도 존재한다.)

 


컨텍스트 관리자 (Context Manager)

컨텍스트 관리자는 파이썬이 제공하는 유용한 기능이다. 이것이 특별히 유용한 이유는 패턴에 잘 대응되기 때문이다. 이 패턴은 사실상 모든 코드에 적용될 수 있으며 사전조건과 사후조건을 가지고 있다. 즉 주요 동작의 전후에 작업을 실행하려고 할 때 유용하다.

일반적으로 리소스 관리와 관련하여 컨텍스트 관리자를 자주 볼 수 있다. 예를 들어 일단 파일을 열면 파일 디스크립터 누수를 막기 위해 작업이 끝나면 적절히 닫히길 기대한다. 또는 서비스나 소켓에 대한 연결을 열었을 때도 적절하게 닫거나 임시 파일을 제거하는 등의 작업을 해야 한다.

이 모든 경우에 일반적으로 할당된 모든 리소스를 해제해야 한다. 모든 것이 잘 처리되었을 경우의 해제는 쉽지만 예외가 발생하거나 오류를 처리해야 하는 경우는 어떻게 될까? ..(중략).. 가장 일반적인 방법은 finally 블록에 정리 코드를 넣는 것이다. 예를 들어 다음과 같은 간단한 예제를 생각해볼 수 있다.
fd = open(filename)
try:
    process_file(fd)
finally:
    fd.close()
그렇지만 똑같은 기능을 매우 우아하고 파이썬스러운 방법으로 구현할 수도 있다.
with open(filename) as fd:
    process_file(fd)

 

with문은 컨텍스트 관리자로 진입하게 한다.

 

컨텍스트 관리자 __enter__() __exit__() 두 개의 매직 메서드로 구성된다.

 

with 블록이 실행되기 전에 __enter__() 메소드를 실행하고 그 반환값은 as로 지정된 변수에 저장되며(__enter__() 메소드가 꼭 값을 반환할 필요는 없다.), with 블록이 끝날 때는 __exit__() 메소드가 호출된다.

 

위 예제에서 open()은 컨텍스트 관리자 프로토콜을 구현하며, 예외가 발생한 경우에도 파일(fd)이 자동으로 닫힌다.

 

 

컨텍스트 관리자는 관심사를 분리하고 독립적으로 유지되어야하는 코드를 분리하는 좋은 방법이다.

 


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

public, private, protected 프로퍼티를 가지는 다른 언어들과는 다르게 파이썬 객체의 모든 프로퍼티와 함수는 public이다. 즉 호출자가 객체의 속성을 호출하지 못하도록 할 방법이 없다.
엄격한 강제사항은 없지만 몇 가지 규칙이 있다. 밑줄로 시작하는 속성은 해당 객체에 대해 private을 의미하며, 외부에서 호출하지 않기를 기대하는 것이다. 다시 말하지만 이것을 금지하는 것은 아니다.

 

2년 넘게 파이썬만 쓰다 보니 접근제어자(Access Modifier)가 있는 자바는 어떻게 사용하는지 까먹은 것 같다. 😢

 

파이썬의 모든 프로퍼티와 함수가 public인 것은 프로젝트 내 어디서든 프로퍼티/함수에 접근이 가능하므로 비즈니스 로직을 구현할 때는 매우 편하지만, 반대로 코드 퀄리티를 신경 쓰지 않으면 스파게티 코드가 돼버리는 단점이 있는 것 같다.

 

특히나 _의 경우는 언어에서 강제하는 것이 아니기 때문에 (우리 팀에 한에서는) 실무에서 잘 쓰지 않는 것 같다.

 

다음엔 신경 써서 잘 활용해봐야겠다.

 

 

밑줄 두 개를 사용하면 실제로 파이썬은 다른 이름을 만든다. 이름 맹글링(name mangling)이라 한다. 이것이 하는 일은 다음과 같은 이름의 속성을 만드는 것이다. "_<class-name>__<attribute-name>" 경우 '_Connector__timeout'이라는 속성이 만들어지며 이러한 속성은 다음과 같이 접근하여 수정할 수 있다.
>>> vars(conn)
{'source': 'postgresql://localhost', '_Connector__timeout': 60}
>>> conn._Connector__timeout
60
>>> conn._Connector__timeout = 30
>>> conn.connect()
connecting with 30s

이중 밑줄은 파이썬스러운(pythonic) 코드가 아니다.

 

속성을 private으로 정의하려는 경우 하나의 밑줄을 사용하고 파이썬스러운 관습을 지키자.

 

 

프로퍼티는 명령-쿼리 분리 원칙(Command and Query Separation - CC08)을 따르기 위한 좋은 방법이다. 명령-쿼리 분리 원칙은 객체의 메서드가 무언가의 상태를 변경하는 커맨드이거나 무언가의 값을 반환하는 쿼리이거나 둘 중에 하나만 수행해야지 둘 다 동시에 수행하면 안 된다는 것이다.

 

Get/Set의 성격을 띠는 메소드는 철저하게 로직을 분리하라는 뜻인 것 같다.

 


이터러블 객체

... 객체를 반복할 수 있는지 확인하기 위해 파이썬은 고수준에서 다음 두 가지를 차례로 검사한다.

👉  객체가 __next____iter__ 이터레이터 메서드 중 하나를 포함하는지 여부
👉  객체가 시퀀스이고 __len____getitem__을 모두 가졌는지 여부
이터러블을 사용하면 메모리를 적게 사용하지만 n 번째 요소를 얻기 위한 시간복잡도는 O(n)이다. 하지만 시퀀스로 구현하면 더 많은 메모리가 사용되지만 (모든 것을 한 번에 보관해야 하므로) 특정 요소를 가져오기 위한 인덱싱의 시간 복잡도는 O(1)로 상수에 가능하다.

 


컨테이너 객체

컨테이너는 __contains__ 메서드를 구현한 객체로 __contains__ 메서드는 일반적으로 Boolean 값을 반환한다. 이 메서드는 파이썬에서 in 키워드가 발견될 때 호출된다.

.. (중략) ..

이 메서드를 잘 사용하면 코드의 가독성이 정말 높아진다. (파이썬스러운 코드다!)

 

__contains__와 같은 매직 메서드를 구현하려면 클래스가 있어야 하는데, 현재 실무에서는 특별히 클래스를 따로 작성하지 않고 있다.

 

Django 프레임워크 내부적으로 모델-시리얼라이저-뷰 레이어를 두고 코드를 작성하기 때문에, 별도의 매직메서드를  위한 새로운 클래스를 작성할 필요가 딱히 없다.

 

Django에서 지원하는 클래스(모델, 시리얼라이저, 뷰)만으로도 대부분의 비즈니스 로직은 구현 가능하기 때문이다.

 

Django도 MVT(Model-View-Template)라는 MVC 패턴과 유사한 패턴을 가지고 있는데, MVC와 마찬가지로 Fat Model Skinny View이다.

 

Fat Model Skinny View를 지향한다면, 파이썬 매직 메서드를 모델 클래스 안에다가 구현하면 될 것 같다.

 

음.. 지금 돌이켜보니 요즘엔 비즈니스 로직을 View 레이어 근처에다가 구현했던 것 같은데, 다시 주의하면서 코드를 짜야겠다.

 

반응형

'Books' 카테고리의 다른 글

파이썬 클린 코드 (5/10)  (0) 2023.01.16
파이썬 클린 코드 (4/10)  (0) 2022.05.07
파이썬 클린 코드 (3/10)  (0) 2022.03.18
파이썬 클린 코드 (1/10)  (0) 2022.03.04
댓글