ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [공부] 파이썬 코딩의 기술 책 정리 #1
    파이썬/책 정리 2021. 3. 22. 23:25

    파이썬답게 생각하기

    Pythonic

    • 파이썬 커뮤니티 유저들은 특정한 스타일을 따르면 코드를 말하기 위해 '파이썬답다'라는 형용사를 사용한다.
    • 엄격하게 통제하며 사용하라고 강요하는 스타일은 아니지만 협업하는 과정에서 자연스럽게 생겨난 스타일이다.
    • 파이썬 프로그래머는 명시적인 것을 좋아하고, 단순하며 가독성을 최대한 높이려고 노력한다.
    • 퍄이썬에서 가장 일반적인 작업을 수행하는 "파이썬다운"방식을 알아야 한다. 이것이 모든 파이썬 프로그래밍에 영향을 미칠 것이다.

    1. 사용 중인 파이썬의 버전을 알아두라

    파이썬 버전을 정확히 아는 방법

    $ python --version

    파이썬3은 보통 python3이라는 이름을 사용한다.

    $ python3 --version

    현재 실행중인 파이썬 버전을 아는 방법은 sys 내장 모듈의 값을 출력하면 알 수 있다.

    import sys
    print(sys.version_info)
    print(sys.version)
    • 파이썬3은 파이썬의 핵심 개발자들이 활발하게 개발하며 끊임없이 개선하고 있다.
    • 널리 쓰이는 대부분의 오픈 소스 라이브러리는 파이썬3와 호환되고 초첨을 맞춘다.
    • 모든 파이썬 프로젝트에서 파이썬3를 사용하는 것을 강력히 추천한다.

    정리

    • 파이썬3는 파이썬 최신 버전으로써 가장 잘 지원되고 있다. 따라서 프로젝트에서는 파이썬3를 사용해야 한다.
    • 시스템에 있는 파이썬 실행 파일이 사용자가 원하는 버전인지 확인해야 한다.
    • 파이썬2는 사용하면 안된다. 2020년 1월 1일부터 파이썬2는 더 이상 지원되지 않는다.

    2. PEP8 스타일 가이드를 따르라

    • 파이썬 개선 제안(Python Enhancement Proposal) #8, 또는 PEP8은 파이썬 코드를 어떤 형식으로 작성할지 알려주는 스타일 가이드이다.
    • 일관된 스타일을 적용하면 코드에 더 친숙하게 접근할 수 있고, 코드를 더 쉽게 읽을 수 있다.
    • 다른 파이썬 개발자들과 프로젝트를 수행할 때 더 쉽게 협력할 수 있다.
    • 또한, 유지보수도 쉬우며 쉬운 실수도 피할 수 있다.

    공백

    파이썬에서 공백은 중요한 의미가 있다. 파이썬 개발자들은 코드의 의미를 명확히 하는데에 있어 공백이 미치는 영향에 특히 민감하다.

    • 탭 대신 스페이스를 사용해 들여쓰기한다.
    • 문법적으로 중요한 들여쓰기에는 4칸 스페이스를 사용한다.
    • 라인 길이는 79개 문자 이햐여야한다.
    • 긴 식을 다음 줄에 이어서 쓸 경우에는 일반적인 들여쓰기보다 4 스페이스를 더 들여쓴다.
    • 파일 안에서 각 함수와 클래스 사이에는 빈 줄을 두 줄 넣는다.
    • 클래스 안에서 메서드와 메서드사이에는 빈 줄을 한 줄 넣는다.
    • 딕셔너리에서 키와 콜론(:) 사이에는 공백을 넣지 않고, 한 줄 안에 키와 값을 같이 넣는 경우에는 콜론 다음에 스페이스를 하나 넣는다.
    • 변수 대입에서 = 전후에는 스페이스를 하나씩만 넣는다.
    • 타입 표기를 덧붙이는 경우에는 변수 이름과 콜론 사이에 공백을 넣지 않도록 주의하고, 콜론과 타입 정보 사이에는 스페이스를 넣지 않고, 콜론과 타입 정보 사이에는 스베이스를 하나 넣어라.

    명명 규약

    PEP8은 파이썬 언어의 여러 부분에 사용하는 이름에 대한 고유 스타일을 제공한다.

    • 함수, 변수, 애트리뷰트는 lowercase_unserscore 처럼 소문자와 밑줄을 사용한다. (Snake case)
    • 보호돼야 하는 인스턴스 애트리뷰트는 일반적인 애트리뷰트 이름 규칙을 따르되, _leading_underscore처럼 밑줄로 시작한다.
    • 비공개(한 클래스 안에서만 쓰이고 다른 곳에서는 쓰면 안되는 경우) 인스턴스 애트리뷰트는 일반적인 애트리뷰트 이름 규칙을 따르되, __leading_usderscore처럼 밑줄 두 개로 시작한다.
    • 클래스(예외도 포함한다)는 CapitalizeWord처럼 여러 단어를 이어 붙이고 각 단어의 첫 글자를 대문자로 만든다.(Camel case)
    • 모듈 수준의 상수는 ALL_CAP처럼 모든 글자를 대문자로하고 단어와 단어 사이를 밑줄로 연결한다.
    • 클래스에 들어 있는 인스턴스 메서드는 호출 대상 객체를 가리키는 첫 번째 인자의 이름으로 반드시 self를 사용한다.
    • 클래스 메서드는 클래스를 카리키는 첫 번째 인자의 이름으로 반드시 cls를 사용해야 한다.

    식과 문

    '파이썬의 선'에서는 '문제를 해결할 명백한 방법이 하나 있고, 가급적이면 유일해야 한다'고 언급한다. PEP 8은 이런 가르침을 따라 식과 문장을 작성한다.

    • 긍정적인 식을 부정하지 말고(if not a is b) 부정을 내부에 넣는다(if a is not b).
    • 빈 컨테이너나 시퀀스([], '')를 검사할 때는 길이로 비교하지 않는다. 대신 컨테이너가 비어있지 않은 경우에 True로 평가된다는 사실을 활용한다.
    • 한 줄짜리 if 문이나 한 줄짜리 for, while 루프, 한 줄짜리 except 복합문을 사용하지 않는다. 명확성을 위해 각 부분을 여러 줄에 나눠 배치한다.
    • 식이 길어질 경우, 식을 괄호로 둘러싸고 줄바꿈과 들여쓰기를 추가해서 읽기 쉽게 만든다.
    • 여러 줄에 걸쳐 식을 쓸 떄는 줄이 계속된다는 표시를 하는 \ 문자보다는 괄호를 사용한다.

    임포트

    PEP 8은 모듈을 임포트해 코드에 사용하는 방법도 제시한다.

    • import 문(from x import y도 포함)을 항상 파일 맨 앞에 위치시킨다.
    • 모듈을 임포트할 때는 절대적인 이름을 사용하고, 현 모듈의 경로에 상대적인 이름은 사용하지 않는다.
    • 반드시 상대적인 경로로 임포트 해야하는 경우에는 from . import foo처럼 명시적인 구문을 사용한다.
    • 임포트를 적을 때는 표준 라이브러리 모듈, 서드 파티 모듈, 자제 제작 모듈 순서로 섹션을 나눈다. 각 섹션에서는 알파벳 순서로 모듈을 임포트한다.

    정리

    • 파이썬 코드를 작성할 대는 항상 PEP 8을 따른다.
    • 큰 파이썬 커뮤니티와 공통된 스타일을 공유하면 다른 사람과 협력할 때 도움이 된다.
    • 일관성 있는 스타일을 사용하면 나중에 유지보수가 수월하다.

    3. byte와 str의 차이를 알아두라

    파이썬에는 문자열 데이터의 시퀀스를 표현하는 두 가지 타입이 있다. bytesstrdlek. 아래의 코드처럼 bytes 타입의 인스턴스에는 부호가 없는 8 바이트 데이터가 그대로 들어간다.

    a = b'h\x65llo'
    print(list(a))
    print(a)
    
    """
    결과:
    [104, 101, 108, 108, 111]
    b'hello'
    """

    str 인스턴스에는 사람이 사용하는 언어의 문자를 표현하는 유니코드 코드 포인트 가 들어있다.

    a = 'a\u0300 propos'
    print(list(a))
    print(a)
    """
    결과:
    ['a'. '`', ' ', 'p', 'r', 'o', 'p', 'o', 's']
    à propos

    중요한 사실은 str 인스턴스에는 직접 대응하는 이진 인코딩이 없고 bytes에는 직접 대응하는 텍스트 인코딩이 없다는 점이다. 인코딩 또는 디코딩을 하려면 str의 encode 메서드를 호출하고 이 byets의 decode 메서드를 호출한다. 일반적으로 UTF-8이 시스템 디폴트 인코딩이다.

    유니코드 샌드위치: 유니코드 데이터를 인코딩하거나 디코딩하는 부분을 인터페이스의 가장 먼 경계 지점에 위치시키는 방식

    문자를 표현하는 타입이 둘로 나뉘어 있기 때문에 파이썬 코드에서 두 가지 상황이 자주 발생한다.

    • UTF-8로 인코딩된 8비트 시퀀스를 그대로 사용하고 싶다.
    • 특정 인코딩을 지정하지 않은 유니코드 문자열을 사용하고 싶다.
      이 경우를 변환해주고 원하는 값과 일치하는지 확신하기 위해 두 가지 도우미 함수가 필요하다.

    도우미 함수1: bytes나 str 인스턴스를 항상 str로 반환

    def to_str(bytes_or_str):
        if isinstance(bytes_to_str, bytes):
            value = bytes_or_str.decode('utf-8')
        else:
            value = bytes_or_str
        return value
    print(repr(to_str(b'foo')))

    도우미 함수2: bytes나 str 인스턴스를 항상 bytes로 반환

    def to_byte(bytes_or_str):
        if isinstance(bytes_to_str, str):
            value = bytes_or_str.encode('utf-8')
        else:
            value = bytes_or_str
        return value
    print(repr(to_str(b'foo')))

    문제점

    • bytes와 str이 서로 호환되지 않기 때문에 어떤 타입인지 알고 있어야 한다.
    • 파일 핸들과 관련한 연산들이 디폴트로 유니코드 문자열을 요구하고 이진 바이트 문자열을 요구하지 않는다.

    정리

    • bytes에는 8비트 값의 시퀀스가 들어있고, str에는 유니코드 코드 포인트의 시퀀스가 들어 있다.
    • 처리할 입력이 원하는 문자 시퀀스인지 확실히 하려면 도우미 함수를 사용한다.
    • bytes와 str 인스턴스를 연산자에 섞어서 사용할 수 없다.
    • 이진 데이터를 파일에서 읽거나 쓰려면 'rb'나 'wb'로 파일은 연다.
    • 유니코드 데이터를 파일에서 읽거나 파일을 쓰고 싶을 때는 시스템 디폴트 인코딩에 주의한다. open에 ending 파라미터를 명시적으로 적용하는 것이 좋다.

    4. C 스타일 형식의 문자열을 str.format과 쓰기보다 는 f-문자열을 통한 인터폴레이션을 사용하라

    C 스타일 형식 문자열의 4가지 문제점

    • 형식화 식에서 오른쪽에 있는 tuple 내 데이터 값의 순서를 바꾸거나 값의 타입을 바꾸면 타입 변환이 불가능하므로 오류가 발생할 수 ㅇㅆ다.
    • 형식화를 하기 전에 값을 살짝 변경해야 한다면 식을 읽기가 매우 어려워 진다.
    • 형식화 문자열에서 같은 값을 여러번 사용하고 싶다면 같은 값을 여러번 반복해야 한다
    • 형식화 식에 딕셔너리를 사용하면 식이 길어지고 시각적으로 잡음이 많아진다.

    내장 함수 format과 str.format

    파이썬 3 부터는 %를 사용하는 대신 더 표현력이 좋은 고급 문자열 형식화 기능이 도입됐다.

    a = 1234.5678
    formatted = format(a, ',.2f')
    print(formatted)
    
    b = 'my string'
    formatted = format(b, '^20s')
    print('*',formatted,'*')
    """
    결과:
    1,234.57
    *      my string       *
    """

    위치 지정자 {}를 사용할 수 있다.

    key = 'my_var'
    value = 1.234
    formatted = '{} = {}'.format(key, value)
    print(formatted)
    """
    결과:
    my_var = 1.234
    """

    각 위치 지정자에는 콜론 뒤에 형식 지정자를 붙여 넣어 어떤 형식으로 변환할지 정할 수 있다.

    formatted = '{:<10} = {:.2f}'.format(key, value)
    """
    결과:
    my_var     = 1.23
    """

    인자의 순서를 표현하는 위치 인덱스를 전달할 수도 있다.

    formatted = '{1} = {0}'.format(key, value)
    print(formatted)
    """
    결과:
    1.234 = my_var
    """

    인터폴레이션을 통한 형식 문자열

    • 파이썬 3.6부터 인터폴레이션을 통한 형식 문자열이 도입됐다.
    • 형식 문자열 앞에 f 문자를 붙이면 된다.
    • 표현력을 극대화 하고 형식화 문자열의 4 번째 문제점을 없애준다.
    • 형식화 식 안에서 현재 파이썬 영역에서 사용할 수 있는 모든 이름을 자유롭게 참조할 수 있도록 허용함으로써 간결함을 제공한다.

    내장 미니 언어를 f-문자열에서도 사용할 수 있다.

    formatted = f'{key!r:<10} = {value:.2f}'
    print(formatted)
    """
    결과:
    'my_var'   = 1.23
    """

    정리

    • C 스타일 형식화 문자열은 여러 가지 단점과 번잡성이라는 문제가 있다.
    • str.format 메서드는 유용한 개념 몇 가지를 제공했지만, 그것을 제외하면 C 스타일 형식 문자열의 문제점을 가지고 있다.
    • f-문자열은 C 스타일 형식화 문자열의 가장 큰 문제점을 해결해준다.

    5. 복잡한 식을 쓰는 대신 도우미 함수를 작성하라

    파이썬은 문법이 간결하므로 상당한 로직이 들어가는 식도 한 줄로 매우 쉽게 작성할 수 있다.

    green_str = my_values.get('초록', [''])
    if green_str[0]:
        green = int(green_str[0])
    else:
        green = 0

    이 로직을 반복 적용하려면 꼭 도우미 함수를 작성해야 한다.

    def get_first_int(values, key, default=0):
        found = values.get(key, [''])
        if found[0]:
            return int(found[0])
        return default
    
    green = get_first_int(my_values, '초록')

    식이 복잡해지기 시작하면 바로 식을 더 작은 조각으로 나눠서 로직을 도우미 함수로 옮길지 고려해야 한다.

    '반복하지 말라(Don't Repeat Yourself)'는 뜻의 DRY 원칙을 따른다.

    정리

    • 파이썬 문법을 사용하면 아주 복잡하고 읽기 어려운 한 줄짜리 식을 쉽게 작성할 수 있다.
    • 복잡한 식을 도우미 함수로 옮긴다. 특히 같은 로직을 반복해 사용할 때에는 꼭 사용한다.
    • 불 연산자 or나 and를 식에 사용하는 것보다 if/else 식을 쓰는 편이 더 가독성에 좋다.

    6. 인덱스를 사용하는 대신 대입을 사용해 데이터를 언패킹하라

    파이썬에는 값으로 이뤄진 불편(Immutable)순서쌍을 만들어낼 수 있는 tuple 내장 타입이 있다. 가장 짧은 튜플은 딕셔너리 키-값 쌍과 비슷하게 두 값으로 이루어진다.

    snack_calories = {
        '감자칩': 140,
        '팝콘': 80,
        '땅콩': 190,
    }
    items = tuple(snack_calories.items())
    print(items)
    """
    결과:
    (('감자칩', 140), ('팝콘', 80), ('땅콩', 190))
    """

    일단 튜플이 만들어지면, 인덱스를 통해 새 값을 대입해서 튜플을 변경할 수 없다.

    파이썬에서는 언패킹 구문이 있다.
    인덱스를 사용해 각 값에 접근하는 대신 이 튜플을 두 변수 이름으로 이뤄진 튜플에 대입할 수 있다.

    item = ('호박엿', '식혜')
    first, second = item

    언패킹은 시각적인 잡음이 적다.

    정리

    • 파이썬은 한 문장 안에서 여러 값을 대입할 수 있는 언패킹이라는 특별한 문법을 제공한다.
    • 파이썬 언패킹은 일반화돼 있으므로 모든 이터러블에 적용할 수 있다. 그리고 이터러블이 여러 계층으로 내포된 경우에도 언패킹을 적용할 수 있다.
    • 인덱스를 사용해 시퀀스 내부에 접근하는 대신 언패킹을 사용해 시각적인 잡음을 줄이고 코드를 더 명확하게 만든다.

    7. range보다는 enumerate를 사용하라

    range 내장 함수는 어떤 정수 집합을 이터레이션하는 루프가 필요할 떄 유용하다.

    enumerate는 이터레이터를 지연 게산 제너레이터로 감싼다. enumerate는 루프 인덱스와 이터레이터의 다음 값으로 이뤄진 쌍을 넘겨준다(yield). 다음 코드는 next 내장 함수를 사용해 다음 원소를 가져온다. 이로부터 enumerate가 반환한 이터레이터가 어떻게 동작하는지 볼 수 있다.

    flavor_list = ['바닐라', '딸기', '초콜릿', '바나나']
    it = enumerate(flavor_list)
    next(it) # (0, '바닐라')
    next(it) # (1, '딸기')

    정리

    • enumerate를 사용하면 이터레이터에 대해 루프를 돌면서 이터레이터에서 가져오는 원소의 인덱스 까지 얻는 코드를 간결하게 작성할 수 있다.
    • range에 대해 루프를 돌면서 시퀀스의 원소를 인덱스로 가져오기보다는 enumerate를 사용한다.
    • enumerate의 두 번째 파라미터로 어디부터 원소를 가져오기 시작할지 지정할 수 있다.

    8. 여러 이터레이터에 대해 나란히 루프를 수행하려면 zip을 사용하라

    names = ["유재석", "남궁민수", "이훈"]
    counts = [len(x) for x in names]
    
    for name, count in zip(names, counts):
        if count > max_count:
            longest_name = name
            max_count = count
    
    names.append('Rosalind')
    for name, count in zip(names, counts):
        print(name)
    """
    결과:
    유재석
    남궁민수
    이훈
    """

    zip은 자신이 감싼 이터레이터 중 어느 하나가 끝날 때까지 튜플을 내놓는다.

    itertools에있는 zip_longest를 사용하면 존재하지 않는 값은 자신에게 전달된 fillvalue로 대신한다. 디폴트는 None이다.

    from itertools import zip_longest
    
    
    for name, count in zip_longest(names, counts):
        print(f"{name},{count}")
    """
    결과:
    유재석,3
    남궁민수,4
    이훈,2
    Rosalind,None
    """

    정리

    • zip 내장 함수를 사용해 여러 이터레이터를 나란히 이터레이션할 수 있다.
    • zip은 튜플을 지연 계산하는 제너레이터를 만든다. 따라서 무한히 긴 입력에서 zip을 쓸 수 있다.
    • 입력 이터레이터의 길이가 서로 다르면 zip은 아무런 경고도 없이 가장 짧은 이터레이터 길이까지만 튜플을 내놓고 더 긴 이터레이터의 나머지 원소를 무시한다.
    • 길이가 다른 이터레이터에 대해 루프를 수향하려면 itertools 내장 모듈의 zip_longest 함수를 사용한다.

    9. for나 while 루프 뒤에 else 블록을 사용하지 말라

    for i in range(3):
        print(i)
    else:
        print('else block!')

    else 블록은 루프가 끝나자마자 실행된다. 이 블록은 '처리할 에외가 없는 경우에 이 블록을 실행한다'는 뜻이다.

    그러나, 실제로 루프안에서 break문을 사용하면 else 블록이 실행되지 않는다.

    for i in range(3):
        print(i)
        if i == 1:
            break
    else:
        print('else block!')

    또 놀라운 점은 빈 시퀀스에 대한 루프를 실행하면 else 블록이 바로 시행된다

    for i in []:
        print('이 줄은 실행되지 않는다')
    else:
        print('else block!')

    while 루프의 조건이 처음부터 False인 경우에도 else 블록이 실행된다

    while False:
        print('이 줄은 실행되지 않는다')
    else:
        print('else block!')

    이런식으로 의미가 명확하지 않고 코드를 이해하려는 사람들이 느끼게될 부담이 더 크다. 따라서 절대로 루프 뒤에 else블록을 사용하지 말아야한다.

    정리

    • for나 while 루프에 속한 블록 바로 뒤에 else 블록에 허용하는 특별한 문법을 제공한다.
    • 루프 뒤에 오는 else블록은 반복되는 도중 break를 만나지 않은 경우에만 실행된다.
    • 혼동을 야기할 수 있으므로 절대로 사용하지 않는다.

    10. 대입식을 사용해 반복을 피하라

    대입식은 영어로 assignment expression이며 왈러스 연산자 라고도 부른다.

    if count:= fresh_fruit.get('레몬', 0):
        make_lemonade(count)
    else:
        out_of_stock()

    if문의 첫 번쨰 블록에서만 의미가 있다는 점이 명확히 보이기 때문에 가독성이 더 좋다.

    if 문에서 대입 결과와 비교하기 위해서는 대입식을 괄호로 둘러싸야 한다.

    if (count:= fresh_fruit.get('레몬', 0)) >= 4:
        make_cider(count)
    else:
        out_of_stock()

    정리

    • 대입식에서는 왈러스 연산자(:=)를 사용해 하나의 식 안에서 변수 이름에 값을 대입하면서 이 값을 평가할 수 있고, 중복을 줄일 수 있다.
    • 대입식이 더 큰 식의 일부분을 쓰일 때는 괄호로 둘러싸야 한다.
    • 파이썬에서는 switch/case 문이나 do/while 루프를 쓸 수 없지만, 대입식을 사용하면 이런 기능을 더 깔끔하게 흉내 낼 수 있다.

    댓글