앎을 경계하기

[가짜연구소3기] Data Engineer

[가짜연구소 3기] 데이터 엔지니어링 44 - Best Practices

양갱맨 2021. 9. 13. 13:38

주제

파이썬의 효율적인 코드 작성과 관련된 개념들에 대해 배웠다.


독스트링(Docstring)

개발을 하다보면 다른 사람의 코드를 많이 보고, 다른 사람이 만든 함수를 써야하는 경우가 많다.

복잡한 함수를 사용하고자 할 때, 함수에 대한 정보를 코드와 함께 제공한다면 사용자에게 큰 도움이 될 것이다.

그래서 우리가 많이 사용하는 것이 주석(comment)이다.

주석과 독스트링의 차이는 뭐지..?

독스트링은 함수 블럭 맨 첫줄에서 여러 줄로 작성된다.

파이썬에서 한 줄 코멘트는 # 을 사용하였고 여러 줄은 """ 을 사용한다는 것을 배웠을 것이다.

주석과 독스트링은 함수가 실행되고 출력하는데 영향을 주지 않는다.

독스트링은 주석과 달리 help(), __doc__ 속성에 저장되어 사용자가 확인할 수 있다.

독스트링이 코드의 세부적인 내용을 담기 위해 사용된다는 것은 알겠는데..

독스트링 내용

내가 독스트링을 작성한다면, 무슨 정보를 넣어야할까?

  • 함수의 기능
  • 파라미터, 아규먼트 정보
  • 반환 값
  • 에러 발생과 관련된 정보
  • 이 외 함수에 관련되어 말하고 싶은 모든 것들

독스트링 형식

이 독스트링도 사람들이 많이 사용하는 형식들이 있다.

  • Google style
  • Numpydoc
  • reStructuredText
  • EpyText

구글 스타일과 넘파이독이 가장 많이 사용된다.

Google Style

함수 기능에 대한 간결한 설명으로 시작한다.

Numpydoc

inspect 라이브러리를 사용해서 docstring을 가져오는 것도 가능하다.


DRY - 한 가지 수행을 원칙으로

데이터 분석하거나 조작하기 위해 흔히 하는 행동이 "여러 번 반복해보기"다.

이게 무슨 말이냐면..

예를 들어,

원본 데이터셋을 Train, Validation, Test set으로 나누었다.

각 데이터에 대한 주성분 분석을 수행하고 시각화하려고 한다.

먼저 Train set에 대한 분석 코드를 보자.

train = pd.read_csv("train.csv") #데이터 읽기
train_y = train['labels'].values #레이블 값 분리
train_X = train[col in train.columns if col != 'labels'].values
train_pca = PCA(n_components = 2).fit_transform(train_X) #주성분 분석
plt.scatter(train_pca[:,0], train_pca[:,1]) #시각화

이러한 과정을 Validation과 Test set에서도 적용하려면 어떻게 할까?

흔히 하는 행동이면서 하면 안되는 행동이 여러 번 반복하기 라는 것이다.

그냥 위 코드를 복붙해서 Train set을 Val 또는 Test set으로 바꿔준다.

이 행동의 위험한 점이 있는데,

실수가 쉽게 발생할 수 있고, 발견하기 어려울 수도 있다.

val = pd.read_csv("validation.csv") #데이터 읽기
val_y = val['labels'].values #레이블 값 분리
val_X = val[col in val.columns if col != 'labels'].values
val_pca = PCA(n_components = 2).fit_transform(val_X) #주성분 분석
plt.scatter(val_pca[:,0], val_pca[:,1]) #시각화

위처럼 문제 없이 잘 바꿔주면 상관없지만 아래같은 상황이라면?

test = pd.read_csv("test.csv") #데이터 읽기
test_y = test['labels'].values #레이블 값 분리
test_X = test[col in test.columns if col != 'labels'].values
test_pca = PCA(n_components = 2).fit_transform(train_X) #주성분 분석
plt.scatter(test_pca[:,0], test_pca[:,1]) #시각화

실수를 발견하기 어렵다.

PCA 호출에 있어서 데이터를 test_X로 수정하지 못한 실수가 발생하여 찾기 어려운 에러가 발생할 수 있다.

만약 위 코드를 순차적으로 진행했다면 train_X라는 변수가 존재하기 때문에 에러없이 진행되고 test_pca에는 train_X를 PCA 한 결과가 들어갈 것이다.

 

함수를 사용해서 파라미터만 바꿔 같은 실행을 하도록 하여 이런 실수로 인한 에러 발생을 줄인다.

def load_and_plot(path):
"""데이터셋을 불러오고 첫번째, 두번째 주성분을 시각화한다.

Args:
	path(str): CSV 파일 경로

Returns:
	tuple of ndarray: (features, labels)
"""
data = pd.read_csv(path)
y = data['label'].values
X = data[col for col in data.columns if col != 'label'].values

pca = PCA(n_components=2).fit_transform(X)
plt.scatter(pca[:,0], pca[:,1])

return X, y

하지만 여전히 이 함수에도 문제는 존재한다.

바로 소프트웨어 엔지니어링 원칙 - Do One Thing(한 가지만 수행) 을 위반한다.

위 함수의 기능

  1. 데이터셋 불러오기
  1. 주성분 피쳐 시각화

이 두 기능을 더 잘게 쪼개자.

def load_data(path):
"""데이터셋 불러오기
Args:
	path (str): CSV file 경로

Returns:
	tuple of ndarray: (features, labels)
"""
data = pd.read_csv(path)
y = data['labels'].values
X = data[col for col in data.columns if col!='labels'].values

return X, y

def plot_data(X):
"""PCA 결과의 첫 번째, 두 번째 주성분 요소 시각화
Args:
	X (numpy.ndarray): 시각화하기 위한 데이터
"""
pca = PCA(n_components=2).fit_transform(X)
plt.scatter(pca[:, 0], pca[:, 1])

쪼개면 좋은 점?

데이터를 읽기만 하고 시각화하지는 않을 때 → 가능

새로 데이터 읽지 않고 주성분 분석 후 시각화하려고 할 때 → 가능

  • 좀 더 유연해진다.
  • 다른 사람이 코드를 이해하고 사용하기 쉬워진다.
  • 업데이트 시, 코드가 미칠 영향을 파악하기 쉽다.

이런 과정을 리팩토링 한다고 말한다.


Pass by assignment

파이썬에서는 call by value와 call by reference가 아닌 call by assignment 를 사용한다.

각각이 무엇인지 보자.

call by value

함수 호출 시, 전달되는 변수 값을 복사해서 다른 메모리에 두고 사용한다.

함수 안에서 값을 변경시켜도 원본 변수에는 영향이 없다.

call by reference

함수 호출 시, 전달되는 변수의 주소 값을 전달한다.

레퍼런스를 전달하게 되면 원본 변수를 가지고 작업을 수행하기 때문에 원본 변수에 영향이 있다.

call by assignment

파이썬에서는 2가지 객체로 나뉜다.

  1. immutable object
    • 변경 불가능한 객체
    • int, float, tuple, string
    • call by value 작동
  1. mutable object
    • 변경 가능한 객체
    • list, dict, set
    • call by reference 작동