オブジェクトの表現方法

Beginner

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

はじめに

この実験では、Python のオブジェクトが内部でどのように表現されるかを学び、属性の割り当てと検索のメカニズムを理解します。これらの概念は、Python がオブジェクト内のデータと振る舞いをどのように管理するかを理解するための基礎となります。

さらに、クラスとインスタンスの関係を探索し、オブジェクト指向プログラミングにおけるクラス定義の役割を調べます。この知識は、Python のオブジェクト指向機能の理解を深めるのに役立ちます。

これは Guided Lab です。学習と実践を支援するためのステップバイステップの指示を提供します。各ステップを完了し、実践的な経験を積むために、指示に注意深く従ってください。過去のデータによると、この 初級 レベルの実験の完了率は 94%です。学習者から 93% の好評価を得ています。

シンプルな株式クラスの作成

このステップでは、株式を表すシンプルなクラスを作成します。クラスの作成方法を理解することは、Python において非常に重要です。なぜなら、これによって現実世界のオブジェクトとその振る舞いをモデル化することができるからです。このシンプルな株式クラスは、Python のオブジェクトが内部でどのように動作するかを探索するための出発点となります。

まず、Python の対話型シェルを開く必要があります。Python の対話型シェルは、Python コードを試すのに最適な場所です。1 つずつ 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 クラスの特殊なメソッドです。これはコンストラクタと呼ばれ、オブジェクトが作成されるときにオブジェクトの属性を初期化するために使用されます。self パラメータは、作成されるクラスのインスタンスを指します。cost メソッドは、株式の数を 1 株あたりの価格で乗算することで、株式の総コストを計算します。

クラスを定義した後、SimpleStock クラスのインスタンスを作成することができます。インスタンスは、クラスの青写真から作成された実際のオブジェクトです。異なる株式を表す 2 つのインスタンスを作成しましょう。

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

これらのインスタンスは、1 株あたり 490.10 ドルの Google 株式 100 株と、1 株あたり 91.23 ドルの IBM 株式 50 株を表しています。各インスタンスは独自の属性値のセットを持っています。

インスタンスが正しく動作していることを確認しましょう。これは、属性をチェックし、コストを計算することで行うことができます。これにより、クラスとそのメソッドが期待どおりに機能していることを確認できます。

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

cost() メソッドは、株式の数を 1 株あたりの価格で乗算して、それらの株式を保有する総コストを計算します。これらのコマンドを実行することで、インスタンスが正しい属性値を持っており、cost メソッドが正確にコストを計算していることがわかります。

オブジェクトの内部辞書の探索

Python において、オブジェクトは基本的な概念です。オブジェクトは、データを保持し、特定の振る舞いを持つコンテナと考えることができます。Python のオブジェクトの興味深い側面の 1 つは、属性をどのように格納するかです。属性は、オブジェクトに属する変数のようなものです。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 インスタンスでは、属性 namesharesprice がそれぞれの値とともに辞書に格納されています。これが、Python がオブジェクト属性を内部的に実装する方法です。すべてのオブジェクトは、そのすべての属性を保持するプライベートな辞書を持っています。

__dict__ 属性がオブジェクトの内部実装について明らかにすることを理解することは重要です。

  1. 辞書のキーは属性名に対応しています。たとえば、goog インスタンスでは、キー 'name' はオブジェクトの name 属性に対応しています。
  2. 辞書の値は属性値です。したがって、値 'GOOG'goog インスタンスの name 属性の値です。
  3. 各インスタンスは独自の __dict__ を持っています。これは、あるインスタンスの属性が他のインスタンスの属性とは独立していることを意味します。たとえば、goog インスタンスの shares 属性は、ibm インスタンスの shares 属性と異なる値を持つことができます。

この辞書ベースのアプローチにより、Python はオブジェクトに対して非常に柔軟に対応できます。次のステップで見るように、この柔軟性を利用して、様々な方法でオブジェクト属性を変更およびアクセスすることができます。

オブジェクト属性の追加と変更

Python では、オブジェクトは辞書に基づいて実装されています。この実装により、Python はオブジェクト属性を扱う際に高い柔軟性を持っています。他のいくつかのプログラミング言語とは異なり、Python はオブジェクトの属性をクラスで定義されたものに限定しません。これは、オブジェクトが作成された後でも、いつでも新しい属性をオブジェクトに追加できることを意味します。

この柔軟性を、インスタンスの 1 つに新しい属性を追加することで探ってみましょう。クラスのインスタンス 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 オブジェクトの 3 つの重要な特性を示しています。

  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 におけるクラスとインスタンスの関係、およびメソッドの検索方法について探っていきます。これは、オブジェクトを操作する際に 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 が内部で行っていることです。

では、クラス属性を追加してみましょう。クラス属性はすべてのインスタンスによって共有されます。これは、クラスのすべてのインスタンスがこの属性にアクセスでき、それがクラスレベルで 1 回だけ格納されることを意味します。

>>> 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. クラスは、共有データと振る舞い(メソッド)のリポジトリとして機能します。クラスは、すべてのインスタンスが使用できる共通の属性とメソッドを格納しています。

同じ名前のインスタンス属性を変更すると、クラス属性が隠されます。これは、そのインスタンスに対して属性にアクセスするときに、Python がクラスレベルの値ではなくインスタンス固有の値を使用することを意味します。

>>> ibm.exchange = 'NYSE'
>>> ibm.exchange
'NYSE'
>>> goog.exchange  ## 依然としてクラス属性を使用
'NASDAQ'
>>> ibm.__dict__['exchange']
'NYSE'

これで、ibm はクラス属性を隠す独自の exchange 属性を持つようになり、goog は依然としてクラス属性を使用しています。

まとめ

この実験では、Python のオブジェクトシステムの内部動作といくつかの重要な概念を学びました。まず、Python のオブジェクトは __dict__ 属性を介してアクセス可能な辞書に属性を格納しており、柔軟性を提供しています。次に、属性の割り当てと検索の仕組み、動的な属性の追加や属性のチェック順序などを理解しました。

さらに、クラスとインスタンスの関係を探りました。クラスは共有データと振る舞いを保持し、インスタンスは独自の状態を維持します。また、メソッド呼び出しの仕組みも学びました。クラス内のメソッドは self パラメータを通じてインスタンスに作用します。これらの概念を理解することで、Python のオブジェクト指向プログラミング(OOP)モデルに対する理解が深まり、デバッグ、クラス階層の設計、高度な機能の学習に役立ちます。