見出し画像

DP.05:ひな形オブジェクトをコピーしてカスタマイズする。-Prototype-【Python】

【1】Prototypeパターン概要

Prototypeではある程度クラス変数(プロパティ)や値が設定されたオブジェクトを用意しておき、そのオブジェクトのコピーをカスタマイズしていくイメージ。

例えば、『パワポ(Microsoft PowerPoint)などでプレゼン資料を作成する際、ゼロから作成することはあまりなく、過去に作成した資料を流用して、項目を追加したり、スライドを追加したりしていく』ような感じに近い。

【2】予備知識:オブジェクトへの属性追加方法

事前に用意したオブジェクトを流用してカスタマイズしていくPrototypeパターンでは、流用したオブジェクトに対してクラス変数(プロパティ)自体を追加するという処理』が必要となる場面もある。

■例:お気に入りのウェブサイトの概要を管理するオブジェクト

class Website:
   
   # 「**kwargs」で何かしらが設定される可変長引数(キーワード)をうける
   def __init__(self, name, domain, description, author, **kwargs):
       self.name = name
       self.domain = domain
       self.description = description
       self.author = author
       print(kwargs)  # kwargsが受けつけた中身を出力

       print("----")
       # クラス変数(属性)を可変長キーワードから生成する
       for key in kwargs:
           setattr(self, key, kwargs[key])
       

   def __str__(self):
       return f'{vars(self)}' # 組み込み関数vars()でプロパティを出力

■動作確認

keywords = ('python','data','apis','automation')
site1 = Website('Python documentation',
        domain='docs.python.org',
        description='documentation for python',
        author='Python Software Foundation',
        category='Document', # 可変長引数**kwargs で受けつける部分
        keywords=keywords # 可変長引数**kwargs で受けつける部分
)

print(site1)

#出力結果例
{'category': 'Document', 'keywords': ('python', 'data', 'apis', 'automation')}
----
{'name': 'Python documentation', 'domain': 'docs.python.org', 'description': 'documentation for python', 'author': 'Python Software Foundation', 'category': 'Document', 'keywords': ('python',
'data', 'apis', 'automation')}

▲事前に定義されたクラス変数に加えて、追加のクラス変数名(category, keywords)と値を指定できている。

画像1

【補足】
オブジェクトへのクラス変数を追加には「setattr()」を使ったが詳細は以下参照。

■setattr()


■vars()
クラス変数の設定状況を確認するために「vars()」を利用している。これは、「object.__dict__」に登録されているオブジェクトを返す関数

■object.__dict__

可変長キーワード「**kwargs」部分について
簡単に言うと「事前に設定がない引数(個数は任意)を受け付けてくれるようになる

【3】例:wikipediaのinfobox

wikipedia右側に表示されることも「infobox」を題材として使う。ここに記載されている項目の一部を独自に作成したクラスオブジェクト(SiteInfoオブジェクト)に格納してみる。

画像2

■情報を格納するSiteInfoオブジェクト

class SiteInfo:
   
   # 「**kwargs」で何かしらが設定される可変長引数(キーワード)をうける
   def __init__(self, name, owner, description, **kwargs):
       self.name = name
       self.owner = owner
       self.description = description

       # クラス変数(属性)を可変長キーワードから生成する
       for key in kwargs:
           setattr(self, key, kwargs[key])
       

   def __str__(self):
       return f'{vars(self)}'

■SiteInfoオブジェクトの使用イメージ

def main():
   site_info1 = SiteInfo(name='Twitch',
                   owner='Amazon',
                   description='online video platform',
                   start_date = '2011-06-06' # 追加属性
   )
   print(site_info1)
   
   # 以降で作成したsite_info1 をコピーしてYoutube側の記載情報を持ったオブジェクトを作るなど

【4】Prototypeオブジェクトを作る

作成したSiteInfoオブジェクトをコピーし、クラス変数を追加する、等をして新しくオブジェクトを生成する。
この時、Prototypeパターンでは「Prototypeオブジェクト」を経由してオブジェクトを作成するようにプログラムを書いていく。

■Prototypeオブジェクト

import copy


# プロトタイプオブジェクト
# コピー元になるオブジェクト名を登録したり、オブジェクトコピーを返す
class Prototype:
   def __init__(self):
       self.objects = dict() # コピー元とするオブジェクト名を登録する辞書


   # 識別用の文字列とオブジェクトをつかってにdictオブジェクトに登録する
   def register(self, identifier, obj):
       self.objects[identifier] = obj

   # dictオブジェクト内にある登録情報を削除
   def unregister(self, identifier):
       del self.objects[identifier]
   
   # 識別子に紐づくオブジェクトのコピー(clone)に追加属性を設定したオブジェクトを返す
   def clone(self, identifier, **attrs):
       
       # 識別子から紐づくオブジェクトを取得する
       found = self.objects.get(identifier)
       if not found:
           raise ValueError(f'Can not find identifier : {identifier}')

       # コピーを取得(deepcopy)して、可変長引数からクラス変数を設定する
       obj = copy.deepcopy(found)
       for key in attrs:
           setattr(obj, key, attrs[key])

       return obj
 

Prototypeオブジェクトに「識別子(文字列)」と「紐づくオブジェクト」をペアにした「dict型」を保持しておく。

 「clone( )」をコールすれば、識別子に対応したオブジェクトを、可変長引数に指定した内容をクラス変数に追加したうえで返す挙動をする。

なお、コピーの時はcopyして別物として取り扱うので「deepcopy」をすることに注意する。

■利用イメージ

def main():
   site_info1 = SiteInfo(name='Twitch',
                   owner='Amazon',
                   description='online video platform',
                   start_date = '2011-06-06' # 追加属性
   )
   print(site_info1)
   
   identifier = 'Twitch-site-info-1'
   prototype = Prototype()
   prototype.register(identifier, site_info1) # Prototypeオブジェクトとして登録(識別子:Twitch-site-info-1)

   print('---------')
   # Prototypeオブジェクト経由でオブジェクトをコピー+追加カスタマイズ
   site_info2 = prototype.clone(identifier,
                           name='YouTube',
                           owner='Google',
                           #description='online video platform', # descriptionはそのまま利用
                           start_date = '2005-02-14',
                           production=('YouTube Premium','YouTube Music','YouTube TV','YouTube Kids') # 追加属性
   )
   print(site_info2)
   
   ... ...

# 出力イメージ
{'name': 'Twitch', 'owner': 'Amazon', 'description': 'online video platform', 'start_date': '2011-06-06'}
---------
{'name': 'YouTube', 'owner': 'Google', 'description': 'online video platform', 'start_date': '2005-02-14', 'production': ('YouTube Premium', 'YouTube Music', 'YouTube TV', 'YouTube Kids')}

▲YouTube用オブジェクト側では、何もしなかった「description」の値はコピー元の値がそのまま設定されている。

【5】全体コード

今回はTwitchの記載内容をPrototypeとして、それからオブジェクトコピーをしてYouTube、ツイキャスの情報をもったオブジェクトを生成してみる。

import copy

class SiteInfo:
   
   # 「**kwargs」で何かしらが設定される可変長引数(キーワード)をうける
   def __init__(self, name, owner, description, **kwargs):
       self.name = name
       self.owner = owner
       self.description = description
       #print(kwargs)

       #print("----")
       # クラス変数(属性)を可変長キーワードから生成する
       for key in kwargs:
           setattr(self, key, kwargs[key])
       

   def __str__(self):
       return f'{vars(self)}'

# プロトタイプオブジェクト
# コピー元になるオブジェクト名を登録したり、オブジェクトコピーを返す
class Prototype:
   def __init__(self):
       self.objects = dict() # コピー元とするオブジェクト名を登録する辞書


   # 識別用の文字列とオブジェクトをつかってにdictオブジェクトに登録する
   def register(self, identifier, obj):
       self.objects[identifier] = obj

   # dictオブジェクト内にある登録情報を削除
   def unregister(self, identifier):
       del self.objects[identifier]
   
   # 識別子に紐づくオブジェクトのコピー(clone)に追加属性を設定したオブジェクトを返す
   def clone(self, identifier, **attrs):
       
       # 識別子から紐づくオブジェクトを取得する
       found = self.objects.get(identifier)
       if not found:
           raise ValueError(f'Can not find identifier : {identifier}')

       # コピーを取得(deepcopy)して、可変長引数からクラス変数を設定する
       obj = copy.deepcopy(found)
       for key in attrs:
           setattr(obj, key, attrs[key])

       return obj

def main():
   site_info1 = SiteInfo(name='Twitch',
                   owner='Amazon',
                   description='online video platform',
                   start_date = '2011-06-06' # 追加属性
   )
   print(site_info1)
   
   identifier = 'Twitch-site-info-1'
   prototype = Prototype()
   prototype.register(identifier, site_info1) # Prototypeオブジェクトとして登録(識別子:Twitch-site-info-1)

   print('---------')
   # Prototypeオブジェクト経由でオブジェクトをコピー+追加カスタマイズ
   site_info2 = prototype.clone(identifier,
                           name='YouTube',
                           owner='Google',
                           #description='online video platform', # descriptionはそのまま利用
                           start_date = '2005-02-14',
                           production=('YouTube Premium','YouTube Music','YouTube TV','YouTube Kids') # 追加属性
   )
   print(site_info2)


   print('---------')

   # ツイキャスのデータも作ってみる
   site_info3 = prototype.clone(identifier,
                               name='TwitCasting',
                               owner='Moi Corporation',
                               description='First, TwitCasting Live for iPhone was released ',
                               start_date = '2010-02-03'
   )
   print(site_info3)


if __name__ == '__main__':
   main()

#出力結果例
{'name': 'Twitch', 'owner': 'Amazon', 'description': 'online video platform', 'start_date': '2011-06-06'}
---------
{'name': 'YouTube', 'owner': 'Google', 'description': 'online video platform', 'start_date': '2005-02-14', 'production': ('YouTube Premium', 'YouTube Music', 'YouTube TV', 'YouTube Kids')}
---------
{'name': 'TwitCasting', 'owner': 'Moi Corporation', 'description': 'First, TwitCasting Live for iPhone was released ', 'start_date': '2010-02-03'}

画像3

Prototypeオブジェクト経由でオブジェクトをコピー・カスタマイズして新しいオブジェクトを作成すれば、オブジェクトをいちから作るよりは楽になる。

もっと応援したいなと思っていただけた場合、よろしければサポートをおねがいします。いただいたサポートは活動費に使わせていただきます。