해당 내용은 Datacamp의 Data engineering track을 정리했습니다.
Writing Functions in Python의 chapter 1에 대한 내용입니다.
기본적인 내용보다는 강의를 들으면서 처음 알게 된 내용을 위주로 작성했습니다.
해당 포스팅에는 아래의 내용을 포함하고 있습니다.
- Docstring
- 함수의 활용
- Immutable, mutable
1. Docstrings
Docstring은 함수나 클래스에 다른 사람이 이해하기 쉽도록 작성하는 내용을 말합니다. 보통 docstring에는 해당 함수가 어떤 역할을 하고, 어떤 인수들이 들어가야 하는지, return은 어떤 형태로 나오는지 등을 포함합니다.
def split_and_stack(df, new_names):
"""Split a DataFrame's columns into two halves and then stack
them vertically, returning a new DataFrame with 'new_names' as the column names.
Args:
df (DataFrame): The DataFrame to split.
new_names (iterable of str): The column names for the new DataFrame.
Returns:
DataFrame
"""
half = int(len(df.columns) / 2)
left = df.iloc[:, :half]
right = df.iloc[:, half:]
return pd.DataFrame(data=np.vstack([left.values, right.values]),
columns=new_names)
Docstring은 Google Style, Numpydoc, reStructuredText, EpyText처럼 다양한 형태를 가지지만, 강의에서는 Google Style과 Numpydoc을 추천하고 있습니다. 추가적으로 만약에 해당 함수의 docstring이 보고 싶을 때에는 split_and_stack.__doc__로 접근하거나 inspect를 import해서 inspect.getdoc(function)으로 접근할 수 있습니다.
print(split_and_stack.__doc__)
import inspect
print(inspect.getdoc(split_and_stack))
2. DRY and "Do One Thing"
여기서의 DRY는 Don't repeat yourself라고 해서, 직접 반복된 작업을 하지말라는 의미를 가지고 있습니다. 되도록이면 반복되는 일에 대해 함수를 만들어서 처리하라는 의미입니다.
위의 예시를 보면, 각기 다른 데이터를 불러와서 x, y값으로 나눈 뒤에 PCA를 통해 차원축소한 이미지를 보고 싶은 상황입니다. 이런 경우에는 각각을 따로 실행하는 것 말고 함수를 작성해서 적용하는 것이 더 좋습니다. 가장 먼저 위의 코드에서 어떤 동작을 해야하는지를 살펴봐야 합니다. 가장 먼저, csv파일을 읽는 과정과 'label' column을 numpy.ndarray로 변환하고, 나머지 feature들도 동일하게 변환하는 과정을 진행합니다. 이후에는 pca를 진행한 뒤에 함수를 그리는 작업으로 구성되어 있습니다.
여기서 함수로 구성할 때 되도록이면 1개의 함수에는 1개의 기능을 수행할 수 있도록 구성하는 것이 가장 좋습니다.
# 1개의 함수에 2개의 기능
def load_and_plot(path):
"""Load a data set and plot the first two principal components.
Args:
path (str): The location of a csv file.
Returns:
tuple of ndarray: (features, labels)
"""
#load the data
data = pd.read_csv(path)
y = data['label'].values
x = data[col for col in train.columns if col != 'label'].values
#plot the first two principle components
pca = PCA(n_components=2).fit_transform(x)
plt.scatter(pca[:,0], pca[:, 1])
return x, y
위의 함수를 2개로 쪼개면, data를 load하는 부분과 차원축소하여 plot을 그리는 부분으로 나눠볼 수 있습니다.
def load_data(path):
"""load a data set.
Args:
path (str): The location of a CSV file.
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
return x, y
def plot_data(x):
"""plot the first two principal components of a matrix.
Args:
x (numpy.ndarray): The data to plot
"""
pca = PCA(n_components=2).fit_transform(x)
plt.scatter(pca[:,0], pca[:,1])
한 개의 함수에 1개의 기능을 넣으면, 좀 더 유연하고 이해하기 쉽습니다. 또한 간단하게 테스트할 수 있고 디버깅도 수월해집니다. 필요하다면 수정하기도 쉽다는 장점이 있습니다.
3. Pass by assignment
기본적으로 파이썬에서 문자에 값이나 리스트를 할당한 경우에 다른 문자에 재할당할 때, 생각한 것과 다른 문제가 발생하신 적이 있을 것 같습니다.
위의 그림에서처럼 a라는 문제 [1, 2, 3] 리스트를 할당한 뒤에 b = a라는 연산을 통해 [1, 2, 3]을 넘겨줬습니다. 그리고 a라는 객체에 4라는 값을 추가했을 때, b는 4가 포함되지 않을 것이라고 생각했으나, 동일한 주소를 가르키고 있기 때문에 동일한 객체를 반환합니다. 추가적으로 b에 5라는 값을 추가해도 a는 b와 동일한 객체를 반환합니다. 그래서 할당할 때 이 점을 유의할 필요가 있습니다.
my_var가 x로 선언되어 함수에 들어간 다음 연산에 의해서 새로운 주소로 point가 바뀌게 됩니다. 이처럼 정수형같이 수정하기 어려운 타입을 Immutable이라고 부릅니다. int, float, bool, string, bytes, tuple, frozenset, None이 Immutable에 포함됩니다. list, dict, set, bytearray, objects, functions 등 대부분은 mutable에 포함됩니다.
# Use Mutable
def foo(var=[]):
var.append(1)
return var
foo() #[1]
foo() #[1, 1]
# Use Immutable
def foo(var=None):
if var is None:
var = []
var.append(1)
return var
foo() # [1]
foo() # [1]
foo() 함수를 연속으로 돌렸을 때, Mutable에는 계속해서 1이 쌓이지만 None으로 된 경우에는 [1]로 결과가 나오는 것을 확인할 수 있습니다. 이러한 차이가 있음에 유의해야 합니다.