CleanCode_Python

Chapter 06 - WRITING PYTHONIC CODE [파이썬스러운 코드 작성히기]

개인의 관점에 따라 다르지만 python다운 코드를 작성하는 일반적인 방법을 제시합니다.

6.1_ Commonly Misused Syntax [일반적으로 잘못 사용되는 구문]

  • range()보다는 enumerate()를 사용하자

    animals = ['cat', 'dog', 'moose']
    for i in range(len(animals)):
      print(i, animals[i])
      
    > 0 cat
    > 1 dog
    > 2 moose
    
    # Pythonic Example
    animals = ['cat', 'dog', 'moose']
    for i, animal in enumerate(animals):
      print(i, animal)
      
    > 0 cat
    > 1 dog
    > 2 moose
    

    index를 출력하거나 사용하기 위해 range(len())을 사용하는 것보다 enumerate가 더 간단히다.

  • open() 및 close() 대신 with문 사용하기

    # Unpythonic Example
    try:
      fileObj = open('spam.txt', 'w')
      eggs = 42 / 0    # A zero divide error happens here.
      fileObj.close()  # This line never runs.
      except:
        print('Some error occurred.')
      
    > Some error occurred.
    

    eggs에서 에러가 생기고 close()가 실행되지 않은 상태로 끝나 파일은 열린 상태로 남아 있게 되고, 파일 손상 버그가 발생하면 try블록까지 추적하기 어렵다.

    # Pythonic Example
    with open('spam.txt', 'w') as fileObj:
      fileObj.write('Hello, world!')
    

    다음과 같이 with문으로 작성하면, 코드를 벗어날 경우 자동으로 close()를 호출한다.

  • None과 비교할 때 == 대신 is를 사용하자

    class SomeClass:
      def __eq__(self, other):
        if other is None:
          return True
          
    spam = SomeClass()
      
    spam == None
    > True
      
    spam is None
    > False
    

    ==는 두 개체의 값을 비교하지만, is는 두 개체의 ID를 비교한다.
    예시 코드처럼 클래스가 == 연산자를 오버로드할 가능성은 희박하지만, 만약을 대비해서 == None보다는 is None을 사용해야 한다.

6.2_ Formatting Strings [문자열 포매팅]

  • 문자열에 \가 많은 경우에는 원시 문자열 사용하기

    # Unpythonic Example
    print('The file is in C:\\Users\\Al\\Desktop\\Info\\Archive\\Spam')
      
    > The file is in C:\Users\Al\Desktop\Info\Archive\Spam
    
    # Pythonic Example
    print(r'The file is in C:\Users\Al\Desktop\Info\Archive\Spam')
      
    > The file is in C:\Users\Al\Desktop\Info\Archive\Spam
    

    아래의 예시와 같이 원시 문자열을 사용할 때 이스케이프 처리를 간단하게 할 수 있다.

  • f-strings을 통한 문자열 포매팅

    원시 문자열 처리에 r이 붙는 것처럼 f가 붙는다.

    name, day, weather = 'Al', 'Sunday', 'sunny'
    f'Hello, {name}. Today is {day} and it is {weather}.'
      
    > 'Hello, Al. Today is Sunday and it is sunny.'
    
    width, length = 10, 12
    f'A {width} by {length} room has an area of {width * length}.'
      
    > 'A 10 by 12 room has an area of 120.'
    

    위의 2가지 예시와 같이 변수를 간단하게 삽입하여 출력 할 수 있다.

    spam = 42
    f'This prints the value in spam: {spam}'
    > 'This prints the value in spam: 42'
      
    f'This prints literal curly braces: '
    > 'This prints literal curly braces: {spam}'
    

    내부에 중괄호를 사용해야 할 경우에는 중괄호를 추가로 사용하면 된다.

6.3_ Making Shallow Copies of Lists [리스트 얕은 복사본 만들기]

spam = ['cat', 'dog', 'rat']
eggs = spam
print(id(spam) == id(eggs))
> True

eggs.append('eggs')
print(eggs, spam)
> ['cat', 'dog', 'rat', 'eggs'] ['cat', 'dog', 'rat', 'eggs']

spam 리스트를 eggs 변수에 할당할 때, 실제로 새로운 리스트 객체를 생성하는 것이 아니라, eggs 변수도 spam 변수가 가리키고 있는 같은 리스트 객체를 참조하게 된다. 따라서 spameggs는 동일한 객체를 가리키게 되므로 id(spam)id(eggs)의 결과가 같게 되고, 둘 중 하나의 리스트에 변화가 생기면 동일한 객체를 가리키게 되면서 연동이 된것처럼 작동하게 된다. 이처럼 의도하지 않은 변화를 예방하기 위해 리스트를 복사 할 때, 주의해야한다.

spam = ['cat', 'dog', 'rat', 'eel']
eggs = spam[:]
print(id(spam) == id(eggs))
> False

리스트를 다음과 같이 shallow copy(얕은 복사)를 하게 되면 서로 다른 리스트 객체를 참조하게 된다.

# Pythonic Example
import copy
spam = ['cat', 'dog', 'rat', 'eel']
eggs = copy.copy(spam)
print(id(spam) == id(eggs))
> False

다음과 같이 copy 모듈의 copy() 함수를 사용하는 [:]보다 가독성이 좋다.

6.4_ Pythonic Ways to Use Dictionaries [파이썬다운 딕셔너리 사용]

  • 딕셔너리에서는 get()과 setdefault()를 사용

    - get()

    # Unpythonic Example
    numberOfPets = {'dogs': 2}
    if 'cats' in numberOfPets: # Check if 'cats' exists as a key.
      print('I have', numberOfPets['cats'], 'cats.')
    else:
      print('I have 0 cats.')
      
    > I have 0 cats.
    
    # Pythonic Example
    numberOfPets = {'dogs': 2}
    print('I have', numberOfPets.get('cats', 0), 'cats.')
    > I have 0 cats.
    

    딕셔너리에 get() 메소드로 접슨하면 키가 없을 때 지정한 기본값을 반환하도록 할 수 있다.

    - setdefault()

    # Unpythonic Example
    numberOfPets = {'dogs': 2}
    if 'cats' not in numberOfPets:
      numberOfPets['cats'] = 0
      
    numberOfPets['cats'] += 10
    print(numberOfPets['cats'])
    > 10
    
    # Pythonic Example
    numberOfPets = {'dogs': 2}
    numberOfPets.setdefault('cats', 0) # Does nothing if 'cats' exists.
    > 0
    numberOfPets['cats'] += 10
    print(numberOfPets['cats'])
    > 10
    

    위의 조건문 대신 setdefault() 메소드를 통해 기본값을 지정할 수 있다.

  • 기본 값을 위해서 collections.defaultdict을 사용

    import collections
    scores = collections.defaultdict(int)
      
    print(scores)
    > defaultdict(<class 'int'>, {})
      
    scores['Al'] += 1 # No need to set a value for the 'Al' key first.
      
    print(scores)
    > defaultdict(<class 'int'>, {'Al': 1})
      
    print(scores['Zophie']) # No need to set a value for the 'Zophie' key first.
    > 0
    scores['Zophie'] += 40
      
    print(scores)
    > defaultdict(<class 'int'>, {'Al': 1, 'Zophie': 40})
    

    collections.default 클래스를 사용하여 KeyError를 방지할 수 있다.
    아래처럼 int 타입을 지정하면 초기값이 0으로 설정된다.

    import collections
    booksReadBy = collections.defaultdict(list)
    booksReadBy['Al'].append('Oryx and Crake')
    booksReadBy['Al'].append('American Gods')
      
    print(len(booksReadBy['Al']))
    > 2
    print(len(booksReadBy['Zophie'])) # The default value is an empty list.
    > 0
    

    다음과 같이 빈 리스트를 초기값으로 설정할 수 있다.

  • switch 문 대신 딕셔너리 사용

    # All of the following if and elif conditions have "season ==":
    if season == 'Winter':
        holiday = 'New Year\'s Day'
    elif season == 'Spring':
        holiday = 'May Day'
    elif season == 'Summer':
        holiday = 'Juneteenth'
    elif season == 'Fall':
        holiday = 'Halloween'
    else:
        holiday = 'Personal day off'
    
    holiday = {'Winter': 'New Year\'s Day',
               'Spring': 'May Day',
               'Summer': 'Juneteenth',
               'Fall':   'Halloween'}.get(season, 'Personal day off')
    

    switch 문 대신 딕셔너리를 사용하여 코드를 간결하게 작성할 수 있다.
    get() 메소드를 통해 key가 존재하지 않으면 'Personal day off' 를 반환한다.
    하지만 switch 문 대신 딕셔너리를 사용할 경우 가독성이 떨어질 수 있어, 사용 여부는 사용자에게 달려있다.

6.5_ 변수값 작업

  • 체이닝 할당과 비교 연산자

    # Unpythonic Example
    if 42 < spam and spam < 99:
      
    # Pythonic Example
    if 42 < spam < 99:
    

    파이썬에는 chaining 비교 연산자가 있기 때문에 and를 사용할 필요가 없다.

    # Pythonic Example
    spam = eggs = bacon = 'string'
    print(spam, eggs, bacon)
    > string string string
      
    # Pythonic Example
    spam = eggs = bacon = 'string'
    print(spam == eggs == bacon == 'string')
    > True
    

    = 할당 연산자도 체이닝이 가능하다. 또한 == 비교 연산자도 체이닝 형식으로 사용할 수 있다.

  • 변수가 여러 값 중 하나인지 확인하기

    # Pythonic Example
    spam = 'cat'
    print(spam in ('cat', 'dog', 'moose'))
    > True
    

    spam == 'cat' or spam == 'dog' or spam == 'moose' 같은 기능을 간단하게 구현할 수 있다.


Reference


[Beyond the Basic Stuff with Python_Al Sweigart] - https://inventwithpython.com/beyond/
[Chapter 6 - Choosing Understandable Names] - https://inventwithpython.com/beyond/chapter6.html
CC License - [CC BY-NC-SA 3.0]

Translator - ChatGPT


댓글남기기