함수(function) 없이 코딩할 수 있을까? - 파이썬 함수에 대한 이야기

Soheon Lee·2021년 3월 7일
5

정확히 말하자면, 언어에서 기본적으로 제공해주는 메소드를 제외하고 독자적으로 만들어 사용하는 함수 없이 코딩할 수 있을까? 그래도 될까?

시작하며

프로그래밍을 처음 공부하기 시작하는 사람으로 가정하고, 공부하는 순서에 대해 한 번 되돌아보자.
우선 변수를 배운다. 어떤 언어인지를 떠나 보통 가장 먼저 정수, 실수, 문자열을 선언하는 방법을 익힌다. 파이썬을 배우는 경우 그 이후로 list, dictionary, tuple과 같은 자료형을 공부한다. 이 용어들이 조금 익숙해지면 보통 함수(function)를 배운다.

우리가 매일 쓰는 함수는 대체 무엇인지, 왜 필요한지, 함수를 알기 전 내 코드들을 다시 한 번 보며 짚어보고자 한다.

무엇을 하려는건지 몰랐어도 아래 코드를 흘낏 보면 비슷한 구조가 3,4번씩 반복되고 있다. 해당 과정은 지난 20년간 한반도 근처 여름평균 기압, 동서바람, 남북 바람의 흐름이 어떻게 변화했는지 시간에 따라 분석하여 시각화 하여 이미지 파일로 저장하는 과정이다. 굵게 처리된 부분은 실험 계획에 따라 늘 바뀔 수 있는 부분이었다.

지금 이 과정을 다시 작성한다면 전체 과정을 함수로 만들고, 관측 자료의 종료, 기간, 영역 등을 parameter로 처리했을 것이다.

실험 계획이 변경될 때마다 비슷한 실험을 여러번 해야했으므로 파일은 늘어났다. 이렇게 함수 없이 4년을 코딩했다. 이때 작성했던 내 코드의 특징을 정확하게 짚어보자면, 아래와 같다.

  • 재사용이 불가능하다.
    매 번 필요할 때마다 해당 과정을 반복해야한다.
  • 독립적인 여러 절차가 동시에 얽혀있다.
    관측 자료 전처리, 통계 분석, 데이터 시각화가 한 번에 이루어지고 있기 때문에 코드를 읽는 호흡이 불필요하게 길어진다.

함수가 만들어진 이유

함수는 가장 기본적인 프로그램 구조이다. 한 번 피땀 흘려 작성한 코드가 여러 곳에서 재사용이 될 수 있도록 하는 구조이다. 복잡한 과정도 작은 단위로 분해하여 각 함수가 독립적인 역할을 하게 되고, 이는 훨씬 코딩하기 쉬운 상태로 만들어준다. 위에서 과거 코드가 보인 특징 두가지가 함수를 잘 사용해야하는 이유이다. 다시 한 번 보자.

🔥 코드를 재사용할 수 있도록 하기

코드의 중복을 최소화하여 재사용성을 높이

🔥 독립적인 작업을 수행하는 단위 만들기

왜 함수를 사용하는가?
1. 코드 재사용성을 높이고 중복을 최소화한다.
가장 기본적인 '분해' 도구다. 하나의 작업에 대하여 한 곳에서 코딩하고 여러 곳에서 활용할 수 있도록 해주기 때문이다.

  1. 절차적 분해
    한 번에 전체 작업 절차를 구현하는 것보다, 서로 간에 독립적인 더 작은 작업들을 ㄱ현하는 것이 더 쉬운 일이다. 일반적으로 함수는 '절차'와 관련된 것으로, 무엇을 위해 그 일을 하는가보다 무엇을 어떻게 하는가와 관련된 것이다.

파이썬 함수의 속성

가장 기본적인 형태의 함수를 한 번 만들어보자.

def analyze_data():
    pass

이렇게 생성된 함수는 기본적으로 35개의 속성과 built-in 메소드를 가지고 있다 dir() 함수는 해당 객체가 가지고 있는 속성과 메소드를 모두 보여준다. 물론 다른 속성들도 추가해줄 수 있으나, built-in으로 제공되는 속성과 메소드를 먼저 확인해보자.

>>> dir(analyze_data)
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', 
'__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', 
'__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', 
'__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', 
'__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', 
'__str__', '__subclasshook__']

Built-in attributes

7개의 파이썬 built-in 속성들을 소개하고자 한다. 몰라도 큰 문제는 없었지만, 한 번 알게 된 이상 자주 활용할 수 있는 속성들이다. 예시 함수는 아래와 같다.

1. annotations & doc & defaults

세 속성은 모두 선언된 함수를 보다 쉽게 이해할 수 있도록 하는 역할을 한다.

  • __annotations__ : 함수를 선언할 때 지정하는 parameter에 덧붙인 부가 설명에 접근 할 수 있고 예시 코드 3, 4, 5번 줄과 같이 지정한다.
  • __defaults__ : 마찬가지로, 함수를 선언할 때 지정하는 parameter에 할당해 둔 __default arguments__에 접근할 수 있는 속성으로, 예시 코드 5번 라인 37.0을 일컫는다.
  • __doc__: 함수 선언시 작성한 docstring 내용을 확인할 때 사용하는 속성이다.

예시 함수의 세가지 속성을 부르면 아래와 같이 나타난다.

list를 정렬할 때 자주 사용하는 sorted() 메소드의 __doc__를 확인해보자.

내가 사용하는 함수가 애초에 어떤 이유로 만들어졌는지 파악하기에 수월하다. 그러나 그 기능을 애초에 전혀 모르고 보았을 때에 바로 알기는 어려우므로, 구글링으로 해당 함수의 대략적인 동작기작을 알고 난 뒤에, 종종 까먹었을 때 확인하기에 좋다고 생각한다.

2. class

특정 인스턴스가 어떤 객체인가를 알려주는 속성으로, 파이썬 built-in 함수인 type()과 같은 역할을 한다.

def analyze_data():
    pass

>>> analyze_data.__class__
<class 'function'>

이 속성은 함수가 아닌 다른 객체에서도 원활히 동작한다.

>>> a = 1
>>> a.__class__
<class 'int'>

그러나 타이핑이 길어 type()을 더 많이 사용할 것 같다.

3. module & name & qualname

속성 이름이 매우 직관적이다.

  • __module__: 함수가 포함되어 있는 모듈, 즉 파일의 이름을 알려준다. 어디서 import 한 함수인지 알고 싶을 때 용이하게 사용할 수 있다.
  • __name__: 해당 함수의 이름을 알려주는 속성. 개인적으로 사용 의도가 의아했던 속성이다.
    그러나 함수를 다른 변수에 할당해서 사용하거나 import pandas as pd 와 같이 축약된 이름으로 객체의 이름을 변경하여 사용할 때, 원래 이름을 알아내기에 매우 편리할 것이다.
    또한, if __name__ == __main__ 와 같은 조건에서 사용된다. 터미널에서 바로 사용되는 모듈과 다른 모듈에서 import 되어 사용되는 모듈에 대해서는 따로 기록하도록 한다.
  • __qualname__: __name__과 마찬가지로 함수의 이름을 표현한다. __name__과 달리 클래스나 함수가 중첩되어 있을 때, 중첩 관계 (path) 까지 보여주기 때문에 같은 이름의 메소드가 있어도 디버깅을 조금 더 수월하게 해주는 장점이 있다.
    아직 사용해본 적이 없는 속성이라, __name__과의 비교 후, 직접 사용해보기로 한다.

    이름을 비교해보기 위하여 fun 이라는 변수에 원본 함수를 할당해두었다. 현재와 같은 상황에서는 __name____qualname__이 같다.

Built-in methods

우리에게 필요한 속성을들 임의로 추가할 수는 없을까? 물론 있다. 파이썬이 함수를 사용할 때 제공해주는 많은 built-in 메소드를 확인해보고, 그 메소드 가운에 하나인 __setattr__를 이용해 속성을 추가해보자.

1. setattr, getattribute, delattr

세 메소드 또한 이름이 직관적이라는 사실이 가장 마음에 든다.

  • __setattr__: 함수 인스턴스에 속성을 추가해줄 때에 사용하는 메소드이다.
  • __getattribute__: 추가해준 속성에 접근할 때 사용하는 메소드이다.
  • __delattr__: 추가했던 속성을 제거할 때 사용하는 메소드이다.
def function1():
    pass
    
>>> function1.__setattr__('nickname', 'function_one')
>>> function1.__getattribute__('nickname')
function_one
>>> function1.__delattr__('nickname') # 제거
>>> function1.__getattribute__('nickname') # 제거한 속성에 다시 접근

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'function' object has no attribute 'c' # 더이상 존재하지 않는 속성이므로 Attribute 에러 발생

이를 사용하다보면 자연스롭게 일반적으로 객체에 속성을 지정하는 방법이 떠오를 것이다.

>>> function1.nickname = 'function_one'
>>> function1.nickname
'function_one'

사실 위와 같은 역할이다. function1.nickname = 'function_one'이라 할당할 때에 __setattr__메소드가 호출되기 때문이다.

2. str, repr

파이썬 장고로 개발하는 것이 익숙해진 나에게 가장 친숙한 메소드 가운데 하나가 __str__이다. 해당 객체가 호출될 때 어떻게 사용자에게 표현될 것인가를 결정하는 메소드들인데, 함수를 예시로 들기보다는 다른 객체들을 사용하여 비교하는 것이 보다 좋다고 판단했다.
strrepr은 모두 객체를 문자열 형태로 표현해주는 역할을 한다. 사람이 눈으로 보고 어떤 객체인지 읽어내야하기 때문이다.


자주 사용하는 객체인 datetime객체를 한 번 보자. 지금 현재 시각을 now라는 변수에 담았는데, __str__() 메소드는 보다 사람이 알기에 친숙하고, 알아보기 쉬운 상태로 표현하고, __repr__()메소드는 그렇게 친절한 편은 아니다.

str()repr()
Make object readableRequired code that reproduces object
Generate output to end userGenerate output for developer

많은 사람들이 이런 기준을 가지고 str()과 repr()을 사용하고 있다. 그리고 또 한가지, str()과 repr() 과 같이 함수를 호출하는 방법으로 사용하는 것이 좋고 직접적으로 __str__()에 접근하는 것은 추천하지 않는다.

마치며

함수를 숨 쉬듯이 사용하고 있다. 위키백과에 따르면 최초의 컴퓨터들과 마이크로프로세서들은 단일 함수 호출 명령어가 없었다고 한다. 함수를 구현할 수는 있었지만, 거의 사용하지 않았다고 보면 된다. 많은 불편함이 쌓여서 함수가 개발되었을테다. 좋은 도구라고 하여도 제대로 사용하지 못하면 그 역할을 제대로 하기 어렵다. 앞으로도 재사용 가능하고 독립적인 기능을 하는 작은 단위로 코드를 잘 분리하여 보다 함수를 잘 활용하고자 다짐한다.

profile
Hello, World

0개의 댓글