Velog글도 오랜만에 작성하는 것 같습니다. 그동안 부트캠프가 끝나고 나름대로 짧게 인턴도 해보고 여러가지 일이 있었는데 그건 다른 글에서 정리해보겠습니다. 이번 시리즈에서는 다시 기초로 돌아가 [파이썬 코딩의 기술(Effective Python) 개정 2판]을 천천히 시간날 때마다 정리해보려 합니다.
이 책이 처음부분을 대충보니 파이썬을 처음 접하시는 분들이라면 이 책말고 다른 책으로 시작하시길 바랍니다.
여러 해 동안 파이썬 커뮤니티 사람들은 특정 스타일을 따르는 코드를 묘사하기 위해 "파이썬답다(Pythonic)"라는 형용사를 사용해왔다. 여기서 "Pythonic"함은 컴파일러가 엄격하게 통제하거나 사용하길 강제하는 스타일이 아니다.
파이썬 프로그래머는 명시적인 것을 좋아하고, 복잡한 것보다 단순한 것을 좋아하며, 가독성을 최대한 높이려고 노력한다. 다음은 Python의 기본 철학을 담고있는 "파이썬의 선(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! 네임스페이스는 정말 대단한 아이디어다. -- 자주 사용하자!
만일 컴퓨터에 다양한 버전의 표준 CPython이 설치되어 있다면, 어떤 버전의 파이썬을 실행할지 잘 판단하여야 한다. 기본적으로 명령줄에서 파이썬을 실행할 때 디폴트로 어떤 버전이 실행될지 명확하지 않기 때문이다.
현재 사용중인 파이썬의 정확한 버전을 알고 싶다면 --version
플래그를 활용하면 된다.
python --version
다른 방법으로는 내장 모듈인 sys
의 값을 검사하면 현재 실행 중인 파이썬의 버전을 알아낼 수 있다.
import sys
print(sys.version_info)
print(sys.version)
파이썬 2는 2020년 1월 1일부로 수명이 다했다.여기서 말하는 수명이 다했다는 말은 더 이상 버그 수정, 보안 패치, 새로운 기능의 역포팅(backporting)이 이뤄지지 않는다는 뜻이다. 만일 파이썬 2로 작성된 코드를 사용해야 한다면 2to3(파이썬 설치 시 함께 설치됨)나 six같은 도구의 도움을 받아 파이썬 3로 코드를 포팅하는 방안을 고려하는 것이 좋다.
💡 포팅과 역포팅
포팅(Porting): 소프트웨어를 원래 설계된 바와 다른 컴퓨터 환경(CPU, 운영체제, 서드파티 라이브러리)에서 동작할 수 있게 변환하는 것을 의미한다.역포팅(Backporting): 소프트웨어 시스템 또는 소프트웨어 구성 요소의 최신 버전에서 일부를 가져와 동일한 소프트웨어의 이전 버전으로 이식(Porting)하는 것을 의미한다. 버그패치나 새로운 버전의 기능을 이전 버전에서 사용할 수 있도록 하는 것이다.
PEP는 Python Enhancement Proposal의 약자로 파이썬 개선 제안이라고 한다. 여기서 PEP 8은 파이썬 고드를 어떤 형식으로 작성할지 알려주는 스타일 가이드이다. 하지만 이 가이드는 말 그대로 가이드일 뿐이기 때문에 문법만 맞다면 어떤 방식으로든 원하는 코드를 작성해도 코드는 돌아간다.
하지만 일관된 스타일을 사용하면 코드에 더 친숙하게 접근하고, 코드를 더 쉽게 읽을 수 있다. 그리고 다른 파이썬 프로그래머들과 협업 시에도 더 쉽게 협력할 수 있다. 그리고 가이드를 따르면 나중에 코드를 수정하기 쉬울 뿐 아니라 흔히 저지르기 쉬운 실수도 피할 수 있다. 이제 각각의 가이드에 대해 한 번 살펴보자.
파이썬에서 공백(Whitespace)은 중요한 의미가 있다. 그렇기 때문에 공백과 관련한 다음 가이드라인을 따르는 것이 좋다.
# correct
def long_function_name(
var_one, var_two, var_three,
var_four):
print(var_one)
---------------------------------------
# wrong
def long_function_name(
var_one, var_two, var_three,
var_four):
print(var_one)
# Correct:
def munge(sep: AnyStr = None): ...
def munge(input: AnyStr, sep: AnyStr = None, limit=1000): ...
-----------------------------------------
# Wrong:
def munge(input: AnyStr=None): ...
def munge(input: AnyStr, limit = 1000): ...
명명규약을 제대로 사용하면 코드를 읽을 때 각 이름이 어떤 유형에 속하는지 쉽게 구분할 수 있다. 이름에 관련한 가이드라인은 다음과 같다.
lowercase_underscore
처럼 소문자와 밑줄을 사용한다.-> Snake Case(뱀표기법)위의 방법을 "맹글링(mangling)"이라고 한다. 파이썬에서는 실제로 비공개로 만들수는 없다. 다만 애트리뷰트 앞에 더블언더스코어를 붙히면 "_classname__variablename"과 같은 이름으로 변환되어 애트리뷰트 이름 자체로 접근할 수 없게되는 것이다.
ALL_CAPS
처럼 모든 글자를 대문자로 하고 단어와 단어 사이를 밑줄로 연결한 형태를 사용한다.self
를 사용해야 한다. 사실 반드시
self
일 필요는 없다. 다만 클래스에 들어있는 인스턴스 메서드는 항상 자기 자신의 객체를 첫 번째 인자로 입력받기 때문에 관습적으로self
를 사용하는 것이다.
cls
를 사용해야 한다.위와 마찬가지다. 클래스 메서드는 클래스 객체를 첫 번째 인자로 입력받기 때문에 관습적으로 cls를 사용한다.
"파이썬의 선"에서는 "문제를 해결할 명백한 방법이 하나 있으며, 가급적이면 유일해야 한다."라고 언급한다. PEP 8은 이런 가르침을 따라 식과 문장을 작성하는 스타일 규칙을 다음과 같이 정했다.
if not a is b
) 부정을 내부에 넣어라(if a is not b
).if len(something) == 0
)하지 말라. 빈 컨테이너나 시퀀스 값이 암묵적으로 False
로 취급된다는 사실을 활용해 if not 컨테이너
라는 조건문을 써라.True
로 평가된다는 사실을 활용하라.\
문자보다는 괄호를 사용하라.from x import y
)을 항상 파일 맨 앞에 위치시켜라.from bar import foo
라고 해야 하며, 단지 import foo라고 하면 안 된다.파이썬에는 문자열 데이터의 시퀀스를 표현하는 두 가지 타입이 있다. 바로 bytes
와 str
이다. 아래 코드같이 bytes
타입의 인스턴스에는 부호 없는 8바이트 데이터가 그대로 들어간다.
a = b'h\x65llo'
print(list(a))
print(a)
>>>
[104, 101, 108, 108, 111]
b'hello'
str
인스턴스에는 사람이 사용하는 언어의 문자를 표현하는 유니코드 코드 포인트(code point)가 들어 있다.
❗️코드 포인트: 문자열을 대표하는 정수값으로 문자열에 할당된 코드값 그 자체를 의미한다.
a = 'a\u0300 propos'
print(list(a))
print(a)
>>>
['a', '`', ' ', 'p', 'r', 'o', 'p', 'o', 's']
à propos
여기서 중요한 사실은 str 인스턴스에는 직접 대응하는 이진 인코딩이 없고 bytes에는 직접 대응하는 텍스트 인코딩이 없다는 점이다. 유니코드 데이터를 이진 데이터로 변환하기 위해서는 str
의 encode
메서드를 호출해야 하고, 이진 데이터를 유니코드 데이터로 변환하려면 bytes
의 decode
메서드를 호출해야 한다. 두 메서드를 호출할 시 인코딩 방식을 명시적으로 지정할 수도 있고, 시스템 디폴트 인코딩을 받아들일 수도 있다. 일반적으로는 UTF-8
이 시스템 디폴트 인코딩 방식이다.
일반적으로 프로그램을 작성할 때 인코딩하거나 디코딩하는 부분을 인터페이스(프로그램의 주요로직, 핵심부분)의 가정 먼 경계 지점에 위치시키길 권장한다. 이런 방식을 유니코드 샌드위치라고 부른다. 조금 쉽게 설명하자면 코드에서 이진 데이터를 유니코드 데이터로 변환하는 작업을 가능한 최대한 빨리 수행하고, 가능한 마지막에 유니코드 데이터를 이진 데이터로 변환하라는 것이다. python에서는 이런 작업을 open
모듈이 수월하게 해준다.
여기서 이진 8비트 값과 유니코드 문자열을 파이썬에서 다룰 때 꼭 기억해야 할 두가지 문제점이 있다.
bytes
와 str
이 똑같이 작동하는 것 같지만 각각의 인스턴스는 서로 호환되지 않는다. 그렇기 때문에 전달 중인 문자 시퀀스가 어떤 타입인지를 항상 잘 알고 있어야 한다.
print(b'one' + b'two')
print('one' + 'two')
>>>
b'onetwo'
onetwo
b'one' + 'two'
>>>
Traceback ...
TypeError: can't concat str to bytes
+
연산자를 사용하면 bytes
를 bytes
에 더하거나 str
을 str
에 더할 수 있지만 서로 다른 인스턴스끼리는 더할 수 없다. 비교연산자(<
등)도 마찬가지다.
그리고 같은 문자열이라고 한다할지라도 bytes
와 str
인스턴스가 같은지 비교하면 항상 False
가 나온다.
문자열 포메팅에 사용되는 %
연산자는 각 타입의 형식화 문자열에 대해 작동한다. 하지만 파이썬이 어떤 이진 텍스트 인코딩을 사용할지 알 수 없으므로 str
인스턴스를 bytes
형식화 문자열에 넘길 수는 없다.
print(b'red %s' % b'blue')
print('red %s' % 'blue')
>>>
b'red blue'
red blue
print(b'red %s' % 'blue')
>>>
Traceback ...
TypeError: %b requires a bytes-like object, or an object that
-> implements __bytes__, not 'str'
str
형식화 문자열에 bytes
인스턴스를 넘길 수는 있지만, 이 경우 예상과 다르게 작동한다.
print('red %s' % b'blue')
>>>
red b'blue'
위 코드는 실제로 bytes
인스턴스의 __repr__
메서드를 호출한 결과로 %s
를 대신한다. 따라서 b'blue'
가 출력에도 그대로 남는다.
__str__
,__repr__
두 메서드 모두 객체를 문자열로 반환하는 공통점이 있다. 하지만 상세하게 들어가면 차이가 존재한다.
__repr__
: 문자열로 객체를 다시 생성하여 출력 -> 개발자를 위함
__str__
:서로 다른 데이터 타입을 가진 데이터끼리 상호작용하기 위해 문자열로 바꿔줌 -> 사용자를 위함
내장 함수인 open을 호출해 얻은 파일 핸들과 관련한 연산들이 디폴트로 유니코드 문자열을 요구하고 이진 바이트 문자열을 요구하지 않는다는 것이다. 예를 들어 이진 데이터를 파일에 기록하려 할 때, 다음과 같이 간단해 보이는 코드도 오류가 발생한다.
with open('data.bin', 'w') as f:
f.write(b'\xf1\xf2\xf3\xf4\xf5')
>>>
Traceback ...
TypeError: write() argument must be str, not bytes
이는 파일을 열 때 이진 쓰기 모드(wb
)가 아닌 텍스트 쓰기 모드(w
)로 열었기 때문이다. 따라서 이진 데이터를 쓰기 위해서는 wb
모드로 열어야 한다.
이는 쓰기뿐만 아니라 파일을 읽으려고 할 때 역시 마찬가지이다. 핸들이 텍스트 모드에 있으면 시스템의 디폴트 텍스트 인코딩을 bytes.encode
(쓰기의 경우)와 str.decode
(읽기의 경우)에 적용해서 이진 데이터를 해석한다. 대부분의 시스템에서 디폴트 인코딩은 UTF-8인데, UTF-8인코딩은 이진 데이터를 받아들일 수 없기 때문에 오류가 발생하는 것이다.
만일 디폴트 인코딩이 의심스러운 경우에는 명시적으로 open
에 encoding
파라미터를 전달해야 한다.