본문 바로가기
Python

The Zen of Python

by AI-BT 2024. 11. 7.
728x90
반응형

1. The Zen of Python 소개

Python에는 The Zen of Python이라는 철학이 있다. 이는 Python의 창시자인 Guido van Rossum의 의도와 Python 커뮤니티의 가치를 반영하여, Tim Peters가 작성한 철학적 지침이다. Python 코드를 작성할 때 지침이 되는 이 철학은 "더 나은 코드"를 위한 방향을 제시하며, Python이 단순하고 가독성 높은 언어로 자리 잡는 데 중요한 역할을 한다. 이를 확인하려면 Python 인터프리터에서 import this 명령어를 입력하면 된다.

아래는 The Zen of Python에 나오는 19가지 원칙과 그 의미를 간략히 설명한 것이다.

import this

print(this)

 

 


 

2. The Zen of Python 의 19가지 원칙

1. Beautiful is better than ugly. (아름다움은 추함보다 낫다.)

코드는 아름답고, 보기 좋게 작성되는 것이 중요하다. 가독성이 높은 코드는 유지보수성이 높으며, 다른 사람과 협업할 때 큰 장점이 된다. 코드 길이를 줄이기 위해 지나치게 압축하거나 가독성을 희생하는 것보다, 조금 길더라도 명확하게 작성하는 것이 더 좋다는 철학이다

# Beautiful
def calculate_area(radius):
    return 3.1415 * radius ** 2

# Ugly
def a(r): return 3.1415 * r ** 2

 

 

2. Explicit is better than implicit. (명시적인 것이 암시적인 것보다 낫다.)

명시적이라는 것은 코드가 어떤 동작을 하는지 직관적으로 드러난다는 의미이다. 암시적인 코드는 코드를 작성한 사람에게는 당연하게 보일 수 있지만, 다른 사람이나 시간이 지나서 다시 본 사람에게는 의도가 분명하지 않을 수 있다. 코드를 명시적으로 작성하면 읽는 사람이 코드의 의도와 흐름을 쉽게 이해할 수 있다.

 

# Explicit
user_age = 25
is_eligible = user_age >= 18

# Implicit (not clear what 18 means)
age = 25
eligible = age >= 18

#==============================================#

# Explicit
def add_numbers(first_number, second_number):
    return first_number + second_number

num1 = 5
num2 = 10
print(add_numbers(num1, num2))

# Implicit
def c(a, b):
    return a + b

x = 5
y = 10
print(c(x, y))

 

 

3. Simple is better than complex. (단순함이 복잡함보다 낫다.)

코드가 단순할수록 이해하고 유지보수하기가 쉽다. 복잡한 코드가 필요한 경우도 있지만, 불필요하게 복잡한 구조를 사용하면 코드의 가독성과 유지보수성이 떨어진다. 단순한 코드는 더 적은 논리 흐름과 간결한 구성으로 의도를 명확하게 표현하며, 버그가 발생할 가능성도 줄어든다. Python은 간단하고 직관적인 코드 작성을 지향하며, 가능한 한 복잡도를 낮추는 것이 좋다.

 

# Simple
def greet(name):
    return f"Hello, {name}!"

# Complex
def greet(name, formal=False):
    if formal:
        return f"Good day, {name}. How do you do?"
    else:
        return f"Hello, {name}!"
    
#==============================================#

# Simple
def sum_of_even_squares(numbers):
    """리스트의 숫자 중 짝수의 제곱 합을 계산합니다."""
    return sum(n ** 2 for n in numbers if (n ** 2) % 2 == 0)

numbers = [1, 2, 3, 4, 5]
print(sum_of_even_squares(numbers))


# Complex
def complex_function(numbers):
    squared_numbers = []
    for n in numbers:
        squared_numbers.append(n ** 2)
    
    even_squared_numbers = []
    for n in squared_numbers:
        if n % 2 == 0:
            even_squared_numbers.append(n)
    
    total_sum = 0
    for n in even_squared_numbers:
        total_sum += n
    
    return total_sum

numbers = [1, 2, 3, 4, 5]
print(complex_function(numbers))

 

4. Complex is better than complicated. (복잡함은 난해함보다 낫다.).

때로는 코드가 복잡해질 수밖에 없지만, 그렇다고 해서 난해하게 작성되어서는 안 된다. 복잡한 코드도 논리적 구조를 잘 갖추고 명확하게 작성해야 한다.

복잡하지만 이해할 수 있는 코드
작업을 여러 단계로 나누고 명확한 변수 이름을 사용하여 코드의 흐름을 드러내면, 복잡할 수는 있지만 코드가 이해하기 쉬워지고 유지보수하기가 쉬워집니다.
난해한 코드
여러 동작을 한 줄에 담아 로직을 지나치게 압축하면, 코드가 하는 일을 이해하기 어려워진다.
이로 인해 가독성이 떨어지고, 문제를 발견하거나 수정하기가 어렵다.
 
# Complex 
def calculate_price_with_discount(price, discount):
    return price * (1 - discount)

# Complicated
def calc(p, d):
    return p * (1 - d)

#==============================================#

# Complex
# 단계를 나누어 명확하게 작성한 코드
def sum_of_even_squares(numbers):
    """짝수인 숫자들의 제곱을 구한 뒤, 그 합을 반환합니다."""
    
    # 짝수만 필터링
    even_numbers = [x for x in numbers if x % 2 == 0]
    
    # 짝수들의 제곱 계산
    squared_evens = [x ** 2 for x in even_numbers]
    
    # 제곱한 값들의 합 계산
    total_sum = sum(squared_evens)
    
    return total_sum

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(sum_of_even_squares(numbers))

# Complicated
# 난해한 코드: 한 줄로 모든 작업을 압축
result = sum([x ** 2 for x in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] if x % 2 == 0])

 

 

 

5. Flat is better than nested. (Flat 한것이 중첩된 것보다 낫다.)

 "Flat"하게 작성된 코드란, 중첩된 구조를 줄이고 가능한 한 한 층으로 작성된 코드를 말한다. 중첩이 많으면 코드를 읽기 어렵고, 로직을 파악하기가 어려워진다. 코드가 여러 단계로 깊게 들어갈수록 가독성이 떨어지고, 추적하기가 어려워진다.대신 조건문이나 반복문을 가능한 한 단순하고 flat 하게 작성하면 코드의 흐름을 더 직관적으로 이해할 수 있다.
# Flat
def check_grades(students):
    for student in students:
        # 성적 확인
        if student["grade"] < 50:
            print(f"{student['name']} failed due to low grade")
            continue # iteration 건너뜀.
        
        # 출석률 확인
        if student["attendance"] < 75:
            print(f"{student['name']} failed due to low attendance")
            continue
        
        # 모든 조건을 만족하면 합격
        print(f"{student['name']} passed")

students = [
    {"name": "Alice", "grade": 65, "attendance": 80},
    {"name": "Bob", "grade": 45, "attendance": 90},
    {"name": "Charlie", "grade": 70, "attendance": 60},
]

check_grades(students)


# Nested
def check_grades(students):
    for student in students:
        if student["grade"] >= 50:
            if student["attendance"] >= 75:
                print(f"{student['name']} passed")
            else:
                print(f"{student['name']} failed due to low attendance")
        else:
            print(f"{student['name']} failed due to low grade")

students = [
    {"name": "Alice", "grade": 65, "attendance": 80},
    {"name": "Bob", "grade": 45, "attendance": 90},
    {"name": "Charlie", "grade": 70, "attendance": 60},
]

check_grades(students)

 

 

6. Sparse is better than dense. (여유 있는 것이 빽빽한 것보다 낫다.)

코드는 여백이 충분히 있어야 가독성이 높아진다. 코드가 너무 빽빽하면 읽기 어려우므로, 적절한 공백을 두어 여유 있게 작성해야 한다.

# Sparse (easy to read)
def add(a, b):
    return a + b

# Dense (hard to read)
def add(a,b): return

 

7. Readability counts. (가독성은 중요하다.)

가독성은 코드의 기본이다. Python은 가독성을 가장 중요하게 생각하며, 이해하기 쉬운 코드가 최우선이다.

# Readable
def calculate_total_price(price, tax_rate):
    return price * (1 + tax_rate)

# Less readable
def calc_tot(p, t):
    return p * (1 + t)

 

 

8. Special cases aren't special enough to break the rules. (특별한 경우도 규칙을 깰 정도로 특별하지 않다.)

"특별한 경우"라는 것은 어떤 예외 상황이나 특수한 조건을 의미한다. 특별한 경우가 있더라도 일반적인 규칙을 따르는 것이 좋으며, 그 상황을 위해 굳이 규칙을 깨는 복잡한 코드를 작성할 필요는 없다는 뜻이다. 이를 통해 코드의 일관성을 유지할 수 있으며, 예외적인 경우를 위해 전체 코드 구조를 변경하거나 복잡성을 더하지 않도록 권장한다.
# 규칙을 깨는 코드 (복잡한 처리)
def check_number(n):
    if n == 0:
        return "Zero"
    elif n > 0:
        return "Positive"
    else:
        return "Negative"

print(check_number(5))  # Positive
print(check_number(-3)) # Negative
print(check_number(0))  # Zero

# 규칙을 유지한 코드 (간단하고 일관성 있는 처리)
def check_number(n):
    if n > 0:
        return "Positive"
    elif n < 0:
        return "Negative"
    return "Zero"  # 특별한 경우인 0은 마지막에 간단히 처리

print(check_number(5))  # Positive
print(check_number(-3)) # Negative
print(check_number(0))  # Zero

# 0을 별도로 처리하지 않고 마지막에 간단히 "Zero"를 반환하도록 했으며, 
# 특별한 경우를 위한 불필요한 조건을 제거하여 규칙을 깨지 않고 자연스럽게 처리

 

9. Practicality beats purity. (실용성이 순수함을 이긴다.)

실용성이란 코드가 현실적인 문제를 해결하는 데 초점을 맞춘다는 의미이다. 순수하게 "완벽하고 이상적인 코드"를 작성하려다 보면, 때로는 코드가 너무 복잡해지거나, 성능이 떨어지거나, 개발 시간이 오래 걸릴 수 있다. 이럴 때는 "실용적인 접근"을 택해, 적당히 작동하고 목적을 달성하는 쪽으로 코드를 작성하는 것이 더 좋다. 실용성은 가독성이나 성능, 개발 효율성을 고려하여 최적의 해결책을 선택하는 것을 의미한다.
# Practical
# 실용적인 코드 (간단한 처리)
def sum_even_numbers(numbers):
    # 짝수만 더함
    return sum(n for n in numbers if n % 2 == 0)

print(sum_even_numbers([1, 2, 3, 4, 5]))  # 6


# Pure but impractical (writing your own join function)
# 순수성을 추구한 코드 (복잡한 처리)
def sum_even_numbers(numbers):
    total = 0
    for n in numbers:
        # 모든 요소가 숫자인지 확인한 후 짝수만 더하기
        if isinstance(n, int) and n % 2 == 0:
            total += n
        elif not isinstance(n, int):
            print(f"Warning: {n} is not an integer, ignoring it.")
    return total

print(sum_even_numbers([1, 2, "three", 4, 5]))  # Warning 출력 후 6 반환
 
 

10. Errors should never pass silently. (오류는 절대 조용히 넘겨서는 안 된다.)

코드에서 오류가 발생할 경우 이를 무시하거나 조용히 지나치기보다는, 오류를 명확히 드러내는 것이 중요하다고 생각한다. 오류가 발생했을 때 이를 적극적으로 처리하지 않고 그냥 넘어가면, 버그를 찾기 어려워지고 코드가 잘못 작동할 가능성이 커진다. 따라서, 오류가 발생하면 이를 로그로 기록하거나 예외 처리를 통해 원인을 명확히 드러내는 것이 좋다.
단, 필요할 때는 명시적으로 오류를 무시할 수 있지만, 그렇지 않다면 기본적으로 오류가 발생한 원인을 알 수 있도록 해주는 것이 Python 철학에 부합하다
# 오류를 조용히 넘기는 코드 (비추천)
def divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        pass  # 오류가 발생해도 조용히 무시함

print(divide(10, 2))  # 5.0
print(divide(10, 0))  # None (하지만 오류 메시지 없음)


# 오류를 명확히 드러내는 코드 (추천)
def divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        print("Error: Division by zero is not allowed.")
        return None

print(divide(10, 2))  # 5.0
print(divide(10, 0))  # Error 메시지 출력 후 None 반환

 

 

11. Unless explicitly silenced. (명시적으로 조용히 처리할 수 있다면 예외이다.)

특정 오류를 명시적으로 무시할 필요가 있는 경우, 예외적으로 조용히 넘어갈 수 있다. 하지만 이 경우에도 의도를 분명히 드러내야 한다.

# 1) 오류를 명시적으로 무시하는 경우 (파일이 없으면 생성)
# 파일이 없는 경우에만 파일을 새로 만드는 의도를 드러낸다
def open_or_create_file(filename):
    try:
        with open(filename, 'r') as f:
            print(f.read())
    except FileNotFoundError:
        # 파일이 없으면 새로 생성
        with open(filename, 'w') as f:
            print(f"{filename} created.")

# 파일이 있을 때
open_or_create_file("existing_file.txt")

# 파일이 없을 때
open_or_create_file("new_file.txt")


# 2) 특정 상황에서의 오류 무시 (기록 로그 삭제 실패 시 무시)
# 로그 파일을 삭제할 때, 로그 파일이 없더라도 프로그램이 계속 정상적으로 작동하게 하려면 오류를 무시할 수 있습니다. 
# 아래 예제에서는 파일이 존재하지 않는 경우 예외를 발생시키지 않고 조용히 넘어갑니다.
import os

def delete_log_file(logfile):
    try:
        os.remove(logfile)
    except FileNotFoundError:
        # 로그 파일이 없으면 그냥 넘어감
        pass

# 로그 파일이 있을 때 삭제
delete_log_file("app.log")

# 로그 파일이 없을 때도 오류 없이 넘어감
delete_log_file("app.log")


# 3) 불필요한 경고 무시하기 (특정 라이브러리 경고 무시)
# 라이브러리에서 발생하는 경고가 프로그램의 주요 기능에 영향을 주지 않는 경우, 경고를 무시하도록 설정할 수 있습니다.
import warnings

def some_function():
    # 특정 경고를 조용히 무시
    warnings.filterwarnings("ignore", category=UserWarning)
    warnings.warn("This is a user warning", UserWarning)

some_function()  # UserWarning이 발생하지만, 조용히 무시함

 

 

12. In the face of ambiguity, refuse the temptation to guess. (모호한 상황에서는 추측하지 말라.)

코드가 모호하거나 불분명할 때는 추측하지 않고 명확히 해결해야 한다. 모호한 상태를 방치하면 오류의 원인이 되므로, 확실한 방향을 택해야 한다.

# 1) 모호한 입력에 대한 추측 (비추천)
def calculate_area(radius):
    # radius가 None인 경우 임의로 1을 기본값으로 사용
    if radius is None:
        radius = 1  # 임의의 값 설정
    return 3.1415 * radius ** 2

print(calculate_area(None))  # 예상치 못한 기본값 사용


# 2) 명확하게 예외 처리 (추천)
def calculate_area(radius):
    # radius가 None이거나 음수인 경우 명확하게 예외 처리
    if radius is None:
        raise ValueError("Radius must be provided.")
    if radius < 0:
        raise ValueError("Radius cannot be negative.")
    return 3.1415 * radius ** 2

print(calculate_area(5))    # 정상 동작
print(calculate_area(None))  # 명확한 예외 발생


# 3) 모호한 조건을 피하는 코드 작성 (문서화와 기본값 활용)
def calculate_area(radius=1):
    if radius < 0:
        raise ValueError("Radius cannot be negative.")
    return 3.1415 * radius ** 2

print(calculate_area())    # 기본값 1 사용
print(calculate_area(3))   # 반지름 3 사용

 

13. There should be one-- and preferably only one --obvious way to do it. (하나의 작업을 수행할 때 가장 명백하고 직관적인 방법을 사용하자.)

하나의 작업을 수행할 때 가장 명백하고 일관된 방법이 있어야 한다. 여러 가지 방법이 가능하더라도, 팀이나 프로젝트에서는 일관된 방법을 선택해야 한다.

numbers = [1, 2, 3, 4, 5]

# 방법 1: for 루프를 사용하여 리스트에 추가
squared_numbers = []
for num in numbers:
    squared_numbers.append(num ** 2)

# 방법 2: map 함수를 사용하여 제곱 계산
squared_numbers_map = list(map(lambda x: x ** 2, numbers))

# 방법 3: 리스트 컴프리헨션 사용
squared_numbers_comp = [num ** 2 for num in numbers]


# 하나의 명백한 방법: 리스트 컴프리헨션을 사용하여 제곱 계산
numbers = [1, 2, 3, 4, 5]
squared_numbers = [num ** 2 for num in numbers]

# 차이점 설명
# 여러 가지 방법으로 작성된 코드
# 한 작업을 여러 방식으로 작성하면 가독성이 떨어지고, 
# 코드가 복잡하게 느껴질 수 있습니다. 각 방식의 차이점을 이해해야 하고, 
# 개발자마다 다른 방식을 선호할 수도 있어 팀 내에서도 코드 일관성이 떨어질 수 있다.

# 하나의 명백한 방법으로 작성된 코드
# Python의 관용구적 스타일을 따른 한 가지 방법으로 코드를 작성하면, 코드가 일관되고 명확해지며, 
# 다른 개발자들도 코드의 의도를 쉽게 이해할 수 있다.

 

 

14. Although that way may not be obvious at first unless you're Dutch. (비록 그 방법이 처음에는 명백하지 않을 수 있지만, 네덜란드인에게는 명백할 것이다.)

 

Python의 창시자인 Guido van Rossum이 네덜란드 출신이라는 점을 유머러스하게 표현한 것이다. Python의 설계 철학이 처음에는 낯설게 느껴질 수 있지만, Python의 철학을 깊이 이해하면 그 의도가 명확히 드러난다는 의미이다.

 

15. Now is better than never. (지금이 결코보다 낫다.)

완벽할 때까지 기다리기보다는, 가능한 지금 시작하는 것이 낫다. 일단 시작한 뒤 점진적으로 개선하는 것이 더 효율적이다. 단, 지금 가능한 만큼 시작하고 점진적으로 개선하라는 의미입니다.

 

 

16. Although never is often better than right now. (지금 당장 서둘러 잘못된 방식으로 실행하는 것보다, 차라리 하지 않는 것이 나을 때도 있다.)

def connect_to_database():
    # 미리 준비되지 않은 설정을 서둘러 사용
    db_config = None  # 아직 설정되지 않은 값
    connection = db_config.connect()  # 오류 발생 가능
    return connection

connect_to_database()

 

 

17. If the implementation is hard to explain, it's a bad idea. (설명이 어려운 구현이라면 좋은 생각이 아니다.)

코드가 복잡해서 설명하기 어렵다면, 그것은 좋은 코드가 아닐 가능성이 크다. 이해하기 쉬운 방식으로 다시 설계하는 것이 좋다.

# Easy to understand 
def calculate_sum(numbers):
    return sum(numbers)

# Hard to understand
def calc_sum(nums):
    total = 0
    for n in nums:
        total += n
    return total

 

 

18. If the implementation is easy to explain, it may be a good idea. (설명이 쉬운 구현이라면 좋은 생각일 수 있다.)

코드가 명확하고 쉽게 설명될 수 있다면, 그것은 좋은 설계일 가능성이 높다. 간결하고 이해하기 쉬운 구현을 지향해야 한다.

def is_even(number):
    return number % 2 == 0

 

19. Namespaces are one honking great idea -- let's do more of those! (네임스페이스는 정말 멋진 아이디어다. 더 많이 활용하자!)

네임스페이스는 코드의 가독성을 높이고 충돌을 방지하는 데 큰 도움이 된다. Python에서는 네임스페이스를 잘 활용해 구조적으로 명확한 코드를 작성해야 한다.

import math
import statistics

print(math.pi)
print(statistics.mean([1, 2, 3]))

 

 

위의 19가지 원칙을 되새기며 파이썬 코드를 작성하자.

문제해결도 좋지만 가독성과 효율적인 코드를 작성하도록 노력하자!

 

감사합니다.

728x90
반응형

댓글