practical_python_design_patterns

Practical Python Design Patterns - Python で学ぶデザインパターン: Singleton Patterns Part. 2 「クラスを単純に利用してみる」の巻 投稿一覧へ戻る

Tags: python , design pattern , singleton pattern

Published 2022年5月15日19:01 by T.Tsuyoshi

Practical Python Design Patterns - The Singleton Pattern 編

Enter the Objects
(クラス入門)

クラスを利用することで、データとそれを利用する関数の論理グループを定義することが可能になります。また、ロガーに対してコンテキストデータ (その時々の状況で値の異なるデータ) を渡すこともできます (例えばログを書き込むファイル名など)。
クラスを最大限に活用するには、新たな考え方を少し取り入れる必要があります。
関連のあるデータと関数をクラスとしてまとめる、ということは、データとそれを操作する関数から成る実体 (instances) を作成するための雛形 (青写真: blue print) を定義する、ということです。そしてこのクラスの実体をオブジェクト (object) と呼んでいます。
我々が作成しているロガーを例にとって考えてみましょう。このロガーをより汎用的に利用できるようにするためにこのロガークラスがファイル名を受け取るようにしておけば (これがコンテキストデータになります)、その情報を基に作成されたこのクラスの実体は、そのファイルに対して書き込みを行うロガーになります。つまり、このロガー (実体) が、指定したファイルに対して書き込みを行うロガークラスのインスタンス (オブジェクト) ということです。
データと、そのデータのみを唯一のエンティティとして操作する関数を組み合わせて考えることが、オブジェクト指向プログラミング (Object-Oriented programming) の基礎になります。
例として単純なロガークラスを実装してみましょう:
logger_class.py
from pathlib import Path


class Logger(object): # ①
""" # ②
ファイルベースロガー

Attributes:
filename: str - ログファイル名
"""


def __init__(self, file_name): # ③
self.file_name = file_name

def _write_log(self, level, msg):
""" # ②
ログレベル文字列を付加したメッセージをファイルに書き込む
"""

with Path(self.file_name).open('a') as log_file:
log_file.write(f'[{level}] {msg}\n')

def critical(self, msg):
self._write_log('CRITICAL', msg)

def error(self, msg):
self._write_log('ERROR', msg)

def warn(self, msg):
self._write_log('WARN', msg)

def info(self, msg):
self._write_log('INFO', msg)

def debug(self, msg):
self._write_log('DEBUG', msg)
このコードを少し追ってみましょう。
class キーワードを使ってクラスを定義します。これがある特定のログファイルに対する書き込みを行う Logger オブジェクトを作成するための「雛形」になります。
OOP (オブジェクト指向プログラミン) の特徴/長所の1つは「継承 (inheritance)」です。あるクラスは、他のクラスの特徴 (data や functions) をすべて引き継いだうえで独自に拡張することができます。
この継承階層の大元に存在するのが object クラスです (ですから、すべてのクラスは object クラスを継承しています)。我々の Logger クラスでは Object クラスしか継承していません。この場合は通常この記述を省略します。つまり、class Logger(object): → class Logger: と記述できます。
doc 文字列です。このクラスを利用する他のプログラマーに対するこのクラスの「取り扱い仕様書」になります。クラスだけではなく、関数 (メソッド) にも記述可能です。
このクラスがインスタンス化される (このクラスのオブジェクトを作成する) 際に呼び出されるコンストラクタメソッドです。ここではログメッセージを書き出すファイル名 (パス文字列) を受け取り「このオブジェクト」の属性として保存しています。この属性値を参照したい場合は、「このオブジェクト自身」を指す self キーワードを利用して、例えば self.file_name のように記述します。
ここで少し「属性 (attributes)」と「メソッド (methods)」について触れておきます。「属性」は、クラス実装時に定義された変数など、オブジェクトの「状態」を表す「データ」を指します。そして、それらのデータを操作する関数が「メソッド」です。あるクラス定義を基にインスタンス化されたオブジェクトのメソッドをそのオブジェクト内で呼び出すには次のようにします:
self.some_method()
この時に Python のインタプリタは、暗黙的に「オブジェクト自身」をこのメソッドに渡してこのメソッドを呼び出します。ですからクラス内で定義したメソッドは必ず第1パラメータとしてこれを受け取るようにします (このパラメータの名前が 'self' でなければダメ、というわけではありません。しかし習慣的にこの名前が使われています)。
そして、先程も少し書きましたが、オブジェクト内で属性を参照するには次のように記述します:
self.some_attribute
この時に Python のインタプリタがやっているのは、オブジェクト自身と指定された属性名をパラメータとして __getattr__() 関数を呼び出すことです。
この __init__() の中では、このオブジェクトの file_name という属性に、呼び出し元が渡した値 (ログファイル名) をセットしています。
残りのコードの内容は以前とほぼ変わりありません。ただし今説明したように、オブジェクト内のメソッドには第1パラメータとして self が渡されており、メソッドの呼び出し時にも self を付けて呼び出しています。
クラス実装の良いパターンは、__init__() 実行終了時にオブジェクトの使用準備が完全に整っているようにすることです。つまり、オブジェクトを使用する際に、これ以外に何らかの設定作業が必要だったり、あるメソッドを実行する必要がないようにすべき、ということです。
また _write_log() メソッド名の先頭に _ (アンダースコア) がついていることを疑問に思う方もいるかもしれません。これは Python における慣習の1つで、このコードを見る他のプログラマーに対して「このメソッドは private と想定しているので、オブジェクト外部から直接呼び出すべきではない」ということを伝えるためのものです。
では、今回実装した新しい Logger クラスを利用してみましょう:
new_script_with_logger.py
from logger_class import Logger # ①

logger_object = Logger('/var/log/class_logger.log') # ②
logger_object.info('クラスロガー経由で書き込んでいます') # ③
Python モジュール (.py ファイル) から定義したロガークラスを読み込みます。
ついでですが、「パッケージ (packages)」と言っている場合は、同じフォルダに集められている関連した機能を提供する Python ファイルの一団のことを指してます。
定義した Logger クラス (これが「雛形」でした) をインスタンス化し、作成したオブジェクトを変数 logger_object にセットしています。この時、このロガーオブジェクトがログメッセージを書き込む先のファイル名を渡しています。これによって、以降このロガーを利用する場合は、書き込む先のファイルのことは一切気にせずに利用することができます。
作成したロガーオブジェクトのメソッドを利用してログメッセージを書き込みます。
このコードを実行後の /var/log/class_logger.log ファイルには、以下のテキストが書き込まれています:
[INFO] クラスロガー経由で書き込んでいます

この投稿をメールでシェアする

0 comments

コメントはまだありません。

コメントを追加する(不適切と思われるコメントは削除する場合があります)