python

Python 코딩 규약

devnote 2023. 6. 25. 16:05

Python 언어에서 가장 중요한 점?

  • 가독성!!이 제일 중요하다. 속도, 메모리 효율을 높이고 싶다면 다른 언어로 가는 것이 맞다.
  • 만들기 전에 검색하자. 우리가 생각하는 건 누군가 이미 만들었다.

기본 컨벤션

목표점

os.path 보다 pathlib 을 쓰자

No:
    path = root + f'/{company}/' + f'/{department}/'  + f'/{age}/' 
    path = os.path.join(root, company, department, str(age))
Yes:
    path = Path(root) / company / department / str(age)

open보다 pathlib의 read write를 쓰자

  • open을 사용하여 file descriptor를 종료 하기 애매할수 있는 문제가 없고 직관적이다
  • with를 사용해도 되지만 이 방법이 더 좋은 거 같음.
No:
    with open(fpath, 'r') as f:
        json_data = json.load(f)
    with open(fpath, 'w') as f:
        json.dump(json_data, f)
    with open(fpath, 'rb') as f:
        byte_data = f
Yes:
    json_data = json.loads(Path(fpath).read_text())
    Path(fpath).write_text(json.dumps(json_data))
    byte_data = Path(fpath).read_bytes()

format이나 %s보다 f"" f-string을 쓰자

No:
    test = "test"
    print("this is %s" % test)
    print("this is {}".format(test))

    import datetime
    date = datetime.datetime.now()
    '{} is on a {}'.format(date.strftime("%Y-%m-%d"), date.strftime("%A") )
     >>> '2019-05-11 is on a Saturday'

Yes:
    print(f"this is {test}")

    import datetime
    date = datetime.datetime.now()
    f'{date:%Y-%m-%d} is on a {date:%A}'
     >>> '2019-05-11 is on a Saturday'

print 보다 pprint를 쓰자 (복잡한 log는 pformat 쓰자)

No:
    data = ("test", [1, 2, 3,'test', 4, 5], "This is a string!",
            {'age':23, 'gender':'F'})
    print(data)
    ('test', [1, 2, 3, 'test', 4, 5], 'This is a string!', {'gender': 'F', 'age': 23})

    logger.info("{'gender': 'F', 'age': 23}")
Yes:
    import pprint
    data = ("test", [1, 2, 3,'test', 4, 5], "This is a string!",
            {'age':23, 'gender':'F'})
    pprint.pprint(data)
    ('test',
     [1, 2, 3, 'test', 4, 5],
     'This is a string!',
     {'age': 23, 'gender': 'F'})

    from pprint import pformat
    logger.info(pformat("{'gender': 'F', 'age': 23}"))

for loop 보다는 map, filter를 쓰자

  • 속도가 차이가 남.
No:
    def add_1(n):
          return n + 1

    target = [1, 2, 3, 4, 5]
    result = []
    for value in target:
         result.append(add_1(value))

    print(result) # 출력결과 : [2, 3, 4, 5, 6]

Yes:
    def add_1(n):
          return n + 1
    target = [1, 2, 3, 4, 5]
    result = map(add_1, target)
    print(list(result))

    target = [1, 2, 3, 4, 5]
    result = map(lambda x : x+1, target)
    print(list(result))

if elif else 보다는 return continue break를 사용하여 들여쓰기를 아끼자

  • tab이 한없이 길어 지는 걸 피하고 코드 흐림이 더 쉬워짐.
No:
    def test():
        do_something()
        if condition == True:
            aaa()
        else:
            bbb()

Yes:
    def test():
        do_something()
        if condition == True:
            aaa()
            return
        bbb()

예외 :
    def test():
        do_something()
        if condition == True:
            aaa()
        else:
            bbb()
        do_end_something()

getopt보다 argparse를 쓰자

No:
    import getopt
    opts, _ = getopt.getopt(sys.argv[1:], "p:o", ["input_path=", "output_path="])

    for opt, arg in opts:
        if opt == '-h':
            aicommon.Utils.usage()
            sys.exit()
        elif opt in ("-m", "--input_path"):
            input_path = arg
        elif opt in ("-t", "--output_path"):
            output_path = arg

Yes:
    import argparse
    parser = argparse.ArgumentParser(prog="path config",
                                        description="path config", add_help=True)
    parser.add_argument('-i', '--INPUT', help='input path.', required=True)
    parser.add_argument('-o', '--OUTPUTP', help='output path.', required=True)
    args = parser.parse_args()
    input_path = args.INPUT
    output_path = args.OUTPUTP

문자열이 개행이 되면 ", \ 보다 """를 쓰자

  • f-string과 함께 여러 행으로 나눠서 가독성을 높일 수 있음.
No:
    message = "company : {company} \n \
    department : {department}  \n \
    age : {age}  \n"

Yes:
    msg_str = """
    company : {company} 
    department : {department} 
    age : {age} 
    """

doc을 사용할 수 있도록 주석은 자세히 써 주자

  • 이후에 API 형태의 문서화를 할때 유리하고
  • 사용자에게 바로 어떤 일을 하는 함수 인지 jupyter나 ipython 콘솔에서 알려 줄 수 있어 직관적이다
class Path:
    def __init__(self):
    """
    ('PurePath subclass that can make system calls.'
    '    Path represents a filesystem path but unlike PurePath, also offers'
    '    methods to do system calls on path objects. Depending on your system,'
    '    instantiating a Path will return either a PosixPath or a WindowsPath'
    '    object. You can also instantiate a PosixPath or WindowsPath directly,'
    '    but cannot instantiate a WindowsPath on a POSIX system or vice versa.'
    '    ')
    """

In [5]: pprint.pprint(Path.__doc__)
('PurePath subclass that can make system calls.\n'
'\n'
'    Path represents a filesystem path but unlike PurePath, also offers\n'
'    methods to do system calls on path objects. Depending on your system,\n'
'    instantiating a Path will return either a PosixPath or a WindowsPath\n'
'    object. You can also instantiate a PosixPath or WindowsPath directly,\n'
'    but cannot instantiate a WindowsPath on a POSIX system or vice versa.\n'
'    ')

가능하면 입력 인자와 출력 인자의 type을 명시해 주자

  • python, java script의 경우 c++, java 와 같이 데이터 타입을 선언하는 부분이 없다
  • 직관적으로 입력 출력 되는 인자의 정보가 없고 주석이 없다면 더욱 알기가 힘들다
  • 입력이나 출력인자를 정해 놓으면 다른 타입이라도 python이 casting하려는 시도를 하게 되고 잘못된 사용을 할때 인터프리터가 알아 차릴 수 있다
    • str() 같은 operator가 동작하게 할수 있음.
No:
class Path:
    def __init__(self, path):
    ...
    def __div__(self, path): 
    ...

Yes:
class Path:
    def __init__(self, path:str):
    ...
    def __div__(self, path:Path)->Path: 
    ...