Python

파이썬 코딩 효율을 크게 높여주는 7가지 팁들

카루-R 2022. 2. 16. 01:43
반응형

환영합니다, Rolling Ress의 카루입니다.

오늘은 파이썬입니다. 사실 제가 파이썬과는 연이 그다지 깊지가 않아서... 파이썬이 Type Reflection이 되던가요? VS에서 C# 쓰다가 VS Code로 파이썬 쓰려니 IntelliSense가 정말 좋지 않아서 못 쓰겠더라고요. 그래도 뭐...프로그래밍 입문자에게 파이썬을 많이 추천하는 추세니 글을 써볼까 합니다.

초보자 분들부터 숙련자 분들에게까지 적용될 수 있는 팁을 두루두루 가져와봤어요.

아니다, 내용이 내용인지라 상당히 어려워요. 특히 마지막 switch 패턴매칭은 꽤 골치 아플 거예요. 그래도 중요하니까, 꼭 보고 가세요.

목차

1. read_csv()를 웹에서 바로

2. 변수 선언 시 타입 사용

3. 함수의 파라미터 타입 제한

4. 클래스와 self로 static 트릭 걸기

5. 열거형 (Enum) 사용하기

6. 상수 사용하기

7. switch 패턴매칭 분기문 사용하기


read_csv()를 웹에서 바로

# 함수 원형 (파라미터 생략)
read_csv(reader: FilePathOrBuffer) -> TextFileReader

import pandas as pd
data = pd.read_csv('인터넷 상 csv 파일의 주소')

pandas.read_csv() 함수는 파일을 인터넷에서 바로 긁어올 수 있습니다. 아마 이런 것들을 지원하는 게 몇 가지 더 있을 거예요. 굳이 csv파일 사용하려고 다운받고 폴더 내에 탑재하는 고생할 필요가 없습니다. 바로 긁어오세요.

변수 선언 시 타입 사용

karu_age: int = 19
pi: float = 3.14159265
cplx: complex = 2+6j # 파이썬, Go 에서는 허수부에 i 대신 j 사용

sentence: str = 'I love you to the moon and back.'
is_true: bool = True

subjects: list = ['언매', '미적분', '화학I', '스페인어I']
friends: tuple[str, str] = ('카루', '라에')
my_dict: dict = { 1: 2, 2: 4, 3: 6, 4: 8, 5: 684 }
blogs: set = { 'Naver', 'Tistory '}

제가 파이썬을 싫어하는 이유 중 하나인데, static type definition이 없다는 거였습니다. 근데 어느 순간부터 이게 점점 들어가더라고요? 그래서, '변수 이름: 타입 = 초깃값'의 형식으로 작성하면 변수에 형식을 지정할 수 있습니다. 참고로 파이썬 3.9 이상에서 작동합니다. 3.8 이하에서도 동작하게 하려면 list, tuple, dict 등은 추가로 모듈을 import 해야 합니다.

from typing import List, Tuple, Dict, Set

subjects: List = ['언매', '미적분', '화학I', '스페인어I']
friends: Tuple[str, str] = ('카루', '라에')
my_dict: Dict = { 1: 2, 2: 4, 3: 6, 4: 8, 5: 684 }
blogs: Set = { 'Naver', 'Tistory '}

다른 클래스를 사용하기 때문에, 대문자로 쓴다는 차이가 있어요. 아무튼, 파이썬에 익숙하신 분들이라면 오히려 이런 문법이 복잡하고 귀찮으실 겁니다. (물론 저같은 컴파일 언어를 주력으로 사용하신다면..좋으실..까요?) 이게 진짜 빛을 발하는 건, 함수의 매개변수로 사용했을 때입니다.

concat은 문자열을 연결해주는 함수입니다. 그런데, 지금 보면 문자열이 아니라 정수를 넣어도 그냥 더해버립니다. 정의하지 않은 동작이 내려지는 거죠. 이런 게 모여서 버그가 되는 겁니다. 해결법은 아래에서 함께 다루죠.

함수의 파라미터 타입 제한

def concat(str1, str2)  -> str:
    if not (isinstance(str1, str) and isinstance(str2, str)):
        raise TypeError('str1, str2: expected string type.')
    return str1 + str2

사실 기존에도 타입에 제한을 거는 건 쉬웠습니다. isinstance(변수, 타입) -> bool 함수를 통해 타입이 일치하는지 쉽게 불린 값으로 받을 수 있었죠. 특히, raise 구문과 함께 하여 오류 메시지를 정확히 전달하면 오류가 나도 문제를 쉽게 알 수 있습니다.

근데 이거의 문제는 뭘까요? 꼭 한 번 실행해보고, 예외가 터져야 알 수 있다는 거. 좀...너무 비효율적이지 않습니까? 그래서, Intellisense의 도움을 받아 코드 작성 시에도 알 수 있도록 해보죠.

def concat(str1: str, str2: str) -> str:

자, 여기 보시면 함수의 원형을 바꾸었습니다. str1, str2를 아까 위에서 본 대로 타입을 선언했습니다. 끝 부분의 -> str은 C++의 trailing return type이랑 비슷해보이죠. 함수의 반환값을 직관적으로 알 수 있게 해줍니다.

간혹 가다 후행 반환 형식을 사용한 경우 다음 줄의 인텔리센스가 먹히지 않을 수 있습니다. 무시하셔도 돼요. 버그입니다. 어쨌든 위와 같이 함수를 정의한 경우, 코드를 타이핑하는 순간 또는 마우스를 올렸을 때 함수의 파라미터와 반환 형식을 정확하게 알 수 있다는 장점이 있어요. 지금 위 사진 보시면 모두 타입이 명시적으로 드러나있죠. 다만 컴파일 언어들과는 다르게 파이썬은 단순히 타입을 지정하는 것만으로는 강제 효과가 없어요. 저렇게 써도 int형 변수 전달하면 잘 동작합니다. 그러니 함수 내부에 isinstance + raise 조합을 사용하는 게 정신 건강에 이롭습니다.

클래스와 self로 static 트릭 걸기

class rollingress:
    static_int: int = 10

    @staticmethod # 이건 원래 없어도 됩니다. 가독성 때문에 넣죠.
    def static_func():
        print('rollingress.static_func()')
    
    def method(self):
        print('rollingress.method()')
    
rr = rollingress()
rr.method()
# rr.static_func() # 오류 발생

rollingress.static_func()
print(rollingress.static_int) # 10 출력

파이썬은 단순합니다. 그 단순함이 때때로 프로그래머를 불편하게 할 때가 있습니다. 파이썬에서는 여타 주요 언어들에서 사용하는 this 포인터가 없죠. 그래서 그걸 직접 지정해줘야 합니다. 첫 번째 인자에 무언가를 넣으면 그게 해당 함수에서 사용하는 this 포인터가 됩니다. (이름은 관례적으로 self를 사용합니다. self: rollingress 처럼 사용해도 되는데, 너무 길어져서 여기서만큼은 타입을 안 써도 될 것 같아요)

rr.method()

인스턴스에 의존적인 호출은 반드시 해당 인스턴스가 위치한 메모리 주소가 필요합니다. 어떤 object가 메서드를 호출했는지 알아야 필드에서 작업을 처리할 거 아니에요? 그래서, 위를 보면 파라미터가 아예 없는 것 같아도 rr 그 자체가 들어갑니다.

rollingress.method(rr)

즉, 위 두 코드는 완전히 동일한 코드입니다.

def static_func():

그러니 위처럼 인자가 아무것도 없으면, 인스턴스에서 호출했을 때 받지 않는 값을 받으므로 Exception이 발생합니다. 호출하는 방법은 클래스에서 직접 호출하는 것(rollingress.static_func()). 그러니, self를 제거하면 자동으로 정적 함수가 됩니다. 보통은 저 위에 @staticmethod를 넣어 정적 메서드임을 명시적으로 표기해줍니다.

class rollingress:
    static_int: int = 10

그리고, 여기에서처럼 클래스에 바로 변수를 선언하면 static 변수가 됩니다. 필드로 만들고 싶은가요? 그럼 __init__(self)를 정의한 뒤 생성자 내에서 self.variable 이런 식으로 대입하면 됩니다. 그럼 그게 멤버 변수가 돼요. 물론, self가 붙어버리면 인스턴스를 통해 호출하는 것만 가능합니다.

파이썬에서 열거형(Enum) 사용하기

from enum import Enum, auto

class Weekday(Enum):
    MONDAY = auto()
    TUESDAY = auto()
    WEDNESDAY = auto()
    THIRSDAY = auto()
    FRIDAY = auto()
    
for day in Weekday:
    print(day) # Enum 순회
    
print(Weekday.TUESDAY.name) # 'TUESDAY' 출력, 타입은 str

이건 모듈을 따로 import 해줘야 합니다. Enum의 기본 원리는 클래스예요. Enum을 상속 받는 클래스는 열거형으로 사용할 수 있습니다. 그리고 위에서 보았듯이, 그냥 클래스 내부에 바로 변수(enum instance)를 선언하면 됩니다. Enum instance의 경우 대문자로 작성하는 게 관례입니다. 원래는 인덱스를 =1, =2 이렇게 붙여주지만, 그냥 = auto()를 사용하는 게 편해요. 이렇게 enum이 만들어지면, for문으로 순회할 수 있어요.또한, enum 인스턴스에서는 .name 필드를 통해서 string 이름에 접근할 수 있습니다.

파이썬에서 상수 사용하기

# 방법 1 (표시만, 실제 수정은 막지 못함)
from typing import Final
CONSTANT: Final = 1000

# 방법 2 (효과는 있지만 변수를 함수처럼 호출해야 함)
def get_const():
    return 1000

# 방법 3 (추가적인 .value가 붙음)
class RR(Enum):
    CONST = 1000

파이썬에서 제일 짜증나는 게 이거예요. 상수. 변하지 않는 수를 만드는 게 제일 어렵습니다. 이게 파이썬의 철학과도 연관이 있는데, 단순한 걸 좋아하죠. 그리고 프로그래머들이 알아서 잘 사용하겠거니 믿고 있어요. 그러니, 안전장치를 달아두지 않은 겁니다.

// C에서의 매크로 상수 (딱히 좋은 방법은 아니지만..)
#define CONSTANT 1000

// C++에서의 constexpr 상수
constexpr auto CONSTANT = 1000 // constexpr은 const 의미 포함

// C#에서의 const 상수
public const int CONSTANT = 1000 // const는 static readonly 포함

다른 언어들에서는 안전장치를 주거든요. 절대로 변경되면 안 되는 값들은 실수로라도 변경하지 말라고. 그런데 파이썬은 그것조차 없어요. 모든 방법을 편법으로 사용해야 하는데, 그마저도 제대로 동작하지 않습니다.

 
# 방법 1 (표시만, 실제 수정은 막지 못함)
from typing import Final
CONSTANT: Final = 1000

# 방법 2 (효과는 있지만 변수를 함수처럼 호출해야 함)
def get_const():
    return 1000

# 방법 3 (추가적인 .value가 붙음)
class RR(Enum):
    CONST = 1000

방법 1의 경우 typing 모듈에서 Final 클래스를 불러오는 겁니다. Final 타입으로 설정하고 인스턴스 이름을 대문자로만 설정해두면 인텔리센스가 '아 얘 상수구나' 하고 상수 취급을 해줘요. 그런데 편집기에서만 그러고, 실제로 작동은 멀쩡히 잘 됩니다.

이건 우리가 의도한 건 아니죠. 편집기에서만 빨간 줄 뜨면 뭐 합니까. 심지어 그냥 메모장이나 콘솔에서 하면 띄워주지도 않잖아요. 그래서, 저는 모양은 좀 깨부셔도 두 번째 방법을 애용합니다.

아예 그냥 상수를 반환하는 함수로 만드는 거죠. 이러면 해당 상수(?)를 사용할 때마다 ()연산자, 즉 function-call을 해줘야 한다는 단점이 생깁니다. 당연히 함수니까 re-assign이 안 되죠. 예쁘진 않지만 기능은 확실합니다.

함수 호출이 싫다면 Enum을 써도 되는데, 이건 더 복잡합니다. 그냥 보기만 하세요. Enum을 통해 상수 취급해줘도 되는데, 이때는 RR.CONST가 아니라 RR.CONST.value로 써야 합니다. '.value'를 안 쓰면 그냥 enum 이름이 나옵니다. 다만 RR.CONST든 RR.COSNT.value든 재할당을 하려고 하면 AttributeError가 뜹니다. 각자 취향 따라 선택하면 될 것 같아요.

파이썬에서 switch 패턴매칭 분기문 사용하기

이건 실제로 제가 2학년 '창의적문제해결기법' 시간에 과제로 제출했던 코드 중 일부입니다. 전체 코드는 nsun527/Updown (github.com) 에서 확인할 수 있어요. 지금 무슨 코드인지 바로 감이 안 오실텐데, 룩업테이블을 구현한 겁니다. 딕셔너리에 인덱스를 매기고 할당할 값들을 튜플로 넣어놨어요. 그 다음, get 메서드를 통해 인덱스에 맞는 값들을 unpack하는 겁니다. 이때 diff_int의 값이 인덱스에 해당하지 않는다면 default 값으로 두 번째 파라미터를 대입하는 겁니다.

여하튼, 다른 언어로 바꾸면 이런 느낌입니다.

// C++
switch (diff) {
case 1:
    tie(min, max, max_count) = tuple{1, 50, INT_MAX};
    break;
case 2:
    tie(min, max, max_count) = tuple{1, 100, 50};
    break;
case 3:
    tie(min, max, max_count) = tuple{1, 500, 20};
    break;
default:
    tie(min, max, max_count) = tuple{1, 100, 50};
    break;
}

// C# 8.0 or upper
(min, max, this.max_count) = diff switch {
    1 => (1, 50, int.MaxValue),
    3 => (1, 500, 20),
    2 or _ => (1, 100, 50)
}

오히려 C# 이 파이썬보다 더 간결하다는 느낌이 들어요. Life is short, you need python이란 말이 무색해지죠. 그런데 파이썬에서도 이제 match ~ case 문으로 switch를 지원하기 시작했습니다. 언제부터요? 파이썬 3.10부터요. 아직 보급이 많이 안 되어서, 지금으로선 미래 얘기입니다.

match diff:
    case 1: (min, max, max_count) = (1, 50, max)
    case 3: (min, max, max_count) = (1, 500, 20)
    case 2 | _: (min, max, max_count) = (1, 100, 50)

파이썬 답지 않게 or 대신 | 연산자를 사용합니다. 이 외에도 상당히 강력한 패턴매칭 기능을 제공합니다. 이왕 늦게 넣는 거, 좀 제대로 하자는 것 같네요. C# 7.1 이상을 아시는 분들에겐 그렇게 어렵지 않을 겁니다.

case a | b: # a or b

# 클래스 패턴매칭: a 타입 인스턴스 중 b 필드가 0인 것
case a(b=0):

# list 패턴매칭 부분 특수화: 첫째 요소가 '1'인 것만
case ['1', b]: 

# C#의 switch~case~when과 비슷함
case [a, b] if not a = 100

# a 자리에 (b|c|d)등 서브 패턴을 넣는 것도 가능.
# list 패턴매칭 - 원소 두 개
case [a, b]: 

# list 패턴매칭 - 원소 두 개 이상: b는 for로 순회 가능
case [a, *b]: 
case _: # 이도저도 안 되면 default

ㅋㅋㅋㅋㅋ이거 처음 보시면 아마...'뭔소리지?' 같은 반응이 나올 거예요. 정상입니다.

패턴매칭은 쉽게 말해서 "이 패턴이 맞으면 실행하고, 아니면 다음 패턴을 대조한다" 정도로 이해하시면 돼요. 정규표현식을 안다면 조금 쉽게 접근할 수 있습니다. 체로 걸러내는 느낌이라고 할까요. 우선순위는 위에서 아래로 내려갑니다. [ list ]는 자유롭게 사용이 가능해요. 특히 리스트 내부에 ( 이런 | 식으로 ) 인스턴스를 넣어 서브패턴을 만드는 것도 가능합니다. 예시를 들어볼게요.

# case ['1', b] 및 case [a, b] if not a = 100 해당
['1', 100], ['1', '2'], ['1', 'asdf'], ....

# case [a, b] 해당: 원소가 두 개인 모든 리스트
[1, 2], ['1', '2'], ['qwerty', 'asdfg'], ...

# case [(1|2|3), a, b] 해당: 첫째 원소가 1, 2, 3중에 하나인 원소 세 개 리스트
[1, 2, 3], [2, '2', 4], [3, 4, '5'], [2, 'asf', 'qwe'], [1, 1, 1], ...

이런 식입니다. 패턴매칭에 대한 글은 나중에 따로 쓰겠습니다. 파이썬에만 한정되는 내용은 아니라서.


자.. 뭐가 많았네요. 혹시 궁금한 점 있으면 언제든 댓글 달아주세요.

다음에는 아마 다시 딥러닝 글로 찾아뵐 것 같습니다.

반응형