사용자 도구

사이트 도구


ps:스타일_가이드

코드 작성 가이드

기본 가이드

    • 두 규칙이 충돌나는 부분이 있다면 Google Python Style Guide를 따른다.
  • Google Python Style Guide 에 따라서, YAPF를 이용해서 포매팅을 시키고, pylint로 체크한다.
    • yapf는 내장된 구글 스타일을 지정해서 사용된다. (–style google 옵션을 붙여서 사용한다)
      • yapf는 assignment operator을 지원하지 않는다.. 이부분은 수동으로 처리하자
      • 포맷팅이 마음에 안드는 부분들도 있긴 한데.. 그냥 시키는 대로 하자. 괜히 사소한 것에 집착해서 바꾸려고 시간을 쓰는것보다는 좀 불편해도 참는게 시간을 아끼는 길이다..(?)
        • 예를 들면, inner function을 정의할때, 그 위에 blank line을 한줄 추가한다. 불필요해보이고 거슬리지만 그냥 두도록 하겠다
    • Google Python Style Guide에는 커스텀 버전 pylintrc 파일을 링크해두고 이것을 사용하라고 하는데, Style guide의 본문과 다르게 인덴트가 2-space로 지정되어 있다 (-_-;). Google의 내부 코딩 스타일 가이드와, 외부 공개용 스타일 가이드와의 차이때문에 꼬인것 같은데.. 외부용 가이드를 따르기로 했으니까.. pylintrc를 4-space로 고쳐서 사용한다

버전

  • Python 3.8에서 지원되는 문법을 사용한다. (대표적으로 assignment expression)

제출 언어 - Python or PyPy

  • [장점]
    • 일반적으로 PyPy으로 제출하면 Python보다 빠른 수행시간을 얻을수 있다. Python으로 시간초과가 나는 코드도 PyPy로 제출하여 통과될 수 있다.
  • [단점]
    • PyPy보다는 Python으로 제출된 코드가 훨씬 더 많은데, PyPy로 제출하면 다른 코드들과의 시간 비교가 어렵다.
    • PyPy에서 지원하는 Python 버전은 최신 Python 버전보다 살짝 낮다.
  • [결정]
    • 기본적으로는 Python3으로 제출한다. Python으로 시간초과가 나는 경우에만 PyPy3으로 제출한다.

입출력 최적화

  • 기본적으로는 input()과 print() 을 쓴다.
  • 입력이 크면 (대략 1000줄 이상?) 이면 유의미한 속도 저하가 발생하므로 sys.stdin.readline()를 쓴다
    • input = sys.stdin.readline 이런식의 핵은 사용하지 않고, 그냥 우직하게 sys.stdin.readline()을 다 쓴다..
    • 줄수가 많은 것이 아니라, 한줄에 많은 글자가 있는 경우는 input()를 써도 느리지 않다.
  • 리스트에 담긴 내용을 출력해야 하는 경우 print(*l, sep='\n') 으로 쓰자
    • print('\n'.join(str(x) for x in l))가 좀더 속도가 빠르게 나오기는 하던데.. 위에가 짧고 간단하다
    • 만약 l에 담겨 있는 것이 스트링이라면, 그때는 print('\n'.join(l)) 을 쓴다. 충분히 짧으면서 속도가 유의미하게 빠르다
    • 테스트. BOJ 15649번 기준 (아웃풋 약 4만개)
      • 176ms - print(*p), (p가 int 튜플)
      • 152ms - print(*p), (p가 스트링 튜플)
      • 160ms - print(' '.join([str(x) for x in p])) (p가 int 튜플)
      • 92ms - print(' '.join(p)) (p가 스트링 튜플)
  • sys.stdout.write는 print와 속도 차이가 없는 듯 하니 신경쓰지 말자.

DocString

  • 일정한 포맷으로 모든 코드에 붙인다.
  • 이런 식으로 쓴다
    • """Solution code for "BOJ 1932. 정수 삼각형".
      
      - Problem link: https://www.acmicpc.net/problem/1932
      - Solution link: http://www.teferi.net/ps/problems/boj/1932
      """

Assignment Expression

  • 너무 편리하다. 사용하자
  • linter에서 아직 지원을 안하고 있는게 불편한데. 그 정도는 충분히 감수.
  • 가끔 BOJ에서 테스트용으로 Pypy로 제출해야 할 경우가 있는데. BOJ의 Pypy 버전은 Python 3.7.4에 대응되어서, Python 3.8부터 도입된 이 기능을 인식 못한다. 그래서 런타임 에러가 나므로 깜빡하지 말자

예외처리

  • 파이썬은 공식적으로 EAFP 스타일을 권장한다. 그것에 따르자
    • 나쁜 코드
      • if "key" in dict_:
            value += dict_["key"]
    • 좋은 코드
      • try:
            value += dict_["key"]
        except KeyError:
            pass

네이밍 가이드

일반적

  • “Number of foos” 를 표현하는 변수는 foo_count. num_foo 보다 오해의 소지가 적다. foo_cnt 는 단어의 중간 글자를 생략하지 말라는 Google style guide에 위배된다.
  • half-open interval 을 표현하는 변수 이름은 [beg, end) 로 통일한다. 다시 말해, 변수명이 beg, end로 되어 있다면 beg은 포함되고, end는 포함이 안된다는 의미이다.
  • ABC를 XYZ로 매핑하는 맵(딕셔너리)의 이름은 abc_to_xyz대신 xyz_by_abc를 사용하자. 변환하는 함수명도 convert_abc_to_xyz 보다는 create_xyz_from_abc 이런식으로.
  • 그래프에서 경로를 찾을 때, 시작 노드와 끝 노드는 source와 target으로 이름짓자.
    • 기각된 후보들: start/end, from/to, src/tgt, sink, dest, …
  • 재귀함수의 이름은 뒤에 _rec를 붙이도록 하자. 예) def dfs_rec()
  • 사용되지 않는 값을 저장하기 위해서 변수명으로 _ 를 사용한다

PS 특화

  • 문제를 풀기 위한 함수를 main 밖으로 빼야 할 경우, 그 함수의 이름은 간단히 solve()로 쓰자.
    • 입력 처리 로직과 풀이 로직을 분리할 때, 풀이 로직을 재귀함수로 짜야 할때 등등
  • 문제의 답을 저장하는 변수는 간단히 answer 로 쓰자.
    • 프로그래머스에서 기본적으로 쓰이는 변수명. 기각된 후보들: ans, result, res, …
  • 특정 자료구조를 가리키는 변수는 그냥 그 이름으로.
    • 그래프는 graph, 가중치그래프는 wgraph, 스택은 stack, 큐는 queue
  • dp 결과를 저장하는 배열이나 테이블 이름은 dp로 하자
    • 2차원 이상의 dp에서, 가장 최근의 행만 저장해도 충분한 경우가 있다. 예를 들어 dp[n][i] = f(dp[n-1][i], dp[n-1][i-1]) 이면 최근 두 행에 대한 배열 두개만 필요하다. 이럴때는 dp_cur, dp_prev로 한다.
  • 그래프에서는 대부분
    • for v in graph[u] 의 꼴로 변수명을 쓸것이다
    • 드물게 for successors in graph: 의 형태로 변수명을 쓸 때가 있다
  • 문제의 입력값을 저장할 때에는, 문제 설명에서 제시되는 변수명을 그대로 쓴다. 대문자인 경우에는 그냥 대문자로 쓴다.
    • 다른 스타일 가이드의 원칙과 위배되기는 한다..
    • 그러나 다른 의미있는 이름으로 변수명을 새로 만드는것 보다, 그냥 문제에 주어진 이름을 쓰는게 가장 직관적이고 이해하기 쉽다.
    • 대문자를 썼다고 상수로 오해받을 여지도 적다
  • 입력받는 값은 모두 변수에 저장한다. 풀이에서 실제로 사용되지 않는 값, 또는 따로 변수에 저장하지 않고 바로 처리가 가능한 값도, 그냥 우선 변수에 저장해놓고 안쓰든가 다른 처리를 하든가 한다. 그게 덜 헷갈린다.
    • 안쓰는 변수는 옆에 # pylint: disable=unused-variable 를 적어서 lint warning을 서프레스하자.

기타

  • LeetCode에서는 가이드에서와는 달리 메소드 이름으로 snake_case가 아닌 camelCase를 사용한다. LeetCode에 작성할 때는 일관성을 위해 새로운 메소드도 camelCase로 만든다.

스타일 가이드에 있지만 잘 까먹는 것들

  • import는 패키지와 모듈에만 사용. 클래스단위로 import하지 않음.

기타 코딩 스타일

  • 리스트에 += 보다 append와 extend를 사용 (약간 더 빠르다)
    • l += [x] 대신 l.append(x)
    • l += [x, y] 대신 l.extend([x,y])
  • 튜플 or 리스트를 소팅할때, n번째 원소를 기준으로 할때는, 키로 lambda가 아닌 operator.itemgetter을 쓰자.
    • 좀 더 명확하고 (이건 의견이 갈릴수도..) 약간 더 빠르다. 그리고 파이썬 공식 문서에서 추천하는 방법이다.
    • 비슷하게, namedTuple를 소팅할때는 operator.attrgetter를 사용
  • 타입 체킹은 isinstance(var, some_type) 으로 한다 type(var) is some_type 보다 약간 빠르고, 상속된 타입을 체크할 수 있는 장점이 있다.
  • 4-space 인덴트를 넣은 블럭이 윗줄과 구분이 잘 안될때, 인덴트를 추가한다
    • 이런상황이다
      • if (condition1 
            and condition2):
            statement
    • PEP8에서는 그냥 두기, 커멘트 넣기, 인덴트 추가하기 등의 해결책을 소개하지만 특정한 방식을 추천하지는 않는다
    • Flake8 은 이것을 anti-pattern으로 간주하고 에러를 낸다. 인덴트 추가하기를 best practice로 소개한다
    • yapf에서도 인덴트를 추가하는 식으로 처리한다.
  • 줄바꿈은 이항연산자 뒤에서 한다.
    • PEP8에서는 새로운 코드들은 이항연산자의 에서 줄바꿈하기를 권장한다.
    • 나도 이항연산자의 에서 줄바꿈하는 것이 더 보기 좋다는 데에 동의한다.
    • 하지만 YAPF는 이항연산자의 에서 줄바꿈을 강제한다. 그냥 그렇게 두자..
  • 함수가 예외적인 상황에 놓였을 때에는 (위상정렬을 해야 하는데 주어진 그래프에 사이클이 있다든가..) return None이 아니라, 예외를 명시적으로 raise하도록 하자
    • 예외를 따로 만들 필요는 없다. built-in중에서 선택하자. (아마도 대부분 ValueError)
  • 리스트에서 인접한 원소 두개씩을 갖고 뭘 해야 하는 경우
    • (1) for i in range(1, len(l)): x, y = l[i-1], l[i]
      • ⇒ 기본적이지만, 안 예쁘다
    • (2) for x, y in zip(l, l[1:]):
      • ⇒ 예쁘지만, 리스트를 복사해서 새로 만들어야 해서 비효율적
    • (3) iterable, copied = itertools.tee(lst); next(copied); for x, y in zip(iterable, copied):
      • ⇒ 가장 효율적이지만 길다.
        • python 3.10부터 추가되는 pairwise도 이런식으로 구현될듯
    • (4) prev = l[0]; [-prev + (prev := x) for x in l[1:]]
      • 신박한데.. 제약이 있어서 못쓸수도 있다.
    • =⇒ 상황에 따라서 (2)나 (3)을 섞어 쓰자. 물론 python 3.10이 나오면 pairwise로 갈아탄다.

기타 코딩 스타일 (PS 특화)

  • '무한대'는 float('inf')를 사용한다.
    • 상수로 정의해서 쓰자.
      • INF = float('inf')
    • 임의의 큰 값, 예를 들면 987654321이나 1e9 보다 의미가 분명하다.
    • math.inf도 동일하지만, math 모듈을 import 해야 하는 것이 불편하다.
  • 결과값을 얼마로 나눈 나머지를 출력하게 하는 문제에서의 '얼마'는 상수로 정의해서 쓴다.
    • MOD = 1_000_000_007
  • 필요한 경우 main()함수의 첫줄에 sys.setrecursionlimit(10**9) 를 적는다.
    • 기본값은 1000이므로, 재귀로 풀어야 하는 웬만한 문제에서는 저 값을 새로 세팅해야 한다. 문제마다 다른 값을 적용할 필요 없이 항상 일괄적으로 10**9로 적도록 하자.

Idiom

  • count_if : Iterable에서 condition을 만족하는 원소의 갯수
    • 가능한 후보들
      • 1) len([x for x in iterable if cond(x)])
      • 2) sum(1 for x in iterable if cond(x))
      • 3) sum(cond(x) for x in iterable)
    • 직관적인것은 1번>2번>3번 순이다. 하지만 1번은 리스트를 전부 만들어야 하니 효율성이 좀 떨어질것 같고. 2번이나 3번은 사실 둘다 비슷한데, 2번을 쓰는 코드를 더 많이 접했던 기억이고 더 익숙하다. 그리고 2번이 3번보다 좀더 빠르다
    • 그러므로 2번 방식으로 쓰자.
  • find_first : condition을 만족하는 첫번째 원소
    • next(x for x in iterable if cond(x))

토론

댓글을 입력하세요:
K Y V L Q
 
ps/스타일_가이드.txt · 마지막으로 수정됨: 2021/06/14 14:57 저자 teferi