파이썬 강좌 – 변수와 리스트 ~ 비슷한 변수들을 묶기

  1. 프롤로그
  2. 개발 첫걸음
    1. 컴퓨터 구성요소 - 컴퓨터는 어떤 걸 할 수 있나?
    2. 개발과 관련된 용어
    3. 파이썬의 선택 - 왜 파이썬인가?
    4. 파이썬 설치 - Hello World 출력하기
    5. Visual Studio Code 의 편리한 기능
    6. REPL과 콘솔 창 - 파이썬 동작시키기
  3. 파이썬 기초
    1. 기초 입출력 - 소통하기
    2. 변수와 대입 - 기억하기
    3. 연산자 - 계산하기
    4. 조건문 - 분기를 만들기
    5. 반복문 - 비슷한 작업을 반복하기
    6. 변수와 리스트 - 비슷한 변수들을 묶기
    7. for, range - 리스트의 항목을 다루기
    8. 파이선 기초 꿀팁
      1. 함수와 메소드의 호출 - 편리한 기능 이용하기
      2. 모듈 설치와 사용 - 유용한 기능 끌어다 쓰기
      3. 문자열 - 텍스트 다루기
  4. 파이썬 중급
    1. 정규표현식 - 문자열을 검색하고 치환하기(작성중)
    2. 함수를 직접 만들기 - 자주 쓰는 기능을 묶기
    3. 딕셔너리, 튜플, 세트 - 변수를 다양한 방법으로 묶기
    4. 클래스와 객체 - 변수를 사람으로 진화시키기
    5. 상속 - 클래스를 확장하기
    6. 파이썬 중급 꿀팁
      1. 코드를 작성하는 사람의 의도(작성중)
      2. 정체성과 동질성 - 객체의 성질(작성중)
      3. 명령문, 표현식 - 문법을 이루는 것들 (작성중)
      4. 슬라이싱 - 리스트를 갖고 놀기
  5. 파이썬 고급
    1. 예외와 에러 - 예상치 못한 상황에 대응하기
    2. 특별 메소드와 연산자 - 파이썬의 내부 작동방식 이해하기
    3. 다양한 함수 인수 - 유연한 함수 만들기
    4. 시퀀스와 반복자 - 반복과 순회를 자유자재로 다루기
    5. 변수의 범위 - 이름 검색의 범위
  6. 파이썬 심화
    1. 제너레이터와 코루틴 -
    2. async와 await
    3. 데코레이터 - 함수의 기능을 강화하기
    4. 객체로서의 클래스 - 클래스를 동적으로 정의하기
  7. 파이썬 프로젝트 실습
    1. 원카드 게임 만들기 (1)
    2. 원카드 게임 만들기 (2)
  8. 실전 (파이썬 외적인 것들)
    1. 유니코드 - 컴퓨터에서 문자를 표기하는 방법
    2. html, css, 인터넷 - 자동화 첫 걸음 내딛기
    3. 네트워크 - 인터넷으로 통신하는 방법
    4. 문서 - 문맥을 읽어보기

학습 목표

이번 시간에 배울 항목은 다음과 같습니다.

  • 변수의 자세한 동작 과정
  • list 개념
  • print, max, minlist의 티키타카
  • list의 각종 기능

학생들 점수를 다루기

코딩의 어려움

학생들 10명의 점수를 받고자 합니다. 그리고 이 점수들을 계속 프로그램 실행 중에 갖고 있으려고 합니다. 학생 하나하나의 점수는 중요하고, 여러가지 통계를 내는 데 점수를 계속 기억하고 있으면 좋으니까요.

다음은 while문을 통해서 평균 구하는 프로그램입니다.

i = 0
scoreAll = 0
while i < 10:
    scoreAll += int(input())
    i += 1
average = scoreAll / 10

print(average)
2
3
4
5
6
1
2
3
4
5
3.5

변수 i는 오직 while의 루프 횟수를 조정하기 위한 변수입니다. i = 0으로 초기화를 해주었고 i < 10일 때까지 동작하며 루프를 돌 때마다 1씩 증감해주고 있습니다. i0부터 9까지 변화하면서 루프는 총 10회 동작하게 되겠습니다.

우리의 의도대로 동작합니다. 하지만 점수 정보는 scoreAll 변수에 대입되고 사라집니다. 우리는 input()으로 입력받은 모든 값들을 기억하고 싶습니다. 그럼 10개의 변수를 써야만 하는 것일까요? 다음 예시를 봅시다.

student0 = int(input())
student1 = int(input())
student2 = int(input())
student3 = int(input())
student4 = int(input())
student5 = int(input())
student6 = int(input())
student7 = int(input())
student8 = int(input())
student9 = int(input())

scoreAll = (student0 + student1 + student2 + student3 + student4 + 
    student5 + student6 + student7 + student8 + student9)

average = scoreAll / 10 # 평균입니다.

maxScore = max(student0, student1, student2, student3, student4, 
    student5, student6, student7, student8, student9)

minScore = min(student0, student1, student2, student3, student4, 
    student5, student6, student7, student8, student9)

print("평균은", average, "입니다.")
print("최대 점수는", maxScore, "입니다.")
print("최소 점수는", minScore, "입니다.")
3
5
4
6
10
40
20
70
30
48
평균은 23.6 입니다.
최대 점수는 70 입니다.
최소 점수는 3 입니다.

파이썬에서는 마음대로 줄을 바꾸거나 들여쓰기를 하면 안 됩니다. 그런 것도 하나의 엄연한 문법이기 때문이지요. 하지만 한 줄에 너무 긴 코드가 들어갈 경우에는 가독성을 헤치는 문제가 발생합니다. 이러한 상황을 극복하기 위해 파이썬에서는 소괄호로 묶는다면 줄을 바꿔도 된다는 규칙 이 있습니다. 위 예제에서는 student0 부터 student9 까지 더하는 데 소괄호로 묶어서 줄을 바꿨습니다.

print는 쉼표(,)를 이용해 여러 항목을 넣을 수 있습니다.

maxmin은 아래에서 다시 설명하도록 하겠습니다. 우선은 쉼표로 구분된 항목들 중 최댓값과 최솟값을 구하는 함수라고 이해하시면 되겠습니다.


뭔가 문제가 보이시나요? 독립적인 변수를 student0부터 student9까지 만들어 각각 값을 할당하고 있습니다. 이런 상황이 되었을 때 우리는 다음과 같은 문제점을 봉착하게 됩니다.

  • 코드의 중복이 늘어납니다. int(input())가 10번 반복되고 있습니다. 학생들이 늘어날 때마다 int(input())을 한 번 더 써주어야 할 것입니다. 코드가 계속해서 중복되면 처리가 힘들어집니다.
  • 한번에 처리하기가 힘듭니다. 학생들의 합계를 구하려면 student0부터 student9까지 일일히 더해야 합니다. 최댓값과 최소값을 구하는 max, min 함수를 이용할 때에도 마찬가지입니다. 학생들이 늘어날 때마다 쉼표와 변수명을 더 추가해서 적어주어야겠죠.

그래서 우리는 어떤 비슷한 목적을 가진 변수들을 한데 모아 손쉽게 관리해줄 수 있는 무언가가 있으면 참 좋겠다고 생각이 듭니다. 그렇다면 코드를 중복해서 쓰지 않아도 되고, 한번에 처리하기 쉬워질 텐데요.

이 때 등장하는 혜성은 바로 list 입니다. 리스트는 여러가지 값을 가지고 있는 변수입니다. 무슨 말인지 이해가 잘 안되시죠? 일단 변수의 본질을 다소 파헤쳐보고, 리스트의 기본적인 사용법을 익힌 후 위 예제를 수정해봅시다.


리스트의 초초 기본 사용법

scoreList = [1, 2, 3]
print(scoreList)
[1, 2, 3]

scoreList는 우리가 평소처럼 변수를 사용하는 것처럼 이름을 정했습니다. 그리고 대입문의 오른쪽에는 [1, 2, 3]이 등장했네요. 여기서 대괄호 []의 뜻은 리스트를 새롭게 만들겠다는 뜻이고, 1, 2, 3은 이 리스트가 가지고 있을 변수를 지정해준다는 뜻입니다.


우리가 이전에 변수의 종류에서 int, float, bool, str 등을 배웠었지요, list도 동일선상의 개념입니다. 리스트 또한 다른 변수와 마찬가지로 대입을 통해 값을 변경할 수 있습니다.

a = [1, 2, 3]
print(a)
a = ["abc", "cde", "efg"]
print(a)
[1, 2, 3]
['abc', 'cde', 'efg']

리스트는 대괄호를 이용해서 항목 하나하나에 접근할 수 있습니다. 변수명을 먼저 쓰고 그 직후에 [숫자]를 적으면 됩니다. 0이 첫 번째 항목이고, 1이 두 번째 항목입니다. 예제를 보겠습니다.

a = [1, 2, 3]
print(a[0])
print(a[1])
1
2

리스트 내부에는 여러 개의 값을 가질 수 있습니다. 하지만 이는 예전에 배운 개념과 다소 혼동됩니다. 변수 하나는 하나의 값을 가질 수 있던 게 아닌가요? 어떻게 변수 하나가 여러 개의 값을 가질 수 있는 것일까요? 이는 변수의 동작 방식을 조금 더 파헤쳐야 비로소 이해할 수 있습니다.


변수에 대한 통찰

변수의 본질

변수의 본질을 더 자세히 살펴봅시다.

아주 초기에 컴퓨터에 대한 공부를 할 때 메모리의 존재에 대해 기억하시나요? 메모리는 프로그램이 실행되는 동안의 모든 정보가 저장되는 곳입니다. 변수 또한 계속해서 메모리에 새롭게 생성되고 삭제됩니다. 메모리를 거대한 창고라고 상상하세요. 파이썬 인터프리터가 거대한 메모리라는 창고에서 변수로 값을 찾는 과정은 다음과 같습니다.

파이썬 인터프리터의 동작 실생활의 예시
변수명을 읽는다. 물건의 이름을 인식한다.
변수의 종류(타입)을 파악한다. 물건의 종류, 크기 등을 파악한다.
변수명에 대응되는 메모리의 주소를 얻는다. (예: 1219942213) 재고표에서 물건의 위치를 파악한다. (예: F구역 왼쪽 선반의 3번째)
해당 주소에서 값을 종류(타입)에 맞게 추출한다. 해당 위치로 가서 물건을 찾는다. (물건이 크다면 카트트로 물건을 빼온다.)

여기서의 핵심은 실제 데이터(값), 변수의 이름(name), 변수의 타입(type), 이 세 가지를 구분해서 생각할 수 있다는 점입니다. 실제 데이터는 01의 조합으로 메모리에 저장되어 있습니다. 이 데이터를 어떻게 조작할 지가 관건인데, 실생활의 예시에서처럼 일종의 재고표가 있어 파이썬 인터프리터는 변수 이름을 통해 메모리의 위치를 즉각적으로 알 수 있고, 변수의 타입으로 메모리의 값을 어떻게 얼마나 읽고 쓸지 알 수 있습니다.

이제부터 강좌 통틀어서 처음으로 다이어그램이 등장하는데요, 변수는 다음과 같이 표현하도록 하겠습니다. 타입에 관한 정보는 따로 표시하지 않습니다.

a = 5
b = '안녕'
이름과 데이터의 구분 다이어그램 이름과 데이터의 구분

실생활과 컴퓨터의 차이점

실생활의 창고에서 물건을 찾을 때에는 사실 위치정보를 대충 알아도 됩니다. 왜냐하면 물건끼리 구분이 어느정도 되어있기 때문입니다. 나사 하나를 찾아본다고 상상해보세요. 물리적으로 하나하나 분리되어 있기 때문에 대략적으로 어디에 있는지만 알고, 그 위치로 가서 눈대중으로 대충 물건의 크기를 파악해서 손으로 가져올 수 있습니다.

하지만 메모리에 저장된 데이터는 어디까지가 유의미하고 무의미한지, 그 데이터를 직접 보기만 해서는 답을 내릴 수 없습니다. 0000000000000000을 보더라도 이게 숫자 0을 의미하는 건지, 문자열 '0'을 의미하는 건지, 심지어 실제 데이터가 어디서부터 시작하는 건지, 혹은 그냥 아무런 의미없는 공간인건지조차 판단이 불가능합니다. 그 때문에 재고표에 위치(변수의 이름과 메모리 주소)를 똑바로 작성하고 물건의 종류(변수의 타입)까지 미리 파악해야 함은 선택사항이 아니라 필수사항입니다.

실생활보다 다소 좋은 점은, 컴퓨터는 메모리의 주소만 안다면 즉시 그 곳으로 순간이동하여 값이 무엇인지 알 수 있다는 점입니다. 주소가 적인 포스트잇을 들고 길찾기 어플을 켜서 직접 발걸음을 옮겨 찾아나서야 하는 우리랑은 효율의 차원이 다르지요.


더 자세히는 알 필요 없다

축복받은 점은, 메모리와 관련된 작업은 파이썬에서는 자동으로 이루어진다는 점입니다. 컴퓨터공학의 달인들이 오랜 세월 갈고 닦은 정수를 이미 파이썬에 녹였습니다. 다음과 같은 과정은 파이썬 인터프리터가 자동으로 처리하므로 우리가 전혀 신경쓸 필요가 없는 것들입니다.

  • 변수가 새로 생성될 때 사용 가능한 (비어 있는) 메모리의 구역을 확보하기
  • 올바른 방식으로 데이터를 메모리에서 읽고 쓰기
  • 쓸모 없어진 변수를 판단하여, 다른 변수도 사용할 수 있도록 그 메모리에 해당하는 데이터를 삭제하기

우리는 단지 우리가 배운 대로 변수를 생성해서 우리 입맛대로 사용하기만 하면 됩니다. 그렇다면 이같은 과정이 있다는 것을 왜 학습하는 것일까요? 왜냐하면 변수가 어떻게 작동하는지 알아야 변수를 자유자재로 사용할 수 있기 때문입니다. if를 이용한 조건문은 데이터를 비교연산자로 이용하여 단순히 TrueFalse로 나누었지만, while 반복문만 하여도 우리가 루프를 제어하기 위해 별도의 변수를 곧장 만들곤 했습니다. 앞으로 배울 for를 비롯한 각종 문법은 변수에 대한 개념이 확실히 잡혀있어야 이해할 수 있습니다.


변수의 낯을 드러내다

묻고 따지지 말고 다음 코드를 복사하여 실행시켜보세요. 변수의 실제 주소, 크기, 실제 데이터를 출력하는 코드입니다.

from ctypes import string_at
from sys import getsizeof
from binascii import hexlify

def print_raw(a):
    print(a, "\t:<memory at ", id(a), " (", getsizeof(a), ")>\t",
          hexlify(string_at(id(a), getsizeof(a))), sep="")

p = 52
print_raw(p)
p = '안녕하십니까? 저는 김철수라고 합니다. 만나서 반갑습니다~!'
print_raw(p)

52라는 값을 가지고 있는 int형 변수를 한번 분석해보았더니 다음과 같은 결과가 나왔습니다.

  • 메모리 주소 : 140723826314496
  • 차지하는 공간 : 28 bytes
  • 실제 데이터 : 0600000000000000103daad1fc7f0000010000000000000034000000

안녕하십니까? 저는 김철수라고 합니다. 만나서 반갑습니다~!라는 값을 가지고 있는 str형 변수를 분석해보았습니다.

  • 메모리 주소 : 2787455457728
  • 차지하는 공간 : 140 bytes
  • 실제 데이터 : 0500000000000000f002100000(중략)cb5c2c8b2e4b27e0021000000

이러한 예제를 보여주는 이유는 실제로 변수가 컴퓨터에서 어떻게 작동하는지를 엿보게 하기 위해서입니다. 당연하지만 변수마다 메모리 주소와 차지하는 공간, 내부에서 저장되는 데이터가 모두 다른 것을 확인할 수 있습니다. 완전히 동일한 변수일지라도, 컴퓨터마다 환경이 다르고 실행시점마다 메모리의 상황이 달라지니 메모리 주소의 값은 항상 바뀔 수 있습니다.


중간 정리

앞서 이야기했을 때에는 실제 데이터(값), 변수의 이름(name), 변수의 타입(type) 세 가지를 확실히 나누어 언급했으나, 파이썬 인터프리터에서는 변수의 타입이 변수의 데이터에 포함될 수 있습니다. 그러므로 다음과 같이 더 콤팩트하게 정리할 수 있습니다.

  • 변수를 구성하는 것은 데이터(값), 이름이다.
  • 변수의 데이터(값)은 메모리 어딘가에 저장되어 있다.
  • 변수의 이름으로 메모리 주소와 데이터 다루는 법을 알 수 있다.

이 세 가지가 꼭 이해가 되어야 할 텐데요… 하하

또 한가지 짚고 넘어갈 게 있습니다. 파이썬 인터프리터는 변수의 이름을 통해 데이터가 들어있는 메모리의 주소를 알 수 있다고 하였습니다. 표현하기 나름입니다만, 이를 변수는 데이터를 가리킨다. 라고 표현할 수 있습니다. 마치 변수를 집주소가 적혀있는 포스트잇으로 비유하는 표현이죠. 이를 이용하여 이전에 배웠던 다음 문장과 연관지어서 바꿀 수 있습니다.

  • 변경 전 : 변수는 하나의 값만 가질 수 있다.
  • 변경 후 : 변수는 하나의 데이터만 가리킬 수 있다.

이름이 없는 변수?

만약 변수에 이름이 없는 상황이 있을까요? 바로 print(10)과 같은 상황입니다. 10은 데이터로서만 존재할 뿐이며, 이름은 없습니다. 그래서 10print가 한 번 사용하고 버려집니다. 10이라는 데이터는 메모리 어딘가에 존재하였겠지만, 거기에 다다를 이름이 없으니 파이썬 인터프리터는 결국 의미없다 판단하고 10을 지워버립니다. 한 마디로 정리할 수 있겠습니다. 이름이 없는 변수는 존재할 수는 있지만, 사라질 운명에 놓여있다구요.


리스트는 변수의 이름을 가지고 있는 변수이다

리스트는 지금껏 사용해 온 intstr과 사뭇 다릅니다. 리스트는 어떤 값들에 대해서 특별한 이름을 부여한 후 리스트 그 자체의 데이터로서 그 이름들을 가지고 있습니다. 지금부터 머릿속으로 열심히 상상을 해봅시다. 리스트에서 이름과 데이터(값)을 구분해보자구요. 앞의 예제를 땡겨옵시다.

a = [1, 2, 3]
print(a[0])
print(a[1])
1
2

자, 여기서 새로운 리스트를 만들어 대입하는 a = [1, 2, 3] 이 단계에서는 다음과 같은 일들이 벌어집니다.

  1. 리스트 데이터를 생성합니다.
  2. 리스트의 0번째 항목이라는 이름으로, 1이라는 값을 생성합니다.
  3. 리스트의 1번째 항목이라는 이름으로, 2라는 값을 생성합니다.
  4. 리스트의 2번째 항목이라는 이름으로, 3이라는 값을 생성합니다.
  5. 이 리스트의 이름을 a로 지정합니다.

그림으로 정리하면 다음과 같습니다.

리스트 관계도 리스트 관계도

위 그림과 같이 리스트 a의 데이터에는 다른 변수들의 이름밖에 없습니다. 그 이름은 내부적으로 0, 1, 2와 같이 순차적 및 자동으로 정해집니다. 그 각각의 이름에 대해서 데이터들은 리스트 외부에 있습니다. 중요한 지점은 우리가 a라는 이름을 통해 또 다른 변수의 이름에 접근할 수 있다는 것입니다. a[0]과 같이 적게 되면 a 리스트 내에 있는, 0이라는 이름이 가리키는 실제 데이터를 가져오게 됩니다. 그리하여 print(a[0])을 했을 때 1이 출력되는 것입니다. 대괄호 안에는 또 다른 변수를 이용할 수 있으므로 다음과 같이 반복문을 돌리는 것도 가능하게 됩니다.

a = [5, 8, 14]
i = 0
while i < 3:
    print(a[i])
    i += 1
5
8
14

리스트의 기능

list는 다른 변수들의 이름을 지닐 수 있다는 특징 외에도 다양한 기능이 있습니다. 위에서 언급했던 기능만 사용할 수 있었다면 딱히 자주 사용될 이유가 없었겠죠. 기본적인 몇 가지를 먼저 말하자면 다음과 같습니다.

  • list 내의 항목을 언제든지 추가하고 삭제할 수 있습니다.
  • list 내의 항목을 예쁘게 출력할 수 있습니다.
  • list 내 모든 항목에 손쉽게 접근할 수 있습니다.

또 어디서 이런 생뚱맞은 기능들이 등장한 겁니까? 왜 이렇게 새로운 기능이 쉴 새 없이 튀어나오냐 이 말입니다!! 네, 이러한 기능은 파이썬 설계자들이 아주 오래전부터 고심하여 만든 기능들입니다만, 천천히 알아봅시다.

항목 추가하기

우선 항목을 추가하는 기능은 리스트명.append(추가할것)으로 기능을 사용할 수 있습니다.

a = [3, 4, 7]
b = [5, 8, 10, 15]

print('a:', a, 'b:', b)
a.append(4)
b.append(2)
print('a:', a, 'b:', b)
a: [3, 4, 7] b: [5, 8, 10, 15]
a: [3, 4, 7, 4] b: [5, 8, 10, 15, 2]

append라는 것이 처음 등장했는데, 이러한 기능은 도대체 어디에서 어떻게 작동하는 걸까요?

리스트 기능 구조도 리스트 기능 구조도

이전에 변수의 타입은 변수의 데이터에 포함될 수 있다고 이야기한 것을 기억하시나요? 위 그림과 같이 데이터에는 타입이 어떻다고 이야기만 해주는 조그마한 정보가 끼여 있습니다. 파이썬 인터프리터는 그 정보를 따라가서, list가 사용할 수 있는 다양한 기능들이 모여있는 황금창고를 발견합니다. 아까 언급했듯이 이 황금창고는 파이썬 언어 설계자들이 미리 만들어놓았습니다. 우리는 언제든지 여기에 있는 기능을 꺼내쓸 수 있습니다. 바로 .를 통해서요.

여기서 새로운 사실을 또 알아갑시다. 바로 기능(.)은 타입에 따라 다르다는 점 입니다. 리스트는 리스트의 기능을 사용할 수 있으리라는 점은 당연합니다. 그렇지만 intstr의 변수가 리스트의 기능을 사용할 수 있을까요? 아니오, 사용할 수 없습니다! 이 또한 당연해보일지 모르겠지만 중요한 점입니다. 다음의 두 예시는 에러가 일어납니다.

a = "hi"
a.append("ho")
Traceback (most recent call last):
  File "c:/Users/tooth/Desktop/test.py", line 2, in <module>
    a.append("ho")
AttributeError: 'str' object has no attribute 'append'

a = 3
a.append(6)
Traceback (most recent call last):
  File "c:/Users/tooth/Desktop/test.py", line 2, in <module>
    a.append(6)
AttributeError: 'int' object has no attribute 'append'

예제 부수기

자, 여기까지 배웠다면 이번 시간의 처음에 맞닥뜨렸던 학생 여러명의 점수를 한번 다루어봅시다. 비슷한 용도나 목적이 있는 변수가 다수 있는 상황에는 리스트의 특성상 아주 효율적으로 코딩할 수 있습니다.

i = 0
scoreAll = 0
students = []  ##a_1##
while i < 10:
    students.append(int(input()))  ##a_2##
    i += 1

i = 0
while i < 10:
    scoreAll += students[i] ##a_3##
    i += 1

print("점수출력:", students) ##a_4##
print("평균:", scoreAll / 10) ##a_5##
print("최대:", max(students)) ##a_6##
print("최소:", min(students)) ##a_7##
4
2
7
4
39
5
28
73
49
1
점수출력: [4, 2, 7, 4, 39, 5, 28, 73, 49, 1]
평균: 21.2
최대: 73
최소: 1

a_1(1.) 빈 리스트 만들기

대괄호 [] 사이에 아무것도 넣어주지 않으면 아무런 항목이 없는, 즉 비어있는 리스트를 만든다는 뜻입니다.

a_2(2.) 추가 반복하기

students 리스트에 계속해서 항목을 추가하고 있습니다. i0에서부터 시작하여 10 미만까지 1씩 증감하며 총 10번 반복합니다. 각각의 input()students 리스트에서 전부 기억되고 있습니다.

a_3(3.) 덧셈 누적하기

마찬가지로 i0 ~ 9동안 반복되며 리스트 내 항목의 값을 읽어오고 있습니다. scoreAll 변수에 덧셈이 누적되고 있습니다.

a_4(4.) 그대로 출력하기

print 함수를 이용하여 그대로 출력하고 있습니다. 별다른 반복문을 쓰지 않았는데도 print는 알아서 리스트 내부의 모든 항목을 예쁘게 출력하고 있습니다. 조금 있다가 다시 설명합니다.

a_5(5.) 평균 출력하기

scoreAll 변수를 이용해서 평균을 구하여 출력합니다.

a_6(6.) 최댓값 출력하기

max 함수는 쉼표로 여러가지 항목들을 넣어줄 수 있지만, 딸랑 리스트 하나만 넣어줄 수도 있습니다. 그렇게 하게 되면 max 함수는 알아서 리스트 내부의 모든 항목을 읽어서 최댓값을 구합니다. 조금 있다가 다시 설명합니다.

a_7(7.) 최솟값 출력하기

min 함수또한 max와 동일하게 작동하지만 한 가지 차이점은 최솟값을 구한다는 점입니다.


print가 리스트를 다루는 방법

print 함수에 리스트를 넣어주었을 때 아무런 오류 없이 잘 작동하는 것을 확인하실 수 있습니다. 사실, 당연하게 느껴집니다. 왜냐하면 print는 이때까지 아무거나 넣어도 잘 작동하기 때문입니다. 하지만 print도 나름의 동작 방식이 있습니다. 리스트를 보여주는 방식이 왜 하필 대괄호와 쉼표를 써서 [4, 2, 7, 4, 39, 5, 28, 73, 49, 1] 가 되는 것일까요? 중괄호를 이용하고 내부 개수가 너무 많으면 …으로 표시해서 {4,2,7,...} 뭐, 이렇게 할 수도 있고, 아니면 항목의 개수만 짤막하게 출력해줄 수도 있을 텐데, 왜 하필? 이는 print가 어떤 식으로 동작하는지 알면 조금 더 이해할 수 있습니다.

graph TD subgraph 리스트의 기능 r1(리스트를 예쁘게<br/>보여주는 법) r2(리스트의 항목에<br/>접근하는 법) r3(리스트의 항목을<br/>추가/삭제하는 법) r4(기타 등등) r1 -.- r2 r2 -.- r3 r3 -.- r4 r4 -.- r1 end a1["print(students)를 만난다"] a1 --> a2[students의 타입을 찾아본다 <br/>... 리스트이다!] a2 --> a3{"리스트의 기능 중<br/> 예쁘게 보여주는<br/> 방법이 있나?"} r1 -.- a3 a3 -->|있다!| a4[그 방법을 이용해 출력한다.] a3 -->|없다| a5[날 것의 데이터로 출력하거나<br/>에러를 일으킨다.] class r1,a1,a2,a3,a4 em

print(students)가 동작하는 과정

중요한 점은, 우리가 리스트를 예쁘게 보여주는 법이 어떤 식으로 구현되어 있는지도 모르고, 심지어 어떻게 쓰는지도 알 필요가 없다는 점입니다. 이 비기를 이용하는 주체는 손가락으로 열심히 영단어를 타이핑치는 우리가 아니라 바로 print입니다. print가 알아서 A부터 Z까지 다 해주므로 우리는 print를 쓰는 방법만 제대로 익히면 됩니다.

그렇다면 print를 쓰는 방법만 알면 될 것이지, 왜 print가 내부적으로 어떻게 돌아가는지까지 학습하냐구요? 왜냐하면 print(123), print([1, 2, 3]), print(student) 등의 다양한 변수를 넣어도 어떻게 동작할 수 있는지에 대해 이해하기 위함입니다. print와 같이 다양한 쓰임새가 있는 함수는 한두 개가 아니며, 심지어는 다음 시간에서 배울 핵심 문법인 for 반복문에서도 비슷한 형식으로 작동합니다.


maxmin 함수

사실 max 함수는 이번 시간에 처음 등장합니다. max 함수란 소괄호 () 내에 쉼표 ,로 구분된 값들을 비교해서 가장 큰 값을 반환하는 함수입니다. min도 동일하게 동작합니다. 다음 예제를 간단히 봅시다.

print(max(6, 1, 2, 4))
print(min(6, 1, 2, 4))
6
1

하지만 max는 상황에 따라 리스트 하나만 값으로 집어넣을 수 있습니다. 그렇게 되면 max는 이 하나의 값이 리스트인지 우선 검사하고, 리스트가 지니고 있는 변수들의 값을 가져올 수 있는지 다시 검사를 한 후에, 리스트의 변수들 간 비교작업을 시작합니다. 간단히 순서도로 그리면 아래와 같습니다.

graph TD subgraph 리스트의 기능 r1(리스트를 예쁘게<br/>보여주는 법) r2(리스트의 항목에<br/>접근하는 법) r3(리스트의 항목을<br/>추가/삭제하는 법) r4(기타 등등) r1 -.- r2 r2 -.- r3 r3 -.- r4 r4 -.- r1 end a1["max(students)<br/>를 만난다"] a1 --> a2{"max로 들어오는<br/>값이 몇 개인가?"} a2 --> |여러 개이다| a3["이 값들 중 <br/>최댓값을 계산해본다."] a2 --> |하나이다| a4["이 값(students)의<br/>타입을 찾아본다<br/> ... 리스트이다!"] a4 --> a5{"리스트의 기능 중<br/>항목에 접근하는<br/>방법이 있나?"} a5 -.- r2 a5 --> |있다| a7["모든 항목에<br/>접근하여 최댓값을<br/>계산한다"] a5 --> |없다| a6["알아서 처리되거나<br/>에러를 일으킨다."] class a1,a2,a4,a5,a7,r2 em

max(students)가 동작하는 과정


중간 정리

변수에 대해서는 우리가 다음과 같이 정리를 했습니다.

  • 변수를 구성하는 것은 데이터(값), 이름이다.
  • 변수의 데이터(값)은 메모리 어딘가에 저장되어 있다.
  • 변수의 이름으로 메모리 주소와 데이터 다루는 법을 알 수 있다.
  • 변수의 데이터에 타입 정보가 포함되어 있다.
  • 변수는 하나의 데이터만 가리킬 수 있다.

변수의 정리에 따라서 리스트의 특징을 다음과 같이 정리하겠습니다.

  • 리스트는 변수의 이름을 가지는 변수이다. (리스트의 데이터에는 변수의 이름들이 들어가 있다.)
  • 리스트 내 항목의 데이터는 리스트 외부에 있다.
  • 리스트는 비슷한 용도나 목적이 있는 변수가 다수 있는 상황에 적합하다.

리스트의 기본적인 사용법은 다음과 같습니다.

  • 빈 리스트는 []로 만든다.
  • 내용이 있는 리스트는 [1, 2, 3]과 같이 만든다.
  • 항목에 접근하려면 변수명[숫자]로 한다. 이 때, 숫자는 0부터 시작한다.

그리고 타입으로서의 list는 다음과 같이 정리할 수 있습니다.

  • 저마다의 변수의 타입(종류)에는 저마다의 기능이 있다.
  • 이 기능을 이용하려면 변수명 뒤에 .을 붙여 이용한다. (예: student.append(~~~~))
  • 다른 타입의 기능은 이용할 수 없다. (intlist의 기능을 이용할 수 없다.)
  • print, max, min같은 함수는 list의 기능을 활용할 줄 안다.

… 그리고, 리스트의 기능 중 다음의 기능이 대충 존재한다는 것만 알게 되었습니다.

  • 리스트의 항목을 추가하는 법 (student.append(~~~~))
  • 리스트를 예쁘게 보여주는 법 (print가 활용)
  • 리스트의 항목에 접근하는 법 (max, min이 활용)

아래에서는 리스트의 각종 기능과 사용법을 추가로 학습합니다.

항목? 요소?

리스트와 같은 나열된 자료구조에서 그 내부에 존재하는 것을 지칭하는 말을 지금껏 항목이라고 표기했습니다. 하지만 이 또한 여러 단어가 있습니다. 한국어로는 항목, 요소라 칭하고 영어에서는 대부분 item이라고 하지만 간혹 element라고도 부릅니다.


in 연산자

파이썬 공식 문서에서는 멤버십 검사 연산자라고 소개하고 있습니다.

in 연산자는 왼쪽에 있는 항목이 오른쪽에 포함되어 있는지를 검사합니다. 만약 포함되어 있다면 True를 반환하고, 그렇지 않다면 False를 반환합니다. 예제를 봅시다.

print(1 in [1, 2, 3])
print("안녕" in ["하이", "안녕하세요", "철수야 안녕?"])
True
False

in의 결과를 반대로 하고 싶다면, 즉 포함되어 있지 않은지에 대해 검사하고 싶다면 not in이라고 작성해도 됩니다.


대괄호의 두 가지 사용법

앞서 언급한 대로, 대괄호를 이용해 새로운 리스트를 만들기도 하며, 이미 존재하는 리스트의 항목에 접근할 수도 있습니다. 하지만 아무래도 같은 대괄호를 사용하다 보니 이 두 개 간에 혼동이 올 수 있습니다. 예시를 통해서 한번 더 짚고 넘어가도록 하겠습니다.

리스트를 직접 만들기

리스트는 대괄호 안에 항목을 나열하여 직접 만들 수 있습니다. 항목을 구분할 때는 쉼표(,)를 이용하면 됩니다. 리스트 안에는 어떠한 타입의 값이 와도 상관 없습니다.

stuff = [1, "안녕", 2, 3, ['this is another list!', 'wow~!']]
print(stuff)
[1, "안녕", 2, 3, ['this is another list!', 'wow~!']]

위 예제를 잘 보시면 리스트 안에 리스트가 들어갈 수 있음을 확인할 수 있습니다. 그림으로 정리하면 다음과 같이 될 것입니다.

stuff의 구조도 stuff의 구조도

리스트 항목에 접근하기

예시를 바로 보면서 이야기합시다.

numbers = [1, 3, 6, 4, 13, 25]
print(numbers[3])
print(numbers[3] + numbers[5])
print(numbers[-2])
print(numbers[10]) # 에러입니다.
4
29
13
Traceback (most recent call last):
  File "c:/Users/tooth/Desktop/test.py", line 5, in <module>
    print(numbers[10])
IndexError: list index out of range

항목에 접근할 때에는 numbers[3]과 같이 변수와 대괄호를 함께 쓰면 됩니다. 안에는 정수 하나만 들어갑니다. 내부의 3이란 뜻은 4번째 의 항목을 가지고 오라는 뜻입니다. 중요한 지점입니다. 첫번째 항목을 가지고 오려면 numbers[0]와 같이 써야 하고, 두번째는 numbers[1]입니다.

대괄호 안에 있는 수, 즉 리스트의 항목에 접근할 때 쓰는 수를 일반적으로 인덱스(index) 라고 이야기합니다.

대괄호 안에 음수를 넣어줄 수도 있습니다. 이는 끝에서 몇 번째의 항목을 가지고 오라는 뜻입니다. 즉 -2라면 뒤에서 두 번째를 뽑아내겠다는 뜻이죠. 그래서 13이 뽑혔습니다. 대괄호로 만약 잘못된 접근을 한다면 위 결과와 같이 IndexError가 발생합니다. 리스트 내 항목이 최소한 11개가 되어야 numbers[10]이 작동하는데, 그 보다는 한참 모자르니까요.


대괄호([])의 두 가지 용법을 어떻게 확실하게 구분할까요? 바로 대괄호 바로 앞에 변수명이 붙어있나 없나로 구분합니다. 변수가 따라온다면 "리스트의 항목에 접근해!"라는 뜻이고 변수 없이 휑하니 대괄호만 있다면 "새로운 리스트를 만들어!"라는 뜻입니다.

한번 다음 코드가 어떻게 작동할지 상상해보세요.

print([1, 4, 7, 10, 13, 16][4])
13

리스트를 다루는 편의 함수들

리스트를 다루는 함수는 앞서 살펴 본 max, min 외에 다양한 함수가 있습니다.


sum

  • 사용법 : sum(list)
  • 설명 : 리스트 내 모든 항목을 더합니다. 숫자형만 작동합니다.

max

  • 사용법 : max(list)
  • 설명 : 리스트 내 항목의 최대값을 구합니다. 숫자형만 작동합니다.

min

  • 사용법 : min(list)
  • 설명 : 리스트 내 항목의 최소값을 구합니다. 숫자형만 작동합니다.

len

  • 사용법 : len(list)
  • 설명 : 리스트 내 항목의 개수를 구합니다.

예제

numbers = [3, 6, 2, 9, 4]
print(min(numbers))
print(max(numbers))
print(sum(numbers))
print(len(numbers))
2
9
24
5

그 외

any, all 등의 리스트 내 항목을 검사하는 함수도 있지만 좀 더 고급 용법이므로 다음 시간에 알아봅시다. (추가 예정)


리스트의 편의 기능들

좀 더 정확한 설명은 파이썬 공식 문서를 참조해주세요.

https://docs.python.org/ko/3/tutorial/datastructures.html#more-on-lists


append

  • 사용법 : list.append(x)
  • 설명 : 리스트의 끝에 항목 x을 더합니다.
  • 예시
people = ['철수', '영미']
people.append('영철')
print(people)
  • 결과
['철수', '영미', '영철']

extend

  • 사용법 : list.extend(x)
  • 설명 : 리스트의 끝에 리스트 x를 덧붙여 확장합니다. (리스트와 비슷한 것도 전부 됩니다.)
  • 예시
people = ['철수', '영미']
people.append(['영철', '수영'])
print(people)
  • 결과
['철수', '영미', '영철', '수영']

insert

  • 사용법 : list.insert(i, x)
  • 설명 : 리스트의 인덱스 i로 항목 x을 삽입합니다. 기존에 있던 항목과 그 뒤의 모든 항목은 한 칸씩 뒤로 밀려납니다.
  • 예시
people = ['철수', '영미']
people.insert(1, '영철')
print(people)
  • 결과
['철수', '영철', '영미']

remove

  • 사용법 : list.remove(x)
  • 설명 : 리스트에서 값이 x와 같은 첫 번째 항목을 삭제합니다. 그런 항목이 없으면 에러를 일으킵니다.
  • 예시
people = ['철수', '영미']
people.remove('철수')
print(people)
  • 결과
['영미']

pop

  • 사용법 : list.pop(i) 또는 list.pop()
  • 설명 : 리스트에서 인덱스 i에 있는 항목을 삭제하고 그 항목을 반환합니다. i를 지정하지 않으면 리스트의 마지막 항목을 삭제하고 그 항목을 반환합니다.
  • 예시
people = ['철수', '영미', '영환']
print(people.pop(0))
print(people.pop())
print(people)
  • 결과
철수
영환
['영미']

clear

  • 사용법 : list.clear()
  • 설명 : 리스트 내의 모든 항목을 삭제합니다.
  • 예시
people = ['철수', '영미', '영환']
people.clear()
print(people)
  • 결과
[]

index

  • 사용법 : list.index(x)
  • 설명 : 리스트에 있는 항목 중 값이 x와 같은 것의 인덱스를 반환합니다. 리스트 내 x가 여러 개이면 첫번째 항목의 인덱스를 반환하고, x가 없으면 에러입니다.
  • 예시
people = ['철수', '영미', '영환']
i = people.index('영미')
print(i)
  • 결과
1

count

  • 사용법 : list.count(x)
  • 설명 : 리스트에서 x가 등장하는 횟수를 반환합니다.
  • 예시
numbers = [1,5,2,2,5,8,3,2,3,1]
c = numbers.count(2)
print(c)
  • 결과
3

sort

  • 사용법 : list.sort() 또는 list.sort(reverse=True)
  • 설명 : 리스트를 정렬합니다. 기본으로는 오름차순 정렬이지만 reverse=True를 넣게 되면 내림차순으로 정렬됩니다. 정렬과 관련된 고급 기능은 다음 번에 다시 알아보도록 합시다.
  • 예시
numbers = [1,5,2,2,5,8,3,2,3,1]
numbers.sort()
print(numbers)
numbers.sort(reverse=True)
print(numbers)
  • 결과
[1, 1, 2, 2, 2, 3, 3, 5, 5, 8]
[8, 5, 5, 3, 3, 2, 2, 2, 1, 1]

reverse

  • 사용법 : list.reverse()
  • 설명 : 리스트 항목들의 순서를 뒤바꿉니다.
  • 예시
people = ['똥개', '영순', '돌쇠', '사슴']
people.reverse()
print(people)
  • 결과
['사슴', '돌쇠', '영순', '똥개']

copy

  • 사용법 : list.copy()
  • 설명 : 리스트의 사본을 반환합니다. 리스트를 다룰 때 단순히 대입하게 되면 같은 리스트에 이름이 두 개가 됩니다. 그래서 별도의 리스트를 만들려면 복사를 해야 합니다. 이는 클래스와 객체를 배운 후 정체성(추가 예정)에서 자세히 다룰 예정입니다.
  • 예시
numbers1 = [1,3,2,4]
numbers2 = numbers1
numbers2.pop() ##b_1## number1에도 영향을 줍니다.
print('numbers1:', numbers1)
print('numbers2:', numbers2)
numbers2 = numbers1.copy() ##b_2## 이제 number1과 number2는 완전히 별도입니다.
numbers2.append(10)
print('numbers1:', numbers1)
print('numbers2:', numbers2)
  • 결과
numbers1: [1, 3, 2]
numbers2: [1, 3, 2]
numbers1: [1, 3, 2]
numbers2: [1, 3, 2, 10]

a_1(1.) copy 이전

과 의  이전 구조도 number1number2copy 이전 구조도

a_2(2.) copy 이후

과 의  이후 구조도 number1number2copy 이후 구조도

연습 문제

  • 빈 리스트를 어떻게 만드는가?
  • 리스트의 길이를 구하는 함수는 무엇인가?
  • 숫자 세 개 들어있는 리스트를 어떻게 만드는가?
  • 변수의 타입 정보는 어디에 포함되어 있는가?
  • 리스트에서 항목을 어떻게 추가할 수 있는가? (한 가지만)
  • print([1,3,2])로 출력하면 결과는 무엇인가?
  • 리스트는 무엇을 가지는 변수인가?
  • 리스트는 어떤 상황에 적합한가?
  • 변수를 구성하는 것 크게 두 가지는 무엇인가?
  • int 변수가 list의 기능을 사용할 수 있는가?
  • 리스트 내 모든 항목을 더하는 함수는 무엇인가?
  • 변수의 데이터는 어디에 저장되어 있는가?
  • list 변수를 print하고자 할 때 printlist의 기능을 활용하는가?
  • 어떤 값이 리스트의 항목으로 포함되어 있는지 알 수 있는 방법은 무엇인가?
  • 변수는 몇 개의 데이터를 가리킬 수 있는가?
  • 리스트의 항목에 접근하려면 어떻게 해야 하는가?

프로그래밍 문제

  1. 총 개수인 n을 먼저 입력받고, n의 수만큼 숫자를 입력받아 리스트에 저장시킨다. 입력을 마친 후 리스트를 출력하라.

    • 예시입력
      4
      2
      3
      1
      5

    • 예시 출력
      [2, 3, 1, 5]

  2. 5개의 숫자를 입력받는다. 입력받은 숫자들에서 홀수의 합과 짝수의 합을 구하고, 홀수의 합이 크다면 홀수가 이겼습니다라고 출력하고 짝수의 합이 크다면 짝수가 이겼습니다라고 출력하라.

  3. 총 개수인 n을 먼저 입력받고, n의 수만큼 숫자를 입력받아, 이 숫자들 중 두 번째로 큰 수를 출력하라. (힌트 : sort를 이용하면 다소 쉽게 풀 수 있다. sort를 이용하지 않고도 한번 도전해보자.)

  4. 5개의 문자열을 입력받는다. 입력받은 문자열 중 길이가 가장 긴 문자열을 출력하라. (힌트 : len(a) 하게 되면 문자열 a의 길이를 알 수 있다.)


프로그래밍 문제 정답

  1. 코드입니다.

    count = int(input())
    i = 0
    nums = []
    while i < count:
        nums.append(int(input()))
        i += 1
    print(nums)
    
  2. 코드입니다.

  3. 코드입니다.

  4. 코드입니다.

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다

Scroll to top