見出し画像

Snowflakeでデザインパターン実装してみた

分析屋の中田(ナカタ)です。
Snowflakeでデザインパターンの実装をしてみました。


デザインパターンとは

プログラミングにおける、クラス設計の典型パターン集です。
こんなクラスを作るといいんじゃない?という設計のテンプレートです。
GoFのデザインパターン23種類が有名です。

今回やること

SnowflakeのPythonワークシートで、GoFデザインパターンの1つ「Iterator」パターンを実装します。
Snowflake独自の機能の話ではないので、メインテーマはあくまでデザインパターンです。

前提

オブジェクト指向の基本用語は知っている前提です。
説明すると長くなるので省略します。
(実際書こうとしたんですが、長い記事になって読みづらくなりそうなのでやめました)

Python初学者でも、精神力に自信のある方はググりながら読み進めてください。

Iteratorパターン

読み方は「イテレータ」
繰り返し処理で使える(走査できる)集合体を作ります。
「わざわざ作らんでもリストがあるやないか」と言えばそうなのですが
標準で用意されているリストのメソッド(appendとか)に機能追加したい場合や、
独自の走査ロジックにしたい場合などに対応できません。
また、特定のクラスに依存した仕様ではないため、他のクラスにも再利用できます。

Iteratorパターンは以下の4つのクラスで構成されています。
Aggregate(アグリゲート):集合体を表すインターフェースです。
ConcreteAggregate(コンクリートアグリゲート):Aggregateを継承した、集合体のクラスです。
Iterator(イテレータ):逐次処理を行うインターフェースです。
ConcreteIterator(コンクリートイテレータ):Iteratorを継承した、逐次処理を行うクラスです。

クラス図

diagrams.net(draw.io)でお絵描きしました。
メソッドは見やすさのために省略しました。
決してさぼったわけではありません。

左下のBookクラスは、Iteratorパターンを構成するクラスとは関係ありません。
今回は本(Bookクラス)を何冊か本棚(BookShelfクラス)に格納して、本棚の中を走査するという題材です。
Iteratorパターンのサンプルコードと言えばこの本棚の例がよく出てきます。

この段階では「うん・・・う~ん・・・?」という気持ちだと思うので
実際のコードを読んでから、クラス図を見返すといいかもしれません。

実装環境

Snowflakeのエディション:エンタープライズ版
クラウド:AWS(東京リージョン)

事前準備

実装するためのPythonワークシートを作成します。
Snowflakeにログインして、「ワークシート」画面の右上に注目です。
以下の画像の通り、青い「+」マークのボタンがあるのでクリックします。
プルダウンメニューの「Pythonワークシート」をクリックします。

また、Pythonワークシートの以下の画面で戻り値の型をTable()以外にしておきます。
Table()のままだと、表形式の戻り値を用意しないとエラーになるためです。

①コード全体

いきなりですが、今回の完成コードは以下の通りです。

from collections.abc import Iterable, Iterator

class Book:
  def __init__(self, name):
    self.__name = name

  def get_name(self):
    return self.__name

class BookShelf(Iterable):
  def __init__(self):
    self.__books = []

  def append_book(self, book: Book):
    self.__books.append(book)

  def get_book_at(self, index):
    return self.__books[index]

  def __iter__(self):
    return BookShelfIterator(self)
  
  def get_iterator(self):
    return BookShelfIterator(self)
    
class BookShelfIterator(Iterator):
  def __init__(self, book_shelf: BookShelf):
    self.__book_shelf = book_shelf
    self.__index = 0

  def __next__(self):
    try:
      print(f'try_index:{self.__index}')
      book = self.__book_shelf.get_book_at(self.__index)
      self.__index += 1
    except IndexError:
      raise StopIteration() 
    return book

def main(dummy):
    book_shelf = BookShelf()
    book_shelf.append_book(Book('Python_1'))
    book_shelf.append_book(Book('Python_2'))
    book_shelf.append_book(Book('Python_3'))
    book_shelf.append_book(Book('Python_4'))

    for book in book_shelf:
        print(book.get_name())

    book_iterator = book_shelf.get_iterator()
    print(next(book_iterator).get_name())

Iteratorパターンはクラスを4つ(Bookを含めると5つ)作ると書いておきながら
よく見ると上記のコード内にはクラスが3つしかありません。
以降で説明します。

②コード読解

コードをそれぞれのブロックに分割して、ざっくり何をしたいのか書いていきます。

from collections.abc import Iterable, Iterator

Pythonではcollectionsのabcモジュールを使用することで
インターフェース2つ(AggregateとIterator)の実装を省略できます。
よって、実装はクラス3つだけで済みます。

IterableがAggregateクラスに対応しています。

class Book:
  def __init__(self, name):
    self.__name = name

  def get_name(self):
    return self.__name

Bookクラスはname属性を持っており
get_nameメソッドでそのnameを返します。
今回のIteratorパターンの練習用に作ったクラスです。

class BookShelf(Iterable):
  def __init__(self):
    self.__books = []

  def append_book(self, book: Book):
    self.__books.append(book)

  def get_book_at(self, index):
    return self.__books[index]

  def __iter__(self):
    return BookShelfIterator(self)
  
  def get_iterator(self):
    return BookShelfIterator(self)


Iterableを継承したクラス(ConcreteAggregate)を作っています。 
__booksというリストを持っており、リストに本を追加したり逐次処理を呼び出すメソッドを持たせたりしています。

class BookShelfIterator(Iterator):
  def __init__(self, book_shelf: BookShelf):
    self.__book_shelf = book_shelf
    self.__index = 0

  def __next__(self):
    try:
      print(f'try_index:{self.__index}')
      book = self.__book_shelf.get_book_at(self.__index)
      self.__index += 1
    except IndexError:
      raise StopIteration() 
    return book

逐次処理を実行するクラスです。
集合体(BookShelf)側で逐次処理の仕組みを持たせないことで
逐次処理の汎用的なクラス(=再利用性が高い)になっています。

インデックス番号0番を初期値として
繰り返し処理の中でprint文で簡易的にログの掃き出しとインデックス番号のインクリメントを行っています。
また、リストの最大インデックス番号を超える場合はIndexErrorが発生するため
繰り返し処理を停止させるエラーハンドリングを実装しています。

def main(dummy):
    book_shelf = BookShelf()
    book_shelf.append_book(Book('Python_1'))
    book_shelf.append_book(Book('Python_2'))
    book_shelf.append_book(Book('Python_3'))
    book_shelf.append_book(Book('Python_4'))

    for book in book_shelf:
        print(book.get_name())

    book_iterator = book_shelf.get_iterator()
    print(next(book_iterator).get_name())

Pythonワークシートではハンドラー関数(絶対に実行される関数)を指定する必要があります。
デフォルトではmain関数がハンドラー関数となっていますので、そのまま利用しています。

引数は省略不可なので、適当に「dummy」という引数を用意していますが使っていません。
やっていることとしては

  • 本を4冊、本棚に入れる

  • ループで本棚の中身を全部画面表示する

  • 本棚の1冊目だけ取り出してみる

です。

以下の1文だけ実行を繰り返すと、次々に本が取り出されていきます。

print(next(book_iterator).get_name())

インデックス番号の最大値を超えたらエラーになります。

③実行結果

BookShelfIteratorの

print(f'try_index:{self.__index}')

のf文字列で表示させたログが繰り返し処理の都度表示されています。

最後の2行、
try_index:0
Python_1
については

    book_iterator = book_shelf.get_iterator()
    print(next(book_iterator).get_name())

を実行した結果です。

最後に

Python初学者でググりながらここまで読み切った方、その勉強体力は一生ものです。



ここまでお読みいただき、ありがとうございました!
この記事が少しでも参考になりましたら「スキ」を押していただけると幸いです!

これまでの記事はこちら!

株式会社分析屋について

弊社が作成を行いました分析レポートを、鎌倉市観光協会様HPに掲載いただきました。

ホームページはこちら。

noteでの会社紹介記事はこちら。

【データ分析で日本を豊かに】
分析屋はシステム分野・ライフサイエンス分野・マーケティング分野の知見を生かし、多種多様な分野の企業様のデータ分析のご支援をさせていただいております。 「あなたの問題解決をする」をモットーに、お客様の抱える課題にあわせた解析・分析手法を用いて、問題解決へのお手伝いをいたします!

【マーケティング】
マーケティング戦略上の目的に向けて、各種のデータ統合及び加工ならびにPDCAサイクル運用全般を支援や高度なデータ分析技術により複雑な課題解決に向けての分析サービスを提供いたします。

【システム】
アプリケーション開発やデータベース構築、WEBサイト構築、運用保守業務などお客様の問題やご要望に沿ってご支援いたします。

【ライフサイエンス】
機械学習や各種アルゴリズムなどの解析アルゴリズム開発サービスを提供いたします。過去には医療系のバイタルデータを扱った解析が主でしたが、今後はそれらで培った経験・技術を工業など他の分野の企業様の問題解決にも役立てていく方針です。

【SES】
SESサービスも行っております。