The Most Diabolical Python Antipattern (번역)

잘못된 코드를 작성하는 방법은 여러가지가 있습니다. 그러나 파이썬에서는 특히 하나가 더 문제입니다.

원문 : https://realpython.com/the-most-diabolical-python-antipattern/

우리는 지쳐 있었지만 기뻐했습니다. 다른 두 명의 엔지니어들이 신비한 유니코드 버그를 해결하기 위해 각각 3일 동안 노력했지만 결국 포기했고, 저는 하루 만에 원인을 격리했습니다. 그리고 10분 후, 우리는 해결 후보를 갖게 되었습니다.

비극적인 것은 우리가 7일 동안의 시간을 낭비했으며 10분 만에 문제를 해결할 수 있었다는 것이다.

여기 핵심 내용이 있다. 다음 코드 조각은 파이썬 개발자가 쓸 수 있는 가장 자기 파괴적인 것 중 하나이다

try:
    do_something()
except:
    pass

예외를 포착하는 다른 방식들도 있지만 결과적으로 같은 문제를 야기합니다. 예를 들어 except Exception: 또는 except Exception as e: 와 같은 코드입니다. 이러한 코드들은 모두 똑같이 매우 나쁜 영향을 미칩니다: 에러 상황을 조용히 그리고 눈에 띄지 않게 숨기기 때문에, 그 에러들을 빨리 발견하고 처리하기 어렵게 만듭니다.

  • 사람들은 특정 에러만 발생할 것으로 예상하고 Exception을 잡았지만, 실제로는 예기치 못한 모든 에러를 숨기게 됩니다.
  • 버그가 프로덕션 환경에서 발생하게 되는데 너무 자주 발견되면 코드 베이스의 어느 부분에서 잘못되는지 거의 또는 전혀 모를 수 있습니다. try 블록에서 오류가 발생하는지 알아내는데 엄청난 시간이 걸릴 수 있다.
  • 에러가 발생한 곳을 찾았다 해도, 중요 정부가 모두 유실되어서 트러블 슈팅하는데 방해가 된다. Error/Exception class 가 뭔지? 무엇이 호촐되었고 변수 데이터 구조는 무엇이었는지? 라인은 몇번째 줄이었는데 무슨 파일인지, 누가 호출한 것인지?
  • 스택 트레이스라는 가치 있는 정보를 버리게 되어, 버그 수정에 며칠이 아닌 몇 분만에 해결할 수도 있는 기회를 놓치게 됩니다.
  • 가장 나쁜 점은 엔지니어들의 사기와 자존감에 해가 갈 수 있다는 것입니다. 문제 원인을 찾는데 많은 시간이 걸려 자신을 나쁜 프로그래머라고 생각하게 되지만, 사실 이렇게 예외를 잡는 방식 자체가 근본적으로 디버깅을 어렵게 만듭니다.

파이썬 애플리케이션 개발 경험 10년 가까이 쌓은 저의 경험에서 이 패턴은 개발자 생산성과 애플리케이션 안정성을 가장 크게 저하시키는 단일 요소로 돋보입니다… 특히 장기적으로는 더욱 그렇습니다. 혹시 더 나쁜 패턴이 있다고 생각하신다면, 꼭 알려주세요.

Why Do We Do This To Ourselves?

물론, 아무도 의도적으로 다른 개발자를 괴롭히거나 애플리케이션의 안정성을 저하시키도록 코드를 작성하지 않습니다. 우리가 모든 예외를 잡아 처리하는 (except Exception:) 패턴을 사용하는 이유는 try 블록 안의 코드가 일반적인 작업 중에 때때로 특정한 방식으로 예외를 발생시킬 수 있기 때문입니다. 낙관적인 시도와 예외 처리 조합은 이러한 상황에 접근하기 위한 훌륭하고 완벽하게 파이썬적인 방법입니다.

하지만, 모든 예외를 잡아서 아무런 처리 없이 계속 진행하는 것은 처음에는 그리 나쁜 생각이 아닌 것처럼 보일 수 있습니다. 하지만 이런 방식으로 코드를 작성하는 순간 실제 개발 과정에서 찾아내기 어려운 최악의 종류의 버그를 만들어 낼 수 있습니다.

  • 개발 중에 탐지를 피하고 라이브 프로덕션 시스템으로 푸시 할 수있는 버그.
  • 버그가 하루 종일 발생했음을 깨닫기 전에 몇 분, 몇 시간, 며칠 또는 몇 주 동안 프로덕션 코드로 사용할 수있는 버그.
  • 문제를 해결하기 어려운 버그.
  • 억제 된 예외가 제기되는 위치를 알고 있어도 수정하기 어려운 버그.

나는 절대로 예외를 잡지 말라고 말하는 것이 아닙니다. 그곳에 있다 예외를 잡은 다음 계속해야 할 좋은 이유-단지 아니 조용히. 좋은 예는 단순히 내려 가고 싶지 않은 미션 크리티컬 프로세스입니다. 똑똑한 패턴은 예외를 포착하는 try 절을 주입하고 전체 스택 추적 심각도 logging.ERROR 또는 더 크고 계속하십시오.

The Solutions

우리가 예외를 붙잡고 싶지 않다면 대신 어떻게해야합니까? 두 가지 선택이 있습니다.

대부분의 경우 최선의 선택은보다 구체적인 예외를 잡는 것입니다. 

try:
    do_something()
# Catch some very specific exception - KeyError, ValueError, etc.
except ValueError:
    pass

이것이 가장 먼저 시도해야 할 것입니다. 호출 된 코드를 약간 이해해야하므로 어떤 유형의 오류가 발생할 수 있는지 알 수 있습니다. 다른 사람의 코드를 정리하는 대신 코드를 처음 작성할 때 더 잘 수행 할 수 있습니다.

만약 어떤 코드가 무조건 모든 익셉션을 잡아야 하는 경우 – 예를 들어 top level 의 코드 (rest API 등) – 은 full stack trace 를 기록해야 한다. 만약 logging module 사용중이라면 매우 쉽다.

import logging

def get_number():
    return int('foo')
try:
    x = get_number()
except Exception as ex:
    logging.exception('Caught an error')

logging.exception 은 trace 를 출력한다.

ERROR:root:Caught an error
Traceback (most recent call last):
  File "example-logging-exception.py", line 8, in <module>
    x = get_number()
  File "example-logging-exception.py", line 5, in get_number
    return int('foo')
ValueError: invalid literal for int() with base 10: 'foo'

What You Can Do Now

Explicitly Prohibit It In Your Coding Guidelines

너의 팀이 코드 리뷰를 한다면, 아마도 코딩 가이드라인 문서가 있을 것이다. 없다고 해도 시작하는 것은 쉽니다. 새로운 위키를 만들어서 새로운 가이드를 만들 된다. 이것만 추가 하자.

  • 어떤 코드가 반드시 모든 Exception 을 잡아야 한다면 – top-level class 이며, 장기적으로 도는 코드인 경우, rest api – 반드시 full stack trace 를 남기도록 한다. 메세지만 남기지 말고!
  • 이외 모든 에러에 대해서 – 실제로 대다수가 따라야 하는 코드 – catch 하는 Exception 은 반드시 구체적이어야 한다. KeyError 또는 ConnectionTimeout 등.

Create Tickets For Existing Overbroad Except Clauses

물론입니다. 해당 내용을 한국어로 번역하면 다음과 같습니다:

기존의 광범위한 예외 잡기(Overbroad Except Clauses)에 대한 티켓 생성

위의 방법은 새로운 문제가 코드 베이스에 들어가는 것을 방지할 수 있습니다. 하지만 기존의 광범위한 예외 잡기들은 어떻게 해야 할까요? 간단합니다: 버그 추적 시스템에 티켓이나 이슈를 생성하여 수정하도록 합니다. 이는 문제를 해결하고 잊어버리지 않을 가능성을 크게 높이는 쉬운 액션 단계입니다. 진지하게, 지금 바로 할 수 있습니다.

저는 각 저장소나 애플리케이션별로 하나의 티켓을 만들어 코드를 감사하여 Exception이 잡히는 모든 곳을 찾는 것을 권장합니다. (코드 베이스에서 “except:”와 “except Exception”을 grep하여 찾을 수 있을 것입니다.) 각각의 발생 지점에 대해, 매우 구체적인 예외 유형을 잡도록 변환하거나, 어떤 예외를 잡아야 할지 명확하지 않다면 except 블록에서 전체 스택 트레이스를 로그로 기록하도록 수정합니다.

선택적으로, 감사 개발자는 특정 try/except 블록에 대해 추가 티켓을 생성할 수 있습니다. 예외 클래스를 더 구체적으로 만들 수 있다고 생각되지만 해당 코드 부분을 충분히 잘 모르는 경우, 이렇게 하는 것이 좋습니다. 이 경우, 전체 스택 트레이스를 로그로 기록하는 코드를 넣고, 추가 조사를 위한 별도의 티켓을 생성한 후 해당 부분을 잘 아는 사람에게 할당합니다. 특정 try/except 블록에 대해 5분 이상 생각하게 된다면, 이렇게 하고 다음 부분으로 이동하는 것을 권장합니다.

의견: 이 방식은 기존의 문제를 체계적으로 해결하는 데 도움이 될 것 같습니다. 티켓을 만들어 추적하면 문제가 잊혀지거나 방치되는 것을 방지할 수 있습니다. 또한 감사 과정에서 우선순위를 정하고 적절한 담당자에게 할당하여 효율적으로 처리할 수 있을 것입니다.

Why Log The Full Stack Trace?

  1. 예외 메시지만 로깅하면 예외가 발생한 파일과 라인만 알 수 있습니다. 하지만 오류의 근본 원인이 어디에서 비롯되었는지는 알 수 없습니다. 일반적으로 완전히 다른 파일이나 모듈에서 발생할 수 있으며, 원인을 추측하기 어렵습니다.
  2. 실제 애플리케이션에서는 다중 코드 경로가 예외를 일으키는 블록을 호출할 수 있습니다. 메시지만 로깅하면 어떤 경로에서 오류가 발생했는지 구분할 수 없습니다.
  3. 50명 규모의 엔지니어링 팀에서 일할 때, 유니코드 버그 때문에 4개월 넘게 당직 엔지니어를 깨워야 했습니다. 예외는 잡혔지만 메시지만 로깅되었고, 두 명의 시니어 엔지니어가 며칠씩 작업한 끝에 포기했습니다.
  4. 절망적으로 제게 넘긴 후, 스택 트레이스를 얻기 위해 6시간 작업했습니다. 스택 트레이스를 얻자마자 10분 만에 해결책을 찾을 수 있었습니다. 처음부터 스택 트레이스를 로깅했다면 엔지니어 1주일 분량의 시간을 절약할 수 있었습니다.
  5. 이런 경험 때문에 파이썬에 대해 더 공부하게 되었고, 엔지니어로서 효과적으로 활용하는 방법을 글로 쓰기 시작했습니다.

스택 트레이스는 버그를 며칠이 아닌 몇 분 만에 해결할 수 있게 해주는 가치 있는 정보입니다. 로깅에 조금 더 노력을 기울여 전체 스택 트레이스를 기록하는 것이 개발 생산성과 애플리케이션 안정성 측면에서 이점이 큽니다

Robust exception handling (번역)

https://eli.thegreenplace.net/2008/08/21/robust-exception-handling/ 번역

Exceptions vs. error status codes

예외는 에러 상태 코드를 반환하는 것보다 좋다. 몇몇 언어는 (파이썬 포함해서) 언어의 코어펑션과 표준 라이브러리가 throw 하는 예외를 처리해야 한다. 그렇지 않은 언어라도 오류 코드보다 예외를 선호하는게 낫다.

파이썬에서는 종종 ‘EAFP 과 LBYL 논쟁’ 이 있다.

  • EAFP : 허락보다는 용서를 구하는 것이 쉽다. Easier to Ask for Forgiveness than Permission
  • LBYL : 도약하기 전에 살펴보라. Look Before You Leap

Alex Martelli 의 말을 인용해보자.

도약하기 전에 살펴보기(LBYL) 라고도 하는 다른 언어의 일반적인 관용구는 작업을 실제로 하기 전에 미리 살펴보는 것이다. 모든 상황에 대해 미리 확인 하는 것인데 여러가지 이유로 적합하지 않을 수 있다.

  • 검사 : 미리 체크 하는 코드 / 작업 : 실제 수행하는 코드
  1. 모든것이 괜찮은 일반적인 주 케이스들에 대해 검사 코드는 가독성과 명확성을 감소 시킨다.
  2. 검사하는 코드는 작업 그 자체, 수행할 작업의 중복이다.
  3. 프로그래머는 아마도 쉽게 검사 코드를 누락하는 오류를 범할 것이다.
  4. 검사가 수행되는 순간과 작업이 시도되는 순간 사이의 상황이 변경될 수 있다.

LBYL 의 예

def do_something(filename):
  if not can_open_file(filename):
    return err(...)
  f = open(filename)
  ...

can_open_fileopen 이 성공할 것을 가정한다고 해보자. 이를 Alex 의 요점이 어떻게 적용되는지 확인해 보자.

  1. 가독성 감소는 덜 분명하지만, 더 많은 오류 검사가 추가된다면 가독성은 떨어진다. (C에 대한 경험이 많은 사람이라면, 누구나 일부 기능의 맨 위에 있는 일반적인 오류검사의 양에 익숙하다.)
  2. can_oen_fileopen 작업을 복제한다. open 이 built-in code 라서 DRY (don’t repeat yourself) 원칙을 위해하는 것을 덜 느끼게 된다.
  3. can_open_file 이 모든 체크를 다 했다고 어떻게 확신할 수 있나? 그렇지 않다면 어떻게 되는가?
  4. 다중처리 환경에서 작업한다고 가정해 보자. (서버에서 실행되는 웹 응용 프로그램이라고 생각해보자) can_open_fileopen 사이에 다른 프로세스가 그 파일을 열었거나 삭제 하거나 수정했다면?. 이는 디버깅 하기 어려운 경합이 발생한다.

EAFP 의 예

def do_something(filename):
  try:
    f = open(filename)
  except IOError, e:
    raise MyApplicationsExceptionType(e.msg)
    # could even pass whole traceback etc
    # etc...

LBYL 코드에서 발생한 문제점들이 발생하지 않으며 더 많은 유연성을 제공한다. 예를 들어 더 높은 수준에서 Exception 을 핸들링 하는게 적절하다고 판단되며, 원하면 예외 처리를 아예 안 할 수도 있다. 이는 에러 코드로는 하기 어렵다.

Never use exceptions for flow-control

예외는 예외적인 상황에서 존재한다. (정상적인 실행이 아닌 이벤트) 프로그래머가 str.find('substring') 을 호출한다면 그는 str 에서 substring 이 없다고 해서 예외가 발생할 것으로 생각하지 않는다. 이것은 find / 찾기 를 호출한 목적 자체가 substring 존재 여부를 확인하고자 했기 때문이다. 이 경우 예외 발생보다는 None 이나 -1 을 반환하는 것이 낫다. 그러나 그가 str[idx] 를 호출했다면 str 의 길이가 idx+1 보다 짧다면 예외가 발생하는 것이 적절하다. 왜냐하면 사용자는 idx 가 유효하다고 생각했지만 그렇지 않았다. 이는 “기대하지 못한 이벤트 (예기치 못한 상황)” 이다.

흐름제어에 사용되는 예외는 goto 와 같다.

Handle exceptions at the level that knows how to handle them

본질적으로, 예외는 여러 상위 계층으로 전파될 수 있고 여러 계층에서 처리(catch)될 수 있다.
질문이 생길 수 있다. – 어디서 예외를 catch 하는 것이 적절한 것인가.
가장 좋은 위치는 그 예외를 핸들링 할 수 있는 코드 조각이다. 프로그래밍 오류 (IndexError, TypeError, NameError 등) 같은 일부 예외는 프로그래머에게 직접 맡겨서 해결해 (handling) 면 예외는 사라진다.

당신이 완전한 어플리케이션을 가지고 있다면, 예외와 함께 무언가 실패하는 것은 적절하지 않다. 정중한 에러메세지를 보여주고 에러를 잘 로깅하는게 낫다. 나중에 분석할 때 도움이 될 것이다. 에러 다이얼로그에서 에러를 리포트 하겠냐고 물어보는 프로그램도 있다.

따라서 try/except 코드를 작성하기 전에 스스로에게 물어보라. “이 에러를 처리하기에 이곳이 적절한 위치인가? 여기에 에러를 처리하기에 충분한 정보가 있나?”

이것이 except: 구문은 모든 에러를 캐치하기 때문에 조심해야 한다. 너가 의도한 에러 외에도 모든 것을 캐치한다.

Do not expose implementation details with exceptions

위에서 언급한것 처럼 예외는 구현 계층 구조를 따라서 급행 열차를 타듯 전파된다. 예를 들어 그들은 캡슐화를 깨지기 쉽게 만들고 구현의 상세를 노출 시킨다.

예를 들어 파일을 사용해서 내부 캐시를 구현한 모듈을 만든다고 가정해 보자. 어떤 이유로 모듈이 캐시 파일을 열 수 없는 오류가 발생할 수 있다. open 함수는 IOError 를 발생시킬 것이다. 어디서 잡아야 할까?

당신이 만드는 객체를 사용하는 사용자에게 맡기지 말아라. 이는 캡슐화를 깨트린다. 사용자는 당신이 파일을 사용하고 있다는 사실을 인식하지 못한다. 어쩌면 다음 버전은 당신은 DB나 API 를 사용하고 있을 수도 있다.

오히려 모듈이 예외를 catch 하고 그것을 custom Exception (CachingFailedError) 로 감싸고, 가능한 많은 정보를 보존하고 전파해야 한다. 그러면 모듈 사용자는 CachingFailedError 만 catch 하면 되고 캐시 모듈이 바뀌더라도 그의 코드를 수정할 필요가 없어진다. 또한 사용자는 테스트 중에 오류의 원인을 조사하는 경우에 필요한 모든 정보를 얻을 수 있을 것이다.

예외를 올바르게 다시 발생시키는 것은 어렵다 만능 레시피도 없다. 더 많은 정보를 줄 수 있는가? 원래의 예외가 필요한가? 역추적이 필요한가?
이전 예외를 유지하면서 더 적절한 형식으로 예외를 다시 발생시키키만 하면 되는 경우는 다음을 수행한다.

class StuffCachingError(Exception): pass

def do_stuff():
    try:
        cache = open(filename)
        # do stuff with cache
    except IOError, e:
        raise StuffCachingError('Caching error: %s' % e)

이점

  1. 내부 구현을 외부 코드에 노출하지 않았다. 사용자는 do_stuff() 를 호출하는데 있어 StuffCahingError 만 처리하면 된다. 파일을 사용하는지는 알 바 아니다.
  2. 사용자가 error 에 대해서 깊이 분석하기 원할 때, 당신은 이미 원래의 에러를 메세지에 더해 제공했다.

원래의 역 추적도 유지해야 한다고 생각되면 (대부분 필요하지 않다.) 다음과 같이 작성하자.

class StuffCachingError(Exception): pass

def do_stuff():
    try:
        cache = open('z.pyg')
        # do stuff with cache
    except IOError:
        exc_class, exc, traceback = sys.exc_info()
        my_exc = StuffCachingError('Caching error: %s' % exc)
        raise my_exc.__class__, my_exc, traceback

이제 이전과 같이 StuffCachingError 예외가 발생하면, open 에러에 대한 traceback 이 발생한다.

Document the exceptions thrown by your code

문서는 그 프로그램의 외부 세계와의 계약이다.

  1. argument 를 전달해서 호출될 때의 기대하는 상태
  2. 반환되는 결과
  3. 사이드 이펙트
  4. throw 되는 예외

처음 3가지 정도를 문서화 하는 것이 일반적이지만 마지막 예외 는 문서화하지 않는다. 이것도 하면 좋겠다

Python에서 Exception Handling

https://stackoverflow.com/questions/30675410/exception-handling-what-level-to-put-it-at

https://eli.thegreenplace.net/2008/08/21/robust-exception-handling/

몇가지 글을 읽어봤는데 저희 설계에 Exception 처리에 대한 내부 가이드가 없어서 검색한 글을 내 방식대로 번역 하였다.

기본적으로 모듈 내에서 처리 해서 캡슐화를 깨지 말아야 한다. 공개된 인터페이스만 호출자가 사용하도록 하는 것인데 익셉션 역시 사용자 정의 익셉션을 문서를 통해 공개하여 사용토록 한다.

처리 할 수 있는 에러는 되도록 해당 모듈 내에서 처리 하며, 그것을 호출 하는 사용자에게 내부에서 어떤 Exception 이 발생할 지를 하나씩 파악하게 하는 어려움을 쥐어주지 말자. 이렇게 되면 사용자가 머리 아파서 에라모르겠다 모든 Exception 을 한번에 처리하자!! 라고 결심하게 된다. 친절한 모듈은 사용자에게 Exception 처리를 떠넘기지 말아야 할 것이다. 다만 모듈 내에서 처리 불가능한, 회복 불가능한 Exception 도 있다. 형제 백엔드 호출 불가, DB접속 오류 등은 API 레벨에서 글로벌 Exception 핸들러가 동작해야 할 수 있다.

python __init__.py 작성 유형

https://towardsdatascience.com/whats-init-for-me-d70a312da583

번역

사용자가 module 사용하는 방법은 package 의 __init__.py 를 사용하는 것.

Modules

작은 modules 로 분리하는 것은 좋다. 첫째로, 모듈은 연관된 소스들이 뭉쳐있다. 둘째, 논리적으로 비슷한 코드가 뭉쳐 있으면 읽기,이해하기가 좋다.

developer 에게 좋은 모듈은 user 에게 좋은 모듈인가? user 는 아필요 없을 것이다. 그 패키지 않에 무엇들이 있는지. 사용자에게는 필요한 모듈만 명시적으로 요청할 수 있어야 한다. 패키지 developer 가 해야 할 것은?

An example package

/src
    /example_pkg
        __init__.py
        foo.py
        bar.py
        baz.py
    setup.py
    README.md
    LICENSE

*.py

# foo.py
def foo_func():
  print('this is foo')

# bar.py
def bar_func():
  print('this is bar')

# baz.py
def baz_func():
  print('this is baz')

Your code as a grocery store

import 문과 패키지 구조에 대해서 문장으로 설명하기 힘들고. package 를 식료품점으로 user 를 쇼핑객으로 생각해보자. developer 는 스토어의 주인이자 관리자이다. 너의 임무는 고객에게 최상의 서비스 제공을 위해 매장을 구성하는 것이다. 너의 __init__.py 파일을 그 구성을 묘사한다. 아래 3가지 방식으로 설명하겠다.

  • 일반 상점
  • 편의점
  • 온라인 상점

일반 상점 (잡화점)

이 가정에서 user 는 모든 상품을 볼 수 있다. import example_pkg. user 의 코드는 package 이름 class, function 을 타이핑 하는 것만으로 모두 사용할 수 있다. 소스 코드에 뭐가 있든 상관 없이. 오래된 잡화점 같다. user 가 문에 들어서면, 매장 주변의 쓰레기통, 선반에 이것저것이 놓여 있다.

Behind the scenes

# __init__.py
from .foo import *
from .bar import *
from .baz import *

User implementation

import example_pkg
example_pkg.foo_func()
example_pkg.bar_func()
example_pkg.baz_func()
장점단점점
user는 모듈 이름을 알 필요 없다. 어떤 기능이 어떤 모듈에 있는지 기억 할 필요 없다. 패키지 이름과 함수 이름만 있으면 된다.
user 는 top-level 패키지가 import 되면 모든 펑션에 접근할 수 있다.
IDE tab 완성으로 example_pkg.<tab> 잡화점 주인장이 모두 알려주는 것과 같다.
새로운 기능이 modules 에 추가되어도 변경을 할 필요가 없다. 자동으로 include 된다. 선반에 그냥 새로운 상품이 추가된다.
모든 함수와 class 이름이 고유해야 한다. 패키지가 크면, 네임스페이스에 많은 것이 추가되고 느려질 수 있다.
일반 상점에는 개발 고객이 원치 않는 잡동사니가 많아 부담스러울 수 있다.
user 가 접근 못하게 하려면 __function_name 등의 노력을 해야 한다.
빗자루나 대걸레 처럼 큰 것은 숨기기 어렵다.
user가 빗자루를 집거나 바닥을 쓸 가능성이 없더라도 developer 는 사용자가 건들지 않기를 원하기에 어떻게든 추가 조치를 해야만 한다.

Recommendations / 언제 쓰는가

  • user의 작업 흐름 예측이 어려울 때, pandas 나 numpy 같이 util 성으로 이것저것 호출될 때. 잡화접의 일반적인 사항
  • user가 여러 모듈 사이를 자주 이동할 때
  • 함수 및 클래스 이름이 매우 설명적이고 기억하기 쉽고 모듈 이름을 지정해도 가독성이 향항되지 않을 때, developer 의 제품이 과일 야채와 같이 친숙한 것이라면 name tag 가 필요 없다.
  • 적은 모듈과 함께 사용, 모듈이 너무 많으면 사용자가 문서에서 원하는 기능을 찾기 어려울 것이다. 너무 커지지 않을 때는 사용할 수 있다. (일본의 돈키호테!)
  • objects 를 자주 추가하거나 제거할 때, 고객을 방해하지 않고 쉽게 추가하고 제거할 수 있다.

편의점

편의점은 일반 잡화점의 변형이다. from .module import * 대신에 from .module import func 방식을 사용한다.

편의점은 잡화점과 많은 특징이 공유된다. 상대적으로 제한된 제품들이 진열됨. 잘 진열되어 있어 상품명 표시를 보지 않도로 한눈에 찾을 수 있다. 잘 정리되어 있고 빈상자가 빗자루는 잘 치워져 있고 판매 제품만 선반에 있다.

Behind the scenes

# __init__.py
from .foo import foo_func
from .bar import bar_func
from .baz import baz_func

User implementation

import example_pkg
example_pkg.foo_func()
example_pkg.bar_func()
example_pkg.baz_func()
장점단점
모든 장점은 잡화점의 것과 같으며 추가로
사용자가 사용할 수 있는 개체를 제어하기가 쉽다.
__init__.py 은 기능이 많은 모듈이 많아지면 복잡해진다. 일반상점과 마찬가지로 어수선한 편의점은 잡화점과 같다.
새로운 기능이 추가되면 __init__.py 에도 명시적으로 추가해야 한다.
최신 IDE 는 누락된 import 감지에 도움이 되지만 잊기 쉽다. 편의점에도 최소한의 상품표가 있다. 선반의 항목을 변경할 때 상품표도 변경해야만 한다.

Recommendations / 언제 쓰는가

  • 모듈이 어느정도 단일 클래스로 구성되어 있을 때 (ex. geopandas.geodataframe import GeoDataFrame)
  • 가벼올 개체 수가 적을 때
  • 사용 개체게 명확한 이름이 있을 때
  • 사용자에게 필요한 개체와 필요하지 않은 개체를 명확히 알 때
  • 새로운 모듈추 추가 되지 않을 것으로 예상 될 때

온라인 잡화점

온라인으로 식료품을 구매한 사람이라면, 누구나 올바른 제품을 주문하는데 고객의 노력이 필요하다는 것을 알고 있다. 사용자는 제품을 검색하고, 브랜드와 원하는 사이즈를 선택해야만 한다. 그러나 이런 과정으로 무제한의 재고실에서 원하는 젶무을 정확하게 구할 수 있다.

파이썬 package 에서, 전체 패키지를 가져오는 편리함을 피하고 어떤 부분이 import 되 있는지 사용자에게 명확하게 하는 것이 현명할 수 있다. 이를 통해 developer 는 user 에게 부담을 주지 않으면서 패키지에 더 많은 부분을 포함할 수 있다.

Behind the scenes

# __init__.py
import example_pkg.foo
import example_pkg.bar
import example_pkg.baz

User implementation

import example_pkg

example_pkg.foo.foo_func()
example_pkg.bar.bar_func()
example_pkg.bar.baz_func()

or 

from example_pkg import foo, bar, baz

foo.foo_func()
bar.bar_func()
baz.baz_func()

or

import example_pkg.foo as ex_foo
import example_pkg.bar as ex_bar
import example_pkg.baz as ex_baz

ex_foo.foo_func()
ex_bar.bar_func()
ex_baz.baz_func()
장점단점
__init__.py 가 단순화된다. 새 모듈이 추가될 때만 업데이트 하면 된다. 온라인 상점 업데이트는 쉽다. 제품 데이터베이스 설정만 변경한다.
flexible 하다. 사용자가 필요한 것만 가져오거나 모든것을 가져오는데 사용할 수 있다. 온라인 상점의 고객은 자신이 원하거나 필요한 것만 검색이 가능하다. 사과가 필요하면 과일상자를 뒤질 필요 없고, 과일상자의 모든것이 필요하면 그것도 가능하다.
앨리어싱으로 긴 package.module 을 정리할 수 있다. 온라인 상점은 복잡하지만 나의 쇼핑 목록을 이용하면 빠른 쇼핑이 가능하다
동일한 이름의 여러 객체를 다룰 수 있다.
foo.foo_func() 는 foo가 어느패키지에서 왔는지 나타나지 않는다.
alias 없이는 매우 긴 코드 덩어리가 만들어 질 수 있음. example_pkg.foo.foo_func()
사용자가 사용가능한 기능을 추적하기 어려움. 온라인 식료품점은 가능한 모든 상품을 보기는 어려움



Recommendations / 언제 쓰는가

  • 복잡한 모듈에 대부분의 기능이 사용자에게 필요하지 않을 때
  • import example_pkg 가 많은 객체를 가져올 때
  • 다양한 종류의 사용자를 위해 매우 명확한 워크플로를 정의할 수 있는 경우에
  • 사용자가 문서를 탐색할 수 있을 것으로 예상할 때 
  • ex) mataploglib, bokeh, scipy

비교

잡화점편의점온라인 잡화
# __init__.py from .foo import * from .bar import * from .baz import *# __init__.py
from .foo import foo_func
from .bar import bar_func
from .baz import baz_func
# __init__.py
import example_pkg.foo
import example_pkg.bar
import example_pkg.baz
import example_pkg example_pkg.foo_func() example_pkg.bar_func() example_pkg.baz_func()import example_pkg example_pkg.foo_func() example_pkg.bar_func() example_pkg.baz_func()import example_pkgexample_pkg.foo.foo_func()
example_pkg.bar.bar_func()
example_pkg.bar.baz_func()

or

from example_pkg import foo, bar, bazfoo.foo_func()
bar.bar_func()
baz.baz_func()

or

import example_pkg.foo as ex_foo
import example_pkg.bar as ex_bar
import example_pkg.baz as ex_bazex_foo.foo_func()
ex_bar.bar_func()
ex_baz.baz_func()