はじめに
クラスを書くとき、内部の詳細をカプセル化しようとするのが一般的です。このセクションでは、これに関するPythonのプログラミング慣用句をいくつか紹介します。これには、プライベート変数やプロパティが含まれます。
This tutorial is from open-source community. Access the source code
💡 このチュートリアルは英語版からAIによって翻訳されています。原文を確認するには、 ここをクリックしてください
クラスを書くとき、内部の詳細をカプセル化しようとするのが一般的です。このセクションでは、これに関するPythonのプログラミング慣用句をいくつか紹介します。これには、プライベート変数やプロパティが含まれます。
クラスの主な役割の1つは、オブジェクトのデータと内部実装の詳細をカプセル化することです。ただし、クラスはまた、外部世界がオブジェクトを操作するために使用するはずの「パブリック」インターフェイスも定義します。実装の詳細とパブリックインターフェイスのこの違いは重要です。
Pythonでは、クラスとオブジェクトに関するほとんどすべてが「オープン」になっています。
これは、内部実装の詳細を分離しようとしているときの問題です。
Pythonは、何かの意図された使い方を示すためにプログラミング規約に依存しています。これらの規約は命名に基づいています。言語が規則を強制するのではなく、プログラマーが規則を守ることに責任があるという一般的な考え方があります。
先頭に _
がある属性名はすべて「プライベート」と見なされます。
class Person(object):
def __init__(self, name):
self._name = 0
前述の通り、これはただのプログラミングスタイルにすぎません。まだアクセスしたり変更したりすることができます。
>>> p = Person('Guido')
>>> p._name
'Guido'
>>> p._name = 'Dave'
>>>
一般的なルールとして、先頭に _
がある名前は、変数、関数、モジュール名のいずれであれ、内部実装と見なされます。そのような名前を直接使用している場合、多分何か間違っています。より高レベルの機能を探してください。
次のクラスを考えてみましょう。
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
驚くべきことに、属性を全く任意の値に設定できます。
>>> s = Stock('IBM', 50, 91.1)
>>> s.shares = 100
>>> s.shares = "hundred"
>>> s.shares = [1, 0, 0]
>>>
これを見ると、追加のチェックが必要だと思うかもしれません。
s.shares = '50' ## TypeErrorが発生します、これは文字列です
どのようにすればよいでしょうか。
一つのアプローチ:アクセサメソッドを導入する。
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.set_shares(shares)
self.price = price
## "取得" 操作を層化する関数
def get_shares(self):
return self._shares
## "設定" 操作を層化する関数
def set_shares(self, value):
if not isinstance(value, int):
raise TypeError('Expected an int')
self._shares = value
残念ながら、これで既存のコードがすべて壊れてしまいます。s.shares = 50
は s.set_shares(50)
になります。
前のパターンに代わるアプローチがあります。
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
@property
def shares(self):
return self._shares
@shares.setter
def shares(self, value):
if not isinstance(value, int):
raise TypeError('Expected int')
self._shares = value
通常の属性アクセスは、今では @property
と @shares.setter
の下のゲッターとセッターメソッドをトリガーします。
>>> s = Stock('IBM', 50, 91.1)
>>> s.shares ## @property をトリガー
50
>>> s.shares = 75 ## @shares.setter をトリガー
>>>
このパターンでは、ソースコードに変更は必要ありません。新しいセッターは、クラス内での代入時、__init__()
メソッド内を含めても呼び出されます。
class Stock:
def __init__(self, name, shares, price):
...
## この代入は下のセッターを呼び出します
self.shares = shares
...
...
@shares.setter
def shares(self, value):
if not isinstance(value, int):
raise TypeError('Expected int')
self._shares = value
プロパティとプライベート名の使用の間にはしばしば混乱があります。プロパティは内部的に _shares
のようなプライベート名を使用しますが、クラスの残りの部分(プロパティではない)は shares
のような名前を引き続き使用できます。
プロパティは計算されたデータ属性にも便利です。
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
@property
def cost(self):
return self.shares * self.price
...
これにより、余分な丸括弧を省略でき、実際にはメソッドであることを隠すことができます。
>>> s = Stock('GOOG', 100, 490.1)
>>> s.shares ## インスタンス変数
100
>>> s.cost ## 計算された値
49010.0
>>>
最後の例は、オブジェクトにより一貫したインターフェイスを与える方法を示しています。これを行わない場合、オブジェクトの使い方が分かりにくくなるかもしれません。
>>> s = Stock('GOOG', 100, 490.1)
>>> a = s.cost() ## メソッド
49010.0
>>> b = s.shares ## データ属性
100
>>>
なぜ cost
には ()
が必要なのに、shares
には必要ないのでしょうか。プロパティを使うことでこれを解決できます。
@
構文は「デコレーション」と呼ばれます。これは、直後に続く関数定義に適用される修飾子を指定します。
...
@property
def cost(self):
return self.shares * self.price
セクション7で詳細を説明します。
__slots__
属性属性名のセットを制限することができます。
class Stock:
__slots__ = ('name','_shares','price')
def __init__(self, name, shares, price):
self.name = name
...
他の属性に対してはエラーが発生します。
>>> s.price = 385.15
>>> s.prices = 410.2
Traceback (most recent call last):
File "<stdin>", line 1, in?
AttributeError: 'Stock' object has no attribute 'prices'
これはエラーを防ぎ、オブジェクトの使用を制限しますが、実際にはパフォーマンス向上のために使用され、Pythonがメモリをより効率的に使用するようになります。
プライベート属性、プロパティ、スロットなどに対して過度にこだわらないでください。それらは特定の目的を果たし、他のPythonコードを読む際に見ることがあります。ただし、ほとんどの日常のコーディングでは必要ありません。
プロパティは、オブジェクトに「計算済み属性」を追加するための便利な方法です。stock.py
では、Stock
オブジェクトを作成しました。作成したオブジェクトでは、異なる種類のデータの抽出方法にわずかな不整合があることに注意してください。
>>> from stock import Stock
>>> s = Stock('GOOG', 100, 490.1)
>>> s.shares
100
>>> s.price
490.1
>>> s.cost()
49010.0
>>>
特に、cost
がメソッドであるため、cost
に追加の ()
を付ける必要があることに注意してください。
cost()
の追加の ()
をなくすことができます。Stock
クラスを取得して変更して、コスト計算が次のように機能するようにします。
>>> ================================ RESTART ================================
>>> from stock import Stock
>>> s = Stock('GOOG', 100, 490.1)
>>> s.cost
49010.0
>>>
cost
をプロパティとして定義したため、s.cost()
を関数として呼び出してみて、機能しないことを確認してください。
>>> s.cost()
... 失敗...
>>>
この変更を行うと、以前の pcost.py
プログラムが壊れる可能性があります。cost()
メソッドの ()
を取り除く必要がある場合があります。
shares
属性を変更して、値をプライベート属性に格納し、常に整数値に設定されるようにするための一対のプロパティ関数を使用します。期待される動作の例は次のとおりです。
>>> ================================ RESTART ================================
>>> from stock import Stock
>>> s = Stock('GOOG',100,490.10)
>>> s.shares = 50
>>> s.shares = 'a lot'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: expected an integer
>>>
Stock
クラスを変更して、__slots__
属性を持たせます。その後、新しい属性を追加できないことを確認します。
>>> ================================ RESTART ================================
>>> from stock import Stock
>>> s = Stock('GOOG', 100, 490.10)
>>> s.name
'GOOG'
>>> s.blah = 42
... 何が起こるか見る...
>>>
__slots__
を使用すると、Pythonはオブジェクトのより効率的な内部表現を使用します。上記の s
の基礎となる辞書を調べようとすると何が起こりますか?
>>> s.__dict__
... 何が起こるか見る...
>>>
__slots__
は、データ構造として機能するクラスに対する最適化として最も一般的に使用されます。スロットを使用することで、そのようなプログラムははるかに少ないメモリを使用し、少し高速に実行されます。ただし、他のほとんどのクラスでは __slots__
を避けるべきです。
おめでとうございます!クラスとカプセル化の実験を完了しました。LabExでさらに多くの実験を行って、スキルを向上させましょう。