1. 변하지 않는 튜플

종류 구성 타입
문자열 문자 Immutable
리스트 요소 Mutable
딕셔너리 key:value Mutable

문자열은 "a[i]"로 문자에 접근할 수 있지만, Immutable 형식으로 값을 수정할 수 없다.

 

반면에 리스트, 딕셔너리는 Mutable 형식으로 추가 및 수정을 할 수 있다. 데이터를 처리할 때는 리스트, 딕셔너리를 많이 사용하지만 값을 수정할 수 없은 Immutable한 자료형이 필요할 때가 있다. 따라서 리스트와 유사하지만 값을 바꿀 수 없는 Immutable 버전인 '튜플'이 있다.

 

t1 = ('a', 'b', 'c', 1, 2, 3)
print(t1, t1[2])

t2 = ("hello",) #하나의 값이면 뒤에 콤마 입력
print(t2)

t3 = "goorm", 'b', "hello", 1, 2, 3 #괄호 생략 가능
print(t3, t3[2])

s1 = list(set([1,2,3])) #다음에 배우게 될 집합 Mutable 타입
t4 = ([1, 2, 3], {"사과":"apple", "포도":"grape"}, ('a', 'b', 'c'), s1) #리스트 내 어떤 값도 가능
print(t4, t4[1])

t4[3][2] = "edit" #중요: 튜플 요소가 Mutable하면 수정할 수 있음
t4[1]["사과"] = "edit"
t4[0][2] = "edit"
print(t4)
  1. 튜플을 선언하고 초기화 할 때는 소괄호를 사용하고 indexing으로 요소에 접근할 수 있다. 중요한 점은 괄호를 사용하지 않아도 튜플로 초기화된다.
  2. t2처럼 한 개의 요소만 초기화 하는 튜플은 요소 뒤에 콤마를 꼭 입력해야한다.
  3. 변수 t3 는 괄호를 사용하지 않고 나열된 값이 튜플로 초기화 된다.
  4. 튜플 안에 있는 Mutable한 값은 수정할 수 있다. 튜플 자체의 요소는 데이터 초기화와 동시에 정해진 값이기에 수정할 수 없지만, Mutable하다면 요소의 요소를 수정할 순 있다.

** 참고

튜플 내부의 mutable 객체 수정이 가능한 이유?

  • 튜플은 불변 객체이다. -> 한 번 생성되면 구성 요소(참조)를 바꿀 수 없다.
  • 튜플 안에 있는 객체가 가변적(mutable)이라면 객체의 내부 상태는 바꿀 수 있다.
t = ([1, 2], "hello", 3)
t[0].append(99)  # 가능
print(t)         # 출력: ([1, 2, 99], 'hello', 3)

t[1] = "world"   # 오류 (튜플 요소 자체를 바꾸는 것)

튜플이 저장하는 건 "값"이 아니라 "객체에 대한 참조(주소)"이다. 불변이란 것은 튜플이 가리키는 참조(포인터)를 바꿀 수 없다는 말이지, 그 참조가 가리키는 객체의 내부가 바뀌지 않는 다는 말이 아니다.

 

t[0] = [1, 2]에서 t[0]은 객체를 가리키는 포인터이다. 포인터는 바꿀 수 없지만, 포인터가 가리키는 리스트의 내부는 바꿀 수 있다.

 

t1 = ('a', 'b', 'c', 1, 2, 3)
t2 = ("hello",)
t3 = "goorm", 'b', "hello", 1, 2, 3

print(t1 + t2 + t3) #튜플 결합
print(t2 * t3[4]) #곱셈으로 반복 출력
t2 = ("hello",)       # 요소가 1개인 튜플
t3 = ("goorm", 'b', "hello", 1, 2, 3)
t3[4] == 2            # 즉, 숫자 2

t2 * t3[4]  ==  ("hello",) * 2

이는 튜플 반복 연산이며, "hello"라는 문자열을 요소로 가진 튜플이 2번 반복된 튜플이 된다.

 

(1, 2) * 3         →  (1, 2, 1, 2, 1, 2)
("a",) * 4         →  ('a', 'a', 'a', 'a')

튜플 곱셈의 개념은 튜플 * 정수는 그 튜플을 정수만큼 반복한 튜플을 생성한다. 리스트의 곱셈과 같은 개념이다.

 

2. 중복과 순서가 없는 집합(Set)

s0 = {3, 2, 5, 1, 8, 4, 3} #집합으로 바로 선언 및 초기화
print(s0, type(s0))

str = "Hello groom!!!"
print(str, type(str))

s1 = set(str)
print(s1, type(s1))
Hello groom!!! <class 'str'>
{'l', 'e', 'g', 'r', ' ', 'o', 'm', 'H', '!'} <class 'set'>

s0와 같이 바로 값을 초기화 할 수 있고, 다른 자료형을 set으로 바꿀 수 있다. set( ) 인자에 문자열, 리스트, 딕셔너리, 튜플 등을 입력할 수 있다. 그럼 값들이 집합으로 변경된다.

 

특징으로는

  1. 요소의 순서가 없다.
  2. 중복되는 값은 1개만 저장된다.
  3. 딕셔너리는 key만 저장한다.
list = ["a", "a", "c", "groom", 10, 20, 30]
print(list, type(list))

s2 = set(list)
print(s2, type(s2))

dic = {"Anna": 25, "Bob": "doctor"}
print(dic, type(dic))

s3 = set(dic)
print(s3, type(s3))

tp = (190,)
print(tp, type(tp))

s4 = set(tp)
print(s4, type(s4))
['a', 'a', 'c', 'groom', 10, 20, 30] <class 'list'>
{'groom', 'a', 10, 'c', 20, 30} <class 'set'>
{'Anna': 25, 'Bob': 'doctor'} <class 'dict'>
{'Bob', 'Anna'} <class 'set'>
(190,) <class 'tuple'>
{190} <class 'set'>

집합에는 순서와 중복이 없기 때문에 리스트, 튜플에 속한 요소의 중복을 제거하기 위한 필터로 많이 사용된다. 그리고 순서가 없기 때문에 indexing, slicing을 사용할 수 없다.

 

str = "Hello World!!!"
print(str, type(str))

s0 = set(str)
print(s0, type(s0))

newStr = tuple(s0)
print(newStr)
print(newStr[4])
print(newStr[5:])
print(type(newStr))
Hello World!!! <class 'str'>
{' ', '!', 'o', 'l', 'd', 'e', 'H', 'W', 'r'} <class 'set'>
(' ', '!', 'o', 'l', 'd', 'e', 'H', 'W', 'r')
d
('e', 'H', 'W', 'r')
<class 'tuple'>

set( )으로 다른 자료형을 변환한 것 처럼 튜플은 tuple( )을 사용해서 변환할 수 있다.

 

3. 집합 함수

함수명 사용 구문
교집합 &, intersection()
합집합 |, union()
차집합 -, difference()
s1 = set([2,4,6,8,10]) 
s2 = set([1,2,3,4,5,6,7,8])

interset = s1 & s2 #교집합
print(interset)
print(s1.intersection(s2), s2.intersection(s1)) #함수 사용
print(s1) #s1의 값이 바뀌는 것이 아님

uniset = s1 | s2 #합집합
print(uniset)
print(s1.union(s2)) 
print(s1) #s1의 값이 바뀌는 것이 아님

difset1 = s1 - s2 #어떤 집합에서 어떤 집합을 빼느냐에 따라 다른 결괏값
difset2 = s2 - s1
print(difset1)
print(difset2)

 

함수명 함수 사용 함수 실행 결과
add set.add(a) 집합 set에 a 값을 추가한다.
update set.update([a,b,c, ...]) 집합 set에 여러 개의 값을 추가한다.
remove set.remove(a) 집합 set에 a 값을 삭제한다.
s1 = {1, 2, 3, 4}

s1.add("hello")
print(s1)

s1.add(10)
print(s1)

s1.add((1, 2, 3))  # add() 사용 시 튜플, 문자열은 값 하나로 인식
print(s1)

s1.update(["a", "b", "c"])  # set()과 같이 여러 값을 한 요소로 저장
print(s1)
s1.update((11, 12))
print(s1)

s1.update("zyx") # s1.add("hello")와의 차이
print(s1)

s1.remove("hello") # 하나의 값만 제거 가능
print(s1)
{1, 2, 3, 4, 'hello'}
{1, 2, 3, 4, 10, 'hello'}
{1, 2, 3, 4, 10, (1, 2, 3), 'hello'}
{1, 2, 3, 4, 10, (1, 2, 3), 'b', 'c', 'hello', 'a'}
{1, 2, 3, 4, 10, (1, 2, 3), 11, 'b', 12, 'c', 'hello', 'a'}
{1, 2, 3, 4, 10, (1, 2, 3), 11, 'b', 12, 'x', 'c', 'hello', 'a', 'z', 'y'}
{1, 2, 3, 4, 10, (1, 2, 3), 11, 'b', 12, 'x', 'c', 'a', 'z', 'y'}

 

** set.add()와 set.update()의 동작 차이와 튜플, 리스트 사용 주의점

 

1. 리스트와 집합은 집합(set)의 원소로 사용할 수 없다.
? 이유 : 리스트와 집합은 mutable(가변) -> 해시 불가능 - > set 내부에 들어갈 수 없음

s = set()
s.add([1,2,3])   # ❌ TypeError 발생

 

2. 튜플은 set의 원소로 사용할 수 있다.

? 이유 : 튜플은 immutable(불변) → 해시 가능 → 집합의 원소로 사용 가능

s = set()
s.add((1, 2, 3))   # ✅ 정상 작동. 튜플 자체가 하나의 원소로 들어감

 

3. add( ) vs update( ) 차이점

메서드 설명
add(x) x를 하나의 원소로 집합에 추가
update(iterable) iterable의 각 원소를 하나씩 집합에 추가
s1 = set()

# add: 튜플 자체가 하나의 원소로 들어감
s1.add((1, 2, 3))
# 결과: s1 = { (1, 2, 3) }

# update: 각 요소가 개별 원소로 들어감
s1.update((11, 12))
# 결과: s1 = { (1, 2, 3), 11, 12 }

즉, add()는 전체 객체를 넣고, update()는 안의 요소들을 꺼내서 각각 넣는다.

 

⭐ update(['a', 'b', 'c'])처럼 리스트를 사용할 수 있음

update()는 iterable을 받기 때문에 리스트, 튜플, 문자열 등 반복 가능한 자료형을 넘겨줄 수 있음. 리스트 자체는 원소로 넣을 수 없지만 update()로 넘기면 각 요소 'a', 'b', 'c'가 원소로 추가됨.

s1 = set()
s1.update(['a', 'b', 'c'])
# 결과: s1 = { 'a', 'b', 'c' }

 

+ Recent posts