객체의 표현 방식

Beginner

This tutorial is from open-source community. Access the source code

소개

이 랩에서는 Python 객체가 내부적으로 어떻게 표현되는지 배우고, 속성 할당 및 조회 메커니즘을 이해하게 됩니다. 이러한 개념은 Python 이 객체 내에서 데이터와 동작을 관리하는 방식을 파악하는 데 기본적인 요소입니다.

또한, 클래스와 인스턴스 간의 관계를 탐구하고 객체 지향 프로그래밍에서 클래스 정의의 역할을 살펴볼 것입니다. 이러한 지식은 Python 의 객체 지향 기능을 이해하는 데 도움이 될 것입니다.

이것은 가이드 실험입니다. 학습과 실습을 돕기 위한 단계별 지침을 제공합니다.각 단계를 완료하고 실무 경험을 쌓기 위해 지침을 주의 깊게 따르세요. 과거 데이터에 따르면, 이것은 초급 레벨의 실험이며 완료율은 94%입니다.학습자들로부터 93%의 긍정적인 리뷰율을 받았습니다.

간단한 주식 클래스 생성

이 단계에서는 주식을 나타내는 간단한 클래스를 생성할 것입니다. 클래스를 생성하는 방법을 이해하는 것은 Python 에서 매우 중요합니다. 이를 통해 실제 객체와 해당 동작을 모델링할 수 있기 때문입니다. 이 간단한 주식 클래스는 Python 객체가 내부적으로 어떻게 작동하는지 탐구하기 위한 시작점이 될 것입니다.

시작하려면 Python 대화형 셸을 열어야 합니다. Python 대화형 셸은 Python 코드를 실험하기에 좋은 곳입니다. Python 명령을 하나씩 입력하고 실행할 수 있습니다. 셸을 열려면 터미널에 다음 명령을 입력하십시오.

python3

명령을 입력하면 Python 프롬프트 (>>>) 가 표시됩니다. 이는 현재 Python 대화형 셸 내에 있으며 Python 코드를 작성할 수 있음을 나타냅니다.

이제 SimpleStock 클래스를 정의해 보겠습니다. Python 에서 클래스는 객체를 생성하기 위한 청사진과 같습니다. 해당 클래스의 객체가 가질 속성 (데이터) 과 메서드 (함수) 를 정의합니다. 필요한 속성과 메서드를 사용하여 SimpleStock 클래스를 정의하는 방법은 다음과 같습니다.

>>> class SimpleStock:
...     def __init__(self, name, shares, price):
...         self.name = name
...         self.shares = shares
...         self.price = price
...     def cost(self):
...         return self.shares * self.price
...

위 코드에서 __init__ 메서드는 Python 클래스의 특수한 메서드입니다. 생성자 (constructor) 라고 하며, 객체가 생성될 때 객체의 속성을 초기화하는 데 사용됩니다. self 매개변수는 생성되는 클래스의 인스턴스를 참조합니다. cost 메서드는 주식 수를 주당 가격으로 곱하여 총 주식 비용을 계산합니다.

클래스를 정의한 후 SimpleStock 클래스의 인스턴스를 생성할 수 있습니다. 인스턴스는 클래스 청사진에서 생성된 실제 객체입니다. 서로 다른 주식을 나타내기 위해 두 개의 인스턴스를 생성해 보겠습니다.

>>> goog = SimpleStock('GOOG', 100, 490.10)
>>> ibm = SimpleStock('IBM', 50, 91.23)

이 인스턴스는 주당 490.10 달러에 Google 주식 100 주와 주당 91.23 달러에 IBM 주식 50 주를 나타냅니다. 각 인스턴스에는 자체 속성 값 집합이 있습니다.

인스턴스가 제대로 작동하는지 확인해 보겠습니다. 속성을 확인하고 비용을 계산하여 확인할 수 있습니다. 이를 통해 클래스와 해당 메서드가 예상대로 작동하는지 확인할 수 있습니다.

>>> goog.name
'GOOG'
>>> goog.shares
100
>>> goog.price
490.1
>>> goog.cost()
49010.0
>>> ibm.cost()
4561.5

cost() 메서드는 주식 수를 주당 가격으로 곱하여 해당 주식을 보유하는 총 비용을 계산합니다. 이러한 명령을 실행하면 인스턴스에 올바른 속성 값이 있고 cost 메서드가 비용을 정확하게 계산하고 있음을 확인할 수 있습니다.

객체의 내부 딕셔너리 탐색

Python 에서 객체는 기본적인 개념입니다. 객체는 데이터를 담고 특정 동작을 수행하는 컨테이너로 생각할 수 있습니다. Python 객체의 흥미로운 측면 중 하나는 속성을 저장하는 방식입니다. 속성은 객체에 속하는 변수와 같습니다. Python 은 이러한 속성을 __dict__ 속성을 통해 접근할 수 있는 특수한 딕셔너리에 저장합니다. 이 딕셔너리는 객체의 내부 부분이며, Python 이 해당 객체와 관련된 모든 데이터를 추적하는 곳입니다.

SimpleStock 인스턴스를 사용하여 이 내부 구조를 자세히 살펴보겠습니다. Python 에서 인스턴스는 클래스에서 생성된 개별 객체입니다. 예를 들어, SimpleStock이 클래스인 경우 googibm은 해당 클래스의 인스턴스입니다.

이러한 인스턴스의 내부 딕셔너리를 보려면 Python 대화형 셸을 사용할 수 있습니다. Python 대화형 셸은 코드를 빠르게 테스트하고 결과를 확인하는 데 유용한 도구입니다. Python 대화형 셸에서 다음 명령을 입력하여 인스턴스의 __dict__ 속성을 검사합니다.

>>> goog.__dict__
{'name': 'GOOG', 'shares': 100, 'price': 490.1}
>>> ibm.__dict__
{'name': 'IBM', 'shares': 50, 'price': 91.23}

이러한 명령을 실행하면 각 인스턴스에 자체 내부 딕셔너리가 있음을 보여줍니다. 이 딕셔너리에는 모든 인스턴스 속성이 포함되어 있습니다. 예를 들어, goog 인스턴스에서 name, shares, price 속성은 해당 값과 함께 딕셔너리에 저장됩니다. 이것이 Python 이 객체 속성을 내부적으로 구현하는 방식입니다. 모든 객체에는 모든 속성을 담고 있는 개인 딕셔너리가 있습니다.

__dict__ 속성이 객체의 내부 구현에 대해 무엇을 나타내는지 이해하는 것이 중요합니다.

  1. 딕셔너리의 키는 속성 이름에 해당합니다. 예를 들어, goog 인스턴스에서 키 'name'은 객체의 name 속성에 해당합니다.
  2. 딕셔너리의 값은 속성 값입니다. 따라서 값 'GOOG'goog 인스턴스의 name 속성 값입니다.
  3. 각 인스턴스에는 자체적인 별도의 __dict__가 있습니다. 즉, 한 인스턴스의 속성은 다른 인스턴스의 속성과 독립적입니다. 예를 들어, goog 인스턴스의 shares 속성은 ibm 인스턴스의 shares 속성과 다를 수 있습니다.

이 딕셔너리 기반 접근 방식을 통해 Python 은 객체에 매우 유연하게 대처할 수 있습니다. 다음 단계에서 보듯이, 이 유연성을 사용하여 다양한 방식으로 객체 속성을 수정하고 접근할 수 있습니다.

객체 속성 추가 및 수정

Python 에서 객체는 딕셔너리를 기반으로 구현됩니다. 이러한 구현은 Python 에 객체 속성을 처리할 때 높은 수준의 유연성을 제공합니다. 다른 프로그래밍 언어와 달리 Python 은 객체의 속성을 클래스에 정의된 속성으로만 제한하지 않습니다. 즉, 객체가 생성된 후에도 언제든지 객체에 새 속성을 추가할 수 있습니다.

인스턴스 중 하나에 새 속성을 추가하여 이러한 유연성을 살펴보겠습니다. SimpleStock 클래스의 goog라는 인스턴스가 있다고 가정해 보겠습니다. 여기에 date 속성을 추가해 보겠습니다.

>>> goog.date = "6/11/2007"
>>> goog.__dict__
{'name': 'GOOG', 'shares': 100, 'price': 490.1, 'date': '6/11/2007'}

여기서 goog 인스턴스에 새로운 속성 date를 추가했습니다. 이 date 속성은 SimpleStock 클래스에 정의되지 않았습니다. 이 새로운 속성은 goog 인스턴스에만 존재합니다. 이를 확인하기 위해 ibm 인스턴스를 확인해 보겠습니다.

>>> ibm.__dict__
{'name': 'IBM', 'shares': 50, 'price': 91.23}
>>> hasattr(ibm, 'date')
False

보시다시피 ibm 인스턴스에는 date 속성이 없습니다. 이는 Python 객체의 세 가지 중요한 특징을 보여줍니다.

  1. 각 인스턴스에는 고유한 속성 집합이 있습니다.
  2. 객체가 생성된 후에도 속성을 추가할 수 있습니다.
  3. 한 인스턴스에 속성을 추가해도 다른 인스턴스에는 영향을 미치지 않습니다.

이제 다른 방법으로 속성을 추가해 보겠습니다. 점 표기법을 사용하는 대신 객체의 기본 딕셔너리를 직접 조작합니다. Python 에서 각 객체에는 모든 속성을 키 - 값 쌍으로 저장하는 특수 속성 __dict__가 있습니다.

>>> goog.__dict__['time'] = '9:45am'
>>> goog.time
'9:45am'
>>> goog.__dict__
{'name': 'GOOG', 'shares': 100, 'price': 490.1, 'date': '6/11/2007', 'time': '9:45am'}

__dict__ 딕셔너리를 직접 수정하여 goog 인스턴스에 새로운 속성 time을 추가했습니다. goog.time에 접근하면 Python 은 __dict__ 딕셔너리에서 'time' 키를 찾고 해당 값을 반환합니다.

이러한 예는 Python 객체가 본질적으로 몇 가지 추가 기능이 있는 딕셔너리임을 보여줍니다. Python 객체의 유연성은 동적 수정을 허용하며, 이는 프로그래밍에서 매우 강력하고 편리합니다.

클래스와 인스턴스의 관계 이해

이제 Python 에서 클래스와 인스턴스가 어떻게 관련되어 있는지, 그리고 메서드 조회 (method lookup) 가 어떻게 작동하는지 살펴보겠습니다. 이는 Python 이 객체로 작업할 때 메서드와 속성을 어떻게 찾고 사용하는지 이해하는 데 도움이 되므로 중요한 개념입니다.

먼저, 인스턴스가 어떤 클래스에 속하는지 확인해 보겠습니다. 인스턴스의 클래스를 아는 것은 Python 이 해당 인스턴스와 관련된 메서드와 속성을 어디에서 찾을지 알려주므로 매우 중요합니다.

>>> goog.__class__
<class '__main__.SimpleStock'>
>>> ibm.__class__
<class '__main__.SimpleStock'>

두 인스턴스 모두 SimpleStock 클래스를 참조합니다. 이 참조는 Python 이 메서드를 찾아야 할 때 사용하는 포인터와 같습니다. 인스턴스에서 메서드를 호출하면 Python 은 이 참조를 사용하여 적절한 메서드 정의를 찾습니다.

인스턴스에서 메서드를 호출하면 Python 은 다음 단계를 따릅니다.

  1. 인스턴스의 __dict__에서 속성을 찾습니다. 인스턴스의 __dict__는 모든 인스턴스별 속성이 보관되는 저장 영역과 같습니다.
  2. 찾을 수 없으면 클래스의 __dict__를 확인합니다. 클래스의 __dict__는 해당 클래스의 모든 인스턴스에 공통적인 모든 속성 및 메서드를 저장합니다.
  3. 클래스에서 찾으면 해당 속성을 반환합니다.

이것을 실제로 살펴보겠습니다. 먼저, cost 메서드가 인스턴스 딕셔너리에 없는지 확인합니다. 이 단계는 cost 메서드가 각 인스턴스에 특정된 것이 아니라 클래스 수준에서 정의되었음을 이해하는 데 도움이 됩니다.

>>> 'cost' in goog.__dict__
False
>>> 'cost' in ibm.__dict__
False

그렇다면 cost 메서드는 어디에서 오는 것일까요? 클래스를 확인해 보겠습니다. 클래스의 __dict__를 보면 cost 메서드가 어디에 정의되어 있는지 알 수 있습니다.

>>> SimpleStock.__dict__['cost']
<function SimpleStock.cost at 0x7f...>

메서드는 인스턴스가 아닌 클래스에 정의되어 있습니다. goog.cost()를 호출하면 Python 은 goog.__dict__에서 cost를 찾지 못하므로 SimpleStock.__dict__를 찾아 거기에서 찾습니다.

실제로 클래스 딕셔너리에서 메서드를 직접 호출하여 인스턴스를 첫 번째 인수로 전달할 수 있습니다 (이것이 self가 됩니다). 이는 일반적인 instance.method() 구문을 사용할 때 Python 이 내부적으로 메서드를 호출하는 방식을 보여줍니다.

>>> SimpleStock.__dict__['cost'](goog)
49010.0
>>> SimpleStock.__dict__['cost'](ibm)
4561.5

이것이 기본적으로 goog.cost()를 호출할 때 Python 이 내부적으로 수행하는 작업입니다.

이제 클래스 속성을 추가해 보겠습니다. 클래스 속성은 모든 인스턴스에서 공유됩니다. 즉, 클래스의 모든 인스턴스가 이 속성에 접근할 수 있으며 클래스 수준에서 한 번만 저장됩니다.

>>> SimpleStock.exchange = 'NASDAQ'
>>> goog.exchange
'NASDAQ'
>>> ibm.exchange
'NASDAQ'

두 인스턴스 모두 exchange 속성에 접근할 수 있지만 개별 딕셔너리에는 저장되지 않습니다. 인스턴스 및 클래스 딕셔너리를 확인하여 이를 확인해 보겠습니다.

>>> 'exchange' in goog.__dict__
False
>>> 'exchange' in SimpleStock.__dict__
True
>>> SimpleStock.__dict__['exchange']
'NASDAQ'

이는 다음을 보여줍니다.

  1. 클래스 속성은 모든 인스턴스에서 공유됩니다. 모든 인스턴스는 자체 복사본 없이 동일한 클래스 속성에 접근할 수 있습니다.
  2. Python 은 먼저 인스턴스 딕셔너리를 확인한 다음 클래스 딕셔너리를 확인합니다. 이는 인스턴스에서 속성에 접근하려고 할 때 Python 이 속성을 찾는 순서입니다.
  3. 클래스는 공유 데이터 및 동작 (메서드) 의 저장소 역할을 합니다. 클래스는 모든 인스턴스에서 사용할 수 있는 모든 공통 속성 및 메서드를 저장합니다.

동일한 이름의 인스턴스 속성을 수정하면 클래스 속성이 가려집니다 (shadow). 즉, 해당 인스턴스에서 속성에 접근하면 Python 은 클래스 수준의 값 대신 인스턴스별 값을 사용합니다.

>>> ibm.exchange = 'NYSE'
>>> ibm.exchange
'NYSE'
>>> goog.exchange  ## Still using the class attribute
'NASDAQ'
>>> ibm.__dict__['exchange']
'NYSE'

이제 ibm은 클래스 속성을 가리는 자체 exchange 속성을 갖는 반면, goog는 여전히 클래스 속성을 사용합니다.

요약

이 랩에서는 Python 객체 시스템의 내부 작동 방식과 몇 가지 핵심 개념에 대해 배웠습니다. 첫째, Python 객체는 __dict__ 속성을 통해 접근할 수 있는 딕셔너리에 속성을 저장하여 유연성을 제공합니다. 둘째, 동적 속성 추가 및 속성 확인 순서를 포함하여 속성 할당 및 조회가 어떻게 작동하는지 이해했습니다.

또한 클래스와 인스턴스의 관계를 탐구했습니다. 여기서 클래스는 공유 데이터와 동작을 보유하고 인스턴스는 자체 상태를 유지합니다. 또한 메서드 호출이 어떻게 작동하는지, 클래스의 메서드가 self 매개변수를 통해 인스턴스에서 작동하는 방식을 발견했습니다. 이러한 개념을 이해하면 Python 의 OOP 모델에 대한 통찰력이 깊어지고, 디버깅, 클래스 계층 구조 설계, 고급 기능 학습에 유용합니다.