さるへい備忘録

さるへいのやったことを綴っているブログです。基本的にテクノロジーの話題です。

Pythonで学ぶデザインパターン入門Builderパターン

結城先生の Java言語で学ぶデザインパターン入門 のコードを Python に書き直していくシリーズ第7段です。

Builderパターンとは

具体的な処理の骨組みを先につくっておくというパターンです。
Template Methodパターンでは、スーパークラスがサブクラスをコントロールしてましたが、このBuilderパターンでは他クラスでコントロールします。

ソースコード

コードの概要

文書を作成するプログラムを作成します。
文書を構成するためには、 Builder クラスを継承します。 Builder クラスを継承したクラスを、 Director クラスがコントロールして文書を具体的に作るといったものになります。

ソースコード

Builder

# coding: utf-8
from abc import ABCMeta, abstractmethod


class Builder(metaclass=ABCMeta):

    @abstractmethod
    def make_title(self, title: str):
        pass

    @abstractmethod
    def make_string(self, string: str):
        pass

    @abstractmethod
    def make_items(self, items: list):
        pass

    @abstractmethod
    def close(self):
        pass

文書を作成するための抽象クラスです。
後に紹介する Direcctor クラスにコントロールされています。

Director

# coding: utf-8
from Builder.builder import Builder


class Director(object):

    def __init__(self, builder: Builder):
        self.__builder = builder

    def construct(self):
        self.__builder.make_title('Greeting')
        self.__builder.make_string('朝から昼にかけて')
        self.__builder.make_items([
            'おはようございます。',
            'こんにちは。'
        ])
        self.__builder.make_string('夜に')
        self.__builder.make_items([
            'こんばんは。',
            'おやすみなさい。',
            'さようなら。'
        ])
        self.__builder.close()

Builder を継承したクラスをコントロールして文書を作成します。
construct で文書を作成します。

TextBuilder(Builderを継承)

# coding: utf-8
from Builder.builder import Builder


class TextBuilder(Builder):

    def __init__(self):
        self.__buffer = ''

    def make_title(self, title: str):
        self.__buffer += '================================\n'
        self.__buffer += '「%s」\n' % title
        self.__buffer += '\n'

    def make_string(self, string: str):
        self.__buffer += '■ %s \n' % string
        self.__buffer += '\n'

    def make_items(self, items: list):
        for i in items:
            self.__buffer += '・%s \n' % i
        self.__buffer += '\n'

    def close(self):
        self.__buffer += '================================\n'

    def get_result(self) -> str:
        return self.__buffer

テキストの文書を作成するクラスです。
最終的には文字列として文書が出力されます。

HTMLBuilder(Builderを継承)

# coding: utf-8
from Builder.builder import Builder


class HTMLBuilder(Builder):

    def __init__(self):
        self.__filename = None

    def make_title(self, title: str):
        self.__filename = '%s.html' % title
        with open(self.__filename, 'a') as f:
            f.write('<html><head><title>%s</title></head><body>' % title)
            f.write('<h1>%s</h1>' % title)

    def make_string(self, string: str):
        with open(self.__filename, 'a') as f:
            f.write('<p>%s</p>' % string)

    def make_items(self, items: list):
        with open(self.__filename, 'a') as f:
            f.write('<ul>')
            for i in items:
                f.write('<li>%s</li>' % i)
            f.write('</ul>')

    def close(self):
        with open(self.__filename, 'a') as f:
            f.write('</body></html>')

    def get_result(self) -> str:
        return self.__filename

HTMLファイルとして文書が作成されるクラスです。
最終的にはファイル名が出力されます。

実行ファイル

# coding: utf-8
import sys

from Builder.html_builder import HTMLBuilder
from Builder.text_builder import TextBuilder
from Builder.director import Director


def usage():
    print('python main.py plain  プレーンテキストで文書作成')
    print('python main.py html   HTMLファイルで文書作成')


if __name__ == '__main__':
    if len(sys.argv) != 2:
        usage()
        exit()

    if sys.argv[1] == 'plain':
        html_builder = TextBuilder()
        director = Director(html_builder)
        director.construct()
        result = html_builder.get_result()
        print(result)
    elif sys.argv[1] == 'html':
        html_builder = HTMLBuilder()
        director = Director(html_builder)
        director.construct()
        filename = html_builder.get_result()
        print(filename)
    else:
        usage()
        exit()

実行結果

python Builder/main.py html
Greeting.html

python Builder/main.py plain
================================
「Greeting」

■ 朝から昼にかけて 

・おはようございます。 
・こんにちは。 

■ 夜に 

・こんばんは。 
・おやすみなさい。 
・さようなら。 

================================

実際どう便利か

Builderパターンを利用する時の便利な点は、Directorクラスは基本的に何も知らなくて良いことです。
Directorクラスは何も知らなくて良いのですが、ただどうやって文書を作成するかの方法だけを指定する必要があります。
実際、 Builderクラスを継承したインスタンスであればなんでもDirectorクラスは受け入れます。
Builderを継承したクラスを入れ替えることができるので、いろいろなBuilderクラスを構築して文書を作成することができます。

ドラゴンクエストXを支える技術を読んでみた

ドラゴンクエストXを支える技術を読んでみたので紹介します。

gihyo.jp

ざっくりどんな本か

  • ドラゴンクエストXがどのように作られたかを紹介している本
  • 技術の知識があまりなくてもよくわかる

といった内容の本です。

良かった点

この本の一番良かったところは、 読み物としてとてつもなく面白かったこと です。
ゲームの中でみかける技術を素人にもわかりやすいように噛み砕いて説明してあるので、誰もが理解することのできる本になっています。
思ったより強引な方法で仕組みができているんだな?みたいなことがわかるのはとても面白かったです。

悩ましかった点

個人的な意見なのですが、 WEBアプリを作成するエンジニアにはそこまで役に立つ知識はなかったかもなと感じました。
読み物としても面白いし、3Dの知識やドラゴンクエストのニッチな機能の仕組みなどが学べたりするのですが、正直WEBエンジニアとしてのキャリアパスで利用するかなぁ・・・というものが多かった印象です。

こういう人に向いている

この本はかなりの人が楽しめる本かなぁと思います。是非皆さんに読んで欲しいおすすめの一冊ですね。
ただ、知識を得たいという方は確実にその知識にピンポイントな本を読んだほうが目的にはかないそうです。

インフラエンジニアの教科書2を読んでみた

以前読んだインフラエンジニアの教科書の続きを読みました。

saruhei1989.hatenablog.com

公式サイトはこちら

www.c-r.com

ざっくりどんな本か

  • 通信などインフラの基礎の仕組み

が記載されています。
各項目は各項目の専門書ほど詳しく書かれていないのですが、仕組みを知るには十分すぎるほどの記載があります。

良かった点

世の中のエンジニアがふんわりしか理解してないであろう箇所がかなり詳細に記載されているので非常に勉強になりました。
メモリの仕組みなど、皆さん知っているつもりでもかなりあやふやという事はあるのではないでしょうか?そんな人に突き刺さる本でした。 この本の中身全部把握したら相当なインフラの知識がついたエンジニアになれるのではないでしょうか?そんなレベルの知識が詰め込まれていました。

とても現場寄りの本で、Linuxコマンドで具体的に出力などを紹介しながらの説明が非常にわかりやすかったです。

悩ましい点

特にないですね。文字と説明が多いので文字が嫌いな人はしんどいかもです。

こういう人に向いている

正直全エンジニアに読んで欲しい本です。
いままで出会った中でも、この知識をちゃんと網羅している方には出会ったことがないです。
インフラ専門のエンジニアの方も実はRDBの中身の理解があやふやだったりします。
そんな方にも対応している本で、本当に幅広い知識がつく本です。

Pythonでsshtunnelをつかって本番DBにトンネリング接続を試してみる

パフォーマンステストをする際に開発用のDBじゃ負荷がわかりづらいみたいなことはありませんか?

そんな時にもう開発環境から本番までパフォーマンス測定のためにつなげてみよう!ってなったのでそのやり方を紹介します。

sshtunnel

Python製のsshトンネリングに使えるライブラリ

sshtunnel.readthedocs.io

sshトンネルの用途だったら様々な箇所で有用。

実際のコード

flaskでつなげたのですが、ちょうどdbのsshtunnelをしている箇所だけ抜き出しました。

# coding: utf-8
import sshtunnel

...
...

tunnel = sshtunnel.SSHTunnelForwarder(
    ssh_address_or_host='server_address',
    ssh_username='username',
    ssh_port=port_num,
    remote_bind_address=(
        '踏み台サーバからのアドレス',
        ポート番号
    )
)

tunnel.start()


app.config['DBのURLを入れるためのconfigのkey'] = 'mysql+pymysql://user:pass@127.0.0.1:{}/dbname?charset=utf8mb4'.format(tunnel.local_bind_port) 

...
...

sshtunnel.SSHTunnelForwarder の引数でパスワード設定などもできるので足りない設定があったらいろいろ付け足しましょう。

本番につなげる場合は、できるだけテスト項目をつくりこんでさっと終わらせましょうね。

Pythonで学ぶデザインパターン入門Prototypeパターン

結城先生の Java言語で学ぶデザインパターン入門 のコードを Python に書き直していくシリーズ第6段です。

Prototypeパターンとは

今回もインスタンス生成に関するパターンです。
ざっくり説明すると、インスタンスの雛形をどこかに保持しておき、それを元に新しいインスタンスを作成するパターンですね。
コピーのような形で作成するのがこのデザインパターンの特徴です。

ソースコード

コードの概要

文字列を渡すと、特定の装飾をした文字列が出力されるといったコードになります。
たとえば、 Hello と渡したら、 *Hello* のように装飾されるといったコードです。
例の * のような装飾の文字が可変になる仕組みになっております。

今回は Manager というクラスで、 Product というクラスを継承したインスタンスをcloneしていきます。
つまり、 Managerインスタンスを保持しておけば好きな時にcloneできるといった仕組みです。

ソースコード

Product

# coding: utf-8
from abc import ABCMeta, abstractmethod


class Product(metaclass=ABCMeta):

    @abstractmethod
    def use(self, s: str):
        pass

    @abstractmethod
    def create_clone(self):
        pass

このデザインパターンで利用する、cloneするインスタンスの抽象クラスです。
後術の Manager クラスで create_clone メソッドを呼び出して複製します。

Manager

# coding: utf-8
from prototype.framework.product import Product


class Manager(object):

    def __init__(self):
        self.__showcase = {}

    def register(self, name: str, proto: Product):
        self.__showcase[name] = proto

    def create(self, protoname: str) -> Product:
        p = self.__showcase.get(protoname)
        return p.create_clone()

具体的にインスタンスを複製するクラスです。
登録したら、そのインスタンスの複製を作成できるようになります。

MassageBox (Productを継承)

# coding: utf-8
import copy

from prototype.framework.product import Product


class MessageBox(Product):

    def __init__(self, decochar: str):
        if len(decochar) > 1:
            raise TypeError('文字は一文字')
        self.__decochar = decochar

    def use(self, s: str):
        length = len(s.encode('utf-8'))
        for i in range(length + 4):
            print(self.__decochar, end='')
        print('')
        print('%s %s %s' % (self.__decochar, s , self.__decochar))
        for i in range(length + 4):
            print(self.__decochar, end='')
        print('')

    def create_clone(self) -> Product:
        return copy.copy(self)

複製するためのインスタンスです。具体的には

***************
* warning box *
***************

のような形で文字列を4方から取り囲む形に出力されます。

UnderlinePen (Productを継承)

# coding: utf-8
import copy

from prototype.framework.product import Product


class UnderlinePen(Product):

    def __init__(self, ulchar):
        if len(ulchar) > 1:
            raise TypeError('文字は一文字')
        self.__ulchar = ulchar

    def use(self, s: str):
        length = len(s.encode('utf-8'))
        print('%s %s %s' % (self.__ulchar, s, self.__ulchar))
        print(' ', end='')
        for i in range(length):
            print(self.__ulchar, end='')
        print('')

    def create_clone(self) -> Product:
        return copy.copy(self)

複製するためのインスタンス2です。具体的には

~ Hello World ~
 ~~~~~~~~~~~

のように下線のような形で文字列を装飾します。

実行ファイル

# coding: utf-8
from prototype.framework.manager import Manager
from prototype.message_box import MessageBox
from prototype.underline_pen import UnderlinePen

if __name__ == '__main__':
    manager = Manager()
    upen = UnderlinePen('~')
    mbox = MessageBox('*')
    sbox = MessageBox('/')
    manager.register('strong message', upen)
    manager.register('warning box', mbox)
    manager.register('slash box', sbox)

    p1 = manager.create('strong message')
    p1.use('Hello World')
    p2 = manager.create('warning box')
    p2.use('warning box')
    p3 = manager.create('slash box')
    p3.use('Hello World')

実行結果

~ Hello World ~
 ~~~~~~~~~~~
***************
* warning box *
***************
///////////////
/ Hello World /
///////////////

実際どう便利か

このデザインパターンは使い所が非常に難しいです。
インスタンスを鬼のように作成して、同じ動作でインスタンスを沢山作成する場合は非常に使えるんではないでしょうか。
また、インスタンスを複製したいけど、マニュアル動作などから生成してるので複製が難しいといった場合も有用ですね。

Pythonでlistをsetで重複排除してソート

Pythonでsetを使う際にいろいろ困ったのでこちらに備忘録を残します。

setとは?

集合を扱うための仕組み。
公式ドキュメントは以下です。

docs.python.org

特徴としては、list みたいな感じだけど重複除去されているということです。

item = set()
item.add(1)
item.add(2)
item.add(3)
item
-> {1, 2, 3}
item.add(1)
item
-> {1, 2, 3}

また、普通の set はミュータブルですが、イミュータブルな frozenset も用意されています。

listの重複を排除

さて、発端としては僕がlistの重複を排除しようとしたことです。

さて、実行してみましょう。

set(["あ", "い", "う", "え", "お", "あ"])
-> {'い', 'お', 'う', 'え', 'あ'}

見事重複が排除されました!
しかしよく見ると、順番がぐちゃぐちゃです。

そうなんです。listをsetにキャストしても順番が保証されません。

順番を維持するにはsortedを利用

Pythonのiterableなオブジェクトの並び替えに sorted が使えます。
どうやら、この際に key としてlistのindexを指定できれば良さそうです。

docs.python.org

listのindexを取得するには、list.index() というものが使えそうです。
つまり、最終的には以下のようにすればOKです。

 hiragana = ["あ", "い", "う", "え", "お", "あ"]
sorted(set(hiragana), key=hiragana.index)
-> ['あ', 'い', 'う', 'え', 'お']

これで重複を排除しつつlistの順番を維持できました。

Pythonで学ぶデザインパターン入門 Singletonパターン

結城先生の Java言語で学ぶデザインパターン入門 のコードを Python に書き直していくシリーズ第5段です。

Singletonパターンとは

定義した範囲内で、そのものが1つしか存在しないことを保証するパターンです。

よくある例でいうと、実行環境内でインスタンス1個以上作られないことを保証するといったことをよくやります。
具体的には、クラスAのインスタンスAを作った後に、もう一度クラスAからインスタンスを生成したらインスタンスAがそのまま返されるという形のものです。
普通なら、別のインスタンスが返りますが、シングルトンパターンの場合は絶対にそのインスタンスを1個以上存在させることはしません。

ソースコード

コードの概要

パターン説明の際に紹介した具体例を実装します。
常にインスタンスが1個しか作られないことを保証するといった動作のものです。

こういった仕組みは言語特有の機能を利用するものが多く、 Java言語で学ぶデザインパターン入門Java特有の機能を利用したものでした。
なので今回に関しては本のコードを写経するわけではなく、 Python 特有のシングルトンパターンの実装を紹介します。

ソースコード

Singleton クラス

# coding: utf-8
import threading


class Singleton(object):

    _instance = None
    _lock = threading.Lock()

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls)

        return cls._instance

    def __init__(self):
        print('initメソッドは毎回呼ばれるので注意')

Pythonでいうコンストラクタは __init__ ですが、インスタンス生成時に __new__ というメソッドが呼び出されます。
今回はそれを上書きして、クラス変数を利用して必ず1個しかインスタンスを保持しないような形にしました。
cls._instance がない場合だけ、スーパークラスにもともと存在しているインスタンス生成メソッドを呼び出す形です。
しかし、もともとのコンストラクタの __init__ は毎回呼び出されてしまうことに注意してください。

_lock = threading.Lock() は、マルチスレッドを考慮した制限になります。

実行ファイル

# coding: utf-8
from Singleton.singleton import Singleton

if __name__ == '__main__':
    s1 = Singleton()
    s2 = Singleton()

    if s1 is s2:
        print('Singleton')
    else:
        print('Not Singleton')

Singleton と表示されたらOKですね。
シングルトンパターンじゃなかったら別のインスタンスになるので Not Singleton と表示されてしまいます。

実際どう便利か

このパターンはいろいろなところで利用することが想定されるパターンです。
たとえば、お金の計算処理などは同じデータに2回も処理が入ってはいけません。
そのようなケースは実際には多々に存在します。
むしろインスタンスを1個だけに限定するパターンは紹介には使われますが実際にはそこまで利用されることはないかもしれませんね。