はじめに
この実験では、特殊メソッドを再定義することでオブジェクトの動作をカスタマイズする方法を学びます。また、ユーザー定義オブジェクトの表示方法を変更し、オブジェクトを比較可能にする方法も学びます。
さらに、コンテキストマネージャを作成する方法を学びます。この実験で変更するファイルは stock.py です。
この実験では、特殊メソッドを再定義することでオブジェクトの動作をカスタマイズする方法を学びます。また、ユーザー定義オブジェクトの表示方法を変更し、オブジェクトを比較可能にする方法も学びます。
さらに、コンテキストマネージャを作成する方法を学びます。この実験で変更するファイルは stock.py です。
__repr__ を使ったオブジェクト表現の改善Python では、オブジェクトを文字列として 2 つの異なる方法で表現することができます。これらの表現はそれぞれ異なる目的を持ち、様々なシナリオで役立ちます。
1 つ目は 文字列表現 です。これは str() 関数によって作成され、print() 関数を使用すると自動的に呼び出されます。文字列表現は人間が読みやすいように設計されており、オブジェクトを理解しやすい形式で提示します。
2 つ目は コード表現 です。これは repr() 関数によって生成されます。コード表現は、オブジェクトを再作成するために必要なコードを示します。コード内でオブジェクトを正確かつ明確に表現する方法を提供することに重点が置かれています。
Python の組み込み date クラスを使った具体的な例を見てみましょう。これにより、文字列表現とコード表現の違いを実際に確認することができます。
>>> from datetime import date
>>> d = date(2008, 7, 5)
>>> print(d) ## Uses str()
2008-07-05
>>> d ## Uses repr()
datetime.date(2008, 7, 5)
この例では、print(d) を使用すると、Python は date オブジェクト d に対して str() 関数を呼び出し、YYYY-MM-DD 形式の人間が読みやすい日付が得られます。対話型シェルで単に d と入力すると、Python は repr() 関数を呼び出し、date オブジェクトを再作成するために必要なコードが表示されます。
repr() 文字列を明示的に取得する方法はいくつかあります。以下に例を示します。
>>> print('The date is', repr(d))
The date is datetime.date(2008, 7, 5)
>>> print(f'The date is {d!r}')
The date is datetime.date(2008, 7, 5)
>>> print('The date is %r' % d)
The date is datetime.date(2008, 7, 5)
では、この概念を Stock クラスに適用しましょう。__repr__ メソッドを実装することで、このクラスを改善します。この特殊メソッドは、Python がオブジェクトのコード表現を必要とするときに呼び出されます。
これを行うには、エディタで stock.py ファイルを開きます。次に、Stock クラスに __repr__ メソッドを追加します。__repr__ メソッドは、Stock オブジェクトを再作成するために必要なコードを示す文字列を返す必要があります。
def __repr__(self):
return f"Stock('{self.name}', {self.shares}, {self.price})"
__repr__ メソッドを追加した後、完成した Stock クラスは次のようになります。
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
def cost(self):
return self.shares * self.price
def sell(self, shares):
self.shares -= shares
def __repr__(self):
return f"Stock('{self.name}', {self.shares}, {self.price})"
では、修正した Stock クラスをテストしましょう。ターミナルで次のコマンドを実行して、Python の対話型シェルを開きます。
python3
対話型シェルが開いたら、次のコマンドを試してみましょう。
>>> import stock
>>> goog = stock.Stock('GOOG', 100, 490.10)
>>> goog
Stock('GOOG', 100, 490.1)
また、__repr__ メソッドが株式ポートフォリオでどのように機能するかも確認できます。以下に例を示します。
>>> import reader
>>> portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
>>> portfolio
[Stock('AA', 100, 32.2), Stock('IBM', 50, 91.1), Stock('CAT', 150, 83.44), Stock('MSFT', 200, 51.23), Stock('GE', 95, 40.37), Stock('MSFT', 50, 65.1), Stock('IBM', 100, 70.44)]
ご覧の通り、__repr__ メソッドにより、対話型シェルやデバッガで Stock オブジェクトを表示する際に、はるかに有益な情報が表示されるようになりました。現在では、各オブジェクトを再作成するために必要なコードが表示されるため、デバッグやオブジェクトの状態の理解に非常に役立ちます。
テストが終了したら、次のコマンドを実行して Python インタープリターを終了できます。
>>> exit()
__eq__ を使ってオブジェクトを比較可能にするPython では、== 演算子を使って 2 つのオブジェクトを比較すると、実際には __eq__ という特殊メソッドが呼び出されます。デフォルトでは、このメソッドはオブジェクトの同一性を比較します。つまり、オブジェクトが同じメモリアドレスに格納されているかどうかをチェックし、内容を比較するわけではありません。
例を見てみましょう。Stock クラスがあり、同じ値を持つ 2 つの Stock オブジェクトを作成したとします。そして、== 演算子を使ってこれらを比較しようとします。Python インタープリターでは次のように操作できます。
まず、ターミナルで次のコマンドを実行して Python インタープリターを起動します。
python3
次に、Python インタープリターで以下のコードを実行します。
>>> import stock
>>> a = stock.Stock('GOOG', 100, 490.1)
>>> b = stock.Stock('GOOG', 100, 490.1)
>>> a == b
False
ご覧の通り、2 つの Stock オブジェクト a と b の属性(name、shares、price)の値は同じですが、Python はこれらを異なるオブジェクトとみなします。なぜなら、これらは異なるメモリ位置に格納されているからです。
この問題を解決するために、Stock クラスに __eq__ メソッドを実装することができます。このメソッドは、Stock クラスのオブジェクトに == 演算子が使用されるたびに呼び出されます。
では、再度 stock.py ファイルを開きましょう。Stock クラスの中に、以下の __eq__ メソッドを追加します。
def __eq__(self, other):
return isinstance(other, Stock) and ((self.name, self.shares, self.price) ==
(other.name, other.shares, other.price))
このメソッドが行うことを分解してみましょう。
isinstance 関数を使って、other オブジェクトが Stock クラスのインスタンスであるかどうかをチェックします。これは、Stock オブジェクトを他の Stock オブジェクトとのみ比較したいからです。other が Stock オブジェクトである場合、self オブジェクトと other オブジェクトの属性(name、shares、price)を比較します。Stock インスタンスであり、それらの属性が同一である場合にのみ True を返します。__eq__ メソッドを追加した後、完成した Stock クラスは次のようになります。
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
def cost(self):
return self.shares * self.price
def sell(self, shares):
self.shares -= shares
def __repr__(self):
return f"Stock('{self.name}', {self.shares}, {self.price})"
def __eq__(self, other):
return isinstance(other, Stock) and ((self.name, self.shares, self.price) ==
(other.name, other.shares, other.price))
では、改善した Stock クラスをテストしましょう。再度 Python インタープリターを起動します。
python3
次に、Python インタープリターで以下のコードを実行します。
>>> import stock
>>> a = stock.Stock('GOOG', 100, 490.1)
>>> b = stock.Stock('GOOG', 100, 490.1)
>>> a == b
True
>>> c = stock.Stock('GOOG', 200, 490.1)
>>> a == c
False
素晴らしい!これで、Stock オブジェクトはメモリアドレスではなく、内容に基づいて適切に比較できるようになりました。
__eq__ メソッドの isinstance チェックは非常に重要です。これにより、Stock オブジェクト同士のみを比較することが保証されます。このチェックがない場合、Stock オブジェクトを Stock オブジェクトではないものと比較するとエラーが発生する可能性があります。
テストが終了したら、次のコマンドを実行して Python インタープリターを終了できます。
>>> exit()
コンテキストマネージャは Python の特殊な種類のオブジェクトです。Python では、オブジェクトにはその振る舞いを定義するさまざまなメソッドがあります。コンテキストマネージャは特に、__enter__ と __exit__ という 2 つの重要なメソッドを定義します。これらのメソッドは with 文と連携して動作します。with 文は、コードブロックに特定のコンテキストを設定するために使用されます。特定のことが起こる小さな環境を作成するようなもので、コードブロックが終了すると、コンテキストマネージャが後片付けを行います。
このステップでは、非常に便利な機能を持つコンテキストマネージャを作成します。これは、標準出力 (sys.stdout) を一時的にリダイレクトします。標準出力は、Python プログラムの通常の出力が行われる場所で、通常はコンソールです。標準出力をリダイレクトすることで、出力をファイルに送ることができます。これは、コンソールに表示されるだけの出力を保存したい場合に便利です。
まず、コンテキストマネージャのコードを書くための新しいファイルを作成する必要があります。このファイルを redirect.py と名付けます。ターミナルで以下のコマンドを使用して作成できます。
touch /home/labex/project/redirect.py
ファイルが作成されたら、エディタで開きます。開いたら、以下の Python コードをファイルに追加します。
import sys
class redirect_stdout:
def __init__(self, out_file):
self.out_file = out_file
def __enter__(self):
self.stdout = sys.stdout
sys.stdout = self.out_file
return self.out_file
def __exit__(self, ty, val, tb):
sys.stdout = self.stdout
このコンテキストマネージャが行うことを分解してみましょう。
__init__: これは初期化メソッドです。redirect_stdout クラスのインスタンスを作成するときに、ファイルオブジェクトを渡します。このメソッドは、そのファイルオブジェクトをインスタンス変数 self.out_file に格納します。つまり、出力をリダイレクトする先を覚えておきます。__enter__:
sys.stdout を保存します。後で元に戻す必要があるため、これは重要です。sys.stdout をファイルオブジェクトで置き換えます。この時点から、通常はコンソールに出力されるものはすべてファイルに出力されるようになります。with ブロック内でファイルオブジェクトを使用したい場合に便利です。__exit__:
sys.stdout を復元します。したがって、with ブロックが終了した後は、出力は通常通りコンソールに戻ります。ty)、例外の値 (val)、トレースバック (tb) です。これらのパラメータはコンテキストマネージャプロトコルで必要とされます。with ブロック内で発生する可能性のある例外を処理するために使用されます。では、コンテキストマネージャをテストしましょう。テーブルの出力をファイルにリダイレクトするために使用します。まず、Python インタープリターを起動します。
python3
次に、インタープリターで以下の Python コードを実行します。
>>> import stock, reader, tableformat
>>> from redirect import redirect_stdout
>>> portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
>>> formatter = tableformat.create_formatter('text')
>>> with redirect_stdout(open('out.txt', 'w')) as file:
... tableformat.print_table(portfolio, ['name','shares','price'], formatter)
... file.close()
...
>>> ## Let's check the content of the output file
>>> print(open('out.txt').read())
name shares price
---------- ---------- ----------
AA 100 32.2
IBM 50 91.1
CAT 150 83.44
MSFT 200 51.23
GE 95 40.37
MSFT 50 65.1
IBM 100 70.44
素晴らしい!コンテキストマネージャは期待通りに動作しました。テーブルの出力を out.txt ファイルに正常にリダイレクトしました。
コンテキストマネージャは Python の非常に強力な機能です。適切にリソースを管理するのに役立ちます。コンテキストマネージャの一般的な使用例をいくつか紹介します。
このパターンは非常に重要です。with ブロック内で例外が発生した場合でも、リソースが適切にクリーンアップされることを保証するからです。
テストが終了したら、Python インタープリターを終了できます。
>>> exit()
この実験では、__repr__ メソッドを使ってオブジェクトの文字列表現をカスタマイズする方法、__eq__ メソッドを使ってオブジェクトを比較可能にする方法、そして __enter__ と __exit__ メソッドを使ってコンテキストマネージャを作成する方法を学びました。これらの特殊な「ダンダーメソッド」は、Python のオブジェクト指向機能の基礎となっています。
クラスにこれらのメソッドを実装することで、オブジェクトを組み込み型のように振る舞わせ、Python の言語機能とスムーズに統合することができます。特殊メソッドにより、カスタム文字列表現、オブジェクト比較、コンテキスト管理などのさまざまな機能が可能になります。Python の学習を進めるにつれて、その強力なオブジェクトモデルを活用するためのさらに多くの特殊メソッドを発見するでしょう。