왜 파이썬 (또는 불변 데이터 타입)에서 튜플이 필요한가?


140

나는 몇 가지 파이썬 튜토리얼 (파이썬으로 다이빙)과 Python.org의 언어 참조를 읽었습니다. 왜 언어에 튜플이 필요한지 알 수 없습니다.

튜플은 목록이나 세트에 비해 메소드가 없으며 튜플을 세트 또는 목록으로 변환하여 정렬 할 수 있다면 튜플을 사용하는 요점은 무엇입니까?

불변성?

변수가 원래 할당되었을 때와 다른 곳에서 변수에 살고 있는지 왜 신경 쓰나요? 파이썬에서 불변성의이 사업 전체가 지나치게 강조된 것 같습니다.

C / C ++에서 포인터를 할당하고 유효한 메모리를 가리키는 경우 주소를 사용하기 전에 null이 아닌 한 주소가 어디에 있는지 상관하지 않습니다.

해당 변수를 참조 할 때마다 포인터가 여전히 원래 주소를 가리키는 지 여부를 알 필요가 없습니다. 방금 null을 확인하고 사용하거나 사용하지 않습니다.

파이썬에서 문자열 (또는 튜플)을 할당 할 때 x에 할당 한 다음 문자열을 수정하면 왜 원래 객체인지 걱정해야합니까? 변수가 내 데이터를 가리키는 한, 그게 전부입니다.

>>> x='hello'
>>> id(x)
1234567
>>> x='good bye'
>>> id(x)
5432167

x 여전히 내가 원하는 데이터를 참조하는데, 왜 ID가 같거나 다른지 누가 신경 써야합니까?


12
당신은 변이성의 잘못된 측면에주의를 기울이고 있습니다 : "id가 ​​같은지 다른지"는 단지 부작용입니다. "이전에 동일한 객체를 가리키는 다른 참조가 가리키는 데이터가 이제 업데이트를 반영하는지 여부"가 중요합니다.
— Charles Duffy

답변:


124
  1. 불변 객체는 실질적인 최적화를 가능하게합니다. 이것은 아마도 문자열이 Java에서도 불변이고, 파이썬과 거의 별도로 개발되었지만 거의 모든 시간이 실제로 기능하는 언어로 변하지 않는 이유 일 것입니다.

  2. 특히 파이썬에서는 불변의 것만 해시 가능합니다 (따라서 사전의 세트 또는 키의 멤버). 다시 말하지만 최적화는 가능하지만 "실질적인"것 이상입니다 (완전히 변경 가능한 객체를 저장하는 괜찮은 해시 테이블을 디자인하는 것은 악몽입니다. ​​해시하자마자 모든 것을 복사하거나 객체의 해시 여부를 확인하는 악몽) 당신이 마지막으로 그것을 참조한 이후로 변경되었습니다.

최적화 문제의 예 :

$ python -mtimeit '["fee", "fie", "fo", "fum"]'
1000000 loops, best of 3: 0.432 usec per loop
$ python -mtimeit '("fee", "fie", "fo", "fum")'
10000000 loops, best of 3: 0.0563 usec per loop

11
@musicfreak, 튜플을 만드는 것이 동등한 목록을 만드는 것보다 7.6 배 더 빠른 곳에서 방금 수행 한 편집을 참조하십시오. 이제 "알림 " 정말 독특합니다 ...
— Alex Martelli

11
@musicfreak 나는 당신이 "조기 최적화는 모든 악의 근원"을 잘못 사용하고 있다고 생각합니다. 애플리케이션에서 조기 최적화를 수행하는 것 (예 : "튜플이 목록보다 빠르므로 모든 앱에서 튜플 만 사용합니다!")과 벤치 마크를 수행하는 것에는 큰 차이가 있습니다. Alex의 벤치 마크는 통찰력이 있으며 튜플을 작성하는 것이 목록을 작성하는 것보다 빠르다는 사실을 알고 있으면 향후 최적화 작업에 도움이 될 수 있습니다 (실제로 필요한 경우).
— Virgil Dupras

5
@Alex는 "목록 작성"보다 실제로 튜플을 "빌드"하고 있습니까, 아니면 파이썬 런타임이 튜플을 캐싱 한 결과를보고 있습니까? 나에게 후자를 보인다.
— Triptych

6
@ACoolie, 그것은 전적으로 random전화에 의해 지배됩니다 (그것을 시도하십시오, 당신은 볼 것입니다!), 그리 중요하지 않습니다. 시도 python -mtimeit -s "x=23" "[x,x]"하고 당신은 목록을 작성 대 튜플을 구축하기위한 2 ~ 3 배의보다 의미있는 속도 향상을 볼 수 있습니다.
— Alex Martelli 2019

9
궁금한 사람은 목록에서 튜플로 전환하여 한 시간 이상 데이터 처리를 줄일 수있었습니다.
— Mark Ribau

42

위의 답변 중 어느 것도 튜플 대 목록의 실제 문제를 지적하지 않았으며, 파이썬에 익숙하지 않은 많은 사람들이 완전히 이해하지 못하는 것 같습니다.

튜플과리스트는 다른 목적으로 사용됩니다. 동종 데이터를 저장합니다. 다음과 같은 목록을 가질 수 있고 있어야합니다.

["Bob", "Joe", "John", "Sam"]

목록을 올바르게 사용하는 이유는 모두 동종 유형의 데이터, 특히 사람들의 이름이기 때문입니다. 그러나 다음과 같은 목록을 작성하십시오.

["Billy", "Bob", "Joe", 42]

그 목록은 한 사람의 이름과 나이입니다. 그것은 한 가지 유형의 데이터가 아닙니다. 해당 정보를 저장하는 올바른 방법은 튜플 또는 객체입니다. 우리에게 몇 가지가 있다고 가정 해 봅시다.

[("Billy", "Bob", "Joe", 42), ("Robert", "", "Smith", 31)]

튜플과리스트의 불변성과 변경 성은 큰 차이점이 아닙니다. 목록은 파일, 이름, 객체와 같은 종류의 항목 목록입니다. 튜플은 다양한 유형의 객체를 그룹화 한 것입니다. 그것들은 다른 용도로 사용되며 많은 파이썬 코더는 튜플의 용도에 대한 목록을 남용합니다.

제발 하지마


편집하다:

나는이 블로그 게시물이 왜 내가 생각했던 것보다 더 나은지에 대해 설명한다고 생각한다 : http://news.e-scribe.com/397


13
나는 당신이 적어도 나에게 동의하지 않는 비전을 가지고 있다고 생각합니다. 다른 사람들을 모릅니다.
— 스테파노 보리 니

13
또한이 답변에 강력하게 동의하지 않습니다. 데이터의 동질성은 목록 또는 튜플을 사용해야하는지 여부와 전혀 관련이 없습니다. 파이썬에서이 차이를 제안하는 것은 없습니다.
— Glenn Maynard에서

14
귀도도 몇 년 전이 점을 지적했습니다. aspn.activestate.com/ASPN/Mail/Message/python-list/1566320
— John La Rooy

11
Guido (Python의 디자이너)가 이종의 데이터와 튜플에 동종의 데이터에 사용되도록 목록을 만들려고했지만 실제로는 언어가이를 강제하지 않습니다. 따라서 나는이 해석이 다른 어떤 것보다 더 스타일 문제라고 생각한다. 많은 사람들의 일반적인 사용 사례에서 목록은 배열과 같은 경향이 있고 튜플은 레코드와 같은 경향이 있습니다. 그러나 이것이 사람들이 이질적인 데이터에 대한 목록을 사용하여 문제에 더 잘 맞는다고 막을 수는 없습니다. 파이썬의 선이 말한 것처럼 : 실용성은 순도를 능가합니다.
— John Y

9
@ Glenn, 당신은 기본적으로 잘못되었습니다. 튜플의 주요 용도 중 하나는 관련된 여러 데이터 조각을 저장하기위한 복합 데이터 형식입니다. 튜플을 반복하고 동일한 작업을 많이 수행 할 수 있다는 사실은이를 변경하지 않습니다. 참고로 다른 많은 언어로 된 튜플은 목록과 동일한 반복 기능을 가지고 있지 않습니다
— .

22

튜플을 세트 또는 목록으로 변환하여 정렬 할 수 있다면 먼저 튜플을 사용하는 요점은 무엇입니까?

이 특별한 경우에는 요점이 없을 것입니다. 이것은 튜플 사용을 고려하는 경우 중 하나가 아니기 때문에 문제가 아닙니다.

지적했듯이 튜플은 변경할 수 없습니다. 불변의 타입을 가지는 이유는 튜플에 적용됩니다 :

  • 복사 효율성 : 불변 개체를 복사하는 대신 별칭을 지정할 수 있습니다 (변수를 참조에 바인딩)
  • 비교 효율성 : 참조 별 복사를 사용하는 경우 컨텐츠가 아닌 위치를 비교하여 두 변수를 비교할 수 있습니다.
  • interning : 변경할 수없는 값의 사본을 하나만 저장해야합니다.
  • 동시 코드에서 불변 객체에 대한 액세스를 동기화 할 필요가 없습니다.
  • const 정확성 : 일부 값은 변경할 수 없습니다. 이것이 (나에게) 불변 유형의 주된 이유입니다.

특정 Python 구현은 위의 모든 기능을 사용하지 않을 수 있습니다.

사전 키는 변경할 수 없어야합니다. 그렇지 않으면 키 개체의 속성을 변경하면 기본 데이터 구조의 변형이 무효화 될 수 있습니다. 따라서 튜플은 잠재적으로 키로 사용될 수 있습니다. 이것은 const 정확성의 결과입니다.

Dive Into Python의 " 튜플 소개 "도 참조하십시오 .


2
id ((1,2,3)) == id ((1,2,3))가 거짓입니다. 튜플은 참조로 복사되었다는 보장이 없으므로 위치를 비교하는 것만으로 비교할 수 없습니다.
— Glenn Maynard 님

@Glenn : "참조 별 복사를 사용하는 경우"적격 한 설명에 유의하십시오. 코더는 자체 구현을 만들 수 있지만 튜플에 대한 참조 별 복사는 대부분 인터프리터 / 컴파일러의 문제입니다. 나는 주로 ==플랫폼 수준에서 어떻게 구현 되는지 언급하고있었습니다 .
— outis

1
@Glenn : 또한 참조 별 복사는의 튜플에는 적용되지 않습니다 (1,2,3) == (1,2,3). 그것은 더 많은 문제입니다.
— outis

내가 분명히 분명히 말했듯이, 그것들이 참조로 복사되었다는 보장은 없습니다 . 튜플은 파이썬에서 인턴되지 않습니다. 그것은 문자열 개념입니다.
— Glenn Maynard 님

내가 매우 명확하게 말했듯이 : 위치를 비교하여 튜플을 비교하는 프로그래머에 대해 이야기하고 있지 않습니다. 나는 플랫폼이 할 수있는 가능성에 대해 이야기하고 있으며, 이는 참조 기준 복사를 보장 할 수 있습니다. 또한 인터 닝은 문자열뿐만 아니라 모든 불변 유형에 적용될 수 있습니다. 기본 파이썬 구현은 인턴 불변 유형이 아닐 수도 있지만 파이썬이 불변 유형을 가지고 있다는 사실은 인턴을 옵션으로 만듭니다.
— outis

15

때때로 우리는 객체를 사전 키로 사용하기를 원합니다

무엇의 가치를 들면, 성장 (2.6+)는 최근 튜플 index()및 count()방법


5
+1 : 사전 키로서의 변경 가능한 목록 (또는 변경 가능한 세트 또는 변경 가능한 사전)이 작동하지 않습니다. 그래서 우리는 불변리스트 ( "tuples"), 고정 된 세트, 그리고 ... ... ... 냉동 된 사전이 필요하다고 생각합니다.
— S.Lott

9

나는 항상 동일한 기본 데이터 구조 (배열)에 대해 완전히 별개의 두 가지 유형이 어색한 디자인이지만 실제로 실제 문제는 아니라는 것을 알았습니다. (모든 언어에는 사마귀가 있으며 파이썬은 포함되어 있지만 중요한 것은 아닙니다.)

변수가 원래 할당되었을 때와 다른 곳에서 변수에 살고 있는지 왜 신경 쓰나요? 파이썬에서 불변성의이 사업 전체가 지나치게 강조된 것 같습니다.

이것들은 다른 것입니다. 돌연변이 성은 메모리에 저장된 장소와 관련이 없습니다. 그것은 의미 가 가리키는 물건을 변경할 수 있습니다.

파이썬 객체는 생성, 변경 가능 여부에 따라 위치를 변경할 수 없습니다. (정확히 말하면 id ()의 값은 변경 될 수 없습니다. 실제로는 동일합니다.) 변경 가능한 객체의 내부 저장소는 변경 될 수 있지만 숨겨진 구현 세부 사항입니다.

>>> x='hello'
>>> id(x)
1234567
>>> x='good bye'
>>> id(x)
5432167

변수를 수정 ( "변경")하지 않습니다. 이름이 같은 새 변수를 만들고 이전 변수를 버립니다. 돌연변이 작업과 비교 :

>>> a = [1,2,3]
>>> id(a)
3084599212L
>>> a[1] = 5
>>> a
[1, 5, 3]
>>> id(a)
3084599212L

다른 사람들이 지적했듯이, 이것은 배열을 사전의 키로 사용하고 불변성을 필요로하는 다른 데이터 구조를 허용합니다.

사전의 키를 완전히 변경할 필요는 없습니다. 키로 사용 된 부분 만 변경할 수 없습니다. 일부 용도의 경우 이는 중요한 차이점입니다. 예를 들어, 고유 한 사용자 이름으로 평등과 해시를 비교하는 사용자를 나타내는 클래스를 가질 수 있습니다. 그런 다음 클래스에 다른 변경 가능한 데이터 ( "사용자가 로그인했습니다"등)를 걸 수 있습니다. 이는 동등성 또는 해시에 영향을 미치지 않으므로이를 사전의 키로 사용하는 것이 가능하고 완벽하게 유효합니다. 이것은 파이썬에서 너무 일반적으로 필요하지 않습니다. 나는 몇몇 사람들이 키가 "불변"이어야한다고 주장했기 때문에 지적했다. 이것은 부분적으로 만 정확하다. 그래도 C ++ 맵 및 세트와 함께 이것을 여러 번 사용했습니다.


>>> a = [1,2,3] >>> id (a) 3084599212L >>> a [1] = 5 >>> a [1, 5, 3] >>> id (a) 3084599212L You ' v 변경 가능한 데이터 유형을 수정 했으므로 원래 질문과 관련이 없습니다. x = 'hello "id (x) 12345 x ="goodbye "id (x) 65432 누가 새로운 객체인지 아닌지에 관심이 있습니다 x가 내가 할당 한 데이터를 가리키는 한, 그게 전부입니다.
— pyNewGuy

4
당신은 당신을 도울 수있는 능력 이상으로 혼란 스럽습니다.
— Glenn Maynard

하위 질문에서 혼동을 지적한 +1은 튜플의 가치를 인식하는 데 주요 어려움의 원인으로 보입니다.
— outis

1
가능한 경우 키의 실제 루 브릭은 객체가 해시 가능한지 여부입니다 ( docs.python.org/glossary.html#term-hashable ).
— outis

7

gnibbler가 의견에서 제안한 것처럼 Guido는 “목록은 동종 데이터, 튜플은 이종 데이터를위한 것”이라는 완전히 받아 들여지지 않은 의견 을 가지고있었습니다 . 물론 많은 반대자들이 이것을 목록의 모든 요소가 같은 유형이어야한다는 의미로 해석했습니다.

나는 다른 사람들 도 과거 와 달리 다르게 보는 것을 좋아합니다 .

blue= 0, 0, 255
alist= ["red", "green", blue]

type (alist [1])! = type (alist [2])인데도리스트가 동질적인 것으로 간주합니다.

요소의 순서를 변경할 수 있고 코드에 문제가없는 경우 (예 : "정렬되어야 함"과 같은 가정) 목록을 사용해야합니다. 그렇지 않으면 ( blue위 의 튜플에서와 같이) 튜플 을 사용해야합니다.


내가이 답변을 15 번 투표 할 수 있다면. 이것이 바로 튜플에 대한 느낌입니다.
— 그랜트 폴

6

발신자가 전달한 객체가 변경되지 않도록 보장하기 때문에 중요합니다. 이렇게하면 :

a = [1,1,1]
doWork(a)

호출자의 가치의 보장이 없습니다 호출 후를. 하나,

a = (1,1,1)
doWorK(a)

이제 호출자 또는이 코드의 독자로 알고 A는 동일합니다. 이 시나리오의 경우 항상 목록을 복사하여 전달할 수 있지만 이제는보다 의미적인 언어 구성을 사용하는 대신주기를 낭비하고 있습니다.


1
이것은 튜플의 매우 이차적 인 속성입니다. 기존 목록이든 다른 클래스이든 상관없이 함수에 전달할 수정 가능한 객체가 있고 수정하지 않은 경우가 너무 많습니다. 파이썬에는 "참조로 콘 스트 매개 변수"라는 개념이 없습니다 (예 : const foo & C ++). 튜플은 튜플을 사용하는 것이 편리하다면 이것을 제공하지만, 발신자로부터 목록을 받으면 다른 곳으로 전달하기 전에 튜플로 변환 할 것입니까?
— Glenn Maynard에서

나는 그것에 동의합니다. 튜플은 const 키워드를 두드리는 것과 다릅니다. 내 요점은 튜플의 불변성은 코드 독자에게 추가적인 의미를 부여한다는 것입니다. 모두가 일하는 것이 상황을 감안할 때 당신의 기대는 (뿐만 아니라 그것을 보장) 독자 여분의 의미를 추가 할 것이다 튜플을 사용하여 변경하지해야한다는 것입니다
— 마태 복음 Manela

a = [1,1,1] doWork (a) dowork ()가 def dowork (arg)로 정의 된 경우 : arg = [0,0,0] 목록 또는 튜플에서 dowork ()를 호출하면 동일한 결과가 나타납니다.
— pyNewGuy


1

귀하의 질문 (및 후속 의견)은 과제 중 id ()가 변경되는지 여부에 중점을 둡니다. 차이 자체보다는 변경 불가능한 개체 교체와 변경 가능한 개체 수정 간의 차이에 따른 후속 효과에 초점을 맞추는 것이 최선의 방법은 아닙니다.

계속하기 전에 아래에 설명 된 동작이 Python에서 기대하는 것인지 확인하십시오.

>>> a1 = [1]
>>> a2 = a1
>>> print a2[0]
1
>>> a1[0] = 2
>>> print a2[0]
2

이 경우 a1에만 새 값이 할당 되었더라도 a2의 내용이 변경되었습니다. 다음과 대조하십시오 :

>>> a1 = (1,)
>>> a2 = a1
>>> print a2[0]
1
>>> a1 = (2,)
>>> print a2[0]
1

후자의 경우 내용을 업데이트하지 않고 전체 목록을 교체했습니다. 튜플과 같은 변경 불가능한 유형에서는 이것이 허용되는 유일한 동작입니다.

이것이 왜 중요한가? dict이 있다고 가정 해 봅시다.

>>> t1 = (1,2)
>>> d1 = { t1 : 'three' }
>>> print d1
{(1,2): 'three'}
>>> t1[0] = 0  ## results in a TypeError, as tuples cannot be modified
>>> t1 = (2,3) ## creates a new tuple, does not modify the old one
>>> print d1   ## as seen here, the dict is still intact
{(1,2): 'three'}

튜플을 사용하면 사전에서 키를 "아래에서"다른 값으로 해시하는 항목으로 변경하지 않아도됩니다. 효율적인 구현을 위해서는 매우 중요합니다.


다른 사람들이 지적했듯이 불변성! = 해시 가능성. 모든 튜플을 사전 키로 사용할 수있는 것은 아닙니다. {([1], [2]) : 'value'} 튜플의 가변 목록을 변경할 수 있지만 {((1), (2)) : ' value '}는 괜찮습니다.
— Ned Deily

네드, 그건 사실이지만, 그 구별이 묻는 질문과 밀접한 관계가 있는지 확실하지 않습니다.
— Charles Duffy

@ K.Nicholas, 여기에서 승인 한 편집은 튜플이 아닌 정수를 할당하는 방식으로 코드를 변경했습니다. 나중에 인덱스 작업이 실패하므로 새로운 것으로 테스트 할 수 없었습니다. 대본이 실제로 가능했습니다. 올바르게 식별 된 문제입니다. 잘못된 솔루션입니다.
— Charles Duffy

@MichaelPuckettII도 마찬가지로 참조하십시오.
— Charles Duffy
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.