값을 가지고 있는 이름을 아울러 변수(variable)라고 부른다. 예로, 다음 코드에서 이름 a는 0이라는 값을 가진다.
a = 0
print(a)
결과
0
파이썬에서는 첫 번째 줄처럼 등호(=)를 기준으로 좌측에 이름을, 우측에 값을 두어 변수를 만들 수 있다. 이를 변수 정의라고 한다.
두 번째 줄의 print(a)는 변수를 사용한 예다. a가 글자 그대로 출력되지 않고, 대신 그 이름이 가지고 있던 값인 0이 출력되었다.
이처럼 변수는 값을 저장하고 있기 때문에, 특정 값을 계속해서 유지시키고 재사용하는 데에 응용할 수 있다.
점수를 계산하는 프로그램을 작성하고 있다고 하자. 반 평균 점수와 본인의 점수를 비교해서, 평균과 얼마나 차이나는지를 계산하는 단순한 계산 코드다. 예제에 나오는 식은 그냥 산수와 동일하게 생각하면 된다.
print(82 - 76.5)
결과
5.5
이 프로그램은 한 줄의 코드만으로 이루어져 있지만, 이렇게 82와 76.5처럼 값만 사용해서는 각각이 무슨 의미를 담고 있는지 알기 어렵다. 다음의 코드를 살펴보자.
my_score = 82
average_score_of_class = 76.5
print(my_score - average_score_of_class)
결과
5.5
마지막 줄의 print문에 포함된 식을 한국어로 해석해보면, 내 점수 - 반의 평균 점수가 된다. 그냥 82와 76.5라는 값이 달랑 있었을 때보다 의도를 파악하기가 더 쉽다. 이처럼 값에 이름을 매겨, 그 값의 의미를 전달하는 것이 변수의 목적 중 하나다.
첫 단원에서 알아봤던 print는 소괄호 안에 전달한 값을 터미널에 출력하는 동작을 한다. input은 그와 반대로 사용자가 터미널에서 Enter를 입력하기까지 기다리고, Enter가 입력되면 그 때까지 사용자가 입력한 값을 가져온다. 다음 코드를 실행하면, 1번째 라인의 input()에 의해서 터미널에 Enter가 입력되기 전까지 프로그램이 일시정지된다. Enter가 입력되면, 그 전까지의 내용이 input()의 자리를 대체한다.
print(input())
입력
123
결과
123
이 예제는 input의 결과를 print에 즉시 사용하고 있다. 만약 input의 결과를 저장해 뒀다가 필요할 때 사용하고자 한다면, 변수를 이용할 수 있다.
x = input()
print(x)
입력
123
결과
123
다음 조건에 해당하는 이름은 변수명으로 사용할 수 없다.
예약어는 프로그래밍 언어에서 문법적인 용도로 사용되고 있어, 변수의 이름과 같은 식별자에 사용할 수 없는 것을 말한다. Python 3.9 버전을 기준으로, 총 35개의 예약어가 있다.
False await else import pass
None break except in raise
True class finally is return
and continue for lambda try
as def from nonlocal while
assert del global not with
async elif if or yield
스펠링이 정확히 일치하는 경우에만 사용 불가능하다. 예를 들어 False는 예약어라서 변수명으로 사용할 수 없지만, false는 사용할 수 있다.
false = 0
print(false)
결과
0
이런 규칙은 변수명을 비롯해 뒤에서 배울 함수, 클래스의 이름처럼 모든 식별자(identifier)에 대해 공통으로 적용되는 규칙이다.
PEP 8은 변수 정의문에서 등호의 앞뒤에 하나의 공백(whitespace)을 포함시킬 것을 권고한다.
a=0 # Incorrect
b = 1 # Correct
PEP 8은 영어 소문자로 이루어진 명사나 명사구를 사용해 변수 이름을 짓는 것을 권장한다. 단어 여러 개가 사용되어 명사구 형태가 되는 경우, 단어 각각을 언더스코어(_)로 구분하도록 한다. 앞에서 사용한 my_score같은 식이다. 이렇게 영어 소문자와 언더스코어로 이름짓는 것을 Snake Case라고 부른다. 대소문자를 어떻게 사용하는지, 단어의 구분을 어떻게 하는지에 따라 몇 가지 규칙들이 존재한다.
다음은 똑같이 My score라는 이름을 각각의 방식으로 네이밍한 것이다.
PEP 8은 변수의 네이밍에 Snake Case를 사용하길 권고한다.
일부 글꼴에서 알파벳 l(L의 소문자)이나 I(i의 대문자)는 숫자 1, O(o의 대문자)는 숫자 0과 구별하기 어려울 수 있다. 때문에 이름을 지을 때 이 부분을 조심할 것을 권고한다. 이름에 l이나 I가 단독으로 사용되거나, l과 I, 1이 함께 사용되는 것이 그 예다.
l = 1
I = 1
Il1 = 1
O = 0
변수 정의문은 등호를 기준으로 왼편과 오른편을 나누어 생각하는 것이 좋다. 각각을 좌변과 우변으로 부르겠다. 다음 예제에서 a는 좌변이고, 0은 우변이다.
a = 0
프로그래밍에서 변수 정의문은 우변을 평가한 후 그 값을 좌변에 할당하는 규칙을 따른다.
a = 5 + 2
연산자 개념이 등장했지만, 그냥 산수의 더하기로 이해하면 된다. 해당 예제에서, a가 저장하고 있는 값은 7이 된다. 우변의 5 + 2를 평가(계산)한 결과 7을 좌변의 a에 할당하기 때문이다.
값이 동일한 변수를 여러 개 정의하는 경우, 다음과 같이 할 수 있다. Chained assignment라고 부른다.
a = b = c = 5
서로 다른 값을 가진 변수를 여러 개 정의하는 경우, 다음과 같이 할 수 있다.
a, b, c = 1, 2, 3
이는 사실 파이썬이 가진 Unpacking이라는 기능을 응용한 것인데, 이번 단원의 주제를 벗어나므로 'Level 3'에서 구체적으로 다룬다.
파이썬에서는 다음처럼 변수 정의문을 작성할 수 있다.
a, b, c = 1, 2, 3
a에는 1, b에는 2, c에는 3이 할당된다. 언뜻 보기에는 변수 정의를 위한 특별한 문법으로 보이지만, 이는 tuple 리터럴이 괄호를 생략할 수 있다는 점과 Unpacking 기능을 응용한 것일 뿐이다. 다음 예제는 앞의 코드를 두 줄에 걸쳐 풀어본 결과다.
t = (1, 2, 3)
a, b, c = t
변수의 이름에는 예약어를 사용할 수 없다. 이런 예약어 정보를 얻기 위해 keyword 모듈을 사용할 수 있다. 예약어의 목록을 list로 정의한 kwlist, 문자열이 예약어에 해당하는지 여부를 bool로 리턴하는 iskeyword 함수가 있다. 예약어의 양이 꽤 많기 때문에, kwlist의 출력 결과는 일부러 조금 생략했다.
import keyword
print(keyword.kwlist[:10])
print(keyword.iskeyword('None'))
print(keyword.iskeyword('hello'))
결과
['False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break', 'class']
True
False
id 함수를 통해, 대상이 참조하고 있는 메모리 주소를 알아낼 수 있다.
a = 1
print(id(a))
결과
2051761248
값으로 list를 가진 a가 있다고 하자. 만약 이 a를 b라는 변수에 할당하는 경우, a와 b의 id는 같아지게 된다.
a = []
b = a
print(a is b)
결과
True
여기서 b가 참조하는 객체의 내용을 변화시키면, a에 이 변화가 전이된다. 같은 메모리 주소를 참조하고 있기 때문이다.
a = []
b = a
a.append(1)
b.append(2)
print(a)
print(b)
결과
[1, 2]
[1, 2]
이 문제를 해결하기 위해서는, 객체의 내용은 복사하되 새로운 메모리 주소를 가지게 해야 한다. 이를 위해 copy 모듈을 사용할 수 있다.
from copy import copy, deepcopy
l = []
l_copy = copy(l)
l_deepcopy = deepcopy(l)
print(l is l_copy)
print(l is l_deepcopy)
l.append(1)
l_copy.append(2)
l_deepcopy.append(3)
print(l)
print(l_copy)
print(l_deepcopy)
결과
False
False
[1]
[2]
[3]
copy와 deepcopy 함수는 shallow copy와 deep copy의 차이라고 정리할 수 있다. shallow copy는 최소한의 내용만을, deep copy는 모든 요소를 복제한다. 예제를 보면 쉽다.
from copy import copy, deepcopy
l = [1, 2, 3, [4, 5]]
l_copy = copy(l)
l_deepcopy = deepcopy(l)
print(l_copy[-1] is l[-1])
print(l_deepcopy[-1] is l[-1])
결과
True
False
list 타입의 변수 l은 마지막 번째 요소로 list를 갖는다. copy 함수를 통해 l을 shallow copy해 l_copy에, deepcopy 함수를 통해 l을 deep copy해 l_deepcopy에 할당했다.
l_copy의 내부 list는 l의 것과 동일한 id를 가지는 데에 비해, l_deepcopy의 내부 list는 l의 것과 다른 id를 가진다.
Chained assignment는 가장 좌측의 대상부터 값을 할당한다.
a = b = 0
여기서, 모든 할당에는 우변의 값이 동일하게 사용된다. 이는 참조 또한 그대로 복제된다는 의미다.
a = b = []
print(a is b)
결과
True
이러한 특정 때문에, 우변에 Mutable 객체가 사용되었을 때 문제가 발생할 수 있다.
a = b = []
a.append(3)
print(b)
결과
[3]
Chained assignment를 통해 a와 b 모두에게 빈 list를 할당한 뒤 a의 내용을 수정했는데, b에도 이 수정 사항이 반영됐다.
따라서, 변수 정의문에 chained assignment를 사용하는 것과 unpacking을 응용하는 것은 메모리 입장에서 완전히 다르다.
a = b = []
c, d = [], []
print(a is b)
print(c is d)
결과
True
False