Update 2023.11.16 2017.05.05

Python デザインパターン サンプルコード Singleton
Mark Summerfield『実践 Python 3』デザインパターンのサンプルコード
Python3(3.11)で動くソースコード(.pyファイル .ipynbファイル)あります
「anaconda3」on .py「PyCharm」.ipynb「Jupyter Notebook」

著作権の問題があるので,本に書いてないことだけを解説します。つまり,視点を変えて解説します。

◆◆Singleton パターンの使われる場面◆◆

GoFによれば,こんな場面で使われるそうだ。
クラスにインスタンスが1つしか存在しないことが重要になる場合がある。たとえば,システムには多数のプリンタを接続することができるが,プリンタスプーラは1つでなければならない。同様に,ファイルシステムやウィンドウマネージャも1つでなければならない。1つのデジタルフィルタは,1つの A/D コンバータを持つだろう。また,1つの会計システムは1つの会社の専用になるだろう。

次項に書いてあるが,この「GoFの23のデザインパターン」にもいくつかシングルトンが使われることがあるそうです。

(2023-11-16)Python3.11で動作確認済み

【重要な注意】ソースコードの16行目にあるURLはブラウザに貼り付けつけるだけでファイルのダウンロードができます(再現性あり)。本プログラムは,そのファイルを1回だけダウンロードできるというシングルトンまがいのプログラムです。

ただ,デバッグでは確かに1回だけはダウンロードできたのだが,その後はダウンロードを再現するのに苦労しました。

本来のシングルトンはクラスのインスタンスオブジェクトを1つしかつくることのできないプログラムです。本プログラムはグローバル関数で疑似的に実現しています。本来のシングルトンは次を参照してください。

結城 浩「Java言語で学ぶデザインパターン入門」をPython化
https://yamakatsusan.web.fc2.com/pythonpattern05.html
『Python デザインパターン サンプルコード Singleton』


◆◆Singleton パターンとは◆◆

GoFによれば,Singleton パターンの目的は, 「あるクラスに対してインスタンスが1つしか存在しないことを保証し,それにアクセスするためのグローバルな方法を提供する。」

GoFによれば,Singleton パターンは次のような場合に利用できる。
・クラスに対してインスタンスが1つしか存在してはならず,また,クライアントが,そのインスタンスを公開されたアクセスポイントを通してアクセスできるようにしなければならない場合。
・唯一のインスタンスがサブクラス化により拡張可能で,また,クライアントが,拡張されたインスタンスをコードの修正なしに利用できるようにしたい場合。

GoFによれば,関連するパターンは次のようなものである。
Abstract Factory パターン,Builder パターン,Prototype パターン: これらのパターンは Singleton パターンを使って実装することができる。

◆Singleton パターンの注意点

本来,クラスというものは,1つのクラスに対して複数のインスタンスを作るのが普通です。 しかし,Singleton はその真逆の使い方であり必要になる場面は希少であるので,事前の検討により本当に適用して大丈夫なのかよく吟味することが重要です。

また,原著でも言及していますが,Singleton パターンには,多くのアリゴリズムがあり,「Python Cookbook」などに紹介されているそうです。多くは,__new__や__init__の特殊メソッドを使ったものです。

このサイトの別記事でも掲載されているので参考にしてください。
https://yamakatsusan.web.fc2.com/hp_software/pythonpattern05.html
『Singleton パターン 結城 浩「Java言語で学ぶデザインパターン入門」をPython化』

◆◆Singleton パターンのサンプルのクラスの構成◆◆

ソースコードは巻末にあり,ソースファイルはこのWebの左上隅にありダウンロードできます。

クラスはありません
グローバル関数が1つあるだけです

Singleton パターンの本来の目的は,1つのクラスを複数回インスタンス化しても1つのインスタンスしかできない特殊なクラスを提供することにある。しかし,サンプルはそうではない。

◆◆Singleton パターンの実装◆◆

サンプルは,1つのグローバル関数だけで,外のモジュールから呼ばれることを想定しています。メインルーチンも付いていますが,あくまでも,debug 用です。

【debug 用メインルーチン】
39
40
41
42
43
44
45
if __name__ == "__main__":
    import sys
    if sys.stdout.isatty():
        print(get())
    else:
        print("Loaded OK")
    print(get())

41行目の動作ですが,Python ドキュメントには,
「文字エンコーディングはプラットフォーム依存です。Windows では、ストリームが対話型 (isatty() メソッドが True を返す場合) であれば、コンソールのコードページが、それ以外では ANSI コードページが使用されます。」
と書かれていますが,debug では,False になってしまうので,やむを得ず,45行目を書き足して関数を呼べるようにしました。

サンプルは,カナダ銀行のWebサイトにある CSV ファイル
  http://www.bankofcanada.ca/stats/assets/csv/fx-seven-day.csv
をダウンロードしようしていますが,このサイトは既にないので,
  https://www.bankofcanada.ca/rates/exchange/daily-exchange-rates/
に有った CSV ファイルをダウンロードして,一部をこのサイトにアップロードしました。

CSV ファイルは,NotePadなどテキストのリーダーで読めば次のようなものです。Wクリックして開くと Excell の表になります。

【FX_RATES.csv】

    
Date,FXAUDCAD,FXBRLCAD,FXCNYCAD,FXEURCAD,FXHKDCAD,FXINRCAD
2017/1/3,0.9702,0.4121,0.193,1.3973,0.173212,0.01965
2017/1/4,0.9678,0.4129,0.192,1.393,0.171687,0.01959
2017/1/5,0.9708,0.4133,0.1922,1.4008,0.170792,0.01954
2017/1/6,0.9668,0.4116,0.1911,1.3953,0.170556,0.01942
2017/1/9,0.9728,0.4135,0.1907,1.3967,0.170572,0.01942

書式としてこれが期待されているものか否かは確認できません。このモジュールのグローバル関数の戻り値は,次のようになります。

【def getの戻り値】

    
{'2017/1/3 (0.9702)': 0.01965, '2017/1/4 (0.9678)': 0.01959,
'2017/1/5 (0.9708)': 0.01954, '2017/1/6 (0.9668)': 0.01942,
'2017/1/9 (0.9728)': 0.01942}

この辞書は,グローバル関数の中で作られた書式です。1列目と2列目と最後の列が使われています。この書式が正しいとしても,それらの列が本来何を表すのかを確認することはできません。

このCSV ファイルを関数が呼ばれるたびにダウンロードしないことで,この関数が Singleton になっている。それを実現しているのが,20行目からの if 文である。

前にも書いたが,Singleton パターンの本来の目的は,1つのクラスを複数回インスタンス化しても1つのインスタンスしかできない特殊なクラスを提供することにある。しかし,サンプルはそうではないことに注意してください。

◆◆ソースコード◆◆

このWebの左上隅からダウンロードできます。

ソースファイルは1つです。CSV ファイルも添付します
・Rates.py;Singleton パターンのPythonサンプル
・FX_RATES.csv;カナダ銀行のFXレート表の一部(書式が正しいか不明)

16行目と45行目がオリジナルから変更されています。

【Rates.py】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#!/usr/bin/env python3
# Copyright c 2012-13 Qtrac Ltd. All rights reserved.
# This program or module is free software: you can redistribute it
# and/or modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version. It is provided for
# educational purposes and is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.

import re
import urllib.request


_URL = "https://yamakatsusan.web.fc2.com/FX_RATES.csv"


def get(refresh=False):
    if refresh:
        get.rates = {}
    if get.rates:
        return get.rates
    with urllib.request.urlopen(_URL) as file:
        for line in file:
            line = line.rstrip().decode("utf-8")
            if not line or line.startswith(("#", "Date")):
                continue
            name, currency, *rest = re.split(r"\s*,\s*", line)
            key = "{} ({})".format(name, currency)
            try:
                get.rates[key] = float(rest[-1])
            except ValueError as err:
                print("error {}: {}".format(err, line))
    return get.rates
get.rates = {}


if __name__ == "__main__":
    import sys
    if sys.stdout.isatty():
        print(get())
    else:
        print("Loaded OK")
    # print(get())           # 故意にエラー?

以上

トップページに戻る
inserted by FC2 system