More Control Flow Tools
이전 시간에 마지막 예제에서 피보나치 수열을 구현하면서 while
반복문을 써봤는데요, 이번 챕터에서는 그밖에 파이썬에서 제공하는 몇가지 유용한 명령어들을 더 배워볼거에요.
if Statements
아마도 if
문은 어느 언어에서나 가장 잘 알려진 명령어가 아닐까 생각이 되어집니다. 아래 예제를 보시면요,
>>> x = int(input("Please enter an integer: "))
Please enter an integer: 42
>>> if x < 0:
... x = 0
... print('Negative changed to zero')
... elif x == 0:
... print('Zero')
... elif x == 1:
... print('Single')
... else:
... print('More')
...
More
elif
나 else
는 생략해도 됩니다. elif
는 else if의 줄임말인데 만약에 if…elif…elif…이렇게 elif
를 여러번 써야하는 경우에는 다른 언어는 switch
, case
를 사용하는데요. 파이썬에서는 match
, case
를 이용하여 구현하실수 있습니다.
>>> a = 1
>>> match a:
... case 0:
... print('영')
... case 1:
... print('하나')
... case 2:
... print('둘')
...
하나
for Statements
파이썬에서의 for문은 C나 파스칼에서 사용되어지는 것과는 조금 다릅니다. 보통 파스칼에서는 산술적으로 숫자를 비교하면서 반복을 하게 되고, C에서는 반복 단계나 정지 조건을 주어 for문을 정의합니다. 하지만 파이썬에서는 list나 문자열같은 시퀀스를 이용해서 해당 시퀀스의 아이템을 하나씩 방문하는 식으로 반복문을 돌리게 됩니다. 아래 예제는 for문을 사용하여 문자열의 길이를 재는 코드입니다.
>>> words = ['cat', 'window', 'defenestrate']
>>> for w in words:
... print(w, len(w))
...
cat 3
window 6
defenestrate 12
말씀드렸다시피 파이썬에서는 for문의 조건문으로 list에 들어갑니다. 그런데 for문 안에서 해당 list의 내용을 수정하거나 길이를 변경한다면 for문의 조건이 되기에는 명확하지 않은 구성이 되어버립니다. 그렇게 코딩을 할수도 있긴 하겠죠 하지만 for문 안에서 조건이 바뀔 수 있다는 걸 염두해 두어야한다면 실제로 구현해야하는 로직에 촛점을 맞추기가 힘들어 질수도 있기때문에 추천하지 않습니다.
>>> users = {'Hans': 'active', 'Éléonore': 'inactive', '景太郎': 'active'}
>>> for user, status in users.copy().items():
... if status == 'inactive':
... del users[user]
...
>>> users
{'Hans': 'active', '景太郎': 'active'}
그래서 list의 내용을 바꿔서 저장해야하는 경우에는 보통 새로운 list를 만들어서 저장하거나 기존 list를 복사해서 결과를 따로 보관합니다.
>>> users = {'Hans': 'active', 'Éléonore': 'inactive', '景太郎': 'active'}
>>> active_users = {}
>>> for user, status in users.items():
... if status == 'active':
... active_users[user] = status
...
>>> users
{'Hans': 'active', 'Éléonore': 'inactive', '景太郎': 'active'}
>>> active_users
{'Hans': 'active', '景太郎': 'active'}
그러면 위의 예제와 같이 for문의 조건으로 사용하는 users
는 원래 그대로의 모습을 간직할 수 있고, active_users
에서 현재 활동중인 회원들만 추려서 볼수 있게 됩니다.
The range() Function
만약 list없이 기존의 C나 파스칼 처럼 숫자로 연산처리를 해서 for문을 돌리고 싶으실때는 파이썬의 빌트인 함수인 range()를 사용하시면 됩니다.
>>> for i in range(5):
... print(i)
...
0
1
2
3
4
여기서 넘겨받은 숫자 5
는 for문의 반복문에 포함되지 않습니다. range(5)
는 5개의 숫자를 출력하지만 range()
가 넘겨주는 숫자는 기본적으로 0
부터 시작되기 때문에 넘겨받은 인자 5
는 결과에 포함되지 않습니다. 만약에 숫자를 0부터 시작하고 싶지 않다면 시작할 숫자를 첫번째 인자에 명시하고 두번째 인자에 총 반복할 횟수를 적어주시면 됩니다. range는 아래와 같이 list에 저장할 수도 있는데요, 시작하는 숫자와 총 반복할 횟수 및 반복할 숫자의 간격을 인자로 넘겨주어 다양한 숫자의 list를 만들어 반복문에 사용할 수 있습니다.
>>> list(range(5, 10))
[5, 6, 7, 8, 9]
>>> list(range(0, 10, 3))
[0, 3, 6, 9]
>>> list(range(-10, -100, -30))
[-10, -40, -70]
만약 시퀀스의 인덱스로 반복문을 돌리고 싶으시면 range()와 len()을 같이 써서 아래와 같이 구현하실 수 있습니다.
>>> a = ['Mary', 'had', 'a', 'little', 'lamb']
>>> for i in range(len(a)):
... print(i, a[i])
...
0 Mary
1 had
2 a
3 little
4 lamb
대부분의 경우에 배열을 반복할때 enumerate()를 사용하시면 굉장히 편리합니다.
>>> seasons = ['Spring', 'Summer', 'Fall', 'Winter']
>>> list(enumerate(seasons))
[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]
>>> list(enumerate(seasons, start=1))
[(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')]
enumerate()에 list를 인자로 주어 반복문의 조건으로 사용하면 인덱스번호와 해당 인덱스의 값을 함께 가져올수 있어 매우 유용합니다.
>>> for i, v in enumerate(['tic', 'tac', 'toe']):
... print(i, v)
...
0 tic
1 tac
2 toe
아래는 Looping Techniques에서 소개한 반복문에 사용될 수 있는 테크닉들입니다.
items()
>>> knights = {'gallahad': 'the pure', 'robin': 'the brave'}
>>> for k, v in knights.items():
... print(k, v)
...
gallahad the pure
robin the brave
zip()
>>> questions = ['name', 'quest', 'favorite color']
>>> answers = ['lancelot', 'the holy grail', 'blue']
>>> for q, a in zip(questions, answers):
... print('What is your {0}? It is {1}.'.format(q, a))
...
What is your name? It is lancelot.
What is your quest? It is the holy grail.
What is your favorite color? It is blue.
reversed()
>>> for i in reversed(range(1, 10, 2)):
... print(i)
...
9
7
5
3
1
sorted()
>>> basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
>>> for i in sorted(basket):
... print(i)
...
apple
apple
banana
orange
orange
pear
set() & sorted()
>>> basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
>>> for f in sorted(set(basket)):
... print(f)
...
apple
banana
orange
pear
만약 파이썬 인터프리터에 range()만 딸랑 실행을 하면 조금 이상한 결과를 보게 될겁니다.
>>> range(10)
range(0, 10)
마치 아무것도 실행하지 않은 것처럼 말이죠. 많은 경우에 range()는 list타입의 데이타를 반환하는 것처럼 행동합니다. 하지만 사실 range는 list를 반환하지 않습니다. 왜냐면 실제 list를 반환하려면 list를 어딘가에 저장해야하는데 그렇게 한다면 너무 많은 공간을 써야하는 경우가 생기기 때문입니다. 그래서 range()는 필요한 list를 만들어 낼수 있는 정보가 들은 Object를 반환하고 for문은 그걸 이미 알고 있기 때문에 그 Object를 자동으로 list로 변환한뒤 반복문을 처리하게 됩니다.
요약하자면 range()가 반환하는 Object는 list를 처리할 수 있는 거의 대부분의 함수나 명령문에서 list처럼 처리를 해줍니다. for문에서 그러하듯이 sum()도 list를 받아서 해당 배열의 값들을 전부 더한 결과를 반환하는데 마찬가지로 range()로 생성된 숫자들도 list로 변환하지 않아도 다 합해서 결과를 반환합니다.
>>> a = [1, 2, 3, 4]
>>> sum (a)
10
>>> sum(range(1, 5))
10
나중에 Data Structures를 설명하는 챕터에서 list()
에 대해서 좀더 자세하게 설명드리도록 하겠습니다.
break and continue Statements, and else Clause on Loops
break
문은 for
문이나 while
문의 반복을 종료합니다. 반복문이 다중으로 겹쳐있을때는 break문이 들어가 있는 해당 반복문만 종료하고 그 반복문 바깥쪽에 선언된 반복문은 계속해서 실행합니다.
for문이나 while문은 else문을 포함할 수 있습니다.
- for문에서 else가 실행되는 시점은 마지막 for문을 다 돌고 나서 for문을 빠져나가기 직전에 else에 기술된 코드를 실행하고 나갑니다.
- while문에서는 while의 조건문이 false가 되는 시점에서 else안의 내용을 실행합니다.
- for와 while 두개 다 break에 의해서 반복문이 종료된다면 else는 실행하지 않고 그냥 반복문을 종료합니다.
아래의 소수찾기 코드가 else를 실행하는 좋은 예가 될것같은데 함께 보시죠.
>>> for n in range(2, 10):
... for x in range(2, n):
... if n % x == 0:
... print(n, 'equals', x, '*', n//x)
... break
... else:
... # loop fell through without finding a factor
... print(n, 'is a prime number')
...
2 is a prime number
3 is a prime number
4 equals 2 * 2
5 is a prime number
6 equals 2 * 3
7 is a prime number
8 equals 2 * 4
9 equals 3 * 3
위의 예제는 숫자 2에서 부터 9까지 돌면서 해당 숫자가 소수인지를 판별하는 코드입니다. 소수인지를 판별하는 방법은 해당 숫자로 나누어 떨어지는 수가 있으면 소수가 아닌게 되어서 해당 숫자 보다 작은 수로 나누어 떨어지는 값이 있는 지를 반복적으로 확인하고 만약 나누어 떨어지는 수가 있으면 소수가 아니라고 출력하고 break를 해서 else를 거치지 않고 다음 숫자로 넘어갑니다. 반대로 반복문을 다 돌도록 나누어 떨어지는 수를 하나도 발견하지 못했다면 break당하지 않고 반복문을 종료하게 되어서 이때는 else를 거치고 갑니다. else에 들어가는 숫자는 소수라는 의미니까 else에서 해당 숫자가 소수라는 것을 출력하고 다음 숫자로 넘어갑니다.
위와 같이 for문과 else절이 함께 사용되면 그 else절은 if문에서의 else절이라기 보다 try
문에서의 else절이라고 생각하면 의미가 좀 더 가깝습니다. try문의 else절은 예외가 발생하지 않았을때 실행이 되는 것과 마찬가지로, 반복문의 break가 발생하지 않았을때 else절이 실행되는 것과 일맥 상통합니다. try문과 예외처리에 대한 자세한 사항은 Handling Exceptions를 참고해주세요.
파이썬의 continue문은 C언어에서 보고 따라 만든건데요. 반복문 안에서 continue를 만나면 다음 숫자로 바로 넘어가라는 뜻입니다.
>>> for num in range(2, 10):
... if num % 2 == 0:
... print("Found an even number", num)
... continue
... print("Found an odd number", num)
...
Found an even number 2
Found an odd number 3
Found an even number 4
Found an odd number 5
Found an even number 6
Found an odd number 7
Found an even number 8
Found an odd number 9
pass Statements
pass
문은 “아무것도 안합니다”. pass는 문법적으로 필요한 부분인데 아무런 액션도 취해서는 안되는 부분을 프로그램 할때 사용됩니다.
>>> while True:
... pass # 키보드에서 인터럽트 키(Ctrl+C)를 칠때까지 기다립니다
...
^CTraceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyboardInterrupt
중간에 아무리 엔터를 쳐도 프롬프트가 나타나지 않고 계속 빈 줄을 보여주면서 기다리다가 Ctrl+C로 인터럽트를 하면 그제서야 상황이 종료가 됩니다. 보통 pass는 비어있는 클래스를 선언할때 주로 사용됩니다.
>>> class MyEmptyClass:
... pass
...
pass는 place-holder함수를 구현할때나 좀더 추상적인 단계에서 코딩을 할때 필요한 구조를 만들고 구체적인 내용은 나중에 생각하자는 마음으로 일단 필요한 함수들을 선언한 뒤에 딱히 내용이 없으면 에러가 나니까 안에다가 pass를 넣어 문법적으로 문제가 없도록 일단 만들어 놓는 것입니다. place-holder함수는 객체지향 코드시 클래스를 정의할때 클래스 안에 함수명만 일단 정의해 놓고 나중에 구현할때 내용을 채워서 재정의하라는 가이드라인같은 건데요. 이때 함수를 정의할때 pass만 들어가있는 빈 함수를 바로 place-holder함수라고 합니다.
>>> def initlog(*args):
... pass # Remember to implement this!
...
match Statements
match문은 표현식을 받아서 값과 비교하고 같은 값을 가지는 case블락에 들어가서 해당 코드를 실행합니다. 파이썬에서의 match문은 근본적으로 C언어나 자바, 자바스크립트(기타 많은 다른 언어들)의 switch문과 같습니다. 하지만 패턴이 매칭되는 매커니즘은 Rust나 Haskell과 좀더 유사합니다. 오직 첫번째로 매칭된 패턴만이 실행되고 또한 값에서 구성요소(시퀀스 요소 또는 객체 속성)를 변수로 추출할 수도 있습니다.
하나 이상의 값을 비교하는 가장 심플한 형태의 코드는 바로 match로 구현할 수 있습니다.
def http_error(status):
match status:
case 400:
return "Bad request"
case 404:
return "Not found"
case 418:
return "I'm a teapot"
case _:
return "Something's wrong with the internet"
마지막 case절을 보면 _
라는 변수가 있습니다. 이것은 와일드카드인데 매치되는 값이 하나도 없을때 _
로 정의된 case절에 매치가 되어 해당 블록의 코드를 실행합니다.
여러개의 값을 하나의 패턴으로 만들고자할때 |
(“or”)를 사용하여 아래와 같이 만들 수 있습니다.
case 401 | 403 | 404:
return "Not allowed"
case절의 패턴들은 변수와 섞어서 실행할때 마다 다른 패턴이 되도록 코딩할 수 도 있는데 예를 들면 다음과 같습니다.
# (x, y)는 집합입니다
match point:
case (0, 0):
print("Origin")
case (0, y):
print(f"Y={y}")
case (x, 0):
print(f"X={x}")
case (x, y):
print(f"X={x}, Y={y}")
case _:
raise ValueError("Not a point")
위의 예제는 좀더 자세히 연구해봐야합니다. 첫번째 패턴은 두개의 고정된 값을 가지며 위에 표시된 리터럴 패턴의 연장선으로 생각할 수 있습니다. 하지만 그 다음 두 패턴은 리터럴과 변수가 섞여 있습니다. 그리고 변수는 비교값 point에서 가져온 값과 바인딩합니다. 네번째 패턴은 2개의 변수를 가지고 값을 비교하는데 이를 표현식으로 바꿔보면 (x, y) = point
과 같습니다.
만약 여러분이 데이타를 구조화 하기 위해 클래스를 사용하신다면, 객체를 생성할때 생성자에 인자를 넘겨주는 것처럼 클래스이름 뒤에 각 변수를 나열하여 사용할 수도 있습니다. 다만 이 경우에는 속성값을 갖다 변수에 넣을 수 있습니다.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def where_is(point):
match point:
case Point(x=0, y=0):
print("Origin")
case Point(x=0, y=y):
print(f"Y={y}")
case Point(x=x, y=0):
print(f"X={x}")
case Point():
print("Somewhere else")
case _:
print("Not a point")
인자로 넘길때는 변수명을 적어주어도 되고, 그냥 값만 넘길 경우에는 인자의 순서에 따라 어떤 값이 어느 변수에 들어갈지가 결정이 됩니다. 아래의 예제처럼 변수명과 값을 둘다 넘겨줄 경우에는 순서가 뒤바뀌어도 상관없습니다.
Point(1, var)
Point(1, y=var)
Point(x=1, y=var)
Point(y=var, x=1)
case절에 들어가는 패턴은 아래의 예제와 같이 다중으로 조건을 설정할 수 있습니다.
class Point:
__match_args__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
match points:
case []:
print("No points")
case [Point(0, 0)]:
print("The origin")
case [Point(x, y)]:
print(f"Single point {x}, {y}")
case [Point(0, y1), Point(0, y2)]:
print(f"Two on the Y axis at {y1}, {y2}")
case _:
print("Something else")
case절에 if
문을 추가할 수도 있습니다. 만약 case절의 패턴이 True
라도 if
문의 조건이 False
이면 match
문은 다음 case절로 넘어갑니다.
match point:
case Point(x, y) if x == y:
print(f"Y=X at {x}")
case Point(x, y):
print(f"Not on the diagonal")
match문에 관한 몇가지 주요기능:
- Like unpacking assignments, tuple and list patterns have exactly the same meaning and actually match arbitrary sequences. An important exception is that they don’t match iterators or strings.
- Sequence patterns support extended unpacking:
[x, y, *rest]
and(x, y, *rest)
work similar to unpacking assignments. The name after*
may also be_
, so(x, y, *_)
matches a sequence of at least two items without binding the remaining items. - Mapping patterns:
{"bandwidth": b, "latency": l}
captures the"bandwidth"
and"latency"
values from a dictionary. Unlike sequence patterns, extra keys are ignored. An unpacking like**rest
is also supported. (But**_
would be redundant, so it is not allowed.) - Subpatterns may be captured using the
as
keyword:case (Point(x1, y1), Point(x2, y2) as p2): ...
will capture the second element of the input asp2
(as long as the input is a sequence of two points) - Most literals are compared by equality, however the singletons
True
,False
andNone
are compared by identity. - Patterns may use named constants. These must be dotted names to prevent them from being interpreted as capture variable:
from enum import Enum
class Color(Enum):
RED = 'red'
GREEN = 'green'
BLUE = 'blue'
color = Color(input("Enter your choice of 'red', 'blue' or 'green': "))
match color:
case Color.RED:
print("I see red!")
case Color.GREEN:
print("Grass is green")
case Color.BLUE:
print("I'm feeling the blues :(")
더욱 자세한 예제와 설명을 원하시면 PEP 636를 참고 하세요.
Defining Functions
우리는 임의의 바운더리안에서 피보나치 수열을 생성하는 함수를 만들 수 있습니다.
>>> def fib(n): # write Fibonacci series up to n
... """Print a Fibonacci series up to n."""
... a, b = 0, 1
... while a < n:
... print(a, end=' ')
... a, b = b, a+b
... print()
...
>>> # Now call the function we just defined:
>>> fib(2000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
예약어 def
는 함수를 정의하겠다는 명령어입니다. 곧이어 def
뒤로는 함수명이 뒤따르고, 괄호 안에 함수에 필요한 매개변수들이 나열됩니다. 다음 줄에서 실제 함수의 몸통 부분이 정의되고 그 내용은 반드시 들여쓰기가 되어있어야합니다.
함수의 몸통부분에 첫번째 줄에는 함수에 대한 설명을 적어넣을 수 있는 문자열이 들어가는데 함수명이 정의 되어 있는 바로 다음줄에 문자열이 나오면 함수에 대한 설명, 즉 주석정도로 인식하고 코드로 인식하지 않습니다. 함수설명은 필수 사항은 아니며, 함수명 정의하고 다음 줄에 문자열이 바로 나오지 않으면 함수 설명이 없는 것으로 간주하고 그냥 넘어갑니다. 근데 함수설명을 달아놓는 것은 좋은 코딩습관입니다. 왠만하면 항상 함수설명을 달아 놓도록 합니다. 사소하지만 습관이 들면 정말 좋은 코딩 습관이 될것입니다.
함수를 실행하면요 함수 안에서 변수들이 저장되는데요, 이때 심볼 테이블이라고 불리는 어떤 테이블형태의 저장공간을 만들어서 거기다가 지역변수들을 저장합니다. 반대로 변수를 참조할때는 먼저 지역 심볼 테이블을 찾고 그 다음에 어미 함수의 지역 심볼 테이블을 찾고 더이상 호출한 어미 함수가 없는 경우 전역 심볼테이블에서 해당 변수명을 찾습니다. 만약 거기서도 찾지 못한다면 파이썬에 내장되어 있는 변수명을 참습니다. 기본적으로는 전역변수와 함수를 호출한 어미 함수의 변수들은 자식 함수 안에서 바로 값을 할당할 수 없고 그저 참조만 할 수 있습니다. 다만, 함수내에서 전역변수에할당하고자 할때 변수명 앞에 global
이라고 명시하면 전역변수라도 함수내에서 할당이 가능하고요, nonlocal
이라고 명시하면 호출한 어미함수의 변수값도 변경이 가능합니다.
함수가 호출되면 함수명과 함께 전달된 매개변수들은 이 심볼테이블에 저장이 됩니다. 그 말은 함수와 함께 전달된 매개변수는 그 값이 복사되어 지역 심볼테이블에 저장되기 때문에 함수 내에서 아무리 그 값을 변경해도 원래 호출할때 사용했던 변수값에는 아무런 영향을 끼치지 못합니다.
근데 변수를 선언하면 사실 그게 Object인건 아시나요? 파이썬에서 모든 것들은 전부 Object입니다. 심지어 변수까지도요. 그리고 변수값은 해당 객체 안에 내부 변수로 저장이 되어있는 거라서 사실 변수명이 가리키고 있는것은 그 값이 아니라 객체의 주소에요. 다만, 그 객체에 접근하면 저장한 값을 반환하도록 설계된 클래스였던거죠. 그러니까 다시 말하면 매개변수를 넘기면 지역함수 안에 객체자체를 새롭게 생성한다는 말이에요.
함수가 호출되면 그 함수만의 심볼테이블이 생성되고, 심지어 재귀적으로 해당 함수를 호출해도 그것은 다 다른 호출로 인식이 되어 호출될 때마다 새로운 심볼테이블을 생성하여 함수 내에서 선언한 변수들과 인자로 전달받은 매개변수들을 그곳에 저장합니다.
함수를 정의하면요, 그 함수 이름과 함수에 정의된 내용 부분인 Object도 마찬가지로 심볼 테이블에 저장됩니다. 인터프리터는 해당 함수명이 가리키는 Object를 사용자 정의 함수로 인식하고요. 해당 함수명을 다른 함수명에 할당하면 새로 할당된 함수명으로도 호출할 수도 있습니다. 하지만 이때 Object가 새로 생성이 되어 할당이 되기때문에 똑같은 모양을 한 두개의 함수가 되는 것이지요. 아래의 예를 보면 이해가 빠르실겁니다.
>>> def a():
... print('원래함수')
...
>>> a()
원래함수
>>> b = a
>>> b()
원래함수
>>> def a():
... print('바뀐함수')
...
>>> a()
바뀐함수
>>> b()
원래함수
다른 언어들에서는 위의 a
같은 함수는 함수가 아니라 프로시저라고 할 수도 있겠습니다. 왜냐면 함수는 항상 값을 반환 해야 하는데 위의 함수는 처리만 하고 끝나버리는 함수니까요. 사실 파이썬 에서는 return
문을 명시하지 않아도 항상 값을 반환받게 됩니다. 바로 None
을 반환하지요. 아래 예제를 보시면 확인이 되실거에요.
>>> def c():
... pass
...
>>> print(c())
None
None
이 반환되는게 싫으시면 함수에서 특정 값을 반환하시면 됩니다.
>>> def c():
... result = []
... result.append(1)
... result.append(2)
... return result
...
>>> print(c())
[1, 2]
아래는 파이썬에 새로 추가된 특징들입니다:
return
문은 값을 반환합니다. 그런데 함수에서 아무 것도return
하지 않는다면None
이 반환됩니다.result.append(1)
은result
객체안의 메쏘드를 호출합니다. 메쏘드는 함수는 함수인데 객체 안에 “소속된” 함수를 메쏘드라고 부르고obj.methodname
형식으로 호출하게 됩니다. 여기서obj
는 객체이며,methodname
은 하나의type
으로써 객체 안에 선언된 함수입니다.
More on Defining Functions
함수를 정의할때 매개변수에 대한 특징을 미리 설정하는 것이 가능합니다. 3가지 형태가 있는데 복합적으로 섞어서 사용도 가능합니다.
Default Argument Values
가장 흔하게 쓰이는 방법은 바로 매개변수의 기본값을 설정하는 것입니다. 매개변수의 기본값이 설정되어 있는 함수는 정의된 매개변수의 개수보다 더 적은 변수가 인자로 들어왔을때 넘겨 받지 못한 변수에 대해서는 미리 설정해둔 기본값을 사용합니다.
def ask_ok(prompt, retries=4, reminder='Please try again!'):
while True:
reply = input(prompt)
if reply in {'y', 'ye', 'yes'}:
return True
if reply in {'n', 'no', 'nop', 'nope'}:
return False
retries = retries - 1
if retries < 0:
raise ValueError('invalid user response')
print(reminder)
이 함수는 다양한 형태로 호출이 될 수 있는데
- 첫째는, 반드시 넘겨주어야하는 기본값이 없는 인자만 가지고 호출했을 때 입니다. 이때는 나머지 두개의 매개변수는 미리 설정된 기본값을 가지게 됩니다:
ask_ok('Do you really want to quit?')
- 두번째는, 필수 매개변수와 함께 두번째 인자를 가지고 호출했을때 입니다. 이때는 두번째 인자가 기본값을 대체하여 넘겨받은 값으로 결과를 도출하고, 세번째 인자는 여전히 기본값을 가집니다:
ask_ok('OK to overwrite the file?', 2)
- 마지막으로, 매개변수 3개를 다 넘겨주었을때 입니다. 이때는 3개의 매개변수가 넘겨받은 값으로 설정되어 결과를 도출합니다:
ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')
위의 예제에서 in
이라는 연산자를 사용했는데요, 이것은 어떤 배열이나 리스트안에 원하는 값이 있는지 확인하기 위한 비교연산자입니다.
함수의 매개변수의 기본값은 변수로도 선언하여 좀더 동적인 함수를 만들수가 있는데요. 이때 주의 하셔야하실 점은 해당 변수의 값이 함수가 정의되는 시점에 어떤 값을 가지느냐에 따라서 매개변수의 기본값이 정해집니다. 나중에 해당 변수의 값을 바꿔도 이미 함수가 정의된 시점 이후에 값을 바꾸면 함수 매개변수의 기본값에는 영향을 미치지 않습니다.
>>> i = 5
>>>
>>> def f(arg=i):
... print(arg)
...
>>> i = 6
>>> f()
5
⛔️ 매우 중요한 경고 ⛔️
기본값은 오직 한번만 설정 될 수 있지만, 만약 기본값이 리스트나 딕셔너리처럼 변동이 가능한 객체라면 이야기가 달라집니다. 기본값에는 변수의 주소만 저장이 되기 때문에list
나dict
의 경우에 항목이 추가되면 기본값도 달라질 수 있습니다.
>>> def f(a, L=[]):
... L.append(a)
... return L
...
>>> print(f(1))
[1]
>>> print(f(2))
[1, 2]
>>> print(f(3))
[1, 2, 3]
만약에 기본값이 자꾸 바뀌는 상황을 만들고 싶지 않다면 함수 호출시 값이 안들어온 경우를 함수 내에서 if
로 잡아내서 기본값을 정해줄 수도 있습니다.
>>> def f(a, L=None):
... if L is None:
... L = []
... L.append(a)
... return L
...
>>> print(f(1))
[1]
>>> print(f(2))
[2]
>>> print(f(3))
[3]
Keyword Arguments
함수는 매개변수를 갖고 호출할때 값과 함께 변수명을 함께 넘겨주어 key=value
형태로 호출 할 수 있습니다. 아래 예제를 함께 보시죠.
def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
print("-- This parrot wouldn't", action, end=' ')
print("if you put", voltage, "volts through it.")
print("-- Lovely plumage, the", type)
print("-- It's", state, "!")
위의 함수는 voltage
라는 필수 매개변수를 하나 가지고, state
, action
, 그리고 type
이렇게 3개의 선택적인 매개변수를 가집니다. 이 함수는 아래와 같이 다양한 형태로 호출 할 수 있습니다.
parrot(1000) # 1개의 위치 매개변수
parrot(voltage=1000) # 1개의 키워드 매개변수
parrot(voltage=1000000, action='VOOOOOM') # 2개의 키워드 매개변수
parrot(action='VOOOOOM', voltage=1000000) # 2개의 키워드 매개변수
parrot('a million', 'bereft of life', 'jump') # 3개의 위치 매개변수
parrot('a thousand', state='pushing up the daisies') # 1개의 위치, 1개의 키워드
voltage
변수가 필수 매개변수이긴 하지만 key=value
형태로 호출 한다면 뒷쪽에 있어도 상관이 없습니다. 물론 key
가 없이 호출이 된다면 반드시 첫번째 인자로 넘겨주어야 하겠지만요. 아래는 이 함수를 호출할 시 에러가 나는 경우들 입니다.
parrot() # 필수 매개변수 없음
parrot(voltage=5.0, 'dead') # key=value로 호출을 시작하면 그 뒤로는 전부 key=value여야함
parrot(110, voltage=220) # 첫번째 위치가 이미 voltage인데 두번째 또 voltage를 넘김
parrot(actor='John Cleese') # actor라는 매개변수는 함수선언시 매개변수로 등록되지 않았음
주석에서 설명했듯이 필수 매개변수를 빼먹어서는 안되고, 앞쪽의 인자가 key=value
형태로 호출한다면 그 뒤로부터는 반드시 key=value
의 형식을 가져야합니다. 그리고 같은 인자가 또 들어와서는 안됩니다.
>>> def function(a):
... pass
...
>>> function(0, a=0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: function() got multiple values for argument 'a'
함수 호출시 같은 인자를 중복해서 넘겨주면 TypeError
가 나면서 같은 매개변수가 여러번 들어왔다는 에러 메세지가 뜹니다.
함수 설정시 매개변수 선언 부분 맨 마지막에 **name
이라는 형식으로 마지막 인자가 선언이 되면, 이건 dict
형태로 key=value
값을 가지는 여러개의 변수를 받겠다는 뜻입니다. 또한 *name
도 설정이 가능한데 이건 key없이 tuple
로 여러개의 값을 넘기겠다는 의미입니다. 그리고 이 두개는 섞어서 쓸수도 있습니다.
def cheeseshop(kind, *arguments, **keywords):
print("-- Do you have any", kind, "?")
print("-- I'm sorry, we're all out of", kind)
for arg in arguments:
print(arg)
print("-" * 40)
for kw in keywords:
print(kw, ":", keywords[kw])
위의 함수를 호출하는 모습은 다음과 같습니다.
cheeseshop("Limburger", "It's very runny, sir.",
"It's really very, VERY runny, sir.",
shopkeeper="Michael Palin",
client="John Cleese",
sketch="Cheese Shop Sketch")
그리고 이것은 아래의 결과를 출력합니다.
-- Do you have any Limburger ?
-- I'm sorry, we're all out of Limburger
It's very runny, sir.
It's really very, VERY runny, sir.
----------------------------------------
shopkeeper : Michael Palin
client : John Cleese
sketch : Cheese Shop Sketch
다중 값을 가지는 매개변수는 함수호출 시 나열한 순서와 동일하게 변수에 저장이 됩니다.
Special parameters
기본적으로 매개변수는 넘겨받는 위치라던가 아니면 key
를 명시해서 넘겨주는 방법이 있습니다. 읽기 좋고 성능이 좋은 코드를 위해서 매개변수를 받는 방법을 제한적으로 하도록 하는 것이 좋습니다. 그래서 개발자가 함수가 선언된 것을 읽을때 position
에 의해서만 매개변수를 넘겨받는 함수인지,position
과 keyword
를 함께 받는 함수인지, keyword
만 받는 함수인지를 바로 알수 있게 말입니다.
함수 선언은 다음과 같아야 합니다:
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
----------- ---------- ----------
| | |
| Positional or keyword |
| - Keyword only
-- Positional only
/
와 *
는 선택사항입니다. 만약 사용된다면 이 기호들은 어떻게 매개변수를 넘겨받을지 그 형식을 지정합니다. /
앞에 있는 매개변수는 position
으로만 넘겨줄수 있고, 그 뒤에 오는 매개변수는 position
과 keyword
를 둘다 허용하고, *
뒤의 매개변수들은 오직 keyword
로만 함수에 넘겨집니다.
Positional-or-Keyword Arguments
만약 함수선언시 매개변수에 /
나 *
가 보이지 않는다면 그건 바로 position
을 기준으로 매개변수를 넘기거나 keyword
로 넘기겠다는 걸 의미합니다.
Positional-Only Parameters
만약 함수선언시 매개변수가 /
앞에 선언되어 있다면 그 매개변수들은 오직 position
으로만 호출할 수 있는 매개변수들 입니다. 걔네들은 key=value
형태로 호출될 시 에러가 납니다. 만약 /
가 없다면 그 함수에는 position
으로만 접근할 수 있는 매개변수는 없다고 할 수 있겠습니다.
참고로, /
뒤에 따라오는 매개변수들은 position
으로도 넘겨줄수 있고, keyword
형식으로도 넘겨줄수 있으며, *
의 존재 여부에 따라 keyword
만 허용 할 수도 있습니다.
Keyword-Only Arguments
만약 함수의 매개변수를 key=value
형식으로만 받고 싶다면 *
를 넣으면 됩니다. *
를 첫번째 매개변수 앞에 넣으면 그 함수는 모든 매개변수를 key=
형식으로만 받을 수 있습니다.value
Function Examples
다음은 /
와 *
를 사용한 함수 선언의 예들입니다.
>>> def standard_arg(arg):
... print(arg)
...
>>> def pos_only_arg(arg, /):
... print(arg)
...
>>> def kwd_only_arg(*, arg):
... print(arg)
...
>>> def combined_example(pos_only, /, standard, *, kwd_only):
... print(pos_only, standard, kwd_only)
...
첫번째 함수, standard_arg
는 가장 익숙한 형태죠? 아무런 제약이 없기 때문에 position
에 맞춰서 value만 보내도 문제가 없고, key=value
의 형태로 호출해도 문제가 없습니다.
>>> standard_arg(2)
2
>>> standard_arg(arg=2)
2
두번째 함수, pos_only_arg
는 /
가 보이죠? /
앞의 모든 매개변수들은 오직 선언된 순서로만 넘겨줄수 있어 position
에 맞춰 value
만 넣어 호출합니다. key=value
로 호출을 시도하면 에러가 납니다.
>>> pos_only_arg(1)
1
>>> pos_only_arg(arg=1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: pos_only_arg() got some positional-only arguments passed as keyword arguments: 'arg'
세번째 함수, kwd_only_args
는 가장 처음에 *
가 있어서 그 뒤로 오는 모든 매개변수는 key=value
형태로만 호출이 가능하기 때문에 이 경우에는 모든 매개변수들을 keyword
와 함께 호출해야합니다. value만 넣고 호출한 경우에는 에러가 납니다.
>>> kwd_only_arg(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: kwd_only_arg() takes 0 positional arguments but 1 was given
>>> kwd_only_arg(arg=3)
3
마지막으로 /
와 *
를 둘다 넣어서 함수를 선언한 경우인데요, 이때는 3가지 특징이 복합적으로 작용하게 됩니다. 아래 호출한 예들을 보시면 더욱 이해가 쉬우실 거에요.
>>> combined_example(1, 2, 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: combined_example() takes 2 positional arguments but 3 were given
>>> combined_example(1, 2, kwd_only=3)
1 2 3
>>> combined_example(1, standard=2, kwd_only=3)
1 2 3
>>> combined_example(pos_only=1, standard=2, kwd_only=3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: combined_example() got some positional-only arguments passed as keyword arguments: 'pos_only'
마지막으로 한가지더! 바로 **kwds
를 사용할 때 주의하실 점이 있는데요. 설명드린대로 **
가 들어가면 key=value
형태의 배열을 dict
으로 넘겨받게 되어 매개변수의 이름을 지정할 수 없게됩니다. 만약 이때 **kwds
이전에 선언된 매개변수의 이름이 **kwds
에도 똑같은 이름으로 들어오게 되면 아무래도 함수안에서 혼선이 있을 수 있겠지요.
def foo(name, **kwds):
return 'name' in kwds
위의 함수는 만약 kwds
에 name
이라는 변수가 있는지 확인하고 있으면 True
를 반환하는 함수 입니다. 하지만 이 함수가 True
를 반환할 일은 절대 없습니다. 왜냐면 name
이라는 이름의 매개변수가 이미 존재하는 경우에 kwds
에 같은 key
를 넣어 함수를 호출하면 에러가 나기 때문입니다.
>>> foo(1, **{'name': 2})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'foo' is not defined
하지만 예외적으로 /
를 사용한 경우에는 이게 가능합니다. name
이 /
앞에 정의가 되어 있고, 그 이후에 **kwds
가 정의되었다면 그때는 name
이 kwds
안에 key
값으로 들어갈 수 있습니다.
>>> def foo(name, /, **kwds):
... return 'name' in kwds
...
>>> foo(1, **{'name': 2})
True
다르게 해석하면, position
으로만 호출이 되는 매개변수 name
은 /
와 **
를 복합적으로 사용해서 key=value
로도 호출을 할 수 있습니다.
Recap
아래의 경우들은 어떤 경우에 어떤 특징의 매개변수를 사용해야할지를 결정하는데 도움이 될겁니다.
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
가이드라인:
position
으로 매개변수를 구분하여value
만 보내는 경우: 사용자에게 함수의 매개변수 이름을 노출하고 싶지 않은 경우. 그리고 매개변수의 이름이 아무런 의미가 없는 경우에도 유용합니다. 아니면, 함수가 호출될때 매개변수의 순서가 중요한 경우에도/
를 쓸수 있구요, 마지막으로 positional 매개변수와 키워드 인수(**kwds)를 취하고 싶은 경우에 필요합니다.key=value
만 허용하는 경우: 매개변수의 이름이 큰 의미를 가질때 주로 사용하고, 그것으로 인해 함수를 사용하는데 있어서 훌륭한 설명으로 역할을 할때, 또는 변수명을 명시함으로 인해 순서만으로 이루어지는 경우 범할 수 있는 실수를 미연에 방지하고자 할때 사용됩니다.- 함수를
API
로 제공할때는 반드시position-only
로 매개변수명을 만드시기 바랍니다. 나중에 매개변수의 이름을 변경해야만 하는 상황이 되었을때 이미 공개된 API를 사용자들에게 변수명을 바꾸라고 다시 공지할 수 없으니까요.
Arbitrary Argument Lists
이번에는 파이썬에서 자주 사용되지 않는 옵션이지만 함수에서 전달할 값들을 나열한 객체를 매개변수로 넣고 전달하는 arbitrary number of arguments
에 대해서 이야기 해보도록 하겠습니다. 이 매개변수들은 Tuples로 구성되어있습니다(더보기 Tuples and Sequences). 보통 이 옵션을 사용하기 전에 일반 매개변수를 몇개 먼저 나열해도 되고 아니면 아무것도 안넣고 바로 *args
를 넣으셔도 됩니다.
def write_multiple_items(file, separator, *args):
file.write(separator.join(args))
보통 이 variadic arguments
는 매개변수 목록의 마지막에 옵니다. 왜냐면 이 특별한 매개변수는 함수에 전달된 다른 매개변수들을 다 잡아먹어버리거든요. 만약 다른 일반 매개변수가 *args
이 후에 와야하면 반드시 key=value
형태로만 받으세요.
>>> def concat(*args, sep="/"):
... return sep.join(args)
...
>>> concat("earth", "mars", "venus")
'earth/mars/venus'
>>> concat("earth", "mars", "venus", sep=".")
'earth.mars.venus'
Unpacking Argument Lists
매개변수가 이미 list나 tuple에 들어가 있을때 함수를 호출하여 unpacking해야하면 반대의 상황이 연출됩니다. 예를 들어, 빌트인 함수 range()가 있습니다. 이 함수는 start와 stop 이 두개의 매개변수를 기다리고 있습니다. 만약 그 변수들을 따로따로 넣는것이 어려운 상황이라면, 매개변수를 tuple이나 list에 담아 *
로 넘겨보세요. 동일한 결과를 기대할 수 있습니다.
>>> list(range(3, 6))
[3, 4, 5]
>>> args = [3, 6]
>>> list(range(*args))
[3, 4, 5]
이와 같은 맥락에서, dict
도 여러개로 정의된 key=value
매개변수를 **
연산자를 이용해서 한번에 전달 할 수 있습니다.
>>> def parrot(voltage, state='a stiff', action='voom'):
... print("-- This parrot wouldn't", action, end=' ')
... print("if you put", voltage, "volts through it.", end=' ')
... print("E's", state, "!")
...
>>> d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
>>> parrot(**d)
-- This parrot wouldn't VOOM if you put four million volts through it. E's bleedin' demised !
위의 함수에서 보듯이 여러개의 변수로 함수의 인자를 선언했지만 3개의 변수를 dict
에 묶어서 한번에 전달하는 것이 가능합니다.
Lambda Expressions
lambda라는 키워드를 사용해서 간단하게 혹은 임시로 쓸 함수를 생성할 수 있습니다. 다음의 매우 간단한 공식은 사실 함수이며, 두개의 매개변수를 받아 그 합을 반환합니다: lambda a, b: a+b
. 람다함수는 함수가 요구되는 어떤 상황에서도 사용될 수 있습니다. 람다는 문법적으로 매우 까다로운데 특히 모든 람다함수는 “한줄로” 정의가 되야합니다.
사실 람다함수는 일반 함수를 좀더 용이하게 선언하게 해주는 사탕과도 같은 기능입니다. 중첩 함수 정의와 마찬가지로 람다 함수는 포함 범위에서 변수를 참조할 수 있습니다.
>>> def make_incrementor(n):
... return lambda x: x + n
...
>>> f = make_incrementor(42)
>>> f(0)
42
>>> f(1)
43
위의 예제를 보시면 make_incrementor
라는 함수는 그 안에서 람다로 정의된 또 다른 함수를 반환합니다. 해당 함수를 호출할때 넘겨받은 매개변수 42
를 람다함수에서는 고정된 값으로 사용합니다.
>>> pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
>>> pairs.sort(key=lambda pair: pair[1])
>>> pairs
[(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]
위의 예제는 pairs
라는 tuple
로 구성된 list
를 정렬하는 코드입니다. list.sort()
를 호출할때 매개변수로 key
를 함께 호출할 수 있는데, 이때 이 key
라는 매개변수에 함수를 lambda
로 바로 선언해서 즉석에서 넘겨주죠. 이 sort
의 key
는 특별히 리스트의 아이템들이 하나의 변수로 구성된게 아니라 여러개의 값으로 이루어져 있을때, 해당 아이템의 변수중에 어떤 것을 기준으로 정렬을 해야할지에 대해서 람다로 설명하여 sort
에게 알려주는 겁니다. key
에 정의되는 함수는 해당 리스트의 아이템을 매개변수로 받고, 그중 하나를 선택하는 로직을 람다에 구현하여 결정한 값을 반환하면 sort
는 key
를 돌려 반환받은 값으로 정렬을 진행합니다.
Documentation Strings
이 챕터에서는 코드를 설명하는 doc-string
의 내용과 형식에 대해서 이야기 해보도록 하겠습니다.
첫번째 줄은 항상 짧고, 객체의 목적을 취합하여 간명하게 표현해야합니다. 코드에 대한 설명은 함수명이나 타입에서 주저리 주저리 설명해서는 안됩니다(함수명은 동사로 시작하는 것은 제외). 코드는 언제나 간략해야하며 추가 설명은 doc-string
에 명확하게 그리고 충분히 설명합니다.
만약 doc-string
에 여러줄의 설명이 들어가야 하는 경우 첫번째 줄에 간략한 설명을 넣고, 두번째 줄은 비워 두며, 세번째 줄부터 다른 설명을 시작합니다. 그렇게 간략한 설명과 나머지 자세한 설명간의 구분을 확실히 해둠으로써 필요에 따라 doc-string
을 활용할 수 있게 합니다.
파이썬 파서는 문자열이 여러줄에 걸쳐 있을때 들여쓰기가 제대로 안되어 있어도 문자열이기 때문에 들여쓰기를 고쳐주지 않습니다. 문자열안의 들여쓰기를 자동으로 맞춰주는 편집툴을 사용하거나 해서 임의로 들여쓰기를 맞추셔야합니다. 들여쓰기의 로직은 다음과 같습니다. 문자열 안의 첫번째 줄 다음 줄이 비어있지 않다면, 그 줄의 들여쓰기가 전체 문서의 들여쓰기 양의 기준이 됩니다(들여쓰기를 몇으로 할지를 첫번째 줄에서 알아내기란 쉽지 않습니다. 보통 문자열이 시작할때는 따옴표 바로 옆에서 시작하므로 들여쓰기 자체를 안하는 경우가 많으니까요). 이 들여쓰기 기준과 동일한 양의 공백은 모든 줄의 시작부분에서 삭제됩니다. 라인중에 들여쓰기가 기준한 양보다 적은 경우에는 삭제하지 않지만, 그 외의 모든 공백은 제거됩니다. 공백의 들여쓰기는 탭을 확장한 후 테스트가 되야합니다(보통 공백8개까지 확장합니다).
이에 대한 정확한 설명은 여기에서..
아래는 다중 docstring
의 예제입니다.
>>> def my_function():
... """Do nothing, but document it.
...
... No, really, it doesn't do anything.
... """
... pass
...
>>> print(my_function.__doc__)
Do nothing, but document it.
No, really, it doesn't do anything.
Function Annotations
함수 주석(Function Annotations)은 메타데이타 정보이며, 필수사항은 아닙니다. 이는 함수가 어떤 타입으로 작성이 되었는지를 자세히 기술합니다. (더 자세한 정보는 PEP 3107와 PEP 484를 참고 해 주세요).
주석은 __annotations__
에 함수의 내부정보로써 dict
형태로 존재하고, 주석이기때문에 실제 함수의 기능과는 관련이 없습니다. 매개변수 주석은 매개변수 이름 뒤에 콜론(:)을 찍고, 그 뒤에 해당 변수가 어떤 데이타 타입을 가지는지 적어줍니다. Return
할 데이타에 대한 설명은, 매개변수 목록과 def
문의 끝을 나타내는 콜론(:) 사이에 문자열 ->
를 찍고그 뒤에 반환할 결과의 데이타 타입을 기술합니다. 아래 예제는 필수 매개변수, 선택적 매개변수 그리고 반환할 결과물의 데이타 타입에 대한 주석을 표현한 것입니다.
>>> def f(ham: str, eggs: str = 'eggs') -> str:
... print("Annotations:", f.__annotations__)
... print("Arguments:", ham, eggs)
... return ham + ' and ' + eggs
...
>>> f('spam')
Annotations: {'ham': <class 'str'>, 'eggs': <class 'str'>, 'return': <class 'str'>}
Arguments: spam eggs
'spam and eggs'
Intermezzo: Coding Style
이제부터 여러분은 좀더 길고 복잡한 파이썬 코드를 작성하게 될거기 때문에 지금이 코딩스타일에 대해서 이야기하기 딱 좋은 타이밍이라고 생각합니다. 대부분의 언어들은 여러 다른 스타일로 좀더 간결하고 형식적으로 쓰여질 수 있습니다. 그래서 어떤 언어들은 다른 언어들 보다 좀더 읽기 편하기도 합니다. 다른 사람들에 의해 읽기 편한 코드를 작성하는 것은 정말 너무 훌륭한 일입니다. 그리고 깔끔하다고 생각되는 코딩스타일을 배워서 본인의 코드에 적용하는 습관도 엄청나게 훌륭하고요.
파이썬에서는 대부분의 프로젝트에서 도입한 코딩스타일 가이드로 PEP 8가 있습니다. 여기서 소개하는 스타일대로 코딩을 하면 읽기에도 좋고 눈이 편안한 코딩이 될것입니다. 모든 파이썬 개발자는 반드시 읽기를 추천드리며, 제가 여지 그중 몇가지 포인트를 추출해서 정리해보았습니다.
- 들여쓰기는 공백 4개를 이용하시고 탭은 사용하지 마세요. 공백 4개보다 작으면 들여쓰기가 반복되는 경우 유용하고, 그보다 크면 읽기가 편하지만 공백4개가 가장 적당한 크기 입니다. 탭을 사용하는 것은 혼동만 가져다 줄 뿐이기때문에 그냥 사용하지 마세요.
- 한줄에 문자가 79개 이상된다면 다음 줄로 나누어 코딩하세요. 어떤 개발자는 화면이 좁은 컴퓨터를 가지고 있을 수 있고, 때로 여러개의 코드를 양옆에 놔두고 같이 봐가면서 코딩을 해야하는 경우도 허다하기 때문에 너무 길게 코딩을 하는 것은 바람직하지 않습니다.
- 함수나 클래스 또는 큰 블락의 코드를 구분해서 보여주기 위해 그 사이에 빈줄을 넣어주세요.
- 주석은 가급적 코드와 같은 줄에 넣으시기 바랍니다.
- 함수나 클래스 선언할때
docstring
을 꼭 넣으세요 - 연산자나 콤마, 괄호등을 사용할 때는 보기 편하게 공백을 넣어주세요:
a = f(1, 2) + g(3, 4)
. - 클래스나 함수이름을 일관되게 지어주세요; 클래스는
UpperCamelCase
이런 식으로 (camel스타일이라고 하죠), 그리고 함수나 메소드는lowercase_with_underscores
이런식으로 (이건 snake스타일입니다) . 클래스안에서 내부함수를 정의할때는 메소드의 첫번째 매개변수로self
를 항상 넣어 주세요(클래스와 메소드에 대한 더 자세한 내용은 A First Look at Classes을 참고해주세요). - 코드파일을 이상한 엔코딩으로 설정하지 마세요. 전세계적으로 통용되는 캐릭터셋은
UTF-8
입니다. 여러분의 코드는 국제적으로 사용이 되어질거니까 특정 국가나 언어에 국한되는 엔코딩은 지양해 주세요. 파이썬은 기본값으로UTF-8
을 채택하였으면ASCII
도 때에따라 사용하기 좋은 엔코딩입니다. - 구분자로 선택하는 값은 ASCII표에 있는 문자로만 사용하세요. ASCII에서 지원하지 않는 문자를 코드파일에 넣으면 다른 언어를 사용하는 개발자 환경에서는 깨져서 보일수도 있습니다.