본문 바로가기

Python/Story

Python - 파이썬의 선(The Zen of python)

 

* 바쁘신 분들을 위한 3줄 요약

1. 가독성은 코드에 정말로 중요합니다.

2. 코드는 가독성이 정말로 중요합니다.

3. 코드는 정말로 가독성 중요합니다.

4. 코드는 정말로 중요합니다 가독성이.

 


소개 (Introduce)

 

The Zen of python은 파이썬 기여자인 Tim Peters가 파이썬의 설계철학을 널리 알리기 위해 작성했습니다.

Zen은 고요할 선(禪)의 영어발음 입니다. 이 한자는 정신 집중을 통해 깨달음의 경지에 다가가는 것을 뜻하는 불교용어입니다. 비슷한 단어로는 명상(meditation)이 있겠습니다만, 명상이란 단어로 직역하기에는 의미 차이가 꽤 있는 편입니다. 발음 그대로 "선"이라고 번역하도록 합니다.

 

이하 내용은 The Zen of Python의 전문 및 번역입니다.

* 만약 여러분 컴퓨터에 파이썬이 이미 있으시다면, import this를 통해 아래 내용을 직접 확인할 수 있습니다.

The Zen of Python, by Tim Peters
파이썬의 선, 팀 피터슨

Beautiful is better than ugly.
아름다운것이 못생긴 것보다 좋습니다.

Explicit is better than implicit.
명시적인 것이 암시적인 것보다 좋습니다.

Simple is better than complex.
단순한 것이 복잡한 것보다 좋습니다.

Complex is better than complicated.
복합적인 것이 복잡한 것보다 좋습니다.

Flat is better than nested.
납작한 것이 중첩된 것보다 좋습니다.

Sparse is better than dense.
흩뿌려진 것이 모인 것보다 좋습니다.

Readability counts.
가독성은 중요합니다.

Special cases aren't special enough to break the rules.
특별한 상황이, 규칙을 깰 만큼 특별하단 얘기는 아닙니다.

Although practicality beats purity.
실용성이 순수함을 이길 때까지는 말이죠.

Errors should never pass silently.
에러는 조용히 넘어가서는 안됩니다.

Unless explicitly silenced.
명시적으로 조용히 만들지 않는 한.

In the face of ambiguity, refuse the temptation to guess.
모호한 상황이라면, 추측하려 하지 마세요.

There should be one-- and preferably only one --obvious way to do it.
문제를 풀 수 있는 (바람직하고 유일한) 분명한 방법이 있어야 합니다.

Although that way may not be obvious at first unless you're Dutch.
하지만 네덜란드 인이 아닌 이상 처음에는 분명하지 않을 수 있습니다.

Now is better than never.
지금 하는 게 하지 않는 것보단 좋습니다.

Although never is often better than *right* now.
하지만 지금 당장 하는 것보다 안 하는 게 나을 때도 있습니다.

If the implementation is hard to explain, it's a bad idea.
구현 방식이 설명하기 어렵다면, 그것은 좋지 않은 생각입니다.

If the implementation is easy to explain, it may be a good idea.
구현 방식이 설명하기 쉽다면, 그것은 아마 좋은 생각일 겁니다.

Namespaces are one honking great idea -- let's do more of those!
Namespaces는 쩌는 생각입니다. 더욱 이런 것들을 해봅시다!

 

제목 1줄과 총 19줄의 내용이 담겨있습니다. 한 줄씩 코드를 예시를 들어가면서 해설해보겠습니다.

 

 

 


 

Beautiful is better than ugly.
아름다운 것이 못생긴 것보다 좋습니다.

 

 

실생활에서 아름답다(Beautiful)고 느끼는 것은 사실 주관적입니다. 하지만 파이썬에서 추구하는 아름다움의 방향성은 정해져 있습니다. 바로 가독성(Readablility)입니다.

간단한 출력을 하는 C++과 python을 비교해 보겠습니다. 

#include <iostream>
#include <string>

using namespace std;

int main(){
    string name("David");
    cout << "His name is " << name << endl;
    return 0;
}

// Write in C++

 

name = "David"
print(f"His name is {name}")

## Write in python 3.6

 

파이썬의 f-string문법이 인상적입니다. 이런 직관적인 문법들은 파이썬 곳곳에서 만나보실 수 있습니다.

 

또 다른 예시입니다.

## Basic
my_list = []
for i in range(0, 10):
    if i % 5 != 0:
        my_list.append(i ** 2)

## List Comprehension
my_list2 = [x ** 2 for x in range(0, 10) if x % 5 != 0]

print(my_list == my_list2) # True

 

이 파이썬 코드는 기본 방식과  List Comprehension 문법을 비교하여 소개하고 있습니다. 이런 문법에 익숙해지신다면, 파이썬 코드량을 줄이면서도, 높은 가독성을 유지할 수 있습니다.

a, b = b, a

 

변수들의 값을 서로 교환하는 문법입니다. 이 문법이 왜 간단한지 모르시는 분들을 위해, 같은 기능을 하는 C style 코드를 소개합니다.

int temp = a;
a = b;
b = temp;

 

다른 언어의 경우는 temp라는 임시변수를 선언해서 값을 교환하는 것이 일반적입니다. 확실히 파이썬이 타 언어 대비 직관적인 문법을 지원함을 알 수 있습니다.

이러한 사용자 친화적 문법설탕(Syntax Sugar)은 파이썬의 큰 특징 중 하나이며, 코드의 가독성을 올리는데 중요한 역할을 합니다. 하지만 가독성을 파이썬 자체에만 의존할 것이 아니라 개발자 스스로도 가독성을 고려하며 코딩해야 합니다. 그래서 파이썬은 PEP(Python Enhancement Proposals) 8이라는 문서를 통해, 가독성 있는 코드를 작성하는 방법을 안내 합니다.

 

 

 

 

 

Explicit is better than implicit.
명시적인 것이 암시적인 것보단 좋습니다.

 

 

사실 파이썬은, 다른 언어에 비해 명시적이지 않기도 합니다. 예를 들면 변수 선언 시 Type을 명시하지 않거나, Class 선언 시, public, private에 대한 명확한 Keyword가 없는 점 등 파이썬은 여러 부분에서 암시적입니다.

그렇다면 파이썬은 어떤 점이 명시적이란 것일까요? 그것은 바로 코드의 변수, 함수명을 명시적으로 작성하란 의미입니다.

## 명시적인(Explicit) 코드.
## lowercase_with_underscores 규칙
def get_total_price(price, quantity):
    return price * quantity

## 암시적인(Implicit) 코드. 
def gtp(p, q):
    return p * q

 

명시적인 코드와 암시적인 코드를 작성해 보았습니다. 암시적인 코드는 명시적인 코드보다 짧아졌기 때문에 읽는데 걸리는 속도는 줄일 수 있습니다. 하지만, 뜻도 모를 gtp라는 함수가 왜 변수 2개를 받아서 곱하는지, 어디서 어떻게 쓰일지 이해하는 데는 꽤 오랜 시간이 걸립니다.

결국, 코드를 읽고 이해하는데 걸리는 시간이 빠른 것은 짧은 암시적이 코드가 아닌 길게 적힌 명시적인 코드입니다. PEP 8는 명시적인 코드를 적는 방법에 대해 안내합니다. 예시 하나를 소개합니다.

# Correct:
with conn.begin_transaction():
    do_stuff_in_transaction(conn)
    
# Wrong:
with conn:
    do_stuff_in_transaction(conn)

* 출처 : https://peps.python.org/pep-0008/

 

PEP 8 예시로 드린 코드에서, Wrong이라 적힌 의미는 문법적인 문제가 아닌, 명시적이지 않다는 것을 지적한 것입니다. 어떤 부분이 암시적인지, 그로 인해 어떤 문제가 있을 지, 생각해 보시길 바랍니다.

 

 

 

 

 

Simple is better than complex.
단순한 것이 복잡한 것보다 좋습니다.

 

 

필자는 이 부분을 Java와 비교해서 설명하고자 합니다.

// Complex code in Java
public class Example{
    public static void main(String[] args) {
        boolean guest_waiting = true;
        if (guest_waiting){
            guide_guests();
        } else {
            cleaning();
        }
    }
}

 

## Simple code in python
def main():
    guests_is_waiting = True
    if guests_is_waiting:
        guide_guests()
    else:
        cleaning()


if __name__ == "__main__":
    main()

 

무엇이 가장 큰 차이일까요? Java 자체가 가지고 있는 엄격한 문법은 기능적인 사항인 것을 제외한다면 괄호의 유무의 차이가 매우 큰 부분입니다. 파이썬에서는 함수, 분기, 반복 영역을 표시하는 block영역을 { }로 표시하는 대신, 문법적으로 공백(space)을 사용합니다. 파이썬 언어의 문법, 데이터 구조 등 많은 부분이 Simple함을 강조하지만, 필자는 이 공백(Space) 문법이 파이썬에서 가장 Simple함을 강조한다고 생각합니다.

 

 

 

 

 

Complex is better than complicated.
번역은 어떻게...?

 

 

한국에서는 Complex와 Complicated를 둘다 복잡하다 라고 번역합니다. 그러다 보니 위의 문장이 번역하기 참 어렵습니다. 구글은 어떻게 번역할까요?

 

 

구글도 번역 하기 힘든 Complex와 Complicated...

 

 

구글 번역도 이 문제를 해결해주지 못합니다. 대신에 좋은 번역을 할 수 있도록 힌트를 주는 예문을 찾았습니다.

 

“He developed a complex argument.” (The argument had many different details, perhaps covering a lot of angles and considering different viewpoints.)
그는 복잡한 주장을 전개했습니다. (그 주장은 많은 각도를 다루고 다른 관점들이 고려된 것으로 보이는, 여러가지 세부사항을 가지고 있었다.)

“He developed a complicated argument.” (The argument had many different details that were perhaps hard to follow.)
그는 복잡한 주장을 전개했습니다. (그 주장에는 이해하기 어려울것으로 보이는, 여러가지 세부사항을 가지고 있었다.)

* 출처 : https://englishlessonsbrighton.co.uk/

 

 

위와 같은 몇 개의 예시를 종합해본 결과, Complex를 "복합적인 것"으로 번역하기로 했습니다. Complicated를 "난잡하다"라는 단어로 하면 번역 해소가 되는 것 같아 보이지만, 복잡함 자체에 이미 부정적인 의미가 담겨있어서 Complex가 왜 좋은지는 설명해 주지 못합니다.

 

그럼, 코드 예시를 통해 Complex와 Complicated의 의미를 살펴보겠습니다.

 

 

 

 

 

Complex is better than complicated.
복합적인 것이 복잡한 것보다 좋습니다.

 

 

# Complicated example
def is_prime(p): return all(p%i for i in range(2,p-1))==1<p

출처 : https://codegolf.stackexchange.com/

 

이 코드는 Simple합니다. List Comprehension 활용, All이라는 내장함수 활용, ==1<p 이라는 예외처리까지 한 줄에 담겨있습니다. 심지어 이 함수는 정수 범위에서는 완벽하게 동작합니다.

그런데 여러분은 이 함수를 읽고, 어떻게 동작하는지 쉽게 파악했나요? 아무래도 어려웠을 것입니다. 심지어 is_prime이라는 함수명조차 없으면 어떤 목적으로 만들어진 함수인지 더욱 알기 어렵습니다.

이렇게 해석하기 어려운 코드를 Complicated 하다 합니다.

 

## Complex example
def is_prime(number):
    if number <= 1:
        return False
    
    for i in range(2, number):
        if number % i == 0:
            return False
    
    return True

 

이 함수는 Complicated 예시코드와 거의 똑같은 원리로 동작하는 함수입니다.

처음에 소개한 함수에 비하면 복합적(Complex)입니다. 왜냐하면, 비교적 많은 단어들이 있고, 여러 줄을 읽어야 하고, if문의 동작과 for문의 동작을 확인해야 하죠. 하지만 각각의 기능들이 이해하는 데에는 복잡(Complicated) 하지 않습니다. 결국 가독성 측면에서는 Complex 코드가 더욱 좋음을 알 수 있습니다.

 

복합적 기능이 있는 Complex code에서 단순함(Simplicity)을 과도하게 추구하면, Oversimplification로 인해 가독성를 잃어버려 결국 Complicated Code가 됩니다. Complex is better than complicated는 결국, Oversimplification에 대한 경고입니다.

 

 

 

 

 

Flat is better than nested.
납작한 것이 중첩된 것보다 좋습니다.

 

 

이 문장은 Block을 필요 이상으로 중첩해서 사용하는 경우에 대해 설명하고 있습니다.

## Nested example
def check_data(data):
    if data >= 0:
        if data % 2 == 0:
            return "data is even"
        else:
            return "data is odd"
    else:
        return "data is not positive"

 

짝수, 홀수를 구하는 함수입니다. if의 중첩(nested)으로 인해 좌측에 space 공간이 넓어졌습니다. 이 부분을 납작(Flat)하게 만들어 봅시다.

## Flat example
def check_data(data):
    ## 예외처리를 앞에서
    if data < 0:
        return "data is not positive"
    
    if data % 2 == 0:
        return "data is even"
    else:
        return "data is odd"

 

다음과 같이 처리하면 사실상 같은 동작인데도 코드의 좌측 space 공간이 사라져 코드가 납작해졌습니다. javascript는 Call back 함수 부분에서 저런 nested한 문제가 쉽게 발생합니다. 어떻게 해결했을까요? 궁금하신 분들은 아래 링크를 참조해보세요.

https://tyoon9781.tistory.com/entry/javascript-asynchronous-event-loop-callback-promise-async-await

 

JavaScript 비동기 완벽하게 이해하기

*목차 - intro - JavaScript는 Single Thread - Event Loop란? - 비동기(Asynchronous) 동작 예시 예제 1. 예제 2. 예제 3. 예제 4. - 비동기 프로그래밍 1. Callback Function 2. Promise 3. Async, await Intro 웹 개발의 첫 단추를 끼

tyoon9781.tistory.com

 

 

 

 

 

 

Sparse is better than dense.
흩뿌려진 것이 모인 것보다 좋습니다.

 

 

 

이 내용은 Complex is better than complicated와 비슷한 내용입니다. 가독성을 유지한 채 밀도(Density) 있게 작성된 코드는 괜찮습니다만, 위 문구는 단순히 모인 것에 대한 얘기가 아닙니다. Simplicity에만 집착하여 과도하게 모아진(dense) 코드가 가독성을 잃어버리는 것에 대한 경고입니다.

 

## Sparse example
my_list = []
for i in range(0, 10):
    if i % 5 != 0:
        my_list.append(i ** 2)

 

## Dense example
my_list = [x ** 2 for x in range(0, 10) if x % 5 != 0]

 

아이러니하게도, 이 예시들은 Beautiful is better than ugly에서 온 예시입니다. 이전에는 아름답다고 했던 코드가 이제는 Dense하다고 별로라 하니, 어찌 된 일일까요?

 

사실 코드는 읽는 사람마다 가독성이 다릅니다. 초보자와 전문가가 같은 코드를 읽는다고 가독성까지 똑같이 느껴지지는 않습니다. 마찬가지로, List Comprehesion가 익숙한 사람에게는 Dense한 코드가 Simple하게 느껴지겠지만, 그렇지 않은 사람에게는 Complicated하게 느껴질 것입니다.

이런 개인차는 본인에게 맞는 스타일을 선택하시거나, 팀의 내부 Rule에 맞춰가면 됩니다.

 

 

 

 

 

 

Readablility counts.
가독성은 중요합니다.

 

지금까지 나왔던 본질적인 얘기입니다. Counts라는 단어가 쉽게 해석이 안되는 단어라, 예문만 남기고 넘어가겠습니다.

 

With our busy lives today we have less and less time for what really counts.
오늘날 우리의 바쁜 삶에서, 우리는 진정으로 중요한 것에 쓸 시간을 잃어가고 있다.

* 출처 : https://www.oxfordlearnersdictionaries.com/

 

 

 

 

 

Special cases aren't special enough to break the rules.
특별한 상황이, 규칙을 깰 만큼 특별하단 얘기는 아닙니다.

 

 

특별한 상황이라 하면 대부분은 Error Handling을 의미합니다. 정말로 Error를 특별한 상황이라 여기고 따로 처리를 하기보다는, Error로 간주하지 않도록 하는 것이 중요합니다.

## special case

def sum_list(input_list):
    result = 0

    try:
        for input_element in input_list:
            result += input_list
    except:
        print("error")
        
    return result

 

sum_list를 작성한 개발자에게 try - except로 저렇게 묶은 이유를 물어보았습니다. 물어보니, input_list로 무슨 값이 들어올지 몰라서 우선 저렇게 error를 처리했다고 합니다.

 

확실한 것은, 저 코드는 당분간은 멀쩡해 보일 것입니다. 하지만, input_list에 정말로 이상한 값이 들어 왔을 때, 오류를 보여줘야 할 sum_list 함수는 계속 동작합니다. 그리고 print("error")로 잠깐 남긴 로그는 개발자들에게 발견되지 않을 것입니다. 결국 이상한 값은 여기서 처리되지 않고 나중에는 큰 문제로 돌아오게 됩니다.

 

이상한 값이 정말로 들어올 지, 팀 단위로 프로그램 요구사항 문서를 확인해 보았습니다. 그 결과, 저 함수는 내부적으로만 쓰이며, 숫자가 담긴 list를 다루는 영역 외에는 사용되지 않는다고 합니다. 이 함수는 이제 특별한 case(list가 아닌 input)를 고려해서 규칙을 깨서는(try except 남용) 안될 것입니다.

 

## Check Special case and rewrite

def sum_list(input_list):
    result = 0
    for input_element in input_list:
        result += input_list
        
    return result

 

예외처리 부분을 제외해도 동일하게 동작합니다. 이러한 예외처리와 관련된 코드 작성은 의사소통이 정말로 중요합니다. 혼자 전전긍긍하지 않고, 제대로 소통하여 특별한 Case를 감별해 냅시다.

 

 

 

 

 

Although practicality beats purity.
실용성이 순수함을 이길 때까지는 말이죠

 

 

이 문장은 이전 문장과 합쳐서 해석할 수 있습니다.

Special cases aren't special enough to break the rules.
Although practicality beats purity.
특별한 상황이라 해서 규칙을 깰 만큼 특별하지는 않습니다.
실용성이 순수함을 이길 때까지는 말이죠

→ 실용성이 규칙을 넘어설 정도면 어쩔 수 없다.

 

코딩을 하다보면, 좀 더 간결하게 적을까? 이게 에러로 처리되어야 하나? 등등 항상 딜레마와 마주합니다. 이때 The Zen of python은 다음과 같은 의견을 제시합니다.

 

가독성 같은게 지금 뭐가 중요합니까, 코드가 안 돌게 생겼는데

 

## Purity example

def sum_list(input_list):
    result = 0
    for input_element in input_list:
        result += input_list
        
    return result

 

이 함수가 돌아간다는 보장은 input값이 나열 가능하며, 더할 수 있는 형태일 때만 가능합니다. 하지만 input값을 일반 사용자가 직접 입력한다면 큰일이 나겠죠. 실제로 sum_list에 input값으로 0을 넣으면 이렇게 됩니다.

>>> def sum_list(input_list):
...     result = 0
...     for input_element in input_list:
...         result += input_list
...     
...     return result
...     
>>> sum_list(0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in sum_list
TypeError: 'int' object is not iterable

 

바로 TypeError가 뜨는것을 확인하고 예외처리를 합시다. dense나 flat같은 purity한 것들을 신경 쓸 여유가 없습니다.

## Practicality example

def sum_list(input_list):
    result = 0
    ## Practicality Part, Type check.
    if not isinstance(input_list, list):
        print("[Warning] input data type is not list")
        return result
        
    for input_element in input_list:
        result += input_list
        
    return result

 

뭔가 이전보다는 코드 가독성이 떨어졌지만(사실 거의 차이 없습니다)  그래도 input값이 어떤 것인지에 따라 코드가 죽지는 않게 되었습니다. 남은 것은 이제 원치 않는 input일 때 return 0이 맞는 것인지에 대해 토론하는 것입니다.

 

 

 

 

 

Errors should never pass silently.
에러는 조용히 넘어가서는 안됩니다.

 

 

에러가 나면 요란하게 알림도 주고, 어디서 틀렸는지 밑줄을 그어주는데, 뭐가 조용하다는 얘기일까요? 바로 무지성으로 try - except 구문으로 조용히 넘어가지 말고, 에러가 나면 확실하게 표시하라는 얘기입니다.

## Bad example for Error handling

def try_somthing(number):
    try:
        return calculation(1/number)
    except Exception as e:
        print("error")
        return number

 

예시코드에 작성된 Error Handling는 모든 에러에 대해서 except를 걸어버렸습니다. 사실 의외로 이렇게 try를 어떠한 안내나 예측도 없이 단지 죽지 않기만을 바라며 걸어버리는 경우가 종종 있습니다. 하지만 이런 형식의 코드는 나중에 유지보수할 때 난이도 상승의 직접적인 원인이 됩니다. 에러가 걸릴 것 같으면 꼭 명시를 제대로 합시다.

 

 

 

 

 

 

Unless explicitly silenced.
명시적으로 조용히 만들지 않는 한.

 

 

이 문장은 이전 문장과 합쳐서 해석할 수 있습니다.

Errors should never pass silently.
Unless explicitly silenced.
에러는 조용히 넘어가서는 안됩니다.
명시적으로 조용히 만들지 않는 한.

→ 예상되는 에러만 조용하게 만들어라.

 

이전의 코드에서 문제가 되었던 것은 모든 에러에 대해서 except가 걸려버리는 것이었습니다. 이렇게 되면 코드는 당장 죽지 않을지 몰라도, 예측 못한 에러까지 죽지 않게 되면서 다른 곳에서 이상함을 나타내게 됩니다.

따라서, 다음과 같이 수정합니다.

## Explicitly silenced example

## float 0에 대한 처리. 이 숫자의 크기는 요구사항에 따라 달라진다.
epsilon = 0.000001

def try_somthing(number):
    if not isinstance(number, (int, float)):
        print("[Warning] number is not int or float type")
        ## Type 오류에 대한 처리.
        ## raise TypeError를 하지 않음. Silence
        return False
    if abs(number) < epsilon:
        print("[Warning] number is Almost ZERO")
        ## 매우 작은 숫자에 대한 처리.
        ## raise ZeroDivisonError를 하지 않음. Silence
        return False
        
    try:
        return calculation(10 / number)
    ## 명시된 Case 외에도 에러가 난다면 다음과 같이 처리한다.
    except Exception as e:
    	print(f"[Warning] Unexpected Error Case.")
        print(f"number is {number}, error is {error}")
        ## raise AttributeError
    	return False

 

이렇게 하면 number가 적어도 사용자의 input의 대부분의 경우는 예상된 동작으로 감당이 가능할 것입니다. 사실 마지막에 except Exception이 남아있는데, 절대로 죽어서는 안 되는 코드라 가정하고 위의 예제처럼 작성되었습니다. 만약 함수가 죽는 것이 허용된다면 raise를 활성화하면 됩니다.

 

 

 

 

 

In the face of ambiguity, refuse the temptation to guess.
모호한 상황이라면, 추측하려 하지 마세요

 

 

이전에 제가 Although practicality beats purity 부분에서 이런 예시를 보여드렸었습니다.

## Practicality example

def sum_list(input_list):
    result = 0
    ## Practicality Part, Type check.
    if not isinstance(input_list, list):
        print("[Warning] input data type is not list")
        return result
        
    for input_element in input_list:
        result += input_list
        
    return result

이 형태에서 토론할 것이 남아있다고 언급했는데, 이렇게 여지가 남은 상황에서 정답이 바로 나오지 않는 상황, 바로 모호한 상황입니다. 정확한 Error Handling을 위해서라면 이런 모호한 부분이 있어서는 안됩니다. 0으로 출력해도 괜찮겠지 라는 유혹(temptation)에 넘어가지 말고 명확히 에러(raise TypeError)를 띄워줍시다.

## refuse temptation to guess about input

def sum_list(input_list):
    result = 0
    ## Practicality Part, Type check.
    if not isinstance(input_list, list):
        print("[Warning] input data type is not list")
        print(f"input data : {input_list}")
        raise TypeError
        
    for input_element in input_list:
        result += input_list
        
    return result

 

 

 

 

 

There should be one-- and preferably only one --obvious way to do it.
문제를 풀 수 있는 (바람직하고 유일한) 분명한 방법이 있어야 합니다.

 

 

이 문장은 Perl의 철학 TIMTOWTDI의 저격입니다. Perl의 유명한 철학 중 다음과 같은 것이 있습니다.

There's more than one way to do.
문제를 풀 수 있는 방법은 여러 가지다.

 

이 의미는 Perl 언어로는 같은 문제를 다양한 방법으로 풀 수 있다는 것을 의미합니다. Sort 예제를 통해 살펴보겠습니다.

#!/usr/bin/perl

## number 1 solve
@numbers = (3, 1, 4, 1, 5, 9, 2, 6, 5, 3);
@sorted = sort @numbers;
print "@sorted\n";

## number 2 solve
@numbers = (3, 1, 4, 1, 5, 9, 2, 6, 5, 3);
@sorted = sort { $a <=> $b } @numbers;
print "@sorted\n";

## number 3 solve
@numbers = (3, 1, 4, 1, 5, 9, 2, 6, 5, 3);
@sorted = map { $_->[0] } sort { $a->[1] <=> $b->[1] } map { [$_, $_] } @numbers;
print "@sorted\n";

 

Perl에서 이 3가지 풀이법은 모두 동일한 Sort를 수행합니다. 그럼 파이썬의 경우는 어떨까요?

## number 1 solve
numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
numbers.sort()
print(numbers)

## number 2 solve
numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
sorted_numbers = sorted(numbers)
print(sorted_numbers)

 

파이썬 또한 2가지 풀이방법을 제공하는 것 같지만, sort는 list의 method로, list 자체가 변화하는 것이고, sorted는 built-in 함수로써, sorted 된 결과물을 return합니다. 이처럼 파이썬은 한 가지 문제에 가장 좋은 풀이법 한 가지를 제시하려 합니다.

 

 

 

 

 

Although that way may not be obvious at first unless you're Dutch.
하지만 네덜란드 인이 아닌 이상 처음에는 분명하지 않을 수 있습니다.

 

위의 문장과 이어서 해석됩니다. 파이썬 창시자(Guido van Rossum) 네덜란드 사람입니다.

 

 

 

 

 

Now is better than never.
지금 하는 게 하지 않는 것보단 좋습니다.

 

지금이라도 하는 게, 하지 않는 것보다는 좋다고 합니다. 지금 바로 코딩을 합시다.

 

 

 

 

 

Although never is often better than right now.
하지만 지금 당장 하는 것보다 안 하는 게 나을 때도 있습니다.

 

너무 조급한 나머지 하드코딩을 해서 동작하도록 한다던가, 충분한 의견 교류 없이 코딩을 해서 팀 전체의 방향성을 잘못된 방향을 끌고 간다던가 하는 등, 급한 마음에 이루어진 일이 올바르지 않은 결과를 낳게 됩니다.

차분히 의견을 교류하고, 좋은 아이디어를 기다린 후에, 솔루션을 선택하는 것이 지금은 느려 보일지 몰라도, 도착지까지는 가장 빠른 길이 될 수 있습니다.

 

 

 

 

 

If the implementation is hard to explain, it's a bad idea.
구현 방식이 설명하기 어렵다면, 그것은 좋지 않은 생각입니다.

 

위의 문구는 꽤 직설적이므로 추가 해설을 하지 않겠습니다.

 

대신에 필자는 조금 다른 예시로, 좋은 생각(혹은 기술)처럼 보이는데도 설명이 어려운 경우를 설명하고자 합니다.

 

  1. 구현 난이도가 너무 높아 설명하기에 상대방도 기술적 사전 지식이 있어야 하는 경우

  2. 기술을 공유하는데, 개발자의 커뮤니케이션 능력이 떨어지는 경우

  3. 영업에게 들을 때는 정말 좋은 기술처럼 들렸는데, 남들에게 전달할 때는 막히는 경우

 

이 모든 경우가 전부 결과적으로는 좋은 구현, 혹은 기술이 아닐 수 있습니다. 이유는 아래와 같습니다.

 

  1. 기술 자체는 좋을 수 있어도, 단체가 쓰기에는 금액, 러닝 커브 대비 산출 시간 등으로 적합하지 않을 수 있습니다.

  2. 커뮤니케이션 자체의 문제가 생기면, 설명 자체에 왜곡 전달이 누적되어, 좋은 점을 사용하기 어려울 수 있습니다..

  3. 당연하지만 영업은 항상 좋은 단어만 사용합니다. 실제로 사용 시 생기는 문제점은 스스로 생각합시다.

 

 

 

 

 

 

If the implementation is easy to explain, it may be a good idea.
구현 방식이 설명하기 쉽다면, 그것은 아마 좋은 생각일 겁니다.

 

"it may be a good idea"의 문장은, 부정할 때와 달리 확신이 줄어든 문장입니다. 위의 영업 예시와 같은 경우입니다. 좋아 보인다고 정말로 좋은 게 아니지요.

 

 

 

 

 

 

 

Namespaces are one honking great idea -- let's do more of those!
Namespaces는 쩌는 생각입니다. 더욱 이런 것들을 해봅시다!

 

Namespace는 이름공간이라 직역할 수 있겠지만, 한글로 많이 쓰는 용어가 아니라 영어 그대로 쓰겠습니다.

Python에서 Namespace scope는 global, nonlocal, local, built-in 이렇게 4개의 공간으로 나눠져 있습니다. 변수들이 namespace를 넘나드는 아래 예제를 보겠습니다.

## namespace example
## global, nonlocal, local

x = 10                ## x is global scope

def outer_function():
    global x          ## get x 
    x += 1            ## 10 + 1
    y = 10            ## y is nonlocal scope
    
    def inner_function():
        global x      ## get x 
        nonlocal y    ## get y
        z = 10        ## z is local scope
        x *= 2        ## 11 * 2 
        y *= 3        ## 10 * 3
        z *= 4        ## 10 * 4
        return z
        
    z = inner_function()
    return y, z
    
print(x)              ## 10
y, z = outer_function()
print(x, y, z)        ## 22, 30, 40


## built-in scope (실제로는 이렇게 사용하면 안됩니다.)
print(-x)             ## -22
print(abs(-x))        ## 22. abs is built-in function
print(abs)            ## <built-in function abs>
abs = 100             ## abs is value 100 now (금기)
print(abs)            ## 100

 

더욱 깊은 이해를 돕기 위해 예제를 하나 더 만들었습니다. x라는 같은 이름으로 계속 변수를 선언하지만, Scope에 따라 서로 참견하지 않고 운영되는 변수들을 확인해 보시기 바랍니다.

## namespaces example
## global, nonlocal, local

x = 10                ## x is global scope

def outer_function():
    x = 20            ## x is nonlocal scope
    
    def inner_function():
        x = 30        ## x is local scope
        return x
        
    return x, inner_function()
    
print(x)                   ## 10
x2, x3 = outer_function()
print(x, x2, x3)           ## 10, 20, 30

 

 

왜 Namespace가 좋다고 언급했을까요? 그 이유는 namespace 간의 공간을 나눠주지 못하면, 변수가 어디서 참조 되었는지 알기 어렵기 때문입니다. 이는 유지보수를 어렵게 하며, 결과적으로 전체적인 가독성을 떨어 뜨립니다.

포인터나 전역변수의 문제점 중 하나는, 값이 의도치 않게 변경되었을 때 어떤 이유로 변경되었는지 알기가 매우 어렵다는 것입니다. Namespace는 변수에 영향을 주는 영역을 제한한다는 큰 장점이 있습니다. Debugging 할 때마다 전체 영역을 조사하는 것이 아닌 Scope 영역만 조사해도 된다면, 매우 빠른 시간 안에 원인을 찾을 수 있습니다.

 

 


 

마치며...

The Zen of python을 전부 훑어 보았습니다. 결국은 가독성으로 시작해서 가독성으로 끝났습니다. 그런데 사실 가독성이란 요소는 뛰어난 알고리즘을 개발하는 것 보다는 디버깅과 유지보수를 위한 것입니다. 그리고 누군가는 "창의적인 알고리즘을 작성하는 것이 더 중요한 게 아닌가?" 라고도 생각할 것입니다.

 

그런 분들을 위해, Clean Code라는 책에서의 문구를 발췌해 보았습니다.

코딩 작업을 전부 녹화하고 재생시켜 보았다.
실제 코딩시간의 90%는 수정할 곳을 찾아 스크롤을 하는 것과, 코드를 읽는 시간이었다.

출처 : 로버트 C. 마틴, Clean Code에서 발췌

 

처음부터 끝까지 완성된 Application을 위해 혼자 프로그래밍하는 경우는 거의 없습니다. 그 코드가 상품성이 있다면, 반드시 경쟁력을 가지기 위해 협업을 하게 됩니다. 그 과정에서 서로 작성한 코드를 읽게 되고, 검토하고, 수정하게 될 것입니다. 그럴 때마다 가독성 품질이 좋을수록 개발속도와 품질 또한 올라갈 것이며, 이는 의도한 기간 내에 의도한 대로 동작하는 코드를 만드는데 큰 힘이 된다는 것을 잊지 말아 주시기 바랍니다.

 

코딩을 하는 데에는, 무엇보다 가독성이 중요하다는 것을 The Zen of python을 통해 알게 되셨으면 합니다.

 

 

 


 

*reference

https://www.python.org/

https://en.wikipedia.org/wiki/Python_(programming_language) 

https://peps.python.org/pep-0008/

https://codegolf.stackexchange.com/questions/57617/is-this-number-a-prime/207119

https://englishlessonsbrighton.co.uk/whats-difference-complex-complicated/

https://www.oxfordlearnersdictionaries.com/definition/english/count_1

 

'Python > Story' 카테고리의 다른 글

파이썬(Python)이란?  (0) 2023.02.08