そうか、犬や自動車のように、処理もクラスで表せるのか!

Event

ラクス オブジェクト指向LT会 vol.3

Presented

2021/11/24 nikkie

お前、誰よ(≒自己紹介)

  • Python大好き にっきー @ftnext / @ftnext

  • Python歴4年。株式会社ユーザベースのデータサイエンティスト(NLPer)

  • Python Conference JP 2021 座長(=開催に責任を持つ人)

アニメも大好き:『アイの歌声を聴かせて』はいいぞ!🤖🎤🎼

プログラミングの題材にもしています (PyCon mini ShizuokaでLT)

オブジェクトとの近況(※オブジェクト指向ではなく JavaScriptのオブジェクト です)

LT:そうか、犬や自動車のように、処理もクラスで表せるのか!

  • クラスへの苦手意識とその払拭について 話します

  • クラスを使いこなしている感覚はまだ持てていなくて、苦手ではなくなったという共有です

お品書き:そうか、犬や自動車のように、処理もクラスで表せるのか!

  • クラスへの苦手意識

  • 払拭のヒント

  • 実践例

クラスへの苦手意識

  • 例えばPythonなどで、文法的にどう書けばいいかは分かる

  • プログラミングで どう使えばいいか が長いこと分かりませんでした

オブジェクト指向設計

「データ(属性)」と「データを操作する手続き(メソッド)」を一体化した オブジェクト を標準部品として定義し、システムを設計すること

徹底攻略 基本情報技術者教科書 平成28年度』(p.360)

オブジェクトとクラス

(前略)オブジェクトを一般化(抽象化)したものを クラス と呼び、オブジェクトを作る雛形とします。

徹底攻略 基本情報技術者教科書 平成28年度』(p.361)

オブジェクト・クラス・インスタンス

クラスから具体的な値を持たせて作ったオブジェクトを インスタンス(実体) と呼びます。

徹底攻略 基本情報技術者教科書 平成28年度』(p.361)

クラスの例:犬

犬というオブジェクトには

  • 名前がある

  • 芸を覚えさせられる

  • 覚えた芸をする

犬を表すクラスの設計

  • 名前 がある 👉 name 属性


class Dog:
    def __init__(self, name: str) -> None:
        self.name = name

犬を表すクラスの設計

  • を覚えさせられる 👉 tricks 属性。add_trick メソッドで芸を追加


class Dog:
    def __init__(self, name: str) -> None:
        self.name = name
        self.tricks = []
    def add_trick(self, trick: str) -> None:
        ...

犬を表すクラスの設計

  • 覚えた をする 👉 trick メソッドで tricks 属性から1つ取り出す


class Dog:
    def __init__(self, name: str) -> None:
        self.name = name
        self.tricks = []
    def add_trick(self, trick: str) -> None:
        ...
    def trick(self) -> str:
        ...

Dog クラス


class Dog:
    def __init__(self, name: str) -> None:
        self.name = name
        self.tricks = []  # 犬が覚えた芸をリスト(=配列)で表す

    def add_trick(self, trick: str) -> None:
        """犬に芸を覚えさせる"""
        self.tricks.append(trick)

    def trick(self) -> str:
        if not self.tricks:
            raise ValueError("覚えている芸がありません。add_trickして覚えさせてください")
        return random.choice(self.tricks)

dog.py

Dog クラスのインスタンスと戯れる


>>> dog = Dog("ラテ")
>>> dog.add_trick("寝返り")
>>> dog.add_trick("死んだふり")
>>> dog.trick()
'死んだふり'

Pythonチュートリアルを元に 作った例です

クラスの別の例:自動車

タイトルに含みますが、 @skip します(Appendixへ)

Why programmers? 犬や自動車、どんなプログラムで使うんですか?

  • 私の普段のプログラミング、犬も自動車も出てこないんですが・・😅

  • これらは クラスという文法を説明するための例

クラスを自分の道具にできていないという苦手意識

  • プログラミングの どんなシーンでどうクラスを使えばいい か(=クラスの使い所)が分からない😖

  • クラスを道具として使えないので、使ってきたPythonやPHPでは関数で書くことが多かった

お品書き:そうか、犬や自動車のように、処理もクラスで表せるのか!

  • クラスへの苦手意識

  • 払拭のヒント

  • 実践例

そうか、犬や自動車のように、処理もクラスで表せるのか!

タイトル回収!

払拭のヒント

  • クラスの使い所が分からないという苦手意識

  • 処理 もクラスで表せるという気付き!(〇〇する処理のクラス)

  • (全てがクラスであるJavaを経験していたらもっと早く気付けたかも)

何がきっかけ?

  • あんまり覚えていません・・

  • クラスの使い所が分からないと考え続けていたら閃いた💡?

  • DDDの UseCaseクラス はなるほどと思いました

実はオブジェクト指向LT会 vol.1 にて共有しています

お品書き:そうか、犬や自動車のように、処理もクラスで表せるのか!

  • クラスへの苦手意識

  • 払拭のヒント

  • 処理をクラスで表す実践例

処理をクラスで表す実践例

  • PyCon JP 2021のスタッフ活動で作った Discord Botの設計 を紹介

  • 処理を表すクラスが少しは自分の道具になったかなというタイミングで書きました

  • 設計力付けたいので、フィードバック歓迎!

Discord Bot: mogirin(もぎりん)

  • 参加者の受付(=チケットの もぎり)を担当

  • 参加者は @mogirin 1234567 のように受付番号を伝える

  • 受付が済むと参加者にRoleが付き、カンファレンスで使うチャンネルが見えるようになる

connpass受付番号(Ticket No)

../_images/202111_connpass_ticket_no.png

connpassご利用ガイド 受付表を確認する

登場人物

  • Discord:参加者用のRole

  • Googleスプレッドシート:connpassの受付番号、受付済みか

受付番号

受付済み

1234567

2345678

受付処理フロー 1/3

  • 参加者が入力した受付番号が スプレッドシートにあるか

  • ある場合は次に進む

  • ない場合はエラー送出(入力ミスや別の勉強会の受付番号と取り違えが考えられる)

受付処理フロー 2/3

  • 参加者が入力した受付番号で まだ受付されていないか

受付番号

受付済み

処理フロー

1234567

エラー送出(入力ミスが考えられる)

2345678

次に進む

受付処理フロー 3/3

  • 処理フローの1や2でエラーが送出されなければ 受付 する

    • 参加者用のRoleを付与する

    • スプレッドシートの「受付済み」のセルを更新

設計へ:もぎり処理オブジェクト

  • スプレッドシートを操作できる(処理フローで使う、値の取得、検索、書き込み)

  • DiscordのRoleを付与できる

  • もぎりメソッドでもぎり処理を実行する

もぎり処理を表すクラス TicketCollector

  • スプレッドシートを操作できる:もぎりで必要な スプレッドシート操作を表すクラスsearcher 属性に持つ


class TicketCollector:
    def __init__(self, spreadsheet_id: str) -> None:
        self.searcher = TicketSheetSearcher.from_id(spreadsheet_id)

もぎり処理を表すクラス TicketCollector

  • もぎりメソッド でもぎり処理を実行する


class TicketCollector:
    def __init__(self, spreadsheet_id: str) -> None:
        self.searcher = TicketSheetSearcher.from_id(spreadsheet_id)
    async def collect(self, ...):
        ...

もぎり処理を表すクラス TicketCollector

  • DiscordのRoleを付与できる:属性には持たせず、Role付与クラス のスタティックメソッドを呼び出した(関数でもよい)


class TicketCollector:
    def __init__(self, spreadsheet_id: str) -> None:
        self.searcher = TicketSheetSearcher.from_id(spreadsheet_id)
    async def collect(self, ...):
        ...
        await RoleAttacher.attach(member, role)
        ...

TicketCollector 実装


class TicketCollector:
    def __init__(self, spreadsheet_id: str) -> None:
        self.searcher = TicketSheetSearcher.from_id(spreadsheet_id)

    async def collect(
        self, ticket_number: str, member: discord.Member, role: discord.Role
    ) -> None:
        if not (ticket_cell := self.searcher.find_cell(ticket_number)):
            raise TicketNumberNotFound  # 処理フロー1の例外
        if self.searcher.query_already_collected(ticket_cell):
            raise TicketAlreadyCollected  # 処理フロー2の例外
        await RoleAttacher.attach(member, role)
        self.searcher.register_as_collected(ticket_cell)

TicketCollector (mogirin.py)

ボットの実装: TicketCollector インスタンスを呼び出す


collector = TicketCollector(getenv("SPREADSHEET_ID"))

@bot.event
async def on_message(message):
    # 省略
    ticket_number = find_ticket_number(message.clean_content)
    reply_message = await collect_ticket(  # collector.collectを呼び出す
        ticket_number, message.author, attendee_role
    )
    await message.channel.send(f"{message.author.mention} {reply_message}")

discordbot.py

まとめ🌯:そうか、犬や自動車のように、処理もクラスで表せるのか!

  • 犬や自動車の例でクラスの文法を掴んだら、処理をクラスで表しちゃおう! (〇〇する処理のクラス)

  • オブジェクトに必要な属性とメソッドをまず考え、それをもとにクラスを実装しています

ご清聴ありがとうございました

Enjoy development with classes!

Links & Appendixが続きます

Links

Appendix

クラスの別の例:自動車

自動車というオブジェクトは

  • 加速できる

  • 減速できる

自動車を表すクラスの設計

  • 加速 できる: speed_up メソッドで speed 属性を操作する


class Car:
    def __init__(self) -> None:
        self.speed = 0
    def speed_up(self, value: int) -> int:
        ...

自動車を表すクラスの設計

  • 減速 できる: speed_down メソッド(speed 属性を操作)


class Car:
    def __init__(self) -> None:
        self.speed = 0
    def speed_up(self, value: int) -> int:
        ...
    def speed_down(self, value: int) -> int:
        ...

Car クラス


class Car:
    def __init__(self) -> None:
        self.speed = 0

    def speed_up(self, value: int) -> int:
        self.speed += value
        return self.speed

    def speed_down(self, value: int) -> int:
        self.speed -= value
        return self.speed

car.py

Car クラスのインスタンスと戯れる


>>> car = Car()
>>> car.speed_up(60)
60
>>> car.speed_down(20)
40

スラスラわかるJava』(p.203-204)をPythonで実装した例です

EOF